Skip to content

Commit

Permalink
Merge pull request #1921 from NREL/fix-2-day-partial-unavailable-period
Browse files Browse the repository at this point in the history
Fix unavailable period less than 24 hours
  • Loading branch information
shorowit authored Feb 6, 2025
2 parents e033ebd + 68ffb9f commit fe64eb4
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 75 deletions.
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ __Bugfixes__
- Fixes zero occupants specified for one unit in a whole MF building from being treated like zero occupants for every unit.
- Fixes using detailed schedules with higher resolution (e.g., 10-min data) than the simulation timestep (e.g., 60-min).
- Fixes possible heating/cooling spikes when using maximum power ratio detailed schedule for variable-speed HVAC systems.
- Fixes unavailable periods for two consecutive, but partial, days.

## OpenStudio-HPXML v1.9.1

Expand Down
8 changes: 4 additions & 4 deletions 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>bd5c3b3f-ccda-4dd5-b16a-1b4f7a4d69e4</version_id>
<version_modified>2025-02-03T19:22:20Z</version_modified>
<version_id>dcdd6d2f-de10-4569-b98b-358e8bbb5b6d</version_id>
<version_modified>2025-02-06T17:32:22Z</version_modified>
<xml_checksum>D8922A73</xml_checksum>
<class_name>HPXMLtoOpenStudio</class_name>
<display_name>HPXML to OpenStudio Translator</display_name>
Expand Down Expand Up @@ -597,7 +597,7 @@
<filename>schedules.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>467EB413</checksum>
<checksum>FD22C411</checksum>
</file>
<file>
<filename>simcontrols.rb</filename>
Expand Down Expand Up @@ -729,7 +729,7 @@
<filename>test_schedules.rb</filename>
<filetype>rb</filetype>
<usage_type>test</usage_type>
<checksum>BDA04315</checksum>
<checksum>61FCB2FA</checksum>
</file>
<file>
<filename>test_simcontrols.rb</filename>
Expand Down
108 changes: 37 additions & 71 deletions HPXMLtoOpenStudio/resources/schedules.rb
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,10 @@ def self.get_unavailable_periods(runner, schedule_name, unavailable_periods)
end

# Add unavailable period rules to the OpenStudio Schedule object.
# An unavailable period rule is an OpenStudio ScheduleRule object.
# Each OpenStudio ScheduleRule stores start month/day, end month/day, and day (24 hour) schedule.
# The unavailable period (i.e., number of consecutive days, whether starting/ending in the middle of the day, etc.) determines
# the number of ScheduleRule objects that are needed, as well as the start, end, and day schedule fields that are set.
#
# @param model [OpenStudio::Model::Model] OpenStudio Model object
# @param schedule [OpenStudio::Model::ScheduleRuleset] the OpenStudio Schedule object for which to set unavailable period rules
Expand Down Expand Up @@ -691,82 +695,44 @@ def self.set_unavailable_periods(model, schedule, sch_name, unavailable_periods)
begin_day_schedule = schedule.getDaySchedules(date_s, date_s)[0]
end_day_schedule = schedule.getDaySchedules(date_e, date_e)[0]

outage_days = day_e - day_s
if outage_days == 0 # outage is less than 1 calendar day (need 1 outage rule)
Model.add_schedule_ruleset_rule(
schedule,
start_date: date_s,
end_date: date_e,
hourly_values: (0..23).map { |h| (h < period.begin_hour) || (h >= period.end_hour) ? begin_day_schedule.getValue(OpenStudio::Time.new(0, h + 1, 0, 0)) : value }
)
else # outage is at least 1 calendar day
if period.begin_hour == 0 && period.end_hour == 24 # 1 outage rule
Model.add_schedule_ruleset_rule(
schedule,
start_date: date_s,
end_date: date_e,
hourly_values: [value] * 24
)
elsif (period.begin_hour == 0 && period.end_hour != 24) || (period.begin_hour != 0 && period.end_hour == 24) # 2 outage rules
# [[start_date, end_date, hourly_values], ...]
schedule_ruleset_rules = []

