Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deprecated SolveIVP transcription. simulate method now uses the ExplicitShooting transcription without derivatives. #898

Merged
merged 20 commits into from
Feb 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
152fb37
Started reworking a new simulate capability that uses ExplicitShootin…
robfalck Jan 30, 2023
d370fbd
added deprecation notice for SolveIVP
robfalck Jan 30, 2023
c84b8c6
Cleaned up SimulationPhase. The methods inherited from Phase which in…
robfalck Jan 31, 2023
69bdaa4
undo matplotlib change in this PR.
robfalck Jan 31, 2023
24c7f2d
Fall back to the old way of initializing phase values until OpenMDAO …
robfalck Jan 31, 2023
0873b18
typo
robfalck Jan 31, 2023
530d3bf
Fixed some logic in how transcription_order is handled in GridData.
robfalck Jan 31, 2023
5f79af6
fixed a units issue in VandermondeControlInterpComp. Fixed poor docst…
robfalck Feb 1, 2023
8f0857b
All rate sources in ode_evaluation_group are expected to be outputs o…
robfalck Feb 1, 2023
4a77f7e
tolerance adjustment and some slight changes to the robertson problem…
robfalck Feb 1, 2023
371c1a1
Cleaning up a few final tests.
robfalck Feb 1, 2023
1ecc972
Merge branch 'master' of https://github.com/OpenMDAO/dymos into i889
robfalck Feb 1, 2023
a1fc4a8
Fix to make sure SimulationPhase respects atol and rtol. Tolerance up…
robfalck Feb 1, 2023
34ef146
replaced use_tempdirs in test_time_targets
robfalck Feb 1, 2023
d8f94bd
Not using System.set_val yet, so minimum OpenMDAO version set back to…
robfalck Feb 1, 2023
dfa86a5
pep8
robfalck Feb 1, 2023
ca41c12
feedback from bret
robfalck Feb 1, 2023
6c87537
remove unnecessary code in get_simulation_phase that was duplicating …
robfalck Feb 1, 2023
e1a7778
updated actions to use checkout@v3
robfalck Feb 4, 2023
9993e22
Merge branch 'master' of https://github.com/OpenMDAO/dymos into i889
robfalck Feb 6, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/dymos_docs_workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:
echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts

- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v3

- name: Fetch tags
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/dymos_tests_workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ jobs:

- name: Checkout code
if: env.RUN_BUILD
uses: actions/checkout@v2
uses: actions/checkout@v3

