Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ lib/
*.pyc
report.xml
resources/Gemfile.lock
/test/local_run

# Non-template postprocessing scripts
postprocessing/compare_comstock_to_ami.py
Expand Down
53 changes: 48 additions & 5 deletions measures/comstock_sensitivity_reports/measure.rb
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ def energyPlusOutputRequests(runner, user_arguments)
# request service water heating use
result << OpenStudio::IdfObject.load('Output:Variable,*,Water Use Connections Hot Water Volume,RunPeriod;').get

# request refrigeration defrost energy
result << OpenStudio::IdfObject.load('Output:Variable,*,Refrigeration Case Defrost Electricity Energy,RunPeriod;').get # J
result << OpenStudio::IdfObject.load('Output:Variable,*,Refrigeration Walk In Defrost Electricity Energy,RunPeriod;').get # J

# request coil and fan energy use for HVAC equipment
result << OpenStudio::IdfObject.load('Output:Variable,*,Cooling Tower Make Up Water Volume,RunPeriod;').get # m3
result << OpenStudio::IdfObject.load('Output:Variable,*,Chiller COP,RunPeriod;').get
Expand Down Expand Up @@ -193,7 +197,6 @@ def energyPlusOutputRequests(runner, user_arguments)
end
end


# result << OpenStudio::IdfObject.load("Output:Variable,*,Fan #{elec} Energy,RunPeriod;").get # J
# result << OpenStudio::IdfObject.load("Output:Variable,*,Humidifier #{elec} Energy,RunPeriod;").get # J
# result << OpenStudio::IdfObject.load("Output:Variable,*,Evaporative Cooler #{elec} Energy,RunPeriod;").get # J
Expand Down Expand Up @@ -645,7 +648,7 @@ def run(runner, user_arguments)
end
end

if ann_env_pd == false
if (ann_env_pd.nil?) || (ann_env_pd == false)
runner.registerError('Cannot find a weather runperiod. Make sure you ran an annual simulation, not just the design days.')
return false
end
Expand Down Expand Up @@ -1150,7 +1153,7 @@ def run(runner, user_arguments)
unless ts_ahu_ma_flow_rate_kg_s_list.nil?
average_non_zero_loop_mass_flow_kg_s = ts_ahu_ma_flow_rate_kg_s_list.reject(&:zero?).sum.to_f / ts_ahu_ma_flow_rate_kg_s_list.reject(&:zero?).count
else
average_non_zero_loop_mass_flow_kg_s = -999
average_non_zero_loop_mass_flow_kg_s = 0.0
end

# Add to weighted
Expand All @@ -1159,8 +1162,8 @@ def run(runner, user_arguments)
air_system_weighted_fan_power_minimum_flow_fraction += fan_minimum_flow_frac * air_loop_mass_flow_rate_kg_s
air_system_weighted_fan_static_pressure += fan_static_pressure * air_loop_mass_flow_rate_kg_s
air_system_weighted_fan_efficiency += fan_efficiency * air_loop_mass_flow_rate_kg_s
if fan_var_vol
air_system_total_vav_mass_flow_kg_s += average_non_zero_loop_mass_flow_kg_s # Track VAV airflow separately for SP reset measure
if fan_var_vol && average_non_zero_loop_mass_flow_kg_s > 0.0
air_system_total_vav_mass_flow_kg_s += average_non_zero_loop_mass_flow_kg_s # Track VAV airflow separately for Static Pressure Reset measure
air_system_total_des_flow_rate_m3_s += des_flow_rate_m3_s
end
end
Expand Down Expand Up @@ -3800,6 +3803,46 @@ def system_type_for(pump)
runner.registerValue('com_report_shw_hp_water_heater_unmet_heat_transfer_demand_j', heat_pump_water_heater_unmet_heat_transfer_demand_j)
runner.registerValue('com_report_shw_non_hp_water_heater_unmet_heat_transfer_demand_j', water_heater_unmet_heat_transfer_demand_j)

