Skip to content

Commit

Permalink
Unify controls and polynomial controls (#1078)
Browse files Browse the repository at this point in the history
* WIP: combining controls

* Removing more of the polynomial controls

* WIP: removing more polynomial controls

* WIP: renaming poly controls throughout

* WIP; controls combining

* WIP; debugging

* Better progress; resolved size issue for control/poly control

* WIP; need to get all tests and benchmarks working

* WIP

* More tests passing for controls rework

* WIP; fixing tests

* All tests passing

* Updating docs

* Addressing PR feedback for controls rework

* Fixing regression

* Lint fixes

* Fixing jupyterbook docs

---------

Co-authored-by: John Jasa <johnjasa11@gmailcom>
  • Loading branch information
johnjasa and John Jasa authored Jun 26, 2024
1 parent 6ee8eec commit ddb8627
Show file tree
Hide file tree
Showing 64 changed files with 1,021 additions and 3,300 deletions.
6 changes: 3 additions & 3 deletions benchmark/benchmark_balanced_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ def _run_balanced_field_length_problem(tx=dm.GaussLobatto, timeseries=True, sim=
rotate.set_time_options(fix_initial=False, duration_bounds=(1.0, 5), duration_ref=1.0)
rotate.add_state('r', fix_initial=False, lower=0, ref=1000.0, defect_ref=1000.0)
rotate.add_state('v', fix_initial=False, lower=0, ref=100.0, defect_ref=100.0)
rotate.add_polynomial_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10,
ref=10, val=[0, 10])
rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10,
ref=10, val=[0, 10], control_type='polynomial')

if timeseries:
rotate.add_timeseries_output('*')
Expand Down Expand Up @@ -229,7 +229,7 @@ def _run_balanced_field_length_problem(tx=dm.GaussLobatto, timeseries=True, sim=
p.set_val('traj.rotate.t_duration', 5)
p.set_val('traj.rotate.states:r', rotate.interp('r', [1750, 1800.0]))
p.set_val('traj.rotate.states:v', rotate.interp('v', [80, 85.0]))
p.set_val('traj.rotate.polynomial_controls:alpha', 0.0, units='deg')
p.set_val('traj.rotate.controls:alpha', 0.0, units='deg')

p.set_val('traj.climb.t_initial', 75)
p.set_val('traj.climb.t_duration', 15)
Expand Down
5 changes: 3 additions & 2 deletions docs/dymos_book/examples/balanced_field/balanced_field.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,8 @@
"rotate.set_time_options(fix_initial=False, duration_bounds=(1.0, 5), duration_ref=1.0)\n",
"rotate.add_state('r', fix_initial=False, lower=0, ref=1000.0, defect_ref=1000.0)\n",
"rotate.add_state('v', fix_initial=False, lower=0, ref=100.0, defect_ref=100.0)\n",
"rotate.add_polynomial_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10])\n",
"rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10,\n",
" ref=10, val=[0, 10], control_type='polynomial')\n",
"rotate.add_timeseries_output('*')\n",
"\n",
"# Fifth Phase: Climb to target speed and altitude at end of runway.\n",
Expand Down Expand Up @@ -687,7 +688,7 @@
"rotate.set_time_val(initial=35.0, duration=5.0)\n",
"rotate.set_state_val('r', [1750, 1800.0])\n",
"rotate.set_state_val('v', [80, 85.0])\n",
"rotate.set_polynomial_control_val('alpha', 0.0, units='deg')\n",
"rotate.set_control_val('alpha', 0.0, units='deg')\n",
"\n",
"climb.set_time_val(initial=30.0, duration=20.0)\n",
"climb.set_state_val('r', [5000, 5500.0], units='ft')\n",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,19 +248,19 @@
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": [
"active-ipynb",
"remove-input",
"remove-output"
]
},
"outputs": [],
"source": [
"%matplotlib inline"
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": [
"active-ipynb",
"remove-input",
"remove-output"
]
},
"outputs": [],
"source": [
"%matplotlib inline"
]
},
{
"cell_type": "code",
Expand Down Expand Up @@ -328,7 +328,9 @@
"rotate.set_time_options(fix_initial=False, duration_bounds=(1.0, 5), duration_ref=1.0)\n",
"rotate.add_state('r', fix_initial=False, lower=0, ref=1000.0, defect_ref=1000.0)\n",
"rotate.add_state('v', fix_initial=False, lower=0, ref=100.0, defect_ref=100.0)\n",
"rotate.add_polynomial_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10])\n",
"rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10,\n",
"ref=10,\n",
" val=[0, 10], control_type='polynomial')\n",
"rotate.add_timeseries_output('*')\n",
"\n",
"# Fifth Phase: Climb to target speed and altitude at end of runway.\n",
Expand Down Expand Up @@ -437,7 +439,7 @@
"rotate.set_time_val(initial=35.0, duration=5.0)\n",
"rotate.set_state_val('r', [1750, 1800.0])\n",
"rotate.set_state_val('v', [80, 85.0])\n",
"rotate.set_polynomial_control_val('alpha', 0.0, units='deg')\n",
"rotate.set_control_val('alpha', 0.0, units='deg')\n",
"\n",
"climb.set_time_val(initial=30.0, duration=20.0)\n",
"climb.set_state_val('r', [5000, 5500.0], units='ft')\n",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,19 +80,19 @@
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": [
"active-ipynb",
"remove-input",
"remove-output"
]
},
"outputs": [],
"source": [
"%matplotlib inline"
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": [
"active-ipynb",
"remove-input",
"remove-output"
]
},
"outputs": [],
"source": [
"%matplotlib inline"
]
},
{
"cell_type": "code",
Expand Down Expand Up @@ -286,8 +286,8 @@
"#\n",
"# The tangent of theta is modeled as a linear polynomial over the duration of the phase.\n",
"#\n",
"phase.add_polynomial_control('tan_theta', order=1, units=None, opt=True,\n",
" targets=['guidance.tan_theta'])\n",
"phase.add_control('tan_theta', order=1, units=None, opt=True,\n",
" targets=['guidance.tan_theta'], control_type='polynomial')\n",
"\n",
"#\n",
"# Parameters values for thrust and specific impulse are design parameters. They are\n",
Expand Down Expand Up @@ -339,7 +339,7 @@
"phase.set_state_val('vx', [0, 1627.0])\n",
"phase.set_state_val('vy', [1.0E-6, 0.0])\n",
"phase.set_state_val('m', 50000)\n",
"phase.set_polynomial_control_val('tan_theta', [[0.5 * np.pi], [0.0]])\n",
"phase.set_control_val('tan_theta', [[0.5 * np.pi], [0.0]])\n",
"\n",
"#\n",
"# Solve the problem.\n",
Expand Down
28 changes: 2 additions & 26 deletions docs/dymos_book/features/phases/variables.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -245,37 +245,13 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"## Polynomial Controls\n",
"### Polynomial Controls\n",
"\n",
"Sometimes it can be easier to optimize a problem by reducing the freedom in the controls.\n",
"For instance, one might want the control to be linearly or quadratically varying throughout a phase, rather than having a different value specified at each node.\n",
"In Dymos, this role is filled by the PolynomialControl.\n",
"Polynomial controls are specified at some limited number of points throughout a _phase_, and then have their values interpolated to each node in each segment.\n",
"\n",
"### Options for Polynomial Control Variables"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"om.show_options_table(\"dymos.phase.options.PolynomialControlOptionsDictionary\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Polynomial values are connected to the ODE using the `targets` argument.\n",
"The values of this argument obey the same rules as those for states.\n",
"\n",
"The polynomial control first and second derivatives w.r.t. time may also be connected to the ODE.\n",
"First derivatives of controls in Dymos assume the name `<control_name>_rate`.\n",
"Second derivatives of controls in Dymos assume the name `<control_name>_rate2`.\n",
"Control rates are automatically connected if a top-level input of the ODE is named `<control_name>_rate` or `<control_name>_rate2`.\n",
"These variables are available in the timeseries output as `timeseries.polynomial_control_rates.<control_name>_rate` and `timeseries.polynomial_control_rates.<control_name>_rate2`, respectively."
"Controls added with `control_type='polynomial'` are added as polynomial controls."
]
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ def test_balanced_field_length_for_docs(self):
rotate.set_time_options(fix_initial=False, duration_bounds=(1.0, 5), duration_ref=1.0)
rotate.add_state('r', fix_initial=False, lower=0, ref=1000.0, defect_ref=1000.0)
rotate.add_state('v', fix_initial=False, lower=0, ref=100.0, defect_ref=100.0)
rotate.add_polynomial_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10])
rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10],
control_type='polynomial')
rotate.add_timeseries_output('*')

# Fifth Phase: Climb to target speed and altitude at end of runway.
Expand Down Expand Up @@ -225,7 +226,7 @@ def test_balanced_field_length_for_docs(self):
rotate.set_time_val(initial=70.0, duration=5.0)
rotate.set_state_val('r', [1750, 1800.0])
rotate.set_state_val('v', [80, 85.0])
rotate.set_polynomial_control_val('alpha', 0.0, units='deg')
rotate.set_control_val('alpha', 0.0, units='deg')

