Skip to content

Comments

Two-dof refactor part 3: Added a new ODE/Phase for a simple cruise that is more consistent with the other phases.#986

Open
Kenneth-T-Moore wants to merge 35 commits intoOpenMDAO:mainfrom
Kenneth-T-Moore:2dof_3
Open

Two-dof refactor part 3: Added a new ODE/Phase for a simple cruise that is more consistent with the other phases.#986
Kenneth-T-Moore wants to merge 35 commits intoOpenMDAO:mainfrom
Kenneth-T-Moore:2dof_3

Conversation

@Kenneth-T-Moore
Copy link
Member

Summary

Motivation: The original Cruise phase in the two-dof equations of motion is based on the Breguet Range equations, and uses an AnalyticPhase from Dymos, and additionally integrates over mass instead of time. This causes problems when trying to add an external subsystem with a state that needs to be integrated over time. This PR adds an alternative that eliminates these problems.

Added a new SimpleCruise eom, ode, and phase to the two-dof formulation.

  • Has 1 dynamic state: mass
  • Integrates in time, so initial time and duration are also controlled by the optimizer.
  • Has no controls: throttle is solved, and altitude and velocity are fixed. Distance is a simple calculation from those.

Added a new option "phase_builder" to the phase_options to allow the user to specify what phase_builder to use for this phase. The following choices are valid:

    DEFAULT: Use the default phase builder for this EquationsOfMotion.

    BREGUET_RANGE: Use a phase builder that implements the Breguet Range equations.

    SIMPLE_CRUISE: Use a phase builder that implements a single DOF (mass) cruise.

Presently, this option only works for the two-dof equations of motion, but there is a potential to expand the capability to use these phases in other equations.

  • The old Cruise ode/eom/phase have all been renamed to include Breguet in their names.
  • All tests and benchmarks that used the old Cruise phase have been switched over to the new SimpleCruisePhase.
  • One new benchmark has been added for the BreguetCruisePhase

Related Issues

  • Resolves #

Backwards incompatibilities

None

New Dependencies

None

for subsystem in subsystems:
# check if subsystem_options has entry for a subsystem of this name
if subsystem.name in subsystem_options:
kwargs.update(subsystem_options[subsystem.name])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to update how we handle the kwargs. Some things like num_nodes, aviary_options could be provided directly as arguments to build_mission(), and we can check for AerodynamicsBuilders before getting the subsystem-specific kwargs to make sure 'method' and 'output_alpha' are defaulted to our desired values.

This prevents kwargs from getting more cluttered with irrelevant options as we loop through subsystems and makes sure the desired aero defaults don't get overwritten if an earlier subsystem happens to use an identically named option

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This applies to all the 2DOF ODEs but should be able to be quickly copy-pasted to them all

Comment on lines 38 to 63
kwargs = {
'num_nodes': nn,
'aviary_inputs': aviary_options,
'method': 'cruise',
'output_alpha': True,
}
for subsystem in subsystems:
# check if subsystem_options has entry for a subsystem of this name
if subsystem.name in subsystem_options:
kwargs.update(subsystem_options[subsystem.name])
system = subsystem.build_mission(**kwargs)
if system is not None:
if isinstance(subsystem, PropulsionBuilder):
prop_group.add_subsystem(
subsystem.name,
system,
promotes_inputs=subsystem.mission_inputs(**kwargs),
promotes_outputs=subsystem.mission_outputs(**kwargs),
)
else:
self.add_subsystem(
subsystem.name,
system,
promotes_inputs=subsystem.mission_inputs(**kwargs),
promotes_outputs=subsystem.mission_outputs(**kwargs),
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took a quick crack at what a revised kwargs handling could look like

Suggested change
kwargs = {
'num_nodes': nn,
'aviary_inputs': aviary_options,
'method': 'cruise',
'output_alpha': True,
}
for subsystem in subsystems:
# check if subsystem_options has entry for a subsystem of this name
if subsystem.name in subsystem_options:
kwargs.update(subsystem_options[subsystem.name])
system = subsystem.build_mission(**kwargs)
if system is not None:
if isinstance(subsystem, PropulsionBuilder):
prop_group.add_subsystem(
subsystem.name,
system,
promotes_inputs=subsystem.mission_inputs(**kwargs),
promotes_outputs=subsystem.mission_outputs(**kwargs),
)
else:
self.add_subsystem(
subsystem.name,
system,
promotes_inputs=subsystem.mission_inputs(**kwargs),
promotes_outputs=subsystem.mission_outputs(**kwargs),
)
for subsystem in subsystems:
kwargs = {}
# check if subsystem_options has entry for a subsystem of this name
if subsystem.name in subsystem_options:
kwargs = subsystem_options[subsystem.name]
if isinstance(subsystem, AerodynamicsBuilder):
# set default options for Aero if not specified by user
base_kwargs = {
'method': 'cruise',
'output_alpha': True
}
kwargs = base_kwargs.update(kwargs)
system = subsystem.build_mission(num_nodes=nn, aviary_inputs=aviary_options, **kwargs)
if system is not None:
if isinstance(subsystem, PropulsionBuilder):
prop_group.add_subsystem(
subsystem.name,
system,
promotes_inputs=subsystem.mission_inputs(**kwargs),
promotes_outputs=subsystem.mission_outputs(**kwargs),
)
else:
self.add_subsystem(
subsystem.name,
system,
promotes_inputs=subsystem.mission_inputs(**kwargs),
promotes_outputs=subsystem.mission_outputs(**kwargs),
)

Copy link
Member Author

@Kenneth-T-Moore Kenneth-T-Moore Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. This should be easy to clean up.

EDIT - Updated Simple Cruise, Breguet Cruise, and Flight. The other ones got deleted and replaced in part 4, so I will continue this there.

Comment on lines 96 to 101
is_analytic_phase=True,
):
if is_analytic_phase is not True:
msg = 'The Breguet Cruise phase does not support dynamic variables in its subsystems.'
raise AttributeError(msg)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why even provide the option to try making Breguet cruise not analytic?
Maybe something I should have noticed earlier... does this ever need to be defined when initializing any PhaseBuilder? The user will typically never be able to toggle this without breaking everything

Copy link
Member Author

@Kenneth-T-Moore Kenneth-T-Moore Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The is_analytic_phase gets toggled to False if one of the subsystems has a dynamic phase. I don't think the user can ever set this flag. I could probably rename it to something like _subsystems_have states.

EDIT - I am going to rework this, and check the subsystems directly in Breguet Range instead of in the base class.

@Kenneth-T-Moore
Copy link
Member Author

Ok, rework done. Much cleaner now. Also added a test for that error message.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants