From 9fec0166e406c1532023ee1d348ae6f5d8f58a66 Mon Sep 17 00:00:00 2001 From: Chad Baker Date: Tue, 2 May 2023 11:51:08 -0600 Subject: [PATCH 1/9] removed unnecessary use of `IPython` --- fastsim/docs/demo.py | 7 ------- fastsim/docs/fusion_thermal_demo.py | 3 --- 2 files changed, 10 deletions(-) diff --git a/fastsim/docs/demo.py b/fastsim/docs/demo.py index 0085708a..179d9396 100644 --- a/fastsim/docs/demo.py +++ b/fastsim/docs/demo.py @@ -1,7 +1,5 @@ # To add a new cell, type '# %%' # To add a new markdown cell, type '# %% [markdown]' -# %% -from IPython import get_ipython # %% [markdown] # # FASTSim Demonstration @@ -32,10 +30,6 @@ # import seaborn as sns # sns.set(font_scale=2, style='whitegrid') -if not __name__ == "__main__": - get_ipython().run_line_magic('matplotlib', 'inline') - - # local modules import fastsim as fsim # importlib.reload(simdrive) @@ -808,7 +802,6 @@ ax.set_xlabel('Cycle Time [s]', weight='bold') ax.set_ylabel('Engine Input Power [kW]', weight='bold', color='xkcd:bluish') ax.tick_params('y', colors='xkcd:bluish') - ax2.set_ylabel('Speed [MPH]', weight='bold', color='xkcd:pale red') ax2.grid(False) ax2.tick_params('y', colors='xkcd:pale red') diff --git a/fastsim/docs/fusion_thermal_demo.py b/fastsim/docs/fusion_thermal_demo.py index 826b8386..cc0a1752 100644 --- a/fastsim/docs/fusion_thermal_demo.py +++ b/fastsim/docs/fusion_thermal_demo.py @@ -11,9 +11,6 @@ from pathlib import Path import os import sys -from IPython import get_ipython -get_ipython().run_line_magic('matplotlib', 'inline') - # %% sns.set() From a2a14d9c5dc0dba54b137fece4be9e2992f5808d Mon Sep 17 00:00:00 2001 From: Chad Baker Date: Tue, 2 May 2023 11:51:55 -0600 Subject: [PATCH 2/9] commented out plot saving code that depends on possibly non-existent folder --- fastsim/docs/demo.py | 4 ++-- fastsim/docs/fusion_thermal_demo.py | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/fastsim/docs/demo.py b/fastsim/docs/demo.py index 179d9396..925b0d4f 100644 --- a/fastsim/docs/demo.py +++ b/fastsim/docs/demo.py @@ -817,6 +817,6 @@ (drag_coef, wheel_rr_coef) = abc_to_drag_coeffs(test_veh, 25.91, 0.1943, 0.01796, simdrive_optimize=True) # %% -print(f'Drag Coefficient: {drag_coef}') -print(f'Wheel Rolling Resistance Coefficient: {wheel_rr_coef}') +print(f'Drag Coefficient: {drag_coef:.3g}') +print(f'Wheel Rolling Resistance Coefficient: {wheel_rr_coef:.3g}') # %% diff --git a/fastsim/docs/fusion_thermal_demo.py b/fastsim/docs/fusion_thermal_demo.py index cc0a1752..47948275 100644 --- a/fastsim/docs/fusion_thermal_demo.py +++ b/fastsim/docs/fusion_thermal_demo.py @@ -99,8 +99,8 @@ ax[-1].set_xlabel("Time") ax[-1].set_ylabel("Speed [mph]") plt.tight_layout() -plt.savefig("plots/fusion udds cold start.png") -plt.savefig("plots/fusion udds cold start.svg") +# plt.savefig("plots/fusion udds cold start.png") +# plt.savefig("plots/fusion udds cold start.svg") # %% Case with cabin cooling @@ -147,8 +147,8 @@ ax[-1].set_xlabel("Time") ax[-1].set_ylabel("Speed [mph]") plt.tight_layout() -plt.savefig("plots/fusion udds hot start.png") -plt.savefig("plots/fusion udds hot start.svg") +# plt.savefig("plots/fusion udds hot start.png") +# plt.savefig("plots/fusion udds hot start.svg") # %% sweep ambient @@ -192,6 +192,6 @@ ax.set_xlabel('Ambient/Init. Temp [°C]') ax.set_ylabel('Fuel Economy [mpg]') plt.tight_layout() -plt.savefig("plots/fusion FE vs temp sweep.png") -plt.savefig("plots/fusion FE vs temp sweep.svg") +# plt.savefig("plots/fusion FE vs temp sweep.png") +# plt.savefig("plots/fusion FE vs temp sweep.svg") # %% From d398987f7bf41b7149b1aa111265644a9e8ff194 Mon Sep 17 00:00:00 2001 From: Chad Baker Date: Tue, 2 May 2023 11:52:10 -0600 Subject: [PATCH 3/9] improved documentation --- fastsim/calibration.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/fastsim/calibration.py b/fastsim/calibration.py index 45acc261..11810448 100644 --- a/fastsim/calibration.py +++ b/fastsim/calibration.py @@ -105,15 +105,20 @@ def get_errors( # or if return_mods is True Dict[str, fsim.simdrive.SimDrive], ]: - # TODO: should return type instead be `Dict[str, Dict[str, float]] | Tuple[Dict[str, Dict[str, float]], Dict[str, fsim.simdrive.SimDrive]]` - # This would make `from typing import Union` unnecessary """ Calculate model errors w.r.t. test data for each element in dfs/models for each objective. Arguments: ---------- + - sim_drives: dictionary with user-defined keys and SimDrive or SimDriveHot instances - return_mods: if true, also returns dict of solved models - - plot: if true, plots objectives + - plot: if true, plots objectives using matplotlib.pyplot + - plot_save_dir: directory in which to save plots. If `None` (default), plots are not saved. + - plot_perc_err: whether to include % error axes in plots + - show: whether to show matplotlib.pyplot plots + - fontsize: plot font size """ + # TODO: should return type instead be `Dict[str, Dict[str, float]] | Tuple[Dict[str, Dict[str, float]], Dict[str, fsim.simdrive.SimDrive]]` + # This would make `from typing import Union` unnecessary objectives = {} solved_mods = {} From 0dd475a5f5d9973649939e10aef14aab1a67c5a1 Mon Sep 17 00:00:00 2001 From: Chad Baker Date: Wed, 3 May 2023 11:54:22 -0600 Subject: [PATCH 4/9] refactored plotting into functions still not working because I did not have the cycle files! added assert to ensure zero-length dataframe will fail --- fastsim/calibration.py | 206 +++++++++++++++++++---------- fastsim/docs/fusion_thermal_cal.py | 31 +++-- 2 files changed, 158 insertions(+), 79 deletions(-) diff --git a/fastsim/calibration.py b/fastsim/calibration.py index 11810448..fc4127d0 100644 --- a/fastsim/calibration.py +++ b/fastsim/calibration.py @@ -12,6 +12,7 @@ from pymoo.termination.default import DefaultMultiObjectiveTermination as DMOT from pymoo.core.problem import Problem, ElementwiseProblem, LoopedElementwiseEvaluation, StarmapParallelization from pymoo.algorithms.moo.nsga3 import NSGA3 +from pymoo.algorithms.moo.nsga2 import NSGA2 from pymoo.util.ref_dirs import get_reference_directions from pymoo.algorithms.base.genetic import GeneticAlgorithm from pymoo.optimize import minimize @@ -22,6 +23,8 @@ import json import time import matplotlib.pyplot as plt +from matplotlib.figure import Figure +from matplotlib.axes import Axes import numpy as np # local -- expects rust-port version of fastsim so make sure this is in the env @@ -84,6 +87,8 @@ class ModelObjectives(object): # calculated in __post_init__ n_obj: int = None + # whether to use simdrive hot + # TOOD: consider passing the type to be used rather than this boolean in the future use_simdrivehot: bool = False def __post_init__(self): @@ -100,6 +105,7 @@ def get_errors( plot_perc_err: Optional[bool] = True, show: Optional[bool] = False, fontsize: Optional[float] = 12, + plotly: Optional[bool] = False, ) -> Union[ Dict[str, Dict[str, float]], # or if return_mods is True @@ -116,6 +122,7 @@ def get_errors( - plot_perc_err: whether to include % error axes in plots - show: whether to show matplotlib.pyplot plots - fontsize: plot font size + - plotly: whether to generate plotly plots, which can be opened manually in a browser window """ # TODO: should return type instead be `Dict[str, Dict[str, float]] | Tuple[Dict[str, Dict[str, float]], Dict[str, fsim.simdrive.SimDrive]]` # This would make `from typing import Union` unnecessary @@ -128,33 +135,31 @@ def get_errors( t0 = time.perf_counter() sim_drive = sim_drive.copy() # TODO: do we need this? sim_drive.sim_drive() + t1 = time.perf_counter() + if self.verbose: + print(f"Time to simulate {key}: {t1 - t0:.3g}") objectives[key] = {} if return_mods or plot: solved_mods[key] = sim_drive.copy() - if plot or plot_save_dir: - Path(plot_save_dir).mkdir(exist_ok=True, parents=True) - if not self.use_simdrivehot: - time_hr = np.array(sim_drive.cyc.time_s) / 3_600 - mph_ach = sim_drive.mph_ach - else: - time_hr = np.array(sim_drive.sd.cyc.time_s) / 3_600 - mph_ach = sim_drive.sd.mph_ach - ax_multiplier = 2 if plot_perc_err else 1 - fig, ax = plt.subplots( - len(self.obj_names) * ax_multiplier + 1, 1, sharex=True, figsize=(12, 8), - ) - plt.suptitle(f"trip: {key}", fontsize=fontsize) - ax[-1].plot( - time_hr, - mph_ach, - ) - ax[-1].set_xlabel('Time [hr]', fontsize=fontsize) - ax[-1].set_ylabel('Speed [mph]', fontsize=fontsize) - - t1 = time.perf_counter() - if self.verbose: - print(f"Time to simulate {key}: {t1 - t0:.3g}") + ax_multiplier = 2 if plot_perc_err else 1 + # extract speed trace for plotting + if not self.use_simdrivehot: + time_hr = np.array(sim_drive.cyc.time_s) / 3_600 + mph_ach = sim_drive.mph_ach + else: + time_hr = np.array(sim_drive.sd.cyc.time_s) / 3_600 + mph_ach = sim_drive.sd.mph_ach + fig, ax = self.setup_plots( + plot, + plot_save_dir, + sim_drive, + fontsize, + key, + ax_multiplier, + time_hr, + mph_ach, + ) # loop through the objectives for each trip for i_obj, obj in enumerate(self.obj_names): @@ -185,51 +190,24 @@ def get_errors( else: pass # TODO: write else block for objective minimization - - if plot or plot_save_dir: - # this code needs to be cleaned up - # raw signals - ax[i_obj * ax_multiplier].set_title( - f"error: {objectives[key][obj[0]]:.3g}", fontsize=fontsize) - ax[i_obj * ax_multiplier].plot(time_hr, - mod_sig, label='mod') - ax[i_obj * ax_multiplier].plot(time_hr, - ref_sig, - linestyle='--', - label="exp", - ) - ax[i_obj * - ax_multiplier].set_ylabel(obj[0], fontsize=fontsize) - ax[i_obj * ax_multiplier].legend(fontsize=fontsize) - - if plot_perc_err: - # error - if "deg_c" in mod_path: - perc_err = (mod_sig - ref_sig) / \ - (ref_sig + 273.15) * 100 - else: - perc_err = (mod_sig - ref_sig) / ref_sig * 100 - # clean up inf and nan - perc_err[np.where(perc_err == np.inf)[0][:]] = 0.0 - # trim off the first few bits of junk - perc_err[np.where(perc_err > 500)[0][:]] = 0.0 - ax[i_obj * ax_multiplier + 1].plot( - time_hr, - perc_err - ) - ax[i_obj * ax_multiplier + - 1].set_ylabel(obj[0] + "\n%Err", fontsize=fontsize) - ax[i_obj * ax_multiplier + 1].set_ylim([-20, 20]) - - if show: - plt.show() - - if plot_save_dir: - if not Path(plot_save_dir).exists(): - Path(plot_save_dir).mkdir() - plt.tight_layout() - plt.savefig(Path(plot_save_dir) / f"{key}.svg") - plt.savefig(Path(plot_save_dir) / f"{key}.png") + + update_plots( + plot, + plot_save_dir, + ax, + i_obj, + ax_multiplier, + objectives, + key, + obj, + fontsize, + time_hr, + mod_sig, + ref_sig, + plot_perc_err, + mod_path, + show, + ) t2 = time.perf_counter() if self.verbose: @@ -272,6 +250,98 @@ def update_params(self, xs: List[Any]): if self.verbose: print(f"Time to update params: {t1 - t0:.3g} s") return sim_drives + + def setup_plots( + self, + plot: bool, + plot_save_dir: Path, + sim_drive: Union[fsr.RustSimDrive, fsr.SimDriveHot], + fontsize: int, + key: str, + ax_multiplier: int, + time_hr: float, + mph_ach: float, + ) -> Tuple[Figure, Axes]: + if plot or plot_save_dir: + # make directory if it doesn't exist + Path(plot_save_dir).mkdir(exist_ok=True, parents=True) + fig, ax = plt.subplots( + len(self.obj_names) * ax_multiplier + 1, 1, sharex=True, figsize=(12, 8), + ) + plt.suptitle(f"trip: {key}", fontsize=fontsize) + ax[-1].plot( + time_hr, + mph_ach, + ) + ax[-1].set_xlabel('Time [hr]', fontsize=fontsize) + ax[-1].set_ylabel('Speed [mph]', fontsize=fontsize) + return fig, ax + else: + return (None, None) + +def update_plots( + plot: bool, + plot_save_dir: Path, + ax: Axes, + i_obj: int, + ax_multiplier: int, + objectives: Dict, + key: str, + obj: Any, # need to check type on this + fontsize: int, + time_hr: np.ndarray, + mod_sig: np.ndarray, + ref_sig: np.ndarray, + plot_perc_err: bool, + mod_path: str, + show: bool, +): + if plot or plot_save_dir: + # this code needs to be cleaned up + # raw signals + ax[i_obj * ax_multiplier].set_title( + f"error: {objectives[key][obj[0]]:.3g}", fontsize=fontsize) + ax[i_obj * ax_multiplier].plot(time_hr, + mod_sig, label='mod') + ax[i_obj * ax_multiplier].plot(time_hr, + ref_sig, + linestyle='--', + label="exp", + ) + ax[i_obj * + ax_multiplier].set_ylabel(obj[0], fontsize=fontsize) + ax[i_obj * ax_multiplier].legend(fontsize=fontsize) + + if plot_perc_err: + # error + if "deg_c" in mod_path: + perc_err = (mod_sig - ref_sig) / \ + (ref_sig + 273.15) * 100 + else: + perc_err = (mod_sig - ref_sig) / ref_sig * 100 + # clean up inf and nan + perc_err[np.where(perc_err == np.inf)[0][:]] = 0.0 + # trim off the first few bits of junk + perc_err[np.where(perc_err > 500)[0][:]] = 0.0 + ax[i_obj * ax_multiplier + 1].plot( + time_hr, + perc_err + ) + ax[i_obj * ax_multiplier + + 1].set_ylabel(obj[0] + "\n%Err", fontsize=fontsize) + ax[i_obj * ax_multiplier + 1].set_ylim([-20, 20]) + + if show: + plt.show() + + if plot_save_dir: + if not Path(plot_save_dir).exists(): + Path(plot_save_dir).mkdir() + plt.tight_layout() + plt.savefig(Path(plot_save_dir) / f"{key}.svg") + plt.savefig(Path(plot_save_dir) / f"{key}.png") + + @dataclass diff --git a/fastsim/docs/fusion_thermal_cal.py b/fastsim/docs/fusion_thermal_cal.py index 1f1de7da..8ac28d45 100644 --- a/fastsim/docs/fusion_thermal_cal.py +++ b/fastsim/docs/fusion_thermal_cal.py @@ -10,6 +10,8 @@ import fastsimrust as fsr +use_nsga2 = True + def load_data() -> Dict[str, pd.DataFrame]: # full data dfs_raw = dict() @@ -37,6 +39,7 @@ def load_data() -> Dict[str, pd.DataFrame]: dfs_raw[file.stem], rate_vars=('Eng_FuelFlow_Direct[cc/s]') ) + assert len(dfs) > 0 return dfs @@ -176,17 +179,23 @@ def get_cal_and_val_objs(use_simdrivehot: bool): if run_minimize: print("Starting calibration.") - - algorithm = fsim.calibration.NSGA3( - ref_dirs=fsim.calibration.get_reference_directions( - "energy", - n_dim=cal_objectives.n_obj, # must be at least cal_objectives.n_obj - n_points=pop_size, # must be at least pop_size - ), - # size of each population - pop_size=pop_size, - sampling=fsim.calibration.LHS(), - ) + if use_nsga2: + algorithm = fsim.calibration.NSGA2( + # size of each population + pop_size=pop_size, + sampling=fsim.calibration.LHS(), + ) + else: + algorithm = fsim.calibration.NSGA3( + ref_dirs=fsim.calibration.get_reference_directions( + "energy", + n_dim=cal_objectives.n_obj, # must be at least cal_objectives.n_obj + n_points=pop_size, # must be at least pop_size + ), + # size of each population + pop_size=pop_size, + sampling=fsim.calibration.LHS(), + ) termination = fsim.calibration.DMOT( # max number of generations, default of 10 is very small n_max_gen=n_max_gen, From c6844a5a90754fd1c2741a9422deb646269ad939 Mon Sep 17 00:00:00 2001 From: Chad Baker Date: Wed, 3 May 2023 13:15:52 -0600 Subject: [PATCH 5/9] fusion thermal cal works! --- fastsim/calibration.py | 24 ++++++++++++------------ fastsim/docs/fusion_thermal_cal.py | 5 +++-- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/fastsim/calibration.py b/fastsim/calibration.py index fc4127d0..03832b19 100644 --- a/fastsim/calibration.py +++ b/fastsim/calibration.py @@ -6,7 +6,7 @@ import argparse # pymoo -from pymoo.util.display.output import Output +from pymoo.util.display.output import Output from pymoo.util.display.column import Column from pymoo.operators.sampling.lhs import LatinHypercubeSampling as LHS from pymoo.termination.default import DefaultMultiObjectiveTermination as DMOT @@ -85,7 +85,7 @@ class ModelObjectives(object): verbose: bool = False # calculated in __post_init__ - n_obj: int = None + n_obj: Optional[int] = None # whether to use simdrive hot # TOOD: consider passing the type to be used rather than this boolean in the future @@ -127,14 +127,14 @@ def get_errors( # TODO: should return type instead be `Dict[str, Dict[str, float]] | Tuple[Dict[str, Dict[str, float]], Dict[str, fsim.simdrive.SimDrive]]` # This would make `from typing import Union` unnecessary - objectives = {} - solved_mods = {} + objectives: Dict = {} + solved_mods: Dict = {} # loop through all the provided trips for ((key, df_exp), sim_drive) in zip(self.dfs.items(), sim_drives.values()): t0 = time.perf_counter() sim_drive = sim_drive.copy() # TODO: do we need this? - sim_drive.sim_drive() + sim_drive.sim_drive() # type: ignore t1 = time.perf_counter() if self.verbose: print(f"Time to simulate {key}: {t1 - t0:.3g}") @@ -145,11 +145,11 @@ def get_errors( ax_multiplier = 2 if plot_perc_err else 1 # extract speed trace for plotting if not self.use_simdrivehot: - time_hr = np.array(sim_drive.cyc.time_s) / 3_600 - mph_ach = sim_drive.mph_ach + time_hr = np.array(sim_drive.cyc.time_s) / 3_600 # type: ignore + mph_ach = sim_drive.mph_ach # type: ignore else: - time_hr = np.array(sim_drive.sd.cyc.time_s) / 3_600 - mph_ach = sim_drive.sd.mph_ach + time_hr = np.array(sim_drive.sd.cyc.time_s) / 3_600 # type: ignore + mph_ach = sim_drive.sd.mph_ach # type: ignore fig, ax = self.setup_plots( plot, plot_save_dir, @@ -245,7 +245,7 @@ def update_params(self, xs: List[Any]): else: veh = sim_drives[key].sd.veh veh.set_derived() - sim_drives[key].sd.veh = veh + fsim.utils.set_attr_with_path(sim_drives[key], "sd.veh", veh) t1 = time.perf_counter() if self.verbose: print(f"Time to update params: {t1 - t0:.3g} s") @@ -256,7 +256,7 @@ def setup_plots( plot: bool, plot_save_dir: Path, sim_drive: Union[fsr.RustSimDrive, fsr.SimDriveHot], - fontsize: int, + fontsize: float, key: str, ax_multiplier: int, time_hr: float, @@ -403,7 +403,7 @@ def run_minimize( copy_algorithm: bool = False, copy_termination: bool = False, save_history: bool = False, - save_path: Optional[str] = Path("pymoo_res/"), + save_path: Union[Path, str] = Path("pymoo_res/"), ): print("`run_minimize` starting at") fsim.utils.print_dt() diff --git a/fastsim/docs/fusion_thermal_cal.py b/fastsim/docs/fusion_thermal_cal.py index 8ac28d45..9954765b 100644 --- a/fastsim/docs/fusion_thermal_cal.py +++ b/fastsim/docs/fusion_thermal_cal.py @@ -124,7 +124,8 @@ def get_cal_and_val_objs(use_simdrivehot: bool): dfs=dfs_cal, obj_names=obj_names, params=params, - verbose=False + use_simdrivehot=use_simdrivehot, + verbose=False, ) # to ensure correct key order @@ -135,7 +136,7 @@ def get_cal_and_val_objs(use_simdrivehot: bool): obj_names=obj_names, params=params, use_simdrivehot=use_simdrivehot, - verbose=False + verbose=False, ) return cal_objectives, val_objectives, params_bounds From a563eb3ef89f07782b4ff52aa0cf194b8c2c8fe7 Mon Sep 17 00:00:00 2001 From: Chad Baker Date: Wed, 3 May 2023 14:41:39 -0600 Subject: [PATCH 6/9] plotly is working well --- fastsim/calibration.py | 115 +++++++++++++++++++----- fastsim/docs/fusion_thermal_cal.py | 13 +-- fastsim/docs/fusion_thermal_cal_post.py | 2 + pyproject.toml | 2 +- 4 files changed, 106 insertions(+), 26 deletions(-) diff --git a/fastsim/calibration.py b/fastsim/calibration.py index 03832b19..3f8709b0 100644 --- a/fastsim/calibration.py +++ b/fastsim/calibration.py @@ -25,6 +25,8 @@ import matplotlib.pyplot as plt from matplotlib.figure import Figure from matplotlib.axes import Axes +import plotly.graph_objects as go +from plotly.subplots import make_subplots import numpy as np # local -- expects rust-port version of fastsim so make sure this is in the env @@ -102,7 +104,7 @@ def get_errors( return_mods: Optional[bool] = False, plot: Optional[bool] = False, plot_save_dir: Optional[str] = None, - plot_perc_err: Optional[bool] = True, + plot_perc_err: Optional[bool] = False, show: Optional[bool] = False, fontsize: Optional[float] = 12, plotly: Optional[bool] = False, @@ -150,8 +152,8 @@ def get_errors( else: time_hr = np.array(sim_drive.sd.cyc.time_s) / 3_600 # type: ignore mph_ach = sim_drive.sd.mph_ach # type: ignore - fig, ax = self.setup_plots( - plot, + fig, ax, pltly_fig = self.setup_plots( + plot or show, plot_save_dir, sim_drive, fontsize, @@ -159,6 +161,7 @@ def get_errors( ax_multiplier, time_hr, mph_ach, + plotly, ) # loop through the objectives for each trip @@ -192,9 +195,8 @@ def get_errors( # TODO: write else block for objective minimization update_plots( - plot, - plot_save_dir, ax, + pltly_fig, i_obj, ax_multiplier, objectives, @@ -208,6 +210,17 @@ def get_errors( mod_path, show, ) + + if plot_save_dir is not None: + if not Path(plot_save_dir).exists(): + Path(plot_save_dir).mkdir(exist_ok=True, parents=True) + if ax is not None: + plt.savefig(Path(plot_save_dir) / f"{key}.svg") + plt.savefig(Path(plot_save_dir) / f"{key}.png") + plt.tight_layout() + if pltly_fig is not None: + pltly_fig.update_layout(showlegend=True) + pltly_fig.write_html(str(Path(plot_save_dir) / f"{key}.html")) t2 = time.perf_counter() if self.verbose: @@ -254,15 +267,43 @@ def update_params(self, xs: List[Any]): def setup_plots( self, plot: bool, - plot_save_dir: Path, + plot_save_dir: Optional[Path], sim_drive: Union[fsr.RustSimDrive, fsr.SimDriveHot], fontsize: float, key: str, ax_multiplier: int, time_hr: float, mph_ach: float, - ) -> Tuple[Figure, Axes]: - if plot or plot_save_dir: + plotly: bool, + ) -> Tuple[Figure, Axes, go.Figure]: + rows = len(self.obj_names) * ax_multiplier + 1 + + if plotly and (plot_save_dir is not None): + pltly_fig = make_subplots( + rows=rows, + cols=1, + shared_xaxes=True, + vertical_spacing=0.05, + ) + pltly_fig.update_layout(yaxis=dict(tickangle=45)) + pltly_fig.update_layout(title=f'trip: {key}') + pltly_fig.add_trace( + go.Scatter( + x=time_hr, + y=mph_ach, + name="mph", + ), + row=rows, + col=1, + ) + pltly_fig.update_xaxes(title_text="Time [hr]", row=rows, col=1) + pltly_fig.update_yaxes(title_text="Speed [mph]", row=rows, col=1) + elif plotly: + raise Exception("`plot_save_dir` must also be provided for `plotly` to have any effect.") + else: + pltly_fig = None + + if plot: # make directory if it doesn't exist Path(plot_save_dir).mkdir(exist_ok=True, parents=True) fig, ax = plt.subplots( @@ -275,14 +316,13 @@ def setup_plots( ) ax[-1].set_xlabel('Time [hr]', fontsize=fontsize) ax[-1].set_ylabel('Speed [mph]', fontsize=fontsize) - return fig, ax + return fig, ax, pltly_fig else: - return (None, None) + return (None, None, None) def update_plots( - plot: bool, - plot_save_dir: Path, - ax: Axes, + ax: Optional[Axes], + pltly_fig: go.Figure, i_obj: int, ax_multiplier: int, objectives: Dict, @@ -296,7 +336,7 @@ def update_plots( mod_path: str, show: bool, ): - if plot or plot_save_dir: + if ax is not None: # this code needs to be cleaned up # raw signals ax[i_obj * ax_multiplier].set_title( @@ -333,13 +373,48 @@ def update_plots( if show: plt.show() + + if pltly_fig is not None: + pltly_fig.add_trace( + go.Scatter( + x=time_hr, + y=mod_sig, + # might want to prepend signal name for this + name=obj[0] + ' mod', + ), + # add 1 for 1-based indexing in plotly + row=i_obj * ax_multiplier + 1, + col=1, + ) + pltly_fig.add_trace( + go.Scatter( + x=time_hr, + y=ref_sig, + # might want to prepend signal name for this + name=obj[0] + ' exp', + ), + # add 1 for 1-based indexing in plotly + row=i_obj * ax_multiplier + 1, + col=1, + ) + # pltly_fig.update_yaxes(title_text=obj[0], row=i_obj * ax_multiplier + 1, col=1) + + if plot_perc_err: + pltly_fig.add_trace( + go.Scatter( + x=time_hr, + y=perc_err, + # might want to prepend signal name for this + name=obj[0] + ' % err', + ), + # add 2 for 1-based indexing and offset for % err plot + row=i_obj * ax_multiplier + 2, + col=1, + ) + # pltly_fig.update_yaxes(title_text=obj[0] + "%Err", row=i_obj * ax_multiplier + 2, col=1) + pltly_fig.update_yaxes(title_text="%Err", row=i_obj * ax_multiplier + 2, col=1) + - if plot_save_dir: - if not Path(plot_save_dir).exists(): - Path(plot_save_dir).mkdir() - plt.tight_layout() - plt.savefig(Path(plot_save_dir) / f"{key}.svg") - plt.savefig(Path(plot_save_dir) / f"{key}.png") diff --git a/fastsim/docs/fusion_thermal_cal.py b/fastsim/docs/fusion_thermal_cal.py index 9954765b..45f08e33 100644 --- a/fastsim/docs/fusion_thermal_cal.py +++ b/fastsim/docs/fusion_thermal_cal.py @@ -37,13 +37,13 @@ def load_data() -> Dict[str, pd.DataFrame]: dfs[file.stem] = fsim.resample( dfs_raw[file.stem], - rate_vars=('Eng_FuelFlow_Direct[cc/s]') + rate_vars=('Eng_FuelFlow_Direct[cc/s]',) ) assert len(dfs) > 0 return dfs -def get_cal_and_val_objs(use_simdrivehot: bool): +def get_cal_and_val_objs(): dfs = load_data() # Separate calibration and validation cycles @@ -124,7 +124,7 @@ def get_cal_and_val_objs(use_simdrivehot: bool): dfs=dfs_cal, obj_names=obj_names, params=params, - use_simdrivehot=use_simdrivehot, + use_simdrivehot=True, verbose=False, ) @@ -135,7 +135,7 @@ def get_cal_and_val_objs(use_simdrivehot: bool): dfs=dfs_val, obj_names=obj_names, params=params, - use_simdrivehot=use_simdrivehot, + use_simdrivehot=True, verbose=False, ) @@ -176,7 +176,7 @@ def get_cal_and_val_objs(use_simdrivehot: bool): # override default of False use_simdrivehot = True - cal_objectives, val_objectives, params_bounds = get_cal_and_val_objs(use_simdrivehot) + cal_objectives, val_objectives, params_bounds = get_cal_and_val_objs() if run_minimize: print("Starting calibration.") @@ -258,12 +258,15 @@ def get_cal_and_val_objs(use_simdrivehot: bool): plot_save_dir=save_path, show=show_plots and make_plots, plot=make_plots, + plotly=make_plots, return_mods=True, ) val_objectives.get_errors( val_objectives.update_params(param_vals), plot_save_dir=save_path, show=show_plots, + plot=make_plots, + plotly=make_plots, ) # save calibrated vehicle to file diff --git a/fastsim/docs/fusion_thermal_cal_post.py b/fastsim/docs/fusion_thermal_cal_post.py index 985dabad..b4b0f816 100644 --- a/fastsim/docs/fusion_thermal_cal_post.py +++ b/fastsim/docs/fusion_thermal_cal_post.py @@ -45,12 +45,14 @@ plot_save_dir=Path(save_path), show=show_plots, return_mods=True, + plotly=True, ) val_errs, val_mods = val_objectives.get_errors( val_objectives.update_params(param_vals), plot_save_dir=Path(save_path), show=show_plots, return_mods=True, + plotly=True ) # %% diff --git a/pyproject.toml b/pyproject.toml index e3a13a8e..e14aa1a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ dependencies = [ "Homepage" = "https://www.nrel.gov/transportation/fastsim.html" [project.optional-dependencies] -dev = ["black", "pytest", "maturin"] +dev = ["black", "pytest", "maturin", "plotly"] [tool.setuptools] zip-safe = false From cc6db35e2c8138aef123ba4af7d2b8ac96cc6d6a Mon Sep 17 00:00:00 2001 From: Chad Baker Date: Wed, 3 May 2023 14:44:17 -0600 Subject: [PATCH 7/9] removed unwanted tickangle spec --- fastsim/calibration.py | 1 - 1 file changed, 1 deletion(-) diff --git a/fastsim/calibration.py b/fastsim/calibration.py index 3f8709b0..2b554fb1 100644 --- a/fastsim/calibration.py +++ b/fastsim/calibration.py @@ -285,7 +285,6 @@ def setup_plots( shared_xaxes=True, vertical_spacing=0.05, ) - pltly_fig.update_layout(yaxis=dict(tickangle=45)) pltly_fig.update_layout(title=f'trip: {key}') pltly_fig.add_trace( go.Scatter( From 97a641fb8e9e9ec7b63e9e6cde0e3ac2dd2c278f Mon Sep 17 00:00:00 2001 From: Chad Baker Date: Wed, 3 May 2023 14:49:57 -0600 Subject: [PATCH 8/9] better looking y-axis labels --- fastsim/calibration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastsim/calibration.py b/fastsim/calibration.py index 2b554fb1..ed4ae38a 100644 --- a/fastsim/calibration.py +++ b/fastsim/calibration.py @@ -396,7 +396,7 @@ def update_plots( row=i_obj * ax_multiplier + 1, col=1, ) - # pltly_fig.update_yaxes(title_text=obj[0], row=i_obj * ax_multiplier + 1, col=1) + pltly_fig.update_yaxes(title_text=obj[1], row=i_obj * ax_multiplier + 1, col=1) if plot_perc_err: pltly_fig.add_trace( From 648a67ee7c411f1511d8f43b8179ab1decbdf80c Mon Sep 17 00:00:00 2001 From: Chad Baker Date: Thu, 4 May 2023 09:52:19 -0600 Subject: [PATCH 9/9] added plotly --- .github/workflows/release.yaml | 2 +- .github/workflows/tests.yaml | 2 +- .github/workflows/wheels.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 50312cd5..bbf67d87 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -55,7 +55,7 @@ jobs: - run: cd rust/ && cargo test - name: install python dependencies - run: pip install -U setuptools wheel twine cibuildwheel + run: pip install -U setuptools wheel twine cibuildwheel plotly - name: build sdist if: matrix.os == 'ubuntu' && matrix.python-version == '8' diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index bf058097..58760bf5 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -36,6 +36,6 @@ jobs: - name: Python unit tests run: | - pip install setuptools_rust pytest + pip install setuptools_rust pytest plotly pip install . pytest -v fastsim/tests/ diff --git a/.github/workflows/wheels.yaml b/.github/workflows/wheels.yaml index bd68f8f8..f50a1aa2 100644 --- a/.github/workflows/wheels.yaml +++ b/.github/workflows/wheels.yaml @@ -54,7 +54,7 @@ jobs: - run: cd rust/ && cargo test - name: install python dependencies - run: pip install -U setuptools wheel twine cibuildwheel + run: pip install -U setuptools wheel twine cibuildwheel plotly - name: build sdist if: matrix.os == 'ubuntu' && matrix.python-version == '8'