climb.set_time_val(initial=75.0, duration=15.0)
climb.set_state_val('r', [5000, 5500.0], units='ft')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,8 @@ def _run_problem(self, tx):
rotate.set_time_options(fix_initial=False, duration_bounds=(1.0, 5), duration_ref=1.0)
rotate.add_state('r', fix_initial=False, lower=0, ref=1000.0, defect_ref=1000.0)
rotate.add_state('v', fix_initial=False, lower=0, ref=100.0, defect_ref=100.0)
rotate.add_polynomial_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10])
rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10,
val=[0, 10], control_type='polynomial')
rotate.add_timeseries_output('*')

# Fifth Phase: Climb to target speed and altitude at end of runway.
Expand Down Expand Up @@ -306,7 +307,7 @@ def _run_problem(self, tx):
rotate.set_time_val(initial=35.0, duration=5.0)
rotate.set_state_val('r', [1750, 1800.0])
rotate.set_state_val('v', [80, 85.0])
rotate.set_polynomial_control_val('alpha', 0.0, units='deg')
rotate.set_control_val('alpha', 0.0, units='deg')

climb.set_time_val(initial=30.0, duration=20.0)
climb.set_state_val('r', [5000, 5500.0], units='ft')
Expand Down
10 changes: 6 additions & 4 deletions dymos/examples/balanced_field/test/test_balanced_field_length.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ def _make_problem(self):
rotate.set_time_options(fix_initial=False, duration_bounds=(1.0, 5), duration_ref=1.0)
rotate.add_state('r', fix_initial=False, lower=0, ref=1000.0, defect_ref=1000.0)
rotate.add_state('v', fix_initial=False, lower=0.0001, ref=100.0, defect_ref=100.0)
rotate.add_polynomial_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10])
rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10,
val=[0, 10], control_type='polynomial')
rotate.add_timeseries_output('*')

# Fifth Phase: Climb to target speed and altitude at end of runway.
Expand Down Expand Up @@ -204,7 +205,7 @@ def _make_problem(self):
rotate.set_time_val(initial=35.0, duration=5.0)
rotate.set_state_val('r', [1750, 1800.0])
rotate.set_state_val('v', [80, 85.0])
rotate.set_polynomial_control_val('alpha', 0.0, units='deg')
rotate.set_control_val('alpha', 0.0, units='deg')

climb.set_time_val(initial=30.0, duration=20.0)
climb.set_state_val('r', [5000, 5500.0], units='ft')
Expand Down Expand Up @@ -321,7 +322,8 @@ def test_default_vals_stick(self):
val=rotate.interp(ys=[1750, 1800.0], nodes='state_input'))
rotate.add_state('v', fix_initial=False, lower=0.0001, ref=100.0, defect_ref=100.0,
val=rotate.interp(ys=[80, 85.0], nodes='state_input'))
rotate.add_polynomial_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10])
rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10,
val=[0, 10], control_type='polynomial')
rotate.add_timeseries_output('*')

# Fifth Phase: Climb to target speed and altitude at end of runway.
Expand Down Expand Up @@ -456,7 +458,7 @@ def test_default_vals_stick(self):

assert_near_equal(p.get_val('traj.rotate.t_initial'), 70)
assert_near_equal(p.get_val('traj.rotate.t_duration'), 5)
assert_near_equal(p.get_val('traj.rotate.polynomial_controls:alpha'), np.array([[0, 10]]).T)
assert_near_equal(p.get_val('traj.rotate.controls:alpha'), np.array([[0, 10]]).T)
assert_near_equal(p.get_val('traj.climb.controls:alpha'),
p.model.traj.phases.climb.interp('', [0.01, 0.01], nodes='control_input'))
assert_near_equal(p.get_val('traj.climb.states:gam'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -530,8 +530,8 @@ def test_brachistochrone_polynomial_control_rate_targets_gauss_lobatto(self):
units='m/s',
fix_initial=True, fix_final=False, solve_segments=False)

phase.add_polynomial_control('theta', order=3, units='deg*s', lower=0.01, upper=179.9,
fix_initial=True)
phase.add_control('theta', order=3, units='deg*s', lower=0.01, upper=179.9,
fix_initial=True, control_type='polynomial')

phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665)

Expand All @@ -547,7 +547,7 @@ def test_brachistochrone_polynomial_control_rate_targets_gauss_lobatto(self):
phase.set_state_val('x', [0, 10])
phase.set_state_val('y', [10, 5])
phase.set_state_val('v', [0, 9.9])
phase.set_polynomial_control_val('theta', [0, 100])
phase.set_control_val('theta', [0, 100])

