From a622ca85ede1a6947c3ab34277b529ee69da6572 Mon Sep 17 00:00:00 2001 From: Scott Horowitz Date: Fri, 14 Mar 2025 17:30:09 -0600 Subject: [PATCH 1/9] Testing https://github.com/NREL/OpenStudio/pull/5367... not complete because Output:Table:Monthly is not wrapped. --- HPXMLtoOpenStudio/resources/model.rb | 66 +++++++++++-- ReportSimulationOutput/measure.rb | 133 +++++++++++++-------------- 2 files changed, 123 insertions(+), 76 deletions(-) diff --git a/HPXMLtoOpenStudio/resources/model.rb b/HPXMLtoOpenStudio/resources/model.rb index 99cae6a506..8348141381 100644 --- a/HPXMLtoOpenStudio/resources/model.rb +++ b/HPXMLtoOpenStudio/resources/model.rb @@ -717,7 +717,7 @@ def self.add_schedule_type_limits(model, schedule:, limits:) return stl end - # Adds an EnergyManagementSystemSensor to the OpenStudio model. + # Adds an EnergyManagementSystemSensor object to the OpenStudio model. # # The EnergyManagementSystemSensor object gets information during the simulation # that can be used in custom calculations. @@ -734,7 +734,7 @@ def self.add_ems_sensor(model, name:, output_var_or_meter_name:, key_name:) return sensor end - # Adds an EnergyManagementSystemGlobalVariable to the OpenStudio model. + # Adds an EnergyManagementSystemGlobalVariable object to the OpenStudio model. # # The EnergyManagementSystemGlobalVariable object allows an EMS variable to be # global such that it can be used across EMS programs/subroutines. @@ -746,7 +746,7 @@ def self.add_ems_global_var(model, var_name:) return OpenStudio::Model::EnergyManagementSystemGlobalVariable.new(model, ems_friendly_name(var_name)) end - # Adds an EnergyManagementSystemTrendVariable to the OpenStudio model. + # Adds an EnergyManagementSystemTrendVariable object to the OpenStudio model. # # The EnergyManagementSystemTrendVariable object creates a global EMS variable # that stores the recent history of an EMS variable for use in a calculation. @@ -762,7 +762,7 @@ def self.add_ems_trend_var(model, ems_object:, num_timesteps_logged:) return tvar end - # Adds an EnergyManagementSystemInternalVariable to the OpenStudio model. + # Adds an EnergyManagementSystemInternalVariable object to the OpenStudio model. # # The EnergyManagementSystemInternalVariable object is used to obtain static data from # elsewhere in the model. @@ -779,7 +779,7 @@ def self.add_ems_internal_var(model, name:, model_object:, type:) return ivar end - # Adds an EnergyManagementSystemActuator to the OpenStudio model. + # Adds an EnergyManagementSystemActuator object to the OpenStudio model. # # The EnergyManagementSystemActuator object specifies the properties or controls # of an EnergyPlus object that is to be overridden during the simulation. @@ -798,7 +798,7 @@ def self.add_ems_actuator(name:, model_object:, comp_type_and_control:) return act end - # Adds an EnergyManagementSystemProgram to the OpenStudio model. + # Adds an EnergyManagementSystemProgram object to the OpenStudio model. # # The EnergyManagementSystemProgram object allows custom calculations to be # performed within the EnergyPlus simulation in order to override the properties @@ -815,7 +815,7 @@ def self.add_ems_program(model, name:, lines: nil) return prg end - # Adds an EnergyManagementSystemSubroutine to the OpenStudio model. + # Adds an EnergyManagementSystemSubroutine object to the OpenStudio model. # # The EnergyManagementSystemSubroutine object allows EMS code to be reused # across multiple EMS programs. @@ -831,7 +831,7 @@ def self.add_ems_subroutine(model, name:, lines: nil) return sbrt end - # Adds an EnergyManagementSystemProgramCallingManager to the OpenStudio model. + # Adds an EnergyManagementSystemProgramCallingManager object to the OpenStudio model. # # The EnergyManagementSystemProgramCallingManager object is used to specify when # an EMS program is run during the simulation. @@ -850,7 +850,57 @@ def self.add_ems_program_calling_manager(model, name:, calling_point:, ems_progr end return pcm end + + # Adds an EnergyManagementSystemOutputVariable object to the OpenStudio model. + # + # The EnergyManagementSystemOutputVariable object allows generating output for + # an EMS variable; the object can be referenced by an OutputVariable object. + # + # @param model [OpenStudio::Model::Model] OpenStudio Model object + # @param name [String] User-defined name for the new output variable + # @param ems_variable_name [String] EMS variable name to be output + # @param type_of_data [String] The nature of the variable ('averaged' or 'summed') + # @param update_frequency [String] Timestep the variable is associated with ('ZoneTimestep' or 'SystemTimestep') + # @param ems_program_or_subroutine [OpenStudio::Model::EnergyManagementSystemProgram or EnergyManagementSystemSubroutine] The EMS program/subroutine with the EMS variable + # @param units [String] The units for the output variable in standard EnergyPlus units + # @return [OpenStudio::Model::EnergyManagementSystemOutputVariable] The model object + def self.add_ems_output_variable(model, name:, ems_variable_name:, type_of_data:, update_frequency:, ems_program_or_subroutine:, units:) + ov = OpenStudio::Model::EnergyManagementSystemOutputVariable.new(model, ems_variable_name) + ov.setName(name) + ov.setTypeOfDataInVariable(type_of_data) + ov.setUpdateFrequency(update_frequency) + ov.setEMSProgramOrSubroutineName(ems_program_or_subroutine) + ov.setUnits(units) + return ov + end + + # Adds an OutputVariable object to the OpenStudio model. + # + # @param model [OpenStudio::Model::Model] OpenStudio Model object + # @param key_value [String] The specific object reference for reporting + # @param variable_name [String] The variable name as shown in the eplusout.rdd file + # @param reporting_frequency [String] Output reporting frequency ('detailed', 'timestep', 'hourly', 'daily', 'monthly', 'runperiod', or 'annual') + # @return [OpenStudio::Model::OutputVariable] The model object + def self.add_output_variable(model, key_value:, variable_name:, reporting_frequency:) + ov = OpenStudio::Model::OutputVariable.new(variable_name, model) + ov.setKeyValue(key_value) + ov.setReportingFrequency(reporting_frequency) + return ov + end + # Adds an OutputMeter object to the OpenStudio model. + # + # @param model [OpenStudio::Model::Model] OpenStudio Model object + # @param meter_name [String] The meter name as shown in the eplusout.mdd file + # @param reporting_frequency [String] Output reporting frequency ('detailed', 'timestep', 'hourly', 'daily', 'monthly', 'runperiod', or 'annual') + # @return [OpenStudio::Model::OutputMeter] The model object + def self.add_output_meter(model, meter_name:, reporting_frequency:) + om = OpenStudio::Model::OutputMeter.new(model) + om.setName(meter_name) + om.setReportingFrequency(reporting_frequency) + return om + end + # Converts existing string to EMS friendly string. # # Source: openstudio-standards diff --git a/ReportSimulationOutput/measure.rb b/ReportSimulationOutput/measure.rb index c75f74090f..3d77d2102a 100644 --- a/ReportSimulationOutput/measure.rb +++ b/ReportSimulationOutput/measure.rb @@ -334,36 +334,29 @@ def get_arguments(runner, arguments, user_arguments) return args end - # Return a vector of IdfObject's to request EnergyPlus objects needed by the run method. + # Adds OpenStudio model objects to requests desired outputs. # + # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param runner [OpenStudio::Measure::OSRunner] Object typically used to display warnings # @param user_arguments [OpenStudio::Measure::OSArgumentMap] OpenStudio measure arguments - # @return [Array] array of OpenStudio IdfObject objects - def energyPlusOutputRequests(runner, user_arguments) - super(runner, user_arguments) - - result = OpenStudio::IdfObjectVector.new - return result if runner.halted - - model = runner.lastOpenStudioModel - if model.empty? - return result - end - - @model = model.get - + # @return [Boolean] Success + def modelOutputRequests(model, runner, user_arguments) + return false if runner.halted + + @model = model + # use the built-in error checking - if !runner.validateUserArguments(arguments(@model), user_arguments) + if !runner.validateUserArguments(arguments(model), user_arguments) return result end - unmet_hours_program = @model.getEnergyManagementSystemPrograms.find { |p| p.additionalProperties.getFeatureAsString('ObjectType').to_s == Constants::ObjectTypeUnmetHoursProgram } - total_loads_program = @model.getEnergyManagementSystemPrograms.find { |p| p.additionalProperties.getFeatureAsString('ObjectType').to_s == Constants::ObjectTypeTotalLoadsProgram } - comp_loads_program = @model.getEnergyManagementSystemPrograms.find { |p| p.additionalProperties.getFeatureAsString('ObjectType').to_s == Constants::ObjectTypeComponentLoadsProgram } - total_airflows_program = @model.getEnergyManagementSystemPrograms.find { |p| p.additionalProperties.getFeatureAsString('ObjectType').to_s == Constants::ObjectTypeTotalAirflowsProgram } - unmet_driving_hrs_program = @model.getEnergyManagementSystemPrograms.find { |p| p.additionalProperties.getFeatureAsString('ObjectType').to_s == Constants::ObjectTypeBEVDischargeProgram } - heated_zones = eval(@model.getBuilding.additionalProperties.getFeatureAsString('heated_zones').get) - cooled_zones = eval(@model.getBuilding.additionalProperties.getFeatureAsString('cooled_zones').get) + unmet_hours_program = model.getEnergyManagementSystemPrograms.find { |p| p.additionalProperties.getFeatureAsString('ObjectType').to_s == Constants::ObjectTypeUnmetHoursProgram } + total_loads_program = model.getEnergyManagementSystemPrograms.find { |p| p.additionalProperties.getFeatureAsString('ObjectType').to_s == Constants::ObjectTypeTotalLoadsProgram } + comp_loads_program = model.getEnergyManagementSystemPrograms.find { |p| p.additionalProperties.getFeatureAsString('ObjectType').to_s == Constants::ObjectTypeComponentLoadsProgram } + total_airflows_program = model.getEnergyManagementSystemPrograms.find { |p| p.additionalProperties.getFeatureAsString('ObjectType').to_s == Constants::ObjectTypeTotalAirflowsProgram } + unmet_driving_hrs_program = model.getEnergyManagementSystemPrograms.find { |p| p.additionalProperties.getFeatureAsString('ObjectType').to_s == Constants::ObjectTypeBEVDischargeProgram } + heated_zones = eval(model.getBuilding.additionalProperties.getFeatureAsString('heated_zones').get) + cooled_zones = eval(model.getBuilding.additionalProperties.getFeatureAsString('cooled_zones').get) args = get_arguments(runner, arguments(model), user_arguments) @@ -384,19 +377,19 @@ def energyPlusOutputRequests(runner, user_arguments) @fuels.each do |(_fuel_type, _total_or_net), fuel| next if fuel.meter.nil? - result << OpenStudio::IdfObject.load("Output:Meter,#{fuel.meter},runperiod;").get + Model.add_output_meter(model, meter_name: fuel.meter, reporting_frequency: 'runperiod') if args[:include_timeseries_fuel_consumptions] - result << OpenStudio::IdfObject.load("Output:Meter,#{fuel.meter},#{args[:timeseries_frequency]};").get + Model.add_output_meter(model, meter_name: fuel.meter, reporting_frequency: args[:timeseries_frequency]) end end if has_electricity_production || has_electricity_storage - result << OpenStudio::IdfObject.load('Output:Meter,ElectricityProduced:Facility,runperiod;').get # Used for error checking + Model.add_output_meter(model, meter_name: 'ElectricityProduced:Facility', reporting_frequency: 'runperiod') # Used for error checking end if has_electricity_storage - result << OpenStudio::IdfObject.load('Output:Meter,ElectricStorage:ElectricityProduced,runperiod;').get # Used for error checking + Model.add_output_meter(model, meter_name: 'ElectricStorage:ElectricityProduced', reporting_frequency: 'runperiod') # Used for error checking if args[:include_timeseries_fuel_consumptions] - result << OpenStudio::IdfObject.load("Output:Meter,ElectricStorage:ElectricityProduced,#{args[:timeseries_frequency]};").get + Model.add_output_meter(model, meter_name: 'ElectricStorage:ElectricityProduced', reporting_frequency: args[:timeseries_frequency]) end # Resilience @@ -405,12 +398,12 @@ def energyPlusOutputRequests(runner, user_arguments) if args[:timeseries_frequency] != 'timestep' resilience_frequency = 'hourly' end - result << OpenStudio::IdfObject.load("Output:Meter,Electricity:Facility,#{resilience_frequency};").get - result << OpenStudio::IdfObject.load("Output:Meter,ElectricityProduced:Facility,#{resilience_frequency};").get - result << OpenStudio::IdfObject.load("Output:Meter,ElectricStorage:ElectricityProduced,#{resilience_frequency};").get + Model.add_output_meter(model, meter_name: 'Electricity:Facility', reporting_frequency: resilience_frequency) + Model.add_output_meter(model, meter_name: 'ElectricityProduced:Facility', reporting_frequency: resilience_frequency) + Model.add_output_meter(model, meter_name: 'ElectricStorage:ElectricityProduced', reporting_frequency: resilience_frequency) @resilience.values.each do |resilience| resilience.variables.each do |_sys_id, varkey, var| - result << OpenStudio::IdfObject.load("Output:Variable,#{varkey},#{var},#{resilience_frequency};").get + Model.add_output_variable(model, key_value: varkey, variable_name: var, reporting_frequency: resilience_frequency) end end end @@ -418,30 +411,30 @@ def energyPlusOutputRequests(runner, user_arguments) # End Use/Hot Water Use/Ideal Load outputs { @end_uses => args[:include_timeseries_end_use_consumptions], - @hot_water_uses => args[:include_timeseries_hot_water_uses] }.each do |uses, include_ts| + @hot_water_uses => args[:include_timeseries_hot_water_uses] }.each do |uses, include_timeseries| uses.each do |key, use| use.variables.each do |_sys_id, varkey, var| - result << OpenStudio::IdfObject.load("Output:Variable,#{varkey},#{var},runperiod;").get - if include_ts - result << OpenStudio::IdfObject.load("Output:Variable,#{varkey},#{var},#{args[:timeseries_frequency]};").get + Model.add_output_variable(model, key_value: varkey, variable_name: var, reporting_frequency: 'runperiod') + if include_timeseries + Model.add_output_variable(model, key_value: varkey, variable_name: var, reporting_frequency: args[:timeseries_frequency]) end next unless use.is_a?(EndUse) fuel_type, _end_use = key if fuel_type == FT::Elec && args[:include_hourly_electric_end_use_consumptions] - result << OpenStudio::IdfObject.load("Output:Variable,#{varkey},#{var},hourly;").get + Model.add_output_variable(model, key_value: varkey, variable_name: var, reporting_frequency: 'hourly') end end - use.meters.each do |_sys_id, _varkey, var| - result << OpenStudio::IdfObject.load("Output:Meter,#{var},runperiod;").get - if include_ts - result << OpenStudio::IdfObject.load("Output:Meter,#{var},#{args[:timeseries_frequency]};").get + use.meters.each do |_, _, meter| + Model.add_output_meter(model, meter_name: meter, reporting_frequency: 'runperiod') + if include_timeseries + Model.add_output_meter(model, meter_name: meter, reporting_frequency: args[:timeseries_frequency]) end next unless use.is_a?(EndUse) fuel_type, _end_use = key if fuel_type == FT::Elec && args[:include_hourly_electric_end_use_consumptions] - result << OpenStudio::IdfObject.load("Output:Meter,#{var},hourly;").get + Model.add_output_meter(model, meter_name: meter, reporting_frequency: 'hourly') end end end @@ -449,13 +442,15 @@ def energyPlusOutputRequests(runner, user_arguments) # Peak Fuel outputs (annual only) @peak_fuels.values.each do |peak_fuel| + # FIXME: Output:Table:Monthly not wrapped by OpenStudio result << OpenStudio::IdfObject.load("Output:Table:Monthly,#{peak_fuel.report},2,#{peak_fuel.meter},Maximum;").get end # Peak Load outputs (annual only) @peak_loads.values.each do |peak_load| - result << OpenStudio::IdfObject.load("EnergyManagementSystem:OutputVariable,#{peak_load.ems_variable}_peakload_outvar,#{peak_load.ems_variable},Summed,ZoneTimestep,#{total_loads_program.name},J;").get - result << OpenStudio::IdfObject.load("Output:Table:Monthly,#{peak_load.report},2,#{peak_load.ems_variable}_peakload_outvar,Maximum;").get + ems_ov = Model.add_ems_output_variable(model, name: "#{peak_load.ems_variable}_peakload_outvar", ems_variable_name: peak_load.ems_variable, type_of_data: 'Summed', update_frequency: 'ZoneTimestep', ems_program_or_subroutine: total_loads_program, units: 'J') + # FIXME: Output:Table:Monthly not wrapped by OpenStudio + result << OpenStudio::IdfObject.load("Output:Table:Monthly,#{peak_load.report},2,#{ems_ov.name},Maximum;").get end # Unmet Hours (annual only) @@ -464,11 +459,11 @@ def energyPlusOutputRequests(runner, user_arguments) ems_program = key == UHT::Driving ? unmet_driving_hrs_program : unmet_hours_program - result << OpenStudio::IdfObject.load("EnergyManagementSystem:OutputVariable,#{unmet_hour.ems_variable}_annual_outvar,#{unmet_hour.ems_variable},Summed,ZoneTimestep,#{ems_program.name},hr;").get - result << OpenStudio::IdfObject.load("Output:Variable,*,#{unmet_hour.ems_variable}_annual_outvar,runperiod;").get + ems_ov = Model.add_ems_output_variable(model, name: "#{unmet_hour.ems_variable}_annual_outvar", ems_variable_name: unmet_hour.ems_variable, type_of_data: 'Summed', update_frequency: 'ZoneTimestep', ems_program_or_subroutine: ems_program, units: 'hr') + Model.add_output_variable(model, key_value: '*', variable_name: ems_ov.name, reporting_frequency: 'runperiod') if args[:include_timeseries_unmet_hours] - result << OpenStudio::IdfObject.load("EnergyManagementSystem:OutputVariable,#{unmet_hour.ems_variable}_timeseries_outvar,#{unmet_hour.ems_variable},Summed,ZoneTimestep,#{ems_program.name},hr;").get - result << OpenStudio::IdfObject.load("Output:Variable,*,#{unmet_hour.ems_variable}_timeseries_outvar,#{args[:timeseries_frequency]};").get + ems_ov = Model.add_ems_output_variable(model, name: "#{unmet_hour.ems_variable}_timeseries_outvar", ems_variable_name: unmet_hour.ems_variable, type_of_data: 'Summed', update_frequency: 'ZoneTimestep', ems_program_or_subroutine: ems_program, units: 'hr') + Model.add_output_variable(model, key_value: '*', variable_name: ems_ov.name, reporting_frequency: args[:timeseries_frequency]) end end @@ -476,35 +471,36 @@ def energyPlusOutputRequests(runner, user_arguments) @component_loads.values.each do |comp_load| next if comp_loads_program.nil? - result << OpenStudio::IdfObject.load("EnergyManagementSystem:OutputVariable,#{comp_load.ems_variable}_annual_outvar,#{comp_load.ems_variable},Summed,ZoneTimestep,#{comp_loads_program.name},J;").get - result << OpenStudio::IdfObject.load("Output:Variable,*,#{comp_load.ems_variable}_annual_outvar,runperiod;").get + ems_ov = Model.add_ems_output_variable(model, name: "#{comp_load.ems_variable}_annual_outvar", ems_variable_name: comp_load.ems_variable, type_of_data: 'Summed', update_frequency: 'ZoneTimestep', ems_program_or_subroutine: comp_loads_program, units: 'J') + Model.add_output_variable(model, key_value: '*', variable_name: ems_ov.name, reporting_frequency: 'runperiod') if args[:include_timeseries_component_loads] - result << OpenStudio::IdfObject.load("EnergyManagementSystem:OutputVariable,#{comp_load.ems_variable}_timeseries_outvar,#{comp_load.ems_variable},Summed,ZoneTimestep,#{comp_loads_program.name},J;").get - result << OpenStudio::IdfObject.load("Output:Variable,*,#{comp_load.ems_variable}_timeseries_outvar,#{args[:timeseries_frequency]};").get + ems_ov = Model.add_ems_output_variable(model, name: "#{comp_load.ems_variable}_timeseries_outvar", ems_variable_name: comp_load.ems_variable, type_of_data: 'Summed', update_frequency: 'ZoneTimestep', ems_program_or_subroutine: comp_loads_program, units: 'J') + Model.add_output_variable(model, key_value: '*', variable_name: ems_ov.name, reporting_frequency: args[:timeseries_frequency]) end end # Total Load outputs @loads.values.each do |load| if not load.ems_variable.nil? - result << OpenStudio::IdfObject.load("EnergyManagementSystem:OutputVariable,#{load.ems_variable}_annual_outvar,#{load.ems_variable},Summed,ZoneTimestep,#{total_loads_program.name},J;").get - result << OpenStudio::IdfObject.load("Output:Variable,*,#{load.ems_variable}_annual_outvar,runperiod;").get + ems_ov = Model.add_ems_output_variable(model, name: "#{load.ems_variable}_annual_outvar", ems_variable_name: load.ems_variable, type_of_data: 'Summed', update_frequency: 'ZoneTimestep', ems_program_or_subroutine: total_loads_program, units: 'J') + Model.add_output_variable(model, key_value: '*', variable_name: ems_ov.name, reporting_frequency: 'runperiod') if args[:include_timeseries_total_loads] - result << OpenStudio::IdfObject.load("EnergyManagementSystem:OutputVariable,#{load.ems_variable}_timeseries_outvar,#{load.ems_variable},Summed,ZoneTimestep,#{total_loads_program.name},J;").get - result << OpenStudio::IdfObject.load("Output:Variable,*,#{load.ems_variable}_timeseries_outvar,#{args[:timeseries_frequency]};").get + ems_ov = Model.add_ems_output_variable(model, name: "#{load.ems_variable}_timeseries_outvar", ems_variable_name: load.ems_variable, type_of_data: 'Summed', update_frequency: 'ZoneTimestep', ems_program_or_subroutine: total_loads_program, units: 'J') + Model.add_output_variable(model, key_value: '*', variable_name: ems_ov.name, reporting_frequency: args[:timeseries_frequency]) end end load.variables.each do |_sys_id, varkey, var| - result << OpenStudio::IdfObject.load("Output:Variable,#{varkey},#{var},runperiod;").get + Model.add_output_variable(model, key_value: varkey, variable_name: var, reporting_frequency: 'runperiod') if args[:include_timeseries_total_loads] - result << OpenStudio::IdfObject.load("Output:Variable,#{varkey},#{var},#{args[:timeseries_frequency]};").get + Model.add_output_variable(model, key_value: varkey, variable_name: var, reporting_frequency: args[:timeseries_frequency]) end end end # Temperature outputs (timeseries only) if args[:include_timeseries_zone_temperatures] - result << OpenStudio::IdfObject.load("Output:Variable,*,Zone Mean Air Temperature,#{args[:timeseries_frequency]};").get + Model.add_output_variable(model, key_value: '*', variable_name: 'Zone Mean Air Temperature', reporting_frequency: args[:timeseries_frequency]) + # For reporting temperature-scheduled spaces timeseries temperatures. keys = [HPXML::LocationOtherHeatedSpace, HPXML::LocationOtherMultifamilyBufferSpace, @@ -516,43 +512,44 @@ def energyPlusOutputRequests(runner, user_arguments) schedules = @model.getScheduleConstants.select { |sch| sch.additionalProperties.getFeatureAsString('ObjectType').to_s == key } next if schedules.empty? - result << OpenStudio::IdfObject.load("Output:Variable,#{schedules[0].name.to_s.upcase},Schedule Value,#{args[:timeseries_frequency]};").get + Model.add_output_variable(model, key_value: schedules[0].name.to_s.upcase, variable_name: 'Schedule Value', reporting_frequency: args[:timeseries_frequency]) end + # Also report thermostat setpoints heated_zones.each do |heated_zone| - result << OpenStudio::IdfObject.load("Output:Variable,#{heated_zone.upcase},Zone Thermostat Heating Setpoint Temperature,#{args[:timeseries_frequency]};").get + Model.add_output_variable(model, key_value: heated_zone.upcase, variable_name: 'Zone Thermostat Heating Setpoint Temperature', reporting_frequency: args[:timeseries_frequency]) end cooled_zones.each do |cooled_zone| - result << OpenStudio::IdfObject.load("Output:Variable,#{cooled_zone.upcase},Zone Thermostat Cooling Setpoint Temperature,#{args[:timeseries_frequency]};").get + Model.add_output_variable(model, key_value: cooled_zone.upcase, variable_name: 'Zone Thermostat Cooling Setpoint Temperature', reporting_frequency: args[:timeseries_frequency]) end end # Airflow outputs (timeseries only) if args[:include_timeseries_airflows] @airflows.each do |_airflow_type, airflow| - result << OpenStudio::IdfObject.load("EnergyManagementSystem:OutputVariable,#{airflow.ems_variable}_timeseries_outvar,#{airflow.ems_variable},Averaged,ZoneTimestep,#{total_airflows_program.name},m^3/s;").get - result << OpenStudio::IdfObject.load("Output:Variable,*,#{airflow.ems_variable}_timeseries_outvar,#{args[:timeseries_frequency]};").get + ems_ov = Model.add_ems_output_variable(model, name: "#{airflow.ems_variable}_timeseries_outvar", ems_variable_name: airflow.ems_variable, type_of_data: 'Averaged', update_frequency: 'ZoneTimestep', ems_program_or_subroutine: total_airflows_program, units: 'm^3/s') + Model.add_output_variable(model, key_value: '*', variable_name: ems_ov.name, reporting_frequency: args[:timeseries_frequency]) end end # Weather outputs (timeseries only) if args[:include_timeseries_weather] @weather.values.each do |weather_data| - result << OpenStudio::IdfObject.load("Output:Variable,*,#{weather_data.variable},#{args[:timeseries_frequency]};").get + Model.add_output_variable(model, key_value: '*', variable_name: weather_data.variable, reporting_frequency: args[:timeseries_frequency]) end end # Output variables (timeseries only) @output_variables_requests.each do |output_variable_name| - result << OpenStudio::IdfObject.load("Output:Variable,*,#{output_variable_name},#{args[:timeseries_frequency]};").get + Model.add_output_variable(model, key_value: '*', variable_name: output_variable_name, reporting_frequency: args[:timeseries_frequency]) end # Output meters (timeseries only) @output_meters_requests.each do |output_meter_name| - result << OpenStudio::IdfObject.load("Output:Meter,#{output_meter_name},#{args[:timeseries_frequency]};").get + Model.add_output_meter(model, meter_name: output_meter_name, reporting_frequency: args[:timeseries_frequency]) end - return result.uniq + return true end # Define what happens when the measure is run. From 52f666926980ffbfeb6e0780dee53c824f888473 Mon Sep 17 00:00:00 2001 From: Scott Horowitz Date: Tue, 18 Mar 2025 09:54:46 -0600 Subject: [PATCH 2/9] Update ReportUtilityBills and meta_measure.rb [ci skip] --- HPXMLtoOpenStudio/measure.xml | 8 ++-- HPXMLtoOpenStudio/resources/meta_measure.rb | 45 +++++++++++++++---- HPXMLtoOpenStudio/resources/model.rb | 8 ++-- ReportSimulationOutput/measure.rb | 14 +++--- ReportSimulationOutput/measure.xml | 6 +-- ReportUtilityBills/measure.rb | 25 ++++------- ReportUtilityBills/measure.xml | 6 +-- ...nd-run-hpxml-with-stochastic-occupancy.osw | 3 +- 8 files changed, 68 insertions(+), 47 deletions(-) diff --git a/HPXMLtoOpenStudio/measure.xml b/HPXMLtoOpenStudio/measure.xml index c380479e6d..c22c9caf14 100644 --- a/HPXMLtoOpenStudio/measure.xml +++ b/HPXMLtoOpenStudio/measure.xml @@ -3,8 +3,8 @@ 3.1 hpxm_lto_openstudio b1543b30-9465-45ff-ba04-1d1f85e763bc - 65084806-b07b-480c-8ce1-ecb9116a3a0e - 2025-02-27T19:05:48Z + 862de871-8ef6-4312-9609-30d88c91a380 + 2025-03-18T15:42:49Z D8922A73 HPXMLtoOpenStudio HPXML to OpenStudio Translator @@ -429,7 +429,7 @@ meta_measure.rb rb resource - D11644B9 + 6C6C4E1D minitest_helper.rb @@ -447,7 +447,7 @@ model.rb rb resource - F0F4648E + 1B70D959 output.rb diff --git a/HPXMLtoOpenStudio/resources/meta_measure.rb b/HPXMLtoOpenStudio/resources/meta_measure.rb index 5fe2402ef0..1ad08e14a1 100644 --- a/HPXMLtoOpenStudio/resources/meta_measure.rb +++ b/HPXMLtoOpenStudio/resources/meta_measure.rb @@ -51,6 +51,15 @@ def run_hpxml_workflow(rundir, measures, measures_dir, debug: false, run_measure return { success: success, runner: runner } end + # Apply reporting measure output requests + apply_model_output_requests(measures_dir, measures, runner, model) + + # Translate model to workspace + forward_translator = OpenStudio::EnergyPlus::ForwardTranslator.new + forward_translator.setExcludeLCCObjects(true) + workspace = forward_translator.translateModel(model) + success = report_ft_errors_warnings(forward_translator, rundir) + # Remove unused objects automatically added by OpenStudio? remove_objects = [] if model.alwaysOnContinuousSchedule.directUseCount == 0 @@ -67,14 +76,6 @@ def run_hpxml_workflow(rundir, measures, measures_dir, debug: false, run_measure remove_objects << ['Schedule:Constant', sch.name.to_s] end - - # Translate model to workspace - forward_translator = OpenStudio::EnergyPlus::ForwardTranslator.new - forward_translator.setExcludeLCCObjects(true) - workspace = forward_translator.translateModel(model) - success = report_ft_errors_warnings(forward_translator, rundir) - - # Remove objects remove_objects.uniq.each do |remove_object| workspace.getObjectByTypeAndName(remove_object[0].to_IddObjectType, remove_object[1]).get.remove end @@ -245,7 +246,33 @@ def get_full_measure_path(measures_dir, measure_name, runner) register_error("Cannot find measure #{measure_name} in any of the measures_dirs: #{measures_dirs.join(', ')}.", runner) end -# Apply OpenStudio measures and arguments (i.e., "energyPlusOutputRequests" method) corresponding to a provided Hash. +# Apply reporting measure output requests (i.e., "modelOutputRequests" method). +# +# @param measures_dir [String or Array] Parent directory path(s) of all OpenStudio-HPXML measures +# @param measures [Hash] Map of OpenStudio-HPXML measure directory name => List of measure argument hashes +# @param runner [OpenStudio::Measure::OSRunner] Object typically used to display warnings +# @param model [OpenStudio::Model::Model] OpenStudio Model object +# @return [Boolean] True if EnergyPlus output requests have been applied successfully +def apply_model_output_requests(measures_dir, measures, runner, model) + # Call each measure in the specified order + measures.keys.each do |measure_subdir| + # Gather measure arguments and call measure + full_measure_path = File.join(measures_dir, measure_subdir, 'measure.rb') + check_file_exists(full_measure_path, runner) + measure = get_measure_instance(full_measure_path) + measures[measure_subdir].each do |args| + next unless measure.class.superclass.name.to_s == 'OpenStudio::Measure::ReportingMeasure' + + argument_map = get_argument_map(model, measure, args, measure_subdir, runner) + runner.setLastOpenStudioModel(model) + measure.modelOutputRequests(model, runner, argument_map) + end + end + + return true +end + +# Apply reporting measure output requests (i.e., "energyPlusOutputRequests" method). # # @param measures_dir [String or Array] Parent directory path(s) of all OpenStudio-HPXML measures # @param measures [Hash] Map of OpenStudio-HPXML measure directory name => List of measure argument hashes diff --git a/HPXMLtoOpenStudio/resources/model.rb b/HPXMLtoOpenStudio/resources/model.rb index 8348141381..258705929e 100644 --- a/HPXMLtoOpenStudio/resources/model.rb +++ b/HPXMLtoOpenStudio/resources/model.rb @@ -850,7 +850,7 @@ def self.add_ems_program_calling_manager(model, name:, calling_point:, ems_progr end return pcm end - + # Adds an EnergyManagementSystemOutputVariable object to the OpenStudio model. # # The EnergyManagementSystemOutputVariable object allows generating output for @@ -873,7 +873,7 @@ def self.add_ems_output_variable(model, name:, ems_variable_name:, type_of_data: ov.setUnits(units) return ov end - + # Adds an OutputVariable object to the OpenStudio model. # # @param model [OpenStudio::Model::Model] OpenStudio Model object @@ -882,7 +882,7 @@ def self.add_ems_output_variable(model, name:, ems_variable_name:, type_of_data: # @param reporting_frequency [String] Output reporting frequency ('detailed', 'timestep', 'hourly', 'daily', 'monthly', 'runperiod', or 'annual') # @return [OpenStudio::Model::OutputVariable] The model object def self.add_output_variable(model, key_value:, variable_name:, reporting_frequency:) - ov = OpenStudio::Model::OutputVariable.new(variable_name, model) + ov = OpenStudio::Model::OutputVariable.new(variable_name.to_s, model) ov.setKeyValue(key_value) ov.setReportingFrequency(reporting_frequency) return ov @@ -900,7 +900,7 @@ def self.add_output_meter(model, meter_name:, reporting_frequency:) om.setReportingFrequency(reporting_frequency) return om end - + # Converts existing string to EMS friendly string. # # Source: openstudio-standards diff --git a/ReportSimulationOutput/measure.rb b/ReportSimulationOutput/measure.rb index 3d77d2102a..62a0d60e48 100644 --- a/ReportSimulationOutput/measure.rb +++ b/ReportSimulationOutput/measure.rb @@ -342,9 +342,9 @@ def get_arguments(runner, arguments, user_arguments) # @return [Boolean] Success def modelOutputRequests(model, runner, user_arguments) return false if runner.halted - + @model = model - + # use the built-in error checking if !runner.validateUserArguments(arguments(model), user_arguments) return result @@ -443,14 +443,14 @@ def modelOutputRequests(model, runner, user_arguments) # Peak Fuel outputs (annual only) @peak_fuels.values.each do |peak_fuel| # FIXME: Output:Table:Monthly not wrapped by OpenStudio - result << OpenStudio::IdfObject.load("Output:Table:Monthly,#{peak_fuel.report},2,#{peak_fuel.meter},Maximum;").get + # result << OpenStudio::IdfObject.load("Output:Table:Monthly,#{peak_fuel.report},2,#{peak_fuel.meter},Maximum;").get end # Peak Load outputs (annual only) @peak_loads.values.each do |peak_load| - ems_ov = Model.add_ems_output_variable(model, name: "#{peak_load.ems_variable}_peakload_outvar", ems_variable_name: peak_load.ems_variable, type_of_data: 'Summed', update_frequency: 'ZoneTimestep', ems_program_or_subroutine: total_loads_program, units: 'J') # FIXME: Output:Table:Monthly not wrapped by OpenStudio - result << OpenStudio::IdfObject.load("Output:Table:Monthly,#{peak_load.report},2,#{ems_ov.name},Maximum;").get + # ems_ov = Model.add_ems_output_variable(model, name: "#{peak_load.ems_variable}_peakload_outvar", ems_variable_name: peak_load.ems_variable, type_of_data: 'Summed', update_frequency: 'ZoneTimestep', ems_program_or_subroutine: total_loads_program, units: 'J') + # result << OpenStudio::IdfObject.load("Output:Table:Monthly,#{peak_load.report},2,#{ems_ov.name},Maximum;").get end # Unmet Hours (annual only) @@ -500,7 +500,7 @@ def modelOutputRequests(model, runner, user_arguments) # Temperature outputs (timeseries only) if args[:include_timeseries_zone_temperatures] Model.add_output_variable(model, key_value: '*', variable_name: 'Zone Mean Air Temperature', reporting_frequency: args[:timeseries_frequency]) - + # For reporting temperature-scheduled spaces timeseries temperatures. keys = [HPXML::LocationOtherHeatedSpace, HPXML::LocationOtherMultifamilyBufferSpace, @@ -514,7 +514,7 @@ def modelOutputRequests(model, runner, user_arguments) Model.add_output_variable(model, key_value: schedules[0].name.to_s.upcase, variable_name: 'Schedule Value', reporting_frequency: args[:timeseries_frequency]) end - + # Also report thermostat setpoints heated_zones.each do |heated_zone| Model.add_output_variable(model, key_value: heated_zone.upcase, variable_name: 'Zone Thermostat Heating Setpoint Temperature', reporting_frequency: args[:timeseries_frequency]) diff --git a/ReportSimulationOutput/measure.xml b/ReportSimulationOutput/measure.xml index dc1ec494c0..df73be1932 100644 --- a/ReportSimulationOutput/measure.xml +++ b/ReportSimulationOutput/measure.xml @@ -3,8 +3,8 @@ 3.1 report_simulation_output df9d170c-c21a-4130-866d-0d46b06073fd - 8b5a1812-c7f9-4efd-b090-2fe0d74f0bcb - 2025-02-18T17:50:35Z + 0deffc68-7dc1-4590-9408-bf5216be9c80 + 2025-03-18T15:43:17Z 9BF1E6AC ReportSimulationOutput HPXML Simulation Output Report @@ -1972,7 +1972,7 @@ measure.rb rb script - 8BAA16E7 + 16045222 test_report_sim_output.rb diff --git a/ReportUtilityBills/measure.rb b/ReportUtilityBills/measure.rb index 611dd364f8..17e27e2c35 100644 --- a/ReportUtilityBills/measure.rb +++ b/ReportUtilityBills/measure.rb @@ -149,23 +149,16 @@ def check_for_next_type_warnings(utility_bill_scenario) return warnings.uniq end - # Return a vector of IdfObject's to request EnergyPlus objects needed by the run method. + # Adds OpenStudio model objects to requests desired outputs. # + # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param runner [OpenStudio::Measure::OSRunner] Object typically used to display warnings # @param user_arguments [OpenStudio::Measure::OSArgumentMap] OpenStudio measure arguments - # @return [Array] array of OpenStudio IdfObject objects - def energyPlusOutputRequests(runner, user_arguments) - super(runner, user_arguments) - - result = OpenStudio::IdfObjectVector.new - return result if runner.halted - - model = runner.lastOpenStudioModel - if model.empty? - return result - end + # @return [Boolean] Success + def modelOutputRequests(model, runner, user_arguments) + return false if runner.halted - @model = model.get + @model = model # use the built-in error checking if !runner.validateUserArguments(arguments(model), user_arguments) @@ -209,13 +202,13 @@ def energyPlusOutputRequests(runner, user_arguments) next unless has_fuel[hpxml_fuel_map[fuel_type]] next if is_production && !has_pv # we don't need to request this meter if there isn't pv - result << OpenStudio::IdfObject.load("Output:Meter,#{fuel.meter},monthly;").get + Model.add_output_meter(model, meter_name: fuel.meter, reporting_frequency: 'monthly') if fuel_type == FT::Elec && @hpxml_header.utility_bill_scenarios.has_detailed_electric_rates - result << OpenStudio::IdfObject.load("Output:Meter,#{fuel.meter},hourly;").get + Model.add_output_meter(model, meter_name: fuel.meter, reporting_frequency: 'hourly') end end - return result.uniq + return true end # Register to the runner each warning. diff --git a/ReportUtilityBills/measure.xml b/ReportUtilityBills/measure.xml index b4a1eaa94f..4dc4057700 100644 --- a/ReportUtilityBills/measure.xml +++ b/ReportUtilityBills/measure.xml @@ -3,8 +3,8 @@ 3.1 report_utility_bills ca88a425-e59a-4bc4-af51-c7e7d1e960fe - 4c6ffc6f-5918-4eaa-a027-01b24f95f1a1 - 2025-02-17T23:51:16Z + d854de31-5491-4968-b149-954210969b8b + 2025-03-18T15:53:43Z 15BF4E57 ReportUtilityBills Utility Bills Report @@ -180,7 +180,7 @@ measure.rb rb script - BBA464A6 + 5BB25DE1 detailed_rates/README.md diff --git a/workflow/template-build-and-run-hpxml-with-stochastic-occupancy.osw b/workflow/template-build-and-run-hpxml-with-stochastic-occupancy.osw index ef48e709fb..7537d10684 100644 --- a/workflow/template-build-and-run-hpxml-with-stochastic-occupancy.osw +++ b/workflow/template-build-and-run-hpxml-with-stochastic-occupancy.osw @@ -76,7 +76,8 @@ "window_left_wwr": 0, "window_right_wwr": 0, "window_shgc": 0.45, - "window_ufactor": 0.33 + "window_ufactor": 0.33, + "utility_bill_scenario_names": "Bills", }, "measure_dir_name": "BuildResidentialHPXML" }, From d856eed9c99adb1041c360c08f9ec14750cb6829 Mon Sep 17 00:00:00 2001 From: Scott Horowitz Date: Wed, 19 Mar 2025 10:48:59 -0600 Subject: [PATCH 3/9] Handle OutputTableMonthly objects, cleanup. [ci skip] --- HPXMLtoOpenStudio/measure.xml | 6 ++-- HPXMLtoOpenStudio/resources/model.rb | 49 ++++++++++++++++++++++++++-- ReportSimulationOutput/measure.rb | 22 ++++++------- ReportSimulationOutput/measure.xml | 6 ++-- 4 files changed, 62 insertions(+), 21 deletions(-) diff --git a/HPXMLtoOpenStudio/measure.xml b/HPXMLtoOpenStudio/measure.xml index c22c9caf14..53d6eadf3d 100644 --- a/HPXMLtoOpenStudio/measure.xml +++ b/HPXMLtoOpenStudio/measure.xml @@ -3,8 +3,8 @@ 3.1 hpxm_lto_openstudio b1543b30-9465-45ff-ba04-1d1f85e763bc - 862de871-8ef6-4312-9609-30d88c91a380 - 2025-03-18T15:42:49Z + bd329daf-9917-478f-bd8b-a39d329cd2a3 + 2025-03-19T16:48:30Z D8922A73 HPXMLtoOpenStudio HPXML to OpenStudio Translator @@ -447,7 +447,7 @@ model.rb rb resource - 1B70D959 + EDFB877F output.rb diff --git a/HPXMLtoOpenStudio/resources/model.rb b/HPXMLtoOpenStudio/resources/model.rb index 258705929e..c34af40c2b 100644 --- a/HPXMLtoOpenStudio/resources/model.rb +++ b/HPXMLtoOpenStudio/resources/model.rb @@ -874,7 +874,8 @@ def self.add_ems_output_variable(model, name:, ems_variable_name:, type_of_data: return ov end - # Adds an OutputVariable object to the OpenStudio model. + # Adds an OutputVariable object to the OpenStudio model. If there is already an + # identical existing object on the model, returns that instead. # # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param key_value [String] The specific object reference for reporting @@ -882,25 +883,67 @@ def self.add_ems_output_variable(model, name:, ems_variable_name:, type_of_data: # @param reporting_frequency [String] Output reporting frequency ('detailed', 'timestep', 'hourly', 'daily', 'monthly', 'runperiod', or 'annual') # @return [OpenStudio::Model::OutputVariable] The model object def self.add_output_variable(model, key_value:, variable_name:, reporting_frequency:) - ov = OpenStudio::Model::OutputVariable.new(variable_name.to_s, model) + model.getOutputVariables.each do |ov| + next unless ov.variableName == variable_name + next unless ov.keyValue == key_value + next unless ov.reportingFrequency == reporting_frequency + + return ov # Duplicate of existing object + end + ov = OpenStudio::Model::OutputVariable.new(variable_name, model) ov.setKeyValue(key_value) ov.setReportingFrequency(reporting_frequency) return ov end - # Adds an OutputMeter object to the OpenStudio model. + # Adds an OutputMeter object to the OpenStudio model. If there is already an + # identical existing object on the model, returns that instead. # # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param meter_name [String] The meter name as shown in the eplusout.mdd file # @param reporting_frequency [String] Output reporting frequency ('detailed', 'timestep', 'hourly', 'daily', 'monthly', 'runperiod', or 'annual') # @return [OpenStudio::Model::OutputMeter] The model object def self.add_output_meter(model, meter_name:, reporting_frequency:) + model.getOutputMeters.each do |om| + next unless om.name == meter_name + next unless om.reportingFrequency == reporting_frequency + + return om # Duplicate of existing object + end om = OpenStudio::Model::OutputMeter.new(model) om.setName(meter_name) om.setReportingFrequency(reporting_frequency) return om end + # Adds an OutputTableMonthly to the OpenStudio model. If there is already an + # identical existing object on the model, returns that instead. + # + # @param model [OpenStudio::Model::Model] OpenStudio Model object + # @param name [String] Name for the output table + # @param digits_after_decimal [Integer] Number of digits after the decimal point + # @param output_var_or_meter_name [String] EnergyPlus Output:Variable or Output:Meter name + # @param aggregation_type [String] Aggregation type (SumOrAverage, Maximum, Minimum, etc.) + # @return [OpenStudio::Model::OutputTableMonthly] The model object + def self.add_output_table_monthly(model, name:, digits_after_decimal: 2, output_var_or_meter_name:, aggregation_type:) + model.getOutputTableMonthlys.each do |otm| + next unless otm.name.to_s == name + next unless otm.digitsAfterDecimal == digits_after_decimal + next unless otm.numberofMonthlyVariableGroups == 1 + + first_group = otm.getMonthlyVariableGroup(0).get + next unless first_group.variableOrMeterName == output_var_or_meter_name + next unless first_group.aggregationType == aggregation_type + + return otm # Duplicate of existing object + end + otm = OpenStudio::Model::OutputTableMonthly.new(model) + otm.setName(name) + otm.setDigitsAfterDecimal(digits_after_decimal) + otm.addMonthlyVariableGroup(output_var_or_meter_name, aggregation_type) + return otm + end + # Converts existing string to EMS friendly string. # # Source: openstudio-standards diff --git a/ReportSimulationOutput/measure.rb b/ReportSimulationOutput/measure.rb index 62a0d60e48..178174b9a5 100644 --- a/ReportSimulationOutput/measure.rb +++ b/ReportSimulationOutput/measure.rb @@ -442,15 +442,13 @@ def modelOutputRequests(model, runner, user_arguments) # Peak Fuel outputs (annual only) @peak_fuels.values.each do |peak_fuel| - # FIXME: Output:Table:Monthly not wrapped by OpenStudio - # result << OpenStudio::IdfObject.load("Output:Table:Monthly,#{peak_fuel.report},2,#{peak_fuel.meter},Maximum;").get + Model.add_output_table_monthly(model, name: peak_fuel.report, output_var_or_meter_name: peak_fuel.meter, aggregation_type: 'Maximum') end # Peak Load outputs (annual only) @peak_loads.values.each do |peak_load| - # FIXME: Output:Table:Monthly not wrapped by OpenStudio - # ems_ov = Model.add_ems_output_variable(model, name: "#{peak_load.ems_variable}_peakload_outvar", ems_variable_name: peak_load.ems_variable, type_of_data: 'Summed', update_frequency: 'ZoneTimestep', ems_program_or_subroutine: total_loads_program, units: 'J') - # result << OpenStudio::IdfObject.load("Output:Table:Monthly,#{peak_load.report},2,#{ems_ov.name},Maximum;").get + ems_ov = Model.add_ems_output_variable(model, name: "#{peak_load.ems_variable}_peakload_outvar", ems_variable_name: peak_load.ems_variable, type_of_data: 'Summed', update_frequency: 'ZoneTimestep', ems_program_or_subroutine: total_loads_program, units: 'J') + Model.add_output_table_monthly(model, name: peak_load.report, output_var_or_meter_name: ems_ov.name.to_s, aggregation_type: 'Maximum') end # Unmet Hours (annual only) @@ -460,10 +458,10 @@ def modelOutputRequests(model, runner, user_arguments) ems_program = key == UHT::Driving ? unmet_driving_hrs_program : unmet_hours_program ems_ov = Model.add_ems_output_variable(model, name: "#{unmet_hour.ems_variable}_annual_outvar", ems_variable_name: unmet_hour.ems_variable, type_of_data: 'Summed', update_frequency: 'ZoneTimestep', ems_program_or_subroutine: ems_program, units: 'hr') - Model.add_output_variable(model, key_value: '*', variable_name: ems_ov.name, reporting_frequency: 'runperiod') + Model.add_output_variable(model, key_value: '*', variable_name: ems_ov.name.to_s, reporting_frequency: 'runperiod') if args[:include_timeseries_unmet_hours] ems_ov = Model.add_ems_output_variable(model, name: "#{unmet_hour.ems_variable}_timeseries_outvar", ems_variable_name: unmet_hour.ems_variable, type_of_data: 'Summed', update_frequency: 'ZoneTimestep', ems_program_or_subroutine: ems_program, units: 'hr') - Model.add_output_variable(model, key_value: '*', variable_name: ems_ov.name, reporting_frequency: args[:timeseries_frequency]) + Model.add_output_variable(model, key_value: '*', variable_name: ems_ov.name.to_s, reporting_frequency: args[:timeseries_frequency]) end end @@ -472,10 +470,10 @@ def modelOutputRequests(model, runner, user_arguments) next if comp_loads_program.nil? ems_ov = Model.add_ems_output_variable(model, name: "#{comp_load.ems_variable}_annual_outvar", ems_variable_name: comp_load.ems_variable, type_of_data: 'Summed', update_frequency: 'ZoneTimestep', ems_program_or_subroutine: comp_loads_program, units: 'J') - Model.add_output_variable(model, key_value: '*', variable_name: ems_ov.name, reporting_frequency: 'runperiod') + Model.add_output_variable(model, key_value: '*', variable_name: ems_ov.name.to_s, reporting_frequency: 'runperiod') if args[:include_timeseries_component_loads] ems_ov = Model.add_ems_output_variable(model, name: "#{comp_load.ems_variable}_timeseries_outvar", ems_variable_name: comp_load.ems_variable, type_of_data: 'Summed', update_frequency: 'ZoneTimestep', ems_program_or_subroutine: comp_loads_program, units: 'J') - Model.add_output_variable(model, key_value: '*', variable_name: ems_ov.name, reporting_frequency: args[:timeseries_frequency]) + Model.add_output_variable(model, key_value: '*', variable_name: ems_ov.name.to_s, reporting_frequency: args[:timeseries_frequency]) end end @@ -483,10 +481,10 @@ def modelOutputRequests(model, runner, user_arguments) @loads.values.each do |load| if not load.ems_variable.nil? ems_ov = Model.add_ems_output_variable(model, name: "#{load.ems_variable}_annual_outvar", ems_variable_name: load.ems_variable, type_of_data: 'Summed', update_frequency: 'ZoneTimestep', ems_program_or_subroutine: total_loads_program, units: 'J') - Model.add_output_variable(model, key_value: '*', variable_name: ems_ov.name, reporting_frequency: 'runperiod') + Model.add_output_variable(model, key_value: '*', variable_name: ems_ov.name.to_s, reporting_frequency: 'runperiod') if args[:include_timeseries_total_loads] ems_ov = Model.add_ems_output_variable(model, name: "#{load.ems_variable}_timeseries_outvar", ems_variable_name: load.ems_variable, type_of_data: 'Summed', update_frequency: 'ZoneTimestep', ems_program_or_subroutine: total_loads_program, units: 'J') - Model.add_output_variable(model, key_value: '*', variable_name: ems_ov.name, reporting_frequency: args[:timeseries_frequency]) + Model.add_output_variable(model, key_value: '*', variable_name: ems_ov.name.to_s, reporting_frequency: args[:timeseries_frequency]) end end load.variables.each do |_sys_id, varkey, var| @@ -528,7 +526,7 @@ def modelOutputRequests(model, runner, user_arguments) if args[:include_timeseries_airflows] @airflows.each do |_airflow_type, airflow| ems_ov = Model.add_ems_output_variable(model, name: "#{airflow.ems_variable}_timeseries_outvar", ems_variable_name: airflow.ems_variable, type_of_data: 'Averaged', update_frequency: 'ZoneTimestep', ems_program_or_subroutine: total_airflows_program, units: 'm^3/s') - Model.add_output_variable(model, key_value: '*', variable_name: ems_ov.name, reporting_frequency: args[:timeseries_frequency]) + Model.add_output_variable(model, key_value: '*', variable_name: ems_ov.name.to_s, reporting_frequency: args[:timeseries_frequency]) end end diff --git a/ReportSimulationOutput/measure.xml b/ReportSimulationOutput/measure.xml index df73be1932..33fb0af4fc 100644 --- a/ReportSimulationOutput/measure.xml +++ b/ReportSimulationOutput/measure.xml @@ -3,8 +3,8 @@ 3.1 report_simulation_output df9d170c-c21a-4130-866d-0d46b06073fd - 0deffc68-7dc1-4590-9408-bf5216be9c80 - 2025-03-18T15:43:17Z + 69f2cd6c-5561-4ae5-aeaf-215cd91fb805 + 2025-03-19T16:34:06Z 9BF1E6AC ReportSimulationOutput HPXML Simulation Output Report @@ -1972,7 +1972,7 @@ measure.rb rb script - 16045222 + 570F2ADD test_report_sim_output.rb From 79413b9e2b1e1fd85fdae14ea666ae0ac99e32f9 Mon Sep 17 00:00:00 2001 From: Scott Horowitz Date: Sat, 3 May 2025 09:55:27 -0600 Subject: [PATCH 4/9] Update new output variables --- ReportSimulationOutput/measure.rb | 10 +++++----- ReportSimulationOutput/measure.xml | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ReportSimulationOutput/measure.rb b/ReportSimulationOutput/measure.rb index 0f644a0c79..b0cee23698 100644 --- a/ReportSimulationOutput/measure.rb +++ b/ReportSimulationOutput/measure.rb @@ -532,11 +532,11 @@ def modelOutputRequests(model, runner, user_arguments) # Detailed air condition outputs (timeseries only) if args[:include_timeseries_zone_conditions] - result << OpenStudio::IdfObject.load("Output:Variable,*,Zone Air Humidity Ratio,#{args[:timeseries_frequency]};").get - result << OpenStudio::IdfObject.load("Output:Variable,*,Zone Air Relative Humidity,#{args[:timeseries_frequency]};").get - result << OpenStudio::IdfObject.load("Output:Variable,*,Zone Mean Air Dewpoint Temperature,#{args[:timeseries_frequency]};").get - result << OpenStudio::IdfObject.load("Output:Variable,*,Zone Mean Radiant Temperature,#{args[:timeseries_frequency]};").get - result << OpenStudio::IdfObject.load("Output:Variable,*,Zone Operative Temperature,#{args[:timeseries_frequency]};").get + Model.add_output_variable(model, key_value: '*', variable_name: 'Zone Air Humidity Ratio', reporting_frequency: args[:timeseries_frequency]) + Model.add_output_variable(model, key_value: '*', variable_name: 'Zone Air Relative Humidity', reporting_frequency: args[:timeseries_frequency]) + Model.add_output_variable(model, key_value: '*', variable_name: 'Zone Mean Air Dewpoint Temperature', reporting_frequency: args[:timeseries_frequency]) + Model.add_output_variable(model, key_value: '*', variable_name: 'Zone Mean Radiant Temperature', reporting_frequency: args[:timeseries_frequency]) + Model.add_output_variable(model, key_value: '*', variable_name: 'Zone Operative Temperature', reporting_frequency: args[:timeseries_frequency]) end # Airflow outputs (timeseries only) diff --git a/ReportSimulationOutput/measure.xml b/ReportSimulationOutput/measure.xml index 3a9cfe5b10..51cfa3748e 100644 --- a/ReportSimulationOutput/measure.xml +++ b/ReportSimulationOutput/measure.xml @@ -3,8 +3,8 @@ 3.1 report_simulation_output df9d170c-c21a-4130-866d-0d46b06073fd - 53176c1c-3733-41b4-b8ec-0f25193e992f - 2025-05-03T15:48:48Z + d2dd5c12-5bcf-4e91-a111-4b54eb50b3d8 + 2025-05-03T15:55:11Z 9BF1E6AC ReportSimulationOutput HPXML Simulation Output Report @@ -1991,7 +1991,7 @@ measure.rb rb script - 9D6CABF7 + 7F8DCB3F test_report_sim_output.rb From 1b4270dafd2904e11b260d19be54a5d5a1a8d3ed Mon Sep 17 00:00:00 2001 From: Scott Horowitz Date: Sat, 3 May 2025 10:17:36 -0600 Subject: [PATCH 5/9] OSW bugfix --- .../template-build-and-run-hpxml-with-stochastic-occupancy.osw | 2 +- workflow/template-build-hpxml.osw | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/workflow/template-build-and-run-hpxml-with-stochastic-occupancy.osw b/workflow/template-build-and-run-hpxml-with-stochastic-occupancy.osw index 8857ffc65d..e312a799ad 100644 --- a/workflow/template-build-and-run-hpxml-with-stochastic-occupancy.osw +++ b/workflow/template-build-and-run-hpxml-with-stochastic-occupancy.osw @@ -77,7 +77,7 @@ "window_right_wwr": 0, "window_shgc": 0.45, "window_ufactor": 0.33, - "utility_bill_scenario_names": "Bills", + "utility_bill_scenario_names": "Bills" }, "measure_dir_name": "BuildResidentialHPXML" }, diff --git a/workflow/template-build-hpxml.osw b/workflow/template-build-hpxml.osw index e88b41bb62..d98ac5781b 100644 --- a/workflow/template-build-hpxml.osw +++ b/workflow/template-build-hpxml.osw @@ -76,7 +76,8 @@ "window_left_wwr": 0, "window_right_wwr": 0, "window_shgc": 0.45, - "window_ufactor": 0.33 + "window_ufactor": 0.33, + "utility_bill_scenario_names": "Bills" }, "measure_dir_name": "BuildResidentialHPXML" } From 012b2af8d20a66a31752a533bce6933711e23aa5 Mon Sep 17 00:00:00 2001 From: Scott Horowitz Date: Sat, 3 May 2025 10:20:55 -0600 Subject: [PATCH 6/9] Disable test --- workflow/tests/util.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workflow/tests/util.rb b/workflow/tests/util.rb index 4c0ad0eb0f..934c84aa84 100644 --- a/workflow/tests/util.rb +++ b/workflow/tests/util.rb @@ -430,7 +430,7 @@ def _verify_outputs(rundir, hpxml_path, results, hpxml, unit_multiplier) end end assert_equal(0, num_unused_objects) - assert_equal(0, num_unused_schedules) + #assert_equal(0, num_unused_schedules) FIXME: Temporarily disabled assert_equal(0, num_unused_constructions) # Check for Output:Meter and Output:Variable warnings From 0e4b05eaaa0fc4b52cfe4752f7db2fcfdcf1757f Mon Sep 17 00:00:00 2001 From: Scott Horowitz Date: Sat, 3 May 2025 10:54:36 -0600 Subject: [PATCH 7/9] Bugfixes. --- ReportSimulationOutput/measure.rb | 2 +- ReportSimulationOutput/measure.xml | 6 +++--- ReportUtilityBills/measure.rb | 6 +++--- ReportUtilityBills/measure.xml | 6 +++--- workflow/tests/util.rb | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/ReportSimulationOutput/measure.rb b/ReportSimulationOutput/measure.rb index b0cee23698..398a14351a 100644 --- a/ReportSimulationOutput/measure.rb +++ b/ReportSimulationOutput/measure.rb @@ -355,7 +355,7 @@ def modelOutputRequests(model, runner, user_arguments) # use the built-in error checking if !runner.validateUserArguments(arguments(model), user_arguments) - return result + return false end unmet_hours_program = model.getEnergyManagementSystemPrograms.find { |p| p.additionalProperties.getFeatureAsString('ObjectType').to_s == Constants::ObjectTypeUnmetHoursProgram } diff --git a/ReportSimulationOutput/measure.xml b/ReportSimulationOutput/measure.xml index 51cfa3748e..c9af603e5d 100644 --- a/ReportSimulationOutput/measure.xml +++ b/ReportSimulationOutput/measure.xml @@ -3,8 +3,8 @@ 3.1 report_simulation_output df9d170c-c21a-4130-866d-0d46b06073fd - d2dd5c12-5bcf-4e91-a111-4b54eb50b3d8 - 2025-05-03T15:55:11Z + 1a03c52f-33c2-4d29-b571-5a25b9c7c755 + 2025-05-03T16:54:19Z 9BF1E6AC ReportSimulationOutput HPXML Simulation Output Report @@ -1991,7 +1991,7 @@ measure.rb rb script - 7F8DCB3F + 645A80FD test_report_sim_output.rb diff --git a/ReportUtilityBills/measure.rb b/ReportUtilityBills/measure.rb index 5c9f6faf92..c44028c5ae 100644 --- a/ReportUtilityBills/measure.rb +++ b/ReportUtilityBills/measure.rb @@ -162,7 +162,7 @@ def modelOutputRequests(model, runner, user_arguments) # use the built-in error checking if !runner.validateUserArguments(arguments(model), user_arguments) - return result + return false end hpxml_defaults_path = @model.getBuilding.additionalProperties.getFeatureAsString('hpxml_defaults_path').get @@ -174,12 +174,12 @@ def modelOutputRequests(model, runner, user_arguments) if @hpxml_header.utility_bill_scenarios.has_detailed_electric_rates uses_unit_multipliers = @hpxml_buildings.count { |hpxml_bldg| hpxml_bldg.building_construction.number_of_units > 1 } > 0 if uses_unit_multipliers || (@hpxml_buildings.size > 1 && hpxml.header.whole_sfa_or_mf_building_sim) - return result + return false end end warnings = check_for_return_type_warnings() - return result if !warnings.empty? + return false if !warnings.empty? fuels = setup_fuel_outputs() diff --git a/ReportUtilityBills/measure.xml b/ReportUtilityBills/measure.xml index 5f0003a7d8..1424f28a7c 100644 --- a/ReportUtilityBills/measure.xml +++ b/ReportUtilityBills/measure.xml @@ -3,8 +3,8 @@ 3.1 report_utility_bills ca88a425-e59a-4bc4-af51-c7e7d1e960fe - 40f5276a-2de3-4913-90d6-c913daeb63de - 2025-05-03T15:48:50Z + 6f3f45c7-e7cf-4732-9fcb-99af569a557c + 2025-05-03T16:54:22Z 15BF4E57 ReportUtilityBills Utility Bills Report @@ -180,7 +180,7 @@ measure.rb rb script - B5ABACA8 + E6457A7B detailed_rates/README.md diff --git a/workflow/tests/util.rb b/workflow/tests/util.rb index 934c84aa84..1d08685b59 100644 --- a/workflow/tests/util.rb +++ b/workflow/tests/util.rb @@ -430,7 +430,7 @@ def _verify_outputs(rundir, hpxml_path, results, hpxml, unit_multiplier) end end assert_equal(0, num_unused_objects) - #assert_equal(0, num_unused_schedules) FIXME: Temporarily disabled + # assert_equal(0, num_unused_schedules) FIXME: Temporarily disabled assert_equal(0, num_unused_constructions) # Check for Output:Meter and Output:Variable warnings From ce7ad0733315512ccc3a82c60dd215697ac51706 Mon Sep 17 00:00:00 2001 From: Scott Horowitz Date: Sat, 3 May 2025 12:48:56 -0600 Subject: [PATCH 8/9] Attempted fix for unused schedule --- HPXMLtoOpenStudio/measure.xml | 6 +++--- HPXMLtoOpenStudio/resources/meta_measure.rb | 22 ++++++++++++++------- workflow/tests/util.rb | 2 +- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/HPXMLtoOpenStudio/measure.xml b/HPXMLtoOpenStudio/measure.xml index 9d9308d7d5..892603bb8e 100644 --- a/HPXMLtoOpenStudio/measure.xml +++ b/HPXMLtoOpenStudio/measure.xml @@ -3,8 +3,8 @@ 3.1 hpxm_lto_openstudio b1543b30-9465-45ff-ba04-1d1f85e763bc - 8fad20d9-6d59-475c-9c4b-abeb8a0d6df9 - 2025-05-03T17:47:27Z + 01fbc97b-b9af-4b73-8bf2-779589cf7fd2 + 2025-05-03T18:48:27Z D8922A73 HPXMLtoOpenStudio HPXML to OpenStudio Translator @@ -435,7 +435,7 @@ meta_measure.rb rb resource - 77CAA760 + 38027F9E minitest_helper.rb diff --git a/HPXMLtoOpenStudio/resources/meta_measure.rb b/HPXMLtoOpenStudio/resources/meta_measure.rb index 9691c081a0..e1fe96a03a 100644 --- a/HPXMLtoOpenStudio/resources/meta_measure.rb +++ b/HPXMLtoOpenStudio/resources/meta_measure.rb @@ -61,20 +61,28 @@ def run_hpxml_workflow(rundir, measures, measures_dir, debug: false, run_measure success = report_ft_errors_warnings(forward_translator, rundir) # Remove unused objects automatically added by OpenStudio? + def is_unused_object(model_obj) + # Don't know why we need to check for AdditionalProperties too, but it works + if model_obj.sources.empty? || model_obj.sources.all? { |s| s.to_AdditionalProperties.is_initialized } + return true + end + + return false + end remove_objects = [] - if model.alwaysOnContinuousSchedule.directUseCount == 0 + if is_unused_object(model.alwaysOnContinuousSchedule) remove_objects << ['Schedule:Constant', model.alwaysOnContinuousSchedule.name.to_s] end - if model.alwaysOnDiscreteSchedule.directUseCount == 0 + if is_unused_object(model.alwaysOnDiscreteSchedule) remove_objects << ['Schedule:Constant', model.alwaysOnDiscreteSchedule.name.to_s] end - if model.alwaysOffDiscreteSchedule.directUseCount == 0 + if is_unused_object(model.alwaysOffDiscreteSchedule) remove_objects << ['Schedule:Constant', model.alwaysOffDiscreteSchedule.name.to_s] end - model.getScheduleConstants.each do |sch| - next unless sch.directUseCount == 0 - - remove_objects << ['Schedule:Constant', sch.name.to_s] + model.getSchedules.each do |sch| + if is_unused_object(sch) + remove_objects << ['Schedule:Constant', sch.name.to_s] + end end remove_objects.uniq.each do |remove_object| workspace.getObjectByTypeAndName(remove_object[0].to_IddObjectType, remove_object[1]).get.remove diff --git a/workflow/tests/util.rb b/workflow/tests/util.rb index 4f3831b9fc..04b15ae008 100644 --- a/workflow/tests/util.rb +++ b/workflow/tests/util.rb @@ -430,7 +430,7 @@ def _verify_outputs(rundir, hpxml_path, results, hpxml, unit_multiplier) end end assert_equal(0, num_unused_objects) - # assert_equal(0, num_unused_schedules) FIXME: Temporarily disabled + assert_equal(0, num_unused_schedules) assert_equal(0, num_unused_constructions) # Check for Output:Meter and Output:Variable warnings From f45c1ab8b3c8baa36ad2b3f347122756cd29e916 Mon Sep 17 00:00:00 2001 From: Scott Horowitz Date: Sat, 3 May 2025 14:02:16 -0600 Subject: [PATCH 9/9] Another try. --- HPXMLtoOpenStudio/measure.xml | 6 ++--- HPXMLtoOpenStudio/resources/meta_measure.rb | 28 ++++----------------- 2 files changed, 8 insertions(+), 26 deletions(-) diff --git a/HPXMLtoOpenStudio/measure.xml b/HPXMLtoOpenStudio/measure.xml index 892603bb8e..f787f697f8 100644 --- a/HPXMLtoOpenStudio/measure.xml +++ b/HPXMLtoOpenStudio/measure.xml @@ -3,8 +3,8 @@ 3.1 hpxm_lto_openstudio b1543b30-9465-45ff-ba04-1d1f85e763bc - 01fbc97b-b9af-4b73-8bf2-779589cf7fd2 - 2025-05-03T18:48:27Z + 7425a4d6-eed7-4b64-8ccd-671060690b7b + 2025-05-03T20:01:42Z D8922A73 HPXMLtoOpenStudio HPXML to OpenStudio Translator @@ -435,7 +435,7 @@ meta_measure.rb rb resource - 38027F9E + 0A27A2C8 minitest_helper.rb diff --git a/HPXMLtoOpenStudio/resources/meta_measure.rb b/HPXMLtoOpenStudio/resources/meta_measure.rb index e1fe96a03a..7d6de479f6 100644 --- a/HPXMLtoOpenStudio/resources/meta_measure.rb +++ b/HPXMLtoOpenStudio/resources/meta_measure.rb @@ -61,31 +61,13 @@ def run_hpxml_workflow(rundir, measures, measures_dir, debug: false, run_measure success = report_ft_errors_warnings(forward_translator, rundir) # Remove unused objects automatically added by OpenStudio? - def is_unused_object(model_obj) + [model.alwaysOnContinuousSchedule, + model.alwaysOnDiscreteSchedule, + model.alwaysOffDiscreteSchedule].each do |sch| # Don't know why we need to check for AdditionalProperties too, but it works - if model_obj.sources.empty? || model_obj.sources.all? { |s| s.to_AdditionalProperties.is_initialized } - return true + if sch.directUseCount == 0 || sch.sources.all? { |s| s.to_AdditionalProperties.is_initialized } + workspace.getObjectByTypeAndName('Schedule:Constant'.to_IddObjectType, sch.name.to_s).get.remove end - - return false - end - remove_objects = [] - if is_unused_object(model.alwaysOnContinuousSchedule) - remove_objects << ['Schedule:Constant', model.alwaysOnContinuousSchedule.name.to_s] - end - if is_unused_object(model.alwaysOnDiscreteSchedule) - remove_objects << ['Schedule:Constant', model.alwaysOnDiscreteSchedule.name.to_s] - end - if is_unused_object(model.alwaysOffDiscreteSchedule) - remove_objects << ['Schedule:Constant', model.alwaysOffDiscreteSchedule.name.to_s] - end - model.getSchedules.each do |sch| - if is_unused_object(sch) - remove_objects << ['Schedule:Constant', sch.name.to_s] - end - end - remove_objects.uniq.each do |remove_object| - workspace.getObjectByTypeAndName(remove_object[0].to_IddObjectType, remove_object[1]).get.remove end if not success