unavail_days = day_e - day_s
if unavail_days == 0 # unavailable period is less than 1 calendar day (need 1 unavailable period rule)
schedule_ruleset_rules << [date_s, date_e, (0..23).map { |h| (h < period.begin_hour) || (h >= period.end_hour) ? begin_day_schedule.getValue(OpenStudio::Time.new(0, h + 1, 0, 0)) : value }]
else # unavailable period is at least 1 calendar day
if period.begin_hour == 0 && period.end_hour == 24 # 1 unavailable period rule
schedule_ruleset_rules << [date_s, date_e, [value] * 24]
elsif (period.begin_hour == 0 && period.end_hour != 24) || (period.begin_hour != 0 && period.end_hour == 24) # 2 unavailable period rules
if period.begin_hour == 0 && period.end_hour != 24
# last day
Model.add_schedule_ruleset_rule(
schedule,
start_date: date_e,
end_date: date_e,
hourly_values: (0..23).map { |h| (h >= period.end_hour) ? end_day_schedule.getValue(OpenStudio::Time.new(0, h + 1, 0, 0)) : value }
)

# all other days
Model.add_schedule_ruleset_rule(
schedule,
start_date: date_s,
end_date: OpenStudio::Date::fromDayOfYear(day_e - 1, year),
hourly_values: [value] * 24
)
schedule_ruleset_rules << [date_e, date_e, (0..23).map { |h| (h >= period.end_hour) ? end_day_schedule.getValue(OpenStudio::Time.new(0, h + 1, 0, 0)) : value }] # last day
schedule_ruleset_rules << [date_s, OpenStudio::Date::fromDayOfYear(day_e - 1, year), [value] * 24] # all other days
elsif period.begin_hour != 0 && period.end_hour == 24
# first day
Model.add_schedule_ruleset_rule(
schedule,
start_date: date_s,
end_date: date_s,
hourly_values: (0..23).map { |h| (h < period.begin_hour) ? begin_day_schedule.getValue(OpenStudio::Time.new(0, h + 1, 0, 0)) : value }
)

# all other days
Model.add_schedule_ruleset_rule(
schedule,
start_date: OpenStudio::Date::fromDayOfYear(day_s + 1, year),
end_date: date_e,
hourly_values: [value] * 24
)
schedule_ruleset_rules << [date_s, date_s, (0..23).map { |h| (h < period.begin_hour) ? begin_day_schedule.getValue(OpenStudio::Time.new(0, h + 1, 0, 0)) : value }] # first day
schedule_ruleset_rules << [OpenStudio::Date::fromDayOfYear(day_s + 1, year), date_e, [value] * 24]
end
else # 2 or 3 unavailable period rules
if unavail_days == 1 # 2 unavailable period rules
schedule_ruleset_rules << [date_s, date_s, (0..23).map { |h| (h < period.begin_hour) ? begin_day_schedule.getValue(OpenStudio::Time.new(0, h + 1, 0, 0)) : value }] # first day
schedule_ruleset_rules << [date_e, date_e, (0..23).map { |h| (h >= period.end_hour) ? end_day_schedule.getValue(OpenStudio::Time.new(0, h + 1, 0, 0)) : value }] # last day
else # 3 unavailable period rules
schedule_ruleset_rules << [date_s, date_s, (0..23).map { |h| (h < period.begin_hour) ? begin_day_schedule.getValue(OpenStudio::Time.new(0, h + 1, 0, 0)) : value }] # first day
schedule_ruleset_rules << [OpenStudio::Date::fromDayOfYear(day_s + 1, year), OpenStudio::Date::fromDayOfYear(day_e - 1, year), [value] * 24] # all other days
schedule_ruleset_rules << [date_e, date_e, (0..23).map { |h| (h >= period.end_hour) ? end_day_schedule.getValue(OpenStudio::Time.new(0, h + 1, 0, 0)) : value }] # last day
end
else # 3 outage rules
# first day
Model.add_schedule_ruleset_rule(
schedule,
start_date: date_s,
end_date: date_s,
hourly_values: (0..23).map { |h| (h < period.begin_hour) ? begin_day_schedule.getValue(OpenStudio::Time.new(0, h + 1, 0, 0)) : value }
)

