From 959b7459f65730f161871526ae5f00f5cc238325 Mon Sep 17 00:00:00 2001 From: Bret Naylor Date: Thu, 21 Jul 2022 16:25:05 -0400 Subject: [PATCH 01/17] working --- dymos/run_problem.py | 4 +- .../test/test_timeseries_plots.py | 184 ++++++++++-------- dymos/visualization/timeseries_plots.py | 47 +++-- 3 files changed, 137 insertions(+), 98 deletions(-) diff --git a/dymos/run_problem.py b/dymos/run_problem.py index 38b3ba284..8f7e7bd70 100755 --- a/dymos/run_problem.py +++ b/dymos/run_problem.py @@ -95,9 +95,9 @@ def run_problem(problem, refine_method='hp', refine_iteration_limit=0, run_drive if make_plots: if simulate: timeseries_plots(solution_record_file, simulation_record_file=simulation_record_file, - plot_dir=plot_dir) + plot_dir=plot_dir, problem=problem) else: timeseries_plots(solution_record_file, simulation_record_file=None, - plot_dir=plot_dir) + plot_dir=plot_dir, problem=problem) return failed diff --git a/dymos/visualization/test/test_timeseries_plots.py b/dymos/visualization/test/test_timeseries_plots.py index a81e1c911..c2de89b19 100644 --- a/dymos/visualization/test/test_timeseries_plots.py +++ b/dymos/visualization/test/test_timeseries_plots.py @@ -1,5 +1,6 @@ import os import unittest +import pathlib import numpy as np @@ -14,110 +15,119 @@ from dymos.visualization.timeseries_plots import timeseries_plots -@use_tempdirs -@require_pyoptsparse(optimizer='SLSQP') -class TestTimeSeriesPlotsBasics(unittest.TestCase): +def _setup_problem(pname): + optimizer = 'SLSQP' + num_segments = 8 + transcription_order = 3 + compressed = True - def setUp(self): - optimizer = 'SLSQP' - num_segments = 8 - transcription_order = 3 - compressed = True + p = om.Problem(model=om.Group(), name=pname) - p = om.Problem(model=om.Group()) + p.driver = om.pyOptSparseDriver() + p.driver.options['optimizer'] = optimizer + p.driver.declare_coloring() - p.driver = om.pyOptSparseDriver() - p.driver.options['optimizer'] = optimizer - p.driver.declare_coloring() + t = dm.GaussLobatto(num_segments=num_segments, + order=transcription_order, + compressed=compressed) + traj = dm.Trajectory() + phase = dm.Phase(ode_class=BrachistochroneODE, transcription=t) + traj.add_phase('phase0', phase) - t = dm.GaussLobatto(num_segments=num_segments, - order=transcription_order, - compressed=compressed) - traj = dm.Trajectory() - phase = dm.Phase(ode_class=BrachistochroneODE, transcription=t) - traj.add_phase('phase0', phase) + p.model.add_subsystem('traj0', traj) - p.model.add_subsystem('traj0', traj) + phase.set_time_options(fix_initial=True, duration_bounds=(.5, 10)) - phase.set_time_options(fix_initial=True, duration_bounds=(.5, 10)) + phase.add_state('x', fix_initial=True, fix_final=False) + phase.add_state('y', fix_initial=True, fix_final=False) + phase.add_state('v', fix_initial=True, fix_final=False) - phase.add_state('x', fix_initial=True, fix_final=False) - phase.add_state('y', fix_initial=True, fix_final=False) - phase.add_state('v', fix_initial=True, fix_final=False) + phase.add_control('theta', continuity=True, rate_continuity=True, + units='deg', lower=0.01, upper=179.9) - phase.add_control('theta', continuity=True, rate_continuity=True, - units='deg', lower=0.01, upper=179.9) + phase.add_parameter('g', units='m/s**2', val=9.80665) - phase.add_parameter('g', units='m/s**2', val=9.80665) + phase.add_timeseries('timeseries2', + transcription=dm.Radau(num_segments=num_segments * 5, + order=transcription_order, + compressed=compressed), + subset='control_input') - phase.add_timeseries('timeseries2', - transcription=dm.Radau(num_segments=num_segments * 5, - order=transcription_order, - compressed=compressed), - subset='control_input') + phase.add_boundary_constraint('x', loc='final', equals=10) + phase.add_boundary_constraint('y', loc='final', equals=5) + # Minimize time at the end of the phase + phase.add_objective('time_phase', loc='final', scaler=10) - phase.add_boundary_constraint('x', loc='final', equals=10) - phase.add_boundary_constraint('y', loc='final', equals=5) - # Minimize time at the end of the phase - phase.add_objective('time_phase', loc='final', scaler=10) + p.model.linear_solver = om.DirectSolver() + p.setup(check=['unconnected_inputs']) - p.model.linear_solver = om.DirectSolver() - p.setup(check=['unconnected_inputs']) + p['traj0.phase0.t_initial'] = 0.0 + p['traj0.phase0.t_duration'] = 2.0 - p['traj0.phase0.t_initial'] = 0.0 - p['traj0.phase0.t_duration'] = 2.0 + p['traj0.phase0.states:x'] = phase.interp('x', [0, 10]) + p['traj0.phase0.states:y'] = phase.interp('y', [10, 5]) + p['traj0.phase0.states:v'] = phase.interp('v', [0, 9.9]) + p['traj0.phase0.controls:theta'] = phase.interp('theta', [5, 100]) + p['traj0.phase0.parameters:g'] = 9.80665 - p['traj0.phase0.states:x'] = phase.interp('x', [0, 10]) - p['traj0.phase0.states:y'] = phase.interp('y', [10, 5]) - p['traj0.phase0.states:v'] = phase.interp('v', [0, 9.9]) - p['traj0.phase0.controls:theta'] = phase.interp('theta', [5, 100]) - p['traj0.phase0.parameters:g'] = 9.80665 + p.setup() - p.setup() + return p - self.p = p + +@use_tempdirs +@require_pyoptsparse(optimizer='SLSQP') +class TestTimeSeriesPlotsBasics(unittest.TestCase): def test_brachistochrone_timeseries_plots(self): - dm.run_problem(self.p, make_plots=False) + p = _setup_problem("brachistochrone_timeseries_plots") + dm.run_problem(p, make_plots=False) - timeseries_plots('dymos_solution.db') - self.assertTrue(os.path.exists('plots/states_x.png')) - self.assertTrue(os.path.exists('plots/states_y.png')) - self.assertTrue(os.path.exists('plots/states_v.png')) - self.assertTrue(os.path.exists('plots/controls_theta.png')) - self.assertTrue(os.path.exists('plots/control_rates_theta_rate.png')) - self.assertTrue(os.path.exists('plots/control_rates_theta_rate2.png')) + timeseries_plots('dymos_solution.db', problem=p) + plot_dir = pathlib.Path(p.get_reports_dir()).joinpath('plots') + + self.assertTrue(plot_dir.joinpath('states_x.png').exists()) + self.assertTrue(plot_dir.joinpath('states_y.png').exists()) + self.assertTrue(plot_dir.joinpath('states_v.png').exists()) + self.assertTrue(plot_dir.joinpath('controls_theta.png').exists()) + self.assertTrue(plot_dir.joinpath('control_rates_theta_rate.png').exists()) + self.assertTrue(plot_dir.joinpath('control_rates_theta_rate2.png').exists()) def test_brachistochrone_timeseries_plots_solution_only_set_solution_record_file(self): + p = _setup_problem("brachistochrone_timeseries_plots_solution_only_set_solution_record_file") # records to the default file 'dymos_simulation.db' - dm.run_problem(self.p, make_plots=False, solution_record_file='solution_record_file.db') + dm.run_problem(p, make_plots=False, solution_record_file='solution_record_file.db') + + timeseries_plots('solution_record_file.db', problem=p) + plot_dir = pathlib.Path(p.get_reports_dir()).joinpath('plots') - timeseries_plots('solution_record_file.db') - self.assertTrue(os.path.exists('plots/states_x.png')) - self.assertTrue(os.path.exists('plots/states_y.png')) - self.assertTrue(os.path.exists('plots/states_v.png')) - self.assertTrue(os.path.exists('plots/controls_theta.png')) - self.assertTrue(os.path.exists('plots/control_rates_theta_rate.png')) - self.assertTrue(os.path.exists('plots/control_rates_theta_rate2.png')) + self.assertTrue(plot_dir.joinpath('states_x.png').exists()) + self.assertTrue(plot_dir.joinpath('states_y.png').exists()) + self.assertTrue(plot_dir.joinpath('states_v.png').exists()) + self.assertTrue(plot_dir.joinpath('controls_theta.png').exists()) + self.assertTrue(plot_dir.joinpath('control_rates_theta_rate.png').exists()) + self.assertTrue(plot_dir.joinpath('control_rates_theta_rate2.png').exists()) def test_brachistochrone_timeseries_plots_solution_and_simulation(self): - dm.run_problem(self.p, simulate=True, make_plots=False, + p = _setup_problem("brachistochrone_timeseries_plots_solution_and_simulation") + dm.run_problem(p, simulate=True, make_plots=False, simulation_record_file='simulation_record_file.db') - timeseries_plots('dymos_solution.db', simulation_record_file='simulation_record_file.db') + timeseries_plots('dymos_solution.db', simulation_record_file='simulation_record_file.db', problem=p) def test_brachistochrone_timeseries_plots_set_plot_dir(self): - dm.run_problem(self.p, make_plots=False) + p = _setup_problem("brachistochrone_timeseries_plots_set_plot_dir") + dm.run_problem(p, make_plots=False) - plot_dir = "test_plot_dir" - timeseries_plots('dymos_solution.db', plot_dir=plot_dir) + plot_dir = pathlib.Path(p.get_reports_dir()).joinpath("test_plot_dir").resolve() + timeseries_plots('dymos_solution.db', plot_dir=plot_dir, problem=p) - self.assertTrue(os.path.exists('test_plot_dir/states_x.png')) - self.assertTrue(os.path.exists('test_plot_dir/states_y.png')) - self.assertTrue(os.path.exists('test_plot_dir/states_v.png')) - self.assertTrue(os.path.exists('test_plot_dir/controls_theta.png')) - self.assertTrue(os.path.exists('test_plot_dir/control_rates_theta_rate.png')) - self.assertTrue(os.path.exists('test_plot_dir/control_rates_theta_rate2.png')) + self.assertTrue(plot_dir.joinpath('states_x.png').exists()) + self.assertTrue(plot_dir.joinpath('states_y.png').exists()) + self.assertTrue(plot_dir.joinpath('states_v.png').exists()) + self.assertTrue(plot_dir.joinpath('controls_theta.png').exists()) + self.assertTrue(plot_dir.joinpath('control_rates_theta_rate.png').exists()) + self.assertTrue(plot_dir.joinpath('control_rates_theta_rate2.png').exists()) @use_tempdirs @@ -127,7 +137,7 @@ class TestTimeSeriesPlotsMultiPhase(unittest.TestCase): def test_trajectory_linked_phases_make_plot(self): self.traj = dm.Trajectory() - p = self.p = om.Problem(model=self.traj) + p = self.p = om.Problem(model=self.traj, name="trajectory_linked_phases_make_plot") p.driver = om.pyOptSparseDriver() p.driver.options['optimizer'] = 'IPOPT' @@ -257,7 +267,9 @@ def test_trajectory_linked_phases_make_plot(self): dm.run_problem(p, simulate=True, make_plots=False, simulation_record_file='simulation_record_file.db') - timeseries_plots('dymos_solution.db', simulation_record_file='simulation_record_file.db') + timeseries_plots('dymos_solution.db', simulation_record_file='simulation_record_file.db', + problem=p) + plot_dir = pathlib.Path(p.get_reports_dir()).joinpath("plots") for varname in ['time_phase', 'states:r', 'state_rates:r', 'states:theta', 'state_rates:theta', 'states:vr', 'state_rates:vr', 'states:vt', @@ -265,11 +277,11 @@ def test_trajectory_linked_phases_make_plot(self): 'state_rates:accel', 'states:deltav', 'state_rates:deltav', 'controls:u1', 'control_rates:u1_rate', 'control_rates:u1_rate2', 'parameters:c']: - self.assertTrue(os.path.exists(f'plots/{varname.replace(":","_")}.png')) + self.assertTrue(plot_dir.joinpath(varname.replace(":","_") + '.png').exists()) def test_overlapping_phases_make_plot(self): - prob = om.Problem() + prob = om.Problem(name="overlapping_phases_make_plot") opt = prob.driver = om.ScipyOptimizeDriver() opt.declare_coloring() @@ -345,11 +357,13 @@ def test_overlapping_phases_make_plot(self): dm.run_problem(prob, simulate=True, make_plots=False, simulation_record_file='simulation_record_file.db') - timeseries_plots('dymos_solution.db', simulation_record_file='simulation_record_file.db') + timeseries_plots('dymos_solution.db', simulation_record_file='simulation_record_file.db', + problem=prob) + plot_dir = pathlib.Path(prob.get_reports_dir()).joinpath("plots") - self.assertTrue(os.path.exists('plots/time_phase.png')) - self.assertTrue(os.path.exists('plots/states_state_of_charge.png')) - self.assertTrue(os.path.exists('plots/state_rates_state_of_charge.png')) + self.assertTrue(plot_dir.joinpath('time_phase.png').exists()) + self.assertTrue(plot_dir.joinpath('states_state_of_charge.png').exists()) + self.assertTrue(plot_dir.joinpath('state_rates_state_of_charge.png').exists()) @require_pyoptsparse(optimizer='IPOPT') def test_trajectory_linked_phases_make_plot_missing_data(self): @@ -359,7 +373,7 @@ def test_trajectory_linked_phases_make_plot_missing_data(self): """ self.traj = dm.Trajectory() - p = self.p = om.Problem(model=self.traj) + p = self.p = om.Problem(model=self.traj, name="trajectory_linked_phases_make_plot_missing_data") p.driver = om.pyOptSparseDriver() p.driver.options['optimizer'] = 'IPOPT' @@ -487,7 +501,9 @@ def test_trajectory_linked_phases_make_plot_missing_data(self): dm.run_problem(p, simulate=True, make_plots=False, simulation_record_file='simulation_record_file.db') - timeseries_plots('dymos_solution.db', simulation_record_file='simulation_record_file.db') + timeseries_plots('dymos_solution.db', simulation_record_file='simulation_record_file.db', + problem=p) + plot_dir = pathlib.Path(p.get_reports_dir()).joinpath("plots") for varname in ['time_phase', 'states:r', 'state_rates:r', 'states:theta', 'state_rates:theta', 'states:vr', 'state_rates:vr', 'states:vt', @@ -495,7 +511,7 @@ def test_trajectory_linked_phases_make_plot_missing_data(self): 'state_rates:accel', 'states:deltav', 'state_rates:deltav', 'controls:u1', 'control_rates:u1_rate', 'control_rates:u1_rate2', 'parameters:c', 'parameters:u1']: - self.assertTrue(os.path.exists(f'plots/{varname.replace(":","_")}.png')) + self.assertTrue(plot_dir.joinpath(varname.replace(":","_") + '.png').exists()) if __name__ == '__main__': # pragma: no cover diff --git a/dymos/visualization/timeseries_plots.py b/dymos/visualization/timeseries_plots.py index 2da58b497..798056294 100644 --- a/dymos/visualization/timeseries_plots.py +++ b/dymos/visualization/timeseries_plots.py @@ -1,5 +1,6 @@ import os import warnings +import pathlib import numpy as np @@ -57,8 +58,9 @@ def _mpl_timeseries_plots(varnames, time_units, var_units, phase_names, phases_n plt.switch_backend('Agg') # use a colormap with 20 values cm = plt.cm.get_cmap('tab20') + plotfiles = [] - for ivar, var_name in enumerate(varnames): + for var_name in varnames: # start a new plot fig, ax = plt.subplots() @@ -129,25 +131,30 @@ def _mpl_timeseries_plots(varnames, time_units, var_units, phase_names, phases_n plt.subplots_adjust(bottom=0.23, top=0.9, left=0.2) # save to file - plot_file_path = os.path.join(plot_dir_path, f'{var_name.replace(":","_")}.png') + plot_file_path = plot_dir_path.joinpath(f'{var_name.replace(":","_")}.png') plt.savefig(plot_file_path) + plotfiles.append(plot_file_path) plt.switch_backend(backend_save) + return plotfiles + def _bokeh_timeseries_plots(varnames, time_units, var_units, phase_names, phases_node_path, last_solution_case, last_simulation_case, plot_dir_path, num_cols=2, bg_fill_color='#282828', grid_line_color='#666666', open_browser=False): from bokeh.io import output_notebook, output_file, save, show - from bokeh.layouts import gridplot, column, row, grid, layout - from bokeh.models import Legend, LegendItem + from bokeh.layouts import gridplot, column + from bokeh.models import Legend from bokeh.plotting import figure import bokeh.palettes as bp if dymos_options['notebook_mode']: output_notebook() + fname = None else: - output_file(os.path.join(plot_dir_path, 'plots.html')) + fname = os.path.join(plot_dir_path, 'plots.html') + output_file(fname) # Prune the edges from the color map cmap = bp.turbo(len(phase_names) + 2)[1:-1] @@ -170,7 +177,7 @@ def _bokeh_timeseries_plots(varnames, time_units, var_units, phase_names, phases max_time = max(max_time, np.max(last_solution_case.outputs[time_name])) colors[phase_name] = cmap[iphase] - for ivar, var_name in enumerate(varnames): + for var_name in varnames: # Get the labels time_label = f'time ({time_units[var_name]})' var_label = f'{var_name} ({var_units[var_name]})' @@ -268,8 +275,12 @@ def _bokeh_timeseries_plots(varnames, time_units, var_units, phase_names, phases else: save(plots) + if fname is None: + return [] + return [fname] -def timeseries_plots(solution_recorder_filename, simulation_record_file=None, plot_dir="plots"): +def timeseries_plots(solution_recorder_filename, simulation_record_file=None, plot_dir="plots", + problem=None): """ Create plots of the timeseries. @@ -285,12 +296,17 @@ def timeseries_plots(solution_recorder_filename, simulation_record_file=None, pl this implies that the data from it should be plotted. plot_dir : str The path to the directory to which the plot files will be written. + problem : Problem or None + If not None, the owning Problem. """ # get ready to generate plot files - plot_dir_path = os.path.join(os.getcwd(), plot_dir) - if not os.path.isdir(plot_dir_path): - os.mkdir(plot_dir_path) + if problem is None: + plot_dir_path = pathlib.Path.cwd().joinpath(plot_dir).resolve() + else: + plot_dir_path = pathlib.Path(problem.get_reports_dir()).joinpath(plot_dir).resolve() + + plot_dir_path.mkdir(parents=True, exist_ok=True) cr = om.CaseReader(solution_recorder_filename) @@ -349,7 +365,14 @@ def timeseries_plots(solution_recorder_filename, simulation_record_file=None, pl _bokeh_timeseries_plots(varnames, time_units, var_units, phase_names, phases_node_path, last_solution_case, last_simulation_case, plot_dir_path) elif dymos_options['plots'] == 'matplotlib': - _mpl_timeseries_plots(varnames, time_units, var_units, phase_names, phases_node_path, - last_solution_case, last_simulation_case, plot_dir_path) + fnames = _mpl_timeseries_plots(varnames, time_units, var_units, phase_names, phases_node_path, + last_solution_case, last_simulation_case, plot_dir_path) + if problem is not None: + for name in fnames: + # create html files that wrap the image files + fpath = pathlib.Path(name).resolve() + htmlpath = str(fpath.parent.joinpath(fpath.stem + '.html')) + with open(htmlpath, 'w', encoding='utf-8') as f: + f.write(om.image2html(fpath.name)) else: raise ValueError(f'Unknown plotting option: {dymos_options["plots"]}') From 4807365ce62797e4d0456c696a1857ef319499e5 Mon Sep 17 00:00:00 2001 From: Bret Naylor Date: Fri, 22 Jul 2022 09:22:22 -0400 Subject: [PATCH 02/17] added make_plots arg to a couple of benchmarks --- benchmark/benchmark_balanced_field.py | 8 ++++++-- benchmark/benchmark_racecar.py | 6 +++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/benchmark/benchmark_balanced_field.py b/benchmark/benchmark_balanced_field.py index 332103315..61e2a1b59 100644 --- a/benchmark/benchmark_balanced_field.py +++ b/benchmark/benchmark_balanced_field.py @@ -8,7 +8,8 @@ from dymos.examples.balanced_field.balanced_field_ode import BalancedFieldODEComp -def _run_balanced_field_length_problem(tx=dm.GaussLobatto, timeseries=True, sim=True, solvesegs=False): +def _run_balanced_field_length_problem(tx=dm.GaussLobatto, timeseries=True, sim=True, solvesegs=False, + make_plots=False): p = om.Problem() _, optimizer = set_pyoptsparse_opt('IPOPT', fallback=True) @@ -238,7 +239,7 @@ def _run_balanced_field_length_problem(tx=dm.GaussLobatto, timeseries=True, sim= p.set_val('traj.climb.states:gam', climb.interp('gam', [0, 5.0]), units='deg') p.set_val('traj.climb.controls:alpha', 5.0, units='deg') - dm.run_problem(p, run_driver=True, simulate=sim) + dm.run_problem(p, run_driver=True, simulate=sim, make_plots=make_plots) # Test this example in Dymos' continuous integration assert_near_equal(p.get_val('traj.rto.states:r')[-1], 2188.2, tolerance=0.01) @@ -267,3 +268,6 @@ def benchmark_radau_timeseries_nosim_nosolveseg(self): def benchmark_radau_notimeseries_nosim_solveseg(self): _run_balanced_field_length_problem(tx=dm.Radau, timeseries=False, sim=False, solvesegs='forward') + +if __name__ == '__main__': + _run_balanced_field_length_problem(make_plots=True) diff --git a/benchmark/benchmark_racecar.py b/benchmark/benchmark_racecar.py index b1b5636c4..ab1962fae 100644 --- a/benchmark/benchmark_racecar.py +++ b/benchmark/benchmark_racecar.py @@ -11,7 +11,7 @@ from dymos.examples.racecar.tracks import ovaltrack # track curvature imports -def _run_racecar_problem(transcription, timeseries=False): +def _run_racecar_problem(transcription, timeseries=False, make_plots=False): # change track here and in curvature.py. Tracks are defined in tracks.py track = ovaltrack @@ -161,7 +161,7 @@ def _run_racecar_problem(transcription, timeseries=False): p.set_val('traj.phase0.controls:thrust', phase.interp('thrust', [0.1, 0.1]), units=None) # a small amount of thrust can speed up convergence - dm.run_problem(p, run_driver=True, simulate=False, make_plots=False) + dm.run_problem(p, run_driver=True, simulate=False, make_plots=make_plots) print('Optimization finished') t = p.get_val('traj.phase0.timeseries.states:t') @@ -187,4 +187,4 @@ def benchmark_radau_timeseries(self): if __name__ == '__main__': - _run_racecar_problem(dm.GaussLobatto, timeseries=False) + _run_racecar_problem(dm.GaussLobatto, timeseries=False, make_plots=True) From 79e5ad77e2924f05f3d6f5d0c798d1957e53e526 Mon Sep 17 00:00:00 2001 From: Bret Naylor Date: Fri, 22 Jul 2022 09:40:10 -0400 Subject: [PATCH 03/17] updated comment --- dymos/visualization/timeseries_plots.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dymos/visualization/timeseries_plots.py b/dymos/visualization/timeseries_plots.py index 798056294..cedb9b7db 100644 --- a/dymos/visualization/timeseries_plots.py +++ b/dymos/visualization/timeseries_plots.py @@ -297,7 +297,8 @@ def timeseries_plots(solution_recorder_filename, simulation_record_file=None, pl plot_dir : str The path to the directory to which the plot files will be written. problem : Problem or None - If not None, the owning Problem. + If not None, the owning Problem, and the plot_dir will be relative to the reports + directory for the Problem. """ # get ready to generate plot files From 49e2905a71123c6573c2fc821e831b19543c8039 Mon Sep 17 00:00:00 2001 From: Bret Naylor Date: Fri, 22 Jul 2022 11:15:38 -0400 Subject: [PATCH 04/17] reverted some unnecessary changes --- .../test/test_timeseries_plots.py | 124 +++++++++--------- 1 file changed, 60 insertions(+), 64 deletions(-) diff --git a/dymos/visualization/test/test_timeseries_plots.py b/dymos/visualization/test/test_timeseries_plots.py index c2de89b19..2c4bebb1f 100644 --- a/dymos/visualization/test/test_timeseries_plots.py +++ b/dymos/visualization/test/test_timeseries_plots.py @@ -15,76 +15,75 @@ from dymos.visualization.timeseries_plots import timeseries_plots -def _setup_problem(pname): - optimizer = 'SLSQP' - num_segments = 8 - transcription_order = 3 - compressed = True +@use_tempdirs +@require_pyoptsparse(optimizer='SLSQP') +class TestTimeSeriesPlotsBasics(unittest.TestCase): - p = om.Problem(model=om.Group(), name=pname) + def setUp(self): + optimizer = 'SLSQP' + num_segments = 8 + transcription_order = 3 + compressed = True - p.driver = om.pyOptSparseDriver() - p.driver.options['optimizer'] = optimizer - p.driver.declare_coloring() + p = om.Problem(model=om.Group()) - t = dm.GaussLobatto(num_segments=num_segments, - order=transcription_order, - compressed=compressed) - traj = dm.Trajectory() - phase = dm.Phase(ode_class=BrachistochroneODE, transcription=t) - traj.add_phase('phase0', phase) + p.driver = om.pyOptSparseDriver() + p.driver.options['optimizer'] = optimizer + p.driver.declare_coloring() - p.model.add_subsystem('traj0', traj) + t = dm.GaussLobatto(num_segments=num_segments, + order=transcription_order, + compressed=compressed) + traj = dm.Trajectory() + phase = dm.Phase(ode_class=BrachistochroneODE, transcription=t) + traj.add_phase('phase0', phase) - phase.set_time_options(fix_initial=True, duration_bounds=(.5, 10)) + p.model.add_subsystem('traj0', traj) - phase.add_state('x', fix_initial=True, fix_final=False) - phase.add_state('y', fix_initial=True, fix_final=False) - phase.add_state('v', fix_initial=True, fix_final=False) + phase.set_time_options(fix_initial=True, duration_bounds=(.5, 10)) - phase.add_control('theta', continuity=True, rate_continuity=True, - units='deg', lower=0.01, upper=179.9) + phase.add_state('x', fix_initial=True, fix_final=False) + phase.add_state('y', fix_initial=True, fix_final=False) + phase.add_state('v', fix_initial=True, fix_final=False) - phase.add_parameter('g', units='m/s**2', val=9.80665) + phase.add_control('theta', continuity=True, rate_continuity=True, + units='deg', lower=0.01, upper=179.9) - phase.add_timeseries('timeseries2', - transcription=dm.Radau(num_segments=num_segments * 5, - order=transcription_order, - compressed=compressed), - subset='control_input') + phase.add_parameter('g', units='m/s**2', val=9.80665) - phase.add_boundary_constraint('x', loc='final', equals=10) - phase.add_boundary_constraint('y', loc='final', equals=5) - # Minimize time at the end of the phase - phase.add_objective('time_phase', loc='final', scaler=10) + phase.add_timeseries('timeseries2', + transcription=dm.Radau(num_segments=num_segments * 5, + order=transcription_order, + compressed=compressed), + subset='control_input') - p.model.linear_solver = om.DirectSolver() - p.setup(check=['unconnected_inputs']) + phase.add_boundary_constraint('x', loc='final', equals=10) + phase.add_boundary_constraint('y', loc='final', equals=5) + # Minimize time at the end of the phase + phase.add_objective('time_phase', loc='final', scaler=10) - p['traj0.phase0.t_initial'] = 0.0 - p['traj0.phase0.t_duration'] = 2.0 + p.model.linear_solver = om.DirectSolver() + p.setup(check=['unconnected_inputs']) - p['traj0.phase0.states:x'] = phase.interp('x', [0, 10]) - p['traj0.phase0.states:y'] = phase.interp('y', [10, 5]) - p['traj0.phase0.states:v'] = phase.interp('v', [0, 9.9]) - p['traj0.phase0.controls:theta'] = phase.interp('theta', [5, 100]) - p['traj0.phase0.parameters:g'] = 9.80665 + p['traj0.phase0.t_initial'] = 0.0 + p['traj0.phase0.t_duration'] = 2.0 - p.setup() + p['traj0.phase0.states:x'] = phase.interp('x', [0, 10]) + p['traj0.phase0.states:y'] = phase.interp('y', [10, 5]) + p['traj0.phase0.states:v'] = phase.interp('v', [0, 9.9]) + p['traj0.phase0.controls:theta'] = phase.interp('theta', [5, 100]) + p['traj0.phase0.parameters:g'] = 9.80665 - return p + p.setup() + self.p = p -@use_tempdirs -@require_pyoptsparse(optimizer='SLSQP') -class TestTimeSeriesPlotsBasics(unittest.TestCase): def test_brachistochrone_timeseries_plots(self): - p = _setup_problem("brachistochrone_timeseries_plots") - dm.run_problem(p, make_plots=False) + dm.run_problem(self.p, make_plots=False) - timeseries_plots('dymos_solution.db', problem=p) - plot_dir = pathlib.Path(p.get_reports_dir()).joinpath('plots') + timeseries_plots('dymos_solution.db', problem=self.p) + plot_dir = pathlib.Path(self.p.get_reports_dir()).joinpath('plots') self.assertTrue(plot_dir.joinpath('states_x.png').exists()) self.assertTrue(plot_dir.joinpath('states_y.png').exists()) @@ -94,12 +93,11 @@ def test_brachistochrone_timeseries_plots(self): self.assertTrue(plot_dir.joinpath('control_rates_theta_rate2.png').exists()) def test_brachistochrone_timeseries_plots_solution_only_set_solution_record_file(self): - p = _setup_problem("brachistochrone_timeseries_plots_solution_only_set_solution_record_file") # records to the default file 'dymos_simulation.db' - dm.run_problem(p, make_plots=False, solution_record_file='solution_record_file.db') + dm.run_problem(self.p, make_plots=False, solution_record_file='solution_record_file.db') - timeseries_plots('solution_record_file.db', problem=p) - plot_dir = pathlib.Path(p.get_reports_dir()).joinpath('plots') + timeseries_plots('solution_record_file.db', problem=self.p) + plot_dir = pathlib.Path(self.p.get_reports_dir()).joinpath('plots') self.assertTrue(plot_dir.joinpath('states_x.png').exists()) self.assertTrue(plot_dir.joinpath('states_y.png').exists()) @@ -109,18 +107,16 @@ def test_brachistochrone_timeseries_plots_solution_only_set_solution_record_file self.assertTrue(plot_dir.joinpath('control_rates_theta_rate2.png').exists()) def test_brachistochrone_timeseries_plots_solution_and_simulation(self): - p = _setup_problem("brachistochrone_timeseries_plots_solution_and_simulation") - dm.run_problem(p, simulate=True, make_plots=False, + dm.run_problem(self.p, simulate=True, make_plots=False, simulation_record_file='simulation_record_file.db') - timeseries_plots('dymos_solution.db', simulation_record_file='simulation_record_file.db', problem=p) + timeseries_plots('dymos_solution.db', simulation_record_file='simulation_record_file.db', problem=self.p) def test_brachistochrone_timeseries_plots_set_plot_dir(self): - p = _setup_problem("brachistochrone_timeseries_plots_set_plot_dir") - dm.run_problem(p, make_plots=False) + dm.run_problem(self.p, make_plots=False) - plot_dir = pathlib.Path(p.get_reports_dir()).joinpath("test_plot_dir").resolve() - timeseries_plots('dymos_solution.db', plot_dir=plot_dir, problem=p) + plot_dir = pathlib.Path(self.p.get_reports_dir()).joinpath("test_plot_dir").resolve() + timeseries_plots('dymos_solution.db', plot_dir=plot_dir, problem=self.p) self.assertTrue(plot_dir.joinpath('states_x.png').exists()) self.assertTrue(plot_dir.joinpath('states_y.png').exists()) @@ -137,7 +133,7 @@ class TestTimeSeriesPlotsMultiPhase(unittest.TestCase): def test_trajectory_linked_phases_make_plot(self): self.traj = dm.Trajectory() - p = self.p = om.Problem(model=self.traj, name="trajectory_linked_phases_make_plot") + p = self.p = om.Problem(model=self.traj) p.driver = om.pyOptSparseDriver() p.driver.options['optimizer'] = 'IPOPT' @@ -281,7 +277,7 @@ def test_trajectory_linked_phases_make_plot(self): def test_overlapping_phases_make_plot(self): - prob = om.Problem(name="overlapping_phases_make_plot") + prob = om.Problem() opt = prob.driver = om.ScipyOptimizeDriver() opt.declare_coloring() @@ -373,7 +369,7 @@ def test_trajectory_linked_phases_make_plot_missing_data(self): """ self.traj = dm.Trajectory() - p = self.p = om.Problem(model=self.traj, name="trajectory_linked_phases_make_plot_missing_data") + p = self.p = om.Problem(model=self.traj) p.driver = om.pyOptSparseDriver() p.driver.options['optimizer'] = 'IPOPT' From 6c8aeeec029494a285e6a5db407dd62f6008589f Mon Sep 17 00:00:00 2001 From: Bret Naylor Date: Fri, 22 Jul 2022 11:27:41 -0400 Subject: [PATCH 05/17] cleanup --- dymos/visualization/test/test_timeseries_plots.py | 7 +++---- dymos/visualization/timeseries_plots.py | 8 +------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/dymos/visualization/test/test_timeseries_plots.py b/dymos/visualization/test/test_timeseries_plots.py index 2c4bebb1f..a9d01fff6 100644 --- a/dymos/visualization/test/test_timeseries_plots.py +++ b/dymos/visualization/test/test_timeseries_plots.py @@ -47,15 +47,15 @@ def setUp(self): phase.add_state('v', fix_initial=True, fix_final=False) phase.add_control('theta', continuity=True, rate_continuity=True, - units='deg', lower=0.01, upper=179.9) + units='deg', lower=0.01, upper=179.9) phase.add_parameter('g', units='m/s**2', val=9.80665) phase.add_timeseries('timeseries2', - transcription=dm.Radau(num_segments=num_segments * 5, + transcription=dm.Radau(num_segments=num_segments * 5, order=transcription_order, compressed=compressed), - subset='control_input') + subset='control_input') phase.add_boundary_constraint('x', loc='final', equals=10) phase.add_boundary_constraint('y', loc='final', equals=5) @@ -78,7 +78,6 @@ def setUp(self): self.p = p - def test_brachistochrone_timeseries_plots(self): dm.run_problem(self.p, make_plots=False) diff --git a/dymos/visualization/timeseries_plots.py b/dymos/visualization/timeseries_plots.py index cedb9b7db..dfea23f21 100644 --- a/dymos/visualization/timeseries_plots.py +++ b/dymos/visualization/timeseries_plots.py @@ -151,10 +151,8 @@ def _bokeh_timeseries_plots(varnames, time_units, var_units, phase_names, phases if dymos_options['notebook_mode']: output_notebook() - fname = None else: - fname = os.path.join(plot_dir_path, 'plots.html') - output_file(fname) + output_file(os.path.join(plot_dir_path, 'plots.html')) # Prune the edges from the color map cmap = bp.turbo(len(phase_names) + 2)[1:-1] @@ -275,10 +273,6 @@ def _bokeh_timeseries_plots(varnames, time_units, var_units, phase_names, phases else: save(plots) - if fname is None: - return [] - return [fname] - def timeseries_plots(solution_recorder_filename, simulation_record_file=None, plot_dir="plots", problem=None): """ From 8c93518fe9d7aae3dc57b3544525a92e1b618829 Mon Sep 17 00:00:00 2001 From: Bret Naylor Date: Fri, 22 Jul 2022 11:32:59 -0400 Subject: [PATCH 06/17] fixed comment --- dymos/visualization/timeseries_plots.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dymos/visualization/timeseries_plots.py b/dymos/visualization/timeseries_plots.py index dfea23f21..8d4680b61 100644 --- a/dymos/visualization/timeseries_plots.py +++ b/dymos/visualization/timeseries_plots.py @@ -291,8 +291,8 @@ def timeseries_plots(solution_recorder_filename, simulation_record_file=None, pl plot_dir : str The path to the directory to which the plot files will be written. problem : Problem or None - If not None, the owning Problem, and the plot_dir will be relative to the reports - directory for the Problem. + If not None, this is the owning Problem, and the plot_dir will be relative to the reports + directory for this Problem. """ # get ready to generate plot files From c54b27e96957cf117adc48accceb8bc181f4346d Mon Sep 17 00:00:00 2001 From: Bret Naylor Date: Fri, 22 Jul 2022 12:45:32 -0400 Subject: [PATCH 07/17] forgot image2html hadn't been added to om yet --- dymos/visualization/timeseries_plots.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dymos/visualization/timeseries_plots.py b/dymos/visualization/timeseries_plots.py index 8d4680b61..0dbc9ac78 100644 --- a/dymos/visualization/timeseries_plots.py +++ b/dymos/visualization/timeseries_plots.py @@ -9,6 +9,7 @@ import matplotlib.patches as mpatches import openmdao.api as om +from openmdao.utils.file_utils import image2html from dymos.options import options as dymos_options @@ -133,6 +134,7 @@ def _mpl_timeseries_plots(varnames, time_units, var_units, phase_names, phases_n # save to file plot_file_path = plot_dir_path.joinpath(f'{var_name.replace(":","_")}.png') plt.savefig(plot_file_path) + plt.close(fig) plotfiles.append(plot_file_path) plt.switch_backend(backend_save) @@ -368,6 +370,6 @@ def timeseries_plots(solution_recorder_filename, simulation_record_file=None, pl fpath = pathlib.Path(name).resolve() htmlpath = str(fpath.parent.joinpath(fpath.stem + '.html')) with open(htmlpath, 'w', encoding='utf-8') as f: - f.write(om.image2html(fpath.name)) + f.write(image2html(fpath.name)) else: raise ValueError(f'Unknown plotting option: {dymos_options["plots"]}') From 6d8feed7a476349b8ec46984394a580dec411ecf Mon Sep 17 00:00:00 2001 From: Bret Naylor Date: Mon, 25 Jul 2022 09:18:38 -0400 Subject: [PATCH 08/17] interim --- ...est_ex_two_burn_orbit_raise_bokeh_plots.py | 16 ++++++++++------ dymos/test/test_run_problem.py | 19 ++++++++++++------- dymos/visualization/timeseries_plots.py | 12 +++++++----- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/dymos/examples/finite_burn_orbit_raise/test/test_ex_two_burn_orbit_raise_bokeh_plots.py b/dymos/examples/finite_burn_orbit_raise/test/test_ex_two_burn_orbit_raise_bokeh_plots.py index 516ec16c9..85e2530e5 100644 --- a/dymos/examples/finite_burn_orbit_raise/test/test_ex_two_burn_orbit_raise_bokeh_plots.py +++ b/dymos/examples/finite_burn_orbit_raise/test/test_ex_two_burn_orbit_raise_bokeh_plots.py @@ -2,6 +2,7 @@ import os import shutil import unittest +import pathlib import numpy as np @@ -200,16 +201,17 @@ def tearDown(self): def test_bokeh_plots(self): dm.options['plots'] = 'bokeh' - two_burn_orbit_raise_problem(transcription='gauss-lobatto', transcription_order=3, - compressed=False, optimizer='SLSQP', show_output=False) + p = two_burn_orbit_raise_problem(transcription='gauss-lobatto', transcription_order=3, + compressed=False, optimizer='SLSQP', show_output=False) - self.assertSetEqual({'plots.html'}, set(os.listdir('plots'))) + plot_dir = pathlib.Path(p.get_reports_dir()).joinpath('plots') + self.assertSetEqual({'plots.html'}, set(os.listdir(plot_dir))) def test_mpl_plots(self): dm.options['plots'] = 'matplotlib' - two_burn_orbit_raise_problem(transcription='gauss-lobatto', transcription_order=3, - compressed=False, optimizer='SLSQP', show_output=False) + p = two_burn_orbit_raise_problem(transcription='gauss-lobatto', transcription_order=3, + compressed=False, optimizer='SLSQP', show_output=False) expected_files = {'control_rates_u1_rate.png', 'state_rates_r.png', 'states_deltav.png', 'states_r.png', 'state_rates_accel.png', 'state_rates_deltav.png', @@ -218,7 +220,9 @@ def test_mpl_plots(self): 'control_rates_u1_rate2.png', 'state_rates_vt.png', 'time_phase.png', 'parameters_c.png', 'state_rates_theta.png', 'state_rates_vr.png', 'dt_dstau.png'} - self.assertSetEqual(expected_files, set(os.listdir('plots'))) + html_files = {str(pathlib.Path(f).with_suffix('.html')) for f in expected_files} + plot_dir = pathlib.Path(p.get_reports_dir()).joinpath('plots') + self.assertSetEqual(expected_files.union(html_files), set(os.listdir(plot_dir))) if __name__ == '__main__': # pragma: no cover diff --git a/dymos/test/test_run_problem.py b/dymos/test/test_run_problem.py index 5f4067bc8..ed6d7c080 100755 --- a/dymos/test/test_run_problem.py +++ b/dymos/test/test_run_problem.py @@ -1,6 +1,7 @@ from __future__ import print_function, division, absolute_import import os import unittest +import pathlib import numpy as np from numpy.testing import assert_almost_equal @@ -605,31 +606,33 @@ def setUp(self): def test_run_brachistochrone_problem_make_plots(self): dm.run_problem(self.p, make_plots=True) + plot_dir = pathlib.Path(self.p.get_reports_dir()).joinpath('plots') for varname in ['time_phase', 'states:x', 'state_rates:x', 'states:y', 'state_rates:y', 'states:v', 'state_rates:v', 'controls:theta', 'control_rates:theta_rate', 'control_rates:theta_rate2', 'parameters:g']: - self.assertTrue(os.path.exists(f'plots/{varname.replace(":","_")}.png')) + self.assertTrue(plot_dir.joinpath(f'{varname.replace(":","_")}.png').exists()) def test_run_brachistochrone_problem_make_plots_set_plot_dir(self): - plot_dir = "test_plot_dir" - dm.run_problem(self.p, make_plots=True, plot_dir=plot_dir) + dm.run_problem(self.p, make_plots=True, plot_dir="test_plot_dir") + plot_dir = pathlib.Path(self.p.get_reports_dir()) for varname in ['time_phase', 'states:x', 'state_rates:x', 'states:y', 'state_rates:y', 'states:v', 'state_rates:v', 'controls:theta', 'control_rates:theta_rate', 'control_rates:theta_rate2', 'parameters:g']: - self.assertTrue(os.path.exists(f'test_plot_dir/{varname.replace(":","_")}.png')) + self.assertTrue(plot_dir.joinpath('test_plot_dir', f'{varname.replace(":","_")}.png').exists()) def test_run_brachistochrone_problem_do_not_make_plots(self): dm.run_problem(self.p, make_plots=False) + plot_dir = pathlib.Path(self.p.get_reports_dir()).joinpath('plots') for varname in ['time_phase', 'states:x', 'state_rates:x', 'states:y', 'state_rates:y', 'states:v', 'state_rates:v', 'controls:theta', 'control_rates:theta_rate', 'control_rates:theta_rate2', 'parameters:g']: - self.assertFalse(os.path.exists(f'plots/{varname.replace(":","_")}.png')) + self.assertFalse(plot_dir.joinpath(f'{varname.replace(":","_")}.png').exists()) def test_run_brachistochrone_problem_set_simulation_record_file(self): simulation_record_file = 'simulation_record_file.db' @@ -645,21 +648,23 @@ def test_run_brachistochrone_problem_set_solution_record_file(self): def test_run_brachistochrone_problem_plot_simulation(self): dm.run_problem(self.p, make_plots=True, simulate=True) + plot_dir = pathlib.Path(self.p.get_reports_dir()).joinpath('plots') for varname in ['time_phase', 'states:x', 'state_rates:x', 'states:y', 'state_rates:y', 'states:v', 'state_rates:v', 'controls:theta', 'control_rates:theta_rate', 'control_rates:theta_rate2', 'parameters:g']: - self.assertTrue(os.path.exists(f'plots/{varname.replace(":","_")}.png')) + self.assertTrue(plot_dir.joinpath(f'{varname.replace(":","_")}.png').exists()) def test_run_brachistochrone_problem_plot_no_simulation_record_file_given(self): dm.run_problem(self.p, make_plots=True, simulate=True) + plot_dir = pathlib.Path(self.p.get_reports_dir()).joinpath('plots') for varname in ['time_phase', 'states:x', 'state_rates:x', 'states:y', 'state_rates:y', 'states:v', 'state_rates:v', 'controls:theta', 'control_rates:theta_rate', 'control_rates:theta_rate2', 'parameters:g']: - self.assertTrue(os.path.exists(f'plots/{varname.replace(":","_")}.png')) + self.assertTrue(plot_dir.joinpath(f'{varname.replace(":","_")}.png').exists()) @use_tempdirs diff --git a/dymos/visualization/timeseries_plots.py b/dymos/visualization/timeseries_plots.py index 0dbc9ac78..44659c622 100644 --- a/dymos/visualization/timeseries_plots.py +++ b/dymos/visualization/timeseries_plots.py @@ -275,6 +275,7 @@ def _bokeh_timeseries_plots(varnames, time_units, var_units, phase_names, phases else: save(plots) + def timeseries_plots(solution_recorder_filename, simulation_record_file=None, plot_dir="plots", problem=None): """ @@ -296,12 +297,13 @@ def timeseries_plots(solution_recorder_filename, simulation_record_file=None, pl If not None, this is the owning Problem, and the plot_dir will be relative to the reports directory for this Problem. """ - # get ready to generate plot files - if problem is None: - plot_dir_path = pathlib.Path.cwd().joinpath(plot_dir).resolve() - else: - plot_dir_path = pathlib.Path(problem.get_reports_dir()).joinpath(plot_dir).resolve() + + if not pathlib.Path(plot_dir).is_absolute(): + if problem is None: + plot_dir_path = pathlib.Path.cwd().joinpath(plot_dir) + else: + plot_dir_path = pathlib.Path(problem.get_reports_dir()).joinpath(plot_dir) plot_dir_path.mkdir(parents=True, exist_ok=True) From 0e65e19acbebe05647b71665ed29eb149db120b2 Mon Sep 17 00:00:00 2001 From: Bret Naylor Date: Mon, 25 Jul 2022 09:56:28 -0400 Subject: [PATCH 09/17] fixed ubc --- dymos/visualization/timeseries_plots.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dymos/visualization/timeseries_plots.py b/dymos/visualization/timeseries_plots.py index 44659c622..3e54cfa71 100644 --- a/dymos/visualization/timeseries_plots.py +++ b/dymos/visualization/timeseries_plots.py @@ -304,6 +304,8 @@ def timeseries_plots(solution_recorder_filename, simulation_record_file=None, pl plot_dir_path = pathlib.Path.cwd().joinpath(plot_dir) else: plot_dir_path = pathlib.Path(problem.get_reports_dir()).joinpath(plot_dir) + else: + plot_dir_path = plot_dir plot_dir_path.mkdir(parents=True, exist_ok=True) From 79cfdd0fd1568c544ac0e6a438cf35d596cc9e1b Mon Sep 17 00:00:00 2001 From: Bret Naylor Date: Wed, 27 Jul 2022 11:58:40 -0400 Subject: [PATCH 10/17] fixed testing pep issue --- dymos/visualization/test/test_timeseries_plots.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dymos/visualization/test/test_timeseries_plots.py b/dymos/visualization/test/test_timeseries_plots.py index a9d01fff6..6fdfbb13a 100644 --- a/dymos/visualization/test/test_timeseries_plots.py +++ b/dymos/visualization/test/test_timeseries_plots.py @@ -272,7 +272,7 @@ def test_trajectory_linked_phases_make_plot(self): 'state_rates:accel', 'states:deltav', 'state_rates:deltav', 'controls:u1', 'control_rates:u1_rate', 'control_rates:u1_rate2', 'parameters:c']: - self.assertTrue(plot_dir.joinpath(varname.replace(":","_") + '.png').exists()) + self.assertTrue(plot_dir.joinpath(varname.replace(":", "_") + '.png').exists()) def test_overlapping_phases_make_plot(self): @@ -506,7 +506,7 @@ def test_trajectory_linked_phases_make_plot_missing_data(self): 'state_rates:accel', 'states:deltav', 'state_rates:deltav', 'controls:u1', 'control_rates:u1_rate', 'control_rates:u1_rate2', 'parameters:c', 'parameters:u1']: - self.assertTrue(plot_dir.joinpath(varname.replace(":","_") + '.png').exists()) + self.assertTrue(plot_dir.joinpath(varname.replace(":", "_") + '.png').exists()) if __name__ == '__main__': # pragma: no cover From bb6d2306fec6af5c34274aa1b423480c7e7213e9 Mon Sep 17 00:00:00 2001 From: Bret Naylor Date: Wed, 27 Jul 2022 13:39:25 -0400 Subject: [PATCH 11/17] fix for testing vs. old om version --- dymos/visualization/timeseries_plots.py | 46 ++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/dymos/visualization/timeseries_plots.py b/dymos/visualization/timeseries_plots.py index 3e54cfa71..94c36346c 100644 --- a/dymos/visualization/timeseries_plots.py +++ b/dymos/visualization/timeseries_plots.py @@ -9,7 +9,6 @@ import matplotlib.patches as mpatches import openmdao.api as om -from openmdao.utils.file_utils import image2html from dymos.options import options as dymos_options @@ -276,6 +275,51 @@ def _bokeh_timeseries_plots(varnames, time_units, var_units, phase_names, phases save(plots) +# this function is defined in newer versions of openmdao, so remove this +# at some point later when we don't care about testing vs. older openmdao versions. +def image2html(imagefile, title='', alt=''): + """ + Wrap the given image for display as an html file. + + Returns an html syntax string that can be written to a file. + + Parameters + ---------- + imagefile : str + Name of image file to be displayed. + title : str + The page title. + alt : str + Set the alt text for the image. + + Returns + ------- + str + Content string to create an html file. + """ + return """ + + + + + + +