# Refrigeration cases and walk-ins
# Medium temperature: caseOperatingTemperature > -3C; Low temperature: <= -3C
refrigeration_med_temp_case_count = 0.0
refrigeration_med_temp_case_total_defrost_electric_j = 0.0
refrigeration_low_temp_case_count = 0.0
refrigeration_low_temp_case_total_defrost_electric_j = 0.0
model.getRefrigerationCases.sort.each do |ref_case|
defrost_electric_j = sql_get_report_variable_data_double(runner, sql, ref_case, 'Refrigeration Case Defrost Electricity Energy')
if ref_case.caseOperatingTemperature > -3.0
refrigeration_med_temp_case_count += 1.0
refrigeration_med_temp_case_total_defrost_electric_j += defrost_electric_j
else
refrigeration_low_temp_case_count += 1.0
refrigeration_low_temp_case_total_defrost_electric_j += defrost_electric_j
end
end
runner.registerValue('com_report_refrigeration_med_temp_case_count', refrigeration_med_temp_case_count)
runner.registerValue('com_report_refrigeration_med_temp_case_total_defrost_electric_j', refrigeration_med_temp_case_total_defrost_electric_j, 'J')
runner.registerValue('com_report_refrigeration_low_temp_case_count', refrigeration_low_temp_case_count)
runner.registerValue('com_report_refrigeration_low_temp_case_total_defrost_electric_j', refrigeration_low_temp_case_total_defrost_electric_j, 'J')

refrigeration_med_temp_walk_in_count = 0.0
refrigeration_med_temp_walk_in_total_defrost_electric_j = 0.0
refrigeration_low_temp_walk_in_count = 0.0
refrigeration_low_temp_walk_in_total_defrost_electric_j = 0.0
model.getRefrigerationWalkIns.sort.each do |walk_in|
defrost_electric_j = sql_get_report_variable_data_double(runner, sql, walk_in, 'Refrigeration Walk In Defrost Electricity Energy')
if walk_in.operatingTemperature > -3.0
refrigeration_med_temp_walk_in_count += 1.0
refrigeration_med_temp_walk_in_total_defrost_electric_j += defrost_electric_j
else
refrigeration_low_temp_walk_in_count += 1.0
refrigeration_low_temp_walk_in_total_defrost_electric_j += defrost_electric_j
end
end
runner.registerValue('com_report_refrigeration_med_temp_walk_in_count', refrigeration_med_temp_walk_in_count)
runner.registerValue('com_report_refrigeration_med_temp_walk_in_total_defrost_electric_j', refrigeration_med_temp_walk_in_total_defrost_electric_j, 'J')
runner.registerValue('com_report_refrigeration_low_temp_walk_in_count', refrigeration_low_temp_walk_in_count)
runner.registerValue('com_report_refrigeration_low_temp_walk_in_total_defrost_electric_j', refrigeration_low_temp_walk_in_total_defrost_electric_j, 'J')

# Error and Warning count from eplusout.err file (sql does not have data)
err_path = File.join(File.dirname(sql.path.to_s), 'eplusout.err')
File.foreach(err_path).each do |line|
Expand Down
10 changes: 5 additions & 5 deletions measures/comstock_sensitivity_reports/measure.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
<schema_version>3.1</schema_version>
<name>com_stock_sensitivity_reports</name>
<uid>aeb81242-de7e-4613-af47-c1faf19d286a</uid>
<version_id>0955d5f5-3a15-4c10-9151-5ea649038f6b</version_id>
<version_modified>2025-09-17T17:07:23Z</version_modified>
<version_id>77e7e9b0-bc0d-4c7d-941a-f1ef6e059cee</version_id>
<version_modified>2026-04-07T20:45:33Z</version_modified>
<xml_checksum>A0069B90</xml_checksum>
<class_name>ComStockSensitivityReports</class_name>
<display_name>ComStock_Sensitivity_Reports</display_name>
Expand Down Expand Up @@ -76,13 +76,13 @@
<filename>measure.rb</filename>
<filetype>rb</filetype>
<usage_type>script</usage_type>
<checksum>58DAA5CC</checksum>
<checksum>2272FBBC</checksum>
</file>
<file>
<filename>Variable List.xlsx</filename>
<filetype>xlsx</filetype>
<usage_type>resource</usage_type>
<checksum>3B3560C3</checksum>
<checksum>F43A7411</checksum>
</file>
<file>
<filename>8172d17_0000008.osm</filename>
Expand Down Expand Up @@ -238,7 +238,7 @@
<filename>comstock_sensitivity_reports_test.rb</filename>
<filetype>rb</filetype>
<usage_type>test</usage_type>
<checksum>BA366C89</checksum>
<checksum>486ECDB4</checksum>
</file>
<file>
<filename>ground_heat_exchanger.osm</filename>
Expand Down
Binary file modified measures/comstock_sensitivity_reports/resources/Variable List.xlsx
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ def test_pump_spec
output_txt = JSON.parse(File.read("#{run_dir(__method__)}/output.txt"))
output_txt.each do |output_var|
puts("### DEBUGGING: output_var = #{output_var['name']} | value = #{output_var['value']}")

