Skip to content

Commit

Permalink
Merge pull request #1851 from NREL/schedule_methods
Browse files Browse the repository at this point in the history
Add schedule methods to model.rb
  • Loading branch information
shorowit authored Oct 5, 2024
2 parents 745026f + 0f2e6b7 commit afdd388
Show file tree
Hide file tree
Showing 10 changed files with 445 additions and 531 deletions.
28 changes: 17 additions & 11 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>2ecbdcc0-d0e3-4c8f-b8f8-a839e9bdbee1</version_id>
<version_modified>2024-10-04T17:02:56Z</version_modified>
<version_id>1424b04d-aae3-4360-9d50-3324d0babb33</version_id>
<version_modified>2024-10-05T18:13:33Z</version_modified>
<xml_checksum>D8922A73</xml_checksum>
<class_name>HPXMLtoOpenStudio</class_name>
<display_name>HPXML to OpenStudio Translator</display_name>
Expand Down Expand Up @@ -189,7 +189,7 @@
<filename>airflow.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>AAA32BAD</checksum>
<checksum>CA1394A8</checksum>
</file>
<file>
<filename>battery.rb</filename>
Expand All @@ -213,7 +213,7 @@
<filename>constructions.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>5D07F8B2</checksum>
<checksum>B3B897EB</checksum>
</file>
<file>
<filename>data/Xing_okstate_0664D_13659_Table_A-3.csv</filename>
Expand Down Expand Up @@ -345,13 +345,13 @@
<filename>geometry.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>7E3D612D</checksum>
<checksum>6C147EFE</checksum>
</file>
<file>
<filename>hotwater_appliances.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>E9F876DA</checksum>
<checksum>4A5DFE48</checksum>
</file>
<file>
<filename>hpxml.rb</filename>
Expand Down Expand Up @@ -387,7 +387,7 @@
<filename>hvac.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>3F8A3A86</checksum>
<checksum>CEDAF4B3</checksum>
</file>
<file>
<filename>hvac_sizing.rb</filename>
Expand Down Expand Up @@ -447,7 +447,7 @@
<filename>model.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>6933774F</checksum>
<checksum>A578B92B</checksum>
</file>
<file>
<filename>output.rb</filename>
Expand Down Expand Up @@ -591,7 +591,7 @@
<filename>schedules.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>B73029E3</checksum>
<checksum>BB101800</checksum>
</file>
<file>
<filename>simcontrols.rb</filename>
Expand Down Expand Up @@ -627,7 +627,7 @@
<filename>waterheater.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>1E7D40B1</checksum>
<checksum>790B832E</checksum>
</file>
<file>
<filename>weather.rb</filename>
Expand All @@ -647,6 +647,12 @@
<usage_type>resource</usage_type>
<checksum>93120E27</checksum>
</file>
<file>
<filename>in.schedules.csv</filename>
<filetype>csv</filetype>
<usage_type>test</usage_type>
<checksum>C8EBFF38</checksum>
</file>
<file>
<filename>test_airflow.rb</filename>
<filetype>rb</filetype>
Expand Down Expand Up @@ -723,7 +729,7 @@
<filename>test_schedules.rb</filename>
<filetype>rb</filetype>
<usage_type>test</usage_type>
<checksum>703BFE7A</checksum>
<checksum>62B8CE90</checksum>
</file>
<file>
<filename>test_simcontrols.rb</filename>
Expand Down
43 changes: 24 additions & 19 deletions HPXMLtoOpenStudio/resources/airflow.rb
Original file line number Diff line number Diff line change
Expand Up @@ -635,27 +635,32 @@ def self.apply_natural_ventilation_and_whole_house_fan(runner, model, spaces, hp
# @param unavailable_periods [HPXML::UnavailablePeriods] Object that defines periods for, e.g., power outages or vacancies
# @return [TODO] TODO
def self.create_nv_and_whf_avail_sch(model, obj_name, num_days_per_week, unavailable_periods)
avail_sch = OpenStudio::Model::ScheduleRuleset.new(model)
sch_name = "#{obj_name} schedule"
avail_sch.setName(sch_name)
avail_sch.defaultDaySchedule.setName("#{sch_name} default day")
Schedule.set_schedule_type_limits(model, avail_sch, EPlus::ScheduleTypeLimitsOnOff)
on_rule = OpenStudio::Model::ScheduleRule.new(avail_sch)
on_rule.setName("#{sch_name} rule")
on_rule_day = on_rule.daySchedule
on_rule_day.setName("#{sch_name} avail day")
on_rule_day.addValue(OpenStudio::Time.new(0, 24, 0, 0), 1)
method_array = ['setApplyMonday', 'setApplyWednesday', 'setApplyFriday', 'setApplySaturday', 'setApplyTuesday', 'setApplyThursday', 'setApplySunday']
for i in 1..7 do
if num_days_per_week >= i
on_rule.public_send(method_array[i - 1], true)
end
end
on_rule.setStartDate(OpenStudio::Date::fromDayOfYear(1))
on_rule.setEndDate(OpenStudio::Date::fromDayOfYear(365))
avail_sch = Model.add_schedule_ruleset(
model,
name: sch_name,
limits: EPlus::ScheduleTypeLimitsOnOff
)

# Apply to days in this order: Mon, Wed, Fri, Sat, Tues, Thurs, Sun
apply_to_days = [0] * 7
apply_to_days[0] = 1 if num_days_per_week >= 7
apply_to_days[1] = 1 if num_days_per_week >= 1
apply_to_days[2] = 1 if num_days_per_week >= 5
apply_to_days[3] = 1 if num_days_per_week >= 2
apply_to_days[4] = 1 if num_days_per_week >= 6
apply_to_days[5] = 1 if num_days_per_week >= 3
apply_to_days[6] = 1 if num_days_per_week >= 4

Model.add_schedule_ruleset_rule(
avail_sch,
start_date: OpenStudio::Date::fromDayOfYear(1),
end_date: OpenStudio::Date::fromDayOfYear(365),
apply_to_days: apply_to_days,
hourly_values: [1] * 24
)

year = model.getYearDescription.assumedYear
Schedule.set_unavailable_periods(avail_sch, sch_name, unavailable_periods, year)
Schedule.set_unavailable_periods(model, avail_sch, sch_name, unavailable_periods)
return avail_sch
end

Expand Down
3 changes: 2 additions & 1 deletion HPXMLtoOpenStudio/resources/constructions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2104,7 +2104,8 @@ def self.apply_window_skylight_shading(model, window_or_skylight, sub_surface, s
sf_sch = Model.add_schedule_constant(
model,
name: sch_name,
value: sf_values[0][0]
value: sf_values[0][0],
limits: EPlus::ScheduleTypeLimitsFraction
)
else
sf_sch = HourlyByDaySchedule.new(model, sch_name, sf_values, sf_values, EPlus::ScheduleTypeLimitsFraction, false).schedule
Expand Down
8 changes: 2 additions & 6 deletions HPXMLtoOpenStudio/resources/geometry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1802,7 +1802,8 @@ def self.get_space_temperature_schedule(model, location, spaces)
sch = Model.add_schedule_constant(
model,
name: location,
value: nil
value: nil,
limits: EPlus::ScheduleTypeLimitsTemperature
)
sch.additionalProperties.setFeature('ObjectType', location)

Expand Down Expand Up @@ -1839,11 +1840,6 @@ def self.get_space_temperature_schedule(model, location, spaces)
end
end

# Schedule type limits compatible
schedule_type_limits = OpenStudio::Model::ScheduleTypeLimits.new(model)
schedule_type_limits.setUnitType('Temperature')
sch.setScheduleTypeLimits(schedule_type_limits)

# Sensors
if space_values[:indoor_weight] > 0
if not spaces[HPXML::LocationConditionedSpace].thermalZone.get.thermostatSetpointDualSetpoint.is_initialized
Expand Down
4 changes: 2 additions & 2 deletions HPXMLtoOpenStudio/resources/hotwater_appliances.rb
Original file line number Diff line number Diff line change
Expand Up @@ -407,9 +407,9 @@ def self.apply(runner, model, weather, spaces, hpxml_bldg, hpxml_header, schedul
mw_temp_schedule = Model.add_schedule_constant(
model,
name: 'mixed water temperature schedule',
value: UnitConversions.convert(t_mix, 'F', 'C')
value: UnitConversions.convert(t_mix, 'F', 'C'),
limits: EPlus::ScheduleTypeLimitsTemperature
)
Schedule.set_schedule_type_limits(model, mw_temp_schedule, EPlus::ScheduleTypeLimitsTemperature)

# Create schedule
fixtures_schedule = nil
Expand Down
15 changes: 7 additions & 8 deletions HPXMLtoOpenStudio/resources/hvac.rb
Original file line number Diff line number Diff line change
Expand Up @@ -940,7 +940,8 @@ def self.apply_boiler(model, runner, heating_system, hvac_sequential_load_fracs,
supply_setpoint = Model.add_schedule_constant(
model,
name: "#{obj_name} hydronic heat supply setpoint",
value: UnitConversions.convert(design_temp, 'F', 'C')
value: UnitConversions.convert(design_temp, 'F', 'C'),
limits: EPlus::ScheduleTypeLimitsTemperature
)

setpoint_manager = OpenStudio::Model::SetpointManagerScheduled.new(model, supply_setpoint)
Expand Down Expand Up @@ -2570,9 +2571,9 @@ def self.create_air_loop_unitary_system(model, obj_name, fan, htg_coil, clg_coil
cycle_fan_sch = Model.add_schedule_constant(
model,
name: "#{obj_name} auto fan schedule",
value: 0
) # 0 denotes that fan cycles on and off to meet the load (i.e., AUTO fan) as opposed to continuous operation
Schedule.set_schedule_type_limits(model, cycle_fan_sch, EPlus::ScheduleTypeLimitsOnOff)
value: 0, # 0 denotes that fan cycles on and off to meet the load (i.e., AUTO fan) as opposed to continuous operation
limits: EPlus::ScheduleTypeLimitsOnOff
)

air_loop_unitary = OpenStudio::Model::AirLoopHVACUnitarySystem.new(model)
air_loop_unitary.setName(obj_name + ' unitary system')
Expand Down Expand Up @@ -4603,10 +4604,8 @@ def self.get_sequential_load_schedule(model, fractions, unavailable_periods)
s = ScheduleConstant.new(model, sch_name, values[0], EPlus::ScheduleTypeLimitsFraction, unavailable_periods: unavailable_periods)
s = s.schedule
else
s = Schedule.create_ruleset_from_daily_season(model, values)
s.setName(sch_name)
Schedule.set_unavailable_periods(s, sch_name, unavailable_periods, model.getYearDescription.assumedYear)
Schedule.set_schedule_type_limits(model, s, EPlus::ScheduleTypeLimitsFraction)
s = Schedule.create_ruleset_from_daily_season(model, sch_name, values)
Schedule.set_unavailable_periods(model, s, sch_name, unavailable_periods)
end

return s
Expand Down
139 changes: 138 additions & 1 deletion HPXMLtoOpenStudio/resources/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -542,14 +542,151 @@ def self.add_curve_quint_linear(model, name:, coeff:)
# @param model [OpenStudio::Model::Model] OpenStudio Model object
# @param name [String] Name for the OpenStudio object
# @param value [Double] Constant value for the year
# @param limits [String] Data type for the values contained in the schedule (EPlus::ScheduleTypeXXX)
# @return [OpenStudio::Model::ScheduleConstant] The model object
def self.add_schedule_constant(model, name:, value:)
def self.add_schedule_constant(model, name:, value:, limits: nil)
sch = OpenStudio::Model::ScheduleConstant.new(model)
sch.setName(name)
sch.setValue(value) unless value.nil? # EMS-actuated if nil
add_schedule_type_limits(model, schedule: sch, limits: limits)
return sch
end

# Adds a ScheduleRuleset object to the OpenStudio model.
#
# @param model [OpenStudio::Model::Model] OpenStudio Model object
# @param name [String] Name for the OpenStudio object
# @param limits [String] Data type for the values contained in the schedule (EPlus::ScheduleTypeXXX)
# @return [OpenStudio::Model::ScheduleRuleset] The model object
def self.add_schedule_ruleset(model, name:, limits: nil)
sch = OpenStudio::Model::ScheduleRuleset.new(model)
sch.setName(name)
sch.defaultDaySchedule.setName("#{name} default day")
add_schedule_type_limits(model, schedule: sch, limits: limits)
return sch
end

# Adds a ScheduleRule object to the OpenStudio model for a given ruleset.
#
# @param schedule [OpenStudio::Model::ScheduleRuleset] The ruleset to which the rule applies
# @param start_date [OpenStudio::Date] Start date
# @param end_date [OpenStudio::Date] End data
# @param apply_to_days [Array<Integer>] Values for Sun, Mon, ..., Sat, where 1 means the rule applies
# @param hourly_values [Array<Double>] 24 hourly values
# @return [OpenStudio::Model::ScheduleRule] The model object
def self.add_schedule_ruleset_rule(schedule, start_date:, end_date:, apply_to_days: [1, 1, 1, 1, 1, 1, 1], hourly_values:)
rule = OpenStudio::Model::ScheduleRule.new(schedule)

if (not apply_to_days.is_a? Array) || (apply_to_days.size != 7)
fail 'Unexpected apply_to_days.'
end

# Allow for either 0-based or 1-based array for now
# FUTURE: Restrict to 0-based
if (not hourly_values.is_a? Array) || (hourly_values.size != 24 && hourly_values.size != 25)
fail 'Unexpected hourly_values.'
end

if hourly_values.size == 24
hourly_values = [nil] + hourly_values
end

if apply_to_days == [1, 1, 1, 1, 1, 1, 1]
rule.setName("#{schedule.name} allday rule")
elsif apply_to_days == [0, 1, 1, 1, 1, 1, 0]
rule.setName("#{schedule.name} weekday rule")
elsif apply_to_days == [1, 0, 0, 0, 0, 0, 1]
rule.setName("#{schedule.name} weekend rule")
else
rule.setName("#{schedule.name} rule")
end
rule.setStartDate(start_date)
rule.setEndDate(end_date)
rule.setApplySunday(apply_to_days[0])
rule.setApplyMonday(apply_to_days[1])
rule.setApplyTuesday(apply_to_days[2])
rule.setApplyWednesday(apply_to_days[3])
rule.setApplyThursday(apply_to_days[4])
rule.setApplyFriday(apply_to_days[5])
rule.setApplySaturday(apply_to_days[6])

day_sch = rule.daySchedule
day_sch.setName("#{schedule.name} day")

previous_value = hourly_values[1]
for h in 1..24
next if (h != 24) && (hourly_values[h + 1] == previous_value)

day_sch.addValue(OpenStudio::Time.new(0, h, 0, 0), previous_value)
previous_value = hourly_values[h + 1]
end

return rule
end

# Adds a ScheduleFile object to the OpenStudio model.
#
# @param model [OpenStudio::Model::Model] OpenStudio Model object
# @param name [String] Name for the OpenStudio object
# @param file_path [String] Full path to the CSV schedule file
# @param col_num [Integer] The column number with the desired schedule values; first column is 1
# @param rows_to_skip [Integer] The number of header rows to skip
# @param num_hours [Integer] Number of hours of data, should be 8760 of 8784
# @param mins_per_item [Integer] Number of minutes associated with each line of the file
# @param limits [String] Data type for the values contained in the schedule (EPlus::ScheduleTypeXXX)
# @return [OpenStudio::Model::ScheduleFile] The model object
def self.add_schedule_file(model, name:, file_path:, col_num:, rows_to_skip:, num_hours:, mins_per_item:, limits: nil)
file_dir = File.dirname(file_path)
if not model.workflowJSON.filePaths.map(&:to_s).include?(file_dir)
model.workflowJSON.addFilePath(file_dir)
end

sch = OpenStudio::Model::ScheduleFile.new(model, File.basename(file_path))
sch.setName(name)
sch.setColumnNumber(col_num)
sch.setRowstoSkipatTop(rows_to_skip)
sch.setNumberofHoursofData(num_hours)
sch.setMinutesperItem(mins_per_item)
sch.setTranslateFileWithRelativePath(true)
add_schedule_type_limits(model, schedule: sch, limits: limits)
return sch
end

# Adds a OpenStudio::Model::ScheduleTypeLimits object to the OpenStuio model (unless
# one has already been added) and assigns the schedule to it.
#
# @param model [OpenStudio::Model::Model] OpenStudio Model object
# @param schedule [OpenStudio::Model::Schedule] The schedule of interest
# @param limits [String] Data type for the values contained in the schedule (EPlus::ScheduleTypeXXX)
# @return [OpenStudio::Model::ScheduleTypeLimits] The model object
def self.add_schedule_type_limits(model, schedule:, limits:)
return if limits.nil?

stl = model.getScheduleTypeLimitss.find { |stl| stl.name.to_s == limits }

if stl.nil?
stl = OpenStudio::Model::ScheduleTypeLimits.new(model)
stl.setName(limits)
if limits == EPlus::ScheduleTypeLimitsFraction
stl.setLowerLimitValue(0)
stl.setUpperLimitValue(1)
stl.setNumericType('Continuous')
elsif limits == EPlus::ScheduleTypeLimitsOnOff
stl.setLowerLimitValue(0)
stl.setUpperLimitValue(1)
stl.setNumericType('Discrete')
elsif limits == EPlus::ScheduleTypeLimitsTemperature
stl.setUnitType('Temperature')
else
fail "Unexpected schedule type limits: #{limits}"
end
end

schedule.setScheduleTypeLimits(stl)

return stl
end

# Adds an EnergyManagementSystemSensor to the OpenStudio model.
#
# The EnergyManagementSystemSensor object gets information during the simulation
Expand Down
Loading

0 comments on commit afdd388

Please sign in to comment.