- name: Fetch tags
if: env.RUN_BUILD
Expand Down
35 changes: 21 additions & 14 deletions docs/dymos_book/examples/robertson_problem/robertson_problem.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -240,10 +240,19 @@
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"## Building and running the problem"
"## Building and running the problem\n",
"\n",
"Here we're using the ExplicitShooting transcription in Dymos.\n",
"The ExplicitShooting transcription explicit integrates the given ODE using the `solve_ivp` method of [scipy](https://scipy.org/).\n",
"\n",
"Since this is purely an integration with no controls to be determined, a single call to `run_model` will propagate the solution for us. There's no need to run a driver. Even the typical follow-up call to `traj.simulate` is unnecessary.\n",
"\n",
"Technically, we could even solve this using a single segment since segment spacing in the explicit shooting transcription determines the density of the control nodes, and there are no controls for this simulation.\n",
"Providing more segments in this case (or a higher segment order) increases the number of nodes at which the outputs are provided."
]
},
{
Expand All @@ -265,11 +274,11 @@
" # Create a trajectory and add a phase to it\n",
" #\n",
" traj = p.model.add_subsystem('traj', dm.Trajectory())\n",
" \n",
" tx = dm.ExplicitShooting(num_segments=10, method='LSODA')\n",
"\n",
" phase = traj.add_phase('phase0',\n",
" dm.Phase(ode_class=RobertsonODE,\n",
" transcription=dm.GaussLobatto(num_segments=50)\n",
" ))\n",
" dm.Phase(ode_class=RobertsonODE, transcription=tx))\n",
"\n",
" #\n",
" # Set the variables\n",
Expand Down Expand Up @@ -311,9 +320,7 @@
"# just set up the problem, test it elsewhere\n",
"p = robertson_problem(t_final=40)\n",
"\n",
"p.run_model()\n",
"\n",
"p_sim = p.model.traj.simulate(method='LSODA')"
"p.run_model()"
]
},
{
Expand All @@ -329,7 +336,7 @@
"source": [
"from openmdao.utils.assert_utils import assert_near_equal\n",
"\n",
"assert_near_equal(p_sim.get_val('traj.phase0.timeseries.states:x0')[-1], 0.71583161, tolerance=1E-5)"
"assert_near_equal(p.get_val('traj.phase0.timeseries.states:x0')[-1], 0.71583161, tolerance=1E-4)"
]
},
{
Expand All @@ -345,7 +352,7 @@
"source": [
"from openmdao.utils.assert_utils import assert_near_equal\n",
"\n",
"assert_near_equal(p_sim.get_val('traj.phase0.timeseries.states:y0')[-1], 9.18571144e-06, tolerance=1E-1)"
"assert_near_equal(p.get_val('traj.phase0.timeseries.states:y0')[-1], 9.18571144e-06, tolerance=1E-4)"
]
},
{
Expand All @@ -361,7 +368,7 @@
"source": [
"from openmdao.utils.assert_utils import assert_near_equal\n",
"\n",
"assert_near_equal(p_sim.get_val('traj.phase0.timeseries.states:z0')[-1], 0.2841592, tolerance=1E-5)"
"assert_near_equal(p.get_val('traj.phase0.timeseries.states:z0')[-1], 0.2841592, tolerance=1E-4)"
]
},
{
Expand All @@ -372,12 +379,12 @@
"source": [
"import matplotlib.pyplot as plt\n",
"\n",
"t_sim = p_sim.get_val('traj.phase0.timeseries.time')\n",
"t = p.get_val('traj.phase0.timeseries.time')\n",
"\n",
"states = ['x0', 'y0', 'z0']\n",
"fig, axes = plt.subplots(len(states), 1)\n",
"for i, state in enumerate(states):\n",
" axes[i].plot(t_sim, p_sim.get_val(f'traj.phase0.timeseries.states:{state}'), '-')\n",
" axes[i].plot(t, p.get_val(f'traj.phase0.timeseries.states:{state}'), 'o')\n",
" axes[i].set_ylabel(state[0])\n",
"axes[-1].set_xlabel('time (s)')\n",
"plt.tight_layout()\n",
Expand All @@ -396,7 +403,7 @@
}
},
"kernelspec": {
"display_name": "Python 3",
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
Expand All @@ -410,7 +417,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.3"
"version": "3.11.0"
}
},
"nbformat": 4,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def test_cruise_results_gl(self):
p.setup()

p['phase0.t_initial'] = 0.0
p['phase0.t_duration'] = 1.515132 * 3600.0
p['phase0.t_duration'] = 3600.0
Copy link
Contributor Author

Choose a reason for hiding this comment

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

initial value was out of bounds of the design variable.

p['phase0.states:range'] = phase.interp('range', (0, 1296.4))
p['phase0.states:mass_fuel'] = phase.interp('mass_fuel', (12236.594555, 1))
p['phase0.states:alt'] = 5.0
Expand Down Expand Up @@ -181,7 +181,7 @@ def test_cruise_results_radau(self):
p.setup()

p['phase0.t_initial'] = 0.0
p['phase0.t_duration'] = 1.515132 * 3600.0
p['phase0.t_duration'] = 1.0 * 3600.0
Copy link
Contributor Author

Choose a reason for hiding this comment

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

initial value was out of bounds of the design variable

p['phase0.states:range'] = phase.interp('range', (0, 1296.4))
p['phase0.states:mass_fuel'] = phase.interp('mass_fuel', (12236.594555, 0))
p['phase0.states:alt'] = 5.0
Expand Down
14 changes: 12 additions & 2 deletions dymos/examples/brachistochrone/test/ex_brachistochrone.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import matplotlib
import matplotlib.pyplot as plt

