diff --git a/lib/files/model.json b/lib/files/model.json
index 37390ec5..74e1e2de 100644
--- a/lib/files/model.json
+++ b/lib/files/model.json
@@ -160,9 +160,9 @@
"description": "\n"
},
{
- "name": "idealairsystem_model",
- "x-displayName": "IdealAirSystem",
- "description": "\n"
+ "name": "idealairsystemabridged_model",
+ "x-displayName": "IdealAirSystemAbridged",
+ "description": "\n"
},
{
"name": "infiltrationabridged_model",
@@ -336,7 +336,7 @@
"floorsetabridged_model",
"gasequipmentabridged_model",
"ground_model",
- "idealairsystem_model",
+ "idealairsystemabridged_model",
"infiltrationabridged_model",
"lightingabridged_model",
"model_model",
@@ -1007,74 +1007,6 @@
"properties"
]
},
- "IdealAirSystem": {
- "title": "IdealAirSystem",
- "description": "Provides a model for an ideal HVAC system.",
- "type": "object",
- "properties": {
- "type": {
- "title": "Type",
- "default": "IdealAirSystem",
- "type": "string",
- "pattern": "^IdealAirSystem$"
- },
- "heating_limit": {
- "title": "Heating Limit",
- "default": "autosize",
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "type": "string"
- }
- ]
- },
- "cooling_limit": {
- "title": "Cooling Limit",
- "default": "autosize",
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "type": "string"
- }
- ]
- },
- "economizer_type": {
- "title": "Economizer Type",
- "default": "DifferentialDryBulb",
- "enum": [
- "NoEconomizer",
- "DifferentialDryBulb",
- "DifferentialEnthalpy"
- ],
- "type": "string"
- },
- "demand_control_ventilation": {
- "title": "Demand Control Ventilation",
- "default": false,
- "type": "boolean"
- },
- "sensible_heat_recovery": {
- "title": "Sensible Heat Recovery",
- "default": 0,
- "minimum": 0,
- "maximum": 1,
- "type": "number"
- },
- "latent_heat_recovery": {
- "title": "Latent Heat Recovery",
- "default": 0,
- "minimum": 0,
- "maximum": 1,
- "type": "number"
- }
- }
- },
"PeopleAbridged": {
"title": "PeopleAbridged",
"description": "Base class for all objects requiring a valid EnergyPlus name.",
@@ -1536,7 +1468,11 @@
"type": "string"
},
"hvac": {
- "$ref": "#/components/schemas/IdealAirSystem"
+ "title": "Hvac",
+ "description": "An optional name of a HVAC system (such as an IdealAirSystem) that specifies how the Room is conditioned. If None, it will be assumed that the Room is not conditioned.",
+ "maxLength": 100,
+ "minLength": 1,
+ "type": "string"
},
"people": {
"title": "People",
@@ -2954,6 +2890,120 @@
"name"
]
},
+ "IdealAirSystemAbridged": {
+ "title": "IdealAirSystemAbridged",
+ "description": "Provides a model for an ideal HVAC system.",
+ "type": "object",
+ "properties": {
+ "name": {
+ "title": "Name",
+ "description": "Name of the object. Must use only ASCII characters and exclude (, ; ! \\n \\t). It cannot be longer than 100 characters.",
+ "maxLength": 100,
+ "minLength": 1,
+ "type": "string"
+ },
+ "type": {
+ "title": "Type",
+ "default": "IdealAirSystemAbridged",
+ "type": "string",
+ "pattern": "^IdealAirSystemAbridged$"
+ },
+ "economizer_type": {
+ "title": "Economizer Type",
+ "description": "Text to indicate the type of air-side economizer used on the ideal air system. Economizers will mix in a greater amount of outdoor air to cool the zone (rather than running the cooling system) when the zone needs cooling and the outdoor air is cooler than the zone.",
+ "default": "DifferentialDryBulb",
+ "enum": [
+ "NoEconomizer",
+ "DifferentialDryBulb",
+ "DifferentialEnthalpy"
+ ],
+ "type": "string"
+ },
+ "demand_control_ventilation": {
+ "title": "Demand Control Ventilation",
+ "description": "Boolean to note whether demand controlled ventilation should be used on the system, which will vary the amount of ventilation air according to the occupancy schedule of the zone.",
+ "default": false,
+ "type": "boolean"
+ },
+ "sensible_heat_recovery": {
+ "title": "Sensible Heat Recovery",
+ "description": "A number between 0 and 1 for the effectiveness of sensible heat recovery within the system.",
+ "default": 0,
+ "minimum": 0,
+ "maximum": 1,
+ "type": "number"
+ },
+ "latent_heat_recovery": {
+ "title": "Latent Heat Recovery",
+ "description": "A number between 0 and 1 for the effectiveness of latent heat recovery within the system.",
+ "default": 0,
+ "minimum": 0,
+ "maximum": 1,
+ "type": "number"
+ },
+ "heating_air_temperature": {
+ "title": "Heating Air Temperature",
+ "description": "A number for the maximum heating supply air temperature [C].",
+ "default": 50,
+ "exclusiveMinimum": 0,
+ "exclusiveMaximum": 100,
+ "type": "number"
+ },
+ "cooling_air_temperature": {
+ "title": "Cooling Air Temperature",
+ "description": "A number for the minimum cooling supply air temperature [C].",
+ "default": 13,
+ "exclusiveMinimum": -100,
+ "exclusiveMaximum": 50,
+ "type": "number"
+ },
+ "heating_limit": {
+ "title": "Heating Limit",
+ "description": "A number for the maximum heating capacity in Watts. This can also be the text \"autosize\" to indicate that the capacity should be determined during the EnergyPlus sizing calculation. This can also be the text \"NoLimit\" to indicate no upper limit to the heating capacity. Note that setting this to None will trigger the default (\"autosize\").",
+ "default": "autosize",
+ "anyOf": [
+ {
+ "type": "number",
+ "minimum": 0
+ },
+ {
+ "type": "string"
+ }
+ ]
+ },
+ "cooling_limit": {
+ "title": "Cooling Limit",
+ "description": "A number for the maximum cooling capacity in Watts. This can also be the text \"autosize\" to indicate that the capacity should be determined during the sizing calculation. This can also be the text \"NoLimit\" to indicate no upper limit to the cooling capacity. Note that setting this to None will trigger the default (\"autosize\").",
+ "default": "autosize",
+ "anyOf": [
+ {
+ "type": "number",
+ "minimum": 0
+ },
+ {
+ "type": "string"
+ }
+ ]
+ },
+ "heating_availability": {
+ "title": "Heating Availability",
+ "description": "An optional name of a schedule to set the availability of heating over the course of the simulation.",
+ "maxLength": 100,
+ "minLength": 1,
+ "type": "string"
+ },
+ "cooling_availability": {
+ "title": "Cooling Availability",
+ "description": "An optional name of a schedule to set the availability of cooling over the course of the simulation.",
+ "maxLength": 100,
+ "minLength": 1,
+ "type": "string"
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
"ProgramTypeAbridged": {
"title": "ProgramTypeAbridged",
"description": "Base class for all objects requiring a valid EnergyPlus name.",
@@ -3419,6 +3469,13 @@
],
"type": "string"
},
+ "global_construction_set": {
+ "title": "Global Construction Set",
+ "description": "Name for the ConstructionSet to be used for all objects lacking their own construction or a parent Room construction_set. This ConstructionSet must appear under the Model construction_sets.",
+ "maxLength": 100,
+ "minLength": 1,
+ "type": "string"
+ },
"construction_sets": {
"title": "Construction Sets",
"description": "List of all ConstructionSets in the Model.",
@@ -3427,13 +3484,6 @@
"$ref": "#/components/schemas/ConstructionSetAbridged"
}
},
- "global_construction_set": {
- "title": "Global Construction Set",
- "description": "Name for the ConstructionSet to be used for all objects lacking their own construction or a parent Room construction_set. This ConstructionSet must appear under the Model construction_sets.",
- "maxLength": 100,
- "minLength": 1,
- "type": "string"
- },
"constructions": {
"title": "Constructions",
"description": "A list of all unique constructions in the model. This includes constructions across all Faces, Apertures, Doors, Shades, Room ConstructionSets, and the global_construction_set.",
@@ -3488,9 +3538,17 @@
]
}
},
+ "hvacs": {
+ "title": "Hvacs",
+ "description": "List of all ProgramTypes in the Model.",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/IdealAirSystemAbridged"
+ }
+ },
"program_types": {
"title": "Program Types",
- "description": "List of all ProgramTypes in the Model.",
+ "description": "List of all HVAC systems in the Model.",
"type": "array",
"items": {
"$ref": "#/components/schemas/ProgramTypeAbridged"
@@ -3498,7 +3556,7 @@
},
"schedules": {
"title": "Schedules",
- "description": "A list of all unique schedules in the model. This includes schedules across all ProgramTypes, Rooms, and Shades.",
+ "description": "A list of all unique schedules in the model. This includes schedules across all HVAC systems, ProgramTypes, Rooms, and Shades.",
"type": "array",
"items": {
"anyOf": [
diff --git a/lib/ladybug/energy_model/ideal_air_system.rb b/lib/ladybug/energy_model/ideal_air_system.rb
index 3cf9e26c..1ecd97ee 100644
--- a/lib/ladybug/energy_model/ideal_air_system.rb
+++ b/lib/ladybug/energy_model/ideal_air_system.rb
@@ -34,12 +34,12 @@
module Ladybug
module EnergyModel
- class IdealAirSystem < ModelObject
+ class IdealAirSystemAbridged < ModelObject
attr_reader :errors, :warnings
def initialize(hash = {})
super(hash)
- raise "Incorrect model type '#{@type}'" unless @type == 'IdealAirSystem'
+ raise "Incorrect model type '#{@type}'" unless @type == 'IdealAirSystemAbridged'
end
def defaults
@@ -47,56 +47,108 @@ def defaults
result
end
- def create_openstudio_object(openstudio_model)
- openstudio_ideal_air = OpenStudio::Model::ZoneHVACIdealLoadsAirSystem.new(openstudio_model)
- if @hash[:heating_limit] && @hash[:heating_limit] != 'NoLimit'
- openstudio_ideal_air.setHeatingLimit('LimitCapacity')
- if @hash[:heating_limit] == 'autosize'
- openstudio_ideal_air.autosizeMaximumSensibleHeatingCapacity()
- else
- openstudio_ideal_air.setMaximumSensibleHeatingCapacity(@hash[:heating_limit])
- end
+ def create_openstudio_object(openstudio_model)
+ # create the ideal air system and set the name
+ os_ideal_air = OpenStudio::Model::ZoneHVACIdealLoadsAirSystem.new(openstudio_model)
+ os_ideal_air.setName(@hash[:name])
+
+ # default properties from the openapi schema
+ default_props = @@schema[:components][:schemas][:IdealAirSystemAbridged][:properties]
+
+ # assign the economizer type
+ if @hash[:economizer_type]
+ os_ideal_air.setOutdoorAirEconomizerType(@hash[:economizer_type])
else
- openstudio_ideal_air.setHeatingLimit('NoLimit')
+ os_ideal_air.setOutdoorAirEconomizerType(
+ default_props[:economizer_type][:default])
end
- if @hash[:cooling_limit] && @hash[:cooling_limit] != 'NoLimit'
- openstudio_ideal_air.setCoolingLimit('LimitFlowRateAndCapacity')
- if @hash[:cooling_limit] == 'autosize'
- openstudio_ideal_air.autosizeMaximumTotalCoolingCapacity()
- openstudio_ideal_air.autosizeMaximumCoolingAirFlowRate()
- else
- openstudio_ideal_air.setMaximumTotalCoolingCapacity(@hash[:cooling_limit])
- openstudio_ideal_air.autosizeMaximumCoolingAirFlowRate()
- end
+
+ # set the sensible heat recovery
+ if @hash[:sensible_heat_recovery] != 0
+ os_ideal_air.setSensibleHeatRecoveryEffectiveness(@hash[:sensible_heat_recovery])
+ os_ideal_air.setHeatRecoveryType('Sensible')
+ else
+ os_ideal_air.setSensibleHeatRecoveryEffectiveness(
+ default_props[:sensible_heat_recovery][:default])
+ end
+
+ # set the latent heat recovery
+ if @hash[:latent_heat_recovery] != 0
+ os_ideal_air.setLatentHeatRecoveryEffectiveness(@hash[:latent_heat_recovery])
+ os_ideal_air.setHeatRecoveryType('Enthalpy')
+ else
+ os_ideal_air.setLatentHeatRecoveryEffectiveness(
+ default_props[:latent_heat_recovery][:default])
+ end
+
+ # assign the demand controlled ventilation
+ if @hash[:demand_controlled_ventilation]
+ os_ideal_air.setDemandControlledVentilationType('OccupancySchedule')
else
- openstudio_ideal_air.setCoolingLimit('NoLimit')
+ os_ideal_air.setDemandControlledVentilationType('None')
end
- if @hash[:economizer_type]
- openstudio_ideal_air.setOutdoorAirEconomizerType(@hash[:economizer_type])
+
+ # set the maximum heating supply air temperature
+ if @hash[:heating_air_temperature]
+ os_ideal_air.setMaximumHeatingSupplyAirTemperature(@hash[:heating_air_temperature])
else
- openstudio_ideal_air.setOutdoorAirEconomizerType(@@schema[:components][:schemas][:IdealAirSystem][:properties][:economizer_type][:default])
+ os_ideal_air.setMaximumHeatingSupplyAirTemperature(
+ default_props[:heating_air_temperature][:default])
end
- if @hash[:sensible_heat_recovery]
- openstudio_ideal_air.setSensibleHeatRecoveryEffectiveness(@hash[:sensible_heat_recovery])
+
+ # set the maximum cooling supply air temperature
+ if @hash[:cooling_air_temperature]
+ os_ideal_air.setMinimumCoolingSupplyAirTemperature(@hash[:cooling_air_temperature])
else
- openstudio_ideal_air.setSensibleHeatRecoveryEffectiveness(@@schema[:components][:schemas][:IdealAirSystem][:properties][:sensible_heat_recovery][:default]) #TODO : Openstudio defaults are different from schema
+ os_ideal_air.setMinimumCoolingSupplyAirTemperature(
+ default_props[:cooling_air_temperature][:default])
end
- if @hash[:latent_heat_recovery]
- openstudio_ideal_air.setLatentHeatRecoveryEffectiveness(@hash[:latent_heat_recovery])
+
+ # assign limits to the system's heating capacity
+ if @hash[:heating_limit] == 'NoLimit'
+ os_ideal_air.setHeatingLimit('NoLimit')
+ else
+ os_ideal_air.setHeatingLimit('LimitCapacity')
+ if @hash[:heating_limit].is_a? Numeric
+ os_ideal_air.setMaximumSensibleHeatingCapacity(@hash[:heating_limit])
+ else
+ os_ideal_air.autosizeMaximumSensibleHeatingCapacity()
+ end
+ end
+
+ # assign limits to the system's cooling capacity
+ if @hash[:cooling_limit] == 'NoLimit'
+ os_ideal_air.setCoolingLimit('NoLimit')
else
- openstudio_ideal_air.setLatentHeatRecoveryEffectiveness(@@schema[:components][:schemas][:IdealAirSystem][:properties][:latent_heat_recovery][:default]) #TODO : Openstudio defaults are different from schema
+ os_ideal_air.setCoolingLimit('LimitFlowRateAndCapacity')
+ if @hash[:cooling_limit].is_a? Numeric
+ os_ideal_air.setMaximumTotalCoolingCapacity(@hash[:cooling_limit])
+ os_ideal_air.autosizeMaximumCoolingAirFlowRate()
+ else
+ os_ideal_air.autosizeMaximumTotalCoolingCapacity()
+ os_ideal_air.autosizeMaximumCoolingAirFlowRate()
+ end
end
- if @hash[:demand_control_ventilation]
- if @hash[:demand_control_ventilation] == true
- openstudio_ideal_air.setDemandControlledVentilationType('OccupancySchedule')
- else
- openstudio_ideal_air.setDemandControlledVentilationType('None')
+
+ # assign heating availability shcedules
+ if @hash[:heating_availability]
+ schedule = openstudio_model.getScheduleByName(@hash[:heating_availability])
+ unless schedule.empty?
+ os_schedule = schedule.get
+ os_ideal_air.setHeatingAvailabilitySchedule(os_schedule)
+ end
+ end
+
+ # assign cooling availability shcedules
+ if @hash[:cooling_availability]
+ schedule = openstudio_model.getScheduleByName(@hash[:cooling_availability])
+ unless schedule.empty?
+ os_schedule = schedule.get
+ os_ideal_air.setCoolingAvailabilitySchedule(os_schedule)
end
- else
- openstudio_ideal_air.setDemandControlledVentilationType('None')
end
- openstudio_ideal_air
+ os_ideal_air
end
end #IdealAirSystem
diff --git a/lib/ladybug/energy_model/model.rb b/lib/ladybug/energy_model/model.rb
index 2b15fff5..617bee18 100644
--- a/lib/ladybug/energy_model/model.rb
+++ b/lib/ladybug/energy_model/model.rb
@@ -56,6 +56,7 @@
require 'ladybug/energy_model/space_type'
require 'ladybug/energy_model/setpoint_thermostat'
require 'ladybug/energy_model/setpoint_humidistat'
+require 'ladybug/energy_model/ideal_air_system'
require 'openstudio'
module Ladybug
@@ -151,6 +152,9 @@ def create_openstudio_objects
create_orphaned_faces
create_orphaned_apertures
create_orphaned_doors
+
+ # create the hvac systems
+ create_hvacs
end
def create_materials
@@ -251,43 +255,35 @@ def create_schedules
def create_space_types
if @hash[:properties][:energy][:program_types]
- $programtype_array = []
+ $programtype_setpoint_hash = Hash.new
@hash[:properties][:energy][:program_types].each do |space_type|
space_type_object = SpaceType.new(space_type)
space_type_object.to_openstudio(@openstudio_model)
end
end
- end
+ end
def create_rooms
if @hash[:rooms]
- $room_array_setpoint = []
@hash[:rooms].each do |room|
room_object = Room.new(room)
openstudio_room = room_object.to_openstudio(@openstudio_model)
+ # for rooms with setpoint objects definied in the ProgramType, make a new thermostat
if room[:properties][:energy][:program_type] && !room[:properties][:energy][:setpoint]
- $room_array_setpoint << room
- end
- end
-
- # for rooms with setpoint objects definied in the ProgramType, make a new thermostat
- $room_array_setpoint.each do |single_room|
- room_name = single_room[:name]
- room_get = @openstudio_model.getSpaceByName(room_name)
- unless room_get.empty?
- room_object_get = room_get.get
- program_type_name = single_room[:properties][:energy][:program_type]
- thermal_zone = room_object_get.thermalZone()
- thermal_zone_object = thermal_zone.get
- program_type_hash = $programtype_array.select{|k| k[:name] == program_type_name.to_s}
- thermostat_object = SetpointThermostat.new(program_type_hash[0][:setpoint])
- openstudio_thermostat = thermostat_object.to_openstudio(@openstudio_model)
- thermal_zone_object.setThermostatSetpointDualSetpoint(openstudio_thermostat)
- if program_type_hash[0][:setpoint][:humidification_schedule] or program_type_hash[0][:setpoint][:dehumidification_schedule]
- humidistat_object = ZoneControlHumidistat.new(program_type_hash[0][:setpoint])
- openstudio_humidistat = humidistat_object.to_openstudio(@openstudio_model)
- thermal_zone_object.setZoneControlHumidistat(openstudio_humidistat)
+ thermal_zone = openstudio_room.thermalZone()
+ unless thermal_zone.empty?
+ thermal_zone_object = thermal_zone.get
+ program_type_name = room[:properties][:energy][:program_type]
+ setpoint_hash = $programtype_setpoint_hash[program_type_name]
+ thermostat_object = SetpointThermostat.new(setpoint_hash)
+ openstudio_thermostat = thermostat_object.to_openstudio(@openstudio_model)
+ thermal_zone_object.setThermostatSetpointDualSetpoint(openstudio_thermostat)
+ if setpoint_hash[:humidification_schedule] or setpoint_hash[:dehumidification_schedule]
+ humidistat_object = ZoneControlHumidistat.new(setpoint_hash)
+ openstudio_humidistat = humidistat_object.to_openstudio(@openstudio_model)
+ thermal_zone_object.setZoneControlHumidistat(openstudio_humidistat)
+ end
end
end
end
@@ -325,6 +321,41 @@ def create_orphaned_doors
end
end
+ def create_hvacs
+ if @hash[:properties][:energy][:hvacs]
+ # gather all of the hashes of the HVACs
+ hvac_hashes = Hash.new
+ @hash[:properties][:energy][:hvacs].each do |hvac|
+ hvac_hashes[hvac[:name]] = hvac
+ hvac_hashes[hvac[:name]]['rooms'] = []
+ end
+ # loop through the rooms and trach which are assigned to each HVAC
+ if @hash[:rooms]
+ @hash[:rooms].each do |room|
+ if room[:properties][:energy][:hvac]
+ hvac_hashes[room[:properties][:energy][:hvac]]['rooms'] << room[:name]
+ end
+ end
+ end
+
+ hvac_hashes.each_value do |hvac|
+ system_type = hvac[:type]
+ case system_type
+ when 'IdealAirSystemAbridged'
+ ideal_air_system = IdealAirSystemAbridged.new(hvac)
+ os_ideal_air_system = ideal_air_system.to_openstudio(@openstudio_model)
+ hvac['rooms'].each do |room_name|
+ zone_get = @openstudio_model.getThermalZoneByName(room_name)
+ unless zone_get.empty?
+ os_thermal_zone = zone_get.get
+ os_ideal_air_system.addToThermalZone(os_thermal_zone)
+ end
+ end
+ end
+ end
+ end
+ end
+
#TODO: create runlog for errors.
end # Model
diff --git a/lib/ladybug/energy_model/room.rb b/lib/ladybug/energy_model/room.rb
index 5d7c9cda..0eb5d8ee 100644
--- a/lib/ladybug/energy_model/room.rb
+++ b/lib/ladybug/energy_model/room.rb
@@ -32,7 +32,13 @@
require 'ladybug/energy_model/model_object'
require 'ladybug/energy_model/face'
require 'ladybug/energy_model/people_abridged'
-require 'ladybug/energy_model/ideal_air_system'
+require 'ladybug/energy_model/lighting_abridged'
+require 'ladybug/energy_model/electric_equipment_abridged'
+require 'ladybug/energy_model/gas_equipment_abridged'
+require 'ladybug/energy_model/infiltration_abridged'
+require 'ladybug/energy_model/ventilation_abridged'
+require 'ladybug/energy_model/setpoint_thermostat'
+require 'ladybug/energy_model/setpoint_humidistat'
require 'ladybug/energy_model/shade'
require 'openstudio'
@@ -86,15 +92,9 @@ def create_openstudio_object(openstudio_model)
end
end
- # assign the hvac system
- if @hash[:properties][:energy][:hvac]
- system_type = @hash[:properties][:energy][:hvac][:type]
- case system_type
- when 'IdealAirSystem'
- ideal_air_system = IdealAirSystem.new(@hash[:properties][:energy][:hvac])
- openstudio_ideal_air_system = ideal_air_system.to_openstudio(openstudio_model)
- openstudio_ideal_air_system.addToThermalZone(openstudio_thermal_zone)
- end
+ # assign the multiplier
+ if @hash[:multiplier] and @hash[:multiplier] != 1
+ openstudio_thermal_zone.setMultiplier(@hash[:multiplier])
end
# assign all of the faces to the room
diff --git a/lib/ladybug/energy_model/space_type.rb b/lib/ladybug/energy_model/space_type.rb
index c43a7468..e0e620c4 100644
--- a/lib/ladybug/energy_model/space_type.rb
+++ b/lib/ladybug/energy_model/space_type.rb
@@ -104,9 +104,9 @@ def create_openstudio_object(openstudio_model)
openstudio_space_type.setDesignSpecificationOutdoorAir(openstudio_ventilation)
end
- #Adding setpoints from space types to global hash.
+ # add setpoints from to a global hash that will be used to assign them to rooms
if @hash[:setpoint]
- $programtype_array << @hash
+ $programtype_setpoint_hash[@hash[:name]] = @hash[:setpoint]
end
openstudio_space_type