# all other days
Model.add_schedule_ruleset_rule(
schedule,
start_date: OpenStudio::Date::fromDayOfYear(day_s + 1, year),
end_date: OpenStudio::Date::fromDayOfYear(day_e - 1, year),
hourly_values: [value] * 24
)

# last day
Model.add_schedule_ruleset_rule(
schedule,
start_date: date_e,
end_date: date_e,
hourly_values: (0..23).map { |h| (h >= period.end_hour) ? end_day_schedule.getValue(OpenStudio::Time.new(0, h + 1, 0, 0)) : value }
)
end
end

schedule_ruleset_rules.each do |schedule_ruleset_rule|
start_date, end_date, hourly_values = schedule_ruleset_rule
Model.add_schedule_ruleset_rule(
schedule,
start_date: start_date,
end_date: end_date,
hourly_values: hourly_values
)
end
end
end

Expand Down
57 changes: 57 additions & 0 deletions HPXMLtoOpenStudio/tests/test_schedules.rb
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,38 @@ def test_simple_power_outage_schedules
assert_in_epsilon(8760 * get_available_hrs_ratio(unavailable_month_hrs), get_annual_equivalent_full_load_hrs(model, Constants::ObjectTypeMechanicalVentilationHouseFan + ' schedule'), @tol)
end

def test_simple_power_outage_schedules_overnight
args_hash = {}
hpxml_path = File.absolute_path(File.join(sample_files_dir, 'base-schedules-simple-power-outage.xml'))
hpxml = HPXML.new(hpxml_path: hpxml_path)
hpxml.header.unavailable_periods[0].begin_month = 12
hpxml.header.unavailable_periods[0].begin_day = 14
hpxml.header.unavailable_periods[0].begin_hour = 20
hpxml.header.unavailable_periods[0].end_month = 12
hpxml.header.unavailable_periods[0].end_day = 15
hpxml.header.unavailable_periods[0].end_hour = 6
XMLHelper.write_file(hpxml.to_doc(), @tmp_hpxml_path)
args_hash['hpxml_path'] = @tmp_hpxml_path
model, _hpxml, _hpxml_bldg = _test_measure(args_hash)

unavailable_month_hrs = { 11 => 10.0 }