import openmdao.api as om
from openmdao.utils.testing_utils import require_pyoptsparse
Expand Down Expand Up @@ -30,6 +29,17 @@ def brachistochrone_min_time(transcription='gauss-lobatto', num_segments=8, tran
t = dm.Radau(num_segments=num_segments,
order=transcription_order,
compressed=compressed)
elif transcription == 'shooting-gauss-lobatto':
grid = dm.GaussLobattoGrid(num_segments=num_segments,
nodes_per_seg=transcription_order,
compressed=compressed)
t = dm.ExplicitShooting(grid=grid)
elif transcription == 'shooting-radau':
grid = dm.RadauGrid(num_segments=num_segments,
nodes_per_seg=transcription_order + 1,
compressed=compressed)
t = dm.ExplicitShooting(grid=grid)

traj = dm.Trajectory()
phase = dm.Phase(ode_class=BrachistochroneODE, transcription=t)
p.model.add_subsystem('traj0', traj)
Expand Down Expand Up @@ -81,5 +91,5 @@ def brachistochrone_min_time(transcription='gauss-lobatto', num_segments=8, tran

if __name__ == '__main__':
p = brachistochrone_min_time(transcription='gauss-lobatto', num_segments=5, run_driver=True,
transcription_order=5, compressed=False, optimizer='SNOPT',
transcription_order=5, compressed=False, optimizer='IPOPT',
solve_segments=False, force_alloc_complex=True)
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,8 @@ def test_make_sim_reports(self):
report_subdirs = sorted([e for e in pathlib.Path(get_reports_dir()).iterdir() if e.is_dir()])

# Test that a report subdir was made
# # There is the nominal problem, the simulation problem, and a subproblem for each segment in the simulation.
self.assertEqual(len(report_subdirs), 12)
# There is the nominal problem, the simulation problem, and a subproblem for the simulation.
self.assertEqual(len(report_subdirs), 3)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed due to having fewer subproblems involved.


for subdir in report_subdirs:
path = pathlib.Path(subdir).joinpath(self.n2_filename)
Expand Down
36 changes: 28 additions & 8 deletions dymos/examples/brachistochrone/test/test_ex_brachistochrone.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,32 +55,52 @@ def test_ex_brachistochrone_radau_compressed(self):
compressed=True)
self.run_asserts(p)
self.tearDown()
if os.path.exists('ex_brach_radau_compressed.db'):
os.remove('ex_brach_radau_compressed.db')

def test_ex_brachistochrone_radau_uncompressed(self):
ex_brachistochrone.SHOW_PLOTS = True
p = ex_brachistochrone.brachistochrone_min_time(transcription='radau-ps',
compressed=False)
self.run_asserts(p)
self.tearDown()
if os.path.exists('ex_brach_radau_uncompressed.db'):
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Handled by @use_tempdir

os.remove('ex_brach_radau_uncompressed.db')

def test_ex_brachistochrone_gl_compressed(self):
ex_brachistochrone.SHOW_PLOTS = True
p = ex_brachistochrone.brachistochrone_min_time(transcription='gauss-lobatto',
compressed=True)
self.run_asserts(p)
self.tearDown()
if os.path.exists('ex_brach_gl_compressed.db'):
os.remove('ex_brach_gl_compressed.db')

def test_ex_brachistochrone_gl_uncompressed(self):
ex_brachistochrone.SHOW_PLOTS = True
p = ex_brachistochrone.brachistochrone_min_time(transcription='gauss-lobatto',
compressed=False)
self.run_asserts(p)
self.tearDown()
if os.path.exists('ex_brach_gl_uncompressed.db'):
os.remove('ex_brach_gl_uncompressed.db')

def test_ex_brachistochrone_shooting_gl_compressed(self):
ex_brachistochrone.SHOW_PLOTS = True
p = ex_brachistochrone.brachistochrone_min_time(transcription='shooting-gauss-lobatto',
compressed=True)
self.run_asserts(p)
self.tearDown()

def test_ex_brachistochrone_shooting_gl_uncompressed(self):
ex_brachistochrone.SHOW_PLOTS = True
p = ex_brachistochrone.brachistochrone_min_time(transcription='shooting-gauss-lobatto',
compressed=False)
self.run_asserts(p)
self.tearDown()

def test_ex_brachistochrone_shooting_radau_compressed(self):
ex_brachistochrone.SHOW_PLOTS = True
p = ex_brachistochrone.brachistochrone_min_time(transcription='shooting-radau',
compressed=True)
self.run_asserts(p)
self.tearDown()

def test_ex_brachistochrone_shooting_radau_uncompressed(self):
ex_brachistochrone.SHOW_PLOTS = True
p = ex_brachistochrone.brachistochrone_min_time(transcription='shooting-radau',
compressed=False)
self.run_asserts(p)
self.tearDown()
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ def _test_integrate_control_rate2(self, transcription):
# Define a Dymos Phase object with GaussLobatto Transcription
#
phase = dm.Phase(ode_class=BrachistochroneODE,
transcription=transcription(num_segments=10, order=3))
transcription=transcription(num_segments=15, order=3))

traj.add_phase(name='phase0', phase=phase)

Expand Down Expand Up @@ -304,7 +304,7 @@ def _test_integrate_control_rate2(self, transcription):
p.set_val('traj.phase0.controls:theta', phase.interp('theta', [0, 100]), units='deg')

