Problem Statement
The output modules negate the wind axes x and z force components so they read as induced drag and lift, and that sign rule is written out at 14 sites across the three output modules: twice in _output_rendering.get_scalars, twice each in plot_results_versus_time's forces and force coefficients figures, four times in its loads CSV assembly, and four times in log_results's value column. Every one of those sites re-derives a named physical quantity from a raw component vector, which is knowledge that belongs with the loads rather than with the code that displays them.
The scalar type vocabulary has the same problem at a smaller scale: the strings "induced drag", "side force", and "lift" are spelled out in eight places, namely the identical validation tuples in draw and animate, a third membership test in draw, the three branches in get_scalars, and four docstrings.
Location(s): pterasoftware/output.py, pterasoftware/_output_rendering.py, pterasoftware/geometry/airplane.py, pterasoftware/_panel.py
Proposed Solution
The fix has to reach two levels, because an Airplane-only fix would leave get_scalars untouched:
Airplane already carries forces_W and forceCoefficients_W, so it gains named forces and named force coefficients. This covers the 12 sites in plot_results_versus_time and log_results. These attributes are additions to the public API.
Panel gains named forces only. A coefficient needs the freestream dynamic pressure, which is a per-step quantity that reaches get_scalars as an argument rather than living on the Panel, so Panel cannot gain coefficient attributes. get_scalars keeps its division while losing its negation, reading, for example, this_panel.inducedDrag_W / qInf__E / this_panel.area. Because a Panel exists per mesh cell, the new attributes should be computed properties following the lazy caching pattern the class already uses rather than new slots. Unlike the Airplane additions, these are not additions to the public API: Panel is a private class, and while its instances are technically reachable through public attributes such as Wing.panels, that reachability is not by design, so the new properties carry no stability promise.
- Fold the scalar type vocabulary and the sign rule into one table in
_output_rendering.py, so the validation tuples, the membership tests, the get_scalars branches, and the docstrings all reference a single source.
- Define the named wind axes force components in
docs/AXES_POINTS_AND_FRAMES.md, so the mapping from forces_W to induced drag, side force, and lift, including the sign convention, is documented with the rest of the axes, points, and frames definitions rather than living only in code.
Additional Context
Nothing outside the output modules re-derives a named load quantity by flipping a sign, so the change is confined to the two loads classes and the output modules.
Problem Statement
The output modules negate the wind axes x and z force components so they read as induced drag and lift, and that sign rule is written out at 14 sites across the three output modules: twice in
_output_rendering.get_scalars, twice each inplot_results_versus_time's forces and force coefficients figures, four times in its loads CSV assembly, and four times inlog_results's value column. Every one of those sites re-derives a named physical quantity from a raw component vector, which is knowledge that belongs with the loads rather than with the code that displays them.The scalar type vocabulary has the same problem at a smaller scale: the strings
"induced drag","side force", and"lift"are spelled out in eight places, namely the identical validation tuples indrawandanimate, a third membership test indraw, the three branches inget_scalars, and four docstrings.Location(s):
pterasoftware/output.py,pterasoftware/_output_rendering.py,pterasoftware/geometry/airplane.py,pterasoftware/_panel.pyProposed Solution
The fix has to reach two levels, because an
Airplane-only fix would leaveget_scalarsuntouched:Airplanealready carriesforces_WandforceCoefficients_W, so it gains named forces and named force coefficients. This covers the 12 sites inplot_results_versus_timeandlog_results. These attributes are additions to the public API.Panelgains named forces only. A coefficient needs the freestream dynamic pressure, which is a per-step quantity that reachesget_scalarsas an argument rather than living on thePanel, soPanelcannot gain coefficient attributes.get_scalarskeeps its division while losing its negation, reading, for example,this_panel.inducedDrag_W / qInf__E / this_panel.area. Because aPanelexists per mesh cell, the new attributes should be computed properties following the lazy caching pattern the class already uses rather than new slots. Unlike theAirplaneadditions, these are not additions to the public API:Panelis a private class, and while its instances are technically reachable through public attributes such asWing.panels, that reachability is not by design, so the new properties carry no stability promise._output_rendering.py, so the validation tuples, the membership tests, theget_scalarsbranches, and the docstrings all reference a single source.docs/AXES_POINTS_AND_FRAMES.md, so the mapping fromforces_Wto induced drag, side force, and lift, including the sign convention, is documented with the rest of the axes, points, and frames definitions rather than living only in code.Additional Context
Nothing outside the output modules re-derives a named load quantity by flipping a sign, so the change is confined to the two loads classes and the output modules.