diff --git a/cal_and_val/thermal/f3-vehicles/2021_Hyundai_Sonata_Hybrid_Blue.yaml b/cal_and_val/thermal/f3-vehicles/2021_Hyundai_Sonata_Hybrid_Blue.yaml index d219c808..01f659cc 100644 --- a/cal_and_val/thermal/f3-vehicles/2021_Hyundai_Sonata_Hybrid_Blue.yaml +++ b/cal_and_val/thermal/f3-vehicles/2021_Hyundai_Sonata_Hybrid_Blue.yaml @@ -238,11 +238,13 @@ hvac: te_deadband_kelvin: 1.5 p_watts_per_kelvin: 100.0 i: 10.0 + # TODO: bump this up pwr_i_max_watts: 5000.0 d: 5.0 pwr_thrml_max_watts: 10000.0 frac_of_ideal_cop: 0.15 heat_source: FuelConverter + # TODO: bump this up pwr_aux_for_hvac_max_watts: 5000.0 mass_kilograms: 1508.195 pwr_aux_base_watts: 500.0 diff --git a/cal_and_val/thermal/val_hev.py b/cal_and_val/thermal/val_hev.py index fd3e0df0..c3b5ab27 100644 --- a/cal_and_val/thermal/val_hev.py +++ b/cal_and_val/thermal/val_hev.py @@ -1,11 +1,16 @@ +# %% import pandas as pd import matplotlib.pyplot as plt +import numpy as np +from copy import deepcopy -from cal_hev import cal_mod_obj, val_mod_obj, save_path, time_column, mps_per_mph, speed_column, cyc_files_dict, cell_temp_column +import fastsim as fsim +from cal_hev import cal_mod_obj, val_mod_obj, save_path, time_column, mps_per_mph, speed_column, cyc_files_dict, cell_temp_column, get_exp_energy_fuel res_df = pd.read_csv(save_path / "pymoo_res_df.csv") res_df_fuel_energy = res_df.filter(regex="get_mod_energy_fuel") -res_df_fuel_energy_summed = res_df.filter(regex="get_mod_energy_fuel").sum(1) +res_df_fuel_energy_summed = res_df.filter( + regex="get_mod_energy_fuel").sum(1) best_row_fuel_energy = res_df_fuel_energy_summed.argmin() param_vals_fuel_energy = res_df.iloc[ best_row_fuel_energy, @@ -19,25 +24,30 @@ best_row, :len(cal_mod_obj.param_fns)].to_numpy() +param_vals_best = param_vals_euclidean + # getting the solved models -(errors_cal, cvs_cal, sds_cal) = cal_mod_obj.get_errors( - sim_drives=cal_mod_obj.update_params(param_vals_fuel_energy), +(errors_cal, cvs_cal, sds_cal_solved, sds_cal) = cal_mod_obj.get_errors( + sim_drives=cal_mod_obj.update_params(param_vals_best), return_mods=True, -) -# (errors_val, sds_val) = val_mod_obj.get_errors( -# sim_drives=val_mod_obj.update_params(param_vals), -# return_mods=True, -# ) + ) +(errors_val, cvs_val, sds_val_solved, sds_val) = val_mod_obj.get_errors( + sim_drives=val_mod_obj.update_params(param_vals_best), + return_mods=True, + ) + + # %% -# plotting + # plotting plot_save_path = save_path / "plots" plot_save_path.mkdir(exist_ok=True) -for ((key, df_cal), (sd_key, sd_cal)) in zip(cal_mod_obj.dfs.items(), sds_cal.items()): +for ((key, df_cal), (sd_key, sd_cal)) in zip(cal_mod_obj.dfs.items(), sds_cal_solved.items()): if not isinstance(sd_cal, dict): print(f"skipping {key}") continue assert key == sd_key + for obj_fn in cal_mod_obj.obj_fns: fig, ax = plt.subplots(2, 1, sharex=True) cell_temp = next(iter( @@ -70,4 +80,235 @@ ) ax[1].legend() ax[1].set_ylabel("Speed [m/s]") - plt.savefig(plot_save_path / f"{key}.svg") + plt.savefig(plot_save_path / f"{key}_{obj_fn[0].__name__}_cal.svg") + +for ((key, df_val), (sd_key, sd_val)) in zip(val_mod_obj.dfs.items(), sds_val_solved.items()): + if not isinstance(sd_val, dict): + print(f"skipping {key}") + continue + assert key == sd_key + + for obj_fn in val_mod_obj.obj_fns: + fig, ax = plt.subplots(2, 1, sharex=True) + cell_temp = next(iter( + [v[cell_temp_column] + for k, v in cyc_files_dict.items() if k.replace(".txt", "") == key] + )) + fig.suptitle(f"{key}\ncell temp [*C]: {cell_temp}") + ax[0].plot( + sd_val['veh']['history']['time_seconds'], + obj_fn[0](sd_val), + label='mod', + ) + ax[0].plot( + df_val[time_column], + obj_fn[1](df_val), + label='exp', + ) + ax[0].legend() + ax[0].set_ylabel(obj_fn[0].__name__) + + ax[1].plot( + sd_val['veh']['history']['time_seconds'], + sd_val['veh']['history']['speed_ach_meters_per_second'], + label='mod', + ) + ax[1].plot( + df_val[time_column], + df_val[speed_column] * mps_per_mph, + label='exp', + ) + ax[1].legend() + ax[1].set_ylabel("Speed [m/s]") + plt.savefig(plot_save_path / f"{key}_{obj_fn[0].__name__}_val.svg") + + # %% + + def draw_error_zones(ax): + """Draw 0%, ±5%, ±10% error regions on MPL Axes object""" + xl, xu = ax.get_xlim() + yl, yu = ax.get_ylim() + l = min(xl, yl) + u = max(xu, yu) + lims = np.array([0, 200]) + + # Plot 0% error diagonalx + ax.plot(lims, lims, linestyle="dotted", color="g", label="0% error") + + # Plot ±5%, ±10% error regions with transparencies + counter = 0 + error_1 = 0 + error_2 = 0 + error_3 = 0 + for err, alpha in zip((0.05, 0.10, 0.15), (0.35, 0.2, 0.15)): + error = ax.fill_between( + lims, + lims * (1 - err), + lims * (1 + err), + alpha=alpha, + color="g", + label=f"±{err*100:.0f}% error", + ) + + ax.set_xlim(left=l, right=u) + ax.set_ylim(bottom=l, top=u) + # ax.legend(loc="lower right", framealpha=0.5, fontsize=8, borderpad=0.25) + + return error + + # %% + # Scatter plots with temperature effects + + fuel_energy_exp_cal = [] + fuel_energy_mod_cal = [] + for ((key, df_cal), (sd_key, sd_cal)) in zip(cal_mod_obj.dfs.items(), sds_cal_solved.items()): + if not isinstance(sd_cal, dict): + print(f"skipping {key}") + continue + assert key == sd_key + + fuel_energy_mod_cal.append( + sd_cal['veh']['pt_type']['HybridElectricVehicle']['fc']['state']['energy_fuel_joules'] * 1e-6 + ) + df_cal = df_cal[df_cal[time_column] <= + sd_cal['veh']['state']['time_seconds']] + fuel_energy_exp_cal.append( + get_exp_energy_fuel(df_cal).iloc[-1] * 1e-6 + ) + + fuel_energy_exp_val = [] + fuel_energy_mod_val = [] + + for ((key, df_val), (sd_key, sd_val)) in zip(val_mod_obj.dfs.items(), sds_val_solved.items()): + if not isinstance(sd_val, dict): + print(f"skipping {key}") + continue + assert key == sd_key + + fuel_energy_mod_val.append( + sd_val['veh']['pt_type']['HybridElectricVehicle']['fc']['state']['energy_fuel_joules'] * 1e-6 + ) + df_val = df_val[df_val[time_column] <= + sd_val['veh']['state']['time_seconds']] + fuel_energy_exp_val.append( + get_exp_energy_fuel(df_val).iloc[-1] * 1e-6 + ) + + fig, ax = plt.subplots() + fig.suptitle("Model v. Test Data With Thermal Effects") + ax.scatter( + fuel_energy_exp_cal, + fuel_energy_mod_cal, + label='cal', + ) + ax.scatter( + fuel_energy_exp_val, + fuel_energy_mod_val, + label='val', + ) + draw_error_zones(ax) + ax.set_xlabel("Test Data Fuel Used [MJ]") + ax.set_ylabel("FASTSim Fuel Used [MJ]") + ax.set_xlim(0, 55) + ax.set_ylim(0, 55) + ax.legend() + plt.savefig(plot_save_path / "scatter with thrml effects.svg") + + # %% + + # Scatter plots without temperature effects + + fuel_energy_mod_cal_no_thrml = [] + fuel_energy_exp_cal_no_thrml = [] + for ((key, df_cal), (sd_key, sd_cal)) in zip(cal_mod_obj.dfs.items(), sds_cal.items()): + if not isinstance(sd_cal, dict): + print(f"skipping {key}") + continue + assert key == sd_key + + sd_cal_no_thrml = deepcopy(sd_cal) + + sd_cal_no_thrml['veh']['hvac'] = 'None' + sd_cal_no_thrml['veh']['cabin'] = 'None' + sd_cal_no_thrml['veh']['pt_type']['HybridElectricVehicle']['fc']['thrml'] = 'None' + sd_cal_no_thrml['veh']['pt_type']['HybridElectricVehicle']['res']['thrml'] = 'None' + res = fsim.ReversibleEnergyStorage.from_pydict( + sd_cal_no_thrml['veh']['pt_type']['HybridElectricVehicle']['res'], skip_init=False) + res.set_default_pwr_interp() + sd_cal_no_thrml['veh']['pt_type']['HybridElectricVehicle']['res'] = res.to_pydict() + sd_cal_no_thrml['veh']['pt_type']['HybridElectricVehicle']['pt_cntrl']['RGWDB']['temp_fc_allowed_off_kelvin'] = None + sd_cal_no_thrml['veh']['pt_type']['HybridElectricVehicle']['pt_cntrl']['RGWDB']['temp_fc_forced_on_kelvin'] = None + + sd_cal_no_thrml = fsim.SimDrive.from_pydict( + sd_cal_no_thrml, skip_init=False) + try: + sd_cal_no_thrml.walk_once() + except: + pass + sd_cal_no_thrml = sd_cal_no_thrml.to_pydict() + + fuel_energy_mod_cal_no_thrml.append( + sd_cal_no_thrml['veh']['pt_type']['HybridElectricVehicle']['fc']['state']['energy_fuel_joules'] * 1e-6 + ) + df_cal = df_cal[df_cal[time_column] <= + sd_cal_no_thrml['veh']['state']['time_seconds']] + fuel_energy_exp_cal_no_thrml.append( + get_exp_energy_fuel(df_cal) + ) + + fuel_energy_exp_val_no_thrml = [] + fuel_energy_mod_val_no_thrml = [] + for ((key, df_val), (sd_key, sd_val)) in zip(val_mod_obj.dfs.items(), sds_val.items()): + if not isinstance(sd_val, dict): + print(f"skipping {key}") + continue + assert key == sd_key + + sd_val_no_thrml = deepcopy(sd_val) + + sd_val_no_thrml['veh']['hvac'] = 'None' + sd_val_no_thrml['veh']['cabin'] = 'None' + sd_val_no_thrml['veh']['pt_type']['HybridElectricVehicle']['fc']['thrml'] = 'None' + sd_val_no_thrml['veh']['pt_type']['HybridElectricVehicle']['res']['thrml'] = 'None' + res = fsim.ReversibleEnergyStorage.from_pydict( + sd_val_no_thrml['veh']['pt_type']['HybridElectricVehicle']['res'], skip_init=False) + res.set_default_pwr_interp() + sd_val_no_thrml['veh']['pt_type']['HybridElectricVehicle']['res'] = res.to_pydict() + sd_val_no_thrml['veh']['pt_type']['HybridElectricVehicle']['pt_cntrl']['RGWDB']['temp_fc_allowed_off_kelvin'] = None + sd_val_no_thrml['veh']['pt_type']['HybridElectricVehicle']['pt_cntrl']['RGWDB']['temp_fc_forced_on_kelvin'] = None + sd_val_no_thrml = fsim.SimDrive.from_pydict( + sd_val_no_thrml, skip_init=False) + try: + sd_val_no_thrml.walk_once() + except: + pass + sd_val_no_thrml = sd_val_no_thrml.to_pydict() + + fuel_energy_mod_val_no_thrml.append( + sd_val_no_thrml['veh']['pt_type']['HybridElectricVehicle']['fc']['state']['energy_fuel_joules'] * 1e-6 + ) + df_val = df_val[df_val[time_column] <= + sd_val_no_thrml['veh']['state']['time_seconds']] + fuel_energy_exp_val_no_thrml.append( + get_exp_energy_fuel(df_val) + ) + + fig, ax = plt.subplots() + fig.suptitle("Model v. Test Data Without Thermal Effects") + ax.scatter( + fuel_energy_exp_cal, + fuel_energy_mod_cal_no_thrml, + label='cal', + ) + ax.scatter( + fuel_energy_exp_val, + fuel_energy_mod_val_no_thrml, + label='val', + ) + draw_error_zones(ax) + ax.set_xlabel("Test Data Fuel Used [MJ]") + ax.set_ylabel("FASTSim Fuel Used [MJ]") + ax.set_xlim(0, 55) + ax.set_ylim(0, 55) + ax.legend() + plt.savefig(plot_save_path / "scatter without thrml effects.svg") diff --git a/fastsim-core/resources/interpolators/res/default_pwr.yaml b/fastsim-core/resources/interpolators/res/default_pwr.yaml index 6a66c3a8..34438291 100644 --- a/fastsim-core/resources/interpolators/res/default_pwr.yaml +++ b/fastsim-core/resources/interpolators/res/default_pwr.yaml @@ -25,4 +25,4 @@ Interp1D: - 0.927243266086586 - 0.864025853742296 strategy: Linear - extrapolate: Error + extrapolate: Clamp diff --git a/fastsim-core/src/vehicle/powertrain/fuel_converter.rs b/fastsim-core/src/vehicle/powertrain/fuel_converter.rs index 65b08a2a..2f7576fd 100755 --- a/fastsim-core/src/vehicle/powertrain/fuel_converter.rs +++ b/fastsim-core/src/vehicle/powertrain/fuel_converter.rs @@ -935,6 +935,7 @@ pub struct FCTempEffModelExponential { impl Default for FCTempEffModelExponential { fn default() -> Self { Self { + // TODO: update after reasonable calibration offset: 0.0 * uc::KELVIN, lag: 25.0 * uc::KELVIN_INT, minimum: 0.2 * uc::R, diff --git a/python/fastsim/demos/demo_hev_thrml_cs_ca.py b/python/fastsim/demos/demo_hev_thrml_cs_ca.py index 961ed809..33c77c44 100644 --- a/python/fastsim/demos/demo_hev_thrml_cs_ca.py +++ b/python/fastsim/demos/demo_hev_thrml_cs_ca.py @@ -50,7 +50,10 @@ # simulation start time t0 = time.perf_counter() # run simulation -sd.walk() +try: + sd.walk() +except Exception: + pass # simulation end time t1 = time.perf_counter() t_fsim3_si1 = t1 - t0 diff --git a/python/fastsim/pymoo_api.py b/python/fastsim/pymoo_api.py index 739c4ec3..fecb1693 100644 --- a/python/fastsim/pymoo_api.py +++ b/python/fastsim/pymoo_api.py @@ -186,6 +186,7 @@ def get_errors( objectives: Dict = {} constraint_violations: Dict = {} solved_mods: Dict = {} + unsolved_mods: Dict = {} # loop through all the provided trips for ((key, df_exp), sd) in zip(self.dfs.items(), sim_drives.values()): @@ -197,6 +198,9 @@ def get_errors( # objectives[key] = [1.0e12] * len(self.obj_fns) # continue + if return_mods: + unsolved_mods[key] = sd.to_pydict() + try: t0 = time.perf_counter() sd.walk_once() # type: ignore @@ -270,7 +274,7 @@ def get_errors( # pprint.pp(objectives) # print("") if return_mods: - return objectives, constraint_violations, solved_mods + return objectives, constraint_violations, solved_mods, unsolved_mods else: return objectives, constraint_violations