# Run the driver to solve the problem
dm.run_problem(p, simulate=True, make_plots=False)
dm.run_problem(p, simulate=True, make_plots=False, simulate_kwargs={'atol': 1.0E-9, 'rtol': 1.0E-9})

sol = om.CaseReader('dymos_solution.db').get_case('final')
sim = om.CaseReader('dymos_simulation.db').get_case('final')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,10 @@ def test_simulate_raises_analysis_error(self):

with self.assertRaises(om.AnalysisError) as e:
dm.run_problem(p, run_driver=False, simulate=True)
expected_pattern = r"'traj.phases.phase0.segments.segment_[0-9]' <class SegmentSimulationComp>: Error calling " \
r"compute\(\), solve_ivp failed: Required step size is less than spacing " \
r"between numbers. Dynamics changing too dramatically"
self.assertRegex(str(e.exception), expected_pattern)
expected_pattern = r"'traj.phases.phase0.integrator' <class ODEIntegrationComp>: Error calling " \
r"compute(), solve_ivp failed: Required step size is less than spacing " \
r"between numbers."
self.assertEqual(str(e.exception), expected_pattern)


if __name__ == '__main__': # pragma: no cover
Expand Down
17 changes: 7 additions & 10 deletions dymos/examples/robertson_problem/doc/test_doc_robertson_problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from dymos.examples.robertson_problem.doc.robertson_ode import RobertsonODE


matplotlib.use('Agg')
# matplotlib.use('Agg')
plt.style.use('ggplot')


Expand All @@ -31,9 +31,8 @@ def robertson_problem(self, t_final=1.0):

phase = traj.add_phase('phase0',
dm.Phase(ode_class=RobertsonODE,
transcription=dm.GaussLobatto(num_segments=50)
transcription=dm.ExplicitShooting(num_segments=10, method='LSODA')
))

#
# Set the variables
#
Expand Down Expand Up @@ -93,18 +92,16 @@ def test_robertson_problem_for_docs(self):

p.run_model()

p_sim = p.model.traj.simulate(method='LSODA')

assert_near_equal(p_sim.get_val('traj.phase0.timeseries.states:x0')[-1], 0.71583161, tolerance=1E-5)
assert_near_equal(p_sim.get_val('traj.phase0.timeseries.states:y0')[-1], 9.18571144e-06, tolerance=1E-1)
assert_near_equal(p_sim.get_val('traj.phase0.timeseries.states:z0')[-1], 0.2841592, tolerance=1E-5)
assert_near_equal(p.get_val('traj.phase0.timeseries.states:x0')[-1], 0.71583161, tolerance=1E-4)
assert_near_equal(p.get_val('traj.phase0.timeseries.states:y0')[-1], 9.18571144e-06, tolerance=1E-4)
assert_near_equal(p.get_val('traj.phase0.timeseries.states:z0')[-1], 0.2841592, tolerance=1E-4)

t_sim = p_sim.get_val('traj.phase0.timeseries.time')
t_sim = p.get_val('traj.phase0.timeseries.time')

states = ['x0', 'y0', 'z0']
fig, axes = plt.subplots(len(states), 1)
for i, state in enumerate(states):
axes[i].plot(t_sim, p_sim.get_val(f'traj.phase0.timeseries.states:{state}'), '-')
axes[i].plot(t_sim, p.get_val(f'traj.phase0.timeseries.states:{state}'), 'o')
axes[i].set_ylabel(state[0])
axes[-1].set_xlabel('time (s)')
plt.tight_layout()
Expand Down
1 change: 1 addition & 0 deletions dymos/phase/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from .analytic_phase import AnalyticPhase
from .phase import Phase
from .simulation_phase import SimulationPhase
2 changes: 1 addition & 1 deletion dymos/phase/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -611,7 +611,7 @@ class SimulateOptionsDictionary(om.OptionsDictionary):
def __init__(self, read_only=False):
super(SimulateOptionsDictionary, self).__init__(read_only)

self.declare('method', values=('RK23', 'RK45', 'DOP853', 'BDF', 'Radau', 'LSODA'), default='RK45',
self.declare('method', values=('RK23', 'RK45', 'DOP853', 'BDF', 'Radau', 'LSODA'), default='DOP853',
desc='The method used by simulate to propagate the ODE.')

self.declare(name='atol', types=(float, np.array), default=1.0E-6,
Expand Down
Loading