# check values with values from example model
if output_var['name'] == 'com_report_pump_flow_weighted_avg_motor_efficiency'
assert_in_delta(0.9086, output_var['value'].to_f, 0.01)
Expand All @@ -328,7 +328,6 @@ def test_pump_spec
if output_var['name'] == 'com_report_pump_count_swh_var_spd'
assert_equal(1, output_var['value'])
end

end
end
end
6 changes: 5 additions & 1 deletion postprocessing/comstockpostproc/comstock.py
Original file line number Diff line number Diff line change
Expand Up @@ -2241,6 +2241,7 @@ def plotting_columns(self):
# Universal
pcs += [self.UPGRADE_APPL, self.UPGRADE_NAME, self.BLDG_ID, self.CZ_ASHRAE, self.DATASET, self.BLDG_WEIGHT]
pcs += [self.col_name_to_weighted(c, new_units=UnitsMixin.UNIT.ENERGY.TBTU) for c in self.COLS_ENDUSE_ANN_ENGY]
pcs += [self.col_name_to_weighted(c, new_units=UnitsMixin.UNIT.ENERGY.TBTU) for c in self.COLS_GROCERY_REFRIG_DEFROST_ENDUSE]
pcs += [self.col_name_to_weighted(c, UnitsMixin.UNIT.MASS.CO2E_MMT) for c in self.GHG_FUEL_COLS]

cols = self.COLS_UTIL_BILLS + ['out.utility_bills.electricity_bill_max..usd', 'out.utility_bills.electricity_bill_min..usd']
Expand Down Expand Up @@ -2326,7 +2327,9 @@ def create_plotting_lazyframe(self):
column_downselection='detailed')

# Select only columns needed for plotting
wtd_agg_outs = wtd_agg_outs.select(self.plotting_columns())
available_cols = set(wtd_agg_outs.collect_schema().names())
plot_cols = [c for c in self.plotting_columns() if c in available_cols]
wtd_agg_outs = wtd_agg_outs.select(plot_cols)

