├── dymos ├── test │ ├── __init__.py │ ├── test_pep8.py │ └── test_options.py ├── utils │ ├── __init__.py │ ├── test │ │ ├── __init__.py │ │ ├── test_misc.py │ │ ├── test_lg.py │ │ ├── test_indexing.py │ │ ├── test_lgl.py │ │ ├── test_lgr.py │ │ └── test_hermite.py │ ├── constants.py │ ├── indexing.py │ └── lagrange.py ├── examples │ ├── __init__.py │ ├── ssto │ │ ├── __init__.py │ │ ├── doc │ │ │ └── __init__.py │ │ └── test │ │ │ └── __init__.py │ ├── cannonball │ │ ├── __init__.py │ │ ├── doc │ │ │ └── __init__.py │ │ ├── test │ │ │ └── __init__.py │ │ ├── size_comp.py │ │ └── cannonball_ode.py │ ├── oscillator │ │ ├── __init__.py │ │ └── doc │ │ │ ├── __init__.py │ │ │ └── oscillator_ode.py │ ├── racecar │ │ ├── __init__.py │ │ ├── doc │ │ │ └── __init__.py │ │ ├── Track.py │ │ ├── timeAdder.py │ │ ├── linewidthhelper.py │ │ └── curvature.py │ ├── robot_arm │ │ ├── __init__.py │ │ ├── doc │ │ │ └── __init__.py │ │ └── test │ │ │ └── __init__.py │ ├── vanderpol │ │ ├── __init__.py │ │ ├── doc │ │ │ └── __init__.py │ │ └── test │ │ │ └── __init__.py │ ├── balanced_field │ │ ├── __init__.py │ │ ├── doc │ │ │ └── __init__.py │ │ └── test │ │ │ └── __init__.py │ ├── flying_robot │ │ ├── __init__.py │ │ ├── test │ │ │ └── __init__.py │ │ └── flying_robot_ode.py │ ├── min_time_climb │ │ ├── __init__.py │ │ ├── doc │ │ │ ├── __init__.py │ │ │ ├── dynamic_pressure_comp_partial_coloring.py │ │ │ └── min_time_climb_ode_partial_coloring.py │ │ ├── test │ │ │ └── __init__.py │ │ ├── aero │ │ │ ├── test │ │ │ │ ├── __init__.py │ │ │ │ ├── test_kappa_comp.py │ │ │ │ ├── test_cd0_comp.py │ │ │ │ ├── test_cla_comp.py │ │ │ │ └── test_aerodynamics.py │ │ │ ├── __init__.py │ │ │ ├── dynamic_pressure_comp.py │ │ │ ├── cl_comp.py │ │ │ ├── mach_comp.py │ │ │ ├── cd0_comp.py │ │ │ ├── cd_comp.py │ │ │ ├── cla_comp.py │ │ │ ├── kappa_comp.py │ │ │ └── lift_drag_force_comp.py │ │ ├── prop │ │ │ ├── test │ │ │ │ ├── __init__.py │ │ │ │ └── test_max_thrust_comp.py │ │ │ ├── __init__.py │ │ │ ├── thrust_comp.py │ │ │ ├── mdot_comp.py │ │ │ └── prop.py │ │ └── min_time_climb_ode.py │ ├── shuttle_reentry │ │ ├── __init__.py │ │ ├── doc │ │ │ └── __init__.py │ │ ├── test │ │ │ └── __init__.py │ │ ├── atmosphere_comp.py │ │ ├── shuttle_ode.py │ │ └── heating_comp.py │ ├── water_rocket │ │ ├── __init__.py │ │ ├── doc │ │ │ └── __init__.py │ │ └── test │ │ │ └── __init__.py │ ├── battery_multibranch │ │ ├── __init__.py │ │ ├── doc │ │ │ └── __init__.py │ │ ├── test │ │ │ └── __init__.py │ │ ├── battery_multibranch_ode.py │ │ └── motors.py │ ├── brachistochrone │ │ ├── doc │ │ │ ├── __init__.py │ │ │ └── brachistochrone_ode.py │ │ ├── test │ │ │ ├── __init__.py │ │ │ ├── test_brachistochrone_vector_states_ode.py │ │ │ └── test_brachistochrone_implicit_recorder.py │ │ ├── __init__.py │ │ └── brachistochrone_vector_states_ode.py │ ├── double_integrator │ │ ├── __init__.py │ │ ├── doc │ │ │ └── __init__.py │ │ ├── test │ │ │ └── __init__.py │ │ └── double_integrator_ode.py │ ├── hyper_sensitive │ │ ├── doc │ │ │ └── __init__.py │ │ ├── test │ │ │ └── __init__.py │ │ ├── __init__.py │ │ └── hyper_sensitive_ode.py │ ├── aircraft_steady_flight │ │ ├── __init__.py │ │ ├── doc │ │ │ └── __init__.py │ │ ├── aero │ │ │ ├── __init__.py │ │ │ ├── test │ │ │ │ └── __init__.py │ │ │ ├── data │ │ │ │ └── CRM_aero_inputs.dat │ │ │ ├── aero_coef_comp.py │ │ │ ├── aero_forces_comp.py │ │ │ └── aerodynamics_group.py │ │ ├── test │ │ │ ├── __init__.py │ │ │ └── test_aircraft_ode.py │ │ ├── propulsion │ │ │ ├── __init__.py │ │ │ ├── test │ │ │ │ ├── __init__.py │ │ │ │ └── test_propulsion_group.py │ │ │ ├── throttle_comp.py │ │ │ ├── fuel_burn_rate_comp.py │ │ │ ├── tsfc_comp.py │ │ │ ├── max_thrust_comp.py │ │ │ ├── thrust_comp.py │ │ │ └── propulsion_group.py │ │ ├── flight_equlibrium │ │ │ ├── __init__.py │ │ │ ├── test │ │ │ │ └── __init__.py │ │ │ ├── lift_equilibrium_comp.py │ │ │ └── steady_flight_equilibrium_group.py │ │ ├── true_airspeed_comp.py │ │ ├── dynamic_pressure_comp.py │ │ ├── range_rate_comp.py │ │ ├── steady_flight_path_angle_comp.py │ │ └── mass_comp.py │ ├── finite_burn_orbit_raise │ │ ├── __init__.py │ │ ├── doc │ │ │ └── __init__.py │ │ └── test │ │ │ ├── __init__.py │ │ │ └── test_finite_burn_eom.py │ ├── length_constrained_brachistochrone │ │ ├── __init__.py │ │ ├── doc │ │ │ └── __init__.py │ │ └── arc_length_comp.py │ └── plotting.py ├── models │ ├── __init__.py │ ├── eom │ │ ├── test │ │ │ └── __init__.py │ │ └── __init__.py │ └── atmosphere │ │ ├── test │ │ └── __init__.py │ │ └── __init__.py ├── phase │ ├── test │ │ ├── __init__.py │ │ └── test_add_parameter_types.py │ └── __init__.py ├── grid_refinement │ ├── __init__.py │ ├── hp_adaptive │ │ └── __init__.py │ ├── ph_adaptive │ │ └── __init__.py │ └── refinement.py ├── trajectory │ ├── test │ │ └── __init__.py │ └── __init__.py ├── visualization │ └── __init__.py ├── transcriptions │ ├── solve_ivp │ │ ├── __init__.py │ │ └── components │ │ │ ├── test │ │ │ └── __init__.py │ │ │ ├── __init__.py │ │ │ └── state_rate_collector_comp.py │ ├── common │ │ ├── test │ │ │ └── __init__.py │ │ └── __init__.py │ ├── pseudospectral │ │ ├── __init__.py │ │ ├── test │ │ │ └── __init__.py │ │ └── components │ │ │ ├── test │ │ │ ├── __init__.py │ │ │ └── test_control_endpoint_defect_comp.py │ │ │ └── __init__.py │ └── __init__.py ├── __init__.py └── options.py ├── .coveralls.yml ├── joss ├── brach_plots.png ├── cannonball_hr.png ├── brachistochone_yx.png ├── cannonball_states_h.png ├── cannonball_states_r.png ├── flow_charts │ ├── opt_control.pdf │ ├── opt_control.png │ ├── coupled_co_design.pdf │ ├── coupled_co_design.png │ ├── opt_control_edited.pdf │ ├── sequential_co_design.pdf │ ├── sequential_co_design.png │ ├── coupled_co_design_edited.pdf │ ├── sequential_co_design_edited.pdf │ └── opt_control.py ├── brachistochrone_states_x.png └── brachistochrone_states_y.png ├── travis_deploy_rsa.enc ├── brachistochroneSolution.png ├── mkdocs └── docs │ ├── dymos_logo.png │ ├── faq │ ├── images │ │ ├── n2_full_model.png │ │ └── n2_rhs_disc.png │ ├── add_ode_output_to_timeseries.md │ ├── downstream_analysis.md │ ├── connect_scalar_parameters_to_ode.md │ └── debugging.md │ ├── examples │ ├── ssto_earth │ │ ├── ssto_fbd.png │ │ ├── ssto_xdsm.png │ │ └── ssto_earth.md │ ├── racecar │ │ └── vehicle_position_states.png │ ├── min_time_climb │ │ ├── min_time_climb_fbd.png │ │ └── min_time_climb.md │ ├── ssto_moon_linear_tangent │ │ ├── ssto_linear_tangent_xdsm.png │ │ └── ssto_moon_linear_tangent.md │ ├── examples.md │ ├── brachistochrone │ │ ├── brachistochrone_upstream_controls.md │ │ ├── brachistochrone_upstream_states.md │ │ └── scripts │ │ │ └── brachistochrone_fbd.py │ ├── ssto_moon_polynomial_controls │ │ └── ssto_moon_polynomial_controls.md │ ├── double_integrator │ │ └── double_integrator.md │ ├── hypersensitive │ │ └── hypersensitive.md │ ├── length_constrained_brachistochrone │ │ └── length_constrained_brachistochrone.md │ ├── finite_burn_orbit_raise │ │ └── finite_burn_orbit_raise.md │ └── multibranch_trajectory │ │ └── multibranch_trajectory.md │ ├── features │ ├── figures │ │ ├── sparsity_plot.png │ │ ├── timeseries_plot.png │ │ └── sparsity_plot_solve_segments.png │ ├── plotting.md │ └── phases │ │ ├── segments.md │ │ └── objective.md │ ├── getting_started │ ├── scripts │ │ ├── lgl_animation_0.png │ │ ├── lgl_animation_1.png │ │ ├── lgl_animation_2.png │ │ ├── lgl_animation_3.png │ │ ├── lgl_solution_0.png │ │ ├── lgl_solution_1.png │ │ ├── lgl_solution_2.png │ │ ├── lgl_solution_3.png │ │ ├── lgl_solution_4.png │ │ ├── lgl_solution_5.png │ │ ├── lgr_animation_0.png │ │ ├── lgr_animation_1.png │ │ ├── lgr_animation_2.png │ │ ├── lgr_animation_3.png │ │ ├── lgr_solution_0.png │ │ ├── lgr_solution_1.png │ │ ├── lgr_solution_2.png │ │ ├── lgr_solution_3.png │ │ ├── lgr_solution_4.png │ │ └── lgr_solution_5.png │ └── intro_to_dymos │ │ └── figures │ │ └── spring_mass_damper.png │ ├── api │ ├── trajectory_api.md │ ├── phase_api.md │ └── run_problem.md │ └── release_notes │ └── version_0.17.0.md ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── workflows │ └── draft-pdf.yml └── ISSUE_TEMPLATE.md ├── .bumpversion.cfg ├── .gitignore └── setup.py /dymos/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/models/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/phase/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/utils/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/ssto/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/grid_refinement/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/models/eom/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/trajectory/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/visualization/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/cannonball/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/oscillator/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/racecar/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/racecar/doc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/robot_arm/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/ssto/doc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/ssto/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/vanderpol/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/balanced_field/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/cannonball/doc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/cannonball/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/flying_robot/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/min_time_climb/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/oscillator/doc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/robot_arm/doc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/robot_arm/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/shuttle_reentry/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/vanderpol/doc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/vanderpol/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/water_rocket/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/water_rocket/doc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/models/atmosphere/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/transcriptions/solve_ivp/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/balanced_field/doc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/balanced_field/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/battery_multibranch/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/brachistochrone/doc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/brachistochrone/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/double_integrator/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/double_integrator/doc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/flying_robot/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/hyper_sensitive/doc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/hyper_sensitive/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/min_time_climb/doc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/min_time_climb/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/shuttle_reentry/doc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/shuttle_reentry/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/water_rocket/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/grid_refinement/hp_adaptive/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/grid_refinement/ph_adaptive/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/transcriptions/common/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/transcriptions/pseudospectral/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/aircraft_steady_flight/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/aircraft_steady_flight/doc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/battery_multibranch/doc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/battery_multibranch/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/double_integrator/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/finite_burn_orbit_raise/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/min_time_climb/aero/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/min_time_climb/prop/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/phase/__init__.py: -------------------------------------------------------------------------------- 1 | from .phase import Phase 2 | -------------------------------------------------------------------------------- /dymos/transcriptions/pseudospectral/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/aircraft_steady_flight/aero/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/aircraft_steady_flight/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/finite_burn_orbit_raise/doc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/finite_burn_orbit_raise/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/transcriptions/solve_ivp/components/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/aircraft_steady_flight/aero/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/aircraft_steady_flight/propulsion/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/length_constrained_brachistochrone/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/transcriptions/pseudospectral/components/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/aircraft_steady_flight/flight_equlibrium/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/aircraft_steady_flight/propulsion/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/length_constrained_brachistochrone/doc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/trajectory/__init__.py: -------------------------------------------------------------------------------- 1 | from .trajectory import Trajectory 2 | -------------------------------------------------------------------------------- /dymos/examples/aircraft_steady_flight/flight_equlibrium/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dymos/examples/min_time_climb/aero/__init__.py: -------------------------------------------------------------------------------- 1 | from .aero import AeroGroup 2 | -------------------------------------------------------------------------------- /dymos/models/atmosphere/__init__.py: -------------------------------------------------------------------------------- 1 | from .atmos_1976 import USatm1976Comp 2 | -------------------------------------------------------------------------------- /dymos/models/eom/__init__.py: -------------------------------------------------------------------------------- 1 | from .flight_path_eom_2d import FlightPathEOM2D 2 | -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-pro 2 | repo_token: qLaSQi30o7m3dbSRDEPRdiKY0yu32OH2d 3 | -------------------------------------------------------------------------------- /joss/brach_plots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/joss/brach_plots.png -------------------------------------------------------------------------------- /dymos/examples/hyper_sensitive/__init__.py: -------------------------------------------------------------------------------- 1 | from .hyper_sensitive_ode import HyperSensitiveODE 2 | -------------------------------------------------------------------------------- /joss/cannonball_hr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/joss/cannonball_hr.png -------------------------------------------------------------------------------- /travis_deploy_rsa.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/travis_deploy_rsa.enc -------------------------------------------------------------------------------- /brachistochroneSolution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/brachistochroneSolution.png -------------------------------------------------------------------------------- /joss/brachistochone_yx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/joss/brachistochone_yx.png -------------------------------------------------------------------------------- /mkdocs/docs/dymos_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/mkdocs/docs/dymos_logo.png -------------------------------------------------------------------------------- /joss/cannonball_states_h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/joss/cannonball_states_h.png -------------------------------------------------------------------------------- /joss/cannonball_states_r.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/joss/cannonball_states_r.png -------------------------------------------------------------------------------- /joss/flow_charts/opt_control.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/joss/flow_charts/opt_control.pdf -------------------------------------------------------------------------------- /joss/flow_charts/opt_control.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/joss/flow_charts/opt_control.png -------------------------------------------------------------------------------- /dymos/examples/min_time_climb/prop/__init__.py: -------------------------------------------------------------------------------- 1 | from .prop import PropGroup 2 | from .mdot_comp import MassFlowRateComp 3 | -------------------------------------------------------------------------------- /joss/brachistochrone_states_x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/joss/brachistochrone_states_x.png -------------------------------------------------------------------------------- /joss/brachistochrone_states_y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/joss/brachistochrone_states_y.png -------------------------------------------------------------------------------- /joss/flow_charts/coupled_co_design.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/joss/flow_charts/coupled_co_design.pdf -------------------------------------------------------------------------------- /joss/flow_charts/coupled_co_design.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/joss/flow_charts/coupled_co_design.png -------------------------------------------------------------------------------- /joss/flow_charts/opt_control_edited.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/joss/flow_charts/opt_control_edited.pdf -------------------------------------------------------------------------------- /mkdocs/docs/faq/images/n2_full_model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/mkdocs/docs/faq/images/n2_full_model.png -------------------------------------------------------------------------------- /mkdocs/docs/faq/images/n2_rhs_disc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/mkdocs/docs/faq/images/n2_rhs_disc.png -------------------------------------------------------------------------------- /joss/flow_charts/sequential_co_design.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/joss/flow_charts/sequential_co_design.pdf -------------------------------------------------------------------------------- /joss/flow_charts/sequential_co_design.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/joss/flow_charts/sequential_co_design.png -------------------------------------------------------------------------------- /joss/flow_charts/coupled_co_design_edited.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/joss/flow_charts/coupled_co_design_edited.pdf -------------------------------------------------------------------------------- /mkdocs/docs/examples/ssto_earth/ssto_fbd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/mkdocs/docs/examples/ssto_earth/ssto_fbd.png -------------------------------------------------------------------------------- /mkdocs/docs/examples/ssto_earth/ssto_xdsm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/mkdocs/docs/examples/ssto_earth/ssto_xdsm.png -------------------------------------------------------------------------------- /mkdocs/docs/features/figures/sparsity_plot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/mkdocs/docs/features/figures/sparsity_plot.png -------------------------------------------------------------------------------- /joss/flow_charts/sequential_co_design_edited.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/joss/flow_charts/sequential_co_design_edited.pdf -------------------------------------------------------------------------------- /mkdocs/docs/features/figures/timeseries_plot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/mkdocs/docs/features/figures/timeseries_plot.png -------------------------------------------------------------------------------- /mkdocs/docs/getting_started/scripts/lgl_animation_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/mkdocs/docs/getting_started/scripts/lgl_animation_0.png -------------------------------------------------------------------------------- /mkdocs/docs/getting_started/scripts/lgl_animation_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/mkdocs/docs/getting_started/scripts/lgl_animation_1.png -------------------------------------------------------------------------------- /mkdocs/docs/getting_started/scripts/lgl_animation_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/mkdocs/docs/getting_started/scripts/lgl_animation_2.png -------------------------------------------------------------------------------- /mkdocs/docs/getting_started/scripts/lgl_animation_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/mkdocs/docs/getting_started/scripts/lgl_animation_3.png -------------------------------------------------------------------------------- /mkdocs/docs/getting_started/scripts/lgl_solution_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/mkdocs/docs/getting_started/scripts/lgl_solution_0.png -------------------------------------------------------------------------------- /mkdocs/docs/getting_started/scripts/lgl_solution_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/mkdocs/docs/getting_started/scripts/lgl_solution_1.png -------------------------------------------------------------------------------- /mkdocs/docs/getting_started/scripts/lgl_solution_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/mkdocs/docs/getting_started/scripts/lgl_solution_2.png -------------------------------------------------------------------------------- /mkdocs/docs/getting_started/scripts/lgl_solution_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/mkdocs/docs/getting_started/scripts/lgl_solution_3.png -------------------------------------------------------------------------------- /mkdocs/docs/getting_started/scripts/lgl_solution_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/mkdocs/docs/getting_started/scripts/lgl_solution_4.png -------------------------------------------------------------------------------- /mkdocs/docs/getting_started/scripts/lgl_solution_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/mkdocs/docs/getting_started/scripts/lgl_solution_5.png -------------------------------------------------------------------------------- /mkdocs/docs/getting_started/scripts/lgr_animation_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/mkdocs/docs/getting_started/scripts/lgr_animation_0.png -------------------------------------------------------------------------------- /mkdocs/docs/getting_started/scripts/lgr_animation_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/mkdocs/docs/getting_started/scripts/lgr_animation_1.png -------------------------------------------------------------------------------- /mkdocs/docs/getting_started/scripts/lgr_animation_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/mkdocs/docs/getting_started/scripts/lgr_animation_2.png -------------------------------------------------------------------------------- /mkdocs/docs/getting_started/scripts/lgr_animation_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/mkdocs/docs/getting_started/scripts/lgr_animation_3.png -------------------------------------------------------------------------------- /mkdocs/docs/getting_started/scripts/lgr_solution_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/mkdocs/docs/getting_started/scripts/lgr_solution_0.png -------------------------------------------------------------------------------- /mkdocs/docs/getting_started/scripts/lgr_solution_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/mkdocs/docs/getting_started/scripts/lgr_solution_1.png -------------------------------------------------------------------------------- /mkdocs/docs/getting_started/scripts/lgr_solution_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/mkdocs/docs/getting_started/scripts/lgr_solution_2.png -------------------------------------------------------------------------------- /mkdocs/docs/getting_started/scripts/lgr_solution_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/mkdocs/docs/getting_started/scripts/lgr_solution_3.png -------------------------------------------------------------------------------- /mkdocs/docs/getting_started/scripts/lgr_solution_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/mkdocs/docs/getting_started/scripts/lgr_solution_4.png -------------------------------------------------------------------------------- /mkdocs/docs/getting_started/scripts/lgr_solution_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/mkdocs/docs/getting_started/scripts/lgr_solution_5.png -------------------------------------------------------------------------------- /mkdocs/docs/examples/racecar/vehicle_position_states.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/mkdocs/docs/examples/racecar/vehicle_position_states.png -------------------------------------------------------------------------------- /mkdocs/docs/examples/min_time_climb/min_time_climb_fbd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/mkdocs/docs/examples/min_time_climb/min_time_climb_fbd.png -------------------------------------------------------------------------------- /mkdocs/docs/features/figures/sparsity_plot_solve_segments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/mkdocs/docs/features/figures/sparsity_plot_solve_segments.png -------------------------------------------------------------------------------- /dymos/examples/brachistochrone/__init__.py: -------------------------------------------------------------------------------- 1 | from .brachistochrone_ode import BrachistochroneODE 2 | from .brachistochrone_vector_states_ode import BrachistochroneVectorStatesODE 3 | -------------------------------------------------------------------------------- /dymos/transcriptions/__init__.py: -------------------------------------------------------------------------------- 1 | from .pseudospectral.gauss_lobatto import GaussLobatto 2 | from .pseudospectral.radau_pseudospectral import Radau 3 | from .solve_ivp.solve_ivp import SolveIVP 4 | -------------------------------------------------------------------------------- /mkdocs/docs/api/trajectory_api.md: -------------------------------------------------------------------------------- 1 | # The Trajectory API 2 | 3 | {{ api_doc('dymos.Trajectory', members=['add_linkage_constraint', 'add_parameter', 'add_phase', 'link_phases', 'simulate']) }} 4 | -------------------------------------------------------------------------------- /mkdocs/docs/examples/ssto_moon_linear_tangent/ssto_linear_tangent_xdsm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/mkdocs/docs/examples/ssto_moon_linear_tangent/ssto_linear_tangent_xdsm.png -------------------------------------------------------------------------------- /mkdocs/docs/getting_started/intro_to_dymos/figures/spring_mass_damper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/dymos/master/mkdocs/docs/getting_started/intro_to_dymos/figures/spring_mass_damper.png -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Summary 2 | 3 | Summary of PR. 4 | 5 | ### Related Issues 6 | 7 | - Resolves # 8 | 9 | ### Status 10 | 11 | - [ ] Ready for merge 12 | 13 | ### Backwards incompatibilities 14 | 15 | None 16 | 17 | ### New Dependencies 18 | 19 | None 20 | -------------------------------------------------------------------------------- /dymos/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.0.0' 2 | 3 | from .phase import Phase 4 | from .transcriptions import GaussLobatto, Radau 5 | from .trajectory.trajectory import Trajectory 6 | from .run_problem import run_problem 7 | from .load_case import load_case 8 | from .options import options 9 | -------------------------------------------------------------------------------- /dymos/transcriptions/pseudospectral/components/__init__.py: -------------------------------------------------------------------------------- 1 | from .collocation_comp import CollocationComp 2 | from .state_interp_comp import StateInterpComp 3 | from .state_independents import StateIndependentsComp 4 | from .control_endpoint_defect_comp import ControlEndpointDefectComp 5 | from .gauss_lobatto_interleave_comp import GaussLobattoInterleaveComp 6 | -------------------------------------------------------------------------------- /mkdocs/docs/api/phase_api.md: -------------------------------------------------------------------------------- 1 | # The Phase API 2 | 3 | {{ api_doc('dymos.Phase', members=['set_time_options', 'add_state', 'set_state_options', 'add_control', 'set_control_options', 'add_polynomial_control', 'set_polynomial_control_options', 'add_parameter', 'set_parameter_options', 'add_timeseries', 'add_timeseries_output', 'add_boundary_constraint', 'add_path_constraint', 'simulate', 'set_refine_options', 'interpolate']) }} 4 | -------------------------------------------------------------------------------- /dymos/transcriptions/common/__init__.py: -------------------------------------------------------------------------------- 1 | from .boundary_constraint_comp import BoundaryConstraintComp 2 | from .continuity_comp import RadauPSContinuityComp, GaussLobattoContinuityComp 3 | from .control_group import ControlGroup 4 | from .polynomial_control_group import PolynomialControlGroup 5 | from .path_constraint_comp import PathConstraintComp 6 | from .timeseries_output_comp import PseudospectralTimeseriesOutputComp 7 | from .time_comp import TimeComp 8 | -------------------------------------------------------------------------------- /dymos/utils/constants.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module houses constants that are used throughout dymos 3 | 4 | Attributes 5 | ---------- 6 | INF_BOUND : float 7 | The value of infinity used for unbounded variables or constraints. Some optimizers treat 8 | bounds as infinity if their value exceeds some threshold. The value used in Dymos is set 9 | such that it satisfies the default value for both IPOPT (1.0E19) and SNOPT (1.0E20). 10 | """ 11 | 12 | 13 | INF_BOUND = 1.0E21 14 | -------------------------------------------------------------------------------- /dymos/options.py: -------------------------------------------------------------------------------- 1 | from openmdao.api import OptionsDictionary 2 | 3 | options = OptionsDictionary() 4 | 5 | options.declare('include_check_partials', default=True, types=bool, 6 | desc='If True, include dymos components when checking partials.') 7 | 8 | options.declare('plots', default='matplotlib', values=['matplotlib', 'bokeh'], 9 | desc='The plot library used to generate output plots for Dymos.') 10 | 11 | options.declare('notebook_mode', default=False, types=bool, 12 | desc='If True, provide notebook-enhanced plots and outputs.') 13 | -------------------------------------------------------------------------------- /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 1.0.0 3 | commit = True 4 | tag = False 5 | parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-(?P[a-z]+))? 6 | serialize = 7 | {major}.{minor}.{patch}-{release} 8 | {major}.{minor}.{patch} 9 | 10 | [bumpversion:file:setup.py] 11 | search = version='{current_version}', 12 | replace = version='{new_version}', 13 | 14 | [bumpversion:file:dymos/__init__.py] 15 | search = __version__ = '{current_version}' 16 | replace = __version__ = '{new_version}' 17 | 18 | [bumpversion:part:release] 19 | optional_value = rel 20 | values = 21 | dev 22 | rel 23 | -------------------------------------------------------------------------------- /dymos/transcriptions/solve_ivp/components/__init__.py: -------------------------------------------------------------------------------- 1 | from .segment_state_mux_comp import SegmentStateMuxComp 2 | from .segment_simulation_comp import SegmentSimulationComp 3 | from .solve_ivp_polynomial_control_group import SolveIVPPolynomialControlGroup 4 | from .ode_integration_interface import ODEIntegrationInterface 5 | from .odeint_control_interpolation_comp import ODEIntControlInterpolationComp 6 | from .solve_ivp_control_group import SolveIVPControlGroup 7 | from .solve_ivp_polynomial_control_group import SolveIVPPolynomialControlGroup 8 | from .solve_ivp_timeseries_comp import SolveIVPTimeseriesOutputComp 9 | from .state_rate_collector_comp import StateRateCollectorComp 10 | -------------------------------------------------------------------------------- /dymos/utils/test/test_misc.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from dymos.utils.misc import get_rate_units 4 | 5 | 6 | class TestMisc(unittest.TestCase): 7 | 8 | def test_get_rate_units(self): 9 | rate_units = get_rate_units('m', 's', deriv=1) 10 | 11 | self.assertEqual(rate_units, 'm/s') 12 | 13 | rate2_units = get_rate_units('m', 's', deriv=2) 14 | 15 | self.assertEqual(rate2_units, 'm/s**2') 16 | 17 | def test_get_rate_units_invalid_deriv(self): 18 | 19 | with self.assertRaises(ValueError) as e: 20 | get_rate_units('m', 's', deriv=0) 21 | self.assertEqual(str(e.exception), 'deriv argument must be 1 or 2.') 22 | 23 | 24 | if __name__ == '__main__': # pragma: no cover 25 | unittest.main() 26 | -------------------------------------------------------------------------------- /.github/workflows/draft-pdf.yml: -------------------------------------------------------------------------------- 1 | on: [push] 2 | 3 | jobs: 4 | paper: 5 | runs-on: ubuntu-latest 6 | name: Paper Draft 7 | steps: 8 | - name: Checkout 9 | uses: actions/checkout@v2 10 | - name: Build draft PDF 11 | uses: openjournals/openjournals-draft-action@master 12 | with: 13 | journal: joss 14 | # This should be the path to the paper within your repo. 15 | paper-path: joss/paper.md 16 | - name: Upload 17 | uses: actions/upload-artifact@v1 18 | with: 19 | name: paper 20 | # This is the output path where Pandoc will write the compiled 21 | # PDF. Note, this should be the same directory as the input 22 | # paper.md 23 | path: joss/paper.pdf -------------------------------------------------------------------------------- /dymos/examples/aircraft_steady_flight/aero/data/CRM_aero_inputs.dat: -------------------------------------------------------------------------------- 1 | 1.100000000000000000e+01 2 | 4.000000000000000000e+00 3 | 4.000000000000000000e+00 4 | 4.000000000000000000e+00 5 | 0.000000000000000000e+00 6 | 4.000000000000000222e-01 7 | 5.999999999999999778e-01 8 | 6.999999999999999556e-01 9 | 8.000000000000000444e-01 10 | 8.249999999999999556e-01 11 | 8.499999999999999778e-01 12 | 8.750000000000000000e-01 13 | 9.000000000000000222e-01 14 | 9.250000000000000444e-01 15 | 9.499999999999999556e-01 16 | -2.000000000000000000e+01 17 | -3.333333333333332149e+00 18 | 1.333333333333333570e+01 19 | 3.000000000000000000e+01 20 | -5.000000000000000000e+01 21 | 1.996666666666666788e+04 22 | 3.998333333333333576e+04 23 | 6.000000000000000000e+04 24 | -3.000000000000000000e+01 25 | -1.000000000000000000e+01 26 | 1.000000000000000000e+01 27 | 3.000000000000000000e+01 28 | -------------------------------------------------------------------------------- /dymos/examples/double_integrator/double_integrator_ode.py: -------------------------------------------------------------------------------- 1 | import openmdao.api as om 2 | 3 | 4 | class DoubleIntegratorODE(om.ExplicitComponent): 5 | """ 6 | The double integrator is a special case where the state rates are all set to other states 7 | or parameters. Since we aren't computing any other outputs, the ODE doesn't actually 8 | need to compute anything. OpenMDAO will warn us that the component has no outputs, but 9 | Dymos will solve the problem just fine. 10 | 11 | Note we still have to declare the num_nodes option in initialize so that Dymos can instantiate 12 | the ODE. 13 | 14 | Also note that neither time, states, nor parameters have targets, since there are no inputs 15 | in the ODE system. 16 | """ 17 | 18 | def initialize(self): 19 | self.options.declare('num_nodes', types=int) 20 | -------------------------------------------------------------------------------- /mkdocs/docs/examples/examples.md: -------------------------------------------------------------------------------- 1 | # Dymos by Example 2 | 3 | The goal of these examples is to walk users through the process of formulating an optimal control problem and solving it using Dymos. 4 | 5 | In working through these examples, we'll try to emphasize the following process: 6 | 7 | 1. Formulate the optimal control problem 8 | 2. Identify state and control variables, and the ordinary differential equations (ODE) which govern the dynamics. 9 | 3. Build the ODE as an OpenMDAO system. 10 | 4. Test the evaluation of the ODE. 11 | 5. Define the partial derivatives for the ODE. 12 | 6. Test the partial derivatives of the ODE against finite-difference or (preferably) complex-step approximations. 13 | 14 | 15 | ## Prerequisites 16 | 17 | These examples assume that the user has a working knowledge of the following: 18 | 19 | - Python 20 | - The numpy package for numerical computing with Python 21 | - The matplotlib package for plotting results 22 | -------------------------------------------------------------------------------- /mkdocs/docs/features/plotting.md: -------------------------------------------------------------------------------- 1 | # Plotting Timeseries 2 | 3 | Dymos provides a simple function to plot timeseries, 4 | 5 | 6 | `timeseries_plots(solution_recorder_filename, simulation_record_file=None, plot_dir="plots")` 7 | 8 | A separate plot file will be created for each timeseries variable. The plots will be saved and not 9 | displayed. The user has to manually open the plot files for them to be displayed. 10 | 11 | The only required argument, `solution_recorder_filename`, is the file path to the case recorder file 12 | containing the solution results. 13 | 14 | If the optional argument, `simulation_record_file`, is given, then that will be used to get data for 15 | plotting the timeseries of the simulation. 16 | 17 | Finally, the optional argument, `plot_dir`, can be set to indicate to what directory the plot files will be saved. 18 | 19 | Here is an example of the kind of plot that is created. 20 | 21 | ![Timeseries Plot](figures/timeseries_plot.png) -------------------------------------------------------------------------------- /dymos/examples/flying_robot/flying_robot_ode.py: -------------------------------------------------------------------------------- 1 | import openmdao.api as om 2 | 3 | 4 | class FlyingRobotODE(om.ExplicitComponent): 5 | """ 6 | The flying robot ODE mimics a free flying robot with thrusters in the vertical axis and 7 | horizontal axis. 8 | 9 | The controls on the robot are external accelerations in these two orthogonal axes. 10 | Gravity acts in the negative axis. 11 | """ 12 | 13 | def initialize(self): 14 | self.options.declare('num_nodes', types=int) 15 | 16 | def setup(self): 17 | nn = self.options['num_nodes'] 18 | 19 | self.add_input('u', shape=(nn, 2), units='m/s**2') 20 | self.add_output('u_mag2', shape=(nn,), units='m**2/s**4', desc='square of control magnitude') 21 | 22 | self.declare_coloring(wrt='u', method='cs') 23 | self.declare_partials(of='u_mag2', wrt='u', method='cs') 24 | 25 | def compute(self, inputs, outputs): 26 | u_x = inputs['u'][:, 0] 27 | u_y = inputs['u'][:, 1] 28 | 29 | outputs['u_mag2'] = u_x**2 + u_y**2 30 | -------------------------------------------------------------------------------- /mkdocs/docs/faq/add_ode_output_to_timeseries.md: -------------------------------------------------------------------------------- 1 | # How do I add an ODE output to the timeseries outputs? 2 | 3 | The timeseries object in Dymos provides a transcription-independent way to get timeseries output of a variable in a phase. 4 | By default, these timeseries outputs include Dymos phase variables (times, states, controls, and parameters). 5 | Often, there will be some other intermediate or auxiliary output calculations in the ODE that we want to track over time. 6 | These can be added to the timeseries outputs using the `add_timeseries_output` method on Phase. 7 | 8 | Multiple timeseries outputs can be added at one time by matching a glob pattern. 9 | For instance, to add all outputs of the ODE to the timeseries, one can use '*' as the 10 | 11 | 12 | See the [Timeseries documentation](../features/phases/timeseries.md) for more information. 13 | 14 | 15 | The [commercial aircraft example](../examples/commercial_aircraft/commercial_aircraft.md) uses the `add_timeseries_output` method to add the lift and drag coefficients to the timeseries outputs. 16 | -------------------------------------------------------------------------------- /dymos/examples/aircraft_steady_flight/aero/aero_coef_comp.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import openmdao.api as om 3 | 4 | from .crm_data import h_bp, alpha_bp, mach_bp, eta_bp, CL_data, CD_data, CM_data 5 | 6 | 7 | class AeroCoefComp(om.MetaModelStructuredComp): 8 | """ Interpolates aerodynamic coefficients for the NASA Common Research Model. """ 9 | 10 | def setup(self): 11 | nn = self.options['vec_size'] 12 | self.add_input(name='mach', val=0.2 * np.ones(nn), units=None, training_data=mach_bp) 13 | self.add_input(name='alpha', val=0.0 * np.ones(nn), units='deg', training_data=alpha_bp) 14 | self.add_input(name='alt', val=0.0 * np.ones(nn), units='ft', training_data=h_bp) 15 | self.add_input(name='eta', val=0.0 * np.ones(nn), units='deg', training_data=eta_bp) 16 | 17 | self.add_output(name='CL', val=np.zeros(nn), units=None, training_data=CL_data) 18 | self.add_output(name='CD', val=np.zeros(nn), units=None, training_data=CD_data + 0.015) 19 | self.add_output(name='CM', val=np.zeros(nn), units=None, training_data=CM_data) 20 | -------------------------------------------------------------------------------- /dymos/examples/min_time_climb/aero/dynamic_pressure_comp.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import openmdao.api as om 4 | 5 | 6 | class DynamicPressureComp(om.ExplicitComponent): 7 | def initialize(self): 8 | self.options.declare('num_nodes', types=int) 9 | 10 | def setup(self): 11 | nn = self.options['num_nodes'] 12 | 13 | self.add_input(name='rho', shape=(nn,), desc='atmospheric density', units='kg/m**3') 14 | self.add_input(name='v', shape=(nn,), desc='air-relative velocity', units='m/s') 15 | 16 | self.add_output(name='q', shape=(nn,), desc='dynamic pressure', units='N/m**2') 17 | 18 | ar = np.arange(nn) 19 | self.declare_partials(of='q', wrt='rho', rows=ar, cols=ar) 20 | self.declare_partials(of='q', wrt='v', rows=ar, cols=ar) 21 | 22 | def compute(self, inputs, outputs): 23 | outputs['q'] = 0.5 * inputs['rho'] * inputs['v'] ** 2 24 | 25 | def compute_partials(self, inputs, partials): 26 | partials['q', 'rho'] = 0.5 * inputs['v'] ** 2 27 | partials['q', 'v'] = inputs['rho'] * inputs['v'] 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # egg info 7 | *.egg-info 8 | 9 | # Pycharm 10 | **/.idea/ 11 | 12 | # view_model 13 | partition_tree_n2.html 14 | *n2.html 15 | 16 | # SNOPT 17 | SNOPT_print.out 18 | SNOPT_summary.out 19 | 20 | # OS X 21 | .DS_Store 22 | 23 | # Sphinx build directory 24 | # docs/build/ 25 | # docs/*.png 26 | 27 | # nosetests coverage 28 | .coverage 29 | 30 | # ipython notebook 31 | .ipynb_checkpoints 32 | 33 | # cProfile 34 | *.profile 35 | 36 | # Latex 37 | *.aux 38 | *.glo 39 | *.idx 40 | *.log 41 | *.toc 42 | *.ist 43 | *.acn 44 | *.acr 45 | *.alg 46 | *.bbl 47 | *.blg 48 | *.tui 49 | *.top 50 | *.tmp 51 | *.mp 52 | *.dvi 53 | *.glg 54 | *.gls 55 | *.nlo 56 | *.ilg 57 | *.ind 58 | *.lof 59 | *.lot 60 | *.maf 61 | *.mtc 62 | *.mtc1 63 | *.out 64 | *.synctex.gz 65 | 66 | # fortran 67 | fort.6 68 | 69 | # JOSS 70 | /joss/apa.csl 71 | /joss/joss-logo.png 72 | /joss/dymos_joss.pdf 73 | /joss/compile 74 | /joss/joss_demo.py 75 | /joss/latex.template 76 | /joss/paper.pdf 77 | 78 | # mkdocs 79 | /mkdocs/site 80 | -------------------------------------------------------------------------------- /mkdocs/docs/features/phases/segments.md: -------------------------------------------------------------------------------- 1 | # Segments 2 | 3 | All phases in Dymos are decomposed into one or more *segments* in time. These segments 4 | serve the following purposes: 5 | 6 | - Gauss-Lobatto collocation and the Radau Pseudospectral method model each state variable as a polynomial segment in nondimensional time within each segment. 7 | - Each control is modeled as a polynomial in nondimensional time within each segment. 8 | 9 | The order of the *state* polynomial segment is given by the phase argument `transcription_order`. 10 | In Dymos the minimum supported transcription order is 3. 11 | 12 | State-time histories within a segment are modelled as a Lagrange polynomial. 13 | Continuity in state value may be enforced via linear constraints at the segment boundaries (the default behavior) or by specifying a *compressed* transcription whereby the state value at a segment boundary is provided as a single value. 14 | The default *compressed* transcription yields an optimization problem with fewer variables, but in some situations using uncompressed transcription can result in more robust convergence. 15 | -------------------------------------------------------------------------------- /dymos/examples/min_time_climb/aero/cl_comp.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import openmdao.api as om 4 | 5 | 6 | class CLComp(om.ExplicitComponent): 7 | 8 | def initialize(self): 9 | self.options.declare('num_nodes', types=int) 10 | 11 | def setup(self): 12 | nn = self.options['num_nodes'] 13 | 14 | # Inputs 15 | self.add_input('CLa', shape=(nn,), desc='alpha lift coefficient', units=None) 16 | self.add_input('alpha', shape=(nn,), desc='angle of attck', units='rad') 17 | 18 | # Outputs 19 | self.add_output(name='CL', val=np.ones(nn), desc='lift coefficient', units=None) 20 | 21 | # Jacobian 22 | ar = np.arange(nn) 23 | self.declare_partials(of='CL', wrt='CLa', rows=ar, cols=ar) 24 | self.declare_partials(of='CL', wrt='alpha', rows=ar, cols=ar) 25 | 26 | def compute(self, inputs, outputs): 27 | outputs['CL'][:] = inputs['CLa'] * inputs['alpha'] 28 | 29 | def compute_partials(self, inputs, partials): 30 | partials['CL', 'CLa'] = inputs['alpha'] 31 | partials['CL', 'alpha'] = inputs['CLa'] 32 | -------------------------------------------------------------------------------- /mkdocs/docs/examples/brachistochrone/brachistochrone_upstream_controls.md: -------------------------------------------------------------------------------- 1 | # The Brachistochrone with Externally-Sourced Controls 2 | 3 | !!! info "Things you'll learn through this example" 4 | - How to provide trajectory control values from an external source. 5 | 6 | This example is the same as the other brachistochrone example with one exception: the control values come from an external source upstream of the trajectory. 7 | 8 | The following script fully defines the brachistochrone problem with Dymos and solves it. 9 | A new `IndepVarComp` is added before the trajectory. 10 | The transcription used in the relevant phase is defined first so that we can obtain the number of control input nodes. 11 | The IndepVarComp then provides the control $\theta$ at the correct number of nodes, and sends them to the trajectory. 12 | Since the control values are no longer managed by Dymos, they are added as design variables using the OpenMDAO `add_design_var` method. 13 | 14 | {{ embed_test('dymos.examples.brachistochrone.doc.test_doc_brachistochrone_upstream_control.TestBrachistochroneUpstreamControl.test_brachistochrone_upstream_control') }} 15 | -------------------------------------------------------------------------------- /dymos/examples/racecar/Track.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | class Track: 5 | def __init__(self, segments): 6 | self.segments = segments 7 | self.cornerspeeds = np.zeros(len(segments), dtype=np.complex) 8 | 9 | def get_segment_type(self, num): 10 | return self.segments[num][0] 11 | 12 | def get_segment_length(self, num): 13 | return self.segments[num][1] 14 | 15 | def get_segment_radius(self, num): 16 | return self.segments[num][2] 17 | 18 | def get_corner_direction(self, num): 19 | return self.segments[num][3] 20 | 21 | def set_corner_speed(self, num, speed): 22 | self.cornerspeeds[num] = speed 23 | 24 | def get_corner_speed(self, num): 25 | return self.cornerspeeds[num] 26 | 27 | def get_total_length(self): 28 | length = 0 29 | for i in range(len(self.segments)): 30 | if self.segments[i][0] == 0: 31 | # straight 32 | length += self.get_segment_length(i) 33 | else: 34 | # corner 35 | length += self.get_segment_length(i) * self.get_segment_radius(i) 36 | return length 37 | -------------------------------------------------------------------------------- /dymos/examples/aircraft_steady_flight/true_airspeed_comp.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import openmdao.api as om 4 | 5 | 6 | class TrueAirspeedComp(om.ExplicitComponent): 7 | """ Compute Mach number based on true airspeed and the local speed of sound. """ 8 | 9 | def initialize(self): 10 | self.options.declare('num_nodes', types=int) 11 | 12 | def setup(self): 13 | nn = self.options['num_nodes'] 14 | self.add_input('sos', val=np.zeros(nn), desc='atmospheric speed of sound', units='m/s') 15 | self.add_input('mach', val=np.zeros(nn), desc='Mach number', units=None) 16 | self.add_output('TAS', val=np.zeros(nn), desc='true airspeed', units='m/s') 17 | 18 | # Setup partials 19 | ar = np.arange(self.options['num_nodes']) 20 | 21 | self.declare_partials(of='TAS', wrt='mach', rows=ar, cols=ar) 22 | self.declare_partials(of='TAS', wrt='sos', rows=ar, cols=ar) 23 | 24 | def compute(self, inputs, outputs): 25 | outputs['TAS'] = inputs['mach'] * inputs['sos'] 26 | 27 | def compute_partials(self, inputs, partials): 28 | partials['TAS', 'mach'] = inputs['sos'] 29 | partials['TAS', 'sos'] = inputs['mach'] 30 | -------------------------------------------------------------------------------- /dymos/examples/min_time_climb/doc/dynamic_pressure_comp_partial_coloring.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import openmdao.api as om 4 | 5 | 6 | class DynamicPressureCompFD(om.ExplicitComponent): 7 | def initialize(self): 8 | self.options.declare('num_nodes', types=int) 9 | self.options.declare('partial_coloring', types=bool, default=False) 10 | 11 | def setup(self): 12 | nn = self.options['num_nodes'] 13 | 14 | self.add_input(name='rho', shape=(nn,), desc='atmospheric density', units='kg/m**3') 15 | self.add_input(name='v', shape=(nn,), desc='air-relative velocity', units='m/s') 16 | 17 | self.add_output(name='q', shape=(nn,), desc='dynamic pressure', units='N/m**2') 18 | 19 | self.declare_partials(of='q', wrt='rho', method='fd') 20 | self.declare_partials(of='q', wrt='v', method='fd') 21 | 22 | if self.options['partial_coloring']: 23 | self.declare_coloring(wrt=['*'], method='fd', tol=1.0E-6, num_full_jacs=2, 24 | show_summary=True, show_sparsity=True, min_improve_pct=10.) 25 | 26 | def compute(self, inputs, outputs): 27 | outputs['q'] = 0.5 * inputs['rho'] * inputs['v'] ** 2 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Summary of Issue 2 | 3 | 4 | 5 | ### Issue Type 6 | 7 | - [x] Bug 8 | - [ ] Enhancement 9 | - [ ] Docs 10 | - [ ] Miscellaneous 11 | 12 | ### Description 13 | 14 | For bugs, explain what is happening and how it differs from the expected behavior. For enhancements, explain the desired functionality. 15 | 16 | ### Example 17 | 18 | For bugs, if at all possible, provide a short snipped which reproduces the issue, 19 | or link to a file in another repository where the issue is demonstrated. 20 | 21 | For enhancements this is not as critical, but if possible provide an example 22 | of the proposed usage of the enhancement. If a reference implementation has 23 | been implemented and you desire to have it merged into Dymos, site that reference 24 | implementation here before issuing a pull request. The pull request should then 25 | cite this issue number in the "Related issues" section. 26 | 27 | ### Environment 28 | 29 | Operating System: 30 | Python environment: 31 | Packages: 32 | -------------------------------------------------------------------------------- /dymos/examples/racecar/timeAdder.py: -------------------------------------------------------------------------------- 1 | import openmdao.api as om 2 | import numpy as np 3 | 4 | 5 | class TimeAdder(om.ExplicitComponent): 6 | # This serves to keep track of the elapsed time. Since we performed a change of variables we 7 | # need to integrate time by adding dt/ds 8 | 9 | def initialize(self): 10 | self.options.declare('num_nodes', types=int) 11 | 12 | def setup(self): 13 | nn = self.options['num_nodes'] 14 | 15 | # states 16 | self.add_input('sdot', val=np.zeros(nn), desc='distance along track', units='m/s') 17 | 18 | # outputs 19 | self.add_output('dt_ds', val=np.zeros(nn), desc='distance perpendicular to centerline', 20 | units='s/m') 21 | 22 | # Setup partials 23 | arange = np.arange(self.options['num_nodes'], dtype=int) 24 | 25 | # partials 26 | self.declare_partials(of='dt_ds', wrt='sdot', rows=arange, cols=arange) 27 | 28 | def compute(self, inputs, outputs): 29 | sdot = inputs['sdot'] 30 | 31 | outputs['dt_ds'] = 1/sdot 32 | 33 | def compute_partials(self, inputs, jacobian): 34 | sdot = inputs['sdot'] 35 | 36 | jacobian['dt_ds', 'sdot'] = -1/sdot**2 37 | -------------------------------------------------------------------------------- /dymos/examples/min_time_climb/aero/mach_comp.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import openmdao.api as om 4 | 5 | 6 | class MachComp(om.ExplicitComponent): 7 | """ Compute the Mach number based on vehicle airspeed and local speed 8 | of sound. 9 | """ 10 | def initialize(self): 11 | self.options.declare('num_nodes', types=int) 12 | 13 | def setup(self): 14 | nn = self.options['num_nodes'] 15 | 16 | # Inputs 17 | self.add_input(name='v', shape=(nn,), desc='velocity magnitude', units='m/s') 18 | self.add_input('sos', shape=(nn,), desc='alpha lift coefficient', units='m/s') 19 | 20 | # Outputs 21 | self.add_output(name='mach', val=0.7*np.ones(nn), desc='Mach number', units=None) 22 | 23 | # Jacobian 24 | ar = np.arange(nn) 25 | self.declare_partials(of='mach', wrt='v', rows=ar, cols=ar) 26 | self.declare_partials(of='mach', wrt='sos', rows=ar, cols=ar) 27 | 28 | def compute(self, inputs, outputs): 29 | outputs['mach'][:] = inputs['v'] / inputs['sos'] 30 | 31 | def compute_partials(self, inputs, partials): 32 | partials['mach', 'v'] = 1.0 / inputs['sos'] 33 | partials['mach', 'sos'] = -inputs['v'] / inputs['sos'] ** 2 34 | -------------------------------------------------------------------------------- /dymos/examples/aircraft_steady_flight/dynamic_pressure_comp.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import openmdao.api as om 3 | 4 | 5 | class DynamicPressureComp(om.ExplicitComponent): 6 | """ Compute the dynamic pressure based on the velocity and the atmospheric density. """ 7 | def initialize(self): 8 | self.options.declare('num_nodes', types=int) 9 | 10 | def setup(self): 11 | nn = self.options['num_nodes'] 12 | 13 | # Inputs 14 | self.add_input(name='rho', shape=(nn,), desc='Atmospheric density', units='kg/m**3') 15 | self.add_input(name='TAS', shape=(nn,), desc='True airspeed', units='m/s') 16 | 17 | # Outputs 18 | self.add_output(name='q', val=np.zeros(nn), desc='Dynamic pressure', units='Pa') 19 | 20 | # Partials 21 | ar = np.arange(nn) 22 | self.declare_partials(of='q', wrt='rho', rows=ar, cols=ar) 23 | self.declare_partials(of='q', wrt='TAS', rows=ar, cols=ar) 24 | 25 | def compute(self, inputs, outputs): 26 | outputs['q'] = 0.5 * inputs['rho'] * inputs['TAS'] ** 2 27 | 28 | def compute_partials(self, inputs, partials): 29 | partials['q', 'rho'] = 0.5 * inputs['TAS'] ** 2 30 | partials['q', 'TAS'] = inputs['rho'] * inputs['TAS'] 31 | -------------------------------------------------------------------------------- /dymos/examples/length_constrained_brachistochrone/arc_length_comp.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, division, absolute_import 2 | 3 | import numpy as np 4 | 5 | from openmdao.api import ExplicitComponent 6 | 7 | 8 | class ArcLengthComp(ExplicitComponent): 9 | 10 | def initialize(self): 11 | 12 | self.options.declare('num_nodes', types=(int,)) 13 | 14 | def setup(self): 15 | nn = self.options['num_nodes'] 16 | 17 | self.add_input('x', val=np.ones(nn), units='m', desc='x at points along the trajectory') 18 | self.add_input('theta', val=np.ones(nn), units='rad', 19 | desc='wire angle with vertical along the trajectory') 20 | 21 | self.add_output('S', val=1.0, units='m', desc='arclength of wire') 22 | 23 | self.declare_partials(of='S', wrt='*', method='cs') 24 | 25 | def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): 26 | 27 | x = inputs['x'] 28 | theta = inputs['theta'] 29 | 30 | dy_dx = -1.0 / np.tan(theta) 31 | dx = np.diff(x) 32 | f = np.sqrt(1 + dy_dx**2) 33 | 34 | # trapezoidal rule 35 | fxm1 = f[:-1] 36 | fx = f[1:] 37 | outputs['S'] = 0.5 * np.dot(fxm1 + fx, dx) 38 | -------------------------------------------------------------------------------- /dymos/examples/racecar/linewidthhelper.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | def linewidth_from_data_units(linewidth, axis, reference='y'): 5 | """ 6 | Convert a linewidth in data units to linewidth in points. 7 | 8 | Parameters 9 | ---------- 10 | linewidth: float 11 | Linewidth in data units of the respective reference-axis 12 | axis: matplotlib axis 13 | The axis which is used to extract the relevant transformation 14 | data (data limits and size must not change afterwards) 15 | reference: string 16 | The axis that is taken as a reference for the data width. 17 | Possible values: 'x' and 'y'. Defaults to 'y'. 18 | 19 | Returns 20 | ------- 21 | linewidth: float 22 | Linewidth in points 23 | """ 24 | fig = axis.get_figure() 25 | if reference == 'x': 26 | length = fig.bbox_inches.width * axis.get_position().width 27 | value_range = np.diff(axis.get_xlim()) 28 | elif reference == 'y': 29 | length = fig.bbox_inches.height * axis.get_position().height 30 | value_range = np.diff(axis.get_ylim()) 31 | # Convert length to points 32 | length *= 72 33 | # Scale linewidth to value range 34 | return linewidth * (length / value_range) 35 | -------------------------------------------------------------------------------- /mkdocs/docs/examples/brachistochrone/brachistochrone_upstream_states.md: -------------------------------------------------------------------------------- 1 | # The Brachistochrone with Externally-Sourced initial state values 2 | 3 | !!! info "Things you'll learn through this example" 4 | - How to link phase state boundary values to an externally provided value. 5 | 6 | This is another modification of the brachistochrone in which the target initial value of a state is provided by an external source (an IndepVarComp in this case). 7 | 8 | Rather than the external value being directly connected to the phase, the values are "linked" via constraint. 9 | This is exactly how phase linkages in trajectories work as well, but the trajectory hides some of the implementation. 10 | 11 | The following script fully defines the brachistochrone problem with Dymos and solves it. 12 | A new `IndepVarComp` is added before the trajectory which provides `x0`. 13 | An ExecComp then computes the error between `x0_target` (taken from the IndepVarComp) and `x0_actual` (taken from the phase timeseries output). 14 | The result of this calculation (`x0_error`) is then constrained as a normal OpenMDAO constraint. 15 | 16 | {{ embed_test('dymos.examples.brachistochrone.doc.test_doc_brachistochrone_upstream_state.TestBrachistochroneUpstreamState.test_brachistochrone_upstream_state') }} 17 | -------------------------------------------------------------------------------- /dymos/examples/shuttle_reentry/atmosphere_comp.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import openmdao.api as om 3 | 4 | 5 | class Atmosphere(om.ExplicitComponent): 6 | """ 7 | Defines the logarithmic atmosphere model for the shuttle reentry problem. 8 | 9 | References 10 | ---------- 11 | .. [1] Betts, John T., Practical Methods for Optimal Control and Estimation Using Nonlinear 12 | Programming, p. 248, 2010. 13 | """ 14 | 15 | def initialize(self): 16 | self.options.declare('num_nodes', types=int) 17 | 18 | def setup(self): 19 | nn = self.options['num_nodes'] 20 | self.add_input('h', val=np.ones(nn), desc='altitude', units='ft') 21 | self.add_output('rho', val=np.ones(nn), desc='local density', units='slug/ft**3') 22 | partial_range = np.arange(nn, dtype=int) 23 | self.declare_partials('rho', 'h', rows=partial_range, cols=partial_range) 24 | 25 | def compute(self, inputs, outputs): 26 | h = inputs['h'] 27 | h_r = 23800 28 | rho_0 = .002378 29 | outputs['rho'] = rho_0 * np.exp(-h / h_r) 30 | 31 | def compute_partials(self, inputs, partials): 32 | h = inputs['h'] 33 | h_r = 23800 34 | rho_0 = .002378 35 | partials['rho', 'h'] = -1 / h_r * rho_0 * np.exp(-h / h_r) 36 | -------------------------------------------------------------------------------- /dymos/examples/min_time_climb/prop/thrust_comp.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import openmdao.api as om 4 | 5 | 6 | class ThrustComp(om.ExplicitComponent): 7 | """ Computes mass flow rate for the F4's 2 J79 engines at full throttle. """ 8 | 9 | def initialize(self): 10 | self.options.declare('num_nodes', types=int) 11 | 12 | def setup(self): 13 | nn = self.options['num_nodes'] 14 | 15 | # Inputs 16 | self.add_input('max_thrust', shape=(nn,), desc='maximum thrust', units='N') 17 | self.add_input('throttle', val=np.ones(nn), desc='throttle parameter', units=None) 18 | 19 | # Outputs 20 | self.add_output(name='thrust', val=np.zeros(nn), 21 | desc='vehicle thrust at given throttle value', 22 | units='N') 23 | 24 | # Jacobian 25 | ar = np.arange(nn) 26 | self.declare_partials(of='thrust', wrt='max_thrust', rows=ar, cols=ar) 27 | self.declare_partials(of='thrust', wrt='throttle', rows=ar, cols=ar) 28 | 29 | def compute(self, inputs, outputs): 30 | outputs['thrust'] = inputs['max_thrust'] * inputs['throttle'] 31 | 32 | def compute_partials(self, inputs, partials): 33 | partials['thrust', 'max_thrust'] = inputs['throttle'] 34 | partials['thrust', 'throttle'] = inputs['max_thrust'] 35 | -------------------------------------------------------------------------------- /dymos/examples/min_time_climb/prop/test/test_max_thrust_comp.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import numpy as np 4 | from numpy.testing import assert_almost_equal 5 | 6 | import openmdao.api as om 7 | 8 | from dymos.examples.min_time_climb.prop.max_thrust_comp import MaxThrustComp, THR_DATA, _LBF2N 9 | 10 | 11 | class TestBrysonThrustComp(unittest.TestCase): 12 | 13 | def test_grid_values(self): 14 | n = 10 15 | 16 | p = om.Problem(model=om.Group()) 17 | 18 | ivc = om.IndepVarComp() 19 | ivc.add_output(name='h', val=np.zeros(n), units='ft') 20 | ivc.add_output(name='mach', val=np.zeros(n), units=None) 21 | 22 | p.model.add_subsystem(name='ivc', subsys=ivc, promotes_outputs=['h', 'mach']) 23 | p.model.add_subsystem(name='tcomp', 24 | subsys=MaxThrustComp(vec_size=n, extrapolate=True, method='cubic')) 25 | 26 | p.model.connect('h', 'tcomp.h') 27 | p.model.connect('mach', 'tcomp.mach') 28 | 29 | p.setup() 30 | 31 | p['mach'] = THR_DATA['mach'] 32 | for i in range(10): 33 | p['h'] = THR_DATA['h'][i] * np.ones(n) 34 | p.run_model() 35 | thrust_N = p['tcomp.max_thrust'] 36 | assert_almost_equal(thrust_N, THR_DATA['thrust'][i, :]) 37 | 38 | 39 | if __name__ == '__main__': # pragma: no cover 40 | unittest.main() 41 | -------------------------------------------------------------------------------- /dymos/examples/oscillator/doc/oscillator_ode.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import openmdao.api as om 3 | 4 | 5 | class OscillatorODE(om.ExplicitComponent): 6 | """ 7 | A Dymos ODE for a damped harmonic oscillator. 8 | """ 9 | 10 | def initialize(self): 11 | self.options.declare('num_nodes', types=int) 12 | 13 | def setup(self): 14 | nn = self.options['num_nodes'] 15 | 16 | # Inputs 17 | self.add_input('x', shape=(nn,), desc='displacement', units='m') 18 | self.add_input('v', shape=(nn,), desc='velocity', units='m/s') 19 | self.add_input('k', shape=(nn,), desc='spring constant', units='N/m') 20 | self.add_input('c', shape=(nn,), desc='damping coefficient', units='N*s/m') 21 | self.add_input('m', shape=(nn,), desc='mass', units='kg') 22 | 23 | # self.add_output('x_dot', val=np.zeros(nn), desc='rate of change of displacement', units='m/s') 24 | self.add_output('v_dot', val=np.zeros(nn), desc='rate of change of velocity', units='m/s**2') 25 | 26 | self.declare_partials(of='*', wrt='*', method='fd') 27 | 28 | def compute(self, inputs, outputs): 29 | x = inputs['x'] 30 | v = inputs['v'] 31 | k = inputs['k'] 32 | c = inputs['c'] 33 | m = inputs['m'] 34 | 35 | f_spring = -k * x 36 | f_damper = -c * v 37 | 38 | outputs['v_dot'] = (f_spring + f_damper) / m 39 | -------------------------------------------------------------------------------- /dymos/examples/min_time_climb/prop/mdot_comp.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import openmdao.api as om 4 | 5 | 6 | class MassFlowRateComp(om.ExplicitComponent): 7 | """ Computes mass flow rate for the F4's 2 J79 engines at full throttle. """ 8 | 9 | def initialize(self): 10 | self.options.declare('num_nodes', types=int) 11 | 12 | def setup(self): 13 | nn = self.options['num_nodes'] 14 | 15 | # Inputs 16 | self.add_input('thrust', shape=(nn,), desc='engine thrust', units='N') 17 | self.add_input('Isp', shape=(nn,), desc='engine specific impulse', units='s') 18 | 19 | # Outputs 20 | self.add_output(name='m_dot', val=np.zeros(nn), 21 | desc='vehicle mass accumulation rate due to fuel consumption', 22 | units='kg/s') 23 | 24 | # Jacobian 25 | ar = np.arange(nn) 26 | self.declare_partials(of='m_dot', wrt='thrust', rows=ar, cols=ar) 27 | self.declare_partials(of='m_dot', wrt='Isp', rows=ar, cols=ar) 28 | 29 | def compute(self, inputs, outputs): 30 | outputs['m_dot'] = -inputs['thrust'] / (9.80665 * inputs['Isp']) 31 | 32 | def compute_partials(self, inputs, partials): 33 | mdot = inputs['thrust'] / (9.80665 * inputs['Isp']) 34 | partials['m_dot', 'thrust'] = -1.0 / (9.80665 * inputs['Isp']) 35 | partials['m_dot', 'Isp'] = mdot / inputs['Isp'] 36 | -------------------------------------------------------------------------------- /dymos/examples/racecar/curvature.py: -------------------------------------------------------------------------------- 1 | import openmdao.api as om 2 | import numpy as np 3 | 4 | from .tracks import ovaltrack 5 | from .spline import get_track_points, get_spline 6 | 7 | 8 | class Curvature(om.ExplicitComponent): 9 | 10 | def initialize(self): 11 | self.options.declare('num_nodes', types=int) 12 | track = ovaltrack # remember to change here and in problemSolver.py 13 | 14 | points = get_track_points(track) 15 | finespline, gates, gatesd, curv, slope = get_spline(points) 16 | 17 | self.curv = curv 18 | self.track_length = track.get_total_length() 19 | 20 | def setup(self): 21 | nn = self.options['num_nodes'] 22 | 23 | # constants 24 | self.add_input('s', val=np.zeros(nn), desc='distance along track', units='m') 25 | 26 | # outputs 27 | self.add_output('kappa', val=np.zeros(nn), desc='track centerline Curvature', units='1/m') 28 | 29 | # no partials needed 30 | 31 | def compute(self, inputs, outputs): 32 | s = inputs['s'] 33 | 34 | num_curv_points = len(self.curv) 35 | 36 | kappa = np.zeros(len(s)) 37 | 38 | for i in range(len(s)): 39 | index = np.floor((s[i]/self.track_length)*num_curv_points) 40 | index = np.minimum(index, num_curv_points-1) 41 | kappa[i] = self.curv[index.astype(int)] 42 | 43 | outputs['kappa'] = kappa 44 | -------------------------------------------------------------------------------- /dymos/examples/aircraft_steady_flight/propulsion/throttle_comp.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import openmdao.api as om 4 | 5 | 6 | class ThrottleComp(om.ExplicitComponent): 7 | """ Compute 'tau' (throttle parameter) which is the ratio of the current thrust 8 | (as determined to provie flight equilibrium) with the maximum thrust given 9 | the current aircraft state. 10 | 11 | """ 12 | def initialize(self): 13 | self.options.declare('num_nodes', types=int) 14 | 15 | def setup(self): 16 | nn = self.options['num_nodes'] 17 | 18 | # Inputs 19 | self.add_input(name='thrust', shape=(nn,), desc='current thrust', units='N') 20 | self.add_input(name='max_thrust', shape=(nn,), desc='maximum possible thrust', units='N') 21 | 22 | # Outputs 23 | self.add_output(name='tau', shape=(nn,), desc='throttle parameter', units=None) 24 | 25 | # Partials 26 | ar = np.arange(nn) 27 | self.declare_partials('tau', 'thrust', rows=ar, cols=ar) 28 | self.declare_partials('tau', 'max_thrust', rows=ar, cols=ar) 29 | 30 | def compute(self, inputs, outputs): 31 | outputs['tau'] = inputs['thrust'] / inputs['max_thrust'] 32 | 33 | def compute_partials(self, inputs, partials): 34 | partials['tau', 'thrust'] = 1.0 / inputs['max_thrust'] 35 | partials['tau', 'max_thrust'] = -inputs['thrust'] / inputs['max_thrust']**2 36 | -------------------------------------------------------------------------------- /dymos/examples/brachistochrone/test/test_brachistochrone_vector_states_ode.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import numpy as np 4 | 5 | import openmdao.api as om 6 | from openmdao.utils.testing_utils import use_tempdirs 7 | 8 | from dymos.utils.testing_utils import assert_check_partials 9 | from dymos.examples.brachistochrone.brachistochrone_vector_states_ode import \ 10 | BrachistochroneVectorStatesODE 11 | 12 | 13 | @use_tempdirs 14 | class TestBrachistochroneVectorStatesODE(unittest.TestCase): 15 | 16 | def test_partials(self): 17 | nn = 5 18 | 19 | p = om.Problem(model=om.Group()) 20 | 21 | ivc = p.model.add_subsystem('ivc', om.IndepVarComp(), promotes_outputs=['*']) 22 | ivc.add_output('v', val=np.ones((nn,)), units='m/s') 23 | ivc.add_output('g', val=np.zeros((nn,)), units='m/s**2') 24 | ivc.add_output('theta', val=np.zeros((nn)), units='rad') 25 | 26 | p.model.add_subsystem('eom', BrachistochroneVectorStatesODE(num_nodes=nn), 27 | promotes_inputs=['*'], promotes_outputs=['*']) 28 | p.setup(check=True, force_alloc_complex=True) 29 | 30 | p['v'] = np.random.rand(nn) 31 | p['g'] = np.random.rand(nn) 32 | p['theta'] = np.random.rand(nn) 33 | 34 | p.run_model() 35 | 36 | np.set_printoptions(linewidth=1024, edgeitems=1000) 37 | cpd = p.check_partials(method='cs') 38 | assert_check_partials(cpd) 39 | -------------------------------------------------------------------------------- /dymos/utils/indexing.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | def get_src_indices_by_row(row_idxs, shape, flat=True): 5 | """ 6 | Provide the src_indices when connecting a vectorized variable from an output to an input. 7 | 8 | Indices are selected by choosing the first indices to be passed, corresponding to node 9 | index in Dymos. 10 | 11 | Parameters 12 | ---------- 13 | row_idxs : array_like 14 | The rows/node indices to be connected from the source to the target. 15 | shape : tuple 16 | The shape of the variable at each node (ignores the first dimension). 17 | flat : bool 18 | If True, return the source indices in flat source indices form. 19 | 20 | Returns 21 | ------- 22 | array_like 23 | If flat, a numpy array of shape `(row_idxs,) + shape` where each element is the index 24 | of the source of that element in the source array, in C-order. 25 | """ 26 | if not flat: 27 | raise NotImplementedError('Currently get_src_indices_by_row only returns ' 28 | 'flat source indices.') 29 | 30 | num_src_rows = np.max(row_idxs) + 1 31 | src_shape = (num_src_rows,) + shape 32 | other_idxs = [np.arange(n, dtype=int) for n in shape] 33 | ixgrid = np.ix_(row_idxs, *other_idxs) 34 | a = np.reshape(np.arange(np.prod(src_shape), dtype=int), newshape=src_shape) 35 | src_idxs = a[ixgrid] 36 | return src_idxs 37 | -------------------------------------------------------------------------------- /dymos/examples/aircraft_steady_flight/propulsion/fuel_burn_rate_comp.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import openmdao.api as om 4 | 5 | 6 | class FuelBurnRateComp(om.ExplicitComponent): 7 | """ Computes the fuel burn rate (rate of change of fuel weight) based on SFC and thrust. """ 8 | def initialize(self): 9 | self.options.declare('num_nodes', types=int) 10 | 11 | def setup(self): 12 | nn = self.options['num_nodes'] 13 | 14 | # Inputs 15 | self.add_input(name='thrust', shape=(nn,), desc='current thrust', units='N') 16 | self.add_input(name='tsfc', shape=(nn,), desc='specific fuel consumption', units='1/s') 17 | 18 | # Outputs 19 | self.add_output(name='dXdt:mass_fuel', shape=(nn,), 20 | desc='rate of aircraft mass change - negative when fuel is being depleted', 21 | units='kg/s') 22 | 23 | # Partials 24 | ar = np.arange(nn) 25 | self.declare_partials('dXdt:mass_fuel', 'thrust', rows=ar, cols=ar) 26 | self.declare_partials('dXdt:mass_fuel', 'tsfc', rows=ar, cols=ar) 27 | 28 | def compute(self, inputs, outputs): 29 | outputs['dXdt:mass_fuel'] = -inputs['tsfc'] * inputs['thrust'] / 9.80665 30 | 31 | def compute_partials(self, inputs, partials): 32 | partials['dXdt:mass_fuel', 'thrust'] = -inputs['tsfc'] / 9.80665 33 | partials['dXdt:mass_fuel', 'tsfc'] = -inputs['thrust'] / 9.80665 34 | -------------------------------------------------------------------------------- /dymos/utils/test/test_lg.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from numpy.testing import assert_almost_equal 4 | 5 | from dymos.utils.lg import lg 6 | 7 | # Known solutions 8 | x_i = {2: [-0.57735, 0.57735], 9 | 3: [-0.774597, 0.0, 0.774597], 10 | 4: [-0.861136, -0.339981, 0.339981, 0.861136], 11 | 5: [-0.90618, -0.538469, 0.0, 0.538469, 0.90618]} 12 | 13 | w_i = {2: [1.0, 1.0], 14 | 3: [0.555556, 0.888889, 0.555556], 15 | 4: [0.347855, 0.652145, 0.652145, 0.347855], 16 | 5: [0.236927, 0.478629, 0.568889, 0.478629, 0.236927]} 17 | 18 | 19 | class TestLG(unittest.TestCase): 20 | 21 | def test_nodes_and_weights_2(self): 22 | x_2, w_2 = lg(2) 23 | assert_almost_equal(x_2, x_i[2], decimal=6) 24 | assert_almost_equal(w_2, w_i[2], decimal=6) 25 | 26 | def test_nodes_and_weights_3(self): 27 | x_3, w_3 = lg(3) 28 | assert_almost_equal(x_3, x_i[3], decimal=6) 29 | assert_almost_equal(w_3, w_i[3], decimal=6) 30 | 31 | def test_nodes_and_weights_4(self): 32 | x_4, w_4 = lg(4) 33 | assert_almost_equal(x_4, x_i[4], decimal=6) 34 | assert_almost_equal(w_4, w_i[4], decimal=6) 35 | 36 | def test_nodes_and_weights_5(self): 37 | x_5, w_5 = lg(5) 38 | assert_almost_equal(x_5, x_i[5], decimal=6) 39 | assert_almost_equal(w_5, w_i[5], decimal=6) 40 | 41 | 42 | if __name__ == '__main__': # pragma: no cover 43 | unittest.main() 44 | -------------------------------------------------------------------------------- /dymos/examples/aircraft_steady_flight/propulsion/tsfc_comp.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import openmdao.api as om 4 | 5 | 6 | class SFCComp(om.ExplicitComponent): 7 | """ Compute the specific fuel consumption based on the altitude 8 | and the sea-level specific fuel consumption. 9 | """ 10 | def initialize(self): 11 | self.options.declare('num_nodes', types=int) 12 | 13 | def setup(self): 14 | nn = self.options['num_nodes'] 15 | 16 | self.Ka = 1.5E-4 * 9.80665 # Altitude correction factor 17 | 18 | # Inputs 19 | self.add_input(name='tsfc_sl', 20 | desc='sea-level specific fuel consumption', 21 | units='1/s') 22 | 23 | self.add_input(name='alt', 24 | shape=(nn,), 25 | desc='altitude', 26 | units='m') 27 | 28 | # Outputs 29 | self.add_output(name='tsfc', 30 | val=np.zeros(nn), 31 | desc='specific fuel consumption', 32 | units='1/s') 33 | 34 | # Partials 35 | ar = np.arange(nn) 36 | self.declare_partials('tsfc', 'tsfc_sl', rows=ar, cols=np.zeros(nn), val=1.0) 37 | self.declare_partials('tsfc', 'alt', rows=ar, cols=ar, val=-1.0E-6 * self.Ka) 38 | 39 | def compute(self, inputs, outputs): 40 | outputs['tsfc'] = inputs['tsfc_sl'] - 1.0E-6 * self.Ka * inputs['alt'] 41 | -------------------------------------------------------------------------------- /dymos/examples/aircraft_steady_flight/propulsion/max_thrust_comp.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import openmdao.api as om 4 | 5 | 6 | class MaxThrustComp(om.ExplicitComponent): 7 | """ Compute the maximum thrust given the current aircraft state and its 8 | maximum sea-level thrust with a simple pressure correction. 9 | """ 10 | def initialize(self): 11 | self.options.declare('num_nodes', types=int) 12 | 13 | def setup(self): 14 | nn = self.options['num_nodes'] 15 | 16 | self.pres_sl = 101325.0 # Pa 17 | 18 | self.add_input(name='pres', shape=(nn,), desc='atmospheric pressure', units='Pa') 19 | 20 | self.add_input(name='max_thrust_sl', shape=(1,), desc='maximum thrust at sea-level', 21 | units='N') 22 | 23 | self.add_output(name='max_thrust', shape=(nn,), desc='maximum thrust at current altitude', 24 | units='N') 25 | 26 | ar = np.arange(nn) 27 | self.declare_partials('max_thrust', 'pres', rows=ar, cols=ar) 28 | self.declare_partials('max_thrust', 'max_thrust_sl', dependent=True) 29 | 30 | def compute(self, inputs, outputs): 31 | outputs['max_thrust'] = inputs['max_thrust_sl'] * inputs['pres'] / self.pres_sl 32 | 33 | def compute_partials(self, inputs, partials): 34 | partials['max_thrust', 'pres'] = inputs['max_thrust_sl'] / self.pres_sl 35 | partials['max_thrust', 'max_thrust_sl'] = inputs['pres'] / self.pres_sl 36 | -------------------------------------------------------------------------------- /dymos/examples/aircraft_steady_flight/range_rate_comp.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import openmdao.api as om 3 | 4 | 5 | class RangeRateComp(om.ExplicitComponent): 6 | """ 7 | Calculates range rate based on true airspeed and flight path angle. 8 | """ 9 | 10 | def initialize(self): 11 | self.options.declare('num_nodes', types=int) 12 | 13 | def setup(self): 14 | nn = self.options['num_nodes'] 15 | 16 | self.add_input('TAS', val=np.ones(nn), desc='True airspeed', units='m/s') 17 | 18 | self.add_input('gam', val=np.zeros(nn), desc='Flight path angle', units='rad') 19 | 20 | self.add_output('dXdt:range', val=np.ones(nn), desc='Velocity along the ground (no wind)', 21 | units='m/s') 22 | 23 | # Setup partials 24 | ar = np.arange(self.options['num_nodes']) 25 | self.declare_partials(of='*', wrt='*', dependent=False) 26 | self.declare_partials(of='dXdt:range', wrt='TAS', rows=ar, cols=ar) 27 | self.declare_partials(of='dXdt:range', wrt='gam', rows=ar, cols=ar) 28 | 29 | def compute(self, inputs, outputs): 30 | TAS = inputs['TAS'] 31 | gam = inputs['gam'] 32 | outputs['dXdt:range'] = TAS*np.cos(gam) 33 | 34 | def compute_partials(self, inputs, partials): 35 | TAS = inputs['TAS'] 36 | gam = inputs['gam'] 37 | 38 | partials['dXdt:range', 'TAS'] = np.cos(gam) 39 | partials['dXdt:range', 'gam'] = -TAS * np.sin(gam) 40 | -------------------------------------------------------------------------------- /dymos/utils/test/test_indexing.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import numpy as np 4 | from numpy.testing import assert_array_equal 5 | 6 | from dymos.utils.indexing import get_src_indices_by_row 7 | 8 | 9 | class TestIndexing(unittest.TestCase): 10 | 11 | def test_get_src_indices_by_row_vector_target(self): 12 | row_idxs = (0, 1, 2) 13 | shape = (5,) 14 | idxs = get_src_indices_by_row(row_idxs, shape, flat=True) 15 | expected = np.reshape(np.arange(15, dtype=int), (3, 5)) 16 | assert_array_equal(idxs, expected) 17 | 18 | def test_get_src_indices_by_row_matrix_target(self): 19 | row_idxs = (0, 2, 4) 20 | shape = (5, 2) 21 | idxs = get_src_indices_by_row(row_idxs, shape, flat=True) 22 | 23 | expected = np.zeros((3, 5, 2), dtype=int) 24 | expected[0, ...] = np.reshape(np.arange(10, dtype=int), newshape=shape) 25 | expected[1, ...] = expected[0, ...] + 20 26 | expected[2, ...] = expected[0, ...] + 40 27 | 28 | assert_array_equal(idxs, expected) 29 | 30 | def test_get_src_indices_by_row_raises_if_not_flat(self): 31 | 32 | with self.assertRaises(NotImplementedError) as e: 33 | get_src_indices_by_row((0, 1, 2), (3, 3), flat=False) 34 | 35 | self.assertEqual(str(e.exception), 36 | 'Currently get_src_indices_by_row only returns flat source indices.') 37 | 38 | 39 | if __name__ == '__main__': # pragma: no cover 40 | unittest.main() 41 | -------------------------------------------------------------------------------- /dymos/examples/aircraft_steady_flight/steady_flight_path_angle_comp.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import openmdao.api as om 4 | 5 | 6 | class SteadyFlightPathAngleComp(om.ExplicitComponent): 7 | """ Compute the flight path angle (gamma) based on true airspeed and climb rate. """ 8 | 9 | def initialize(self): 10 | self.options.declare('num_nodes', types=int) 11 | 12 | def setup(self): 13 | nn = self.options['num_nodes'] 14 | self.add_input('TAS', val=np.zeros(nn), desc='true airspeed', units='m/s') 15 | self.add_input('climb_rate', val=np.zeros(nn), desc='climb rate', units='m/s') 16 | self.add_output('gam', val=np.zeros(nn), desc='flight path angle', units='rad') 17 | 18 | # Setup partials 19 | ar = np.arange(self.options['num_nodes']) 20 | 21 | self.declare_partials(of='gam', wrt='TAS', rows=ar, cols=ar) 22 | self.declare_partials(of='gam', wrt='climb_rate', rows=ar, cols=ar) 23 | 24 | def compute(self, inputs, outputs): 25 | ratio = np.clip(inputs['climb_rate'] / inputs['TAS'], -1.0, 1.0) 26 | outputs['gam'] = np.arcsin(ratio) 27 | 28 | def compute_partials(self, inputs, partials): 29 | tas = inputs['TAS'] 30 | h_rate = inputs['climb_rate'] 31 | 32 | k = np.sqrt(1 - (h_rate / tas)**2) 33 | 34 | dgam_dhdot = 1.0 / (tas * k) 35 | dgam_dtas = -h_rate / (k * tas**2) 36 | 37 | partials['gam', 'TAS'] = dgam_dtas 38 | partials['gam', 'climb_rate'] = dgam_dhdot 39 | -------------------------------------------------------------------------------- /dymos/examples/min_time_climb/aero/cd0_comp.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import openmdao.api as om 4 | 5 | 6 | class CD0Comp(om.ExplicitComponent): 7 | """ Computes the zero-lift drag coefficient 8 | """ 9 | def initialize(self): 10 | self.options.declare('num_nodes', types=int) 11 | 12 | def setup(self): 13 | nn = self.options['num_nodes'] 14 | 15 | # Inputs 16 | self.add_input('mach', shape=(nn,), desc='Mach number', units=None) 17 | 18 | # Outputs 19 | self.add_output(name='CD0', val=np.zeros(nn), desc='zero-lift drag coefficient', units=None) 20 | 21 | # Jacobian 22 | ar = np.arange(nn) 23 | self.declare_partials(of='CD0', wrt='mach', rows=ar, cols=ar) 24 | 25 | def compute(self, inputs, outputs): 26 | M = inputs['mach'] 27 | 28 | idx_low = np.where(M < 1.15)[0] 29 | idx_high = np.where(M >= 1.15)[0] 30 | 31 | outputs['CD0'][idx_low] = 0.013 + 0.0144 * (1.0 + np.tanh((M[idx_low] - 0.98) / 0.06)) 32 | outputs['CD0'][idx_high] = 0.013 + \ 33 | 0.0144 * (1.0 + np.tanh(0.17 / 0.06)) - 0.011 * (M[idx_high] - 1.15) 34 | 35 | def compute_partials(self, inputs, partials): 36 | M = inputs['mach'] 37 | 38 | idx_low = np.where(M < 1.15)[0] 39 | idx_high = np.where(M >= 1.15)[0] 40 | 41 | k = 50.0 / 3.0 42 | 43 | partials['CD0', 'mach'][idx_low] = 0.24 / (np.cosh(k * (M[idx_low] - 0.98))**2) 44 | partials['CD0', 'mach'][idx_high] = -0.011 45 | -------------------------------------------------------------------------------- /dymos/examples/min_time_climb/min_time_climb_ode.py: -------------------------------------------------------------------------------- 1 | import openmdao.api as om 2 | from ...models.atmosphere import USatm1976Comp 3 | from .aero import AeroGroup 4 | from .prop import PropGroup 5 | from ...models.eom import FlightPathEOM2D 6 | 7 | 8 | class MinTimeClimbODE(om.Group): 9 | 10 | def initialize(self): 11 | self.options.declare('num_nodes', types=int) 12 | 13 | def setup(self): 14 | nn = self.options['num_nodes'] 15 | 16 | self.add_subsystem(name='atmos', 17 | subsys=USatm1976Comp(num_nodes=nn), 18 | promotes_inputs=['h']) 19 | 20 | self.add_subsystem(name='aero', 21 | subsys=AeroGroup(num_nodes=nn), 22 | promotes_inputs=['v', 'alpha', 'S']) 23 | 24 | self.connect('atmos.sos', 'aero.sos') 25 | self.connect('atmos.rho', 'aero.rho') 26 | 27 | self.add_subsystem(name='prop', 28 | subsys=PropGroup(num_nodes=nn), 29 | promotes_inputs=['h', 'Isp', 'throttle']) 30 | 31 | self.connect('aero.mach', 'prop.mach') 32 | 33 | self.add_subsystem(name='flight_dynamics', 34 | subsys=FlightPathEOM2D(num_nodes=nn), 35 | promotes_inputs=['m', 'v', 'gam', 'alpha']) 36 | 37 | self.connect('aero.f_drag', 'flight_dynamics.D') 38 | self.connect('aero.f_lift', 'flight_dynamics.L') 39 | self.connect('prop.thrust', 'flight_dynamics.T') 40 | -------------------------------------------------------------------------------- /mkdocs/docs/faq/downstream_analysis.md: -------------------------------------------------------------------------------- 1 | # How do connect the outputs of Dymos to a downstream analysis? 2 | 3 | One of the design goals of Dymos is to allow the trajectory to be a part of a larger multidisciplinary optimization problem. 4 | Sometimes, you may want to take the results from the Dymos trajectory and feed them to some downstream analysis. 5 | 6 | In the case of only being concerned with the final value of some parameter, this can be accomplished by connecting the relevant output from 7 | the timeseries with `src_indices=[-1]`. 8 | 9 | For example, something like the following might be used to connect the final value of the state `range` to some downstream component. 10 | 11 | ``` 12 | problem.model.connect('trajectory.phase0.timeseries.states:range', 13 | 'postprocess.final_range', 14 | src_indices=[-1]) 15 | ``` 16 | 17 | !!! note 18 | We _highly_ recommend you use the phase timeseries outputs to retrieve outputs from Dymos, since it is transcription-indepdendent. 19 | 20 | When the downstream analysis requires the entire trajectory, things get slightly more complicated. 21 | We need to know the number of nodes in a phase when we're building the problem, so the downstream component knows how many values of a variable to expect. 22 | To do this, we can initialize the transcription object and obtain the total number of nodes in the phase using the transcriptions `grid_data.num_nodes` attribute. 23 | The [length-constrained brachistochrone example](../examples/length_constrained_brachistochrone/length_constrained_brachistochrone.md) demonstrates how to do this. -------------------------------------------------------------------------------- /dymos/examples/min_time_climb/aero/cd_comp.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import openmdao.api as om 4 | 5 | 6 | class CDComp(om.ExplicitComponent): 7 | 8 | def initialize(self): 9 | self.options.declare('num_nodes', types=int) 10 | 11 | def setup(self): 12 | nn = self.options['num_nodes'] 13 | 14 | # Inputs 15 | self.add_input('CD0', shape=(nn,), desc='zero-lift drag coefficient', units=None) 16 | self.add_input('CLa', shape=(nn,), desc='alpha lift coefficient', units=None) 17 | self.add_input('kappa', shape=(nn,), desc='induced drag coefficient', units=None) 18 | self.add_input('alpha', shape=(nn,), desc='angle of attack', units='rad') 19 | 20 | # Outputs 21 | self.add_output(name='CD', val=np.zeros(nn), desc='drag coefficient', units=None) 22 | 23 | # Jacobian 24 | ar = np.arange(nn) 25 | self.declare_partials(of='CD', wrt='CD0', rows=ar, cols=ar, val=1.0) 26 | self.declare_partials(of='CD', wrt='CLa', rows=ar, cols=ar) 27 | self.declare_partials(of='CD', wrt='alpha', rows=ar, cols=ar) 28 | self.declare_partials(of='CD', wrt='kappa', rows=ar, cols=ar) 29 | 30 | def compute(self, inputs, outputs): 31 | outputs['CD'] = inputs['CD0'] + inputs['CLa'] * inputs['kappa'] * inputs['alpha']**2 32 | 33 | def compute_partials(self, inputs, partials): 34 | partials['CD', 'CLa'] = inputs['kappa'] * inputs['alpha']**2 35 | partials['CD', 'alpha'] = 2.0 * inputs['CLa'] * inputs['kappa'] * inputs['alpha'] 36 | partials['CD', 'kappa'] = inputs['CLa'] * inputs['alpha']**2 37 | -------------------------------------------------------------------------------- /dymos/examples/min_time_climb/aero/cla_comp.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import openmdao.api as om 4 | 5 | 6 | class CLaComp(om.ExplicitComponent): 7 | """ Computes the alpha lift coefficient for induced drag 8 | 9 | """ 10 | def initialize(self): 11 | self.options.declare('num_nodes', types=int) 12 | 13 | def setup(self): 14 | nn = self.options['num_nodes'] 15 | 16 | # Inputs 17 | self.add_input('mach', shape=(nn,), desc='Mach number', units=None) 18 | 19 | # Outputs 20 | self.add_output(name='CLa', val=np.ones(nn), desc='alpha lift coefficient', units=None) 21 | 22 | # Jacobian 23 | ar = np.arange(nn) 24 | self.declare_partials(of='CLa', wrt='mach', rows=ar, cols=ar) 25 | 26 | def compute(self, inputs, outputs): 27 | M = inputs['mach'] 28 | 29 | idx_low = np.where(M < 1.15)[0] 30 | idx_high = np.where(M >= 1.15)[0] 31 | 32 | c2_low = 1.0 / np.cosh((M[idx_low] - 1.0) / 0.06)**2 33 | c2_high = 1.0 / np.cosh(0.15 / 0.06)**2 34 | 35 | outputs['CLa'][idx_low] = 3.44 + c2_low 36 | outputs['CLa'][idx_high] = 3.44 + c2_high - 0.96 / 0.63 * (M[idx_high] - 1.15) 37 | 38 | def compute_partials(self, inputs, partials): 39 | M = inputs['mach'] 40 | 41 | idx_low = np.where(M < 1.15)[0] 42 | idx_high = np.where(M >= 1.15)[0] 43 | 44 | k = 50.0 / 3.0 45 | tanh = np.tanh(k * (M[idx_low] - 1.0)) 46 | sech2 = 1.0 - tanh**2 47 | 48 | partials['CLa', 'mach'][idx_low] = -2.0 * k * tanh * sech2 49 | partials['CLa', 'mach'][idx_high] = -32.0 / 21.0 50 | -------------------------------------------------------------------------------- /dymos/examples/min_time_climb/aero/kappa_comp.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import openmdao.api as om 4 | 5 | 6 | class KappaComp(om.ExplicitComponent): 7 | r""" Computes the term kappa in the drag equation: 8 | 9 | .. math:: 10 | 11 | C_D = C_{D0} + \kappa C_{L\alpha} \alpha^2 12 | 13 | """ 14 | def initialize(self): 15 | self.options.declare('num_nodes', types=int) 16 | 17 | def setup(self): 18 | nn = self.options['num_nodes'] 19 | 20 | # Inputs 21 | self.add_input('mach', shape=(nn,), desc='Mach number', units=None) 22 | 23 | # Outputs 24 | self.add_output(name='kappa', val=np.zeros(nn), desc='induced drag coefficient', units=None) 25 | 26 | # Jacobian 27 | ar = np.arange(nn) 28 | self.declare_partials(of='kappa', wrt='mach', rows=ar, cols=ar) 29 | 30 | def compute(self, inputs, outputs): 31 | M = inputs['mach'] 32 | 33 | idx_low = np.where(M < 1.15)[0] 34 | idx_high = np.where(M >= 1.15)[0] 35 | 36 | outputs['kappa'][idx_low] = 0.54 + 0.15 * (1.0 + np.tanh((M[idx_low] - 0.9) / 0.06)) 37 | outputs['kappa'][idx_high] = 0.54 + 0.15 * (1.0 + np.tanh(0.25 / 0.06)) \ 38 | + 0.14 * (M[idx_high] - 1.15) 39 | 40 | def compute_partials(self, inputs, partials): 41 | M = inputs['mach'] 42 | 43 | idx_low = np.where(M < 1.15)[0] 44 | idx_high = np.where(M >= 1.15)[0] 45 | 46 | k = 50.0 / 3.0 47 | tanh = np.tanh(k * (M[idx_low] - 0.9)) 48 | sech2 = 1.0 - tanh**2 49 | 50 | partials['kappa', 'mach'][idx_low] = 2.5 * sech2 51 | partials['kappa', 'mach'][idx_high] = 0.14 52 | -------------------------------------------------------------------------------- /dymos/examples/hyper_sensitive/hyper_sensitive_ode.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import openmdao.api as om 3 | 4 | 5 | class HyperSensitiveODE(om.ExplicitComponent): 6 | states = {'x': {'rate_source': 'x_dot'}, 7 | 'xL': {'rate_source': 'L'}} 8 | 9 | parameters = {'u': {'targets': 'u'}} 10 | 11 | def initialize(self): 12 | self.options.declare('num_nodes', types=int) 13 | 14 | def setup(self): 15 | nn = self.options['num_nodes'] 16 | 17 | # inputs 18 | self.add_input('x', val=np.zeros(nn), desc='state') 19 | self.add_input('xL', val=np.zeros(nn), desc='cost_state') 20 | 21 | self.add_input('u', val=np.zeros(nn), desc='control') 22 | 23 | self.add_output('x_dot', val=np.zeros(nn), desc='state rate', units='1/s') 24 | self.add_output('L', val=np.zeros(nn), desc='Lagrangian', units='1/s') 25 | 26 | # Setup partials 27 | self.declare_partials(of='x_dot', wrt='x', rows=np.arange(nn), cols=np.arange(nn), val=-1) 28 | self.declare_partials(of='x_dot', wrt='u', rows=np.arange(nn), cols=np.arange(nn), val=1) 29 | 30 | self.declare_partials(of='L', wrt='x', rows=np.arange(nn), cols=np.arange(nn)) 31 | self.declare_partials(of='L', wrt='u', rows=np.arange(nn), cols=np.arange(nn)) 32 | 33 | def compute(self, inputs, outputs): 34 | x = inputs['x'] 35 | u = inputs['u'] 36 | 37 | outputs['x_dot'] = -x + u 38 | outputs['L'] = (x ** 2 + u ** 2) / 2 39 | 40 | def compute_partials(self, inputs, jacobian): 41 | x = inputs['x'] 42 | u = inputs['u'] 43 | 44 | jacobian['L', 'x'] = x 45 | jacobian['L', 'u'] = u 46 | -------------------------------------------------------------------------------- /dymos/examples/min_time_climb/aero/test/test_kappa_comp.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import numpy as np 4 | 5 | import openmdao.api as om 6 | from openmdao.utils.assert_utils import assert_near_equal 7 | from dymos.utils.testing_utils import assert_check_partials 8 | from dymos.examples.min_time_climb.aero.kappa_comp import KappaComp 9 | 10 | 11 | class TestKappaComp(unittest.TestCase): 12 | 13 | def test_value(self): 14 | n = 500 15 | p = om.Problem() 16 | p.model.add_subsystem(name='kappa_comp', subsys=KappaComp(num_nodes=n)) 17 | p.setup() 18 | p.set_val('kappa_comp.mach', np.linspace(0, 1.8, n)) 19 | p.run_model() 20 | 21 | M = p.get_val('kappa_comp.mach') 22 | kappa = p.get_val('kappa_comp.kappa') 23 | 24 | idxs_0 = np.where(M <= 1.15)[0] 25 | idxs_1 = np.where(M > 1.15)[0] 26 | 27 | kappa_analtic_0 = 0.54 + 0.15 * (1.0 + np.tanh((M[idxs_0] - 0.9)/0.06)) 28 | kappa_analtic_1 = 0.54 + 0.15 * (1.0 + np.tanh(0.25/0.06)) + 0.14 * (M[idxs_1] - 1.15) 29 | 30 | assert_near_equal(kappa[idxs_0], kappa_analtic_0) 31 | assert_near_equal(kappa[idxs_1], kappa_analtic_1) 32 | 33 | def test_partials(self): 34 | n = 10 35 | p = om.Problem(model=om.Group()) 36 | p.model.add_subsystem(name='kappa_comp', subsys=KappaComp(num_nodes=n)) 37 | p.setup() 38 | p.set_val('kappa_comp.mach', np.linspace(0, 1.8, n)) 39 | p.run_model() 40 | cpd = p.check_partials(compact_print=False, out_stream=None) 41 | assert_check_partials(cpd, atol=1.0E-5, rtol=1.0E-4) 42 | 43 | 44 | if __name__ == '__main__': # pragma: no cover 45 | unittest.main() 46 | -------------------------------------------------------------------------------- /dymos/test/test_pep8.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import pep8 4 | import unittest 5 | from io import StringIO 6 | 7 | import dymos 8 | 9 | EXCLUDE_FILES = ['crm_data.py'] 10 | 11 | 12 | def _discover_python_files(path): 13 | """ 14 | Recursively walk through the path and find all python files. 15 | 16 | Parameters 17 | ---------- 18 | path : str 19 | The path to be traversed. 20 | 21 | Returns 22 | ------- 23 | list 24 | All the python files contained within the given path 25 | 26 | """ 27 | python_files = [] 28 | for root, dirs, files in os.walk(path): 29 | python_files += [os.path.join(root, file) for file in files if file.endswith('.py')] 30 | return python_files 31 | 32 | 33 | class TestPep8(unittest.TestCase): 34 | 35 | def test_pep8(self): 36 | """ Tests that all files in this, directory, the parent directory, and test 37 | sub-directories are PEP8 compliant. 38 | 39 | Notes 40 | ----- 41 | max_line_length has been set to 130 for this test. 42 | """ 43 | dymos_path = os.path.split(dymos.__file__)[0] 44 | pyfiles = _discover_python_files(dymos_path) 45 | 46 | style = pep8.StyleGuide(ignore=['E201', 'E226', 'E241', 'E402', 'E731']) 47 | style.options.max_line_length = 130 48 | 49 | save = sys.stdout 50 | sys.stdout = msg = StringIO() 51 | try: 52 | report = style.check_files(pyfiles) 53 | finally: 54 | sys.stdout = save 55 | 56 | if report.total_errors > 0: 57 | self.fail("Found pep8 errors:\n%s" % msg.getvalue()) 58 | 59 | 60 | if __name__ == '__main__': # pragma: no cover 61 | unittest.main() 62 | -------------------------------------------------------------------------------- /dymos/examples/plotting.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | 3 | 4 | def plot_results(axes, title, figsize=(10, 8), p_sol=None, p_sim=None): 5 | """ 6 | Plot the timeseries results of a Dymos problem using matplotlib. 7 | 8 | Parameters 9 | ---------- 10 | axes : list of tuple of (str, str, str, str) 11 | A sequence of tuples wherein each tuple contains ('x path', 'y path' , 'x label', 'y label'). 12 | title : str 13 | The figure title 14 | figsize : tuple of (int, int) 15 | The size of the created figure, in inches 16 | p_sol : The solution problem instance. 17 | p_sim : The simulation problem instance. 18 | 19 | Returns 20 | ------- 21 | fig, axes 22 | The Figure object and sequence of axes associated with the plot. 23 | 24 | """ 25 | nrows = len(axes) 26 | 27 | fig, axs = plt.subplots(nrows=nrows, ncols=1, figsize=figsize) 28 | fig.suptitle(title) 29 | 30 | if nrows == 1: 31 | axs = [axs] 32 | 33 | for i, (x, y, xlabel, ylabel) in enumerate(axes): 34 | axs[i].plot(p_sol.get_val(x), 35 | p_sol.get_val(y), 36 | marker='o', 37 | ms=4, 38 | linestyle='None', 39 | label='solution' if i == 0 else None) 40 | 41 | axs[i].plot(p_sim.get_val(x), 42 | p_sim.get_val(y), 43 | marker=None, 44 | linestyle='-', 45 | label='simulation' if i == 0 else None) 46 | 47 | axs[i].set_xlabel(xlabel) 48 | axs[i].set_ylabel(ylabel) 49 | fig.suptitle(title) 50 | fig.legend(loc='lower center', ncol=2) 51 | 52 | return fig, axs 53 | -------------------------------------------------------------------------------- /dymos/examples/aircraft_steady_flight/mass_comp.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import openmdao.api as om 4 | 5 | 6 | class MassComp(om.ExplicitComponent): 7 | """ Compute the total mass of the aircraft """ 8 | 9 | def initialize(self): 10 | self.options.declare('num_nodes', types=int) 11 | 12 | def setup(self): 13 | nn = self.options['num_nodes'] 14 | self.add_input('mass_fuel', val=np.ones(nn), desc='fuel mass', units='kg') 15 | self.add_input('mass_empty', val=np.ones(nn), desc='empty aircraft mass', units='kg') 16 | self.add_input('mass_payload', val=np.ones(nn), desc='aircraft payload mass', units='kg') 17 | 18 | self.add_output('mass_total', val=np.ones(nn), desc='total aircraft mass', units='kg') 19 | self.add_output('W_total', val=np.ones(nn), desc='total aircraft weight', units='N') 20 | 21 | # Setup partials 22 | ar = np.arange(self.options['num_nodes']) 23 | 24 | self.declare_partials(of='mass_total', wrt='mass_fuel', rows=ar, cols=ar, val=1.0) 25 | self.declare_partials(of='mass_total', wrt='mass_empty', rows=ar, cols=ar, val=1.0) 26 | self.declare_partials(of='mass_total', wrt='mass_payload', rows=ar, cols=ar, val=1.0) 27 | 28 | self.declare_partials(of='W_total', wrt='mass_fuel', rows=ar, cols=ar, val=9.80665) 29 | self.declare_partials(of='W_total', wrt='mass_empty', rows=ar, cols=ar, val=9.80665) 30 | self.declare_partials(of='W_total', wrt='mass_payload', rows=ar, cols=ar, val=9.80665) 31 | 32 | def compute(self, inputs, outputs): 33 | outputs['mass_total'] = inputs['mass_fuel'] + inputs['mass_empty'] + inputs['mass_payload'] 34 | outputs['W_total'] = 9.80665 * outputs['mass_total'] 35 | -------------------------------------------------------------------------------- /mkdocs/docs/release_notes/version_0.17.0.md: -------------------------------------------------------------------------------- 1 | # dymos 0.17.0 2 | 3 | ## Enhancements 4 | 5 | ### OpenMDAO variable tags may be used to specify rate sources in ODE systems. 6 | 7 | OpenMDAO supports tagging of inputs and outputs. 8 | Dymos now can use this capability to specify the rate source and default units of a state from within the ODE. 9 | The following code demonstrates how to tag state rate sources and provide their default units. 10 | 11 | {{ upgrade_doc('dymos.test.test_upgrade_guide.TestUpgrade_0_17_0.test_tags', 12 | feature='tag_rate_source', 13 | old_label='dymos < 0.17.0', 14 | new_label='dymos >= 0.17.0') }} 15 | 16 | This allows the user to avoid repetitive specification of the rate sources every time a specific ODE is used. 17 | 18 | {{ upgrade_doc('dymos.test.test_upgrade_guide.TestUpgrade_0_17_0.test_tags', 19 | feature='declare_rate_source', 20 | old_label='dymos < 0.17.0', 21 | new_label='dymos >= 0.17.0') }} 22 | 23 | ### Dymos can now automatically generate plots of timeseries outputs. 24 | 25 | Typically Dymos usage featured a lot of boilerplate code for plotting the results after a run and, optionally, a simulation of the resulting control profile. 26 | As of 0.17.0, Dymos can now generate plots of all timeseries outputs by giving the `make_plots=True` option to `dymos.run_problem`. 27 | 28 | ### Dymos now uses introspection to automatically determine shapes of states, controls, and parameters if they are left unspecified. 29 | 30 | In a continuing effort to minimize the amount of boilerplate code required, Dymos can now automatically determine the default units and shapes of states, controls, polynomial controls, parameters, and time. 31 | 32 | -------------------------------------------------------------------------------- /dymos/examples/aircraft_steady_flight/propulsion/thrust_comp.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import openmdao.api as om 4 | 5 | 6 | class ThrustComp(om.ExplicitComponent): 7 | """ Compute thrust from the thrust coefficient 8 | """ 9 | def initialize(self): 10 | self.options.declare('num_nodes', types=int) 11 | 12 | def setup(self): 13 | nn = self.options['num_nodes'] 14 | 15 | # Inputs 16 | self.add_input(name='CT', 17 | shape=(nn,), 18 | desc='thrust coefficient', 19 | units=None) 20 | 21 | self.add_input(name='q', 22 | shape=(nn,), 23 | desc='dynamic pressure', 24 | units='Pa') 25 | 26 | self.add_input(name='S', 27 | shape=(nn,), 28 | desc='reference area', 29 | units='m**2') 30 | 31 | # Outputs 32 | self.add_output(name='thrust', 33 | val=np.zeros(nn), 34 | desc='thrust', 35 | units='N') 36 | 37 | # Partials 38 | ar = np.arange(nn) 39 | self.declare_partials('thrust', 'CT', rows=ar, cols=ar) 40 | self.declare_partials('thrust', 'q', rows=ar, cols=ar) 41 | self.declare_partials('thrust', 'S', rows=ar, cols=ar) 42 | 43 | def compute(self, inputs, outputs): 44 | outputs['thrust'] = inputs['CT'] * inputs['q'] * inputs['S'] 45 | 46 | def compute_partials(self, inputs, partials): 47 | partials['thrust', 'CT'] = inputs['q'] * inputs['S'] 48 | partials['thrust', 'q'] = inputs['CT'] * inputs['S'] 49 | partials['thrust', 'S'] = inputs['CT'] * inputs['q'] 50 | -------------------------------------------------------------------------------- /dymos/examples/aircraft_steady_flight/propulsion/test/test_propulsion_group.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import numpy as np 4 | 5 | import openmdao.api as om 6 | from openmdao.utils.assert_utils import assert_near_equal 7 | from openmdao.utils.testing_utils import use_tempdirs 8 | 9 | from dymos.utils.testing_utils import assert_check_partials 10 | from dymos.examples.aircraft_steady_flight.propulsion.propulsion_group import PropulsionGroup 11 | 12 | 13 | @use_tempdirs 14 | class TestPropulsionComp(unittest.TestCase): 15 | 16 | @classmethod 17 | def setUp(self): 18 | self.n = 10 19 | 20 | self.p = om.Problem(model=om.Group()) 21 | 22 | ivc = self.p.model.add_subsystem('ivc', om.IndepVarComp(), promotes_outputs=['*']) 23 | 24 | ivc.add_output('alt', val=np.zeros(self.n), units='m', desc='altitude above MSL') 25 | 26 | ivc.add_output('pres', val=101325.0*np.ones(self.n), units='Pa', desc='atmospheric_pressure') 27 | 28 | ivc.add_output('CT', val=np.linspace(0, 1.0E4, self.n), units=None, 29 | desc='coefficient of thrust') 30 | 31 | self.p.model.add_subsystem('propulsion', PropulsionGroup(num_nodes=self.n)) 32 | 33 | self.p.model.connect('alt', 'propulsion.alt') 34 | self.p.model.connect('pres', 'propulsion.pres') 35 | self.p.model.connect('CT', 'propulsion.CT') 36 | 37 | self.p.setup(force_alloc_complex=True) 38 | 39 | self.p.run_model() 40 | 41 | def test_results(self): 42 | 43 | assert_near_equal(self.p['propulsion.tsfc'], 44 | 2 * 8.951e-6 * 9.80665 * np.ones(self.n)) 45 | 46 | def test_partials(self): 47 | cpd = self.p.check_partials(method='cs', out_stream=None) 48 | assert_check_partials(cpd) 49 | -------------------------------------------------------------------------------- /mkdocs/docs/api/run_problem.md: -------------------------------------------------------------------------------- 1 | # The Dymos run_problem function 2 | 3 | In the Brachistochrone example we used two methods on the OpenMDAO Problem class to execute the model. 4 | 5 | **run_model** Takes the current model design variables and runs a single execution of the Problem's model. 6 | Any iterative systems are converged, but no optimization is performed. 7 | When using Dymos with an optimizer-driven implicit transcription, `run_model` will **not** produce a physically valid trajectory on output. 8 | If using a solver-driven transcription, the collocation defects will be satisfied (if possible) and the resulting outputs will provide a physically valid trajectory (to the extent possible given the collocation grid). 9 | 10 | 11 | **run_driver** Runs a driver wrapped around the model (typically done for optimization) and repeatedly executes `run_model` until the associated optimization problem is satisfied. 12 | This approach will provide a physically valid trajectory, to the extent that the grid is sufficient to accurately model the dynamics. 13 | But what happens if the grid is not dense enough to accurately capture the physics of the problem. 14 | This is the purpose of _grid refinement_. 15 | There have been numerous methods of grid refinment posed for implcit optimal control techniques. 16 | In general, they follow the following procedure: 17 | 18 | 1. Optimize the trajectory 19 | 2. Assess errors in the solution 20 | 3. Propose a new grid to reduce these errors to an acceptable level. 21 | 4. Repeat until the errors are within some acceptable tolerance. 22 | 23 | This requires another layer of iteration outside of the OpenMDAO `run_driver` method. 24 | This is the original motivation for Dymos' `run_problem` function. 25 | 26 | {{ api_doc('dymos.run_problem.run_problem') }} 27 | -------------------------------------------------------------------------------- /dymos/examples/shuttle_reentry/shuttle_ode.py: -------------------------------------------------------------------------------- 1 | from openmdao.api import Problem, Group 2 | from dymos.examples.shuttle_reentry.heating_comp import AerodynamicHeating 3 | from dymos.examples.shuttle_reentry.flight_dynamics_comp import FlightDynamics 4 | from dymos.examples.shuttle_reentry.aerodynamics_comp import Aerodynamics 5 | from dymos.examples.shuttle_reentry.atmosphere_comp import Atmosphere 6 | 7 | 8 | class ShuttleODE(Group): 9 | """ 10 | The ODE for the Shuttle reentry problem. 11 | 12 | References 13 | ---------- 14 | .. [1] Betts, John T., Practical Methods for Optimal Control and Estimation Using Nonlinear 15 | Programming, p. 248, 2010. 16 | """ 17 | 18 | def initialize(self): 19 | self.options.declare('num_nodes', types=int) 20 | 21 | def setup(self): 22 | nn = self.options['num_nodes'] 23 | 24 | self.add_subsystem('atmosphere', subsys=Atmosphere(num_nodes=nn), 25 | promotes_inputs=['h'], promotes_outputs=['rho']) 26 | self.add_subsystem('aerodynamics', subsys=Aerodynamics(num_nodes=nn), 27 | promotes_inputs=['alpha', 'v', 'rho'], 28 | promotes_outputs=['lift', 'drag']) 29 | self.add_subsystem('heating', subsys=AerodynamicHeating(num_nodes=nn), 30 | promotes_inputs=['rho', 'v', 'alpha'], promotes_outputs=['q']) 31 | self.add_subsystem('eom', subsys=FlightDynamics(num_nodes=nn), 32 | promotes_inputs=['beta', 'gamma', 'h', 'psi', 'theta', 'v', 'lift', 33 | 'drag'], 34 | promotes_outputs=['hdot', 'gammadot', 'phidot', 'psidot', 'thetadot', 35 | 'vdot']) 36 | -------------------------------------------------------------------------------- /dymos/examples/cannonball/size_comp.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import openmdao.api as om 4 | 5 | 6 | class CannonballSizeComp(om.ExplicitComponent): 7 | """ 8 | Compute the reference area and mass of a cannonball with a given radius and density. 9 | 10 | Notes 11 | ----- 12 | This component is not vectorized with 'num_nodes' as is the usual way with Dymos, but is instead 13 | intended to compute a scalar mass and reference area from scalar radius and density inputs. This 14 | component does not reside in the ODE but instead its outputs are connected to the trajectory via 15 | input design parameters. 16 | """ 17 | def setup(self): 18 | self.add_input(name='radius', val=1.0, desc='cannonball radius', units='m') 19 | self.add_input(name='dens', val=7870., desc='cannonball density', units='kg/m**3') 20 | 21 | self.add_output(name='mass', shape=(1,), desc='cannonball mass', units='kg') 22 | self.add_output(name='S', shape=(1,), desc='aerodynamic reference area', units='m**2') 23 | 24 | self.declare_partials(of='mass', wrt='dens') 25 | self.declare_partials(of='mass', wrt='radius') 26 | 27 | self.declare_partials(of='S', wrt='radius') 28 | 29 | def compute(self, inputs, outputs): 30 | radius = inputs['radius'] 31 | dens = inputs['dens'] 32 | 33 | outputs['mass'] = (4/3.) * dens * np.pi * radius ** 3 34 | outputs['S'] = np.pi * radius ** 2 35 | 36 | def compute_partials(self, inputs, partials): 37 | radius = inputs['radius'] 38 | dens = inputs['dens'] 39 | 40 | partials['mass', 'dens'] = (4/3.) * np.pi * radius ** 3 41 | partials['mass', 'radius'] = 4. * dens * np.pi * radius ** 2 42 | 43 | partials['S', 'radius'] = 2 * np.pi * radius 44 | -------------------------------------------------------------------------------- /mkdocs/docs/examples/ssto_moon_polynomial_controls/ssto_moon_polynomial_controls.md: -------------------------------------------------------------------------------- 1 | # SSTO Lunar Ascent with Polynomial Controls 2 | 3 | This example demonstrates the use of polynomial controls in Dymos. 4 | Polynomial controls define the control profile as a *single* polynomial 5 | across the entire phase where the control values are specified at the 6 | Legendre Gauss Lobatto (LGL) nodes in *phase dimensionless time*. These 7 | controls can be of any arbitrary order greater than 1 (linear). 8 | 9 | We've already demonstrated that the optimal single stage ascent in the 10 | absense of an atmosphere follows the linear tangent guidance law. In 11 | this example, we'll change the control parameterization such that 12 | $\tan \theta$ is provided by a polynomial control of order 1. The LGL 13 | nodes of a first order polynomial are the endpoints of the phase, thus 14 | the optimizer will be governing the value of $\tan \theta$ at the 15 | initial and final times of the phase, and the Dymos will interpolate the 16 | values of $\tan \theta$ to all other nodes in the Phase. 17 | 18 | This example is equivalent to the previous linear tangent example in 19 | that we've reduced the problem from finding the appropriate control 20 | value at all nodes to that of finding the optimal value of just two 21 | quantities. But instead of optimizing the slope and intercept given by 22 | the parameters $a$ and $b$, we're parameterizing the control using the 23 | endpoint values of the linear polynomial. 24 | 25 | Now the guidance comp needs to convert the inverse tangent of the 26 | current value of the polynomial controls. 27 | 28 | $$\theta = \arctan{p}$$ 29 | 30 | ## Solving the problem 31 | 32 | {{ embed_test('dymos.examples.ssto.doc.test_doc_ssto_polynomial_control.TestDocSSTOPolynomialControl.test_doc_ssto_polynomial_control') }} 33 | -------------------------------------------------------------------------------- /mkdocs/docs/examples/ssto_moon_linear_tangent/ssto_moon_linear_tangent.md: -------------------------------------------------------------------------------- 1 | # SSTO Lunar Ascent with Linear Tangent Guidance 2 | 3 | The following example implements a minimum time, single-stage to orbit 4 | ascent problem for launching from the lunar surface. Unlike the SSTO 5 | Earth Ascent example, here we use knowledge of the solution to simplify 6 | the optimization. 7 | 8 | Instead of optimizing the thrust angle at any point in time as a dynamic 9 | control, we use our knowledge that the form of the solution is a 10 | _linear tangent_. See section 4.6 of Longiski[@longuski2014optimal] for more 11 | explanation. In short, we've simplified the problem by finding the 12 | optimal value of $\theta$ at many points into optimizing the value of 13 | just two scalar parameters, $a$ and $b$. 14 | 15 | $$\theta = \arctan{\left(a * t + b\right)}$$ 16 | 17 | Implementing this modified control scheme requires only a few changes. 18 | Rather than declaring $\theta$ as a controllable parameter for the ODE system, we implement a new component, _LinearTangentGuidanceComp_ that accepts $a$ and $b$ as parameters to be optimized. 19 | It calculates $\theta$, which is then connected to the equations of motion component. 20 | 21 | ## Extended Design Structure Matrix 22 | 23 | ![The XDSM diagram for the ODE system in the SSTO inear tangent problem.](ssto_linear_tangent_xdsm.png) 24 | 25 | In the XDSM for the ODE system for the SSTO linear tangent problem, the 26 | only significant change is that we have a new component, 27 | _guidance_, which accepts $a$, $b$, and $time$, and computes 28 | $\theta$. 29 | 30 | ## Solving the problem 31 | 32 | {{ embed_test('dymos.examples.ssto.doc.test_doc_ssto_linear_tangent_guidance.TestDocSSTOLinearTangentGuidance.test_doc_ssto_linear_tangent_guidance') }} 33 | 34 | ## References 35 | 36 | \bibliography 37 | -------------------------------------------------------------------------------- /mkdocs/docs/examples/double_integrator/double_integrator.md: -------------------------------------------------------------------------------- 1 | # Double Integrator 2 | 3 | In the double integrator problem, we seek to maximize the distance 4 | traveled by a block (that starts and ends at rest) sliding without 5 | friction along a horizontal surface, with acceleration as the control. 6 | 7 | We minimize the final time, $t_f$, by varying the dynamic control, 8 | $u$, subject to the dynamics: 9 | 10 | \begin{align} 11 | \frac{dx}{dt} &= v \\ 12 | \frac{dv}{dt} &= u 13 | \end{align} 14 | 15 | The initial conditions are 16 | 17 | \begin{align} 18 | x_0 &= 0 \\ 19 | v_0 &= 0 20 | \end{align} 21 | 22 | and the final conditions are 23 | 24 | \begin{align} 25 | x_f &= \rm{free} \\ 26 | v_f &= 0 27 | \end{align} 28 | 29 | The control $u$ is constrained to fall between -1 and 1. Due to the fact 30 | that the control appears linearly in the equations of motion, we should 31 | expect _bang-bang_ behavior in the control (alternation between its extreme values). 32 | 33 | ## The ODE System: double\_integrator\_ode.py 34 | 35 | This problem is unique in that we do not actually have to calculate 36 | anything in the Dymos formulation of the ODE. We create an 37 | _ExplicitComponent_ and provide it with the _num\_nodes_ 38 | option, but it has no inputs and no outputs. The rates for the states 39 | are entirely provided by the other states and controls. 40 | 41 | === "double_integrator_ode.py" 42 | {{ inline_source('dymos.examples.double_integrator.double_integrator_ode', 43 | include_def=True, 44 | include_docstring=True, 45 | indent_level=0) 46 | }} 47 | 48 | ## Building and running the problem 49 | 50 | In order to facilitate the bang-bang behavior in the control, we disable 51 | continuity and rate continuity in the control value. 52 | 53 | {{ embed_test('dymos.examples.double_integrator.doc.test_doc_double_integrator.TestDoubleIntegratorForDocs.test_double_integrator_for_docs') }} 54 | -------------------------------------------------------------------------------- /dymos/examples/aircraft_steady_flight/aero/aero_forces_comp.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import openmdao.api as om 4 | 5 | 6 | class AeroForcesComp(om.ExplicitComponent): 7 | 8 | def initialize(self): 9 | self.options.declare('num_nodes', types=int) 10 | 11 | def setup(self): 12 | nn = self.options['num_nodes'] 13 | 14 | self.add_input('q', val=np.ones(nn), desc='dynamic pressure', units='Pa') 15 | self.add_input('S', val=np.ones(nn), desc='aerodynamic reference area', units='m**2') 16 | self.add_input('CL', val=np.ones(nn), desc='lift coefficient', units=None) 17 | self.add_input('CD', val=np.ones(nn), desc='drag coefficient', units=None) 18 | 19 | self.add_output('L', val=np.ones(nn), desc='lift', units='N') 20 | self.add_output('D', val=np.ones(nn), desc='drag', units='N') 21 | 22 | ar = np.arange(nn) 23 | 24 | self.declare_partials('L', 'q', rows=ar, cols=ar) 25 | self.declare_partials('L', 'S', rows=ar, cols=ar) 26 | self.declare_partials('L', 'CL', rows=ar, cols=ar) 27 | 28 | self.declare_partials('D', 'q', rows=ar, cols=ar) 29 | self.declare_partials('D', 'S', rows=ar, cols=ar) 30 | self.declare_partials('D', 'CD', rows=ar, cols=ar) 31 | 32 | def compute(self, inputs, outputs): 33 | qS = inputs['q'] * inputs['S'] 34 | 35 | outputs['L'] = qS * inputs['CL'] 36 | outputs['D'] = qS * inputs['CD'] 37 | 38 | def compute_partials(self, inputs, partials): 39 | q = inputs['q'] 40 | S = inputs['S'] 41 | CL = inputs['CL'] 42 | CD = inputs['CD'] 43 | qS = inputs['q'] * inputs['S'] 44 | 45 | partials['L', 'q'] = S * CL 46 | partials['L', 'S'] = q * CL 47 | partials['L', 'CL'] = qS 48 | 49 | partials['D', 'q'] = S * CD 50 | partials['D', 'S'] = q * CD 51 | partials['D', 'CD'] = qS 52 | -------------------------------------------------------------------------------- /dymos/utils/test/test_lgl.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import numpy as np 4 | from numpy.testing import assert_almost_equal 5 | 6 | from dymos.utils.lgl import lgl 7 | 8 | # Known solutions 9 | x_i = {2: [-1.0, 1.0], 10 | 3: [-1.0, 0.0, 1.0], 11 | 4: [-1.0, -np.sqrt(5)/5, np.sqrt(5)/5, 1.0], 12 | 5: [-1.0, -np.sqrt(21)/7, 0.0, np.sqrt(21)/7, 1.0], 13 | 6: [-1.0, -np.sqrt((7 + 2 * np.sqrt(7)) / 21), -np.sqrt((7 - 2 * np.sqrt(7)) / 21), 14 | np.sqrt((7 - 2 * np.sqrt(7)) / 21), np.sqrt((7 + 2 * np.sqrt(7)) / 21), 1.0]} 15 | 16 | w_i = {2: [1.0, 1.0], 17 | 3: [1/3, 4/3, 1/3], 18 | 4: [1/6, 5/6, 5/6, 1/6], 19 | 5: [1/10, 49/90, 32/45, 49/90, 1/10], 20 | 6: [1/15, (14 - np.sqrt(7))/30, (14 + np.sqrt(7))/30, 21 | (14 + np.sqrt(7))/30, (14 - np.sqrt(7))/30, 1/15]} 22 | 23 | 24 | class TestLGL(unittest.TestCase): 25 | 26 | def test_nodes_and_weights_2(self): 27 | x_2, w_2 = lgl(2) 28 | assert_almost_equal(x_2, x_i[2], decimal=6) 29 | assert_almost_equal(w_2, w_i[2], decimal=6) 30 | 31 | def test_nodes_and_weights_3(self): 32 | x_3, w_3 = lgl(3) 33 | assert_almost_equal(x_3, x_i[3], decimal=6) 34 | assert_almost_equal(w_3, w_i[3], decimal=6) 35 | 36 | def test_nodes_and_weights_4(self): 37 | x_4, w_4 = lgl(4) 38 | assert_almost_equal(x_4, x_i[4], decimal=6) 39 | assert_almost_equal(w_4, w_i[4], decimal=6) 40 | 41 | def test_nodes_and_weights_5(self): 42 | x_5, w_5 = lgl(5) 43 | assert_almost_equal(x_5, x_i[5], decimal=6) 44 | assert_almost_equal(w_5, w_i[5], decimal=6) 45 | 46 | def test_nodes_and_weights_6(self): 47 | x_6, w_6 = lgl(6) 48 | assert_almost_equal(x_6, x_i[6], decimal=6) 49 | assert_almost_equal(w_6, w_i[6], decimal=6) 50 | 51 | 52 | if __name__ == '__main__': # pragma: no cover 53 | unittest.main() 54 | -------------------------------------------------------------------------------- /mkdocs/docs/examples/min_time_climb/min_time_climb.md: -------------------------------------------------------------------------------- 1 | # Supersonic Interceptor Minimum Time Climb 2 | 3 | This example is based on the _A/C Min Time to Climb_ example given in 4 | chapter 4 of Bryson[@bryson1999dynamic]. It finds the 5 | angle-of-attack history required to accelerate a supersonic interceptor 6 | from near ground level, Mach 0.4 to an altitude of 20 km and Mach 1.0. 7 | 8 | ![The free-body-diagram of the min-time-climb problem.](min_time_climb_fbd.png) 9 | 10 | The vehicle dynamics are given by 11 | 12 | \begin{align} 13 | \frac{dv}{dt} &= \frac{T}{m} \cos \alpha - \frac{D}{m} - g \sin \gamma \\ 14 | \frac{d\gamma}{dt} &= \frac{T}{m v} \sin \alpha + \frac{L}{m v} - \frac{g \cos \gamma}{v} \\ 15 | \frac{dh}{dt} &= v \sin \gamma \\ 16 | \frac{dr}{dt} &= v \cos \gamma \\ 17 | \frac{dm}{dt} &= - \frac{T}{g I_{sp}} 18 | \end{align} 19 | 20 | The initial conditions are 21 | 22 | \begin{align} 23 | r_0 &= 0 \rm{\,m} \\ 24 | h_0 &= 100 \rm{\,m} \\ 25 | v_0 &= 135.964 \rm{\,m/s} \\ 26 | \gamma_0 &= 0 \rm{\,deg} \\ 27 | m_0 &= 19030.468 \rm{\,kg} 28 | \end{align} 29 | 30 | and the final conditions are 31 | 32 | \begin{align} 33 | h_f &= 20000 \rm{\,m} \\ 34 | M_f &= 1.0 \\ 35 | \gamma_0 &= 0 \rm{\,deg} 36 | \end{align} 37 | 38 | ## The ODE System: min_time_climb_ode.py 39 | 40 | The top level ODE definition is a _Group_ that connects several subsystems. 41 | 42 | === "min_time_climb.min_time_climb_ode.py" 43 | {{ inline_source('dymos.examples.min_time_climb.min_time_climb_ode', 44 | include_def=True, 45 | include_docstring=True, 46 | indent_level=0) 47 | }} 48 | 49 | ## Building and running the problem 50 | 51 | In the following code we follow the following process to solve the 52 | problem: 53 | 54 | {{ embed_test('dymos.examples.min_time_climb.doc.test_doc_min_time_climb.TestMinTimeClimbForDocs.test_min_time_climb_for_docs_gauss_lobatto') }} 55 | 56 | ## References 57 | 58 | \bibliography 59 | -------------------------------------------------------------------------------- /dymos/examples/battery_multibranch/battery_multibranch_ode.py: -------------------------------------------------------------------------------- 1 | """ 2 | ODE for example that shows how to use multiple phases in Dymos to model failure of a battery cell 3 | in a simple electrical system. 4 | """ 5 | import numpy as np 6 | import openmdao.api as om 7 | 8 | from dymos.examples.battery_multibranch.batteries import Battery 9 | from dymos.examples.battery_multibranch.motors import Motors 10 | 11 | 12 | class BatteryODE(om.Group): 13 | 14 | def initialize(self): 15 | self.options.declare('num_nodes', default=1) 16 | self.options.declare('num_battery', default=3) 17 | self.options.declare('num_motor', default=3) 18 | 19 | def setup(self): 20 | num_nodes = self.options['num_nodes'] 21 | num_battery = self.options['num_battery'] 22 | num_motor = self.options['num_motor'] 23 | 24 | self.add_subsystem(name='pwr_balance', 25 | subsys=om.BalanceComp(name='I_Li', val=1.0*np.ones(num_nodes), 26 | rhs_name='pwr_out_batt', 27 | lhs_name='P_pack', 28 | units='A', eq_units='W', lower=0.0, upper=50.)) 29 | 30 | self.add_subsystem('battery', Battery(num_nodes=num_nodes, n_parallel=num_battery), 31 | promotes_inputs=['SOC'], 32 | promotes_outputs=['dXdt:SOC']) 33 | 34 | self.add_subsystem('motors', Motors(num_nodes=num_nodes, n_parallel=num_motor)) 35 | 36 | self.connect('battery.P_pack', 'pwr_balance.P_pack') 37 | self.connect('motors.power_in_motor', 'pwr_balance.pwr_out_batt') 38 | self.connect('pwr_balance.I_Li', 'battery.I_Li') 39 | self.connect('battery.I_pack', 'motors.current_in_motor') 40 | 41 | self.nonlinear_solver = om.NewtonSolver(solve_subsystems=False, maxiter=20) 42 | self.linear_solver = om.DirectSolver() 43 | -------------------------------------------------------------------------------- /mkdocs/docs/faq/connect_scalar_parameters_to_ode.md: -------------------------------------------------------------------------------- 1 | # How do I connect a scalar input to the ODE? 2 | 3 | By default, we recommend that users treat all ODE input variables as if they are _potentially_ dynamic. 4 | This allows the user to use the input as either a dynamic control, or as a static design or input parameter. 5 | By default, parameters will "fan" the value out to all nodes. 6 | This allows the partials to be defined in a consistent fashion (generally a diagonal matrix for a scalar input and output) regardless of whether the input is static or dynamic. 7 | 8 | **But** there are some cases in which the user may know that a variable will never have the potential to change throughout the trajectory. 9 | In these cases, we can reduce a bit of the data transfer OpenMDAO needs to perform by defining the input as a scalar in the ODE, rather than sizing it based on the number of nodes. 10 | 11 | ## The Brachistochrone with a static input. 12 | 13 | The local gravity `g` in the brachistochrone problem makes a good candidate for a static input parameter. 14 | The brachistochrone generally won't be in an environment where the local acceleration of gravity is varying by any significant amount. 15 | 16 | 17 | In the slightly modified brachistochrone example below, we add a new option to the BrachistochroneODE `static_gravity` that allows us to decide whether gravity is a vectorized input or a scalar input to the ODE. 18 | 19 | === "brachistochrone_ode.py" 20 | {{ inline_source('dymos.examples.brachistochrone.brachistochrone_ode', 21 | include_def=True, 22 | include_docstring=True, 23 | indent_level=0) 24 | }} 25 | 26 | In the corresponding run script, we pass `{'static_gravity': True}` as one of the `ode_init_kwargs` to the Phase, and declare $g$ as a static design variable using the `dynamic=False` argument. 27 | 28 | {{ embed_test('dymos.examples.brachistochrone.doc.test_doc_brachistochrone_static_gravity.TestBrachistochroneStaticGravity.test_brachistochrone_static_gravity') }} -------------------------------------------------------------------------------- /dymos/examples/aircraft_steady_flight/aero/aerodynamics_group.py: -------------------------------------------------------------------------------- 1 | import openmdao.api as om 2 | from .aero_coef_comp import AeroCoefComp 3 | from .aero_forces_comp import AeroForcesComp 4 | from .mbi_aero_coef_comp import MBIAeroCoeffComp, setup_surrogates_all 5 | 6 | try: 7 | import MBI 8 | except ImportError: 9 | MBI = None 10 | 11 | 12 | class AerodynamicsGroup(om.Group): 13 | """ 14 | The purpose of the Aerodynamics is to compute the lift and 15 | drag forces on the aircraft. 16 | """ 17 | def initialize(self): 18 | self.options.declare('num_nodes', types=int) 19 | 20 | def setup(self): 21 | n = self.options['num_nodes'] 22 | 23 | if MBI is None: 24 | self.add_subsystem(name='aero_coef_comp', 25 | subsys=AeroCoefComp(vec_size=n, extrapolate=True, 26 | method='lagrange3'), 27 | promotes_inputs=['mach', 'alpha', 'alt', 'eta'], 28 | promotes_outputs=['CL', 'CD', 'CM']) 29 | 30 | else: 31 | mbi_CL, mbi_CD, mbi_CM, mbi_num = setup_surrogates_all() 32 | mbi_CL.seterr(bounds='warn') 33 | mbi_CD.seterr(bounds='warn') 34 | mbi_CM.seterr(bounds='warn') 35 | 36 | self.add_subsystem(name='aero_coef_comp', 37 | subsys=MBIAeroCoeffComp(vec_size=n, mbi_CL=mbi_CL, mbi_CD=mbi_CD, 38 | mbi_CM=mbi_CM, mbi_num=mbi_num), 39 | promotes_inputs=[('M', 'mach'), 'alpha', ('h', 'alt'), 'eta'], 40 | promotes_outputs=['CL', 'CD', 'CM']) 41 | 42 | self.add_subsystem(name='aero_forces_comp', 43 | subsys=AeroForcesComp(num_nodes=n), 44 | promotes_inputs=['q', 'S', 'CL', 'CD'], 45 | promotes_outputs=['L', 'D']) 46 | -------------------------------------------------------------------------------- /dymos/examples/min_time_climb/prop/prop.py: -------------------------------------------------------------------------------- 1 | import openmdao.api as om 2 | from .mdot_comp import MassFlowRateComp 3 | from .max_thrust_comp import MaxThrustComp 4 | from .thrust_comp import ThrustComp 5 | 6 | 7 | class PropGroup(om.Group): 8 | """ 9 | The purpose of the PropGroup is to compute the propulsive forces on the 10 | aircraft in the body frame. 11 | 12 | Parameters 13 | ---------- 14 | mach : float 15 | Mach number (unitless) 16 | alt : float 17 | altitude (m) 18 | Isp : float 19 | specific impulse (s) 20 | throttle : float 21 | throttle value nominally between 0.0 and 1.0 (unitless) 22 | 23 | Unknowns 24 | -------- 25 | thrust : float 26 | Vehicle thrust force (N) 27 | mdot : float 28 | Vehicle mass accumulation rate (kg/s) 29 | 30 | """ 31 | def initialize(self): 32 | self.options.declare('num_nodes', types=int, 33 | desc='Number of nodes to be evaluated in the RHS') 34 | 35 | def setup(self): 36 | nn = self.options['num_nodes'] 37 | 38 | max_thrust_comp = MaxThrustComp(vec_size=nn, extrapolate=True, method='cubic') 39 | # max_thrust_comp = BrysonMaxThrustComp(num_nodes=nn) 40 | 41 | self.add_subsystem(name='max_thrust_comp', 42 | subsys=max_thrust_comp, 43 | promotes_inputs=['mach', 'h'], 44 | promotes_outputs=['max_thrust']) 45 | 46 | self.add_subsystem(name='thrust_comp', 47 | subsys=ThrustComp(num_nodes=nn), 48 | promotes_inputs=['max_thrust', 'throttle'], 49 | promotes_outputs=['thrust']) 50 | 51 | self.add_subsystem(name='mdot_comp', 52 | subsys=MassFlowRateComp(num_nodes=nn), 53 | promotes_inputs=['thrust', 'Isp'], 54 | promotes_outputs=['m_dot']) 55 | -------------------------------------------------------------------------------- /dymos/examples/min_time_climb/doc/min_time_climb_ode_partial_coloring.py: -------------------------------------------------------------------------------- 1 | import openmdao.api as om 2 | from ....models.atmosphere import USatm1976Comp 3 | from .aero_partial_coloring import AeroGroup 4 | from ..prop import PropGroup 5 | from ....models.eom import FlightPathEOM2D 6 | 7 | 8 | class MinTimeClimbODE(om.Group): 9 | 10 | def initialize(self): 11 | self.options.declare('num_nodes', types=int) 12 | self.options.declare('fd', types=bool, default=False, desc='If True, use fd for partials') 13 | self.options.declare('partial_coloring', types=bool, default=False, 14 | desc='If True and fd is True, color the approximated partials') 15 | 16 | def setup(self): 17 | nn = self.options['num_nodes'] 18 | 19 | self.add_subsystem(name='atmos', 20 | subsys=USatm1976Comp(num_nodes=nn), 21 | promotes_inputs=['h']) 22 | 23 | self.add_subsystem(name='aero', 24 | subsys=AeroGroup(num_nodes=nn, 25 | fd=self.options['fd'], 26 | partial_coloring=self.options['partial_coloring']), 27 | promotes_inputs=['v', 'alpha', 'S']) 28 | 29 | self.connect('atmos.sos', 'aero.sos') 30 | self.connect('atmos.rho', 'aero.rho') 31 | 32 | self.add_subsystem(name='prop', 33 | subsys=PropGroup(num_nodes=nn), 34 | promotes_inputs=['h', 'Isp', 'throttle']) 35 | 36 | self.connect('aero.mach', 'prop.mach') 37 | 38 | self.add_subsystem(name='flight_dynamics', 39 | subsys=FlightPathEOM2D(num_nodes=nn), 40 | promotes_inputs=['m', 'v', 'gam', 'alpha']) 41 | 42 | self.connect('aero.f_drag', 'flight_dynamics.D') 43 | self.connect('aero.f_lift', 'flight_dynamics.L') 44 | self.connect('prop.thrust', 'flight_dynamics.T') 45 | -------------------------------------------------------------------------------- /dymos/test/test_options.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from openmdao.utils.testing_utils import use_tempdirs 4 | import dymos as dm 5 | from dymos.examples.brachistochrone.test.ex_brachistochrone import brachistochrone_min_time 6 | 7 | 8 | @use_tempdirs 9 | class TestOptions(unittest.TestCase): 10 | 11 | def test_include_check_partials_false_radau(self): 12 | dm.options['include_check_partials'] = False 13 | p = brachistochrone_min_time(transcription='radau-ps', compressed=False, 14 | run_driver=False, force_alloc_complex=True) 15 | cpd = p.check_partials(out_stream=None) 16 | self.assertSetEqual(set(cpd.keys()), {'traj0.phases.phase0.rhs_all'}) 17 | 18 | def test_include_check_partials_false_gl(self): 19 | dm.options['include_check_partials'] = False 20 | p = brachistochrone_min_time(transcription='gauss-lobatto', compressed=False, 21 | run_driver=False, force_alloc_complex=True) 22 | cpd = p.check_partials(out_stream=None, method='fd') 23 | self.assertSetEqual(set(cpd.keys()), {'traj0.phases.phase0.rhs_disc', 24 | 'traj0.phases.phase0.rhs_col'}) 25 | 26 | def test_include_check_partials_true_radau(self): 27 | dm.options['include_check_partials'] = True 28 | p = brachistochrone_min_time(transcription='radau-ps', compressed=False, 29 | run_driver=False, force_alloc_complex=True) 30 | cpd = p.check_partials(out_stream=None, method='fd') 31 | self.assertTrue(len(list(cpd.keys())) > 1) 32 | 33 | def test_include_check_partials_true_gl(self): 34 | dm.options['include_check_partials'] = True 35 | p = brachistochrone_min_time(transcription='gauss-lobatto', compressed=False, 36 | run_driver=False, force_alloc_complex=True) 37 | cpd = p.check_partials(out_stream=None, method='fd') 38 | self.assertTrue(len(list(cpd.keys())) > 1) 39 | -------------------------------------------------------------------------------- /mkdocs/docs/examples/hypersensitive/hypersensitive.md: -------------------------------------------------------------------------------- 1 | # Hyper-Sensitive Problem 2 | 3 | This example is based on the Hyper-Sensitive problem given in 4 | Patterson[@patterson2015ph]. In this problem we seek to minimize both 5 | the distance traveled when moving between fixed boundary conditions and 6 | also to minimize the control $u$ used. The cost function to be minimized is: 7 | 8 | \begin{align} 9 | J &= \frac{1}{2} \int_{0}^{t_f} (x^2 + u^2) dt 10 | \end{align} 11 | 12 | The system is subject to the dynamic constraints: 13 | 14 | \begin{align} 15 | \frac{dx}{dt} &= -x + u 16 | \end{align} 17 | 18 | The boundary conditions are: 19 | 20 | \begin{align} 21 | x(t_0) &= 1.5 \\ 22 | x(t_f) &= 1 23 | \end{align} 24 | 25 | The control $u$ is unconstrained while the final time $t_f$ is fixed. 26 | 27 | Due to the nature of dynamics, for sufficiently large values of $t_f$, 28 | the problem exhibits a _dive_, _cruise_, and _resurface_ type 29 | structure, where the all interesting behavior occurs at the beginning and 30 | end while remaining relatively constant in the middle. 31 | 32 | This problem has a known analytic optimal solution: 33 | 34 | \begin{align} 35 | x^*(t) &= c_1 e^{\sqrt{2} t} + c_2 e^{-\sqrt{2} t} \\ 36 | u^*(t) &= \dot{x}^*(t) + x^*(t) 37 | \end{align} 38 | 39 | where: 40 | 41 | \begin{align} 42 | c_1 &= \frac{1.5 e^{-\sqrt{2} t_f} - 1}{e^{-\sqrt{2} t_f} - e^{\sqrt{2} t_f}} \\ 43 | c_2 &= \frac{1 - 1.5 e^{\sqrt{2} t_f}}{e^{-\sqrt{2} t_f} - e^{\sqrt{2} t_f}} 44 | \end{align} 45 | 46 | ## The ODE System: hyper\_sensitive\_ode.py 47 | 48 | === "hyper_sensitive_ode.py" 49 | {{ inline_source('dymos.examples.hyper_sensitive.hyper_sensitive_ode', 50 | include_def=True, 51 | include_docstring=True, 52 | indent_level=0) 53 | }} 54 | 55 | ## Building and running the problem 56 | 57 | The following code shows the procedure for solving the problem 58 | 59 | {{ embed_test('dymos.examples.hyper_sensitive.doc.test_doc_hyper_sensitive.TestHyperSensitive.test_hyper_sensitive_for_docs') }} 60 | 61 | ## References 62 | 63 | \bibliography 64 | -------------------------------------------------------------------------------- /mkdocs/docs/examples/length_constrained_brachistochrone/length_constrained_brachistochrone.md: -------------------------------------------------------------------------------- 1 | # The Length-Constrained Brachistochrone 2 | 3 | !!! info "Things you'll learn through this example" 4 | - How to connect the outputs from a trajectory to a downstream system. 5 | 6 | This is a modified take on the brachistochrone problem. 7 | In this instance, we assume that the quantity of wire available is limited. 8 | Now, we seek to find the minimum time brachistochrone trajectory subject to a upper-limit on the arclength of the wire. 9 | 10 | The most efficient way to approach this problem would be to treat the arc-length $S$ as an integrated state variable. 11 | In this case, as is often the case in real-world MDO analyses, the implementation of our arc-length function is not integrated into our pseudospectral approach. 12 | Rather than rewrite an analysis tool to accommodate the pseudospectral approach, the arc-length analysis simply takes the result of the trajectory in its entirety and computes the arc-length constraint via the trapezoidal rule:\ 13 | 14 | \begin{align} 15 | S &= \frac{1}{2} \left( \sum_{i=1}^{N-1} \sqrt{1 + \frac{1}{\tan{\theta_{i-1}}}} + \sqrt{1 + \frac{1}{\tan{\theta_{i}}}} \right) \left(x_{i-1} - x_i \right) 16 | \end{align} 17 | 18 | The OpenMDAO component used to compute the arclength is defined as follows: 19 | 20 | === "arc_length_comp.py" 21 | {{ inline_source('dymos.examples.length_constrained_brachistochrone.arc_length_comp', 22 | include_def=True, 23 | include_docstring=True, 24 | indent_level=0) 25 | }} 26 | 27 | !!! note 28 | In this example, the number of nodes used to compute the arclength is needed when building the problem. 29 | The transcription object is initialized and its attribute `grid_data.num_nodes` is used to provide the number of total nodes (the number of points in the timeseries) to the downstream arc length calculation. 30 | 31 | {{ embed_test('dymos.examples.length_constrained_brachistochrone.doc.test_doc_length_constrained_brachistochrone.TestLengthConstrainedBrachistochrone.test_length_constrained_brachistochrone') }} 32 | -------------------------------------------------------------------------------- /joss/flow_charts/opt_control.py: -------------------------------------------------------------------------------- 1 | from pyxdsm.XDSM import XDSM, OPT, SUBOPT, SOLVER, DOE, IFUNC, FUNC, GROUP, IGROUP, METAMODEL 2 | 3 | 4 | x = XDSM(use_sfmath=False) 5 | 6 | x.add_system('OPT', OPT, r"\text{Optimizer}") 7 | x.add_system('ODE', GROUP, r"\text{ODE or DAE}") 8 | 9 | x.connect('ODE', 'OPT', ["J", r"\bar{g}_0", r"\bar{g}_f", r"\bar{p}"], label_width=4) 10 | x.connect('OPT', 'ODE', ["t", r"\bar{x}", r"\bar{u}", r"\bar{d}"], label_width=4) 11 | 12 | x.write('opt_control') 13 | 14 | 15 | x = XDSM(use_sfmath=False) 16 | 17 | x.add_system('OPT_static', OPT, r"\text{Static Optimizer}") 18 | x.add_system('static', GROUP, r"\text{Static System Model}") 19 | 20 | x.add_system('OPT_dynamic', OPT, r"\text{Dynamic Optimizer}") 21 | x.add_system('dynamic', GROUP, r"\text{ODE or DAE}") 22 | 23 | x.connect('dynamic', 'OPT_dynamic', [r"J_\text{dynamic}", r"\bar{g}_0", r"\bar{g}_f", r"\bar{p}"], label_width=4) 24 | x.connect('OPT_dynamic', 'dynamic', ["t", r"\bar{x}", r"\bar{u}"], label_width=3) 25 | 26 | x.connect('static', 'OPT_static', [r"J_\text{static}", r"g_\text{static}"], label_width=3) 27 | x.connect('OPT_static', 'static', [r"\bar{d}"], label_width=3) 28 | 29 | x.connect('OPT_static', 'OPT_dynamic', [r'\text{static optimization}', r'\text{outputs}']) 30 | x.connect('OPT_dynamic', 'OPT_static', [r'\text{dynamic optimization}', r'\text{outputs}']) 31 | 32 | x.write('sequential_co_design') 33 | 34 | 35 | x = XDSM(use_sfmath=False) 36 | 37 | x.add_system('OPT', OPT, r"\text{Optimizer}") 38 | x.add_system('static', GROUP, r"\text{Static System Model}") 39 | x.add_system('dynamic', GROUP, r"\text{ODE or DAE}") 40 | 41 | x.connect('dynamic', 'OPT', [r"J", r"\bar{g}_0", r"\bar{g}_f", r"\bar{p}"], label_width=4) 42 | x.connect('OPT', 'dynamic', ["t", r"\bar{x}", r"\bar{u}"], label_width=3) 43 | 44 | x.connect('static', 'OPT', [r"g_\text{static}"], label_width=3) 45 | x.connect('OPT', 'static', [r"\bar{d}"], label_width=3) 46 | 47 | x.connect('static', 'dynamic', [r'\text{static}', r'\text{outputs}']) 48 | x.connect('dynamic', 'static', [r'\text{dynamic}', r'\text{outputs}']) 49 | 50 | x.write('coupled_co_design') 51 | -------------------------------------------------------------------------------- /dymos/examples/min_time_climb/aero/test/test_cd0_comp.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import numpy as np 4 | 5 | import openmdao.api as om 6 | from dymos.utils.testing_utils import assert_check_partials 7 | 8 | from dymos.examples.min_time_climb.aero.cd0_comp import CD0Comp 9 | 10 | assert_almost_equal = np.testing.assert_almost_equal 11 | 12 | import matplotlib 13 | matplotlib.use('Agg') 14 | 15 | SHOW_PLOTS = True 16 | 17 | 18 | class TestCD0Comp(unittest.TestCase): 19 | @unittest.skipIf(not SHOW_PLOTS, 'this test is for visual confirmation, requires plotting') 20 | def test_visual_inspection(self): 21 | n = 500 22 | 23 | p = om.Problem(model=om.Group()) 24 | 25 | ivc = om.IndepVarComp() 26 | 27 | ivc.add_output(name='mach', units=None, val=np.zeros(n)) 28 | 29 | p.model.add_subsystem(name='ivc', subsys=ivc, promotes_outputs=['*']) 30 | 31 | p.model.add_subsystem(name='cd0_comp', 32 | subsys=CD0Comp(num_nodes=n)) 33 | 34 | p.model.connect('mach', 'cd0_comp.mach') 35 | 36 | p.setup() 37 | 38 | p['mach'] = np.linspace(0, 1.8, n) 39 | p.run_model() 40 | 41 | import matplotlib.pyplot as plt 42 | plt.plot(p['mach'], p['cd0_comp.CD0'], 'ro', ms=2) 43 | plt.show() 44 | 45 | def test_partials(self): 46 | 47 | n = 10 48 | 49 | p = om.Problem(model=om.Group()) 50 | 51 | ivc = om.IndepVarComp() 52 | 53 | ivc.add_output(name='mach', units=None, val=np.zeros(n)) 54 | 55 | p.model.add_subsystem(name='ivc', subsys=ivc, promotes_outputs=['*']) 56 | 57 | p.model.add_subsystem(name='cd0_comp', 58 | subsys=CD0Comp(num_nodes=n)) 59 | 60 | p.model.connect('mach', 'cd0_comp.mach') 61 | 62 | p.setup() 63 | 64 | p['mach'] = np.linspace(0, 1.14, n) 65 | 66 | p.run_model() 67 | 68 | cpd = p.check_partials(compact_print=False, out_stream=None) 69 | assert_check_partials(cpd, atol=1.0E-5, rtol=1.0E-5) 70 | 71 | 72 | if __name__ == '__main__': # pragma: no cover 73 | unittest.main() 74 | -------------------------------------------------------------------------------- /dymos/examples/min_time_climb/aero/test/test_cla_comp.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import numpy as np 4 | 5 | import openmdao.api as om 6 | from dymos.utils.testing_utils import assert_check_partials 7 | 8 | from dymos.examples.min_time_climb.aero.cla_comp import CLaComp 9 | 10 | assert_almost_equal = np.testing.assert_almost_equal 11 | 12 | import matplotlib 13 | matplotlib.use('Agg') 14 | 15 | SHOW_PLOTS = True 16 | 17 | 18 | class TestCLaComp(unittest.TestCase): 19 | 20 | @unittest.skipIf(not SHOW_PLOTS, 'this test is for visual confirmation, requires plotting') 21 | def test_visual_inspection(self): 22 | n = 500 23 | 24 | p = om.Problem(model=om.Group()) 25 | 26 | ivc = om.IndepVarComp() 27 | 28 | ivc.add_output(name='mach', units=None, val=np.zeros(n)) 29 | 30 | p.model.add_subsystem(name='ivc', subsys=ivc, promotes_outputs=['*']) 31 | 32 | p.model.add_subsystem(name='cla_comp', 33 | subsys=CLaComp(num_nodes=n)) 34 | 35 | p.model.connect('mach', 'cla_comp.mach') 36 | 37 | p.setup() 38 | 39 | p['mach'] = np.linspace(0, 1.8, n) 40 | p.run_model() 41 | 42 | import matplotlib.pyplot as plt 43 | plt.plot(p['mach'], p['cla_comp.CLa'], 'ro', ms=2) 44 | plt.show() 45 | 46 | def test_partials(self): 47 | 48 | n = 10 49 | 50 | p = om.Problem(model=om.Group()) 51 | 52 | ivc = om.IndepVarComp() 53 | 54 | ivc.add_output(name='mach', units=None, val=np.zeros(n)) 55 | 56 | p.model.add_subsystem(name='ivc', subsys=ivc, promotes_outputs=['*']) 57 | 58 | p.model.add_subsystem(name='cla_comp', 59 | subsys=CLaComp(num_nodes=n)) 60 | 61 | p.model.connect('mach', 'cla_comp.mach') 62 | 63 | p.setup() 64 | 65 | p['mach'] = np.linspace(0, 1.8, n) 66 | 67 | p.run_model() 68 | 69 | cpd = p.check_partials(compact_print=False, out_stream=None) 70 | assert_check_partials(cpd, atol=1.0E-3, rtol=1.0E-4) 71 | 72 | 73 | if __name__ == '__main__': # pragma: no cover 74 | unittest.main() 75 | -------------------------------------------------------------------------------- /dymos/utils/lagrange.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | def lagrange_matrices(x_disc, x_interp): 5 | """ 6 | Compute the lagrange matrices. 7 | 8 | The lagrange matrics are given 'cardinal' nodes at which 9 | values are specified and 'interior' nodes at which values will be desired, 10 | returns interpolation and differentiation matrices which provide polynomial 11 | values and derivatives. 12 | 13 | Parameters 14 | ---------- 15 | x_disc : np.array 16 | The cardinal nodes at which values of the variable are specified. 17 | x_interp : np.array 18 | The interior nodes at which interpolated values of the variable or its derivative 19 | are desired. 20 | 21 | Returns 22 | ------- 23 | np.array 24 | A num_i x num_c matrix which, when post-multiplied by values specified 25 | at the cardinal nodes, returns the intepolated values at the interior 26 | nodes. 27 | 28 | np.array 29 | A num_i x num_c matrix which, when post-multiplied by values specified 30 | at the cardinal nodes, returns the intepolated derivatives at the interior 31 | nodes. 32 | """ 33 | nd = len(x_disc) 34 | ni = len(x_interp) 35 | wb = np.ones(nd) 36 | 37 | Li = np.zeros((ni, nd)) 38 | Di = np.zeros((ni, nd)) 39 | 40 | # Barycentric Weights 41 | for j in range(nd): 42 | for k in range(nd): 43 | if k != j: 44 | wb[j] /= (x_disc[j] - x_disc[k]) 45 | 46 | # Compute Li 47 | for i in range(ni): 48 | for j in range(nd): 49 | Li[i, j] = wb[j] 50 | for k in range(nd): 51 | if k != j: 52 | Li[i, j] *= (x_interp[i] - x_disc[k]) 53 | 54 | # Compute Di 55 | for i in range(ni): 56 | for j in range(nd): 57 | for k in range(nd): 58 | prod = 1.0 59 | if k != j: 60 | for m in range(nd): 61 | if m != j and m != k: 62 | prod *= (x_interp[i] - x_disc[m]) 63 | Di[i, j] += (wb[j] * prod) 64 | 65 | return Li, Di 66 | -------------------------------------------------------------------------------- /dymos/utils/test/test_lgr.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import numpy as np 4 | from numpy.testing import assert_almost_equal 5 | 6 | from dymos.utils.lgr import lgr 7 | 8 | # Known solutions 9 | x_i = {2: [-1.0, 1 / 3.0], 10 | 3: [-1.0, -0.289898, 0.689898], 11 | 4: [-1.0, -0.575319, 0.181066, 0.822824], 12 | 5: [-1.0, -0.72048, -0.167181, 0.446314, 0.885792]} 13 | 14 | w_i = {2: [0.5, 1.5], 15 | 3: [2.0 / 9.0, (16 + np.sqrt(6)) / 18.0, (16 - np.sqrt(6)) / 18.0], 16 | 4: [0.125, 0.657689, 0.776387, 0.440924], 17 | 5: [0.08, 0.446208, 0.623653, 0.562712, 0.287427]} 18 | 19 | 20 | class TestLGR(unittest.TestCase): 21 | 22 | def test_nodes_and_weights_2(self): 23 | x_2, w_2 = lgr(2) 24 | assert_almost_equal(x_2, x_i[2], decimal=6) 25 | assert_almost_equal(w_2, w_i[2], decimal=6) 26 | 27 | x_2, w_2 = lgr(2, include_endpoint=True) 28 | assert_almost_equal(x_2, x_i[2] + [1], decimal=6) 29 | assert_almost_equal(w_2, w_i[2] + [0], decimal=6) 30 | 31 | def test_nodes_and_weights_3(self): 32 | x_3, w_3 = lgr(3) 33 | assert_almost_equal(x_3, x_i[3], decimal=6) 34 | assert_almost_equal(w_3, w_i[3], decimal=6) 35 | 36 | x_3, w_3 = lgr(3, include_endpoint=True) 37 | assert_almost_equal(x_3, x_i[3] + [1], decimal=6) 38 | assert_almost_equal(w_3, w_i[3] + [0], decimal=6) 39 | 40 | def test_nodes_and_weights_4(self): 41 | x_4, w_4 = lgr(4) 42 | assert_almost_equal(x_4, x_i[4], decimal=6) 43 | assert_almost_equal(w_4, w_i[4], decimal=6) 44 | 45 | x_4, w_4 = lgr(4, include_endpoint=True) 46 | assert_almost_equal(x_4, x_i[4] + [1], decimal=6) 47 | assert_almost_equal(w_4, w_i[4] + [0], decimal=6) 48 | 49 | def test_nodes_and_weights_5(self): 50 | x_5, w_5 = lgr(5) 51 | assert_almost_equal(x_5, x_i[5], decimal=6) 52 | assert_almost_equal(w_5, w_i[5], decimal=6) 53 | 54 | x_5, w_5 = lgr(5, include_endpoint=True) 55 | assert_almost_equal(x_5, x_i[5] + [1], decimal=6) 56 | assert_almost_equal(w_5, w_i[5] + [0], decimal=6) 57 | 58 | 59 | if __name__ == '__main__': # pragma: no cover 60 | unittest.main() 61 | -------------------------------------------------------------------------------- /mkdocs/docs/examples/brachistochrone/scripts/brachistochrone_fbd.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import matplotlib.pyplot as plt 4 | plt.switch_backend('Agg') 5 | from matplotlib.patches import FancyArrowPatch, Arc 6 | 7 | LW = 2 8 | 9 | fig, ax = plt.subplots(1, 1, figsize=(5, 5)) 10 | ax.axis('off') 11 | 12 | ax.set_xlim(-1, 11) 13 | ax.set_ylim(-1, 11) 14 | 15 | circle = plt.Circle((0, 10), radius=0.1, fc='k') 16 | ax.add_patch(circle) 17 | plt.text(0.2, 10.2, 'A') 18 | 19 | circle = plt.Circle((10, 5), radius=0.1, fc='k') 20 | ax.add_patch(circle) 21 | plt.text(10.2, 5.2, 'B') 22 | 23 | # Choose a to suite, compute b 24 | a = 0.1 25 | b = -0.5 - 10*a 26 | c = 10 27 | 28 | def y_wire(x): 29 | return a*x**2 + b*x + c, 2*a*x + b 30 | 31 | x = np.linspace(0, 10, 100) 32 | y, _ = y_wire(x) 33 | plt.plot(x, y, 'b-') 34 | 35 | # Add the bead to the wire 36 | x = 3 37 | y, dy_dx = y_wire(x) 38 | plt.plot(x, y, 'ro', ms=10) 39 | 40 | # Draw and label the gravity vector 41 | gvec = FancyArrowPatch((x, y), (x, y-2), arrowstyle='->', mutation_scale=10, linewidth=LW, color='k') 42 | lv_line = plt.Line2D((x, x), (y, y-2), visible=False) # Local vertical 43 | ax.add_patch(gvec) 44 | plt.text(x - 0.5, y-1, 'g') 45 | 46 | # Draw and label the velocity vector 47 | dx = 2 48 | dy = dy_dx * dx 49 | vvec = FancyArrowPatch((x, y), (x+dx, y+dy), arrowstyle='->', mutation_scale=10, linewidth=LW, color='k') 50 | ax.add_patch(vvec) 51 | plt.text(x+dx-0.25, y+dy-0.25, 'v') 52 | 53 | # Draw angle theta 54 | vvec_line = plt.Line2D((x, x+dx), (y, y+dy), visible=False) 55 | # angle_plot = get_angle_plot(lv_line, vvec_line, color='k', origin=(x, y), radius=3) 56 | # ax.add_patch(angle_plot) 57 | ax.text(x+0.25, y-1.25, r'$\theta$') 58 | 59 | # Draw the axes 60 | x = 0 61 | y = 2 62 | dx = 5 63 | dy = 0 64 | xhat = FancyArrowPatch((x, y), (x+dx, y+dy), arrowstyle='->', mutation_scale=10, linewidth=LW, color='k') 65 | ax.add_patch(xhat) 66 | plt.text(x+dx/2.0-0.5, y+dy/2.0-0.5, 'x') 67 | 68 | dx = 0 69 | dy = 5 70 | yhat = FancyArrowPatch((x, y), (x+dx, y+dy), arrowstyle='->', mutation_scale=10, linewidth=LW, color='k') 71 | ax.add_patch(yhat) 72 | plt.text(x+dx/2.0-0.5, y+dy/2.0-0.5, 'y') 73 | 74 | plt.ylim(1, 11) 75 | plt.xlim(-0.5, 10.5) 76 | 77 | # plt.savefig('brachistochrone_fbd.png') 78 | 79 | # plt.show() 80 | -------------------------------------------------------------------------------- /mkdocs/docs/examples/finite_burn_orbit_raise/finite_burn_orbit_raise.md: -------------------------------------------------------------------------------- 1 | # Two-Burn Orbit Raise 2 | 3 | This example demonstrates the use of a Trajectory to encapsulate a 4 | three-phase orbit raising maneuver with a burn-coast-burn phase 5 | sequence. This example is based on the problem provided in 6 | Enright[@enright1991optimal]. 7 | 8 | The dynamics are given by 9 | 10 | \begin{align} 11 | \frac{dr}{dt} &= v_r \\ 12 | \frac{d\theta}{dt} &= \frac{v_\theta}{r} \\ 13 | \frac{dv_r}{dt} &= \frac{v^2_\theta}{r} - \frac{1}{r^2} + a_{thrust} \sin u_1 \\ 14 | \frac{dv_\theta}{dt} &= - \frac{v_r v_\theta}{r} + a_{thrust} \cos u_1 \\ 15 | \frac{da_{thrust}}{dt} &= \frac{a^2_{thrust}}{c} \\ 16 | \frac{d \Delta v}{dt} &= a_{thrust} 17 | \end{align} 18 | 19 | The initial conditions are 20 | 21 | \begin{align} 22 | r &= 1 \rm{\,DU} \\ 23 | \theta &= 0 \rm{\,rad} \\ 24 | v_r &= 0 \rm{\,DU/TU}\\ 25 | v_\theta &= 1 \rm{\,DU/TU}\\ 26 | a_{thrust} &= 0.1 \rm{\,DU/TU^2}\\ 27 | \Delta v &= 0 \rm{\,DU/TU} 28 | \end{align} 29 | 30 | and the final conditions are 31 | 32 | \begin{align} 33 | r &= 3 \rm{\,DU} \\ 34 | \theta &= \rm{free} \\ 35 | v_r &= 0 \rm{\,DU/TU}\\ 36 | v_\theta &= \sqrt{\frac{1}{3}} \rm{\,DU/TU}\\ 37 | a_{thrust} &= \rm{free}\\ 38 | \Delta v &= \rm{free} 39 | \end{align} 40 | 41 | ## Building and running the problem 42 | 43 | The following code instantiates our problem, our trajectory, three 44 | phases, and links them accordingly. The spacecraft initial position, 45 | velocity, and acceleration magnitude are fixed. The objective is to 46 | minimize the delta-V needed to raise the spacecraft into a circular 47 | orbit at 3 Earth radii. 48 | 49 | Note the call to _link\_phases_ which provides time, 50 | position, velocity, and delta-V continuity across all phases, and 51 | acceleration continuity between the first and second burn phases. 52 | Acceleration is 0 during the coast phase. Alternatively, we could have 53 | specified a different ODE for the coast phase, as in the example. 54 | 55 | This example runs inconsistently with SLSQP but is solved handily by 56 | SNOPT. 57 | 58 | {{ embed_test('dymos.examples.finite_burn_orbit_raise.doc.test_doc_finite_burn_orbit_raise.TestFiniteBurnOrbitRaise.test_finite_burn_orbit_raise') }} 59 | 60 | ## References 61 | 62 | \bibliography 63 | -------------------------------------------------------------------------------- /dymos/examples/min_time_climb/aero/lift_drag_force_comp.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import openmdao.api as om 4 | 5 | 6 | class LiftDragForceComp(om.ExplicitComponent): 7 | """ 8 | Compute the aerodynamic forces on the vehicle in the wind axis frame 9 | (lift, drag, cross) force. 10 | """ 11 | def initialize(self): 12 | self.options.declare('num_nodes', types=int) 13 | 14 | def setup(self): 15 | nn = self.options['num_nodes'] 16 | 17 | self.add_input(name='CL', val=np.zeros(nn,), desc='lift coefficient', units=None) 18 | self.add_input(name='CD', val=np.zeros(nn,), desc='drag coefficient', units=None) 19 | self.add_input(name='q', val=np.zeros(nn,), desc='dynamic pressure', units='N/m**2') 20 | self.add_input(name='S', val=np.zeros(nn,), desc='aerodynamic reference area', units='m**2') 21 | 22 | self.add_output(name='f_lift', shape=(nn,), desc='aerodynamic lift force', units='N') 23 | self.add_output(name='f_drag', shape=(nn,), desc='aerodynamic drag force', units='N') 24 | 25 | ar = np.arange(nn) 26 | 27 | self.declare_partials(of='f_lift', wrt='q', dependent=True, rows=ar, cols=ar) 28 | self.declare_partials(of='f_lift', wrt='S', dependent=True, rows=ar, cols=ar) 29 | self.declare_partials(of='f_lift', wrt='CL', dependent=True, rows=ar, cols=ar) 30 | 31 | self.declare_partials(of='f_drag', wrt='q', dependent=True, rows=ar, cols=ar) 32 | self.declare_partials(of='f_drag', wrt='S', dependent=True, rows=ar, cols=ar) 33 | self.declare_partials(of='f_drag', wrt='CD', dependent=True, rows=ar, cols=ar) 34 | 35 | def compute(self, inputs, outputs): 36 | q = inputs['q'] 37 | S = inputs['S'] 38 | CL = inputs['CL'] 39 | CD = inputs['CD'] 40 | 41 | qS = q * S 42 | 43 | outputs['f_lift'] = qS * CL 44 | outputs['f_drag'] = qS * CD 45 | 46 | def compute_partials(self, inputs, partials): 47 | q = inputs['q'] 48 | S = inputs['S'] 49 | CL = inputs['CL'] 50 | CD = inputs['CD'] 51 | 52 | qS = q * S 53 | 54 | partials['f_lift', 'q'] = S * CL 55 | partials['f_lift', 'S'] = q * CL 56 | partials['f_lift', 'CL'] = qS 57 | 58 | partials['f_drag', 'q'] = S * CD 59 | partials['f_drag', 'S'] = q * CD 60 | partials['f_drag', 'CD'] = qS 61 | -------------------------------------------------------------------------------- /dymos/examples/battery_multibranch/motors.py: -------------------------------------------------------------------------------- 1 | """ 2 | Simple model for a set of motors in parallel where efficiency is a function of current. 3 | """ 4 | import numpy as np 5 | 6 | import openmdao.api as om 7 | 8 | 9 | class Motors(om.ExplicitComponent): 10 | """ 11 | Model for motors in parallel. 12 | """ 13 | 14 | def initialize(self): 15 | self.options.declare('num_nodes', default=1) 16 | self.options.declare('n_parallel', default=3, desc='number of motors in parallel') 17 | 18 | def setup(self): 19 | num_nodes = self.options['num_nodes'] 20 | 21 | # Inputs 22 | self.add_input('power_out_gearbox', val=3.6*np.ones(num_nodes), units='W', 23 | desc='Power at gearbox output') 24 | self.add_input('current_in_motor', val=np.ones(num_nodes), units='A', 25 | desc='Total current demanded') 26 | 27 | # Outputs 28 | self.add_output('power_in_motor', val=np.ones(num_nodes), units='W', 29 | desc='Power required at motor input') 30 | 31 | # Derivatives 32 | row_col = np.arange(num_nodes) 33 | 34 | self.declare_partials(of='power_in_motor', wrt=['*'], rows=row_col, cols=row_col) 35 | 36 | def compute(self, inputs, outputs): 37 | current = inputs['current_in_motor'] 38 | power_out = inputs['power_out_gearbox'] 39 | n_parallel = self.options['n_parallel'] 40 | 41 | # Simple linear curve fit for efficiency. 42 | eff = 0.9 - 0.3 * current / n_parallel 43 | 44 | outputs['power_in_motor'] = power_out / eff 45 | 46 | def compute_partials(self, inputs, partials): 47 | current = inputs['current_in_motor'] 48 | power_out = inputs['power_out_gearbox'] 49 | n_parallel = self.options['n_parallel'] 50 | 51 | eff = 0.9 - 0.3 * current / n_parallel 52 | 53 | partials['power_in_motor', 'power_out_gearbox'] = 1.0 / eff 54 | partials['power_in_motor', 'current_in_motor'] = 0.3 * power_out / (n_parallel * eff**2) 55 | 56 | 57 | if __name__ == '__main__': 58 | 59 | import openmdao.api as om 60 | num_nodes = 1 61 | 62 | prob = om.Problem(model=Motors(num_nodes=num_nodes)) 63 | model = prob.model 64 | 65 | prob.setup() 66 | 67 | prob.run_model() 68 | 69 | derivs = prob.check_partials(compact_print=True) 70 | 71 | print('done') 72 | -------------------------------------------------------------------------------- /dymos/examples/aircraft_steady_flight/test/test_aircraft_ode.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import numpy as np 4 | import openmdao.api as om 5 | from openmdao.utils.assert_utils import assert_near_equal 6 | from openmdao.utils.testing_utils import use_tempdirs 7 | 8 | from dymos.examples.aircraft_steady_flight.aircraft_ode import AircraftODE 9 | 10 | 11 | try: 12 | import MBI 13 | except: 14 | MBI = None 15 | 16 | 17 | @unittest.skipIf(MBI is None, 'MBI not available') 18 | @use_tempdirs 19 | class TestAircraftODEGroup(unittest.TestCase): 20 | 21 | def test_results(self): 22 | n = 10 23 | 24 | p = om.Problem(model=om.Group()) 25 | 26 | ivc = p.model.add_subsystem('ivc', om.IndepVarComp(), promotes_outputs=['*']) 27 | 28 | ivc.add_output('mass_fuel', val=20000 * np.ones(n), units='kg', 29 | desc='aircraft fuel mass') 30 | ivc.add_output('mass_payload', val=84.02869 * 400 * np.ones(n), units='kg', 31 | desc='aircraft fuel mass') 32 | ivc.add_output('mass_empty', val=0.15E6 * np.ones(n), units='kg', 33 | desc='aircraft empty mass') 34 | ivc.add_output('alt', val=9.144*np.ones(n), units='km', desc='altitude') 35 | ivc.add_output('climb_rate', val=np.zeros(n), units='m/s', desc='climb rate') 36 | ivc.add_output('mach', val=0.8*np.ones(n), units=None, desc='mach number') 37 | ivc.add_output('S', val=427.8 * np.ones(n), units='m**2', desc='reference area') 38 | 39 | p.model.add_subsystem('ode', AircraftODE(num_nodes=n)) 40 | 41 | p.model.connect('mass_fuel', 'ode.mass_fuel') 42 | p.model.connect('mass_payload', 'ode.mass_comp.mass_payload') 43 | p.model.connect('mass_empty', 'ode.mass_comp.mass_empty') 44 | p.model.connect('alt', 'ode.alt') 45 | p.model.connect('mach', ['ode.tas_comp.mach', 'ode.aero.mach']) 46 | p.model.connect('climb_rate', ['ode.gam_comp.climb_rate']) 47 | p.model.connect('S', ('ode.aero.S', 'ode.flight_equilibrium.S', 'ode.propulsion.S')) 48 | 49 | p.model.linear_solver = om.DirectSolver() 50 | 51 | p.setup(check=True, force_alloc_complex=True) 52 | 53 | p.run_model() 54 | 55 | assert_near_equal(p['ode.range_rate_comp.dXdt:range'], 56 | p['ode.tas_comp.TAS'] * np.cos(p['ode.gam_comp.gam'])) 57 | 58 | 59 | if __name__ == "__main__": 60 | unittest.main() 61 | -------------------------------------------------------------------------------- /dymos/examples/aircraft_steady_flight/propulsion/propulsion_group.py: -------------------------------------------------------------------------------- 1 | import openmdao.api as om 2 | 3 | from .thrust_comp import ThrustComp 4 | from .max_thrust_comp import MaxThrustComp 5 | from .throttle_comp import ThrottleComp 6 | from .tsfc_comp import SFCComp 7 | from .fuel_burn_rate_comp import FuelBurnRateComp 8 | 9 | 10 | class PropulsionGroup(om.Group): 11 | """ 12 | The PropulsionGroup computes propulsive forces (thrust), the specific fuel consumption and 13 | fuel expenditure rate, and the aircraft throttle setting. 14 | """ 15 | def initialize(self): 16 | self.options.declare('num_nodes', types=int) 17 | 18 | def setup(self): 19 | n = self.options['num_nodes'] 20 | 21 | assumptions = self.add_subsystem('assumptions', subsys=om.IndepVarComp()) 22 | 23 | assumptions.add_output('tsfc_sl', val=2 * 8.951e-6 * 9.80665, units='1/s', 24 | desc='thrust specific fuel consumption at sea-level') 25 | 26 | assumptions.add_output('max_thrust_sl', val=1.02E6, units='N', 27 | desc='maximum thrust at sea-level') 28 | 29 | self.add_subsystem(name='thrust_comp', 30 | subsys=ThrustComp(num_nodes=n), 31 | promotes_inputs=['CT', 'q', 'S'], 32 | promotes_outputs=['thrust']) 33 | 34 | self.add_subsystem(name='max_thrust_comp', 35 | subsys=MaxThrustComp(num_nodes=n), 36 | promotes_inputs=['pres', 'max_thrust_sl'], 37 | promotes_outputs=['max_thrust']) 38 | 39 | self.add_subsystem(name='throttle_comp', 40 | subsys=ThrottleComp(num_nodes=n), 41 | promotes_inputs=['thrust', 'max_thrust'], 42 | promotes_outputs=['tau']) 43 | 44 | self.add_subsystem(name='tsfc_comp', 45 | subsys=SFCComp(num_nodes=n), 46 | promotes_inputs=['alt'], 47 | promotes_outputs=['tsfc']) 48 | 49 | self.add_subsystem(name='fuel_burn_rate_comp', 50 | subsys=FuelBurnRateComp(num_nodes=n), 51 | promotes_inputs=['tsfc', 'thrust'], 52 | promotes_outputs=['dXdt:mass_fuel']) 53 | 54 | self.connect('assumptions.tsfc_sl', 'tsfc_comp.tsfc_sl') 55 | self.connect('assumptions.max_thrust_sl', 'max_thrust_sl') 56 | -------------------------------------------------------------------------------- /dymos/utils/test/test_hermite.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import numpy as np 4 | from numpy.testing import assert_almost_equal 5 | 6 | from dymos.utils.hermite import hermite_matrices 7 | 8 | 9 | class TestHermiteMatrices(unittest.TestCase): 10 | 11 | def test_quadratic(self): 12 | 13 | # Interpolate with values and rates provided at [-1, 1] in tau space 14 | tau_given = [-1.0, 1.0] 15 | tau_eval = np.linspace(-1, 1, 100) 16 | 17 | # In time space use the boundaries [-2, 2] 18 | dt_dtau = 4.0 / 2.0 19 | 20 | # Provide values for y = t**2 and its time-derivative 21 | y_given = [4.0, 4.0] 22 | ydot_given = [-4.0, 4.0] 23 | 24 | # Get the hermite matrices. 25 | Ai, Bi, Ad, Bd = hermite_matrices(tau_given, tau_eval) 26 | 27 | # Interpolate y and ydot at tau_eval points in tau space. 28 | y_i = np.dot(Ai, y_given) + dt_dtau * np.dot(Bi, ydot_given) 29 | ydot_i = (1.0 / dt_dtau) * np.dot(Ad, y_given) + np.dot(Bd, ydot_given) 30 | 31 | # Compute our function as a point of comparison. 32 | y_computed = (tau_eval * dt_dtau)**2 33 | ydot_computed = 2.0 * (tau_eval * dt_dtau) 34 | 35 | # Check results 36 | assert_almost_equal(y_i, y_computed) 37 | assert_almost_equal(ydot_i, ydot_computed) 38 | 39 | def test_cubic(self): 40 | 41 | # Interpolate with values and rates provided at [-1, 1] in tau space 42 | tau_given = [-1.0, 0.0, 1.0] 43 | tau_eval = np.linspace(-1, 1, 101) 44 | 45 | # In time space use the boundaries [-2, 2] 46 | dt_dtau = 4.0 / 2.0 47 | 48 | # Provide values for y = t**2 and its time-derivative 49 | y_given = [-8.0, 0.0, 8.0] 50 | ydot_given = [12.0, 0.0, 12.0] 51 | 52 | # Get the hermite matrices. 53 | Ai, Bi, Ad, Bd = hermite_matrices(tau_given, tau_eval) 54 | 55 | # Interpolate y and ydot at tau_eval points in tau space. 56 | y_i = np.dot(Ai, y_given) + dt_dtau * np.dot(Bi, ydot_given) 57 | ydot_i = (1.0 / dt_dtau) * np.dot(Ad, y_given) + np.dot(Bd, ydot_given) 58 | 59 | # Compute our function as a point of comparison. 60 | y_computed = (tau_eval * dt_dtau)**3 61 | ydot_computed = 3.0 * (tau_eval * dt_dtau)**2 62 | 63 | # Check results 64 | assert_almost_equal(y_i, y_computed) 65 | assert_almost_equal(ydot_i, ydot_computed) 66 | 67 | 68 | if __name__ == '__main__': # pragma: no cover 69 | unittest.main() 70 | -------------------------------------------------------------------------------- /mkdocs/docs/examples/ssto_earth/ssto_earth.md: -------------------------------------------------------------------------------- 1 | # SSTO Earth Launch 2 | 3 | This example is based on the _Time-Optimal Launch of a Titan II_ 4 | example given in Appendix B of Longuski[@longuski2014optimal]. 5 | It finds the pitch profile for a single-stage-to-orbit launch vehicle that minimizes the time 6 | required to reach orbit insertion under constant thrust. 7 | 8 | ![The free-body-diagram of the single-stage-to-orbit problem.](ssto_fbd.png) 9 | 10 | The vehicle dynamics are given by 11 | 12 | \begin{align} 13 | \frac{dx}{dt} &= v_x \\ 14 | \frac{dy}{dt} &= v_y \\ 15 | \frac{dv_x}{dt} &= \frac{1}{m} (T \cos \theta - D \cos \gamma) \\ 16 | \frac{dv_y}{dt} &= \frac{1}{m} (T \sin \theta - D \sin \gamma) - g \\ 17 | \frac{dm}{dt} &= \frac{T}{g I_{sp}} 18 | \end{align} 19 | 20 | The initial conditions are 21 | 22 | \begin{align} 23 | x_0 &= 0 \\ 24 | y_0 &= 0 \\ 25 | v_{x0} &= 0 \\ 26 | v_{y0} &= 0 \\ 27 | m_0 &= 117000 \rm{\,kg} 28 | \end{align} 29 | 30 | and the final conditions are 31 | 32 | \begin{align} 33 | x_f &= \rm{free} \\ 34 | y_f &= 185 \rm{\,km} \\ 35 | v_{xf} &= V_{circ} \\ 36 | v_{yf} &= 0 \\ 37 | m_f &= \rm{free} 38 | \end{align} 39 | 40 | ## Defining the ODE 41 | 42 | Generally, one could define the ODE system as a composite group of multile components. 43 | The atmosphere component computes density ($\rho$). 44 | The eom component computes the state rates. 45 | Decomposing the ODE into smaller calculations makes it easier to derive the analytic derivatives. 46 | 47 | ![The notional XDSM diagram for the ODE system in the SSTO problem.](ssto_xdsm.png) 48 | 49 | However, for this example we will demonstrate the use of complex-step differentiation and define the ODE as a single component. 50 | This saves time up front in the deevlopment of the ODE at a minor cost in execution time. 51 | 52 | The unconnected inputs to the EOM at the top of the diagram are provided by the Dymos phase as states, controls, or time values. 53 | The outputs, including the state rates, are shown on the right side of the diagram. 54 | The Dymos phases use state rate values to ensure that the integration technique satisfies the dynamics of the system. 55 | 56 | === "launch_vehicle_ode.py" 57 | {{ inline_source('dymos.examples.ssto.launch_vehicle_ode', 58 | include_def=True, 59 | include_docstring=True, 60 | indent_level=0) 61 | }} 62 | 63 | ## Solving the problem 64 | 65 | {{ embed_test('dymos.examples.ssto.doc.test_doc_ssto_earth.TestDocSSTOEarth.test_doc_ssto_earth') }} 66 | 67 | ## References 68 | 69 | \bibliography 70 | -------------------------------------------------------------------------------- /mkdocs/docs/features/phases/objective.md: -------------------------------------------------------------------------------- 1 | # Objective 2 | 3 | Optimal control problems in Dymos are posed within the context of *phases*. 4 | 5 | When using an optimizer-based transcription, (Gauss-Lobatto or Radau *without* `solve_segments=forward`), the user is **required** to provide an objective somewhere in the problem, even for a simple initial value problem. 6 | 7 | To support objectives, phases use an overridden version of OpenMDAO's `add_objective` method. 8 | This method handles a few complexities that would be involved in the standard OpenMDAO 9 | `add_objective` method. 10 | 11 | First, since the path to a variable within the phase might depend on the transcription used, the 12 | `add_objective` method uses the same name-detection capability as other phase methods like 13 | `add_boundary_constraint` and `add_path_constraint`. The name provided should be one of 14 | 15 | - `time` 16 | - the name of a state variable 17 | - the name of a control variable 18 | - the name of a control rate or rate2 (second derivative) 19 | - the path of an output in the ODE relative to the top level of the ODE 20 | 21 | Dymos will find the full path of the given variable and add it to the problem as an objective. 22 | 23 | .. note:: 24 | 25 | Many optimizers do not support multiple objective functions. When constructing a composite 26 | objective you may need to form the objective as an output of a component in your ODE system. 27 | 28 | Second, unlike typical OpenMDAO problems where the `index` can be used to effectively specify 29 | the first or last value of a variable, optimal control problems have two competing notions of index: 30 | the first is the location in time where the objective is to be measured, and the second is the index of a 31 | vector valued variable that is to be considered the objective value, which must be scalar. 32 | 33 | To remove this ambiguity, the `add_objective` method on phase has an argument `loc`, which may 34 | have value `initial` or `final`, specifying whether the objective is to be quantified at the 35 | start or end of the phase. The `index` option gives the index into a non-scalar variable value 36 | to be used as the objective, which must be scalar. 37 | 38 | ## Example: Minimizing Final Time 39 | .. code-block:: python 40 | 41 | phase.add_objective('time', loc='final') 42 | 43 | ## Example: Maximizing Final Mass 44 | 45 | This example assumes that the phase has a state variable named *mass*. 46 | 47 | .. code-block:: python 48 | 49 | phase.add_objective('mass', loc='final', scaler=-1) 50 | -------------------------------------------------------------------------------- /mkdocs/docs/faq/debugging.md: -------------------------------------------------------------------------------- 1 | # How can I debug models when things go wrong? 2 | 3 | Dymos allow the user to build complex optimization models that include dynamic behavior. 4 | Managing that complexity can be a challenge as models grow larger. 5 | In this section we'll talk about some tools that can help when things are going as expected. 6 | 7 | ## Testing 8 | 9 | If you look at the dymos source code, a considerable portion of it is used for testing. 10 | We strongly recommend that you develop tests of your models, from testing that the most basic components work as expected, to testing integrated systems with nonlinear solvers. 11 | In most cases these tests consist of these steps: 12 | 13 | 1. Instantiate an OpenMDAO Problem 14 | 2. Add your model. 15 | 3. Setup the problem. 16 | 4. Set the model inputs. 17 | 5. Call `run_model()` 18 | 6. Check the outputs against known values. 19 | 7. Run `problem.check_partials()` to verify that the analytic partials are reasonably close to finite-difference or complex-step results. 20 | 21 | For example, the tests for the `kappa_comp` in the minimum time-to-climb model looks like this: 22 | 23 | === "test_kappa_comp.py" 24 | {{ inline_source('dymos.examples.min_time_climb.aero.test.test_kappa_comp', 25 | include_def=True, 26 | include_docstring=True, 27 | indent_level=0) 28 | }} 29 | 30 | This consists of two separate tests: one that tests results, and one that tests the partials against finite-differencing. 31 | OpenMDAO includes a useful `assert_check_partials` method that can be used to programmatically verify accurate partials in automated testing. 32 | 33 | ## The N2 Viewer 34 | 35 | When complex models don't output the correct value and the compute method has been double-checked, an incorrect or non-existent connection is frequently to blame. 36 | The goto tool for checking to see if models are correctly connected is [OpenMDAO's N-squared (N2) viewer](http://openmdao.org/twodocs/versions/latest/features/model_visualization/n2_details.html). 37 | This tool provides information about how models are connected and lets the user know when inputs aren't connected to an output as expected. 38 | 39 | It can be invoked from a run script using 40 | 41 | ```python 42 | om.n2(problem.model) 43 | ``` 44 | 45 | or from the command line using 46 | 47 | ```bash 48 | openmdao n2 file.py 49 | ``` 50 | 51 | where file.py is the file that contains an instantiated OpenMDAO Problem. 52 | 53 | ## An example of an N2 of a Dymos model 54 | 55 | Coming soon 56 | 57 | ## Using `debug_print` 58 | 59 | Coming soon 60 | 61 | 62 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | from setuptools import find_packages 3 | 4 | # Setup optional dependencies 5 | optional_dependencies = { 6 | 'docs': [ 7 | 'mkdocs', 8 | 'mkdocs-material', 9 | 'pymdown-extensions', 10 | 'mkdocs-macros-plugin', 11 | 'mkdocs-bibtex', 12 | 'numpydoc>=0.9.1', 13 | 'redbaron', 14 | 'tabulate' 15 | ], 16 | 'test': [ 17 | 'pep8', 18 | 'testflo>=1.3.6', 19 | 'matplotlib', 20 | 'numpydoc>=1.1', 21 | ] 22 | } 23 | 24 | # Add an optional dependency that concatenates all others 25 | optional_dependencies['all'] = sorted([ 26 | dependency 27 | for dependencies in optional_dependencies.values() 28 | for dependency in dependencies 29 | ]) 30 | 31 | 32 | setup(name='dymos', 33 | version='1.0.0', 34 | description='Open-Source Optimization of Dynamic Multidisciplinary Systems', 35 | long_description=''' 36 | Dymos is a framework for the simulation and optimization of dynamical systems within the OpenMDAO Multidisciplinary Analysis and Optimization environment. 37 | Dymos leverages implicit and explicit simulation techniques to simulate generic dynamic systems of arbitary complexity. 38 | 39 | The software has two primary objectives: 40 | - Provide a generic ODE integration interface that allows for the analysis of dynamical systems. 41 | - Allow the user to solve optimal control problems involving dynamical multidisciplinary systems.''', 42 | long_description_content_type='text/markdown', 43 | url='https://github.com/OpenMDAO/dymos', 44 | classifiers=[ 45 | 'Development Status :: 5 - Production/Stable', 46 | 'Intended Audience :: Science/Research', 47 | 'License :: OSI Approved :: Apache Software License', 48 | 'Natural Language :: English', 49 | 'Operating System :: MacOS :: MacOS X', 50 | 'Operating System :: POSIX :: Linux', 51 | 'Operating System :: Microsoft :: Windows', 52 | 'Topic :: Scientific/Engineering', 53 | 'Programming Language :: Python', 54 | 'Programming Language :: Python :: 3', 55 | 'Programming Language :: Python :: Implementation :: CPython', 56 | ], 57 | license='Apache License', 58 | packages=find_packages(), 59 | python_requires=">=3.6", 60 | install_requires=[ 61 | 'openmdao>=3.3.0', 62 | 'numpy>=1.14.1', 63 | 'scipy>=1.0.0' 64 | ], 65 | extras_require=optional_dependencies, 66 | zip_safe=False, 67 | package_data={'dymos.examples.aircraft_steady_flight.aero': ['data/CRM_aero_inputs.dat', 'data/CRM_aero_outputs.dat']}, 68 | ) 69 | -------------------------------------------------------------------------------- /dymos/transcriptions/solve_ivp/components/state_rate_collector_comp.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from ....utils.misc import get_rate_units 3 | from ....options import options as dymos_options 4 | import openmdao.api as om 5 | 6 | 7 | class StateRateCollectorComp(om.ExplicitComponent): 8 | """ 9 | Class definition for StateRateCollectorComp. 10 | 11 | Collects the state rates and outputs them in the units specified in the state options. 12 | For explicit integration this is necessary when the output providing the state rate has 13 | different units than those defined in the state_options/time_options. 14 | 15 | Parameters 16 | ---------- 17 | **kwargs : dict 18 | Dictionary of optional arguments. 19 | """ 20 | def initialize(self): 21 | """ 22 | Declare component options. 23 | """ 24 | self.options.declare( 25 | 'state_options', types=dict, 26 | desc='Dictionary of options for the ODE state variables.') 27 | self.options.declare( 28 | 'time_units', default=None, allow_none=True, types=str, 29 | desc='Units of time') 30 | 31 | # Save the names of the dynamic controls/parameters 32 | self._input_names = {} 33 | self._output_names = {} 34 | 35 | self._no_check_partials = not dymos_options['include_check_partials'] 36 | 37 | def setup(self): 38 | """ 39 | Create inputs/outputs on this component. 40 | """ 41 | state_options = self.options['state_options'] 42 | time_units = self.options['time_units'] 43 | 44 | for name, options in state_options.items(): 45 | self._input_names[name] = 'state_rates_in:{0}_rate'.format(name) 46 | self._output_names[name] = 'state_rates:{0}_rate'.format(name) 47 | shape = options['shape'] 48 | units = options['units'] 49 | 50 | rate_units = get_rate_units(units, time_units) 51 | 52 | self.add_input(self._input_names[name], val=np.ones(shape), units=rate_units) 53 | self.add_output(self._output_names[name], shape=shape, units=rate_units) 54 | 55 | def compute(self, inputs, outputs): 56 | """ 57 | Compute component outputs. 58 | 59 | Parameters 60 | ---------- 61 | inputs : `Vector` 62 | `Vector` containing inputs. 63 | outputs : `Vector` 64 | `Vector` containing outputs. 65 | """ 66 | state_options = self.options['state_options'] 67 | 68 | for name, options in state_options.items(): 69 | outputs[self._output_names[name]] = inputs[self._input_names[name]] 70 | -------------------------------------------------------------------------------- /dymos/phase/test/test_add_parameter_types.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import openmdao.api as om 4 | from openmdao.utils.testing_utils import use_tempdirs 5 | 6 | import dymos as dm 7 | 8 | 9 | class ODEComp(om.ExplicitComponent): 10 | def initialize(self): 11 | self.options.declare('num_nodes', types=int) 12 | 13 | def setup(self): 14 | nn = self.options['num_nodes'] 15 | # z is the state vector, a nn x 2 x 2 in the form of [[x, y], [vx, vy]] 16 | self.add_input('param', shape=3, units=None) 17 | self.add_input('z', shape=(nn, 2, 2), units=None) 18 | self.add_output('zdot', shape=(nn, 2, 2), units=None) 19 | 20 | self.declare_partials(of='zdot', wrt='z', method='cs') 21 | self.declare_coloring(wrt=['z'], method='cs', num_full_jacs=5, tol=1.0E-12) 22 | 23 | def compute(self, inputs, outputs): 24 | print('param', inputs['param']) 25 | outputs['zdot'][:, 0, 0] = inputs['z'][:, 1, 0] 26 | outputs['zdot'][:, 0, 1] = inputs['z'][:, 1, 1] 27 | outputs['zdot'][:, 1, 0] = 0.0 28 | outputs['zdot'][:, 1, 1] = -9.81 29 | 30 | 31 | def add_parameter_test(testShape=None): 32 | p = om.Problem() 33 | 34 | tx = dm.Radau(num_segments=10, order=3, solve_segments=False) 35 | 36 | traj = dm.Trajectory() 37 | phase = dm.Phase(ode_class=ODEComp, transcription=tx) 38 | 39 | if testShape is None: 40 | phase.add_parameter('param', dynamic=False) 41 | else: 42 | phase.add_parameter('param', shape=testShape, dynamic=False) 43 | 44 | traj.add_phase('phase', phase) 45 | 46 | p.model.add_subsystem('traj', traj) 47 | 48 | phase.set_time_options(fix_initial=True, duration_bounds=(1, 5), units=None) 49 | phase.add_state('z', rate_source='zdot', fix_initial=True, units=None) 50 | 51 | phase.add_boundary_constraint('z', loc='final', lower=0, upper=0, indices=[1]) 52 | phase.add_objective('time', loc='final') 53 | 54 | p.driver = om.pyOptSparseDriver() 55 | p.driver.declare_coloring(tol=1.0E-12) 56 | 57 | p.setup() 58 | 59 | p['traj.phase.parameters:param'] = [2.5, 3.5, 4.5] 60 | 61 | p.set_val('traj.phase.t_initial', 0) 62 | p.set_val('traj.phase.t_duration', 5) 63 | p.set_val('traj.phase.states:z', phase.interpolate(ys=[[[0, 0], [10, 10]], [[10, 0], [10, -10]]], nodes='state_input')) 64 | 65 | p.run_driver() 66 | 67 | 68 | @use_tempdirs 69 | class TestParameterTypes(unittest.TestCase): 70 | def test_tuple(self): 71 | add_parameter_test((3, )) 72 | 73 | def test_list(self): 74 | add_parameter_test([3, ]) 75 | 76 | def test_scaler(self): 77 | add_parameter_test(3) 78 | 79 | def test_nothing(self): 80 | add_parameter_test() 81 | -------------------------------------------------------------------------------- /dymos/examples/cannonball/cannonball_ode.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.interpolate import interp1d 3 | 4 | import openmdao.api as om 5 | from dymos.models.atmosphere.atmos_1976 import USatm1976Data 6 | 7 | 8 | english_to_metric_rho = om.unit_conversion('slug/ft**3', 'kg/m**3')[0] 9 | english_to_metric_h = om.unit_conversion('ft', 'm')[0] 10 | rho_interp = interp1d(np.array(USatm1976Data.alt*english_to_metric_h, dtype=complex), 11 | np.array(USatm1976Data.rho*english_to_metric_rho, dtype=complex), kind='linear') 12 | 13 | 14 | class CannonballODE(om.ExplicitComponent): 15 | """ 16 | Cannonball ODE assuming flat earth and accounting for air resistance 17 | """ 18 | 19 | def initialize(self): 20 | self.options.declare('num_nodes', types=int) 21 | 22 | def setup(self): 23 | nn = self.options['num_nodes'] 24 | 25 | # static parameters 26 | self.add_input('m', units='kg') 27 | self.add_input('S', units='m**2') 28 | # 0.5 good assumption for a sphere 29 | self.add_input('CD', 0.5) 30 | 31 | # time varying inputs 32 | self.add_input('h', units='m', shape=nn) 33 | self.add_input('v', units='m/s', shape=nn) 34 | self.add_input('gam', units='rad', shape=nn) 35 | 36 | # state rates 37 | self.add_output('v_dot', shape=nn, units='m/s**2') 38 | self.add_output('gam_dot', shape=nn, units='rad/s') 39 | self.add_output('h_dot', shape=nn, units='m/s') 40 | self.add_output('r_dot', shape=nn, units='m/s') 41 | self.add_output('ke', shape=nn, units='J') 42 | 43 | # Ask OpenMDAO to compute the partial derivatives using complex-step 44 | # with a partial coloring algorithm for improved performance, and use 45 | # coloring to determine the sparsity pattern. 46 | self.declare_coloring(wrt='*', method='cs') 47 | 48 | def compute(self, inputs, outputs): 49 | 50 | gam = inputs['gam'] 51 | v = inputs['v'] 52 | h = inputs['h'] 53 | m = inputs['m'] 54 | S = inputs['S'] 55 | CD = inputs['CD'] 56 | 57 | GRAVITY = 9.80665 # m/s**2 58 | 59 | # handle complex-step gracefully from the interpolant 60 | if np.iscomplexobj(h): 61 | rho = rho_interp(inputs['h']) 62 | else: 63 | rho = rho_interp(inputs['h']).real 64 | 65 | q = 0.5*rho*inputs['v']**2 66 | qS = q * S 67 | D = qS * CD 68 | cgam = np.cos(gam) 69 | sgam = np.sin(gam) 70 | outputs['v_dot'] = - D/m-GRAVITY*sgam 71 | outputs['gam_dot'] = -(GRAVITY/v)*cgam 72 | outputs['h_dot'] = v*sgam 73 | outputs['r_dot'] = v*cgam 74 | outputs['ke'] = 0.5*m*v**2 75 | -------------------------------------------------------------------------------- /dymos/grid_refinement/refinement.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utility for performing grid refinement on each phase. 3 | """ 4 | from .ph_adaptive.ph_adaptive import PHAdaptive 5 | from .hp_adaptive.hp_adaptive import HPAdaptive 6 | from .write_iteration import write_error, write_refine_iter 7 | 8 | from dymos.grid_refinement.error_estimation import check_error 9 | from dymos.load_case import load_case, find_phases 10 | 11 | import numpy as np 12 | import sys 13 | 14 | 15 | def _refine_iter(problem, refine_iteration_limit=0, refine_method='hp'): 16 | """ 17 | This function performs grid refinement for a phases in which solve_segments is true. 18 | 19 | Parameters 20 | ---------- 21 | problem : om.Problem 22 | The OpenMDAO problem object to be run. 23 | refine_method : String 24 | The choice of refinement algorithm to use for grid refinement 25 | refine_iteration_limit : int 26 | The number of passes through the grid refinement algorithm to be made. 27 | """ 28 | phases = find_phases(problem.model) 29 | refinement_methods = {'hp': HPAdaptive, 'ph': PHAdaptive} 30 | 31 | if refine_iteration_limit > 0: 32 | out_file = 'grid_refinement.out' 33 | 34 | ref = refinement_methods[refine_method](phases) 35 | with open(out_file, 'w+') as f: 36 | for i in range(refine_iteration_limit): 37 | refine_results = check_error(phases) 38 | 39 | refined_phases = [phase_path for phase_path in refine_results if 40 | phases[phase_path].refine_options['refine'] and 41 | np.any(refine_results[phase_path]['need_refinement'])] 42 | 43 | for stream in f, sys.stdout: 44 | write_error(stream, i, phases, refine_results) 45 | 46 | if not refined_phases: 47 | break 48 | 49 | ref.refine(refine_results, i) 50 | 51 | for stream in f, sys.stdout: 52 | write_refine_iter(stream, i, phases, refine_results) 53 | 54 | prev_soln = {'inputs': problem.model.list_inputs(out_stream=None, units=True, prom_name=True), 55 | 'outputs': problem.model.list_outputs(out_stream=None, units=True, prom_name=True)} 56 | 57 | problem.setup() 58 | 59 | load_case(problem, prev_soln) 60 | 61 | problem.run_driver() 62 | 63 | for stream in [f, sys.stdout]: 64 | if i == refine_iteration_limit-1: 65 | print('Iteration limit exceeded. Unable to satisfy specified tolerance', file=stream) 66 | else: 67 | print('Successfully completed grid refinement.', file=stream) 68 | print(50 * '=') 69 | -------------------------------------------------------------------------------- /mkdocs/docs/examples/multibranch_trajectory/multibranch_trajectory.md: -------------------------------------------------------------------------------- 1 | # Multibranch Trajectory 2 | 3 | This example demonstrates the use of a Trajectory to encapsulate a 4 | series of branching phases. 5 | 6 | ## Overview 7 | 8 | For this example, we build a system that contains two components: the 9 | first component represents a battery pack that contains multiple cells 10 | in parallel, and the second component represents a bank of DC electric 11 | motors (also in parallel) driving a gearbox to achieve a desired power 12 | output. The battery cells have a state of charge that decays as current 13 | is drawn from the battery. The open circuit voltage of the battery is a 14 | function of the state of charge. At any point in time, the coupling 15 | between the battery and the motor component is solved with a Newton 16 | solver in the containing group for a line current that satisfies the 17 | equations. 18 | 19 | Both the battery and the motor models allow the number of cells and the 20 | number of motors to be modified by setting the _n\_parallel_ option in 21 | their respective options dictionaries. For this model, we start with 3 22 | cells and 3 motors. We will simulate failure of a cell or battery by 23 | setting _n\_parallel_ to 2. 24 | 25 | Branching phases are a set of linked phases in a trajectory where the 26 | input ends of multiple phases are connected to the output of a single 27 | phase. This way you can simulate alternative trajectory paths in the 28 | same model. For this example, we will start with a single phase 29 | (_phase0_) that simulates the model for one hour. Three follow-on 30 | phases will be linked to the output of the first phase: _phase1_ will 31 | run as normal, _phase1\_bfail_ will fail one of the battery cells, and 32 | _phase1\_mfail_ will fail a motor. All three of these phases start 33 | where _phase0_ leaves off, so they share the same initial time and 34 | state of charge. 35 | 36 | ## Battery and Motor models 37 | 38 | The models are loosely based on the work done in Chin[@chin2019battery]. 39 | 40 | === "batteries.py" 41 | {{ inline_source('dymos.examples.battery_multibranch.batteries', 42 | include_def=True, 43 | include_docstring=True, 44 | indent_level=0) 45 | }} 46 | 47 | === "motors.py" 48 | {{ inline_source('dymos.examples.battery_multibranch.motors', 49 | include_def=True, 50 | include_docstring=True, 51 | indent_level=0) 52 | }} 53 | 54 | === "battery_multibranch_ode.py" 55 | {{ inline_source('dymos.examples.battery_multibranch.battery_multibranch_ode', 56 | include_def=True, 57 | include_docstring=True, 58 | indent_level=0) 59 | }} 60 | 61 | ## Building and running the problem 62 | 63 | {{ embed_test('dymos.examples.battery_multibranch.doc.test_multibranch_trajectory_for_docs.TestBatteryBranchingPhasesForDocs.test_basic') }} 64 | 65 | ## References 66 | 67 | \bibliography 68 | -------------------------------------------------------------------------------- /dymos/examples/min_time_climb/aero/test/test_aerodynamics.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | 4 | import numpy as np 5 | from numpy.testing import assert_almost_equal 6 | 7 | import openmdao.api as om 8 | 9 | from dymos.examples.min_time_climb.aero import AeroGroup 10 | 11 | 12 | class TestAeroGroup(unittest.TestCase): 13 | 14 | def setUp(self): 15 | self.prob = om.Problem(model=om.Group()) 16 | nn = 5 17 | 18 | ivc = om.IndepVarComp() 19 | 20 | ivc.add_output('rho', val=0.0001 * np.ones(nn), units='kg/m**3') 21 | ivc.add_output('v', val=0.0001 * np.ones(nn), units='m/s') 22 | ivc.add_output('S', val=0.0001 * np.ones(nn), units='m**2') 23 | ivc.add_output('alpha', val=np.zeros(nn), units='rad') 24 | ivc.add_output('sos', val=np.ones(nn), units='m/s') 25 | self.prob.model.add_subsystem(name='ivc', subsys=ivc, promotes_outputs=['*']) 26 | 27 | self.prob.model.add_subsystem(name='aero', subsys=AeroGroup(num_nodes=nn)) 28 | 29 | self.prob.model.connect('rho', 'aero.rho') 30 | self.prob.model.connect('v', 'aero.v') 31 | self.prob.model.connect('S', 'aero.S') 32 | self.prob.model.connect('alpha', 'aero.alpha') 33 | self.prob.model.connect('sos', 'aero.sos') 34 | 35 | self.prob.setup() 36 | 37 | def test_aero_values(self): 38 | 39 | self.prob['rho'] = 1.2250409 40 | self.prob['v'] = 115.824 41 | self.prob['S'] = 49.2386 42 | 43 | self.prob['alpha'] = np.radians(4.1900199) 44 | self.prob['sos'] = 340.29396 45 | self.prob.run_model() 46 | 47 | q_expected = 0.5 * self.prob['rho'] * self.prob['v']**2 48 | lift_expected = q_expected * self.prob['S'] * self.prob['aero.CL'] # [101779.451502] 49 | drag_expected = q_expected * self.prob['S'] * self.prob['aero.CD'] # [9278.85725577] 50 | 51 | assert_almost_equal(self.prob['aero.q'], q_expected, decimal=7) 52 | assert_almost_equal(self.prob['aero.f_lift'], lift_expected, decimal=7) 53 | assert_almost_equal(self.prob['aero.f_drag'], drag_expected, decimal=7) 54 | 55 | def testAeroDerivs(self): 56 | cpd = self.prob.check_partials(compact_print=True) 57 | 58 | for comp in cpd: 59 | for (var, wrt) in cpd[comp]: 60 | np.testing.assert_almost_equal(actual=cpd[comp][var, wrt]['J_fwd'], 61 | desired=cpd[comp][var, wrt]['J_fd'], 62 | decimal=5, 63 | err_msg='Possible error in partials of component ' 64 | '{0} for {1} wrt {2}'.format(comp, var, wrt)) 65 | 66 | 67 | if __name__ == '__main__': # pragma: no cover 68 | unittest.main() 69 | -------------------------------------------------------------------------------- /dymos/examples/shuttle_reentry/heating_comp.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import openmdao.api as om 3 | 4 | 5 | class AerodynamicHeating(om.ExplicitComponent): 6 | """ 7 | Defines the Aerodynamic heating equations for the shuttle reentry problem. 8 | 9 | References 10 | ---------- 11 | .. [1] Betts, John T., Practical Methods for Optimal Control and Estimation Using Nonlinear 12 | Programming, p. 248, 2010. 13 | """ 14 | 15 | def initialize(self): 16 | self.options.declare('num_nodes', types=int) 17 | 18 | def setup(self): 19 | nn = self.options['num_nodes'] 20 | 21 | self.add_input('rho', val=np.ones(nn), desc='local density', units='slug/ft**3') 22 | self.add_input('v', val=np.ones(nn), desc='velocity of shuttle', units='ft/s') 23 | self.add_input('alpha', val=np.ones(nn), desc='angle of attack of shuttle', 24 | units='deg') 25 | 26 | self.add_output('q', val=np.ones(nn), 27 | desc='aerodynamic heating on leading edge of shuttle', 28 | units='Btu/ft**2/s') 29 | 30 | partial_range = np.arange(nn) 31 | 32 | self.declare_partials('q', 'rho', rows=partial_range, cols=partial_range) 33 | self.declare_partials('q', 'v', rows=partial_range, cols=partial_range) 34 | self.declare_partials('q', 'alpha', rows=partial_range, cols=partial_range) 35 | 36 | def compute(self, inputs, outputs): 37 | rho = inputs['rho'] 38 | v = inputs['v'] 39 | alpha = inputs['alpha'] 40 | c_0 = 1.0672181 41 | c_1 = -0.19213774e-1 42 | c_2 = 0.21286289e-3 43 | c_3 = -0.10117249e-5 44 | 45 | if np.any(v < 0): 46 | raise om.AnalysisError('Negative velocity magnitude encountered') 47 | 48 | q_r = 17700.0 * np.sqrt(rho) * (0.0001 * v) ** 3.07 49 | q_a = c_0 + c_1 * alpha + c_2 * alpha ** 2 + c_3 * alpha ** 3 50 | 51 | outputs['q'] = q_r * q_a 52 | 53 | def compute_partials(self, inputs, partials): 54 | rho = inputs['rho'] 55 | v = inputs['v'] 56 | alpha = inputs['alpha'] 57 | c_0 = 1.0672181 58 | c_1 = -.19213774e-1 59 | c_2 = .21286289e-3 60 | c_3 = -.10117249e-5 61 | 62 | if np.any(v < 0): 63 | raise om.AnalysisError('Negative velocity magnitude encountered') 64 | 65 | sqrt_rho = np.sqrt(rho) 66 | 67 | q_r = 17700 * sqrt_rho * (.0001 * v) ** 3.07 68 | q_a = c_0 + c_1 * alpha + c_2 * alpha ** 2 + c_3 * alpha ** 3 69 | 70 | dqr_drho = 0.5 * q_r / rho 71 | dqr_dv = 17700 * sqrt_rho * 0.0001 * 3.07 * (0.0001 * v) ** 2.07 72 | 73 | dqa_dalpha = c_1 + 2 * c_2 * alpha + 3 * c_3 * alpha ** 2 74 | 75 | partials['q', 'rho'] = dqr_drho * q_a 76 | partials['q', 'v'] = dqr_dv * q_a 77 | partials['q', 'alpha'] = dqa_dalpha * q_r 78 | -------------------------------------------------------------------------------- /dymos/examples/aircraft_steady_flight/flight_equlibrium/lift_equilibrium_comp.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import openmdao.api as om 4 | 5 | 6 | class LiftEquilibriumComp(om.ExplicitComponent): 7 | """ 8 | Compute the rates of TAS and flight path angle required to match a given 9 | flight condition. 10 | """ 11 | 12 | def initialize(self): 13 | self.options.declare('num_nodes', types=int) 14 | 15 | self._g = 9.80665 16 | 17 | def setup(self): 18 | n = self.options['num_nodes'] 19 | 20 | # Parameter inputs 21 | self.add_input(name='CT', shape=(n,), desc='thrust coefficient', units=None) 22 | self.add_input(name='W_total', shape=(n,), desc='total aircraft weight', units='N') 23 | self.add_input(name='alpha', shape=(n,), desc='angle of attack', units='rad') 24 | self.add_input(name='gam', shape=(n,), desc='flight path angle', units='rad') 25 | self.add_input(name='q', shape=(n,), desc='dynamic pressure', units='Pa') 26 | self.add_input(name='S', shape=(n,), desc='reference area', units='m**2') 27 | 28 | self.add_output(name='CL_eq', shape=(n,), 29 | desc='lift coefficient required for steady, level flight', 30 | units=None) 31 | 32 | ar = np.arange(n) 33 | 34 | self.declare_partials(of='CL_eq', wrt='CT', rows=ar, cols=ar) 35 | self.declare_partials(of='CL_eq', wrt='W_total', rows=ar, cols=ar) 36 | self.declare_partials(of='CL_eq', wrt='alpha', rows=ar, cols=ar) 37 | self.declare_partials(of='CL_eq', wrt='gam', rows=ar, cols=ar) 38 | self.declare_partials(of='CL_eq', wrt='q', rows=ar, cols=ar) 39 | self.declare_partials(of='CL_eq', wrt='S', rows=ar, cols=ar) 40 | 41 | def compute(self, inputs, outputs): 42 | CT = inputs['CT'] 43 | W_total = inputs['W_total'] 44 | alpha = inputs['alpha'] 45 | gam = inputs['gam'] 46 | q = inputs['q'] 47 | S = inputs['S'] 48 | 49 | sa = np.sin(alpha) 50 | cgam = np.cos(gam) 51 | 52 | qS = q * S 53 | 54 | outputs['CL_eq'] = W_total * cgam / qS - CT * sa 55 | 56 | def compute_partials(self, inputs, partials): 57 | CT = inputs['CT'] 58 | W_total = inputs['W_total'] 59 | alpha = inputs['alpha'] 60 | gam = inputs['gam'] 61 | q = inputs['q'] 62 | S = inputs['S'] 63 | 64 | ca = np.cos(alpha) 65 | sa = np.sin(alpha) 66 | cgam = np.cos(gam) 67 | sgam = np.sin(gam) 68 | 69 | qS = q * S 70 | 71 | partials['CL_eq', 'CT'] = -sa 72 | partials['CL_eq', 'W_total'] = cgam / qS 73 | partials['CL_eq', 'alpha'] = -CT * ca 74 | partials['CL_eq', 'gam'] = -W_total * sgam / qS 75 | partials['CL_eq', 'q'] = -W_total * cgam / (q**2 * S) 76 | partials['CL_eq', 'S'] = -W_total * cgam / (q * S**2) 77 | -------------------------------------------------------------------------------- /dymos/examples/finite_burn_orbit_raise/test/test_finite_burn_eom.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import numpy as np 4 | 5 | import openmdao.api as om 6 | 7 | from openmdao.utils.assert_utils import assert_near_equal, assert_check_partials 8 | from openmdao.utils.testing_utils import use_tempdirs 9 | 10 | from dymos.examples.finite_burn_orbit_raise.finite_burn_eom import FiniteBurnODE 11 | 12 | 13 | @use_tempdirs 14 | class TestFiniteBurnEOM(unittest.TestCase): 15 | 16 | @classmethod 17 | def setUpClass(cls): 18 | nn = 2 19 | 20 | p = cls.p = om.Problem(model=om.Group()) 21 | 22 | ivc = p.model.add_subsystem('ivc', om.IndepVarComp(), promotes_outputs=['*']) 23 | 24 | p.model.add_subsystem('ode', subsys=FiniteBurnODE(num_nodes=nn), promotes_inputs=['*'], 25 | promotes_outputs=['*']) 26 | 27 | ivc.add_output('r', 28 | val=np.ones(nn), 29 | desc='radius from center of attraction', 30 | units='DU') 31 | 32 | ivc.add_output('theta', 33 | val=np.zeros(nn), 34 | desc='anomaly term', 35 | units='rad') 36 | 37 | ivc.add_output('vr', 38 | val=np.zeros(nn), 39 | desc='local vertical velocity component', 40 | units='DU/TU') 41 | 42 | ivc.add_output('vt', 43 | val=np.zeros(nn), 44 | desc='local horizontal velocity component', 45 | units='DU/TU') 46 | 47 | ivc.add_output('accel', 48 | val=np.zeros(nn), 49 | desc='acceleration due to thrust', 50 | units='DU/TU**2') 51 | 52 | ivc.add_output('u1', 53 | val=np.zeros(nn), 54 | desc='thrust angle above local horizontal', 55 | units='rad') 56 | 57 | ivc.add_output('c', 58 | val=np.zeros(nn), 59 | desc='exhaust velocity', 60 | units='DU/TU') 61 | 62 | p.setup() 63 | 64 | p['r'] = 2.0 65 | p['theta'] = 0.05 66 | p['vr'] = 0.2 67 | p['vt'] = 1.0 68 | p['accel'] = [0.1, 0.1] 69 | p['u1'] = [0.0, np.pi] 70 | p['c'] = 9.80665 * 2000 71 | 72 | p.run_model() 73 | 74 | def test_outputs(self): 75 | p = self.p 76 | assert_near_equal(p['r_dot'], p['vr']) 77 | assert_near_equal(p['theta_dot'], p['vt'] / p['r']) 78 | assert_near_equal(p['at_dot'], p['accel']**2 / p['c']) 79 | 80 | def test_partials(self): 81 | cpd = self.p.check_partials(compact_print=False) 82 | assert_check_partials(cpd, atol=1.0E-5, rtol=2.0) 83 | 84 | 85 | if __name__ == '__main__': # pragma: no cover 86 | unittest.main() 87 | -------------------------------------------------------------------------------- /dymos/examples/aircraft_steady_flight/flight_equlibrium/steady_flight_equilibrium_group.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import openmdao.api as om 4 | 5 | from ..aero.aerodynamics_group import AerodynamicsGroup 6 | from .lift_equilibrium_comp import LiftEquilibriumComp 7 | from .thrust_equilibrium_comp import ThrustEquilibriumComp 8 | 9 | 10 | class SteadyFlightEquilibriumGroup(om.Group): 11 | 12 | def initialize(self): 13 | self.options.declare('num_nodes', types=int, 14 | desc='Number of nodes to be evaluated in the RHS') 15 | 16 | def setup(self): 17 | nn = self.options['num_nodes'] 18 | 19 | self.add_subsystem('aero', 20 | subsys=AerodynamicsGroup(num_nodes=nn), 21 | promotes_inputs=['alt']) 22 | 23 | self.add_subsystem('thrust_eq_comp', 24 | subsys=ThrustEquilibriumComp(num_nodes=nn), 25 | promotes_inputs=['q', 'S', 'gam', 'alpha', 'W_total'], 26 | promotes_outputs=['CT']) 27 | 28 | self.add_subsystem('lift_eq_comp', 29 | subsys=LiftEquilibriumComp(num_nodes=nn), 30 | promotes_inputs=['q', 'S', 'gam', 'alpha', 'W_total', 'CT'], 31 | promotes_outputs=['CL_eq']) 32 | 33 | bal = self.add_subsystem(name='alpha_eta_balance', 34 | subsys=om.BalanceComp(), 35 | promotes_outputs=['alpha', 'eta']) 36 | 37 | self.connect('alpha', ('aero.alpha')) 38 | self.connect('eta', ('aero.eta')) 39 | 40 | bal.add_balance('alpha', units='rad', eq_units=None, lhs_name='CL_eq', 41 | rhs_name='CL', val=0.01*np.ones(nn), lower=-20, upper=30, res_ref=1.0) 42 | 43 | bal.add_balance('eta', units='rad', val=0.01*np.ones(nn), eq_units=None, lhs_name='CM', 44 | lower=-30, upper=30, res_ref=1.0) 45 | 46 | self.connect('aero.CL', 'alpha_eta_balance.CL') 47 | self.connect('aero.CD', 'thrust_eq_comp.CD') 48 | self.connect('aero.CM', 'alpha_eta_balance.CM') 49 | self.connect('CL_eq', ('alpha_eta_balance.CL_eq')) 50 | 51 | self.linear_solver = om.DirectSolver() 52 | self.nonlinear_solver = om.NewtonSolver() 53 | self.nonlinear_solver.options['atol'] = 1e-14 54 | self.nonlinear_solver.options['rtol'] = 1e-14 55 | self.nonlinear_solver.options['solve_subsystems'] = True 56 | self.nonlinear_solver.options['err_on_non_converge'] = True 57 | self.nonlinear_solver.options['max_sub_solves'] = 10 58 | self.nonlinear_solver.options['maxiter'] = 150 59 | self.nonlinear_solver.options['iprint'] = -1 60 | self.nonlinear_solver.linesearch = om.BoundsEnforceLS() 61 | self.nonlinear_solver.linesearch.options['print_bound_enforce'] = True 62 | -------------------------------------------------------------------------------- /dymos/examples/brachistochrone/test/test_brachistochrone_implicit_recorder.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | 4 | from openmdao.utils.testing_utils import use_tempdirs 5 | 6 | 7 | @use_tempdirs 8 | class TestBrachistochroneRecordingExample(unittest.TestCase): 9 | 10 | @classmethod 11 | def tearDownClass(cls): 12 | for filename in ['brachistochrone_solution.db']: 13 | if os.path.exists(filename): 14 | os.remove(filename) 15 | 16 | def test_brachistochrone_recording(self): 17 | import matplotlib 18 | matplotlib.use('Agg') 19 | import openmdao.api as om 20 | from openmdao.utils.assert_utils import assert_near_equal 21 | import dymos as dm 22 | from dymos.examples.brachistochrone.brachistochrone_ode import BrachistochroneODE 23 | 24 | p = om.Problem(model=om.Group()) 25 | p.driver = om.ScipyOptimizeDriver() 26 | 27 | phase = dm.Phase(ode_class=BrachistochroneODE, transcription=dm.GaussLobatto(num_segments=10)) 28 | 29 | p.model.add_subsystem('phase0', phase) 30 | 31 | phase.set_time_options(fix_initial=True, duration_bounds=(.5, 10)) 32 | 33 | phase.add_state('x', fix_initial=True, fix_final=True) 34 | phase.add_state('y', fix_initial=True, fix_final=True) 35 | phase.add_state('v', fix_initial=True) 36 | 37 | phase.add_control('theta', units='deg', 38 | rate_continuity=False, lower=0.01, upper=179.9) 39 | 40 | phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) 41 | 42 | # Minimize time at the end of the phase 43 | phase.add_objective('time', loc='final', scaler=10) 44 | 45 | p.model.linear_solver = om.DirectSolver() 46 | 47 | p.setup() 48 | 49 | p['phase0.t_initial'] = 0.0 50 | p['phase0.t_duration'] = 2.0 51 | 52 | p['phase0.states:x'] = phase.interpolate(ys=[0, 10], nodes='state_input') 53 | p['phase0.states:y'] = phase.interpolate(ys=[10, 5], nodes='state_input') 54 | p['phase0.states:v'] = phase.interpolate(ys=[0, 9.9], nodes='state_input') 55 | p['phase0.controls:theta'] = phase.interpolate(ys=[5, 100.5], nodes='control_input') 56 | 57 | # Solve for the optimal trajectory 58 | dm.run_problem(p) 59 | 60 | # Test the results 61 | assert_near_equal(p.get_val('phase0.timeseries.time')[-1], 1.8016, tolerance=1.0E-3) 62 | 63 | case = om.CaseReader('dymos_solution.db').get_case('final') 64 | 65 | outputs = dict([(o[0], o[1]) for o in case.list_outputs(units=True, shape=True, 66 | out_stream=None)]) 67 | 68 | assert_near_equal(p['phase0.controls:theta'], 69 | outputs['phase0.control_group.indep_controls.controls:theta']['value']) 70 | 71 | 72 | if __name__ == '__main__': # pragma: no cover 73 | unittest.main() 74 | -------------------------------------------------------------------------------- /dymos/transcriptions/pseudospectral/components/test/test_control_endpoint_defect_comp.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import numpy as np 4 | 5 | import openmdao.api as om 6 | from openmdao.utils.assert_utils import assert_near_equal 7 | from dymos.utils.testing_utils import assert_check_partials 8 | 9 | import dymos as dm 10 | from dymos.transcriptions.pseudospectral.components import ControlEndpointDefectComp 11 | from dymos.transcriptions.grid_data import GridData 12 | 13 | 14 | class TestControlEndpointDefectComp(unittest.TestCase): 15 | 16 | def setUp(self): 17 | dm.options['include_check_partials'] = True 18 | gd = GridData(num_segments=2, segment_ends=np.array([0., 2., 4.]), 19 | transcription='radau-ps', transcription_order=3) 20 | 21 | self.gd = gd 22 | 23 | self.p = om.Problem(model=om.Group()) 24 | 25 | control_opts = {'u': {'units': 'm', 'shape': (1,), 'dynamic': True, 'opt': True}, 26 | 'v': {'units': 'm', 'shape': (3, 2), 'dynamic': True, 'opt': True}} 27 | 28 | indep_comp = om.IndepVarComp() 29 | self.p.model.add_subsystem('indep', indep_comp, promotes=['*']) 30 | 31 | indep_comp.add_output('controls:u', 32 | val=np.zeros((gd.subset_num_nodes['all'], 1)), units='m') 33 | 34 | indep_comp.add_output('controls:v', 35 | val=np.zeros((gd.subset_num_nodes['all'], 3, 2)), units='m') 36 | 37 | self.p.model.add_subsystem('endpoint_defect_comp', 38 | subsys=ControlEndpointDefectComp(grid_data=gd, 39 | control_options=control_opts)) 40 | 41 | self.p.model.connect('controls:u', 'endpoint_defect_comp.controls:u') 42 | self.p.model.connect('controls:v', 'endpoint_defect_comp.controls:v') 43 | 44 | self.p.setup(force_alloc_complex=True) 45 | 46 | self.p['controls:u'] = np.random.random((gd.subset_num_nodes['all'], 1)) 47 | self.p['controls:v'] = np.random.random((gd.subset_num_nodes['all'], 3, 2)) 48 | 49 | self.p.run_model() 50 | 51 | def tearDown(self): 52 | dm.options['include_check_partials'] = False 53 | 54 | def test_results(self): 55 | 56 | u_coefs = np.polyfit(self.gd.node_ptau[-4:-1], self.p['controls:u'][-4:-1], deg=2) 57 | u_poly = np.poly1d(u_coefs.ravel()) 58 | u_interp = u_poly(1.0) 59 | u_given = self.p['controls:u'][-1] 60 | assert_near_equal(np.ravel(self.p['endpoint_defect_comp.control_endpoint_defects:u']), 61 | np.ravel(u_given - u_interp), 62 | tolerance=1.0E-12) 63 | 64 | def test_partials(self): 65 | cpd = self.p.check_partials(compact_print=False, method='cs') 66 | assert_check_partials(cpd) 67 | 68 | 69 | if __name__ == '__main__': # pragma: no cover 70 | unittest.main() 71 | -------------------------------------------------------------------------------- /dymos/examples/brachistochrone/brachistochrone_vector_states_ode.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import openmdao.api as om 3 | 4 | 5 | class BrachistochroneVectorStatesODE(om.ExplicitComponent): 6 | 7 | def initialize(self): 8 | self.options.declare('num_nodes', types=int) 9 | 10 | def setup(self): 11 | nn = self.options['num_nodes'] 12 | 13 | # Inputs 14 | self.add_input('v', val=np.zeros(nn), desc='velocity', units='m/s') 15 | 16 | self.add_input('g', val=9.80665 * np.ones(nn), desc='grav. acceleration', units='m/s/s') 17 | 18 | self.add_input('theta', val=np.zeros(nn), desc='angle of wire', units='rad') 19 | 20 | self.add_output('pos_dot', val=np.zeros((nn, 2)), desc='velocity components', units='m/s', 21 | tags=['state_rate_source:pos']) 22 | 23 | self.add_output('vdot', val=np.zeros(nn), desc='acceleration magnitude', units='m/s**2', 24 | tags=['state_rate_source:v', 'state_units:m/s']) 25 | 26 | self.add_output('check', val=np.zeros(nn), desc='check solution: v/sin(theta) = constant', 27 | units='m/s') 28 | 29 | # Setup partials 30 | arange = np.arange(self.options['num_nodes']) 31 | 32 | self.declare_partials(of='vdot', wrt='g', rows=arange, cols=arange) 33 | self.declare_partials(of='vdot', wrt='theta', rows=arange, cols=arange) 34 | 35 | rs = np.arange(2*nn, dtype=int) 36 | cs = np.repeat(np.arange(nn, dtype=int), 2) 37 | self.declare_partials(of='pos_dot', wrt='v', rows=rs, cols=cs) 38 | self.declare_partials(of='pos_dot', wrt='theta', rows=rs, cols=cs) 39 | 40 | self.declare_partials(of='check', wrt='v', rows=arange, cols=arange) 41 | self.declare_partials(of='check', wrt='theta', rows=arange, cols=arange) 42 | 43 | def compute(self, inputs, outputs): 44 | theta = inputs['theta'] 45 | cos_theta = np.cos(theta) 46 | sin_theta = np.sin(theta) 47 | g = inputs['g'] 48 | v = inputs['v'] 49 | 50 | outputs['vdot'] = g * cos_theta 51 | outputs['pos_dot'][:, 0] = v * sin_theta 52 | outputs['pos_dot'][:, 1] = -v * cos_theta 53 | outputs['check'] = v / sin_theta 54 | 55 | def compute_partials(self, inputs, jacobian): 56 | theta = inputs['theta'] 57 | cos_theta = np.cos(theta) 58 | sin_theta = np.sin(theta) 59 | g = inputs['g'] 60 | v = inputs['v'] 61 | 62 | jacobian['vdot', 'g'] = cos_theta 63 | jacobian['vdot', 'theta'] = -g * sin_theta 64 | 65 | jacobian['pos_dot', 'v'][0::2] = sin_theta 66 | jacobian['pos_dot', 'v'][1::2] = -cos_theta 67 | 68 | jacobian['pos_dot', 'theta'][0::2] = v * cos_theta 69 | jacobian['pos_dot', 'theta'][1::2] = v * sin_theta 70 | 71 | jacobian['check', 'v'] = 1 / sin_theta 72 | jacobian['check', 'theta'] = -v * cos_theta / sin_theta**2 73 | -------------------------------------------------------------------------------- /dymos/examples/brachistochrone/doc/brachistochrone_ode.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import openmdao.api as om 3 | 4 | 5 | class BrachistochroneODE(om.ExplicitComponent): 6 | 7 | def initialize(self): 8 | self.options.declare('num_nodes', types=int) 9 | self.options.declare('g', default=9.80665, desc='gravitational acceleration in m/s**2') 10 | 11 | def setup(self): 12 | nn = self.options['num_nodes'] 13 | 14 | # Inputs 15 | self.add_input('v', val=np.zeros(nn), desc='velocity', units='m/s') 16 | 17 | self.add_input('theta', val=np.ones(nn), desc='angle of wire', units='rad') 18 | 19 | self.add_output('xdot', val=np.zeros(nn), desc='velocity component in x', units='m/s', 20 | tags=['state_rate_source:x', 'state_units:m']) 21 | 22 | self.add_output('ydot', val=np.zeros(nn), desc='velocity component in y', units='m/s', 23 | tags=['state_rate_source:y', 'state_units:m']) 24 | 25 | self.add_output('vdot', val=np.zeros(nn), desc='acceleration magnitude', units='m/s**2', 26 | tags=['state_rate_source:v', 'state_units:m/s']) 27 | 28 | self.add_output('check', val=np.zeros(nn), desc='check solution: v/sin(theta) = constant', 29 | units='m/s') 30 | 31 | # Setup partials 32 | ar = np.arange(self.options['num_nodes'], dtype=int) 33 | 34 | self.declare_partials(of='vdot', wrt='theta', rows=ar, cols=ar) 35 | 36 | self.declare_partials(of='xdot', wrt='v', rows=ar, cols=ar) 37 | self.declare_partials(of='xdot', wrt='theta', rows=ar, cols=ar) 38 | 39 | self.declare_partials(of='ydot', wrt='v', rows=ar, cols=ar) 40 | self.declare_partials(of='ydot', wrt='theta', rows=ar, cols=ar) 41 | 42 | self.declare_partials(of='check', wrt='v', rows=ar, cols=ar) 43 | self.declare_partials(of='check', wrt='theta', rows=ar, cols=ar) 44 | 45 | def compute(self, inputs, outputs): 46 | theta = inputs['theta'] 47 | cos_theta = np.cos(theta) 48 | sin_theta = np.sin(theta) 49 | g = self.options['g'] 50 | v = inputs['v'] 51 | 52 | outputs['vdot'] = g * cos_theta 53 | outputs['xdot'] = v * sin_theta 54 | outputs['ydot'] = -v * cos_theta 55 | outputs['check'] = v / sin_theta 56 | 57 | def compute_partials(self, inputs, partials): 58 | theta = inputs['theta'] 59 | cos_theta = np.cos(theta) 60 | sin_theta = np.sin(theta) 61 | g = self.options['g'] 62 | v = inputs['v'] 63 | 64 | partials['vdot', 'theta'] = -g * sin_theta 65 | 66 | partials['xdot', 'v'] = sin_theta 67 | partials['xdot', 'theta'] = v * cos_theta 68 | 69 | partials['ydot', 'v'] = -cos_theta 70 | partials['ydot', 'theta'] = v * sin_theta 71 | 72 | partials['check', 'v'] = 1 / sin_theta 73 | partials['check', 'theta'] = -v * cos_theta / sin_theta**2 74 | --------------------------------------------------------------------------------