assert_in_epsilon(6020, get_annual_equivalent_full_load_hrs(model, Constants::ObjectTypeOccupants + ' schedule'), @tol)
assert_in_epsilon(3049 * get_available_hrs_ratio(unavailable_month_hrs, @default_schedules_csv_data[SchedulesFile::Columns[:LightingInterior].name]['InteriorMonthlyScheduleMultipliers']), get_annual_equivalent_full_load_hrs(model, Constants::ObjectTypeLightingInterior + ' schedule'), @tol)
assert_in_epsilon(2895 * get_available_hrs_ratio(unavailable_month_hrs, @default_schedules_csv_data[SchedulesFile::Columns[:LightingInterior].name]['InteriorMonthlyScheduleMultipliers']), get_annual_equivalent_full_load_hrs(model, Constants::ObjectTypeLightingExterior + ' schedule'), @tol)
assert_in_epsilon(6673 * get_available_hrs_ratio(unavailable_month_hrs, @default_schedules_csv_data[SchedulesFile::Columns[:Refrigerator].name]['MonthlyScheduleMultipliers']), get_annual_equivalent_full_load_hrs(model, Constants::ObjectTypeRefrigerator + ' schedule'), @tol)
assert_in_epsilon(2441 * get_available_hrs_ratio(unavailable_month_hrs, @default_schedules_csv_data[SchedulesFile::Columns[:CookingRange].name]['MonthlyScheduleMultipliers']), get_annual_equivalent_full_load_hrs(model, Constants::ObjectTypeCookingRange + ' schedule'), @tol)
assert_in_epsilon(3285 * get_available_hrs_ratio(unavailable_month_hrs, @default_schedules_csv_data[SchedulesFile::Columns[:Dishwasher].name]['MonthlyScheduleMultipliers']), get_annual_equivalent_full_load_hrs(model, Constants::ObjectTypeDishwasher + ' schedule'), @tol)
assert_in_epsilon(4248 * get_available_hrs_ratio(unavailable_month_hrs, @default_schedules_csv_data[SchedulesFile::Columns[:ClothesWasher].name]['MonthlyScheduleMultipliers']), get_annual_equivalent_full_load_hrs(model, Constants::ObjectTypeClothesWasher + ' schedule'), @tol)
assert_in_epsilon(4502 * get_available_hrs_ratio(unavailable_month_hrs, @default_schedules_csv_data[SchedulesFile::Columns[:ClothesDryer].name]['MonthlyScheduleMultipliers']), get_annual_equivalent_full_load_hrs(model, Constants::ObjectTypeClothesDryer + ' schedule'), @tol)
assert_in_epsilon(6880 * get_available_hrs_ratio(unavailable_month_hrs, @default_schedules_csv_data[SchedulesFile::Columns[:PlugLoadsOther].name]['MonthlyScheduleMultipliers']), get_annual_equivalent_full_load_hrs(model, Constants::ObjectTypeMiscPlugLoads + ' schedule'), @tol)
assert_in_epsilon(3373 * get_available_hrs_ratio(unavailable_month_hrs, @default_schedules_csv_data[SchedulesFile::Columns[:PlugLoadsTV].name]['MonthlyScheduleMultipliers']), get_annual_equivalent_full_load_hrs(model, Constants::ObjectTypeMiscTelevision + ' schedule'), @tol)
assert_in_epsilon(4204 * get_available_hrs_ratio(unavailable_month_hrs, @default_schedules_csv_data[SchedulesFile::Columns[:HotWaterFixtures].name]['WaterFixturesMonthlyScheduleMultipliers']), get_annual_equivalent_full_load_hrs(model, Constants::ObjectTypeFixtures + ' schedule'), @tol)
assert_in_epsilon(4244 * get_available_hrs_ratio(unavailable_month_hrs, @default_schedules_csv_data[SchedulesFile::Columns[:HotWaterRecirculationPump].name]['RecirculationPumpMonthlyScheduleMultipliers']), get_annual_equivalent_full_load_hrs(model, Constants::ObjectTypeHotWaterRecircPump + ' schedule'), @tol)
assert_in_epsilon(5000, get_annual_equivalent_full_load_hrs(model, Constants::ObjectTypeGeneralWaterUse + ' schedule'), @tol)
assert_in_epsilon(8760 * get_available_hrs_ratio(unavailable_month_hrs), get_annual_equivalent_full_load_hrs(model, Constants::ObjectTypeMechanicalVentilationHouseFan + ' schedule'), @tol)
end

def test_stochastic_schedules
args_hash = {}
args_hash['hpxml_path'] = File.absolute_path(File.join(sample_files_dir, 'base-schedules-detailed-occupancy-stochastic.xml'))
Expand Down Expand Up @@ -580,6 +612,31 @@ def test_set_unavailable_periods_lighting
_test_day_schedule(schedule, end_month, end_day, year, 0, end_hour)
_test_day_schedule(schedule, end_month, end_day + 1, year, nil, nil)

# 2 calendar days, partial first and last day
begin_month = 12
begin_day = 14
begin_hour = 20
end_month = 12
end_day = 15
end_hour = 6

model, hpxml, _hpxml_bldg = _test_measure(args_hash)
year = model.getYearDescription.assumedYear

schedule = model.getScheduleRulesets.find { |schedule| schedule.name.to_s == sch_name }
unavailable_periods = _add_unavailable_period(hpxml, 'Power Outage', begin_month, begin_day, begin_hour, end_month, end_day, end_hour) # note the change of end month/day

schedule_rules = schedule.scheduleRules
Schedule.set_unavailable_periods(model, schedule, sch_name, unavailable_periods)
unavailable_schedule_rules = schedule.scheduleRules - schedule_rules

assert_equal(2, unavailable_schedule_rules.size)

_test_day_schedule(schedule, begin_month, begin_day - 1, year, nil, nil)
_test_day_schedule(schedule, begin_month, begin_day, year, begin_hour, 24)
_test_day_schedule(schedule, end_month, end_day, year, 0, end_hour)
_test_day_schedule(schedule, end_month, end_day + 1, year, nil, nil)

# wrap around
begin_month = 12
begin_day = 1
Expand Down

0 comments on commit fe64eb4

Please sign in to comment.