From dde7f2bcc862d22d40cebacd0378a970ec1a322c Mon Sep 17 00:00:00 2001 From: Genevieve Starke Date: Thu, 8 Jan 2026 11:31:35 -0500 Subject: [PATCH 01/29] Add csv post process for time series variables --- examples/08_wind_electrolyzer/post_process.py | 39 +++++++++++ h2integrate/tools/save_to_csv_partial.py | 69 +++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 examples/08_wind_electrolyzer/post_process.py create mode 100644 h2integrate/tools/save_to_csv_partial.py diff --git a/examples/08_wind_electrolyzer/post_process.py b/examples/08_wind_electrolyzer/post_process.py new file mode 100644 index 000000000..bbfdaba3b --- /dev/null +++ b/examples/08_wind_electrolyzer/post_process.py @@ -0,0 +1,39 @@ +import numpy as np +from matplotlib import pyplot as plt + +import openmdao.api as om +from pathlib import Path +import pandas as pd +from h2integrate.tools.save_to_csv_partial import save_as_csv + + +# set the path for the recorder from stuff specified in the driver_config.yaml +fpath = Path.cwd() / "wind_electrolyzer" / "cases.sql" + +# load the cases +cr = om.CaseReader(fpath) + +# get the cases as a list +cases = list(cr.get_cases()) + +variable_list = [ + "electrolyzer.hydrogen_out", + "wind.electricity_out", +] +units_list = [ + "kg/h", + "MW", +] +alternative_name_list = [ + "Hydrogen Produced (kg/hr)", + "Wind Electricity (MW)", +] + +save_as_csv( + variable_list, + units_list, + cases[-1], + Path.cwd() / "wind_electrolyzer" / "wind_electrolyzer_ouputs.csv", + # alternative_name_list=alternative_name_list, +) + diff --git a/h2integrate/tools/save_to_csv_partial.py b/h2integrate/tools/save_to_csv_partial.py new file mode 100644 index 000000000..6514970bd --- /dev/null +++ b/h2integrate/tools/save_to_csv_partial.py @@ -0,0 +1,69 @@ +import numpy as np +from matplotlib import pyplot as plt + +import openmdao.api as om +from pathlib import Path +import pandas as pd + +def save_as_csv(variable_list, units_list, case, filename, alternative_name_list=None): + data = pd.DataFrame() + if alternative_name_list is None: + alternative_name_list = [None]*len(variable_list) + + for loc, var in enumerate(variable_list): + if alternative_name_list[loc] is not None: + save_key = alternative_name_list[loc] # Formatting of this may need work + else: + name_list = var.split(".") + name_list.append(units_list[loc]) + save_key = " ".join(name_list) + data[save_key] = case.get_val(var, units=units_list[loc]) + + data.to_csv(filename, index=False) + +if __name__ == "__main__": + # set the path for the recorder from stuff specified in the driver_config.yaml + fpath = Path.cwd() / "outputs" / "cases.sql" + + # load the cases + cr = om.CaseReader(fpath) + + # get the cases as a list + cases = list(cr.get_cases()) + + variable_list = [ + "battery.SOC", + "battery.electricity_in", + "battery.unused_electricity_out", + "battery.electricity_out", + "battery.bought_electricity_out", + "battery.battery_electricity_discharge", + "battery.electricity_demand", + ] + units_list = [ + "percent", + "MW", + "MW", + "MW", + "MW", + "MW", + "MW", + ] + alternative_name_list = [ + "Battery SOC (%)", + "Wind Electricity (MW)", + "Excess Electricity (MW)", + "Electricity to Load (MW)", + "Grid Purchased Electricity (MW)", + "Battery Electricity (MW)", + "Electrical Demand (MW)", + ] + + save_as_csv( + variable_list, + units_list, + cases[-1], + Path.cwd() / "outputs" / "wind_battery_grid_plant.csv", + alternative_name_list=alternative_name_list, + ) + From 7e9c7d577ee178cc503fd587ba9da914d655738e Mon Sep 17 00:00:00 2001 From: Genevieve Starke Date: Thu, 8 Jan 2026 11:48:43 -0500 Subject: [PATCH 02/29] Rename and move to postprocesss folder --- examples/08_wind_electrolyzer/post_process.py | 7 ++----- .../timeseries_to_csv.py} | 7 ++----- 2 files changed, 4 insertions(+), 10 deletions(-) rename h2integrate/{tools/save_to_csv_partial.py => postprocess/timeseries_to_csv.py} (91%) diff --git a/examples/08_wind_electrolyzer/post_process.py b/examples/08_wind_electrolyzer/post_process.py index bbfdaba3b..87a66efb4 100644 --- a/examples/08_wind_electrolyzer/post_process.py +++ b/examples/08_wind_electrolyzer/post_process.py @@ -1,10 +1,7 @@ -import numpy as np -from matplotlib import pyplot as plt - import openmdao.api as om from pathlib import Path import pandas as pd -from h2integrate.tools.save_to_csv_partial import save_as_csv +from h2integrate.postprocess.timeseries_to_csv import save_timeseries_vars_as_csv # set the path for the recorder from stuff specified in the driver_config.yaml @@ -29,7 +26,7 @@ "Wind Electricity (MW)", ] -save_as_csv( +save_timeseries_vars_as_csv( variable_list, units_list, cases[-1], diff --git a/h2integrate/tools/save_to_csv_partial.py b/h2integrate/postprocess/timeseries_to_csv.py similarity index 91% rename from h2integrate/tools/save_to_csv_partial.py rename to h2integrate/postprocess/timeseries_to_csv.py index 6514970bd..a8e645f8f 100644 --- a/h2integrate/tools/save_to_csv_partial.py +++ b/h2integrate/postprocess/timeseries_to_csv.py @@ -1,11 +1,8 @@ -import numpy as np -from matplotlib import pyplot as plt - import openmdao.api as om from pathlib import Path import pandas as pd -def save_as_csv(variable_list, units_list, case, filename, alternative_name_list=None): +def save_timeseries_vars_as_csv(variable_list, units_list, case, filename, alternative_name_list=None): data = pd.DataFrame() if alternative_name_list is None: alternative_name_list = [None]*len(variable_list) @@ -59,7 +56,7 @@ def save_as_csv(variable_list, units_list, case, filename, alternative_name_list "Electrical Demand (MW)", ] - save_as_csv( + save_timeseries_vars_as_csv( variable_list, units_list, cases[-1], From 329160a9e4161e688e82779a77f2932aa5eee28e Mon Sep 17 00:00:00 2001 From: Genevieve Starke Date: Thu, 8 Jan 2026 11:50:41 -0500 Subject: [PATCH 03/29] Clean up function file --- examples/08_wind_electrolyzer/post_process.py | 1 - h2integrate/postprocess/timeseries_to_csv.py | 46 ------------------- 2 files changed, 47 deletions(-) diff --git a/examples/08_wind_electrolyzer/post_process.py b/examples/08_wind_electrolyzer/post_process.py index 87a66efb4..52fcff68e 100644 --- a/examples/08_wind_electrolyzer/post_process.py +++ b/examples/08_wind_electrolyzer/post_process.py @@ -1,6 +1,5 @@ import openmdao.api as om from pathlib import Path -import pandas as pd from h2integrate.postprocess.timeseries_to_csv import save_timeseries_vars_as_csv diff --git a/h2integrate/postprocess/timeseries_to_csv.py b/h2integrate/postprocess/timeseries_to_csv.py index a8e645f8f..df38e9d6e 100644 --- a/h2integrate/postprocess/timeseries_to_csv.py +++ b/h2integrate/postprocess/timeseries_to_csv.py @@ -1,5 +1,3 @@ -import openmdao.api as om -from pathlib import Path import pandas as pd def save_timeseries_vars_as_csv(variable_list, units_list, case, filename, alternative_name_list=None): @@ -18,49 +16,5 @@ def save_timeseries_vars_as_csv(variable_list, units_list, case, filename, alter data.to_csv(filename, index=False) -if __name__ == "__main__": - # set the path for the recorder from stuff specified in the driver_config.yaml - fpath = Path.cwd() / "outputs" / "cases.sql" - # load the cases - cr = om.CaseReader(fpath) - - # get the cases as a list - cases = list(cr.get_cases()) - - variable_list = [ - "battery.SOC", - "battery.electricity_in", - "battery.unused_electricity_out", - "battery.electricity_out", - "battery.bought_electricity_out", - "battery.battery_electricity_discharge", - "battery.electricity_demand", - ] - units_list = [ - "percent", - "MW", - "MW", - "MW", - "MW", - "MW", - "MW", - ] - alternative_name_list = [ - "Battery SOC (%)", - "Wind Electricity (MW)", - "Excess Electricity (MW)", - "Electricity to Load (MW)", - "Grid Purchased Electricity (MW)", - "Battery Electricity (MW)", - "Electrical Demand (MW)", - ] - - save_timeseries_vars_as_csv( - variable_list, - units_list, - cases[-1], - Path.cwd() / "outputs" / "wind_battery_grid_plant.csv", - alternative_name_list=alternative_name_list, - ) From b9c6d7337eb6a53b7b3d670e936bda5d1418d84b Mon Sep 17 00:00:00 2001 From: Genevieve Starke Date: Thu, 8 Jan 2026 11:52:21 -0500 Subject: [PATCH 04/29] Fis ruff formatting --- h2integrate/postprocess/timeseries_to_csv.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/h2integrate/postprocess/timeseries_to_csv.py b/h2integrate/postprocess/timeseries_to_csv.py index df38e9d6e..d5b40cc3c 100644 --- a/h2integrate/postprocess/timeseries_to_csv.py +++ b/h2integrate/postprocess/timeseries_to_csv.py @@ -1,13 +1,15 @@ import pandas as pd -def save_timeseries_vars_as_csv(variable_list, units_list, case, filename, alternative_name_list=None): +def save_timeseries_vars_as_csv( + variable_list, units_list, case, filename, alternative_name_list=None +): data = pd.DataFrame() if alternative_name_list is None: - alternative_name_list = [None]*len(variable_list) + alternative_name_list = [None] * len(variable_list) for loc, var in enumerate(variable_list): if alternative_name_list[loc] is not None: - save_key = alternative_name_list[loc] # Formatting of this may need work + save_key = alternative_name_list[loc] else: name_list = var.split(".") name_list.append(units_list[loc]) From b0f65bf04a4a761b47d8efcce8a43269b00bcb20 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Thu, 8 Jan 2026 14:05:35 -0700 Subject: [PATCH 05/29] added alternative approach to saving timeseries data --- h2integrate/postprocess/timeseries_to_csv.py | 142 ++++++++++++++++++- 1 file changed, 141 insertions(+), 1 deletion(-) diff --git a/h2integrate/postprocess/timeseries_to_csv.py b/h2integrate/postprocess/timeseries_to_csv.py index d5b40cc3c..45924af5b 100644 --- a/h2integrate/postprocess/timeseries_to_csv.py +++ b/h2integrate/postprocess/timeseries_to_csv.py @@ -1,7 +1,12 @@ +from pathlib import Path + +import numpy as np import pandas as pd +import openmdao.api as om + def save_timeseries_vars_as_csv( - variable_list, units_list, case, filename, alternative_name_list=None + variable_list, units_list, case, filename, alternative_name_list=None ): data = pd.DataFrame() if alternative_name_list is None: @@ -19,4 +24,139 @@ def save_timeseries_vars_as_csv( data.to_csv(filename, index=False) +def check_get_units_for_var(case, var, electricity_base_unit: str, user_specified_unit=None): + electricity_type_units = ["W", "kW", "MW", "GW"] + + if user_specified_unit is not None: + val = case.get_val(var, units=user_specified_unit) + return val, user_specified_unit + + var_unit = case._get_units(var) + is_electric = any(electricity_unit in var_unit for electricity_unit in electricity_type_units) + if is_electric: + var_electricity_unit = [ + electricity_unit + for electricity_unit in electricity_type_units + if electricity_unit in var_unit + ] + new_var_unit = var_unit.replace(var_electricity_unit[-1], electricity_base_unit) + val = case.get_val(var, units=new_var_unit) + return val, new_var_unit + + # get the value + val = case.get_val(var, units=var_unit) + return val, var_unit + + +def save_case_timeseries_as_csv( + sql_fpath: Path | str, + case_index: int = 0, + electricity_base_unit="MW", + vars_to_save: dict | list = {}, + save_to_file: bool = True, +): + electricity_type_units = ["W", "kW", "MW", "GW"] + if electricity_base_unit not in electricity_type_units: + msg = ( + f"Invalid input for electricity_base_unit {electricity_base_unit}. " + f"Valid options are {electricity_type_units}." + ) + raise ValueError(msg) + + sql_fpath = Path(sql_fpath) + + # check if multiple sql files exist with the same name and suffix. + sql_files = list(Path(sql_fpath.parent).glob(f"{sql_fpath.name}*")) + + # check that at least one sql file exists + if len(sql_files) == 0: + raise FileNotFoundError(f"{sql_fpath} file does not exist.") + + # check if a metadata file is contained in sql_files + contains_meta_sql = any("_meta" in sql_file.suffix for sql_file in sql_files) + if contains_meta_sql: + # remove metadata file from filelist + sql_files = [sql_file for sql_file in sql_files if "_meta" not in sql_file.suffix] + # check that only one sql file was input + if len(sql_files) > 1: + msg = ( + f"{sql_fpath} points to {len(sql_files)} sql files, please specify the filepath " + "of a single sql file." + ) + raise FileNotFoundError(msg) + + # load the sql file and extract cases + cr = om.CaseReader(Path(sql_files[0])) + case = cr.get_case(case_index) + + # get list of input and output names + output_var_dict = case.list_outputs(val=False, out_stream=None, return_format="dict") + input_var_dict = case.list_inputs(val=False, out_stream=None, return_format="dict") + + # create list of variables to loop through + var_list = [v["prom_name"] for v in output_var_dict.values()] + var_list += [v["prom_name"] for v in input_var_dict.values()] + var_list.sort() + + # if vars_to_save is not empty, then only include the variables in var_list + if bool(vars_to_save): + if isinstance(vars_to_save, dict): + varnames_to_save = list(vars_to_save.keys()) + var_list = [v for v in var_list if v in varnames_to_save] + if isinstance(vars_to_save, list): + var_list = [v for v in var_list if v in vars_to_save] + + if len(var_list) == 0: + raise ValueError("No variables were found to be saved") + + # initialize output dictionaries + var_to_values = {} # variable to the units + var_to_units = {} # variable to the value + for var in var_list: + if var in var_to_values: + # don't duplicate data + continue + + # get the value + val = case.get_val(var) + + # Skip costs that are per year of plant life + if "varopex" in var.lower() or "annual_fixed_costs" in var.lower(): + continue + + # skip discrete inputs/outputs (like resource data) + if isinstance(val, (dict, pd.DataFrame, pd.Series)): + continue + + # skip scalar data + if isinstance(val, (int, float, str, bool)): + continue + + if isinstance(val, (np.ndarray, list, tuple)): + if len(val) > 1: + user_units = None + if isinstance(vars_to_save, dict): + user_units = vars_to_save.get(var, None) + + var_val, var_units = check_get_units_for_var( + case, var, electricity_base_unit, user_specified_unit=user_units + ) + var_to_units[var] = var_units + var_to_values[var] = var_val + + # rename columns to include units + column_rename_mapper = { + v_name: f"{v_name} ({v_units})" for v_name, v_units in var_to_units.items() + } + + results = pd.DataFrame(var_to_values) + + results = results.rename(columns=column_rename_mapper) + + # save file to csv + if save_to_file: + csv_fname = f"{sql_fpath.name.replace('.sql','_').strip('_')}_Case{case_index}.csv" + output_fpath = sql_fpath.parent / csv_fname + results.to_csv(output_fpath, index=False) + return results From 5e307b5d0f7f635ff149993d4063800783ba8e48 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Thu, 8 Jan 2026 14:30:33 -0700 Subject: [PATCH 06/29] added docstrings and comments to new methods --- h2integrate/postprocess/timeseries_to_csv.py | 67 +++++++++++++++++++- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/h2integrate/postprocess/timeseries_to_csv.py b/h2integrate/postprocess/timeseries_to_csv.py index 45924af5b..c8e8c47fd 100644 --- a/h2integrate/postprocess/timeseries_to_csv.py +++ b/h2integrate/postprocess/timeseries_to_csv.py @@ -25,13 +25,44 @@ def save_timeseries_vars_as_csv( def check_get_units_for_var(case, var, electricity_base_unit: str, user_specified_unit=None): + """Check the units for a variable within a case, with the following logic: + + 0) If ``user_specified_unit`` is a string, get the variable value in units of + ``user_specified_unit`` then continue to Step 5. + If ``user_specified_unit`` is None, continue to Step 1. + 1) Get the default units of the variable. Continue to Step 2. + 2) Check if the default units contain electricity units. + If the default units do contain an electricity unit, then continue to Step 3. + Otherwise, continue to Step 4. + 3) Replace the default electricity unit in the default units with ``electricity_base_unit``. + Get the variable value in units of the updated units and continue to Step 5. + 4) Get the variable value in the default units and continue to Step 5. + 5) Return the variable value and the corresponding units. + + Args: + case (om.recorders.case.Case): OpenMDAO case object. + var (str): variable name + electricity_base_unit (str): Units to save any electricity-based profiles in. + Must be either "W", "kW", "MW", or "GW". + user_specified_unit (str | None, optional): _description_. Defaults to None. + + Returns: + 2-element tuple containing + + - **val** (np.ndarry | list | tuple): value of the `var` in units `var_unit`. + - **var_unit** (str): units that `val` is returned in. + """ electricity_type_units = ["W", "kW", "MW", "GW"] - + # 0) check if user_specified_unit is not None if user_specified_unit is not None: + # Get the variable value in units of ``user_specified_unit`` val = case.get_val(var, units=user_specified_unit) return val, user_specified_unit + # 1) Get the default units of the variable var_unit = case._get_units(var) + + # 2) Check if the default units contain electricity units. is_electric = any(electricity_unit in var_unit for electricity_unit in electricity_type_units) if is_electric: var_electricity_unit = [ @@ -39,11 +70,12 @@ def check_get_units_for_var(case, var, electricity_base_unit: str, user_specifie for electricity_unit in electricity_type_units if electricity_unit in var_unit ] + # 3) Replace the default electricity unit in var_unit with electricity_base_unit new_var_unit = var_unit.replace(var_electricity_unit[-1], electricity_base_unit) val = case.get_val(var, units=new_var_unit) return val, new_var_unit - # get the value + # 4) Get the variable value in the default units val = case.get_val(var, units=var_unit) return val, var_unit @@ -55,6 +87,36 @@ def save_case_timeseries_as_csv( vars_to_save: dict | list = {}, save_to_file: bool = True, ): + """Summarize timeseries data from a case within an sql recorder file to a DataFrame + and save to csv file if `save_to_file` is True. + + Each column is a variable, each row is a timestep. + Column names are formatted as: + + - "{promoted variable name} ({units})" for continuous variables + + Args: + sql_fpath (Path | str): Filepath to sql recorder file. + case_index (int, optional): Index of the case in the sql file to save results for. + Defaults to 0. + electricity_base_unit (str, optional): Units to save any electricity-based profiles in. + Must be either "W", "kW", "MW", or "GW". Defaults to "MW". + vars_to_save (dict | list, optional): An empty list or dictionary indicates to save + all the timeseries variables in the case. If a list, should be a list of variable names + to save. If a dictionary, should have keys of variable names and values of units for + the corresponding variable. Defaults to {}. + save_to_file (bool, optional): Whether to save the summary csv file to the same + folder as the sql file(s). Defaults to True. + + Raises: + ValueError: if electricity_base_unit is not "W", "kW", "MW", or "GW". + FileNotFoundError: If the sql file does not exist or multiple sql files have the same name. + ValueError: If no valid timeseries variables are input with vars_to_save and + vars_to_save is not an empty list or dictionary. + + Returns: + pd.DataFrame: summary of timeseries results from the sql file. + """ electricity_type_units = ["W", "kW", "MW", "GW"] if electricity_base_unit not in electricity_type_units: msg = ( @@ -77,6 +139,7 @@ def save_case_timeseries_as_csv( if contains_meta_sql: # remove metadata file from filelist sql_files = [sql_file for sql_file in sql_files if "_meta" not in sql_file.suffix] + # check that only one sql file was input if len(sql_files) > 1: msg = ( From cc36149b261798f987702a65fc93ee76aecbb8c4 Mon Sep 17 00:00:00 2001 From: Genevieve Starke Date: Thu, 8 Jan 2026 16:37:56 -0500 Subject: [PATCH 07/29] Add column renaming ability --- examples/08_wind_electrolyzer/post_process.py | 17 +++++++++++++--- h2integrate/postprocess/timeseries_to_csv.py | 20 +++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/examples/08_wind_electrolyzer/post_process.py b/examples/08_wind_electrolyzer/post_process.py index 52fcff68e..049f40385 100644 --- a/examples/08_wind_electrolyzer/post_process.py +++ b/examples/08_wind_electrolyzer/post_process.py @@ -1,6 +1,6 @@ import openmdao.api as om from pathlib import Path -from h2integrate.postprocess.timeseries_to_csv import save_timeseries_vars_as_csv +from h2integrate.postprocess.timeseries_to_csv import save_case_timeseries_as_csv, save_timeseries_vars_as_csv # set the path for the recorder from stuff specified in the driver_config.yaml @@ -21,8 +21,9 @@ "MW", ] alternative_name_list = [ - "Hydrogen Produced (kg/hr)", - "Wind Electricity (MW)", + "Hydrogen Produced", + # "Wind Electricity", + None, ] save_timeseries_vars_as_csv( @@ -33,3 +34,13 @@ # alternative_name_list=alternative_name_list, ) +save_case_timeseries_as_csv("wind_electrolyzer/cases.sql", + case_index=-1, + electricity_base_unit="MW", + vars_to_save=["electrolyzer.hydrogen_out", + "wind.electricity_out"], + save_to_file=True, + alternative_name_list=alternative_name_list) + + + diff --git a/h2integrate/postprocess/timeseries_to_csv.py b/h2integrate/postprocess/timeseries_to_csv.py index 45924af5b..431eb4c49 100644 --- a/h2integrate/postprocess/timeseries_to_csv.py +++ b/h2integrate/postprocess/timeseries_to_csv.py @@ -54,6 +54,7 @@ def save_case_timeseries_as_csv( electricity_base_unit="MW", vars_to_save: dict | list = {}, save_to_file: bool = True, + alternative_name_list: list | None = None, ): electricity_type_units = ["W", "kW", "MW", "GW"] if electricity_base_unit not in electricity_type_units: @@ -144,6 +145,25 @@ def save_case_timeseries_as_csv( var_to_units[var] = var_units var_to_values[var] = var_val + if alternative_name_list is not None: + if len(alternative_name_list) != len(var_to_values): + raise ValueError( + "Length of alternative_name_list must match the number of variables being saved." + ) + + # map alternative names to variable names + alt_name_mapper = { + old_name: new_name if new_name is not None else old_name + for old_name, new_name in zip(var_to_values.keys(), alternative_name_list) + } + # update var_to_values and var_to_units with alternative names + var_to_values = { + alt_name_mapper[k]: v for k, v in var_to_values.items() + } + var_to_units = { + alt_name_mapper[k]: v for k, v in var_to_units.items() + } + # rename columns to include units column_rename_mapper = { v_name: f"{v_name} ({v_units})" for v_name, v_units in var_to_units.items() From 27596975bf775189ddbd176c9cbce03032c12348 Mon Sep 17 00:00:00 2001 From: Genevieve Starke Date: Thu, 8 Jan 2026 16:39:43 -0500 Subject: [PATCH 08/29] Add alternative varibale name capability --- h2integrate/postprocess/timeseries_to_csv.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/h2integrate/postprocess/timeseries_to_csv.py b/h2integrate/postprocess/timeseries_to_csv.py index a9ba85f95..2516214f9 100644 --- a/h2integrate/postprocess/timeseries_to_csv.py +++ b/h2integrate/postprocess/timeseries_to_csv.py @@ -108,12 +108,16 @@ def save_case_timeseries_as_csv( the corresponding variable. Defaults to {}. save_to_file (bool, optional): Whether to save the summary csv file to the same folder as the sql file(s). Defaults to True. + alternative_name_list (list | None, optional): List of alternative names for the + variables being saved. If None, the promoted variable names are used. Defaults to None. Raises: ValueError: if electricity_base_unit is not "W", "kW", "MW", or "GW". FileNotFoundError: If the sql file does not exist or multiple sql files have the same name. ValueError: If no valid timeseries variables are input with vars_to_save and vars_to_save is not an empty list or dictionary. + ValueError: If the length of alternative_name_list does not match the number of variables + being saved. Returns: pd.DataFrame: summary of timeseries results from the sql file. @@ -208,6 +212,7 @@ def save_case_timeseries_as_csv( var_to_units[var] = var_units var_to_values[var] = var_val + # check if alternative names for variables being saved were input if alternative_name_list is not None: if len(alternative_name_list) != len(var_to_values): raise ValueError( From e8b45380dea3cce0efc3482e88820ecd89632530 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Thu, 8 Jan 2026 14:45:49 -0700 Subject: [PATCH 09/29] bugfix in set_recorders --- h2integrate/core/pose_optimization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h2integrate/core/pose_optimization.py b/h2integrate/core/pose_optimization.py index e2ae0fc5b..c4eaff39b 100644 --- a/h2integrate/core/pose_optimization.py +++ b/h2integrate/core/pose_optimization.py @@ -525,7 +525,7 @@ def set_recorders(self, opt_prob): opt_prob.model.recording_options["excludes"] = self.config["recorder"].get( "excludes", ["*resource_data"] ) - return + return recorder_path if recorder_attachment == "driver": recorder_options += [ From 86bef92beed3fae4afb1c22fdc3418f9e4283e18 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Thu, 8 Jan 2026 14:52:35 -0700 Subject: [PATCH 10/29] added timeseries saving to example 2 and removed post_process --- examples/02_texas_ammonia/driver_config.yaml | 17 +++++++++ .../run_texas_ammonia_plant.py | 21 +++++++++-- examples/08_wind_electrolyzer/post_process.py | 35 ------------------- 3 files changed, 35 insertions(+), 38 deletions(-) delete mode 100644 examples/08_wind_electrolyzer/post_process.py diff --git a/examples/02_texas_ammonia/driver_config.yaml b/examples/02_texas_ammonia/driver_config.yaml index d3b06a03e..22fa1d24f 100644 --- a/examples/02_texas_ammonia/driver_config.yaml +++ b/examples/02_texas_ammonia/driver_config.yaml @@ -3,3 +3,20 @@ description: "This analysis runs a hybrid plant to match the first example in H2 general: folder_output: outputs + +recorder: + # required inputs + flag: True #record outputs + file: "cases.sql" #this file will be written to the folder `outputs` + + # optional but recommended inputs + overwrite_recorder: True #If True, do not create a unique recorder file for subsequent runs. Defaults to False. + recorder_attachment: "model" #"driver" or "model", defaults to "model". Use "driver" if running a parallel simulation. + includes: ["*"] #include everything + excludes: ["*resource_data*"] #exclude resource data + + # below are optional and defaulted to the OpenMDAO default + # record_inputs: True #defaults to True + # record_outputs: True #defaults to True + # record_residuals: True #defaults to True + # options_excludes: #this is only used if recorder_attachment is "model" diff --git a/examples/02_texas_ammonia/run_texas_ammonia_plant.py b/examples/02_texas_ammonia/run_texas_ammonia_plant.py index 3c579acd1..20d3a98dc 100644 --- a/examples/02_texas_ammonia/run_texas_ammonia_plant.py +++ b/examples/02_texas_ammonia/run_texas_ammonia_plant.py @@ -1,10 +1,25 @@ from h2integrate.core.h2integrate_model import H2IntegrateModel +from h2integrate.postprocess.timeseries_to_csv import save_case_timeseries_as_csv # Create a H2Integrate model -model = H2IntegrateModel("02_texas_ammonia.yaml") +h2i = H2IntegrateModel("02_texas_ammonia.yaml") # Run the model -model.run() +h2i.run() -model.post_process() +h2i.post_process() + +# Save all timeseries data to a csv file +timeseries_data = save_case_timeseries_as_csv(h2i.recorder_path, save_to_file=True) + +# Get a subset of timeseries data and don't save it to a csv file +vars_to_save = [ + "electrolyzer.hydrogen_out", + "hopp.electricity_out", + "ammonia.ammonia_out", + "h2_storage.hydrogen_out", +] +timeseries_data = save_case_timeseries_as_csv( + h2i.recorder_path, vars_to_save=vars_to_save, save_to_file=False +) diff --git a/examples/08_wind_electrolyzer/post_process.py b/examples/08_wind_electrolyzer/post_process.py deleted file mode 100644 index 52fcff68e..000000000 --- a/examples/08_wind_electrolyzer/post_process.py +++ /dev/null @@ -1,35 +0,0 @@ -import openmdao.api as om -from pathlib import Path -from h2integrate.postprocess.timeseries_to_csv import save_timeseries_vars_as_csv - - -# set the path for the recorder from stuff specified in the driver_config.yaml -fpath = Path.cwd() / "wind_electrolyzer" / "cases.sql" - -# load the cases -cr = om.CaseReader(fpath) - -# get the cases as a list -cases = list(cr.get_cases()) - -variable_list = [ - "electrolyzer.hydrogen_out", - "wind.electricity_out", -] -units_list = [ - "kg/h", - "MW", -] -alternative_name_list = [ - "Hydrogen Produced (kg/hr)", - "Wind Electricity (MW)", -] - -save_timeseries_vars_as_csv( - variable_list, - units_list, - cases[-1], - Path.cwd() / "wind_electrolyzer" / "wind_electrolyzer_ouputs.csv", - # alternative_name_list=alternative_name_list, -) - From 286249f0a7a66af01129afba96f6ef98fcc1ee16 Mon Sep 17 00:00:00 2001 From: Genevieve Starke Date: Thu, 8 Jan 2026 17:06:54 -0500 Subject: [PATCH 11/29] Fix ruff issue, delete old code --- h2integrate/postprocess/timeseries_to_csv.py | 28 ++------------------ 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/h2integrate/postprocess/timeseries_to_csv.py b/h2integrate/postprocess/timeseries_to_csv.py index 2516214f9..4699391d5 100644 --- a/h2integrate/postprocess/timeseries_to_csv.py +++ b/h2integrate/postprocess/timeseries_to_csv.py @@ -4,26 +4,6 @@ import pandas as pd import openmdao.api as om - -def save_timeseries_vars_as_csv( - variable_list, units_list, case, filename, alternative_name_list=None -): - data = pd.DataFrame() - if alternative_name_list is None: - alternative_name_list = [None] * len(variable_list) - - for loc, var in enumerate(variable_list): - if alternative_name_list[loc] is not None: - save_key = alternative_name_list[loc] - else: - name_list = var.split(".") - name_list.append(units_list[loc]) - save_key = " ".join(name_list) - data[save_key] = case.get_val(var, units=units_list[loc]) - - data.to_csv(filename, index=False) - - def check_get_units_for_var(case, var, electricity_base_unit: str, user_specified_unit=None): """Check the units for a variable within a case, with the following logic: @@ -225,12 +205,8 @@ def save_case_timeseries_as_csv( for old_name, new_name in zip(var_to_values.keys(), alternative_name_list) } # update var_to_values and var_to_units with alternative names - var_to_values = { - alt_name_mapper[k]: v for k, v in var_to_values.items() - } - var_to_units = { - alt_name_mapper[k]: v for k, v in var_to_units.items() - } + var_to_values = {alt_name_mapper[k]: v for k, v in var_to_values.items()} + var_to_units = {alt_name_mapper[k]: v for k, v in var_to_units.items()} # rename columns to include units column_rename_mapper = { From 68c5bd5753753fdf66f51f22e3bfde446bc78bbf Mon Sep 17 00:00:00 2001 From: Genevieve Starke Date: Thu, 8 Jan 2026 17:10:21 -0500 Subject: [PATCH 12/29] Sort import statements --- h2integrate/postprocess/timeseries_to_csv.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/h2integrate/postprocess/timeseries_to_csv.py b/h2integrate/postprocess/timeseries_to_csv.py index 4699391d5..1016553f1 100644 --- a/h2integrate/postprocess/timeseries_to_csv.py +++ b/h2integrate/postprocess/timeseries_to_csv.py @@ -1,8 +1,7 @@ -from pathlib import Path - import numpy as np -import pandas as pd import openmdao.api as om +import pandas as pd +from pathlib import Path def check_get_units_for_var(case, var, electricity_base_unit: str, user_specified_unit=None): """Check the units for a variable within a case, with the following logic: From 138daf3466005da2d3b2e8aa4301ae4877aaa5e9 Mon Sep 17 00:00:00 2001 From: Genevieve Starke Date: Thu, 8 Jan 2026 17:12:21 -0500 Subject: [PATCH 13/29] Unsort import statements --- h2integrate/postprocess/timeseries_to_csv.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/h2integrate/postprocess/timeseries_to_csv.py b/h2integrate/postprocess/timeseries_to_csv.py index 1016553f1..4699391d5 100644 --- a/h2integrate/postprocess/timeseries_to_csv.py +++ b/h2integrate/postprocess/timeseries_to_csv.py @@ -1,7 +1,8 @@ +from pathlib import Path + import numpy as np -import openmdao.api as om import pandas as pd -from pathlib import Path +import openmdao.api as om def check_get_units_for_var(case, var, electricity_base_unit: str, user_specified_unit=None): """Check the units for a variable within a case, with the following logic: From dcbd69c8af197e4e675ed783f3df89fa232312a2 Mon Sep 17 00:00:00 2001 From: Genevieve Starke Date: Thu, 8 Jan 2026 17:15:20 -0500 Subject: [PATCH 14/29] Add line --- h2integrate/postprocess/timeseries_to_csv.py | 1 + 1 file changed, 1 insertion(+) diff --git a/h2integrate/postprocess/timeseries_to_csv.py b/h2integrate/postprocess/timeseries_to_csv.py index 4699391d5..5d21f367c 100644 --- a/h2integrate/postprocess/timeseries_to_csv.py +++ b/h2integrate/postprocess/timeseries_to_csv.py @@ -4,6 +4,7 @@ import pandas as pd import openmdao.api as om + def check_get_units_for_var(case, var, electricity_base_unit: str, user_specified_unit=None): """Check the units for a variable within a case, with the following logic: From ab7978872ca42f3ecee0c7aba2f0198b58d7ee23 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Thu, 8 Jan 2026 16:18:56 -0700 Subject: [PATCH 15/29] added basic tests for saving timeseries results --- h2integrate/postprocess/test/__init__.py | 0 .../test/test_timeseries_from_sql.py | 85 +++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 h2integrate/postprocess/test/__init__.py create mode 100644 h2integrate/postprocess/test/test_timeseries_from_sql.py diff --git a/h2integrate/postprocess/test/__init__.py b/h2integrate/postprocess/test/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/h2integrate/postprocess/test/test_timeseries_from_sql.py b/h2integrate/postprocess/test/test_timeseries_from_sql.py new file mode 100644 index 000000000..f308b1f4d --- /dev/null +++ b/h2integrate/postprocess/test/test_timeseries_from_sql.py @@ -0,0 +1,85 @@ +import os + +from pytest import fixture + +from h2integrate import EXAMPLE_DIR +from h2integrate.core.h2integrate_model import H2IntegrateModel +from h2integrate.postprocess.timeseries_to_csv import save_case_timeseries_as_csv + + +@fixture +def run_example_02_sql_fpath(): + # check if case file exists, if so, return the filepath + sql_fpath = EXAMPLE_DIR / "02_texas_ammonia" / "outputs" / "cases.sql" + if sql_fpath.exists(): + return sql_fpath + else: + os.chdir(EXAMPLE_DIR / "02_texas_ammonia") + # Create a H2Integrate model + h2i = H2IntegrateModel("02_texas_ammonia.yaml") + + # Run the model + h2i.run() + + return h2i.recorder_path.absolute() + + +def test_save_csv_all_results(subtests, run_example_02_sql_fpath): + expected_csv_fpath = EXAMPLE_DIR / "02_texas_ammonia" / "outputs" / "cases_Case0.csv" + res = save_case_timeseries_as_csv(run_example_02_sql_fpath, save_to_file=True) + + with subtests.test("Check number of columns"): + assert len(res.columns.to_list()) == 15 + + with subtests.test("Check number of rows"): + assert len(res) == 8760 + + with subtests.test("CSV File exists"): + assert expected_csv_fpath.exists() + + +def test_make_df_from_varname_list(subtests, run_example_02_sql_fpath): + vars_to_save = [ + "electrolyzer.hydrogen_out", + "hopp.electricity_out", + "ammonia.ammonia_out", + "h2_storage.hydrogen_out", + ] + + res = save_case_timeseries_as_csv( + run_example_02_sql_fpath, vars_to_save=vars_to_save, save_to_file=False + ) + + with subtests.test("Check number of columns"): + assert len(res.columns.to_list()) == len(vars_to_save) + + with subtests.test("Check number of rows"): + assert len(res) == 8760 + + with subtests.test("All vars in dataframe"): + colnames_no_units = [c.split("(")[0].strip() for c in vars_to_save] + assert all(var_name in colnames_no_units for var_name in vars_to_save) + + +def test_make_df_from_varname_unit_dict(subtests, run_example_02_sql_fpath): + vars_units_to_save = { + "ammonia.hydrogen_in": "kg/h", + "h2_storage.hydrogen_in": "kg/h", + "electrolyzer.electricity_in": "kW", + } + + res = save_case_timeseries_as_csv( + run_example_02_sql_fpath, vars_to_save=vars_units_to_save, save_to_file=False + ) + + with subtests.test("Check number of columns"): + assert len(res.columns.to_list()) == len(vars_units_to_save) + + with subtests.test("Check number of rows"): + assert len(res) == 8760 + + with subtests.test("All vars in dataframe"): + expected_colnames = [ + f"{v_name} ({v_unit})" for v_name, v_unit in vars_units_to_save.items() + ] + assert all(c_name in res.columns.to_list() for c_name in expected_colnames) From bf8119c58ab35c9b1cc593a8f14ac76ce1132835 Mon Sep 17 00:00:00 2001 From: Genevieve Starke Date: Fri, 9 Jan 2026 11:02:30 -0500 Subject: [PATCH 16/29] Add test for alternative column names and adjust alternative column name logic --- .../test/test_timeseries_from_sql.py | 43 +++++++++++++++- h2integrate/postprocess/timeseries_to_csv.py | 49 ++++++++++--------- 2 files changed, 67 insertions(+), 25 deletions(-) diff --git a/h2integrate/postprocess/test/test_timeseries_from_sql.py b/h2integrate/postprocess/test/test_timeseries_from_sql.py index f308b1f4d..23377845a 100644 --- a/h2integrate/postprocess/test/test_timeseries_from_sql.py +++ b/h2integrate/postprocess/test/test_timeseries_from_sql.py @@ -57,7 +57,7 @@ def test_make_df_from_varname_list(subtests, run_example_02_sql_fpath): assert len(res) == 8760 with subtests.test("All vars in dataframe"): - colnames_no_units = [c.split("(")[0].strip() for c in vars_to_save] + colnames_no_units = [c.split("(")[0].strip() for c in res.columns.to_list()] assert all(var_name in colnames_no_units for var_name in vars_to_save) @@ -83,3 +83,44 @@ def test_make_df_from_varname_unit_dict(subtests, run_example_02_sql_fpath): f"{v_name} ({v_unit})" for v_name, v_unit in vars_units_to_save.items() ] assert all(c_name in res.columns.to_list() for c_name in expected_colnames) + +def test_alternative_names_list(subtests, run_example_02_sql_fpath): + vars_to_save = { + "electrolyzer.hydrogen_out":{ + "alternative_name": "Electrolyzer Hydrogen Output" + }, + "hopp.electricity_out":{ + "alternative_name": "Plant Electricity Output" + }, + "ammonia.ammonia_out":{ + "alternative_name": None + }, + "h2_storage.hydrogen_out":{ + "alternative_name": "H2 Storage Hydrogen Output" + }, + } + + res = save_case_timeseries_as_csv( + run_example_02_sql_fpath, + vars_to_save=vars_to_save, + save_to_file=False, + ) + + expected_name_list = [ + "Electrolyzer Hydrogen Output", + "Plant Electricity Output", + "ammonia.ammonia_out", + "H2 Storage Hydrogen Output", + ] + + with subtests.test("Check number of columns"): + assert len(res.columns.to_list()) == len(vars_to_save) + + with subtests.test("Check number of rows"): + assert len(res) == 8760 + + with subtests.test("All alternative names in dataframe"): + colnames_no_units = [c.split("(")[0].strip() for c in res.columns.to_list()] + assert all( + alt_name in colnames_no_units for alt_name in expected_name_list + ) diff --git a/h2integrate/postprocess/timeseries_to_csv.py b/h2integrate/postprocess/timeseries_to_csv.py index 5d21f367c..c94f47235 100644 --- a/h2integrate/postprocess/timeseries_to_csv.py +++ b/h2integrate/postprocess/timeseries_to_csv.py @@ -67,7 +67,6 @@ def save_case_timeseries_as_csv( electricity_base_unit="MW", vars_to_save: dict | list = {}, save_to_file: bool = True, - alternative_name_list: list | None = None, ): """Summarize timeseries data from a case within an sql recorder file to a DataFrame and save to csv file if `save_to_file` is True. @@ -85,20 +84,17 @@ def save_case_timeseries_as_csv( Must be either "W", "kW", "MW", or "GW". Defaults to "MW". vars_to_save (dict | list, optional): An empty list or dictionary indicates to save all the timeseries variables in the case. If a list, should be a list of variable names - to save. If a dictionary, should have keys of variable names and values of units for - the corresponding variable. Defaults to {}. + to save. If a dictionary, should have keys of variable names and either values of units + for the corresponding variable or a dictionary containing the keys "units" and/or + "alternative_name". Defaults to {}. save_to_file (bool, optional): Whether to save the summary csv file to the same folder as the sql file(s). Defaults to True. - alternative_name_list (list | None, optional): List of alternative names for the - variables being saved. If None, the promoted variable names are used. Defaults to None. Raises: ValueError: if electricity_base_unit is not "W", "kW", "MW", or "GW". FileNotFoundError: If the sql file does not exist or multiple sql files have the same name. ValueError: If no valid timeseries variables are input with vars_to_save and vars_to_save is not an empty list or dictionary. - ValueError: If the length of alternative_name_list does not match the number of variables - being saved. Returns: pd.DataFrame: summary of timeseries results from the sql file. @@ -161,6 +157,7 @@ def save_case_timeseries_as_csv( # initialize output dictionaries var_to_values = {} # variable to the units var_to_units = {} # variable to the value + var_to_alternative_names = [] # variable to the alternative name for var in var_list: if var in var_to_values: # don't duplicate data @@ -184,30 +181,34 @@ def save_case_timeseries_as_csv( if isinstance(val, (np.ndarray, list, tuple)): if len(val) > 1: user_units = None - if isinstance(vars_to_save, dict): - user_units = vars_to_save.get(var, None) + alternative_name = None + # Only do this if vars_to_save is a dict and it is not empty + if vars_to_save and isinstance(vars_to_save, dict): + # Only do this if the vars_to_save[var] is a dict for units and alternative name + if isinstance(vars_to_save[var], dict): + user_units = vars_to_save[var].get("units", None) + alternative_name = vars_to_save[var].get("alternative_name", None) + # Otherwise, just pull the units directly + # This means that you can only specify units by itself, not alternative names + # Should we make all of these be enteres as dicts then? + else: + user_units = vars_to_save.get(var, None) var_val, var_units = check_get_units_for_var( case, var, electricity_base_unit, user_specified_unit=user_units ) var_to_units[var] = var_units var_to_values[var] = var_val + var_to_alternative_names.append(alternative_name) - # check if alternative names for variables being saved were input - if alternative_name_list is not None: - if len(alternative_name_list) != len(var_to_values): - raise ValueError( - "Length of alternative_name_list must match the number of variables being saved." - ) - - # map alternative names to variable names - alt_name_mapper = { - old_name: new_name if new_name is not None else old_name - for old_name, new_name in zip(var_to_values.keys(), alternative_name_list) - } - # update var_to_values and var_to_units with alternative names - var_to_values = {alt_name_mapper[k]: v for k, v in var_to_values.items()} - var_to_units = {alt_name_mapper[k]: v for k, v in var_to_units.items()} + # map alternative names to variable names if not None + alt_name_mapper = { + old_name: new_name if new_name is not None else old_name + for old_name, new_name in zip(var_to_values.keys(), var_to_alternative_names) + } + # update var_to_values and var_to_units with alternative names + var_to_values = {alt_name_mapper[k]: v for k, v in var_to_values.items()} + var_to_units = {alt_name_mapper[k]: v for k, v in var_to_units.items()} # rename columns to include units column_rename_mapper = { From 0690fc97d4d4ae20470bc23fd83e3fe38a092c4b Mon Sep 17 00:00:00 2001 From: Genevieve Starke Date: Fri, 9 Jan 2026 11:08:44 -0500 Subject: [PATCH 17/29] fix formatting --- .../test/test_timeseries_from_sql.py | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/h2integrate/postprocess/test/test_timeseries_from_sql.py b/h2integrate/postprocess/test/test_timeseries_from_sql.py index 23377845a..a04dc20f3 100644 --- a/h2integrate/postprocess/test/test_timeseries_from_sql.py +++ b/h2integrate/postprocess/test/test_timeseries_from_sql.py @@ -86,18 +86,10 @@ def test_make_df_from_varname_unit_dict(subtests, run_example_02_sql_fpath): def test_alternative_names_list(subtests, run_example_02_sql_fpath): vars_to_save = { - "electrolyzer.hydrogen_out":{ - "alternative_name": "Electrolyzer Hydrogen Output" - }, - "hopp.electricity_out":{ - "alternative_name": "Plant Electricity Output" - }, - "ammonia.ammonia_out":{ - "alternative_name": None - }, - "h2_storage.hydrogen_out":{ - "alternative_name": "H2 Storage Hydrogen Output" - }, + "electrolyzer.hydrogen_out": {"alternative_name": "Electrolyzer Hydrogen Output"}, + "hopp.electricity_out": {"alternative_name": "Plant Electricity Output"}, + "ammonia.ammonia_out": {"alternative_name": None}, + "h2_storage.hydrogen_out": {"alternative_name": "H2 Storage Hydrogen Output"}, } res = save_case_timeseries_as_csv( @@ -121,6 +113,4 @@ def test_alternative_names_list(subtests, run_example_02_sql_fpath): with subtests.test("All alternative names in dataframe"): colnames_no_units = [c.split("(")[0].strip() for c in res.columns.to_list()] - assert all( - alt_name in colnames_no_units for alt_name in expected_name_list - ) + assert all(alt_name in colnames_no_units for alt_name in expected_name_list) From ac3a91ee29aaa0fe2ac9030d3162b5f769f5bd33 Mon Sep 17 00:00:00 2001 From: Genevieve Starke Date: Fri, 9 Jan 2026 11:10:28 -0500 Subject: [PATCH 18/29] update test name --- h2integrate/postprocess/test/test_timeseries_from_sql.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/h2integrate/postprocess/test/test_timeseries_from_sql.py b/h2integrate/postprocess/test/test_timeseries_from_sql.py index a04dc20f3..07b32600a 100644 --- a/h2integrate/postprocess/test/test_timeseries_from_sql.py +++ b/h2integrate/postprocess/test/test_timeseries_from_sql.py @@ -84,7 +84,8 @@ def test_make_df_from_varname_unit_dict(subtests, run_example_02_sql_fpath): ] assert all(c_name in res.columns.to_list() for c_name in expected_colnames) -def test_alternative_names_list(subtests, run_example_02_sql_fpath): + +def test_alternative_column_names(subtests, run_example_02_sql_fpath): vars_to_save = { "electrolyzer.hydrogen_out": {"alternative_name": "Electrolyzer Hydrogen Output"}, "hopp.electricity_out": {"alternative_name": "Plant Electricity Output"}, From 78a9b222d7f0531d16b483b1bca9ddc444741289 Mon Sep 17 00:00:00 2001 From: Genevieve Starke Date: Fri, 9 Jan 2026 13:57:31 -0500 Subject: [PATCH 19/29] Add unit and alternative name test --- .../postprocess/test/test_timeseries_from_sql.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/h2integrate/postprocess/test/test_timeseries_from_sql.py b/h2integrate/postprocess/test/test_timeseries_from_sql.py index 07b32600a..13862c501 100644 --- a/h2integrate/postprocess/test/test_timeseries_from_sql.py +++ b/h2integrate/postprocess/test/test_timeseries_from_sql.py @@ -88,7 +88,7 @@ def test_make_df_from_varname_unit_dict(subtests, run_example_02_sql_fpath): def test_alternative_column_names(subtests, run_example_02_sql_fpath): vars_to_save = { "electrolyzer.hydrogen_out": {"alternative_name": "Electrolyzer Hydrogen Output"}, - "hopp.electricity_out": {"alternative_name": "Plant Electricity Output"}, + "hopp.electricity_out": {"units": "kW", "alternative_name": "Plant Electricity Output"}, "ammonia.ammonia_out": {"alternative_name": None}, "h2_storage.hydrogen_out": {"alternative_name": "H2 Storage Hydrogen Output"}, } @@ -100,10 +100,10 @@ def test_alternative_column_names(subtests, run_example_02_sql_fpath): ) expected_name_list = [ - "Electrolyzer Hydrogen Output", - "Plant Electricity Output", - "ammonia.ammonia_out", - "H2 Storage Hydrogen Output", + "Electrolyzer Hydrogen Output (kg/h)", + "Plant Electricity Output (kW)", + "ammonia.ammonia_out (kg/h)", + "H2 Storage Hydrogen Output (kg/h)", ] with subtests.test("Check number of columns"): @@ -112,6 +112,6 @@ def test_alternative_column_names(subtests, run_example_02_sql_fpath): with subtests.test("Check number of rows"): assert len(res) == 8760 - with subtests.test("All alternative names in dataframe"): - colnames_no_units = [c.split("(")[0].strip() for c in res.columns.to_list()] - assert all(alt_name in colnames_no_units for alt_name in expected_name_list) + with subtests.test("All vars in dataframe with units"): + expected_colnames = [f"{v_name}" for v_name in expected_name_list] + assert all(c_name in res.columns.to_list() for c_name in expected_colnames) From 3276716ca58987c899748722010a313afb54534d Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Fri, 9 Jan 2026 12:21:15 -0700 Subject: [PATCH 20/29] updated CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3b6b93e6..7b8f48921 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Added capability to have transport models that require user input parameters - Add geologic hydrogen surface processing converter - Add baseclass for caching functionality +- Added postprocessing function to save timeseries ## 0.5.1 [December 18, 2025] From cb26f81074ad0a790aec72ba70c52adb101c55e5 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Fri, 9 Jan 2026 12:22:55 -0700 Subject: [PATCH 21/29] renamed new files and updated import statements --- examples/02_texas_ammonia/run_texas_ammonia_plant.py | 2 +- .../{timeseries_to_csv.py => sql_timeseries_to_csv.py} | 0 ...est_timeseries_from_sql.py => test_sql_timeseries_to_csv.py} | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename h2integrate/postprocess/{timeseries_to_csv.py => sql_timeseries_to_csv.py} (100%) rename h2integrate/postprocess/test/{test_timeseries_from_sql.py => test_sql_timeseries_to_csv.py} (97%) diff --git a/examples/02_texas_ammonia/run_texas_ammonia_plant.py b/examples/02_texas_ammonia/run_texas_ammonia_plant.py index 20d3a98dc..8e79f9ad7 100644 --- a/examples/02_texas_ammonia/run_texas_ammonia_plant.py +++ b/examples/02_texas_ammonia/run_texas_ammonia_plant.py @@ -1,5 +1,5 @@ from h2integrate.core.h2integrate_model import H2IntegrateModel -from h2integrate.postprocess.timeseries_to_csv import save_case_timeseries_as_csv +from h2integrate.postprocess.sql_timeseries_to_csv import save_case_timeseries_as_csv # Create a H2Integrate model diff --git a/h2integrate/postprocess/timeseries_to_csv.py b/h2integrate/postprocess/sql_timeseries_to_csv.py similarity index 100% rename from h2integrate/postprocess/timeseries_to_csv.py rename to h2integrate/postprocess/sql_timeseries_to_csv.py diff --git a/h2integrate/postprocess/test/test_timeseries_from_sql.py b/h2integrate/postprocess/test/test_sql_timeseries_to_csv.py similarity index 97% rename from h2integrate/postprocess/test/test_timeseries_from_sql.py rename to h2integrate/postprocess/test/test_sql_timeseries_to_csv.py index 13862c501..82db9111f 100644 --- a/h2integrate/postprocess/test/test_timeseries_from_sql.py +++ b/h2integrate/postprocess/test/test_sql_timeseries_to_csv.py @@ -4,7 +4,7 @@ from h2integrate import EXAMPLE_DIR from h2integrate.core.h2integrate_model import H2IntegrateModel -from h2integrate.postprocess.timeseries_to_csv import save_case_timeseries_as_csv +from h2integrate.postprocess.sql_timeseries_to_csv import save_case_timeseries_as_csv @fixture From 6726c44e352de8f2a2221cd85e559aa6011efdee Mon Sep 17 00:00:00 2001 From: John Jasa Date: Fri, 9 Jan 2026 13:55:36 -0700 Subject: [PATCH 22/29] Minor comment updates --- h2integrate/core/pose_optimization.py | 5 +++-- h2integrate/postprocess/sql_timeseries_to_csv.py | 12 +++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/h2integrate/core/pose_optimization.py b/h2integrate/core/pose_optimization.py index c4eaff39b..2cc367e6d 100644 --- a/h2integrate/core/pose_optimization.py +++ b/h2integrate/core/pose_optimization.py @@ -448,8 +448,8 @@ def set_recorders(self, opt_prob): for current optimization problem Returns: - opt_prob (openmdao problem instance): openmdao problem instance for - current optimization problem edited to include a set up recorder + recorder_path (Path or None): Path to the recorder file if recorder is enabled, + None otherwise """ folder_output = self.config["general"]["folder_output"] @@ -550,6 +550,7 @@ def set_recorders(self, opt_prob): "excludes", ["*resource_data"] ) return recorder_path + return None def set_restart(self, opt_prob): diff --git a/h2integrate/postprocess/sql_timeseries_to_csv.py b/h2integrate/postprocess/sql_timeseries_to_csv.py index c94f47235..e3703d041 100644 --- a/h2integrate/postprocess/sql_timeseries_to_csv.py +++ b/h2integrate/postprocess/sql_timeseries_to_csv.py @@ -30,7 +30,7 @@ def check_get_units_for_var(case, var, electricity_base_unit: str, user_specifie Returns: 2-element tuple containing - - **val** (np.ndarry | list | tuple): value of the `var` in units `var_unit`. + - **val** (np.ndarray | list | tuple): value of the `var` in units `var_unit`. - **var_unit** (str): units that `val` is returned in. """ electricity_type_units = ["W", "kW", "MW", "GW"] @@ -58,6 +58,8 @@ def check_get_units_for_var(case, var, electricity_base_unit: str, user_specifie # 4) Get the variable value in the default units val = case.get_val(var, units=var_unit) + + # 5) Return the variable value and the corresponding units return val, var_unit @@ -125,8 +127,8 @@ def save_case_timeseries_as_csv( # check that only one sql file was input if len(sql_files) > 1: msg = ( - f"{sql_fpath} points to {len(sql_files)} sql files, please specify the filepath " - "of a single sql file." + f"{sql_fpath} points to {len(sql_files)} different sql files, please specify the " + f"filepath of a single sql file." ) raise FileNotFoundError(msg) @@ -144,7 +146,7 @@ def save_case_timeseries_as_csv( var_list.sort() # if vars_to_save is not empty, then only include the variables in var_list - if bool(vars_to_save): + if vars_to_save: if isinstance(vars_to_save, dict): varnames_to_save = list(vars_to_save.keys()) var_list = [v for v in var_list if v in varnames_to_save] @@ -190,7 +192,7 @@ def save_case_timeseries_as_csv( alternative_name = vars_to_save[var].get("alternative_name", None) # Otherwise, just pull the units directly # This means that you can only specify units by itself, not alternative names - # Should we make all of these be enteres as dicts then? + # Should we make all of these be entered as dicts then? else: user_units = vars_to_save.get(var, None) From 3bb1066306884f29d0cd94154c3e5176035cd731 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Mon, 12 Jan 2026 10:49:17 -0700 Subject: [PATCH 23/29] minor updates based on feedback --- examples/02_texas_ammonia/run_texas_ammonia_plant.py | 4 +++- h2integrate/postprocess/sql_timeseries_to_csv.py | 6 +++--- h2integrate/postprocess/test/test_sql_timeseries_to_csv.py | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/examples/02_texas_ammonia/run_texas_ammonia_plant.py b/examples/02_texas_ammonia/run_texas_ammonia_plant.py index 8e79f9ad7..b818580c2 100644 --- a/examples/02_texas_ammonia/run_texas_ammonia_plant.py +++ b/examples/02_texas_ammonia/run_texas_ammonia_plant.py @@ -13,13 +13,15 @@ # Save all timeseries data to a csv file timeseries_data = save_case_timeseries_as_csv(h2i.recorder_path, save_to_file=True) -# Get a subset of timeseries data and don't save it to a csv file +# Get a subset of timeseries data vars_to_save = [ "electrolyzer.hydrogen_out", "hopp.electricity_out", "ammonia.ammonia_out", "h2_storage.hydrogen_out", ] + +# Don't save subset of timeseries to a csv file using save_to_file=False timeseries_data = save_case_timeseries_as_csv( h2i.recorder_path, vars_to_save=vars_to_save, save_to_file=False ) diff --git a/h2integrate/postprocess/sql_timeseries_to_csv.py b/h2integrate/postprocess/sql_timeseries_to_csv.py index e3703d041..c8dd2d62c 100644 --- a/h2integrate/postprocess/sql_timeseries_to_csv.py +++ b/h2integrate/postprocess/sql_timeseries_to_csv.py @@ -65,7 +65,7 @@ def check_get_units_for_var(case, var, electricity_base_unit: str, user_specifie def save_case_timeseries_as_csv( sql_fpath: Path | str, - case_index: int = 0, + case_index: int = -1, electricity_base_unit="MW", vars_to_save: dict | list = {}, save_to_file: bool = True, @@ -81,7 +81,7 @@ def save_case_timeseries_as_csv( Args: sql_fpath (Path | str): Filepath to sql recorder file. case_index (int, optional): Index of the case in the sql file to save results for. - Defaults to 0. + Defaults to -1. electricity_base_unit (str, optional): Units to save any electricity-based profiles in. Must be either "W", "kW", "MW", or "GW". Defaults to "MW". vars_to_save (dict | list, optional): An empty list or dictionary indicates to save @@ -168,7 +168,7 @@ def save_case_timeseries_as_csv( # get the value val = case.get_val(var) - # Skip costs that are per year of plant life + # Skip costs that are per year of plant life (not per timestep) if "varopex" in var.lower() or "annual_fixed_costs" in var.lower(): continue diff --git a/h2integrate/postprocess/test/test_sql_timeseries_to_csv.py b/h2integrate/postprocess/test/test_sql_timeseries_to_csv.py index 82db9111f..4eb2152c5 100644 --- a/h2integrate/postprocess/test/test_sql_timeseries_to_csv.py +++ b/h2integrate/postprocess/test/test_sql_timeseries_to_csv.py @@ -25,7 +25,7 @@ def run_example_02_sql_fpath(): def test_save_csv_all_results(subtests, run_example_02_sql_fpath): - expected_csv_fpath = EXAMPLE_DIR / "02_texas_ammonia" / "outputs" / "cases_Case0.csv" + expected_csv_fpath = EXAMPLE_DIR / "02_texas_ammonia" / "outputs" / "cases_Case-1.csv" res = save_case_timeseries_as_csv(run_example_02_sql_fpath, save_to_file=True) with subtests.test("Check number of columns"): From 96075218abd7ca28154601cf8b1c8aec3907506e Mon Sep 17 00:00:00 2001 From: Genevieve Starke Date: Fri, 16 Jan 2026 16:38:33 -0500 Subject: [PATCH 24/29] Add to docs --- docs/user_guide/postprocessing_results.md | 35 +++++++++++++++++++ .../run_texas_ammonia_plant.py | 2 +- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/docs/user_guide/postprocessing_results.md b/docs/user_guide/postprocessing_results.md index 4df3ef15b..a7808fe85 100644 --- a/docs/user_guide/postprocessing_results.md +++ b/docs/user_guide/postprocessing_results.md @@ -88,3 +88,38 @@ print(model.prob.get_val("electrolyzer.total_hydrogen_produced", units='kg')) This will print the total hydrogen produced by the electrolyzer in kg. The `get_val` method is used to access the value of the variable in the `prob` object. The `units` argument is used to specify the units of the value to be returned. + +### Saving the outputs + +The time series outputs can be saved to a csv output using the `save_case_timeseries_as_csv` function. If no variables are specified, then the function saves all time series variables in the simulation. Otherwise, the specified variables are saves. +Here is an example of how to save time series results: +```python +from h2integrate.postprocess.sql_timeseries_to_csv import save_case_timeseries_as_csv + +# Create a H2Integrate model +model = H2IntegrateModel("top_level_config.yaml") + +# Run the model +model.run() + +model.post_process() + +# Save all timeseries data to a csv file +timeseries_data = save_case_timeseries_as_csv(h2i.recorder_path) + +# Get a subset of timeseries data +output_vars = [ + "electrolyzer.hydrogen_out", + "hopp.electricity_out", + "ammonia.ammonia_out", + "h2_storage.hydrogen_out", +] + +# Don't save subset of timeseries to a csv file using save_to_file=False +timeseries_data = save_case_timeseries_as_csv( + h2i.recorder_path, vars_to_save=output_vars, save_to_file=False +) + +``` +This example starts by saving all time series outputs to a csv file. Then, it specifies specific variables to pull out and outputs them as a dictionary (not a csv) when `save_to_file=False` + diff --git a/examples/02_texas_ammonia/run_texas_ammonia_plant.py b/examples/02_texas_ammonia/run_texas_ammonia_plant.py index 8cb82752a..a4e4bc0d4 100644 --- a/examples/02_texas_ammonia/run_texas_ammonia_plant.py +++ b/examples/02_texas_ammonia/run_texas_ammonia_plant.py @@ -17,7 +17,7 @@ h2i.post_process() # Save all timeseries data to a csv file -timeseries_data = save_case_timeseries_as_csv(h2i.recorder_path, save_to_file=True) +timeseries_data = save_case_timeseries_as_csv(h2i.recorder_path) # Get a subset of timeseries data vars_to_save = [ From 38084aabdfd9e02207bb13fc13e80820c5c52cc4 Mon Sep 17 00:00:00 2001 From: Genevieve Starke Date: Fri, 16 Jan 2026 16:44:43 -0500 Subject: [PATCH 25/29] Update Ex 2 and docs --- docs/user_guide/postprocessing_results.md | 2 +- examples/02_texas_ammonia/run_texas_ammonia_plant.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/user_guide/postprocessing_results.md b/docs/user_guide/postprocessing_results.md index a7808fe85..543c78445 100644 --- a/docs/user_guide/postprocessing_results.md +++ b/docs/user_guide/postprocessing_results.md @@ -89,7 +89,7 @@ This will print the total hydrogen produced by the electrolyzer in kg. The `get_val` method is used to access the value of the variable in the `prob` object. The `units` argument is used to specify the units of the value to be returned. -### Saving the outputs +### Saving outputs The time series outputs can be saved to a csv output using the `save_case_timeseries_as_csv` function. If no variables are specified, then the function saves all time series variables in the simulation. Otherwise, the specified variables are saves. Here is an example of how to save time series results: diff --git a/examples/02_texas_ammonia/run_texas_ammonia_plant.py b/examples/02_texas_ammonia/run_texas_ammonia_plant.py index a4e4bc0d4..430a68bb0 100644 --- a/examples/02_texas_ammonia/run_texas_ammonia_plant.py +++ b/examples/02_texas_ammonia/run_texas_ammonia_plant.py @@ -12,12 +12,12 @@ model.setup() model.prob.set_val("battery.electricity_demand", demand_profile, units="MW") # Run the model -h2i.run() +model.run() -h2i.post_process() +model.post_process() # Save all timeseries data to a csv file -timeseries_data = save_case_timeseries_as_csv(h2i.recorder_path) +timeseries_data = save_case_timeseries_as_csv(model.recorder_path) # Get a subset of timeseries data vars_to_save = [ @@ -29,5 +29,5 @@ # Don't save subset of timeseries to a csv file using save_to_file=False timeseries_data = save_case_timeseries_as_csv( - h2i.recorder_path, vars_to_save=vars_to_save, save_to_file=False + model.recorder_path, vars_to_save=vars_to_save, save_to_file=False ) From 32d51061874a12621ae9762d1c6c604a66ba3ed9 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Tue, 20 Jan 2026 10:52:25 -0700 Subject: [PATCH 26/29] minor updates to doc page for formatting --- docs/user_guide/postprocessing_results.md | 6 ++++-- examples/02_texas_ammonia/run_texas_ammonia_plant.py | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/user_guide/postprocessing_results.md b/docs/user_guide/postprocessing_results.md index 543c78445..f427ebb99 100644 --- a/docs/user_guide/postprocessing_results.md +++ b/docs/user_guide/postprocessing_results.md @@ -92,8 +92,10 @@ The `units` argument is used to specify the units of the value to be returned. ### Saving outputs The time series outputs can be saved to a csv output using the `save_case_timeseries_as_csv` function. If no variables are specified, then the function saves all time series variables in the simulation. Otherwise, the specified variables are saves. -Here is an example of how to save time series results: +An example of how to save time series results is shown in `examples/02_texas_ammonia/run_texas_ammonia_plant.py`: + ```python +from h2integrate.core.h2integrate_model import H2IntegrateModel from h2integrate.postprocess.sql_timeseries_to_csv import save_case_timeseries_as_csv # Create a H2Integrate model @@ -121,5 +123,5 @@ timeseries_data = save_case_timeseries_as_csv( ) ``` -This example starts by saving all time series outputs to a csv file. Then, it specifies specific variables to pull out and outputs them as a dictionary (not a csv) when `save_to_file=False` +This example starts by saving all time series outputs to a csv file. Then, it specifies specific variables to pull out and outputs them as a dictionary (not a csv) when `save_to_file=False`. diff --git a/examples/02_texas_ammonia/run_texas_ammonia_plant.py b/examples/02_texas_ammonia/run_texas_ammonia_plant.py index 430a68bb0..14c766e51 100644 --- a/examples/02_texas_ammonia/run_texas_ammonia_plant.py +++ b/examples/02_texas_ammonia/run_texas_ammonia_plant.py @@ -6,11 +6,13 @@ # Create a H2Integrate model model = H2IntegrateModel("02_texas_ammonia.yaml") + # Set battery demand profile to electrolyzer capacity # TODO: Update with demand module once it is developed demand_profile = np.ones(8760) * 640.0 model.setup() model.prob.set_val("battery.electricity_demand", demand_profile, units="MW") + # Run the model model.run() From 4a5dc35828d17d3a33d2dafebfb1f0ac7f49c9aa Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Tue, 20 Jan 2026 11:33:57 -0700 Subject: [PATCH 27/29] updated test since merged in develop --- h2integrate/postprocess/test/test_sql_timeseries_to_csv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h2integrate/postprocess/test/test_sql_timeseries_to_csv.py b/h2integrate/postprocess/test/test_sql_timeseries_to_csv.py index 4eb2152c5..fd4d85e74 100644 --- a/h2integrate/postprocess/test/test_sql_timeseries_to_csv.py +++ b/h2integrate/postprocess/test/test_sql_timeseries_to_csv.py @@ -29,7 +29,7 @@ def test_save_csv_all_results(subtests, run_example_02_sql_fpath): res = save_case_timeseries_as_csv(run_example_02_sql_fpath, save_to_file=True) with subtests.test("Check number of columns"): - assert len(res.columns.to_list()) == 15 + assert len(res.columns.to_list()) == 26 with subtests.test("Check number of rows"): assert len(res) == 8760 From 5ce318c3a8086d68d57962ca631f39c5a5f3472d Mon Sep 17 00:00:00 2001 From: John Jasa Date: Tue, 20 Jan 2026 22:33:35 -0700 Subject: [PATCH 28/29] Added doc content to address review feedback --- docs/user_guide/postprocessing_results.md | 67 +++++++++++++++++++---- 1 file changed, 57 insertions(+), 10 deletions(-) diff --git a/docs/user_guide/postprocessing_results.md b/docs/user_guide/postprocessing_results.md index f427ebb99..5c1e47578 100644 --- a/docs/user_guide/postprocessing_results.md +++ b/docs/user_guide/postprocessing_results.md @@ -91,25 +91,35 @@ The `units` argument is used to specify the units of the value to be returned. ### Saving outputs -The time series outputs can be saved to a csv output using the `save_case_timeseries_as_csv` function. If no variables are specified, then the function saves all time series variables in the simulation. Otherwise, the specified variables are saves. -An example of how to save time series results is shown in `examples/02_texas_ammonia/run_texas_ammonia_plant.py`: +The time series outputs can be saved to a csv output using the `save_case_timeseries_as_csv` function. If no variables are specified, then the function saves all time series variables in the simulation. Otherwise, the specified variables are saved. + +The `vars_to_save` argument supports three different input formats: + +1. **List of variable names** - saves variables with their default units +2. **Dictionary with units** - keys are variable names, values are the desired units +3. **Dictionary with options** - keys are variable names, values are dictionaries with `"units"` and/or `"alternative_name"` keys + +#### Example 1: Save all timeseries data ```python from h2integrate.core.h2integrate_model import H2IntegrateModel from h2integrate.postprocess.sql_timeseries_to_csv import save_case_timeseries_as_csv -# Create a H2Integrate model +# Create and run a H2Integrate model model = H2IntegrateModel("top_level_config.yaml") - -# Run the model model.run() - model.post_process() # Save all timeseries data to a csv file -timeseries_data = save_case_timeseries_as_csv(h2i.recorder_path) +timeseries_data = save_case_timeseries_as_csv(model.recorder_path) +``` + +#### Example 2: Specify variables as a list -# Get a subset of timeseries data +When providing a list of variable names, the function uses the default units for each variable. + +```python +# Get a subset of timeseries data using a list of variable names output_vars = [ "electrolyzer.hydrogen_out", "hopp.electricity_out", @@ -119,9 +129,46 @@ output_vars = [ # Don't save subset of timeseries to a csv file using save_to_file=False timeseries_data = save_case_timeseries_as_csv( - h2i.recorder_path, vars_to_save=output_vars, save_to_file=False + model.recorder_path, vars_to_save=output_vars, save_to_file=False +) +``` + +#### Example 3: Specify variables with custom units + +When providing a dictionary with variable names as keys and unit strings as values, the function converts each variable to the specified units. + +```python +# Specify variables with custom units +vars_with_units = { + "ammonia.hydrogen_in": "kg/h", + "h2_storage.hydrogen_in": "kg/h", + "electrolyzer.electricity_in": "kW", +} + +timeseries_data = save_case_timeseries_as_csv( + model.recorder_path, vars_to_save=vars_with_units, save_to_file=False ) +``` + +#### Example 4: Specify variables with alternative column names +When providing a dictionary with variable names as keys and dictionaries as values, you can specify both custom units and alternative column names for the output DataFrame. + +```python +# Specify variables with alternative names and/or units +vars_with_options = { + "electrolyzer.hydrogen_out": {"alternative_name": "Electrolyzer Hydrogen Output"}, + "hopp.electricity_out": {"units": "kW", "alternative_name": "Plant Electricity Output"}, + "ammonia.ammonia_out": {"alternative_name": None}, # Uses default variable name + "h2_storage.hydrogen_out": {"alternative_name": "H2 Storage Hydrogen Output"}, +} + +timeseries_data = save_case_timeseries_as_csv( + model.recorder_path, vars_to_save=vars_with_options, save_to_file=False +) +# Resulting columns: "Electrolyzer Hydrogen Output (kg/h)", "Plant Electricity Output (kW)", etc. ``` -This example starts by saving all time series outputs to a csv file. Then, it specifies specific variables to pull out and outputs them as a dictionary (not a csv) when `save_to_file=False`. +```{note} +The `electricity_base_unit` argument (default: `"MW"`) controls the units used for electricity-based variables when no specific units are provided. Valid options are `"W"`, `"kW"`, `"MW"`, or `"GW"`. +``` From 6fb4e6caafd901552aa0a843b34e904be94c3a4b Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Wed, 21 Jan 2026 10:03:20 -0700 Subject: [PATCH 29/29] updated tests for changes to example --- h2integrate/postprocess/test/test_sql_timeseries_to_csv.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/h2integrate/postprocess/test/test_sql_timeseries_to_csv.py b/h2integrate/postprocess/test/test_sql_timeseries_to_csv.py index fd4d85e74..993c31764 100644 --- a/h2integrate/postprocess/test/test_sql_timeseries_to_csv.py +++ b/h2integrate/postprocess/test/test_sql_timeseries_to_csv.py @@ -29,7 +29,7 @@ def test_save_csv_all_results(subtests, run_example_02_sql_fpath): res = save_case_timeseries_as_csv(run_example_02_sql_fpath, save_to_file=True) with subtests.test("Check number of columns"): - assert len(res.columns.to_list()) == 26 + assert len(res.columns.to_list()) == 35 with subtests.test("Check number of rows"): assert len(res) == 8760 @@ -41,7 +41,7 @@ def test_save_csv_all_results(subtests, run_example_02_sql_fpath): def test_make_df_from_varname_list(subtests, run_example_02_sql_fpath): vars_to_save = [ "electrolyzer.hydrogen_out", - "hopp.electricity_out", + "combiner.electricity_out", "ammonia.ammonia_out", "h2_storage.hydrogen_out", ] @@ -88,7 +88,7 @@ def test_make_df_from_varname_unit_dict(subtests, run_example_02_sql_fpath): def test_alternative_column_names(subtests, run_example_02_sql_fpath): vars_to_save = { "electrolyzer.hydrogen_out": {"alternative_name": "Electrolyzer Hydrogen Output"}, - "hopp.electricity_out": {"units": "kW", "alternative_name": "Plant Electricity Output"}, + "combiner.electricity_out": {"units": "kW", "alternative_name": "Plant Electricity Output"}, "ammonia.ammonia_out": {"alternative_name": None}, "h2_storage.hydrogen_out": {"alternative_name": "H2 Storage Hydrogen Output"}, }