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