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

Trajectory results report plots now plot vector variables, respect the x_name argument, and avoid noisy plots when values are nearly constant. #1135

Merged
merged 11 commits into from
Dec 11, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
def brachistochrone_min_time(transcription='gauss-lobatto', num_segments=8, transcription_order=3,
grid_type='lgl', compressed=True, optimizer='SLSQP',
dynamic_simul_derivs=True, force_alloc_complex=False,
solve_segments=False, run_driver=True):
solve_segments=False, run_driver=True, simulate=False,
make_plots=False):
p = om.Problem(model=om.Group())

if optimizer == 'SNOPT':
Expand Down Expand Up @@ -78,9 +79,11 @@ def brachistochrone_min_time(transcription='gauss-lobatto', num_segments=8, tran
phase.set_state_val('v', [0, 9.9])
phase.set_control_val('theta', [5, 100])
phase.set_parameter_val('g', 9.80665)
p.run_model()
if run_driver:
p.run_driver()

dm.run_problem(p,
run_driver=run_driver,
simulate=simulate,
make_plots=make_plots)

return p

Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,35 @@
import importlib
import os
import pathlib
import unittest
from numpy.testing import assert_almost_equal

import sys

from openmdao.utils.general_utils import set_pyoptsparse_opt, printoptions
from openmdao.utils.testing_utils import use_tempdirs
from openmdao.utils.testing_utils import use_tempdirs, set_env_vars_context
from openmdao.utils.tests.test_hooks import hooks_active

import dymos as dm
import dymos.examples.brachistochrone.test.ex_brachistochrone_vector_states as ex_brachistochrone_vs
from dymos.utils.testing_utils import assert_check_partials
from dymos.utils.testing_utils import assert_check_partials, _get_reports_dir

bokeh_available = importlib.util.find_spec('bokeh') is not None

OPT, OPTIMIZER = set_pyoptsparse_opt('SNOPT')


@use_tempdirs
class TestBrachistochroneVectorStatesExample(unittest.TestCase):

def setUp(self):
self.testflo_running = os.environ.pop('TESTFLO_RUNNING', None)

def tearDown(self):
# restore what was there before running the test
if self.testflo_running is not None:
os.environ['TESTFLO_RUNNING'] = self.testflo_running

def assert_results(self, p):
t_initial = p.get_val('traj0.phase0.timeseries.time')[0]
t_final = p.get_val('traj0.phase0.timeseries.time')[-1]
Expand Down Expand Up @@ -45,7 +62,7 @@ def assert_results(self, p):

def assert_partials(self, p):
with printoptions(linewidth=1024, edgeitems=100):
cpd = p.check_partials(method='cs', compact_print=True)
cpd = p.check_partials(method='cs', compact_print=True, out_stream=None)
assert_check_partials(cpd)

def test_ex_brachistochrone_vs_radau_compressed(self):
Expand Down Expand Up @@ -97,6 +114,37 @@ def test_ex_brachistochrone_vs_birkhoff(self):
self.assert_results(p)
self.assert_partials(p)

@unittest.skipIf(not bokeh_available, 'bokeh unavailable')
@hooks_active
def test_bokeh_plots(self):
with set_env_vars_context(OPENMDAO_REPORTS='1'):
with dm.options.temporary(plots='bokeh'):
p = ex_brachistochrone_vs.brachistochrone_min_time(transcription='radau-ps',
compressed=False,
force_alloc_complex=True,
run_driver=True,
simulate=True,
make_plots=True)

self.assert_results(p)
self.assert_partials(p)

html_file = pathlib.Path(_get_reports_dir(p)) / 'traj0_results_report.html'
self.assertTrue(html_file.exists(), msg=f'{html_file} does not exist!')

with open(html_file) as f:
html_data = f.read()

expected_labels = ['"axis_label":"pos[0] (m)"',
'"axis_label":"pos[1] (m)"',
'"axis_label":"v (m/s)"',
'"axis_label":"theta (deg)"']

for label in expected_labels:
self.assertIn(label, html_data)

self.assertNotIn('"axis_label":"pos (m)"', html_data)


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from openmdao.utils.general_utils import set_pyoptsparse_opt
from openmdao.utils.testing_utils import use_tempdirs, set_env_vars_context


import dymos as dm
from dymos.examples.finite_burn_orbit_raise.finite_burn_orbit_raise_problem import two_burn_orbit_raise_problem
from dymos.utils.testing_utils import _get_reports_dir
Expand Down
100 changes: 80 additions & 20 deletions dymos/examples/min_time_climb/test/test_ex_min_time_climb.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import importlib
import os
import unittest
import numpy as np
from numpy.polynomial import Polynomial as P
Expand All @@ -9,18 +11,21 @@

import openmdao.api as om
from openmdao.utils.assert_utils import assert_near_equal
from dymos.utils.testing_utils import assert_timeseries_near_equal
from dymos.utils.testing_utils import assert_timeseries_near_equal, _get_reports_dir
from dymos.utils.introspection import get_promoted_vars

import dymos as dm
from dymos.examples.min_time_climb.min_time_climb_ode import MinTimeClimbODE
from dymos.utils.misc import om_version
from openmdao.utils.testing_utils import use_tempdirs, require_pyoptsparse
from openmdao.utils.testing_utils import use_tempdirs, require_pyoptsparse, set_env_vars_context


bokeh_available = importlib.util.find_spec('bokeh') is not None


def min_time_climb(optimizer='SLSQP', num_seg=3, transcription='gauss-lobatto',
transcription_order=3, force_alloc_complex=False, add_rate=False, time_name='time',
simulate=True, path_constraints=True):
simulate=True, path_constraints=True, make_plots=False):