""" + title + "

" + f""" +{alt} + + + +""" + + def timeseries_plots(solution_recorder_filename, simulation_record_file=None, plot_dir="plots", problem=None): """ From d9d1f97233415a38458530aa8223466db703a827 Mon Sep 17 00:00:00 2001 From: Bret Naylor Date: Wed, 27 Jul 2022 15:11:49 -0400 Subject: [PATCH 12/17] fix for dymos testing vs old om --- dymos/visualization/timeseries_plots.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/dymos/visualization/timeseries_plots.py b/dymos/visualization/timeseries_plots.py index 94c36346c..fdea394f4 100644 --- a/dymos/visualization/timeseries_plots.py +++ b/dymos/visualization/timeseries_plots.py @@ -320,6 +320,17 @@ def image2html(imagefile, title='', alt=''): """ +def _get_reports_dir(prob): + # need this to work with older OM versions. + try: + return prob.get_reports_dir() + except Exception: + pass + + from openmdao.utils.reports_system import get_reports_dir + return str(pathlib.Path(get_reports_dir()).joinpath(prob._name)) + + def timeseries_plots(solution_recorder_filename, simulation_record_file=None, plot_dir="plots", problem=None): """ @@ -347,7 +358,7 @@ def timeseries_plots(solution_recorder_filename, simulation_record_file=None, pl if problem is None: plot_dir_path = pathlib.Path.cwd().joinpath(plot_dir) else: - plot_dir_path = pathlib.Path(problem.get_reports_dir()).joinpath(plot_dir) + plot_dir_path = pathlib.Path(_get_reports_dir(problem)).joinpath(plot_dir) else: plot_dir_path = plot_dir From 3eb43d0a749ad13a837815e6c1b085a53a864b3a Mon Sep 17 00:00:00 2001 From: Bret Naylor Date: Wed, 27 Jul 2022 16:10:42 -0400 Subject: [PATCH 13/17] another issue with old om --- dymos/visualization/timeseries_plots.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dymos/visualization/timeseries_plots.py b/dymos/visualization/timeseries_plots.py index fdea394f4..8cb934973 100644 --- a/dymos/visualization/timeseries_plots.py +++ b/dymos/visualization/timeseries_plots.py @@ -328,7 +328,7 @@ def _get_reports_dir(prob): pass from openmdao.utils.reports_system import get_reports_dir - return str(pathlib.Path(get_reports_dir()).joinpath(prob._name)) + return str(pathlib.Path(get_reports_dir(prob)).joinpath(prob._name)) def timeseries_plots(solution_recorder_filename, simulation_record_file=None, plot_dir="plots", From 9d56f135c29a90da4a5fa907fea1b1a2a3e923fc Mon Sep 17 00:00:00 2001 From: Bret Naylor Date: Thu, 28 Jul 2022 09:15:45 -0400 Subject: [PATCH 14/17] fix for testing with old openmdao --- .../test_ex_two_burn_orbit_raise_bokeh_plots.py | 10 ++++++---- dymos/test/test_run_problem.py | 14 ++++++++------ dymos/utils/testing_utils.py | 14 ++++++++++++++ dymos/visualization/test/test_timeseries_plots.py | 13 +++++++------ dymos/visualization/timeseries_plots.py | 13 +------------ 5 files changed, 36 insertions(+), 28 deletions(-) diff --git a/dymos/examples/finite_burn_orbit_raise/test/test_ex_two_burn_orbit_raise_bokeh_plots.py b/dymos/examples/finite_burn_orbit_raise/test/test_ex_two_burn_orbit_raise_bokeh_plots.py index 85e2530e5..4ad28eba4 100644 --- a/dymos/examples/finite_burn_orbit_raise/test/test_ex_two_burn_orbit_raise_bokeh_plots.py +++ b/dymos/examples/finite_burn_orbit_raise/test/test_ex_two_burn_orbit_raise_bokeh_plots.py @@ -9,12 +9,14 @@ import openmdao.api as om from openmdao.utils.testing_utils import require_pyoptsparse from openmdao.utils.general_utils import set_pyoptsparse_opt -_, optimizer = set_pyoptsparse_opt('IPOPT', fallback=True) +from openmdao.utils.testing_utils import use_tempdirs + import dymos as dm from dymos.examples.finite_burn_orbit_raise.finite_burn_eom import FiniteBurnODE -from openmdao.utils.testing_utils import use_tempdirs +from dymos.utils.testing_utils import _get_reports_dir +_, optimizer = set_pyoptsparse_opt('IPOPT', fallback=True) bokeh_available = importlib.util.find_spec('bokeh') is not None @@ -204,7 +206,7 @@ def test_bokeh_plots(self): p = two_burn_orbit_raise_problem(transcription='gauss-lobatto', transcription_order=3, compressed=False, optimizer='SLSQP', show_output=False) - plot_dir = pathlib.Path(p.get_reports_dir()).joinpath('plots') + plot_dir = pathlib.Path(_get_reports_dir(p)).joinpath('plots') self.assertSetEqual({'plots.html'}, set(os.listdir(plot_dir))) def test_mpl_plots(self): @@ -221,7 +223,7 @@ def test_mpl_plots(self): 'parameters_c.png', 'state_rates_theta.png', 'state_rates_vr.png', 'dt_dstau.png'} html_files = {str(pathlib.Path(f).with_suffix('.html')) for f in expected_files} - plot_dir = pathlib.Path(p.get_reports_dir()).joinpath('plots') + plot_dir = pathlib.Path(_get_reports_dir(p)).joinpath('plots') self.assertSetEqual(expected_files.union(html_files), set(os.listdir(plot_dir))) diff --git a/dymos/test/test_run_problem.py b/dymos/test/test_run_problem.py index e06a88d69..e0ef3cc32 100755 --- a/dymos/test/test_run_problem.py +++ b/dymos/test/test_run_problem.py @@ -10,12 +10,14 @@ import openmdao.api as om from openmdao.utils.assert_utils import assert_near_equal from openmdao.utils.testing_utils import use_tempdirs, require_pyoptsparse +from openmdao.utils.general_utils import set_pyoptsparse_opt import dymos as dm from dymos.examples.hyper_sensitive.hyper_sensitive_ode import HyperSensitiveODE from dymos.examples.brachistochrone.brachistochrone_ode import BrachistochroneODE from dymos.examples.brachistochrone.brachistochrone_vector_states_ode import BrachistochroneVectorStatesODE -from openmdao.utils.general_utils import set_pyoptsparse_opt +from dymos.utils.testing_utils import _get_reports_dir + _, optimizer = set_pyoptsparse_opt('IPOPT', fallback=True) om_version = tuple([int(s) for s in openmdao.__version__.split('-')[0].split('.')]) @@ -628,7 +630,7 @@ def setUp(self): def test_run_brachistochrone_problem_make_plots(self): dm.run_problem(self.p, make_plots=True) - plot_dir = pathlib.Path(self.p.get_reports_dir()).joinpath('plots') + plot_dir = pathlib.Path(_get_reports_dir(self.p)).joinpath('plots') for varname in ['time_phase', 'states:x', 'state_rates:x', 'states:y', 'state_rates:y', 'states:v', @@ -639,7 +641,7 @@ def test_run_brachistochrone_problem_make_plots(self): def test_run_brachistochrone_problem_make_plots_set_plot_dir(self): dm.run_problem(self.p, make_plots=True, plot_dir="test_plot_dir") - plot_dir = pathlib.Path(self.p.get_reports_dir()) + plot_dir = pathlib.Path(_get_reports_dir(self.p)) for varname in ['time_phase', 'states:x', 'state_rates:x', 'states:y', 'state_rates:y', 'states:v', 'state_rates:v', 'controls:theta', 'control_rates:theta_rate', @@ -648,7 +650,7 @@ def test_run_brachistochrone_problem_make_plots_set_plot_dir(self): def test_run_brachistochrone_problem_do_not_make_plots(self): dm.run_problem(self.p, make_plots=False) - plot_dir = pathlib.Path(self.p.get_reports_dir()).joinpath('plots') + plot_dir = pathlib.Path(_get_reports_dir(self.p)).joinpath('plots') for varname in ['time_phase', 'states:x', 'state_rates:x', 'states:y', 'state_rates:y', 'states:v', @@ -670,7 +672,7 @@ def test_run_brachistochrone_problem_set_solution_record_file(self): def test_run_brachistochrone_problem_plot_simulation(self): dm.run_problem(self.p, make_plots=True, simulate=True) - plot_dir = pathlib.Path(self.p.get_reports_dir()).joinpath('plots') + plot_dir = pathlib.Path(_get_reports_dir(self.p)).joinpath('plots') for varname in ['time_phase', 'states:x', 'state_rates:x', 'states:y', 'state_rates:y', 'states:v', @@ -680,7 +682,7 @@ def test_run_brachistochrone_problem_plot_simulation(self): def test_run_brachistochrone_problem_plot_no_simulation_record_file_given(self): dm.run_problem(self.p, make_plots=True, simulate=True) - plot_dir = pathlib.Path(self.p.get_reports_dir()).joinpath('plots') + plot_dir = pathlib.Path(_get_reports_dir(self.p)).joinpath('plots') for varname in ['time_phase', 'states:x', 'state_rates:x', 'states:y', 'state_rates:y', 'states:v', diff --git a/dymos/utils/testing_utils.py b/dymos/utils/testing_utils.py index b26fc4129..863d7eff2 100644 --- a/dymos/utils/testing_utils.py +++ b/dymos/utils/testing_utils.py @@ -1,4 +1,6 @@ import io +import pathlib +from packaging.version import Version import numpy as np @@ -6,6 +8,7 @@ import openmdao.api as om import openmdao.utils.assert_utils as _om_assert_utils +from openmdao import __version__ as openmdao_version def assert_check_partials(data, atol=1.0E-6, rtol=1.0E-6): @@ -193,3 +196,14 @@ def assert_timeseries_near_equal(t1, x1, t2, x2, tolerance=1.0E-6): y_interp = np.reshape(interp(t_check), newshape=(num_points,) + shape1) _om_assert_utils.assert_near_equal(y_interp, x_check, tolerance=tolerance) + + +def _get_reports_dir(prob): + # need this to work with older OM versions with old reports system API + # reports API changed between 3.18 and 3.19, so handle it here in order to be able to test against older + # versions of openmdao + if Version(openmdao_version) > Version("3.18"): + return prob.get_reports_dir() + + from openmdao.utils.reports_system import get_reports_dir + return str(pathlib.Path(get_reports_dir()).joinpath(prob._name)) diff --git a/dymos/visualization/test/test_timeseries_plots.py b/dymos/visualization/test/test_timeseries_plots.py index 6fdfbb13a..1fb802de1 100644 --- a/dymos/visualization/test/test_timeseries_plots.py +++ b/dymos/visualization/test/test_timeseries_plots.py @@ -12,6 +12,7 @@ from dymos.examples.brachistochrone.brachistochrone_ode import BrachistochroneODE from dymos.examples.battery_multibranch.battery_multibranch_ode import BatteryODE from dymos.utils.lgl import lgl +from dymos.utils.testing_utils import _get_reports_dir from dymos.visualization.timeseries_plots import timeseries_plots @@ -82,7 +83,7 @@ def test_brachistochrone_timeseries_plots(self): dm.run_problem(self.p, make_plots=False) timeseries_plots('dymos_solution.db', problem=self.p) - plot_dir = pathlib.Path(self.p.get_reports_dir()).joinpath('plots') + plot_dir = pathlib.Path(_get_reports_dir(self.p)).joinpath('plots') self.assertTrue(plot_dir.joinpath('states_x.png').exists()) self.assertTrue(plot_dir.joinpath('states_y.png').exists()) @@ -96,7 +97,7 @@ def test_brachistochrone_timeseries_plots_solution_only_set_solution_record_file dm.run_problem(self.p, make_plots=False, solution_record_file='solution_record_file.db') timeseries_plots('solution_record_file.db', problem=self.p) - plot_dir = pathlib.Path(self.p.get_reports_dir()).joinpath('plots') + plot_dir = pathlib.Path(_get_reports_dir(self.p)).joinpath('plots') self.assertTrue(plot_dir.joinpath('states_x.png').exists()) self.assertTrue(plot_dir.joinpath('states_y.png').exists()) @@ -114,7 +115,7 @@ def test_brachistochrone_timeseries_plots_solution_and_simulation(self): def test_brachistochrone_timeseries_plots_set_plot_dir(self): dm.run_problem(self.p, make_plots=False) - plot_dir = pathlib.Path(self.p.get_reports_dir()).joinpath("test_plot_dir").resolve() + plot_dir = pathlib.Path(_get_reports_dir(self.p)).joinpath("test_plot_dir").resolve() timeseries_plots('dymos_solution.db', plot_dir=plot_dir, problem=self.p) self.assertTrue(plot_dir.joinpath('states_x.png').exists()) @@ -264,7 +265,7 @@ def test_trajectory_linked_phases_make_plot(self): timeseries_plots('dymos_solution.db', simulation_record_file='simulation_record_file.db', problem=p) - plot_dir = pathlib.Path(p.get_reports_dir()).joinpath("plots") + plot_dir = pathlib.Path(_get_reports_dir(p)).joinpath("plots") for varname in ['time_phase', 'states:r', 'state_rates:r', 'states:theta', 'state_rates:theta', 'states:vr', 'state_rates:vr', 'states:vt', @@ -354,7 +355,7 @@ def test_overlapping_phases_make_plot(self): timeseries_plots('dymos_solution.db', simulation_record_file='simulation_record_file.db', problem=prob) - plot_dir = pathlib.Path(prob.get_reports_dir()).joinpath("plots") + plot_dir = pathlib.Path(_get_reports_dir(prob)).joinpath("plots") self.assertTrue(plot_dir.joinpath('time_phase.png').exists()) self.assertTrue(plot_dir.joinpath('states_state_of_charge.png').exists()) @@ -498,7 +499,7 @@ def test_trajectory_linked_phases_make_plot_missing_data(self): timeseries_plots('dymos_solution.db', simulation_record_file='simulation_record_file.db', problem=p) - plot_dir = pathlib.Path(p.get_reports_dir()).joinpath("plots") + plot_dir = pathlib.Path(_get_reports_dir(p)).joinpath("plots") for varname in ['time_phase', 'states:r', 'state_rates:r', 'states:theta', 'state_rates:theta', 'states:vr', 'state_rates:vr', 'states:vt', diff --git a/dymos/visualization/timeseries_plots.py b/dymos/visualization/timeseries_plots.py index 8cb934973..94c36346c 100644 --- a/dymos/visualization/timeseries_plots.py +++ b/dymos/visualization/timeseries_plots.py @@ -320,17 +320,6 @@ def image2html(imagefile, title='', alt=''): """ -def _get_reports_dir(prob): - # need this to work with older OM versions. - try: - return prob.get_reports_dir() - except Exception: - pass - - from openmdao.utils.reports_system import get_reports_dir - return str(pathlib.Path(get_reports_dir(prob)).joinpath(prob._name)) - - def timeseries_plots(solution_recorder_filename, simulation_record_file=None, plot_dir="plots", problem=None): """ @@ -358,7 +347,7 @@ def timeseries_plots(solution_recorder_filename, simulation_record_file=None, pl if problem is None: plot_dir_path = pathlib.Path.cwd().joinpath(plot_dir) else: - plot_dir_path = pathlib.Path(_get_reports_dir(problem)).joinpath(plot_dir) + plot_dir_path = pathlib.Path(problem.get_reports_dir()).joinpath(plot_dir) else: plot_dir_path = plot_dir From 04a39bc4c6a9bc7f8580e9c32f3200944217fe2f Mon Sep 17 00:00:00 2001 From: Bret Naylor Date: Thu, 28 Jul 2022 11:28:27 -0400 Subject: [PATCH 15/17] fixing issues with old openmdao versions --- dymos/utils/testing_utils.py | 2 +- dymos/visualization/linkage/report.py | 4 ++-- dymos/visualization/linkage/test/test_linkage_report_ui.py | 6 +++++- dymos/visualization/timeseries_plots.py | 7 ++++++- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/dymos/utils/testing_utils.py b/dymos/utils/testing_utils.py index 863d7eff2..b7d43a9e2 100644 --- a/dymos/utils/testing_utils.py +++ b/dymos/utils/testing_utils.py @@ -206,4 +206,4 @@ def _get_reports_dir(prob): return prob.get_reports_dir() from openmdao.utils.reports_system import get_reports_dir - return str(pathlib.Path(get_reports_dir()).joinpath(prob._name)) + return get_reports_dir(prob) diff --git a/dymos/visualization/linkage/report.py b/dymos/visualization/linkage/report.py index 30ade6608..8d1002cf1 100644 --- a/dymos/visualization/linkage/report.py +++ b/dymos/visualization/linkage/report.py @@ -39,10 +39,10 @@ def create_linkage_report(traj, output_file: str = 'linkage_report.html', import openmdao openmdao_dir = os.path.dirname(inspect.getfile(openmdao)) - vis_dir = os.path.join(openmdao_dir, "visualization/n2_viewer") + vis_dir = os.path.join(openmdao_dir, "visualization", "n2_viewer") dymos_dir = os.path.dirname(inspect.getfile(dm)) - reports_dir = os.path.join(dymos_dir, "visualization/linkage") + reports_dir = os.path.join(dymos_dir, "visualization", "linkage") HtmlPreprocessor(os.path.join(reports_dir, "report_template.html"), output_file, search_path=[vis_dir, reports_dir], allow_overwrite=True, var_dict=html_vars, diff --git a/dymos/visualization/linkage/test/test_linkage_report_ui.py b/dymos/visualization/linkage/test/test_linkage_report_ui.py index da4c64947..d9b8654b3 100644 --- a/dymos/visualization/linkage/test/test_linkage_report_ui.py +++ b/dymos/visualization/linkage/test/test_linkage_report_ui.py @@ -1,12 +1,15 @@ """Test Dymos Linkage Reports GUI using Playwright.""" import asyncio +from packaging.version import Version from playwright.async_api import async_playwright from aiounittest import async_test +import unittest from openmdao.utils.gui_testing_utils import _GuiTestCase import sys import openmdao.api as om +import openmdao from openmdao.utils.testing_utils import use_tempdirs from openmdao.utils.assert_utils import assert_near_equal @@ -18,6 +21,7 @@ # Windows specific event-loop policy & cmd asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) + my_loop = asyncio.get_event_loop() """ A set of toolbar tests that runs on each model. """ @@ -241,7 +245,7 @@ current_test = 1 - +@unittest.skipUnless(Version(openmdao.__version__) >= Version("3.19"), "reports API is too old") @use_tempdirs class dymos_linkage_gui_test_case(_GuiTestCase): def setUp(self): diff --git a/dymos/visualization/timeseries_plots.py b/dymos/visualization/timeseries_plots.py index 94c36346c..44f827716 100644 --- a/dymos/visualization/timeseries_plots.py +++ b/dymos/visualization/timeseries_plots.py @@ -347,7 +347,12 @@ def timeseries_plots(solution_recorder_filename, simulation_record_file=None, pl if problem is None: plot_dir_path = pathlib.Path.cwd().joinpath(plot_dir) else: - plot_dir_path = pathlib.Path(problem.get_reports_dir()).joinpath(plot_dir) + try: + repdir = problem.get_reports_dir() + except AttributeError: + from openmdao.utils.reports_system import get_reports_dir + repdir = get_reports_dir(problem) + plot_dir_path = pathlib.Path(repdir).joinpath(plot_dir) else: plot_dir_path = plot_dir From 798df06b2bb6029fdfcf64e3957583e95e8be34e Mon Sep 17 00:00:00 2001 From: Bret Naylor Date: Thu, 28 Jul 2022 13:51:32 -0400 Subject: [PATCH 16/17] cleanup --- .../test/test_grid_refinement.py | 2 + .../linkage/test/test_linkage_report_ui.py | 1 + dymos/visualization/timeseries_plots.py | 87 ++++++++++--------- 3 files changed, 48 insertions(+), 42 deletions(-) diff --git a/dymos/grid_refinement/test/test_grid_refinement.py b/dymos/grid_refinement/test/test_grid_refinement.py index 73853d96a..0a07981eb 100644 --- a/dymos/grid_refinement/test/test_grid_refinement.py +++ b/dymos/grid_refinement/test/test_grid_refinement.py @@ -3,6 +3,7 @@ import numpy as np import openmdao.api as om +from openmdao.utils.testing_utils import use_tempdirs import dymos as dm @@ -34,6 +35,7 @@ def compute_partials(self, inputs, partials): partials['J_dot', 'u'] = inputs['u'] +@use_tempdirs class TestGridRefinement(unittest.TestCase): def test_refine_hp_non_ode_rate_sources(self): diff --git a/dymos/visualization/linkage/test/test_linkage_report_ui.py b/dymos/visualization/linkage/test/test_linkage_report_ui.py index d9b8654b3..163ba1273 100644 --- a/dymos/visualization/linkage/test/test_linkage_report_ui.py +++ b/dymos/visualization/linkage/test/test_linkage_report_ui.py @@ -245,6 +245,7 @@ current_test = 1 + @unittest.skipUnless(Version(openmdao.__version__) >= Version("3.19"), "reports API is too old") @use_tempdirs class dymos_linkage_gui_test_case(_GuiTestCase): diff --git a/dymos/visualization/timeseries_plots.py b/dymos/visualization/timeseries_plots.py index 44f827716..fef19e138 100644 --- a/dymos/visualization/timeseries_plots.py +++ b/dymos/visualization/timeseries_plots.py @@ -275,49 +275,52 @@ def _bokeh_timeseries_plots(varnames, time_units, var_units, phase_names, phases save(plots) -# this function is defined in newer versions of openmdao, so remove this -# at some point later when we don't care about testing vs. older openmdao versions. -def image2html(imagefile, title='', alt=''): +try: + from openmdao.utils.file_utils import image2html +except ImportError: + # FIXME: this function is defined in newer versions of openmdao, so remove this + # at some point later when we don't care about testing vs. older (< 3.19) openmdao versions. + def image2html(imagefile, title='', alt=''): + """ + Wrap the given image for display as an html file. + + Returns an html syntax string that can be written to a file. + + Parameters + ---------- + imagefile : str + Name of image file to be displayed. + title : str + The page title. + alt : str + Set the alt text for the image. + + Returns + ------- + str + Content string to create an html file. + """ + return """ + + + + + + +

""" + title + "

" + f""" + {alt} + + + """ - Wrap the given image for display as an html file. - - Returns an html syntax string that can be written to a file. - - Parameters - ---------- - imagefile : str - Name of image file to be displayed. - title : str - The page title. - alt : str - Set the alt text for the image. - - Returns - ------- - str - Content string to create an html file. - """ - return """ - - - - - - -

""" + title + "

" + f""" -{alt} - - - -""" def timeseries_plots(solution_recorder_filename, simulation_record_file=None, plot_dir="plots", From b605b4f923332f4b64990fe01733e3bb2bc30b07 Mon Sep 17 00:00:00 2001 From: Bret Naylor Date: Thu, 28 Jul 2022 15:05:18 -0400 Subject: [PATCH 17/17] upped timeout for doc cells --- docs/dymos_book/_config.yml | 2 +- .../brachistochrone/brachistochrone_fbd.png | Bin 6705 -> 6711 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dymos_book/_config.yml b/docs/dymos_book/_config.yml index 4d289c8dc..0a2833a18 100644 --- a/docs/dymos_book/_config.yml +++ b/docs/dymos_book/_config.yml @@ -12,7 +12,7 @@ execute: execute_notebooks: force stderr_output: show allow_errors: false - timeout: 240 + timeout: 360 # Define the name of the latex output file for PDF builds latex: diff --git a/docs/dymos_book/examples/brachistochrone/brachistochrone_fbd.png b/docs/dymos_book/examples/brachistochrone/brachistochrone_fbd.png index accfa08d4a3b28ae27d3bf5a2995fd6e954c19de..451840fdd7f4f41477c539a86e5bb6ad05a44c55 100644 GIT binary patch delta 5975 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 LCqnffq~!kt5<&B) literal 6705 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