diff --git a/BuildResidentialHPXML/measure.xml b/BuildResidentialHPXML/measure.xml index 7e190bf194..cddc8b6409 100644 --- a/BuildResidentialHPXML/measure.xml +++ b/BuildResidentialHPXML/measure.xml @@ -3,8 +3,8 @@ 3.1 build_residential_hpxml a13a8983-2b01-4930-8af2-42030b6e4233 - 74f8cfa4-d51d-49d3-a974-af3388ec0aff - 2025-05-03T15:58:46Z + a35bc8f9-f4f8-4bfc-9387-701d162da262 + 2025-05-03T17:47:18Z 2C38F48B BuildResidentialHPXML HPXML Builder @@ -7713,12 +7713,6 @@ resource D8A38780 - - options/ducts.tsv - tsv - resource - 5F1FC0B0 - version.txt txt diff --git a/HPXMLtoOpenStudio/measure.xml b/HPXMLtoOpenStudio/measure.xml index 549275d66c..f787f697f8 100644 --- a/HPXMLtoOpenStudio/measure.xml +++ b/HPXMLtoOpenStudio/measure.xml @@ -3,8 +3,8 @@ 3.1 hpxm_lto_openstudio b1543b30-9465-45ff-ba04-1d1f85e763bc - 8af045cd-29e2-42a0-87ee-cd566e934120 - 2025-05-03T15:58:56Z + 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 - D7DF1BEA + 0A27A2C8 minitest_helper.rb @@ -453,7 +453,7 @@ model.rb rb resource - DA47D013 + D85755A1 output.rb diff --git a/HPXMLtoOpenStudio/resources/meta_measure.rb b/HPXMLtoOpenStudio/resources/meta_measure.rb index 644fcd9dbe..7d6de479f6 100644 --- a/HPXMLtoOpenStudio/resources/meta_measure.rb +++ b/HPXMLtoOpenStudio/resources/meta_measure.rb @@ -51,22 +51,8 @@ def run_hpxml_workflow(rundir, measures, measures_dir, debug: false, run_measure return { success: success, runner: runner } end - # Remove unused objects automatically added by OpenStudio? - remove_objects = [] - if model.alwaysOnContinuousSchedule.directUseCount == 0 - remove_objects << ['Schedule:Constant', model.alwaysOnContinuousSchedule.name.to_s] - end - if model.alwaysOnDiscreteSchedule.directUseCount == 0 - remove_objects << ['Schedule:Constant', model.alwaysOnDiscreteSchedule.name.to_s] - end - if model.alwaysOffDiscreteSchedule.directUseCount == 0 - 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] - 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 @@ -74,9 +60,14 @@ def run_hpxml_workflow(rundir, measures, measures_dir, debug: false, run_measure 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 + # Remove unused objects automatically added by OpenStudio? + [model.alwaysOnContinuousSchedule, + model.alwaysOnDiscreteSchedule, + model.alwaysOffDiscreteSchedule].each do |sch| + # Don't know why we need to check for AdditionalProperties too, but it works + 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 end if not success @@ -250,7 +241,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 a491567c96..a71ff59fe8 100644 --- a/HPXMLtoOpenStudio/resources/model.rb +++ b/HPXMLtoOpenStudio/resources/model.rb @@ -722,7 +722,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. @@ -739,7 +739,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. @@ -751,7 +751,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. @@ -767,7 +767,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. @@ -784,7 +784,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. @@ -803,7 +803,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 @@ -820,7 +820,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. @@ -836,7 +836,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. @@ -856,6 +856,99 @@ def self.add_ems_program_calling_manager(model, name:, calling_point:, ems_progr 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. 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 + # @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:) + 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. 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 6b02cecb22..398a14351a 100644 --- a/ReportSimulationOutput/measure.rb +++ b/ReportSimulationOutput/measure.rb @@ -342,36 +342,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) + # @return [Boolean] Success + def modelOutputRequests(model, runner, user_arguments) + return false if runner.halted - result = OpenStudio::IdfObjectVector.new - return result if runner.halted - - model = runner.lastOpenStudioModel - if model.empty? - return result - end - - @model = model.get + @model = model # use the built-in error checking - if !runner.validateUserArguments(arguments(@model), user_arguments) - return result + if !runner.validateUserArguments(arguments(model), user_arguments) + return false 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) @@ -392,19 +385,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 @@ -413,12 +406,12 @@ def energyPlusOutputRequests(runner, user_arguments) if args[:timeseries_frequency] != EPlus::TimeseriesFrequencyTimestep resilience_frequency = EPlus::TimeseriesFrequencyHourly 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 @@ -426,30 +419,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 @@ -457,13 +450,13 @@ def energyPlusOutputRequests(runner, user_arguments) # Peak Fuel outputs (annual only) @peak_fuels.values.each do |peak_fuel| - 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| - 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') + 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) @@ -472,11 +465,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.to_s, 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.to_s, reporting_frequency: args[:timeseries_frequency]) end end @@ -484,35 +477,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.to_s, 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.to_s, 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.to_s, 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.to_s, 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, @@ -524,52 +518,53 @@ 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 # 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) 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.to_s, 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. diff --git a/ReportSimulationOutput/measure.xml b/ReportSimulationOutput/measure.xml index 4f82961f5e..c9af603e5d 100644 --- a/ReportSimulationOutput/measure.xml +++ b/ReportSimulationOutput/measure.xml @@ -3,8 +3,8 @@ 3.1 report_simulation_output df9d170c-c21a-4130-866d-0d46b06073fd - 52b3b0d6-345f-46a2-93ab-34f9705f6907 - 2025-04-25T21:17:08Z + 1a03c52f-33c2-4d29-b571-5a25b9c7c755 + 2025-05-03T16:54:19Z 9BF1E6AC ReportSimulationOutput HPXML Simulation Output Report @@ -1991,7 +1991,7 @@ measure.rb rb script - 9ACFB98A + 645A80FD test_report_sim_output.rb diff --git a/ReportUtilityBills/measure.rb b/ReportUtilityBills/measure.rb index f6bda70cb0..c44028c5ae 100644 --- a/ReportUtilityBills/measure.rb +++ b/ReportUtilityBills/measure.rb @@ -149,27 +149,20 @@ 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 + # @return [Boolean] Success + def modelOutputRequests(model, runner, user_arguments) + return false if runner.halted - model = runner.lastOpenStudioModel - if model.empty? - return result - end - - @model = model.get + @model = model # 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 @@ -181,12 +174,12 @@ def energyPlusOutputRequests(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() @@ -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 7fd4ec4c7e..1424f28a7c 100644 --- a/ReportUtilityBills/measure.xml +++ b/ReportUtilityBills/measure.xml @@ -3,8 +3,8 @@ 3.1 report_utility_bills ca88a425-e59a-4bc4-af51-c7e7d1e960fe - 61a6d525-77d4-47a5-8a6d-683afcd13b21 - 2025-04-25T17:37:30Z + 6f3f45c7-e7cf-4732-9fcb-99af569a557c + 2025-05-03T16:54:22Z 15BF4E57 ReportUtilityBills Utility Bills Report @@ -180,7 +180,7 @@ measure.rb rb script - D50684D8 + E6457A7B 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 a66ff083cd..e312a799ad 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" }, 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" }