# Write data to parquet file, hive partition on upgrade to make later processing faster
file_name = f'cached_ComStock_plotting_upgrade{upgrade_id}.parquet'
Expand Down Expand Up @@ -3371,6 +3374,7 @@ def add_weighted_area_energy_savings_columns(self, input_lf):
self.COLS_TOT_ANN_ENGY +
self.COLS_GEN_ANN_ENGY +
self.COLS_ENDUSE_ANN_ENGY +
self.COLS_GROCERY_REFRIG_DEFROST_ENDUSE +
self.COLS_ENDUSE_GROUP_TOT_ANN_ENGY +
self.COLS_ENDUSE_GROUP_ANN_ENGY +
self.load_component_cols()):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,12 @@ def make_plots(self, lazy_frame: pl.LazyFrame, column_for_grouping, timeseries_l
plot_method=self.plot_floor_area_and_energy_totals_by_building_type,
lazy_frame=lazy_frame.clone(),
columns=(self.lazyframe_plotter.BASE_COLUMNS + self.lazyframe_plotter.WTD_COLUMNS_SUMMARIZE))(**BASIC_PARAMS)
_lf_schema = lazy_frame.collect_schema().names()
_grocery_detail_cols = [c for c in self.lazyframe_plotter.WTD_COLS_GROCERY_REFRIG_DETAIL if c in _lf_schema]
LazyFramePlotter.plot_with_lazy(
plot_method=self.plot_end_use_totals_by_building_type,
lazy_frame=lazy_frame.clone(),
columns=(self.lazyframe_plotter.BASE_COLUMNS + self.lazyframe_plotter.WTD_COLUMNS_ANN_ENDUSE + [self.BLDG_TYPE, self.CEN_DIV]))(**BASIC_PARAMS)
columns=(self.lazyframe_plotter.BASE_COLUMNS + self.lazyframe_plotter.WTD_COLUMNS_ANN_ENDUSE + _grocery_detail_cols + [self.BLDG_TYPE, self.CEN_DIV]))(**BASIC_PARAMS)
LazyFramePlotter.plot_with_lazy(plot_method=self.plot_eui_histograms_by_building_type,
lazy_frame=lazy_frame.clone(), columns=(self.lazyframe_plotter.BASE_COLUMNS + self.lazyframe_plotter.EUI_ANN_TOTL_COLUMNS + [self.BLDG_TYPE]))(**BASIC_PARAMS)
LazyFramePlotter.plot_with_lazy(plot_method=self.plot_eui_boxplots_by_building_type,
Expand Down
57 changes: 52 additions & 5 deletions postprocessing/comstockpostproc/comstock_to_cbecs_comparison.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
logger = logging.getLogger(__name__)

class ComStockToCBECSComparison(NamingMixin, UnitsMixin, PlottingMixin):
def __init__(self, comstock_list: List[ComStock], cbecs_list: List[CBECS], upgrade_id=0, image_type='jpg', name=None, make_comparison_plots=True, make_hvac_plots = False):
def __init__(self, comstock_list: List[ComStock], cbecs_list: List[CBECS], upgrade_id=0, image_type='jpg', name=None, make_comparison_plots=True, make_hvac_plots = False, building_type: str | None = None):
"""
Creates the ComStock to CBECS comaprison plots.

Expand All @@ -30,6 +30,11 @@ def __init__(self, comstock_list: List[ComStock], cbecs_list: List[CBECS], upgra
image_type (str, optional): Image file type to use. Defaults to 'jpg'.
name (str, optional): Name of output directory. If None, a name will be generated. Defaults to None.
make_comparison_plots (bool, optional): Flag to create compairison plots. Defaults to True.
building_type (str | None, optional): ComStock building type to filter plots to.
Valid values: 'FullServiceRestaurant', 'QuickServiceRestaurant', 'RetailStripmall',
'RetailStandalone', 'SmallOffice', 'MediumOffice', 'LargeOffice', 'PrimarySchool',
'SecondarySchool', 'Outpatient', 'Hospital', 'SmallHotel', 'LargeHotel', 'Warehouse',
'Grocery'. If None, all building types are plotted.
"""
# Initialize members
self.comstock_list = comstock_list
Expand All @@ -41,6 +46,9 @@ def __init__(self, comstock_list: List[ComStock], cbecs_list: List[CBECS], upgra
self.column_for_grouping = self.DATASET
self.lazyframe_plotter: LazyFramePlotter = LazyFramePlotter()

# Optional building type filter for faster plotting.
requested_building_type = self._validate_building_type(building_type)

# Concatenate the datasets and create a color map
dfs_to_concat = []
comstock_dfs_to_concat = []
Expand Down Expand Up @@ -108,12 +116,25 @@ def __init__(self, comstock_list: List[ComStock], cbecs_list: List[CBECS], upgra
# Combine into a single dataframe for convenience
# self.data = pd.concat(dfs_to_concat, join='inner', ignore_index=True)

#There is no such a join='inner' in polars.concat, implement it manually
# Preserve optional grocery refrigeration detail columns in mixed CBECS/ComStock comparisons
# by adding zero-filled placeholders where they are missing before schema intersection.
optional_enduse_cols = list(self.lazyframe_plotter.WTD_COLS_GROCERY_REFRIG_DETAIL)
dfs_with_optional_cols = []
for df in dfs_to_concat:
missing_optional_cols = [c for c in optional_enduse_cols if c not in df.collect_schema().names()]
if missing_optional_cols:
df = df.with_columns([pl.lit(0.0).alias(c) for c in missing_optional_cols])
dfs_with_optional_cols.append(df)
dfs_to_concat = dfs_with_optional_cols

# There is no such a join='inner' in polars.concat, implement it manually
common_columns = set(dfs_to_concat[0].collect_schema().names())
for df in dfs_to_concat:
common_columns = common_columns.intersection(set(df.collect_schema().names()))
dfs_to_concat = [df.select(common_columns) for df in dfs_to_concat]
self.data: pl.LazyFrame = pl.concat(dfs_to_concat, how="vertical_relaxed")
if requested_building_type is not None:
self.data = self._filter_to_building_type(self.data, requested_building_type)
# Add Replicate Weights (CBECS only) for RSE calculations
cbecs_src = cbecs_list[0]
rep_cols = [self.BLDG_ID] + self.list_replicate_weight_cols(cbecs_src.data)
Expand Down Expand Up @@ -143,9 +164,12 @@ def __init__(self, comstock_list: List[ComStock], cbecs_list: List[CBECS], upgra
logger.info(f"Not including columns {all_columns - common_columns} in comstock only plots")
comstock_dfs_to_concat = [df.select(common_columns) for df in comstock_dfs_to_concat]
comstock_qoi_columns = [self.DATASET] + self.QOI_MAX_DAILY_TIMING_COLS + self.QOI_MAX_USE_COLS + self.QOI_MIN_USE_COLS + self.QOI_MAX_USE_COLS_NORMALIZED + self.QOI_MIN_USE_COLS_NORMALIZED
comstock_df: pl.LazyFrame = pl.concat(comstock_dfs_to_concat, how="vertical_relaxed").select(comstock_qoi_columns)
comstock_all_df: pl.LazyFrame = pl.concat(comstock_dfs_to_concat, how="vertical_relaxed")
if requested_building_type is not None:
comstock_all_df = self._filter_to_building_type(comstock_all_df, requested_building_type)
comstock_df: pl.LazyFrame = comstock_all_df.select(comstock_qoi_columns)
comstock_enduse_columns = [self.DATASET] + self.lazyframe_plotter.WTD_COLUMNS_ANN_ENDUSE + self.lazyframe_plotter.WTD_COLUMNS_ANN_PV + self.lazyframe_plotter.WTD_COLUMNS_SUMMARIZE
comstock_enduse_df: pl.LazyFrame = pl.concat(comstock_dfs_to_concat, how="vertical_relaxed").select(comstock_enduse_columns)
comstock_enduse_df: pl.LazyFrame = comstock_all_df.select(comstock_enduse_columns)

# Make directories
self.output_dir = os.path.join(current_dir, '..', 'output', self.name)
Expand Down Expand Up @@ -211,7 +235,12 @@ def make_plots(self, lazy_frame: pl.LazyFrame, column_for_grouping, color_map: d
LazyFramePlotter.plot_with_lazy(
plot_method=self.plot_end_use_totals_by_building_type,
lazy_frame=lazy_frame.clone(),
columns=( [column_for_grouping] + self.lazyframe_plotter.WTD_COLUMNS_ANN_ENDUSE + [self.BLDG_TYPE, self.CEN_DIV]))(**BASIC_PARAMS)
columns=(
[column_for_grouping]
+ self.lazyframe_plotter.WTD_COLUMNS_ANN_ENDUSE
+ self.lazyframe_plotter.WTD_COLS_GROCERY_REFRIG_DETAIL
+ [self.BLDG_TYPE, self.CEN_DIV]
))(**BASIC_PARAMS)

logger.info('Making EUI histogram by building type plots')
LazyFramePlotter.plot_with_lazy(
Expand Down Expand Up @@ -291,3 +320,21 @@ def export_to_csv_wide(self):
logger.info(f'Exported comparison data to {file_path}')
except Exception as e:
logger.error(f"CSV export failed: {e}")

def _validate_building_type(self, building_type: str | None) -> str | None:
if building_type is None:
return None

allowed_types = list(self.ORDERED_CATEGORIES[self.BLDG_TYPE])
requested = building_type.strip()
if requested not in allowed_types:
raise ValueError(
f"Invalid building_type '{building_type}'. Valid options are: {allowed_types}"
)
return requested

def _filter_to_building_type(self, lazy_frame: pl.LazyFrame, building_type: str) -> pl.LazyFrame:
schema = lazy_frame.collect_schema().names()
if self.BLDG_TYPE not in schema:
raise ValueError(f"Cannot filter by building type; missing required column: {self.BLDG_TYPE}")
return lazy_frame.filter(pl.col(self.BLDG_TYPE) == building_type)
3 changes: 3 additions & 0 deletions postprocessing/comstockpostproc/lazyframeplotter.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ def __init__(self):
self.WTD_COLUMNS_ANN_ENDUSE = [self.col_name_to_weighted(
col_name=c, new_units=UnitsMixin.UNIT.ENERGY.TBTU) for c in self.COLS_ENDUSE_ANN_ENGY]

self.WTD_COLS_GROCERY_REFRIG_DETAIL = [self.col_name_to_weighted(
col_name=c, new_units=UnitsMixin.UNIT.ENERGY.TBTU) for c in self.COLS_GROCERY_REFRIG_DEFROST_ENDUSE]

self.WTD_COLUMNS_ANN_PV = [self.col_name_to_weighted(
col_name=c, new_units=UnitsMixin.UNIT.ENERGY.TBTU) for c in self.COLS_GEN_ANN_ENGY]

Expand Down
Loading