Skip to content

Commit

Permalink
Merge commit '287a3592cdff12868c7da8b520656ddf4710a862' into latest-o…
Browse files Browse the repository at this point in the history
…s-hpxml
  • Loading branch information
joseph-robertson committed May 9, 2024
2 parents ccd9871 + 287a359 commit fa2c35b
Show file tree
Hide file tree
Showing 15 changed files with 1,130 additions and 1,167 deletions.
1 change: 1 addition & 0 deletions resources/hpxml-measures/Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ __New Features__
- Manual J design load calculations:
- Allow additional outdoor design condition inputs: `DailyTemperatureRange` and `HumidityDifference`.
- Miscellaneous improvements.
- Improves heating/cooling component loads; for timesteps where there is no heating/cooling load, assigns heat transfer to heating or cooling by comparing indoor temperature to the average of heating/cooling setpoints.
- Adds net energy and net electricity timeseries output columns even when there is no PV or generator.
- Adds more error-checking for inappropriate inputs (e.g., HVAC SHR=0 or clothes washer IMEF=0).
- Allow alternative label energy use (W) input for ceiling fans.
Expand Down
110 changes: 72 additions & 38 deletions resources/hpxml-measures/HPXMLtoOpenStudio/measure.rb
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,11 @@ def run(model, runner, user_arguments)
end

