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

Add schedule methods to model.rb #1851

Merged
merged 12 commits into from
Oct 5, 2024
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