p = om.Problem(model=om.Group())

Expand Down Expand Up @@ -139,7 +144,7 @@ def min_time_climb(optimizer='SLSQP', num_seg=3, transcription='gauss-lobatto',
with np.printoptions(linewidth=1024, edgeitems=1024):
p.check_partials(compact_print=False, method='cs', show_only_incorrect=True, out_stream=f)

dm.run_problem(p, simulate=simulate, make_plots=False)
dm.run_problem(p, simulate=simulate, make_plots=make_plots, plot_kwargs={'x_name': time_name})

return p

Expand Down Expand Up @@ -293,35 +298,90 @@ def test_results_birkhoff(self):

self._test_timeseries_units(p)


@use_tempdirs
class TestMinTimeClimbWithReports(TestMinTimeClimb):

def setUp(self):
self.testflo_running = os.environ.pop('TESTFLO_RUNNING', None)

def tearDown(self):
# restore what was there before running the test
if self.testflo_running is not None:
os.environ['TESTFLO_RUNNING'] = self.testflo_running

def _test_traj_results_report(self, p):
html_file = _get_reports_dir(p) / 'traj_results_report.html'
self.assertTrue(html_file.exists(), msg=f'{html_file} does not exist!')

with open(html_file) as f:
html_data = f.read()

expected_labels = ['"axis_label":"alpha (deg)"',
'"axis_label":"t (s)"',
'"axis_label":"CD (None)"',
'"axis_label":"CD0 (None)"',
'"axis_label":"CL (None)"',
'"axis_label":"CLa (None)"',
'"axis_label":"f_drag (N)"',
'"axis_label":"f_lift (lbf)"',
'"axis_label":"gam (rad)"',
'"axis_label":"h (m)"',
'"axis_label":"kappa (None)"',
'"axis_label":"m (kg)"',
'"axis_label":"m_dot (kg/s)"',
'"axis_label":"mach (None)"',
'"axis_label":"mach_rate (None)"',
'"axis_label":"q (N/m**2)"',
'"axis_label":"r (m)"',
'"axis_label":"thrust (lbf)"',
'"axis_label":"v (m/s)"']

for label in expected_labels:
self.assertIn(label, html_data)

@require_pyoptsparse(optimizer='IPOPT')
@unittest.skipIf(not bokeh_available, 'bokeh is not available')
def test_results_gauss_lobatto_renamed_time(self):
NUM_SEG = 12
ORDER = 3
p = min_time_climb(optimizer='IPOPT', num_seg=NUM_SEG, transcription_order=ORDER,
transcription='gauss-lobatto', add_rate=True, time_name='t')
with set_env_vars_context(OPENMDAO_REPORTS='1'):
with dm.options.temporary(plots='bokeh'):
NUM_SEG = 12
ORDER = 3
p = min_time_climb(optimizer='IPOPT', num_seg=NUM_SEG, transcription_order=ORDER,
force_alloc_complex=True,
transcription='gauss-lobatto', add_rate=True, time_name='t',
make_plots=True)

self._test_results(p, time_name='t')
self._test_results(p, time_name='t')

self._test_wilcard_outputs(p)
self._test_wilcard_outputs(p)

self._test_timeseries_units(p)
self._test_timeseries_units(p)

self._test_mach_rate(p, time_name='t')

self._test_mach_rate(p, time_name='t')
self._test_traj_results_report(p)

@require_pyoptsparse(optimizer='IPOPT')
@unittest.skipIf(not bokeh_available, 'bokeh is not available')
def test_results_radau_renamed_time(self):
NUM_SEG = 15
ORDER = 3
p = min_time_climb(optimizer='IPOPT', num_seg=NUM_SEG, transcription_order=ORDER,
transcription='radau-ps', add_rate=True, time_name='t', force_alloc_complex=True)
with set_env_vars_context(OPENMDAO_REPORTS='1'):
with dm.options.temporary(plots='bokeh'):
NUM_SEG = 15
ORDER = 3
p = min_time_climb(optimizer='IPOPT', num_seg=NUM_SEG, transcription_order=ORDER,
transcription='radau-ps', add_rate=True, time_name='t',
force_alloc_complex=True, make_plots=True)

self._test_results(p, time_name='t')
self._test_results(p, time_name='t')

self._test_wilcard_outputs(p)
self._test_wilcard_outputs(p)

self._test_timeseries_units(p)
self._test_timeseries_units(p)

self._test_mach_rate(p, plot=False, time_name='t')

self._test_mach_rate(p, plot=False, time_name='t')
self._test_traj_results_report(p)


if __name__ == '__main__': # pragma: no cover
Expand Down
7 changes: 4 additions & 3 deletions dymos/run_problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,6 @@ def run_problem(problem, refine_method='hp', refine_iteration_limit=0, run_drive
sims[subsys.pathname] = sim_prob

if make_plots:

if om_version()[0] > (3, 34, 2):
outputs_dir = problem.get_outputs_dir()
if os.sep in str(solution_record_file):
Expand All @@ -142,13 +141,15 @@ def run_problem(problem, refine_method='hp', refine_iteration_limit=0, run_drive
_sol_record_file = solution_record_file
_sim_record_file = None if not simulate else simulation_record_file

_plot_kwargs = plot_kwargs if plot_kwargs is not None else {}

if dymos_options['plots'] == 'bokeh':
from dymos.visualization.timeseries.bokeh_timeseries_report import make_timeseries_report
make_timeseries_report(prob=problem,
solution_record_file=_sol_record_file,
simulation_record_file=_sim_record_file)
simulation_record_file=_sim_record_file,
**_plot_kwargs)
else:
_plot_kwargs = plot_kwargs if plot_kwargs is not None else {}
plots_dir = problem.get_reports_dir() / 'plots'
timeseries_plots(_sol_record_file,
simulation_record_file=_sim_record_file,
Expand Down
33 changes: 32 additions & 1 deletion dymos/test/test_pycodestyle.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import os
import pathlib
import subprocess
import sys
import unittest

Expand Down Expand Up @@ -33,6 +35,33 @@ def _discover_python_files(path):
return python_files


def _get_tracked_python_files(git_root):
"""
Given a git root directory

Parameters
----------
git_root : str or Path
The root directory of the git repository.

Returns
-------
set
Python files in the given git directory that a tracked by git.
"""
_git_root = str(git_root)
file_list = _discover_python_files(_git_root)
untracked_set = set(
subprocess.run(
['git', 'ls-files', _git_root, '--exclude-standard', '--others'],
stdout=subprocess.PIPE,
text=True
).stdout.splitlines()
)
untracked_set = {str(pathlib.Path(f).absolute()) for f in untracked_set if f.endswith('.py')}
return set(file_list) - untracked_set


@unittest.skipIf(pycodestyle is None, "This test requires pycodestyle")
class TestPyCodeStyle(unittest.TestCase):

Expand All @@ -47,6 +76,8 @@ def test_pycodestyle(self):
dymos_path = os.path.split(dymos.__file__)[0]
pyfiles = _discover_python_files(dymos_path)

files_to_check = _get_tracked_python_files(dymos_path)

style = pycodestyle.StyleGuide(ignore=['E226', # missing whitespace around arithmetic operator
'E241', # multiple spaces after ','
'W504', # line break after binary operator
Expand All @@ -65,7 +96,7 @@ def test_pycodestyle(self):
try:
sys.stdout = buff_out = StringIO()
sys.stdout = buff_err = StringIO()
report = style.check_files(pyfiles)
report = style.check_files(files_to_check)
finally:
sys.stdout = save_out
sys.stderr = save_err
Expand Down
Loading
Loading