# Output
add_unmet_hours_output(model, hpxml_osm_map)
add_loads_output(model, args[:add_component_loads], hpxml_osm_map)
season_day_nums = add_unmet_hours_output(model, hpxml_osm_map)
loads_data = add_total_loads_output(model, hpxml_osm_map)
if args[:add_component_loads]
add_component_loads_output(model, hpxml_osm_map, loads_data, season_day_nums)
end
set_output_files(model)
add_additional_properties(model, hpxml, hpxml_osm_map, args[:hpxml_path], args[:building_id], hpxml_defaults_path)
# Uncomment to debug EMS
Expand Down Expand Up @@ -459,7 +462,6 @@ def create_unit_model(hpxml, hpxml_bldg, runner, model, epw_path, epw_file, weat
add_pools_and_permanent_spas(runner, model, spaces)

# Other
add_cooling_season(model, weather)
add_airflow(runner, model, weather, spaces, airloop_map)
add_photovoltaics(model)
add_generators(model)
Expand Down Expand Up @@ -1261,18 +1263,6 @@ def add_thermal_mass(model, spaces)
end
end

def add_cooling_season(model, weather)
# Create cooling season schedule
# Applies to natural ventilation and calculation of component loads, not HVAC equipment
# Uses BAHSP cooling season, not user-specified cooling season (which may be, e.g., year-round)
_, default_cooling_months = HVAC.get_default_heating_and_cooling_seasons(weather, @hpxml_bldg.latitude)

clg_season_sch = MonthWeekdayWeekendSchedule.new(model, 'cooling season schedule', Array.new(24, 1), Array.new(24, 1), default_cooling_months, Constants.ScheduleTypeLimitsFraction)
@clg_ssn_sensor = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Schedule Value')
@clg_ssn_sensor.setName('cool_season')
@clg_ssn_sensor.setKeyName(clg_season_sch.schedule.name.to_s)
end

def add_windows(model, spaces)
# We already stored @fraction_of_windows_operable, so lets remove the
# fraction_operable properties from windows and re-collapse the enclosure
Expand Down Expand Up @@ -1935,7 +1925,7 @@ def add_airflow(runner, model, weather, spaces, airloop_map)
end

Airflow.apply(model, runner, weather, spaces, @hpxml_header, @hpxml_bldg, @cfa,
@ncfl_ag, duct_systems, airloop_map, @clg_ssn_sensor, @eri_version,
@ncfl_ag, duct_systems, airloop_map, @eri_version,
@frac_windows_operable, @apply_ashrae140_assumptions, @schedules_file,
@hpxml_header.unavailable_periods, hvac_availability_sensor)
end
Expand Down Expand Up @@ -2082,7 +2072,7 @@ def add_unmet_hours_output(model, hpxml_osm_map)
# Create sensors and gather data
htg_sensors, clg_sensors = {}, {}
total_heat_load_serveds, total_cool_load_serveds = {}, {}
htg_start_days, htg_end_days, clg_start_days, clg_end_days = {}, {}, {}, {}
season_day_nums = {}
hpxml_osm_map.each_with_index do |(hpxml_bldg, unit_model), unit|
conditioned_zone_name = unit_model.getThermalZones.find { |z| z.additionalProperties.getFeatureAsString('ObjectType').to_s == HPXML::LocationConditionedSpace }.name.to_s

Expand All @@ -2102,10 +2092,12 @@ def add_unmet_hours_output(model, hpxml_osm_map)
next unless not hvac_control.nil?

sim_year = @hpxml_header.sim_calendar_year
htg_start_days[unit] = Schedule.get_day_num_from_month_day(sim_year, hvac_control.seasons_heating_begin_month, hvac_control.seasons_heating_begin_day)
htg_end_days[unit] = Schedule.get_day_num_from_month_day(sim_year, hvac_control.seasons_heating_end_month, hvac_control.seasons_heating_end_day)
clg_start_days[unit] = Schedule.get_day_num_from_month_day(sim_year, hvac_control.seasons_cooling_begin_month, hvac_control.seasons_cooling_begin_day)
clg_end_days[unit] = Schedule.get_day_num_from_month_day(sim_year, hvac_control.seasons_cooling_end_month, hvac_control.seasons_cooling_end_day)
season_day_nums[unit] = {
htg_start: Schedule.get_day_num_from_month_day(sim_year, hvac_control.seasons_heating_begin_month, hvac_control.seasons_heating_begin_day),
htg_end: Schedule.get_day_num_from_month_day(sim_year, hvac_control.seasons_heating_end_month, hvac_control.seasons_heating_end_day),
clg_start: Schedule.get_day_num_from_month_day(sim_year, hvac_control.seasons_cooling_begin_month, hvac_control.seasons_cooling_begin_day),
clg_end: Schedule.get_day_num_from_month_day(sim_year, hvac_control.seasons_cooling_end_month, hvac_control.seasons_cooling_end_day)
}
end

hvac_availability_sensor = model.getEnergyManagementSystemSensors.find { |s| s.additionalProperties.getFeatureAsString('ObjectType').to_s == Constants.ObjectNameHVACAvailabilitySensor }
Expand All @@ -2120,10 +2112,10 @@ def add_unmet_hours_output(model, hpxml_osm_map)
program.addLine("Set #{clg_hrs} = 0")
for unit in 0..hpxml_osm_map.size - 1
if total_heat_load_serveds[unit] > 0
if htg_end_days[unit] >= htg_start_days[unit]
line = "If ((DayOfYear >= #{htg_start_days[unit]}) && (DayOfYear <= #{htg_end_days[unit]}))"
if season_day_nums[unit][:htg_end] >= season_day_nums[unit][:htg_start]
line = "If ((DayOfYear >= #{season_day_nums[unit][:htg_start]}) && (DayOfYear <= #{season_day_nums[unit][:htg_end]}))"
else
line = "If ((DayOfYear >= #{htg_start_days[unit]}) || (DayOfYear <= #{htg_end_days[unit]}))"
line = "If ((DayOfYear >= #{season_day_nums[unit][:htg_start]}) || (DayOfYear <= #{season_day_nums[unit][:htg_end]}))"
end
line += " && (#{hvac_availability_sensor.name} == 1)" if not hvac_availability_sensor.nil?
program.addLine(line)
Expand All @@ -2134,10 +2126,10 @@ def add_unmet_hours_output(model, hpxml_osm_map)
end
next unless total_cool_load_serveds[unit] > 0

if clg_end_days[unit] >= clg_start_days[unit]
line = "If ((DayOfYear >= #{clg_start_days[unit]}) && (DayOfYear <= #{clg_end_days[unit]}))"
if season_day_nums[unit][:clg_end] >= season_day_nums[unit][:clg_start]
line = "If ((DayOfYear >= #{season_day_nums[unit][:clg_start]}) && (DayOfYear <= #{season_day_nums[unit][:clg_end]}))"
else
line = "If ((DayOfYear >= #{clg_start_days[unit]}) || (DayOfYear <= #{clg_end_days[unit]}))"
line = "If ((DayOfYear >= #{season_day_nums[unit][:clg_start]}) || (DayOfYear <= #{season_day_nums[unit][:clg_end]}))"
end
line += " && (#{hvac_availability_sensor.name} == 1)" if not hvac_availability_sensor.nil?
program.addLine(line)
Expand All @@ -2152,13 +2144,8 @@ def add_unmet_hours_output(model, hpxml_osm_map)
program_calling_manager.setName("#{program.name} calling manager")
program_calling_manager.setCallingPoint('EndOfZoneTimestepBeforeZoneReporting')
program_calling_manager.addProgram(program)
end

def add_loads_output(model, add_component_loads, hpxml_osm_map)
loads_data = add_total_loads_output(model, hpxml_osm_map)
return unless add_component_loads

add_component_loads_output(model, hpxml_osm_map, loads_data)
return season_day_nums
end

def add_total_loads_output(model, hpxml_osm_map)
Expand Down Expand Up @@ -2246,7 +2233,7 @@ def add_total_loads_output(model, hpxml_osm_map)
return htg_cond_load_sensors, clg_cond_load_sensors, total_heat_load_serveds, total_cool_load_serveds, dehumidifier_sensors
end

def add_component_loads_output(model, hpxml_osm_map, loads_data)
def add_component_loads_output(model, hpxml_osm_map, loads_data, season_day_nums)
htg_cond_load_sensors, clg_cond_load_sensors, total_heat_load_serveds, total_cool_load_serveds, dehumidifier_sensors = loads_data

# Output diagnostics needed for some output variables used below
Expand Down Expand Up @@ -2599,17 +2586,64 @@ def add_component_loads_output(model, hpxml_osm_map, loads_data)
program.addLine("Set hr_ducts = hr_ducts + (#{ducts_mix_loss_sensor.name} - #{ducts_mix_gain_sensor.name})")
end

# EMS Sensors: Indoor temperature, setpoints
tin_sensor = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Zone Mean Air Temperature')
tin_sensor.setName('tin s')
tin_sensor.setKeyName(conditioned_zone.name.to_s)
thermostat = nil
if conditioned_zone.thermostatSetpointDualSetpoint.is_initialized
thermostat = conditioned_zone.thermostatSetpointDualSetpoint.get

htg_sp_sensor = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Schedule Value')
htg_sp_sensor.setName('htg sp s')
htg_sp_sensor.setKeyName(thermostat.heatingSetpointTemperatureSchedule.get.name.to_s)

clg_sp_sensor = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Schedule Value')
clg_sp_sensor.setName('clg sp s')
clg_sp_sensor.setKeyName(thermostat.coolingSetpointTemperatureSchedule.get.name.to_s)
end

# EMS program: Heating vs Cooling logic
program.addLine('Set htg_mode = 0')
program.addLine('Set clg_mode = 0')
program.addLine("If (#{htg_cond_load_sensors[unit].name} > 0)") # Assign hour to heating if heating load
program.addLine(" Set htg_mode = #{total_heat_load_serveds[unit]}")
program.addLine("ElseIf (#{clg_cond_load_sensors[unit].name} > 0)") # Assign hour to cooling if cooling load
program.addLine(" Set clg_mode = #{total_cool_load_serveds[unit]}")
program.addLine("ElseIf (#{@clg_ssn_sensor.name} > 0)") # No load, assign hour to cooling if in cooling season definition (Note: natural ventilation & whole house fan only operate during the cooling season)
program.addLine(" Set clg_mode = #{total_cool_load_serveds[unit]}")
program.addLine('Else') # No load, assign hour to heating if not in cooling season definition
program.addLine(" Set htg_mode = #{total_heat_load_serveds[unit]}")
program.addLine('Else')
program.addLine(' Set htg_season = 0')
program.addLine(' Set clg_season = 0')
if not season_day_nums[unit].nil?
# Determine whether we're in the heating and/or cooling season
if season_day_nums[unit][:clg_end] >= season_day_nums[unit][:clg_start]
program.addLine(" If ((DayOfYear >= #{season_day_nums[unit][:clg_start]}) && (DayOfYear <= #{season_day_nums[unit][:clg_end]}))")
else
program.addLine(" If ((DayOfYear >= #{season_day_nums[unit][:clg_start]}) || (DayOfYear <= #{season_day_nums[unit][:clg_end]}))")
end
program.addLine(' Set clg_season = 1')
program.addLine(' EndIf')
if season_day_nums[unit][:htg_end] >= season_day_nums[unit][:htg_start]
program.addLine(" If ((DayOfYear >= #{season_day_nums[unit][:htg_start]}) && (DayOfYear <= #{season_day_nums[unit][:htg_end]}))")
else
program.addLine(" If ((DayOfYear >= #{season_day_nums[unit][:htg_start]}) || (DayOfYear <= #{season_day_nums[unit][:htg_end]}))")
end
program.addLine(' Set htg_season = 1')
program.addLine(' EndIf')
end
program.addLine(" If ((#{natvent_sensors[0].name} <> 0) || (#{natvent_sensors[1].name} <> 0)) && (clg_season == 1)") # Assign hour to cooling if natural ventilation is operating
program.addLine(" Set clg_mode = #{total_cool_load_serveds[unit]}")
program.addLine(" ElseIf ((#{whf_sensors[0].name} <> 0) || (#{whf_sensors[1].name} <> 0)) && (clg_season == 1)") # Assign hour to cooling if whole house fan is operating
program.addLine(" Set clg_mode = #{total_cool_load_serveds[unit]}")
if not thermostat.nil?
program.addLine(' Else') # Indoor temperature floating between setpoints; determine assignment by comparing to average of heating/cooling setpoints
program.addLine(" Set Tmid_setpoint = (#{htg_sp_sensor.name} + #{clg_sp_sensor.name}) / 2")
program.addLine(" If (#{tin_sensor.name} > Tmid_setpoint) && (clg_season == 1)")
program.addLine(" Set clg_mode = #{total_cool_load_serveds[unit]}")
program.addLine(" ElseIf (#{tin_sensor.name} < Tmid_setpoint) && (htg_season == 1)")
program.addLine(" Set htg_mode = #{total_heat_load_serveds[unit]}")
program.addLine(' EndIf')
end
program.addLine(' EndIf')
program.addLine('EndIf')

unit_multiplier = @hpxml_bldg.building_construction.number_of_units
Expand Down
16 changes: 8 additions & 8 deletions resources/hpxml-measures/HPXMLtoOpenStudio/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>hpxm_lto_openstudio</name>
<uid>b1543b30-9465-45ff-ba04-1d1f85e763bc</uid>
<version_id>c8af87d2-2192-4515-8289-a76f12fcc7b3</version_id>
<version_modified>2024-05-07T13:35:01Z</version_modified>
<version_id>2c8cf9b0-56d7-4c2a-b6b3-4a03c811c8f4</version_id>
<version_modified>2024-05-07T22:25:44Z</version_modified>
<xml_checksum>D8922A73</xml_checksum>
<class_name>HPXMLtoOpenStudio</class_name>
<display_name>HPXML to OpenStudio Translator</display_name>
Expand Down Expand Up @@ -142,13 +142,13 @@
<filename>measure.rb</filename>
<filetype>rb</filetype>
<usage_type>script</usage_type>
<checksum>C90BFC49</checksum>
<checksum>054E23C2</checksum>
</file>
<file>
<filename>airflow.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>81518569</checksum>
<checksum>93C49D9A</checksum>
</file>
<file>
<filename>battery.rb</filename>
Expand All @@ -160,7 +160,7 @@
<filename>constants.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>2568BDAC</checksum>
<checksum>1359BE6A</checksum>
</file>
<file>
<filename>constructions.rb</filename>
Expand Down Expand Up @@ -310,7 +310,7 @@
<filename>hpxml_defaults.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>7DD4B643</checksum>
<checksum>D494105F</checksum>
</file>
<file>
<filename>hpxml_schema/HPXML.xsd</filename>
Expand Down Expand Up @@ -562,7 +562,7 @@
<filename>waterheater.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>80D00EE1</checksum>
<checksum>FFCEC636</checksum>
</file>
<file>
<filename>weather.rb</filename>
Expand Down Expand Up @@ -676,7 +676,7 @@
<filename>test_water_heater.rb</filename>
<filetype>rb</filetype>
<usage_type>test</usage_type>
<checksum>485EE23B</checksum>
<checksum>AEF9864A</checksum>
</file>
<file>
<filename>test_weather.rb</filename>
Expand Down
11 changes: 10 additions & 1 deletion resources/hpxml-measures/HPXMLtoOpenStudio/resources/airflow.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ class Airflow
InfilPressureExponent = 0.65

def self.apply(model, runner, weather, spaces, hpxml_header, hpxml_bldg, cfa,
ncfl_ag, duct_systems, airloop_map, clg_ssn_sensor, eri_version,
ncfl_ag, duct_systems, airloop_map, eri_version,
frac_windows_operable, apply_ashrae140_assumptions, schedules_file,
unavailable_periods, hvac_availability_sensor)

Expand Down Expand Up @@ -115,6 +115,15 @@ def self.apply(model, runner, weather, spaces, hpxml_header, hpxml_bldg, cfa,
conditioned_ach50 *= infil_values[:a_ext] unless conditioned_ach50.nil?
has_flue_chimney_in_cond_space = hpxml_bldg.air_infiltration.has_flue_or_chimney_in_conditioned_space

# Cooling season schedule
# Applies to natural ventilation, not HVAC equipment.
# Uses BAHSP cooling season, not user-specified cooling season (which may be, e.g., year-round).
_, default_cooling_months = HVAC.get_default_heating_and_cooling_seasons(weather, hpxml_bldg.latitude)
clg_season_sch = MonthWeekdayWeekendSchedule.new(model, 'cooling season schedule', Array.new(24, 1), Array.new(24, 1), default_cooling_months, Constants.ScheduleTypeLimitsFraction)
clg_ssn_sensor = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Schedule Value')
clg_ssn_sensor.setName('cool_season')
clg_ssn_sensor.setKeyName(clg_season_sch.schedule.name.to_s)

apply_natural_ventilation_and_whole_house_fan(model, hpxml_bldg.site, vent_fans_whf, open_window_area, clg_ssn_sensor, hpxml_bldg.header.natvent_days_per_week,
infil_values[:volume], infil_values[:height], unavailable_periods)
apply_infiltration_and_ventilation_fans(model, weather, hpxml_bldg.site, vent_fans_mech, vent_fans_kitchen, vent_fans_bath, vented_dryers, duct_lk_imbals,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -372,10 +372,6 @@ def self.ObjectNameWaterHeaterSetpoint
return 'water heater setpoint'
end

def self.ObjectNameWaterHeaterAdjustment
return 'water heater energy adjustment'
end

def self.ObjectNameWaterLoopHeatPump
return 'water loop heat pump'
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,7 @@ def self.apply_building(hpxml_bldg, epw_file)
end

if hpxml_bldg.elevation.nil?
hpxml_bldg.elevation = UnitConversions.convert(epw_file.elevation, 'm', 'ft').round(1)
hpxml_bldg.elevation = UnitConversions.convert([epw_file.elevation, 0.0].max, 'm', 'ft').round(1)
hpxml_bldg.elevation_isdefaulted = true
end

Expand Down
Loading

0 comments on commit fa2c35b

Please sign in to comment.