# Solve for the optimal trajectory
p.run_driver()
Expand Down Expand Up @@ -624,8 +624,8 @@ def test_brachistochrone_polynomial_control_rate_targets_radau(self):
units='m/s',
fix_initial=True, fix_final=False, solve_segments=False)

phase.add_polynomial_control('theta', order=3, units='deg*s', lower=0.01, upper=179.9,
fix_initial=True)
phase.add_control('theta', order=3, units='deg*s', lower=0.01, upper=179.9,
fix_initial=True, control_type='polynomial')

phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665)

Expand All @@ -641,7 +641,7 @@ def test_brachistochrone_polynomial_control_rate_targets_radau(self):
phase.set_state_val('x', [0, 10])
phase.set_state_val('y', [10, 5])
phase.set_state_val('v', [0, 9.9])
phase.set_polynomial_control_val('theta', [0, 100])
phase.set_control_val('theta', [0, 100])

# Solve for the optimal trajectory
p.run_driver()
Expand Down Expand Up @@ -721,8 +721,8 @@ def test_brachistochrone_polynomial_control_rate_targets_gauss_lobatto(self):
units='m/s',
fix_initial=True, fix_final=False, solve_segments=False)

phase.add_polynomial_control('theta', order=3, units='deg*s', lower=0.01, upper=179.9,
rate_targets=['theta_rate'], fix_initial=True)
phase.add_control('theta', order=3, units='deg*s', lower=0.01, upper=179.9,
rate_targets=['theta_rate'], fix_initial=True, control_type='polynomial')

phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665)

Expand All @@ -738,7 +738,7 @@ def test_brachistochrone_polynomial_control_rate_targets_gauss_lobatto(self):
phase.set_state_val('x', [0, 10])
phase.set_state_val('y', [10, 5])
phase.set_state_val('v', [0, 9.9])
phase.set_polynomial_control_val('theta', [0, 100])
phase.set_control_val('theta', [0, 100])

# Solve for the optimal trajectory
p.run_driver()
Expand Down Expand Up @@ -815,8 +815,8 @@ def test_brachistochrone_polynomial_control_rate_targets_radau(self):
units='m/s',
fix_initial=True, fix_final=False, solve_segments=False)

phase.add_polynomial_control('theta', order=3, units='deg*s', lower=0.01, upper=179.9,
rate_targets=['theta_rate'], fix_initial=True)
phase.add_control('theta', order=3, units='deg*s', lower=0.01, upper=179.9,
rate_targets=['theta_rate'], fix_initial=True, control_type='polynomial')

phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665)

Expand All @@ -832,7 +832,7 @@ def test_brachistochrone_polynomial_control_rate_targets_radau(self):
phase.set_state_val('x', [0, 10])
phase.set_state_val('y', [10, 5])
phase.set_state_val('v', [0, 9.9])
phase.set_polynomial_control_val('theta', [0, 100])
phase.set_control_val('theta', [0, 100])

# Solve for the optimal trajectory
p.run_driver()
Expand Down Expand Up @@ -912,8 +912,8 @@ def test_brachistochrone_polynomial_control_rate_targets_gauss_lobatto(self):
units='m/s',
fix_initial=True, fix_final=False, solve_segments=False)

phase.add_polynomial_control('theta', order=5, units='deg*s**2', lower=0.01, upper=179.9,
rate_targets=None, rate2_targets=['theta_rate'], fix_initial=True)
phase.add_control('theta', order=5, units='deg*s**2', lower=0.01, upper=179.9,
rate_targets=None, rate2_targets=['theta_rate'], fix_initial=True, control_type='polynomial')

phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665)

Expand All @@ -929,8 +929,8 @@ def test_brachistochrone_polynomial_control_rate_targets_gauss_lobatto(self):
phase.set_state_val('x', [0, 10])
phase.set_state_val('y', [10, 5])
phase.set_state_val('v', [0, 9.9])
phase.set_polynomial_control_val('theta', [0, 10, 40, 60, 80, 100],
time_vals=[0, 0.4, 0.8, 1.2, 1.6, 2.0])
phase.set_control_val('theta', [0, 10, 40, 60, 80, 100],
time_vals=[0, 0.4, 0.8, 1.2, 1.6, 2.0])

# Solve for the optimal trajectory
dm.run_problem(p, simulate=True)
Expand Down
Loading

0 comments on commit ddb8627

Please sign in to comment.