Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

hpxml file has a start to reading in the performance data points (line 850ish) #143

Open
wants to merge 46 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
5018b49
bug fix for garage interior ceiling
mnblonsky Dec 21, 2023
e3a061d
OS-HPXML updates for zone name and spas
mnblonsky Dec 21, 2023
09e630a
replace hot tub with spa in docs
mnblonsky Dec 21, 2023
ab8e3a4
add input to edit occupancy
mnblonsky Dec 21, 2023
f5967ee
Merge branch 'pysam-bugs' into os-hpxml-1.7
mnblonsky Dec 29, 2023
d9d817f
Merge branch 'new-controls' into os-hpxml-1.7
mnblonsky Mar 12, 2024
f1f8ed6
Merge branch 'pysam-bugs' into os-hpxml-1.7
mnblonsky Mar 12, 2024
71d97da
fixed issue with schedule file import with pandas v2.2
mnblonsky Mar 12, 2024
53c13f5
Merge branch 'pysam-bugs' into os-hpxml-1.7
mnblonsky Mar 12, 2024
9491339
updated gitignore for venv
mnblonsky Mar 14, 2024
7a17bec
add ignore_error option in Analysis.load_timeseries_file
mnblonsky Mar 14, 2024
deb8a73
Merge branch 'new-controls' into os-hpxml-1.7
mnblonsky Mar 28, 2024
4d0deda
updates for spa equipment
mnblonsky May 24, 2024
18ede07
fixed bug for doors on adjacent/adiabatic walls
mnblonsky May 29, 2024
8b12a75
docs updates
mnblonsky Apr 5, 2024
e3f13d5
ignoring Vacancy and Power Outage schedule columns
mnblonsky May 31, 2024
8ecbf02
Adding new HVAC efficiency options to match latest ResStock
jmaguire1 Jul 3, 2024
47590c7
Add some more cooling options
jmaguire1 Jul 3, 2024
2b64b6d
additional HVAC multispeed param changes
mnblonsky Jul 3, 2024
f801309
Merge branch 'dev' into new_hvac
mnblonsky Jul 8, 2024
5f785c5
update changelog
mnblonsky Jul 8, 2024
094f8d2
Add multi-speed parameters for HVAC in ResStock 2024 (#130)
jmaguire1 Jul 8, 2024
b91e742
Merge branch 'dev' into os-hpxml-1.7
mnblonsky Aug 2, 2024
95c0ecb
update input files using ResStock v3.2
mnblonsky Aug 2, 2024
b623bc3
new sample file with ERWH
mnblonsky Aug 2, 2024
355cd53
minor fix in run_dwelling
mnblonsky Aug 2, 2024
beb37e4
minor script updates
mnblonsky Aug 2, 2024
3bee50d
added occupancy option in run_dwelling
mnblonsky Aug 2, 2024
6b0c3dc
updated changelog
mnblonsky Aug 2, 2024
5a33493
noted OS-HPXML restrictions in docs
mnblonsky Aug 2, 2024
6c1d2b9
OS-HPXML v1.7 (#132)
mnblonsky Aug 2, 2024
e17b904
Added DSE=1 for Room AC
sugirdhalakshmi Aug 29, 2024
4cec4ba
Added DSE=1 for Room AC (#138)
jmaguire1 Sep 4, 2024
18d5098
basic draft of reading in detailed performance
kendallbaertlein Sep 16, 2024
73c6dd7
worked on heat pump imports (line 850 ish)
kendallbaertlein Sep 16, 2024
e0c45bc
runs with hpxml.py
kendallbaertlein Sep 16, 2024
8fac9f7
change path to hpxml file so we can both use run_dwelling.py
jmaguire1 Sep 18, 2024
340f53a
test hpxml file
kendallbaertlein Sep 18, 2024
2e80008
Add files via upload
kendallbaertlein Sep 18, 2024
1754046
Fix test input file to be from ResStock, rather than OS-HPXML. OCHRE …
jmaguire1 Sep 19, 2024
f9f3a86
making heating and cooling detailed performance data available kwargs…
jmaguire1 Sep 19, 2024
9663f4b
added in interpolate functions to hvac.py, nonfunctional
kendallbaertlein Oct 14, 2024
b8289b4
updated with more changes to interpolate, still doesn't run
kendallbaertlein Oct 14, 2024
f28d867
updated with (still not running) code for the interpolation/extrapola…
kendallbaertlein Oct 17, 2024
50d19eb
updated a bit, methods still not complete.
kendallbaertlein Oct 23, 2024
51f31dc
updated exceptions
kendallbaertlein Oct 28, 2024
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
5 changes: 1 addition & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,4 @@ docs/_build
*.code-workspace
environment.yml

defaults/Input Files/OCHRE*
outputs/
test/outputs/
ochre/Models/SAM_weather_*.csv
ochre/defaults/Input Files/OCHRE*

Large diffs are not rendered by default.

9 changes: 7 additions & 2 deletions bin/run_dwelling.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
'time_zone': None, # option to specify daylight savings, in development

# Input parameters - Sample building (uses HPXML file and time series schedule file)
'hpxml_file': os.path.join(default_input_path, 'Input Files', 'sample_resstock_properties.xml'),
'hpxml_file': os.path.join(default_input_path, 'Input Files', 'detailed-heat-pump-performance.xml'),
'schedule_input_file': os.path.join(default_input_path, 'Input Files', 'sample_resstock_schedule.csv'),

# Input parameters - weather (note weather_path can be used when Weather Station is specified in HPXML file)
Expand Down Expand Up @@ -53,6 +53,11 @@
# }},
# },

# Occupancy parameters
# 'Occupancy': {
# 'Number of Occupants (-)': 3,
# },

# Equipment parameters
'Equipment': {
# HVAC equipment
Expand Down Expand Up @@ -81,7 +86,7 @@
# 'save_ebm_results': True,
# },
# 'Heat Pump Water Heater': {
# 'HPWH COP': 4.5,
# 'HPWH COP (-)': 4.5,
# # 'hp_only_mode': True
# },
# 'Electric Resistance Water Heater': {
Expand Down
1 change: 0 additions & 1 deletion bin/run_equipment.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,6 @@ def run_equipment_from_house_model():
equipment.results_file = dwelling.results_file

# If necessary, update equipment schedule
assert equipment.schedule is None
equipment.model.schedule['Zone Temperature (C)'] = 20
equipment.reset_time()

Expand Down
2 changes: 1 addition & 1 deletion bin/run_external_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,4 +166,4 @@ def run_controls_from_file(control_file):
# run_with_schedule_control()
run_constant_control_signal(example_control_signal)
# run_with_hvac_controller()
# run_controls_from_file(external_control_file='path/to/control_file.csv')
# run_controls_from_file('path/to/control_file.csv')
3 changes: 2 additions & 1 deletion bin/run_fleet.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ def run_water_heater_fleet(num_water_heaters=5):


def run_fleet_controlled():
#TODO
# TODO: not working, convert to battery fleet
# maybe add another example with EV fleet
equipment_args = {
# Equipment parameters
# See defaults/Battery/default_parameters.csv for more options
Expand Down
8 changes: 8 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
## OCHRE Changelog

### New from PRs

- Added multi-speed HVAC parameters for ResStock 2024 dataset
- Updated with OS-HPXML v1.7 naming conventions (e.g., spa equipment, indoor zone)
- Fixed garage interior ceiling connections
- Fixed issue with adjacent doors (e.g., for multi-family units, hallways)
- Allowed "Occupancy" adjustments in input arguments

### OCHRE v0.8.5-beta

- Updated PV model to integrate with PVWatts using PySAM v5.0 (not backwards compatible)
Expand Down
9 changes: 5 additions & 4 deletions docs/source/InputsAndArguments.rst
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,9 @@ The table below lists the optional arguments for creating a ``Dwelling`` model.
``ext_time_res`` datetime.timedelta None Time resolution for external controller. Required for Duty Cycle control.
``seed`` int or string HPXML or schedule file Random seed for initial temperatures and EV event data
``modify_hpxml_dict`` dict empty dict Dictionary that directly modifies values from HPXML file
``Envelope`` dict empty dict Includes envelope specific arguments
``Equipment`` dict empty dict Includes equipment specific arguments
``Occupancy`` dict empty dict Includes arguments for building occupancy
``Envelope`` dict empty dict Includes arguments for the building Envelope
``Equipment`` dict empty dict Includes Equipment-specific arguments
========================== ========================= ============================== ====================================================================================================================================================================

.. [#] While not required, a warm up period **is recommended**. The warm up gets more accurate initial conditions
Expand Down Expand Up @@ -660,9 +661,9 @@ lighting, and miscellaneous electric and gas loads:
+----------+-------------------+-------------------+
| Other | ``ScheduledLoad`` | Pool Heater |
+----------+-------------------+-------------------+
| Other | ``ScheduledLoad`` | Hot Tub Pump |
| Other | ``ScheduledLoad`` | Spa Pump |
+----------+-------------------+-------------------+
| Other | ``ScheduledLoad`` | Hot Tub Heater |
| Other | ``ScheduledLoad`` | Spa Heater |
+----------+-------------------+-------------------+
| Other | ``ScheduledLoad`` | Ceiling Fan |
+----------+-------------------+-------------------+
Expand Down
4 changes: 3 additions & 1 deletion docs/source/ModelingApproach.rst
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ be electric or natural gas loads. Schedule-based loads include:

- Ceiling fan and ventilation fan

- Pool Equipment (pool pump and heater, hot tub pump and heater)
- Pool Equipment (pool pump and heater, spa pump and heater)

- Miscellaneous electric loads (television, other)

Expand Down Expand Up @@ -386,6 +386,8 @@ current list of technologies not supported in OCHRE is:

- Overhangs

- Cathedral ceilings

- Structural Insulated Panel (SIP) walls

- Ground source heat pumps
Expand Down
10 changes: 8 additions & 2 deletions ochre/Analysis.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import os
import re
import json
import pandas as pd
import datetime as dt
import pandas as pd
import pyarrow.parquet as pq
import numpy as np
import numba # required for array-based psychrolib
import psychrolib
Expand Down Expand Up @@ -33,7 +34,7 @@ def get_agg_func(column, agg_type='Time'):
return 'mean' if unit in units_to_mean else 'sum'


def load_timeseries_file(file_name, columns=None, resample_res=None, **kwargs):
def load_timeseries_file(file_name, columns=None, resample_res=None, ignore_errors=True, **kwargs):
# Loads OCHRE-defined timeseries files, csv and parquet options
# option to specify columns to load as a list. Will add 'Time' index
# option to resample the file at a given timedelta, will take the mean of all columns
Expand All @@ -44,6 +45,11 @@ def load_timeseries_file(file_name, columns=None, resample_res=None, **kwargs):

extn = os.path.splitext(file_name)[1]
if extn == '.parquet':
if ignore_errors and columns:
# check that all columns exist, see:
# https://stackoverflow.com/questions/65705660/ignore-columns-not-present-in-parquet-with-pyarrow-in-pandas
parquet_file = pq.ParquetFile(file_name)
columns = [c for c in columns if c in parquet_file.schema.names]
df = pd.read_parquet(file_name, columns=columns, **kwargs)
elif extn == '.csv':
df = pd.read_csv(file_name, index_col='Time', parse_dates=True, usecols=columns, **kwargs)
Expand Down
151 changes: 147 additions & 4 deletions ochre/Equipment/HVAC.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,11 @@ def __init__(self, envelope_model=None, use_ideal_capacity=None, **kwargs):
self.duct_dse = ducts.get('DSE (-)') # Duct distribution system efficiency
self.duct_zone = self.envelope_model.zones.get(ducts.get('Zone'))
if self.duct_dse is None:
# Calculate DSE using ASHRAE 152
self.duct_dse = utils_equipment.calculate_duct_dse(self, ducts, **kwargs)
if self.name == 'Room AC':
self.duct_dse = 1
else:
# Calculate DSE using ASHRAE 152
self.duct_dse = utils_equipment.calculate_duct_dse(self, ducts, **kwargs)
if self.duct_dse < 1 and self.duct_zone == self.zone:
self.warn(f'Ignoring duct DSE because ducts are in {self.zone.name} zone.')
self.duct_dse = 1
Expand Down Expand Up @@ -671,6 +674,16 @@ def __init__(self, control_type='Time', **kwargs):
min_time_in_low = kwargs.get('Minimum Low Time (minutes)', 5)
min_time_in_high = kwargs.get('Minimum High Time (minutes)', 5)
self.min_time_in_speed = [dt.timedelta(minutes=min_time_in_low), dt.timedelta(minutes=min_time_in_high)]

# not sure about the format of this
if kwargs.get('CoolingDetailedPerformance'):
self.cooling_detailed_performance = kwargs.get('CoolingDetailedPerformance')
else:
self.cooling_detailed_performance = None
if kwargs.get('HeatingDetailedPerformance'):
self.heating_detailed_performance = kwargs.get('HeatingDetailedPerformance')
else:
self.heating_detailed_performance = None

# Load biquadratic parameters from file - only keep those with the correct speed type
if not kwargs.get('Disable HVAC Biquadratics', False):
Expand All @@ -687,7 +700,7 @@ def __init__(self, control_type='Time', **kwargs):
(df_speed['HVAC Efficiency'] == rated_efficiency) &
(df_speed['Number of Speeds'] == self.n_speeds)]
if not len(speed_params):
raise OCHREException(f'Cannot find multispeed parameters for {rated_efficiency} {self.name}')
raise OCHREException(f'Cannot find multispeed parameters for {self.n_speeds}-speed {rated_efficiency} {self.name}')
assert len(speed_params) == 1
speed_params = speed_params.iloc[0].to_dict()

Expand Down Expand Up @@ -824,6 +837,120 @@ def run_thermostat_control(self, setpoint=None):
else:
raise OCHREException('Incompatible number of speeds for dynamic equipment:', self.n_speeds)

def calculate_odb_at_zero_cop_or_capacity(self, detailed_performance_data, user_odbs, property, find_high, min_cop_or_capacity=0): # os hpxml : https://github.com/NREL/OpenStudio-HPXML/blob/afdd3884ed151f1e985c90c85921d1a9eecd0548/HPXMLtoOpenStudio/resources/hvac.rb#L2921
if find_high:
if detailed_performance_data is None:
raise OCHREException('Unable to process heat pump detailed performance data', detailed_performance_data)
if user_odbs is None:
raise OCHREException('Unable to process User ODBs', user_odbs)
if user_odbs[-1] in detailed_performance_data:
odb_1 = user_odbs[-1]
odb_dp1 = detailed_performance_data[user_odbs[-1]] # data
else:
odb_dp1 = None
if user_odbs[-2] in detailed_performance_data:
odb_2 = user_odbs[-2]
odb_dp2 = detailed_performance_data[user_odbs[-2]]
else:
odb_dp2 = None
else:
if user_odbs[0] in detailed_performance_data:
odb_1 = user_odbs[0]
odb_dp1 = detailed_performance_data[user_odbs[0]]
else:
odb_dp1 = None
if user_odbs[1] in detailed_performance_data:
odb_2 = user_odbs[1]
odb_dp2 = detailed_performance_data[user_odbs[1]]
else:
odb_dp2 = None
v1 = float(odb_dp1[property][0])
v2 = float(odb_dp2[property][0])
slope = (float(odb_dp1[property][0]) - float(odb_dp2[property][0])) / (odb_1 - odb_2) # syntax ?

# # Datapoints don't trend toward zero COP?
# if find_high == True and slope >= 0:
# return 999999.0
# elif find_high == False and slope <= 0:
# return -999999.0
# TODO: use this when using gross cop/capacity (doesn't flag correctly with our min/max cop/capacity)


# solve for intercept
intercept = float(odb_dp2[property][0]) - slope*odb_2
# find target odb
target_odb = (min_cop_or_capacity-intercept)/slope
# add small buffer
delta_odb = 1.0
if find_high:
return target_odb - delta_odb
else:
return target_odb + delta_odb

def interpolate_to_odb_table_point(self, detailed_performance_data, property, target_odb): #capacity_description): # os hpxml: https://github.com/NREL/OpenStudio-HPXML/blob/afdd3884ed151f1e985c90c85921d1a9eecd0548/HPXMLtoOpenStudio/resources/hvac.rb#L2958
# TODO: when updating with gross cop/capacity, add in capacity description (right now they are combined with our "property")
if target_odb in detailed_performance_data.keys():
return detailed_performance_data[target_odb][property]

user_odbs = sorted(set(list(detailed_performance_data.keys())))
if max(user_odbs) < target_odb:
right_odb = user_odbs[-1]
left_odb = user_odbs[-2]
elif (min(user_odbs) > target_odb):
right_odb = user_odbs[1]
left_odb = user_odbs[0]
else:
right_odb = max(user_odbs)
left_odb = min(user_odbs)

slope = (float(detailed_performance_data[right_odb][property][0]) - float(detailed_performance_data[left_odb][property][0])) / (right_odb - left_odb)
val = (target_odb - left_odb) * slope + float(detailed_performance_data[left_odb][property][0])

return val

def interpolate_to_odb_table_points(self, detailed_performance_data): #, compressor_lockout_temp, weather_temp): # os hpxml https://github.com/NREL/OpenStudio-HPXML/blob/afdd3884ed151f1e985c90c85921d1a9eecd0548/HPXMLtoOpenStudio/resources/hvac.rb#L2872
# TODO: add compressor_lockout_temp and weather_temp
user_odbs = list(detailed_performance_data.keys())

properties = list(detailed_performance_data[list(detailed_performance_data.keys())[0]].keys())

if self.end_use == 'HVAC Cooling':
outdoor_dry_bulbs = []
# TODO: update to gross COP/capacity
high_odb_at_zero_cop = self.calculate_odb_at_zero_cop_or_capacity(detailed_performance_data, user_odbs, 'maximum_COP', True)
high_odb_at_zero_capacity = self.calculate_odb_at_zero_cop_or_capacity(detailed_performance_data, user_odbs, 'maximum_capacity', True)
low_odb_at_zero_cop = self.calculate_odb_at_zero_cop_or_capacity(detailed_performance_data, user_odbs, 'minimum_COP', False)
low_odb_at_zero_capacity = self.calculate_odb_at_zero_cop_or_capacity(detailed_performance_data, user_odbs, 'minimum_capacity', False)

outdoor_dry_bulbs += [max(low_odb_at_zero_cop, low_odb_at_zero_capacity, 55)] # min cooling odb
outdoor_dry_bulbs += [min(high_odb_at_zero_cop, high_odb_at_zero_capacity)] #, weather_temp)] # max cooling odb
else:
outdoor_dry_bulbs = []
# TODO: update to gross COP/capacity
high_odb_at_zero_cop = self.calculate_odb_at_zero_cop_or_capacity(detailed_performance_data, user_odbs, 'maximum_COP', True)
high_odb_at_zero_capacity = self.calculate_odb_at_zero_cop_or_capacity(detailed_performance_data, user_odbs, 'maximum_capacity', True)
low_odb_at_zero_cop = self.calculate_odb_at_zero_cop_or_capacity(detailed_performance_data, user_odbs, 'minimum_COP', False)
low_odb_at_zero_capacity = self.calculate_odb_at_zero_cop_or_capacity(detailed_performance_data, user_odbs, 'minimum_capacity', False)

outdoor_dry_bulbs += [max(low_odb_at_zero_cop, low_odb_at_zero_capacity)] #, compressor_lockout_temp, weather_temp)] # min heating odb
outdoor_dry_bulbs += [min(high_odb_at_zero_cop, high_odb_at_zero_capacity, 60)] # max heating odb

print("high odb zero cop", high_odb_at_zero_cop)
print("high odb zero cap", high_odb_at_zero_capacity)
print("low odb zero cop", low_odb_at_zero_cop)
print("low odb zero cap", low_odb_at_zero_capacity)


for target_odb in outdoor_dry_bulbs:
if target_odb not in user_odbs:
#make new sub dictionary for target odb in the detailed performance data dictionary
temp_dict = {}
temp_dict[target_odb] = {}
for property in properties:
temp_dict[target_odb][property] = [self.interpolate_to_odb_table_point(detailed_performance_data, property, target_odb)]
detailed_performance_data.update(temp_dict)


def calculate_biquadratic_param(self, param, speed_idx, flow_fraction=1, part_load_ratio=1):
# runs biquadratic equation for EIR or capacity given the speed index
# param is 'cap' or 'eir'
Expand All @@ -839,6 +966,18 @@ def calculate_biquadratic_param(self, param, speed_idx, flow_fraction=1, part_lo
if speed_idx == 0 or self.biquad_params is None:
return rated

# flag + interpolation here
detailed_data = True

if detailed_data == True:
if self.end_use == 'HVAC Cooling':
testval = self.interpolate_to_odb_table_points(self.cooling_detailed_performance)
elif self.end_use == 'HVAC Heating':
testval = self.interpolate_to_odb_table_points(self.heating_detailed_performance)
else:
raise OCHREException('Unable to determine if detailed data is heating or cooling', self.end_use)
print(testval)

# get biquadratic parameters for current speed
params = self.biquad_params[speed_idx]

Expand Down Expand Up @@ -1162,9 +1301,13 @@ def update_internal_control(self):
else:
return 'Off'

def run_er_thermostat_control(self):
def run_er_thermostat_control(self, temperature_offset=2):
# run thermostat control for ER element - lower the setpoint by the deadband
# TODO: add option to keep setpoint as is, e.g. when using external control
# input for how far off of setpoint (setpoint - user input)
# lockout after setpoint changes
# checking indoor temp
# staged backup (gradually increasing amount of capacity available) (lowest priority)
er_setpoint = self.temp_setpoint - self.temp_deadband
temp_indoor = self.zone.temperature

Expand Down
4 changes: 2 additions & 2 deletions ochre/Equipment/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@
'Well Pump': ScheduledLoad,
'Pool Pump': ScheduledLoad,
'Pool Heater': ScheduledLoad,
'Hot Tub Pump': ScheduledLoad,
'Hot Tub Heater': ScheduledLoad,
'Spa Pump': ScheduledLoad,
'Spa Heater': ScheduledLoad,
'Gas Grill': ScheduledLoad,
'Gas Fireplace': ScheduledLoad,
'Gas Lighting': ScheduledLoad,
Expand Down
2 changes: 1 addition & 1 deletion ochre/defaults/Envelope/Envelope Boundaries.csv
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Garage Wall,GW,EXT,GAR
Garage Attached Wall,GA,GAR,LIV
Garage Roof,GR,EXT,GAR
Garage Ceiling,GC,ATC,GAR
Garage Interior Ceiling,GI,ATC,GAR
Garage Interior Ceiling,GI,GAR,LIV
Garage Floor,GF,GND,GAR
Garage Door,GD,EXT,GAR
Garage Furniture,GM,GAR,GAR
Expand Down
Loading