Skip to content

Commit

Permalink
adding shower draw for unmet loads
Browse files Browse the repository at this point in the history
  • Loading branch information
mnblonsky committed Aug 28, 2024
1 parent 1174c46 commit b781132
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 63 deletions.
12 changes: 8 additions & 4 deletions ochre/Models/Water.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ class StratifiedWaterModel(RCModel):
"""
name = 'Water Tank'
optional_inputs = [
'Water Heating (L/min)',
'Water Fixtures (L/min)',
'Showers (L/min)',
'Clothes Washer (L/min)',
'Dishwasher (L/min)',
'Mains Temperature (C)',
Expand Down Expand Up @@ -133,7 +134,8 @@ def update_water_draw(self):
self.outlet_temp = self.states[self.t_1_idx] # initial outlet temp, for estimating draw volume

# Note: removing target draw temperature for clothes washers, not implemented in ResStock
draw_tempered = self.current_schedule.get('Water Heating (L/min)', 0)
draw_tempered = self.current_schedule.get('Water Fixtures (L/min)', 0)
draw_showers = self.current_schedule.get('Showers (L/min)', 0)
draw_hot = (self.current_schedule.get('Clothes Washer (L/min)', 0)
+ self.current_schedule.get('Dishwasher (L/min)', 0))
# draw_cw = self.current_schedule.get('Clothes Washer (L/min)', 0)
Expand Down Expand Up @@ -224,8 +226,10 @@ def update_water_draw(self):
self.h_delivered = q_delivered / t_s
heats_to_model += q_nodes / t_s

# calculate unmet loads, fixtures only, in W
self.h_unmet_load = max(draw_tempered / 60 * water_c * (self.tempered_draw_temp - self.outlet_temp), 0) # in W
# calculate unmet loads, showers only, in W
self.h_unmet_load = max(
draw_showers / 60 * water_c * (self.tempered_draw_temp - self.outlet_temp), 0
) # in W

return heats_to_model

Expand Down
3 changes: 1 addition & 2 deletions ochre/utils/hpxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -1116,8 +1116,7 @@ def parse_water_heater(water_heater, water, construction, solar_fraction=0):
distribution_gal_per_day = mw_gpd * fixture_multiplier

# Combine fixture and distribution water draws in schedule
wh['Fixture Average Water Draw (L/day)'] = convert(fixture_gal_per_day + distribution_gal_per_day, 'gallon/day',
'L/day')
wh['Average Water Draw (L/day)'] = convert(fixture_gal_per_day + distribution_gal_per_day, 'gallon/day', 'L/day')

return wh

Expand Down
124 changes: 67 additions & 57 deletions ochre/utils/schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
# 'basement_mels': 'Basement MELs', # not modeled
},
"Water": {
"hot_water_fixtures": "Water Heating",
"hot_water_fixtures": "Water Fixtures",
# "hot_water_showers": "Showers", # for unmet loads only
"hot_water_clothes_washer": "Clothes Washer",
"hot_water_dishwasher": "Dishwasher",
},
Expand All @@ -67,7 +68,6 @@
"water_heater_operating_mode": None,
"Vacancy": None,
"Power Outage": None,
"hot_water_showers": None, #TODO: use this for unmet loads
},
}

Expand Down Expand Up @@ -285,59 +285,67 @@ def create_simple_schedule(weekday_fractions, weekend_fractions=None, month_mult
return df['w_fracs'] * df['m_fracs']


def convert_schedule_column(s_hpxml, ochre_name, properties, category='Power'):
if category == 'Power':
# try getting from max power or from annual energy, priority goes to max power
if 'Max Electric Power (W)' in properties:
max_value = properties['Max Electric Power (W)'] / 1000 # W to kW
elif 'Annual Electric Energy (kWh)' in properties:
annual_mean = properties['Annual Electric Energy (kWh)'] / 8760
schedule_mean = s_hpxml.mean()
max_value = annual_mean / schedule_mean if schedule_mean != 0 else 0
else:
max_value = None
if max_value is not None:
out = s_hpxml * max_value
out.name = f'{ochre_name} (kW)'
else:
out = None

# check for gas (max power and annual energy), and copy schedule
if 'Max Gas Power (therms/hour)' in properties:
max_value = properties['Max Gas Power (therms/hour)'] # in therms/hour
elif 'Annual Gas Energy (therms)' in properties:
annual_mean = properties['Annual Gas Energy (therms)'] / 8760 # in therms/hour
schedule_mean = s_hpxml.mean()
max_value = annual_mean / schedule_mean if schedule_mean != 0 else 0
else:
max_value = None
if max_value is None:
pass
elif out is None:
out = s_hpxml * max_value
out.name = f'{ochre_name} (therms/hour)'
else:
# combine 2 series into data frame
s_gas = s_hpxml * max_value
s_gas.name = f'{ochre_name} (therms/hour)'
out = pd.concat([out, s_gas], axis=1)

if out is None:
raise OCHREException(f'Could not determine max value for {s_hpxml.name} schedule ({ochre_name}).')

elif category == 'Water':
if ochre_name == 'Water Heating':
# Fixtures include sinks, showers, and baths (SSB), all combined
avg_water_draw = properties.get('Fixture Average Water Draw (L/day)', 0)
annual_mean = avg_water_draw / 1440 # in L/min
else:
# For dishwasher and clothes washer, get average draw value from their properties dict
annual_mean = properties['Average Water Draw (L/day)'] / 1440 # in L/min
schedule_mean = s_hpxml.mean()
def convert_power_column(s_hpxml, ochre_name, properties):
# try getting from max power or from annual energy, priority goes to max power
if 'Max Electric Power (W)' in properties:
max_value = properties['Max Electric Power (W)'] / 1000 # W to kW
elif 'Annual Electric Energy (kWh)' in properties:
annual_mean = properties['Annual Electric Energy (kWh)'] / 8760
schedule_mean = s_hpxml.mean()
max_value = annual_mean / schedule_mean if schedule_mean != 0 else 0
else:
max_value = None
if max_value is not None:
out = s_hpxml * max_value
out.name = f'{ochre_name} (L/min)'
out.name = f'{ochre_name} (kW)'
else:
out = None

# check for gas (max power and annual energy), and copy schedule
if 'Max Gas Power (therms/hour)' in properties:
max_value = properties['Max Gas Power (therms/hour)'] # in therms/hour
elif 'Annual Gas Energy (therms)' in properties:
annual_mean = properties['Annual Gas Energy (therms)'] / 8760 # in therms/hour
schedule_mean = s_hpxml.mean()
max_value = annual_mean / schedule_mean if schedule_mean != 0 else 0
else:
max_value = None
if max_value is None:
pass
elif out is None:
out = s_hpxml * max_value
out.name = f'{ochre_name} (therms/hour)'
else:
# combine 2 series into data frame
s_gas = s_hpxml * max_value
s_gas.name = f'{ochre_name} (therms/hour)'
out = pd.concat([out, s_gas], axis=1)

if out is None:
raise OCHREException(f'Could not determine max value for {s_hpxml.name} schedule ({ochre_name}).')

return out


def convert_water_column(s_hpxml, ochre_name, equipment):
if ochre_name in ["Water Fixtures", "Showers"]:
# Fixtures include sinks, showers, and baths (SSB), all combined
# Showers are only included for unmet loads calculation
equipment_name = "Water Heating"
else:
equipment_name = ochre_name

if equipment_name not in equipment:
return None

properties = equipment[equipment_name]
avg_water_draw = properties.get('Average Water Draw (L/day)', 0)
annual_mean = avg_water_draw / 1440 # in L/min

schedule_mean = s_hpxml.mean()
max_value = annual_mean / schedule_mean if schedule_mean != 0 else 0
out = s_hpxml * max_value
out.name = f'{ochre_name} (L/min)'

return out

Expand Down Expand Up @@ -409,11 +417,13 @@ def import_occupancy_schedule(occupancy, equipment, start_time, schedule_input_f
s_ochre = s_hpxml * occupancy['Number of Occupants (-)']
s_ochre.name = f'{ochre_name} (Persons)'
schedule_data.append(s_ochre)
elif category in ['Power', 'Water']:
if ochre_name not in equipment:
continue
else:
schedule_data.append(convert_schedule_column(s_hpxml, ochre_name, equipment[ochre_name], category))
elif category == "Power":
if ochre_name in equipment:
schedule_data.append(convert_power_column(s_hpxml, ochre_name, equipment[ochre_name]))
elif category == "Water":
s_ochre = convert_water_column(s_hpxml, ochre_name, equipment)
if s_ochre is not None:
schedule_data.append(s_ochre)
elif category == 'Setpoint':
# Already in the correct units
s_ochre = s_hpxml
Expand Down

0 comments on commit b781132

Please sign in to comment.