diff --git a/Changelog.md b/Changelog.md index ef5b09b3b2..326731da4a 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,7 @@ ## OpenStudio-HPXML v1.10.0 __New Features__ +- Allows requesting timeseries EnergyPlus output meters (e.g., `--hourly "MainsWater:Facility"`), similar to requesting EnergyPlus output variables. __Bugfixes__ - Fixes zero occupants specified for one unit in a whole MF building from being treated like zero occupants for every unit. diff --git a/ReportSimulationOutput/README.md b/ReportSimulationOutput/README.md index 594b8dbac9..c12a865e67 100644 --- a/ReportSimulationOutput/README.md +++ b/ReportSimulationOutput/README.md @@ -424,6 +424,17 @@ Optionally generates timeseries EnergyPlus output variables. If multiple output
+**Generate Timeseries Output: EnergyPlus Output Meters** + +Optionally generates timeseries EnergyPlus output meters. If multiple output meters are desired, use a comma-separated list. Example: "Electricity:Facility, HeatingCoils:EnergyTransfer" + +- **Name:** ``user_output_meters`` +- **Type:** ``String`` + +- **Required:** ``false`` + +
+ **Annual Output File Name** If not provided, defaults to 'results_annual.csv' (or 'results_annual.json' or 'results_annual.msgpack'). diff --git a/ReportSimulationOutput/measure.rb b/ReportSimulationOutput/measure.rb index fcec01b1bd..ca0e0c2ae2 100644 --- a/ReportSimulationOutput/measure.rb +++ b/ReportSimulationOutput/measure.rb @@ -270,6 +270,11 @@ def arguments(model) # rubocop:disable Lint/UnusedMethodArgument arg.setDescription('Optionally generates timeseries EnergyPlus output variables. If multiple output variables are desired, use a comma-separated list. Do not include key values; by default all key values will be requested. Example: "Zone People Occupant Count, Zone People Total Heating Energy"') args << arg + arg = OpenStudio::Measure::OSArgument::makeStringArgument('user_output_meters', false) + arg.setDisplayName('Generate Timeseries Output: EnergyPlus Output Meters') + arg.setDescription('Optionally generates timeseries EnergyPlus output meters. If multiple output meters are desired, use a comma-separated list. Example: "Electricity:Facility, HeatingCoils:EnergyTransfer"') + args << arg + arg = OpenStudio::Measure::OSArgument::makeStringArgument('annual_output_file_name', false) arg.setDisplayName('Annual Output File Name') arg.setDescription("If not provided, defaults to 'results_annual.csv' (or 'results_annual.json' or 'results_annual.msgpack').") @@ -361,7 +366,7 @@ def energyPlusOutputRequests(runner, user_arguments) args = get_arguments(runner, arguments(model), user_arguments) - setup_outputs(false, args[:user_output_variables]) + setup_outputs(false, args) args = setup_timeseries_includes(@emissions, args) has_electricity_production = false @@ -531,11 +536,16 @@ def energyPlusOutputRequests(runner, user_arguments) end end - # Optional output variables (timeseries only) - @output_variables_requests.each do |output_variable_name, _output_variable| + # 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 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 + end + return result.uniq end @@ -576,7 +586,7 @@ def run(runner, user_arguments) @hpxml_header = hpxml.header @hpxml_bldgs = hpxml.buildings - setup_outputs(false, args[:user_output_variables]) + setup_outputs(false, args) if not File.exist? File.join(output_dir, 'eplusout.msgpack') runner.registerError('Cannot find eplusout.msgpack.') @@ -608,7 +618,7 @@ def run(runner, user_arguments) end if args[:timeseries_frequency] != 'none' - @timestamps, timestamps_dst, timestamps_utc = get_timestamps(@msgpackDataTimeseries, @hpxml_header, @hpxml_bldgs, args) + @timestamps, timestamps_dst, timestamps_utc = get_timestamps(@msgpackDataTimeseries, @msgpackData, @hpxml_header, @hpxml_bldgs, args) end # Retrieve outputs @@ -627,15 +637,24 @@ def run(runner, user_arguments) # TODO # + # @param msgpackDataTimeseries [TODO] TODO # @param msgpackData [TODO] TODO # @param hpxml_header [TODO] TODO # @param hpxml_bldgs [TODO] TODO # @param args [Hash] Map of :argument_name => value # @return [TODO] TODO - def get_timestamps(msgpackData, hpxml_header, hpxml_bldgs, args) - return if msgpackData.nil? + def get_timestamps(msgpackDataTimeseries, msgpackData, hpxml_header, hpxml_bldgs, args) + if not msgpackDataTimeseries.nil? + ep_timestamps = msgpackDataTimeseries['Rows'].map { |r| r.keys[0] } + elsif not msgpackData.nil? + msgpack_timeseries_name = get_msgpack_timeseries_name(args[:timeseries_frequency]) + timeseries_data = msgpackData['MeterData'][msgpack_timeseries_name] + if not timeseries_data.nil? + ep_timestamps = timeseries_data['Rows'].map { |r| r.keys[0] } + end + end - ep_timestamps = msgpackData['Rows'].map { |r| r.keys[0] } + return if ep_timestamps.nil? if args[:add_timeseries_dst_column] || args[:use_dview_format] dst_start_ts = Time.utc(hpxml_header.sim_calendar_year, hpxml_bldgs[0].dst_begin_month, hpxml_bldgs[0].dst_begin_day, 2) @@ -1190,10 +1209,15 @@ def sanitize_name(name) end end + # Output Variables @output_variables = {} - @output_variables_requests.each do |output_variable_name, _output_variable| + @output_variables_requests.each do |output_variable_name| key_values, units = get_report_variable_data_timeseries_key_values_and_units(output_variable_name) - runner.registerWarning("Request for output variable '#{output_variable_name}' returned no key values.") if key_values.empty? + if key_values.empty? + runner.registerWarning("Request for output variable '#{output_variable_name}' returned no results.") + next + end + key_values.each do |key_value| @output_variables[[output_variable_name, key_value]] = OutputVariable.new @output_variables[[output_variable_name, key_value]].name = "#{output_variable_name}: #{key_value.split.map(&:capitalize).join(' ')}" @@ -1202,6 +1226,21 @@ def sanitize_name(name) end end + # Output Meters + @output_meters = {} + @output_meters_requests.each do |output_meter_name| + units = get_report_meter_data_timeseries_units(output_meter_name, args[:timeseries_frequency]) + if units.nil? + runner.registerWarning("Request for output meter '#{output_meter_name}' returned no results.") + next + end + + @output_meters[output_meter_name] = OutputMeter.new + @output_meters[output_meter_name].name = output_meter_name + @output_meters[output_meter_name].timeseries_units = units + @output_meters[output_meter_name].timeseries_output = get_report_meter_data_timeseries([output_meter_name], 1, 0, args[:timeseries_frequency]) + end + # Emissions if not @emissions.empty? kwh_to_mwh = UnitConversions.convert(1.0, 'kWh', 'MWh') @@ -1806,17 +1845,24 @@ def report_timeseries_output_results(runner, outputs, timeseries_output_path, ar output_variables_data = [] end + # EnergyPlus output meters + if not @output_meters.empty? + output_meters_data = @output_meters.values.map { |x| [x.name, x.timeseries_units] + x.timeseries_output } + else + output_meters_data = [] + end + return if (total_energy_data.size + fuel_data.size + end_use_data.size + system_use_data.size + emissions_data.size + emission_fuel_data.size + emission_end_use_data.size + hot_water_use_data.size + total_loads_data.size + comp_loads_data.size + unmet_hours_data.size + - zone_temps_data.size + airflows_data.size + weather_data.size + resilience_data.size + output_variables_data.size) == 0 + zone_temps_data.size + airflows_data.size + weather_data.size + resilience_data.size + output_variables_data.size + output_meters_data.size) == 0 fail 'Unable to obtain timestamps.' if @timestamps.empty? if ['csv'].include? args[:output_format] # Assemble data data = data.zip(*timestamps2, *timestamps3, *total_energy_data, *fuel_data, *end_use_data, *system_use_data, *emissions_data, - *emission_fuel_data, *emission_end_use_data, *hot_water_use_data, *total_loads_data, *comp_loads_data, - *unmet_hours_data, *zone_temps_data, *airflows_data, *weather_data, *resilience_data, *output_variables_data) + *emission_fuel_data, *emission_end_use_data, *hot_water_use_data, *total_loads_data, *comp_loads_data, *unmet_hours_data, + *zone_temps_data, *airflows_data, *weather_data, *resilience_data, *output_variables_data, *output_meters_data) # Error-check n_elements = [] @@ -1882,7 +1928,7 @@ def report_timeseries_output_results(runner, outputs, timeseries_output_path, ar [total_energy_data, fuel_data, end_use_data, system_use_data, emissions_data, emission_fuel_data, emission_end_use_data, hot_water_use_data, total_loads_data, comp_loads_data, unmet_hours_data, - zone_temps_data, airflows_data, weather_data, resilience_data, output_variables_data].each do |d| + zone_temps_data, airflows_data, weather_data, resilience_data, output_variables_data, output_meters_data].each do |d| d.each do |o| grp, name = o[0].split(':', 2) h[grp] = {} if h[grp].nil? @@ -2011,10 +2057,7 @@ def get_resilience_timeseries(init_time_step, batt_kwh, batt_kw, batt_soc_kwh, c def get_report_meter_data_timeseries(meter_names, unit_conv, unit_adder, timeseries_frequency) return [0.0] * @timestamps.size if meter_names.empty? - msgpack_timeseries_name = { 'timestep' => 'TimeStep', - 'hourly' => 'Hourly', - 'daily' => 'Daily', - 'monthly' => 'Monthly' }[timeseries_frequency] + msgpack_timeseries_name = get_msgpack_timeseries_name(timeseries_frequency) timeseries_data = @msgpackData['MeterData'][msgpack_timeseries_name] cols = timeseries_data['Cols'] rows = timeseries_data['Rows'] @@ -2107,23 +2150,55 @@ def apply_ems_shift(timeseries_frequency) # TODO # - # @param var [TODO] TODO + # @param var_name [TODO] TODO # @return [TODO] TODO - def get_report_variable_data_timeseries_key_values_and_units(var) + def get_report_variable_data_timeseries_key_values_and_units(var_name) keys = [] units = '' - if not @msgpackDataTimeseries.nil? - @msgpackDataTimeseries['Cols'].each do |col| - next unless col['Variable'].end_with? ":#{var}" + return keys, units if @msgpackDataTimeseries.nil? - keys << col['Variable'].split(':')[0..-2].join(':') - units = col['Units'] - end + @msgpackDataTimeseries['Cols'].each do |col| + next unless col['Variable'].end_with? ":#{var_name}" + + keys << col['Variable'].split(':')[0..-2].join(':') + units = col['Units'] end return keys, units end + # TODO + # + # @param meter_name [TODO] TODO + # @param timeseries_frequency [TODO] TODO + # @return [TODO] TODO + def get_report_meter_data_timeseries_units(meter_name, timeseries_frequency) + return if @msgpackData.nil? + + msgpack_timeseries_name = get_msgpack_timeseries_name(timeseries_frequency) + timeseries_data = @msgpackData['MeterData'][msgpack_timeseries_name] + return if timeseries_data.nil? + + timeseries_data['Cols'].each do |col| + next unless col['Variable'] == meter_name + + return col['Units'] + end + + return + end + + # TODO + # + # @param timeseries_frequency [TODO] TODO + # @return [TODO] TODO + def get_msgpack_timeseries_name(timeseries_frequency) + return { 'timestep' => 'TimeStep', + 'hourly' => 'Hourly', + 'daily' => 'Daily', + 'monthly' => 'Monthly' }[timeseries_frequency] + end + # TODO # # @param report_name [TODO] TODO @@ -2425,12 +2500,20 @@ def initialize attr_accessor() end + # TODO + class OutputMeter < BaseOutput + def initialize + super() + end + attr_accessor() + end + # TODO # # @param called_from_outputs_method [TODO] TODO - # @param user_output_variables [TODO] TODO + # @param args [TODO] TODO # @return [TODO] TODO - def setup_outputs(called_from_outputs_method, user_output_variables = nil) + def setup_outputs(called_from_outputs_method, args = {}) # TODO # # @param fuel_type [TODO] TODO @@ -2760,13 +2843,10 @@ def get_timeseries_units_from_fuel_type(fuel_type) end # Output Variables - @output_variables_requests = {} - if not user_output_variables.nil? - output_variables = user_output_variables.split(',').map(&:strip) - output_variables.each do |output_variable| - @output_variables_requests[output_variable] = OutputVariable.new - end - end + @output_variables_requests = args[:user_output_variables].to_s.split(',').map(&:strip) + + # Output Meters + @output_meters_requests = args[:user_output_meters].to_s.split(',').map(&:strip) end # TODO diff --git a/ReportSimulationOutput/measure.xml b/ReportSimulationOutput/measure.xml index 3d43e4ede3..5ae0444c80 100644 --- a/ReportSimulationOutput/measure.xml +++ b/ReportSimulationOutput/measure.xml @@ -3,8 +3,8 @@ 3.1 report_simulation_output df9d170c-c21a-4130-866d-0d46b06073fd - 5e609cb1-8e83-479b-afda-c1de10cd62ea - 2024-11-27T02:33:41Z + ca741975-505b-4e74-a17b-b96671720b39 + 2025-01-25T08:36:10Z 9BF1E6AC ReportSimulationOutput HPXML Simulation Output Report @@ -712,6 +712,14 @@ false false + + user_output_meters + Generate Timeseries Output: EnergyPlus Output Meters + Optionally generates timeseries EnergyPlus output meters. If multiple output meters are desired, use a comma-separated list. Example: "Electricity:Facility, HeatingCoils:EnergyTransfer" + String + false + false + annual_output_file_name Annual Output File Name @@ -1912,7 +1920,7 @@ README.md md readme - CDB2D617 + E1D3B16D README.md.erb @@ -1929,13 +1937,13 @@ measure.rb rb script - 4C7478A8 + 2D3EC7C0 test_report_sim_output.rb rb test - 8552F493 + F630E4A7 diff --git a/ReportSimulationOutput/tests/test_report_sim_output.rb b/ReportSimulationOutput/tests/test_report_sim_output.rb index b147a71913..151e701075 100644 --- a/ReportSimulationOutput/tests/test_report_sim_output.rb +++ b/ReportSimulationOutput/tests/test_report_sim_output.rb @@ -401,6 +401,11 @@ def teardown 'Surface Construction Index: Window4' ] + BaseHPXMLTimeseriesColsEnergyPlusOutputMeters = [ + 'MainsWater:Facility', + 'HeatingCoils:EnergyTransfer' + ] + def all_base_hpxml_timeseries_cols return (BaseHPXMLTimeseriesColsEnergy + BaseHPXMLTimeseriesColsFuels + @@ -1320,6 +1325,26 @@ def test_timeseries_energyplus_output_variables assert(File.readlines(run_log).any? { |line| line.include?("Request for output variable 'Foo'") }) end + def test_timeseries_energyplus_output_meters + args_hash = { 'hpxml_path' => File.join(File.dirname(__FILE__), '../../workflow/sample_files/base.xml'), + 'skip_validation' => true, + 'add_component_loads' => true, + 'timeseries_frequency' => 'hourly', + 'user_output_meters' => 'MainsWater:Facility, Foo:Meter, HeatingCoils:EnergyTransfer' } + annual_csv, timeseries_csv, run_log = _test_measure(args_hash) + assert(File.exist?(annual_csv)) + assert(File.exist?(timeseries_csv)) + expected_timeseries_cols = ['Time'] + BaseHPXMLTimeseriesColsEnergyPlusOutputMeters + actual_timeseries_cols = File.readlines(timeseries_csv)[0].strip.split(',') + assert_equal(expected_timeseries_cols.sort, actual_timeseries_cols.sort) + timeseries_rows = CSV.read(timeseries_csv) + assert_equal(8760, timeseries_rows.size - 2) + timeseries_cols = timeseries_rows.transpose + assert_equal(1, _check_for_constant_timeseries_step(timeseries_cols[0])) + _check_for_nonzero_avg_timeseries_value(timeseries_csv, BaseHPXMLTimeseriesColsEnergyPlusOutputMeters) + assert(File.readlines(run_log).any? { |line| line.include?("Request for output meter 'Foo:Meter'") }) + end + def test_for_unsuccessful_simulation_infinity # Create HPXML w/ AFUE=0 to generate Infinity result hpxml_path = File.join(File.dirname(__FILE__), '../../workflow/sample_files/base.xml') diff --git a/docs/source/usage_instructions.rst b/docs/source/usage_instructions.rst index 0031d80089..91dc94afb3 100644 --- a/docs/source/usage_instructions.rst +++ b/docs/source/usage_instructions.rst @@ -38,6 +38,7 @@ Basic Run | ``openstudio workflow/run_simulation.rb -x workflow/sample_files/base.xml --hourly ALL`` | ``openstudio workflow/run_simulation.rb -x workflow/sample_files/base.xml --monthly fuels --monthly temperatures --output-format json`` | ``openstudio workflow/run_simulation.rb -x workflow/sample_files/base.xml --monthly fuels --hourly temperatures --hourly 'Zone People Occupant Count'`` +| ``openstudio workflow/run_simulation.rb -x workflow/sample_files/base.xml --monthly fuels --hourly temperatures --hourly 'MainsWater:Facility'`` | ``openstudio workflow/run_simulation.rb -x workflow/sample_files/base.xml --hourly ALL --output-format csv_dview`` | The last command will create a timeseries CSV output file that can be visualized by `DView `_ (available for download `here `_). diff --git a/docs/source/workflow_outputs.rst b/docs/source/workflow_outputs.rst index b1775f243f..936284247a 100644 --- a/docs/source/workflow_outputs.rst +++ b/docs/source/workflow_outputs.rst @@ -619,26 +619,26 @@ If multiple timeseries frequencies are requested (e.g., hourly and daily), the t Depending on the outputs requested, the file may include: - =========================== =================== ================================================================================================================================== - Type Argument [#]_ Notes - =========================== =================== ================================================================================================================================== - Total Consumptions ``total`` Energy use for building total and net (i.e., subtracts any power produced by PV or generators). - Fuel Consumptions ``fuels`` Energy use for each fuel type (in kBtu for fossil fuels and kWh for electricity). - End Use Consumptions ``enduses`` Energy use for each end use type (in kBtu for fossil fuels and kWh for electricity). - System Use Consumptions ``systemuses`` Energy use for each HVAC and water heating system (in kBtu). - Emissions ``emissions`` Emissions (e.g., CO2) for each scenario defined in the HPXML file. - Emission Fuels ``emissionfuels`` Emissions (e.g., CO2) disaggregated by fuel type for each scenario defined in the HPXML file. - Emission End Uses ``emissionenduses`` Emissions (e.g., CO2) disaggregated by end use for each scenario defined in the HPXML file. - Hot Water Uses ``hotwater`` Water use for each end use type (in gallons). - Total Loads ``loads`` Heating, cooling, and hot water loads (in kBtu). - Component Loads ``componentloads`` Heating and cooling loads (in kBtu) disaggregated by component (e.g., Walls, Windows, Infiltration, Ducts, etc.). - Unmet Hours ``unmethours`` Heating and cooling unmet hours. - Zone Temperatures ``temperatures`` Zone temperatures (in deg-F) for each space (e.g., conditioned space, attic, garage, basement, crawlspace, etc.) plus heating/cooling setpoints. - Airflows ``airflows`` Airflow rates (in cfm) for infiltration, mechanical ventilation (including clothes dryer exhaust), natural ventilation, whole house fans. - Weather ``weather`` Weather file data including outdoor temperatures, relative humidity, wind speed, and solar. - Resilience ``resilience`` Resilience outputs (currently only average resilience hours for battery storage). - EnergyPlus Output Variables Any user-specified EnergyPlus output variables (e.g., 'Zone People Occupant Count'). - =========================== =================== ================================================================================================================================== + ================================== =================== ================================================================================================================================== + Type Argument [#]_ Notes + ================================== =================== ================================================================================================================================== + Total Consumptions ``total`` Energy use for building total and net (i.e., subtracts any power produced by PV or generators). + Fuel Consumptions ``fuels`` Energy use for each fuel type (in kBtu for fossil fuels and kWh for electricity). + End Use Consumptions ``enduses`` Energy use for each end use type (in kBtu for fossil fuels and kWh for electricity). + System Use Consumptions ``systemuses`` Energy use for each HVAC and water heating system (in kBtu). + Emissions ``emissions`` Emissions (e.g., CO2) for each scenario defined in the HPXML file. + Emission Fuels ``emissionfuels`` Emissions (e.g., CO2) disaggregated by fuel type for each scenario defined in the HPXML file. + Emission End Uses ``emissionenduses`` Emissions (e.g., CO2) disaggregated by end use for each scenario defined in the HPXML file. + Hot Water Uses ``hotwater`` Water use for each end use type (in gallons). + Total Loads ``loads`` Heating, cooling, and hot water loads (in kBtu). + Component Loads ``componentloads`` Heating and cooling loads (in kBtu) disaggregated by component (e.g., Walls, Windows, Infiltration, Ducts, etc.). + Unmet Hours ``unmethours`` Heating and cooling unmet hours. + Zone Temperatures ``temperatures`` Zone temperatures (in deg-F) for each space (e.g., conditioned space, attic, garage, basement, crawlspace, etc.) plus heating/cooling setpoints. + Airflows ``airflows`` Airflow rates (in cfm) for infiltration, mechanical ventilation (including clothes dryer exhaust), natural ventilation, whole house fans. + Weather ``weather`` Weather file data including outdoor temperatures, relative humidity, wind speed, and solar. + Resilience ``resilience`` Resilience outputs (currently only average resilience hours for battery storage). + EnergyPlus Output Variables/Meters Any user-specified EnergyPlus output variables/meters (e.g., 'Zone People Occupant Count', 'MainsWater:Facility'). + ================================== =================== ================================================================================================================================== .. [#] This is the argument provided to ``run_simulation.rb`` as described in the :ref:`basic_run` usage instructions. diff --git a/workflow/run_simulation.rb b/workflow/run_simulation.rb index 83f1ce0066..9b52bbd023 100644 --- a/workflow/run_simulation.rb +++ b/workflow/run_simulation.rb @@ -57,6 +57,11 @@ def run_workflow(basedir, rundir, hpxml, debug, skip_validation, add_comp_loads, 'timestep' => timestep_outputs }.each do |timeseries_output_freq, timeseries_outputs| next if (timeseries_outputs.empty? && timeseries_output_freq != 'none') + comma_output = timeseries_outputs.find { |o| o.include? ',' } + if not comma_output.nil? + fail "Timeseries output request '#{comma_output}' cannot include a comma." + end + if timeseries_outputs.include? 'ALL' # Replace 'ALL' with all individual timeseries types timeseries_outputs.delete('ALL') @@ -85,8 +90,11 @@ def run_workflow(basedir, rundir, hpxml, debug, skip_validation, add_comp_loads, args['include_timeseries_airflows'] = timeseries_outputs.include? 'airflows' args['include_timeseries_weather'] = timeseries_outputs.include? 'weather' args['include_timeseries_resilience'] = timeseries_outputs.include? 'resilience' - user_output_variables = timeseries_outputs - $timeseries_types - args['user_output_variables'] = user_output_variables.join(', ') unless user_output_variables.empty? + remaining_outputs = timeseries_outputs - $timeseries_types + output_variables = remaining_outputs.select { |o| !o.include?(':') } + output_meters = remaining_outputs.select { |o| o.include?(':') } + args['user_output_variables'] = output_variables.join(', ') unless output_variables.empty? + args['user_output_meters'] = output_meters.join(', ') unless output_meters.empty? if n_timeseries_freqs > 1 # Need to use different timeseries filenames args['timeseries_output_file_name'] = "results_timeseries_#{timeseries_output_freq}.#{output_format}" @@ -125,22 +133,22 @@ def run_workflow(basedir, rundir, hpxml, debug, skip_validation, add_comp_loads, end options[:hourly_outputs] = [] - opts.on('--hourly NAME', 'Request hourly output category* or EnergyPlus output variable; can be called multiple times') do |t| + opts.on('--hourly NAME', 'Request hourly output category* or EnergyPlus output variable/meter; can be called multiple times') do |t| options[:hourly_outputs] << t end options[:daily_outputs] = [] - opts.on('--daily NAME', 'Request daily output category* or EnergyPlus output variable; can be called multiple times') do |t| + opts.on('--daily NAME', 'Request daily output category* or EnergyPlus output variable/meter; can be called multiple times') do |t| options[:daily_outputs] << t end options[:monthly_outputs] = [] - opts.on('--monthly NAME', 'Request monthly output category* or EnergyPlus output variable; can be called multiple times') do |t| + opts.on('--monthly NAME', 'Request monthly output category* or EnergyPlus output variable/meter; can be called multiple times') do |t| options[:monthly_outputs] << t end options[:timestep_outputs] = [] - opts.on('--timestep NAME', 'Request timestep output category* or EnergyPlus output variable; can be called multiple times') do |t| + opts.on('--timestep NAME', 'Request timestep output category* or EnergyPlus output variable/meter; can be called multiple times') do |t| options[:timestep_outputs] << t end 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 da3a974b27..ef48e709fb 100644 --- a/workflow/template-build-and-run-hpxml-with-stochastic-occupancy.osw +++ b/workflow/template-build-and-run-hpxml-with-stochastic-occupancy.osw @@ -135,7 +135,8 @@ "timeseries_timestamp_convention": "start", "add_timeseries_dst_column": false, "add_timeseries_utc_column": false, - "user_output_variables": "" + "user_output_variables": "", + "user_output_meters": "" }, "measure_dir_name": "ReportSimulationOutput" }, diff --git a/workflow/template-run-hpxml-with-stochastic-occupancy-subset.osw b/workflow/template-run-hpxml-with-stochastic-occupancy-subset.osw index 376cc13ca1..1552e18dc2 100644 --- a/workflow/template-run-hpxml-with-stochastic-occupancy-subset.osw +++ b/workflow/template-run-hpxml-with-stochastic-occupancy-subset.osw @@ -60,7 +60,8 @@ "timeseries_timestamp_convention": "start", "add_timeseries_dst_column": false, "add_timeseries_utc_column": false, - "user_output_variables": "" + "user_output_variables": "", + "user_output_meters": "" }, "measure_dir_name": "ReportSimulationOutput" }, diff --git a/workflow/template-run-hpxml-with-stochastic-occupancy.osw b/workflow/template-run-hpxml-with-stochastic-occupancy.osw index 737d8ab397..8a0dff1170 100644 --- a/workflow/template-run-hpxml-with-stochastic-occupancy.osw +++ b/workflow/template-run-hpxml-with-stochastic-occupancy.osw @@ -59,7 +59,8 @@ "timeseries_timestamp_convention": "start", "add_timeseries_dst_column": false, "add_timeseries_utc_column": false, - "user_output_variables": "" + "user_output_variables": "", + "user_output_meters": "" }, "measure_dir_name": "ReportSimulationOutput" }, diff --git a/workflow/template-run-hpxml.osw b/workflow/template-run-hpxml.osw index cedb0cce1a..ca67d06488 100644 --- a/workflow/template-run-hpxml.osw +++ b/workflow/template-run-hpxml.osw @@ -52,7 +52,8 @@ "timeseries_timestamp_convention": "start", "add_timeseries_dst_column": false, "add_timeseries_utc_column": false, - "user_output_variables": "" + "user_output_variables": "", + "user_output_meters": "" }, "measure_dir_name": "ReportSimulationOutput" }, diff --git a/workflow/tests/test_other.rb b/workflow/tests/test_other.rb index 59a6df1c52..1da4549bdc 100644 --- a/workflow/tests/test_other.rb +++ b/workflow/tests/test_other.rb @@ -135,8 +135,10 @@ def test_run_simulation_timeseries_outputs command += ' --hourly ALL' command += " --hourly 'Zone People Occupant Count'" command += " --hourly 'Zone People Total Heating Energy'" + command += " --hourly 'MainsWater:Facility'" end command += " --hourly 'Foobar Variable'" # Test invalid output variable request + command += " --hourly 'Foobar:Meter'" # Test invalid output variable request system(command, err: File::NULL) # Check for output files @@ -151,22 +153,34 @@ def test_run_simulation_timeseries_outputs assert_equal(1, timeseries_rows[0].count { |r| r == 'Time' }) assert_equal(1, timeseries_rows[0].count { |r| r == 'Zone People Occupant Count: Conditioned Space' }) assert_equal(1, timeseries_rows[0].count { |r| r == 'Zone People Total Heating Energy: Conditioned Space' }) + assert_equal(1, timeseries_rows[0].count { |r| r == 'MainsWater:Facility' }) else refute(File.exist? timeseries_output_path) end - # Check run.log has warning about missing Foobar Variable + # Check run.log has warning about missing Foobar Variable & Meter assert(File.exist? File.join(File.dirname(xml), 'run', 'run.log')) log_lines = File.readlines(File.join(File.dirname(xml), 'run', 'run.log')).map(&:strip) - assert(log_lines.include? "Warning: Request for output variable 'Foobar Variable' returned no key values.") + assert(log_lines.include? "Warning: Request for output variable 'Foobar Variable' returned no results.") + assert(log_lines.include? "Warning: Request for output meter 'Foobar:Meter' returned no results.") end end + def test_run_simulation_timeseries_outputs_comma + # Check that the simulation produces timeseries with requested outputs + rb_path = File.join(File.dirname(__FILE__), '..', 'run_simulation.rb') + xml = File.join(File.dirname(__FILE__), '..', 'sample_files', 'base.xml') + command = "\"#{OpenStudio.getOpenStudioCLI}\" \"#{rb_path}\" -x \"#{xml}\" --hourly 'Zone People Occupant Count,MainsWater:Facility'" + success = system(command, err: File::NULL) + + refute(success) + end + def test_run_simulation_mixed_timeseries_frequencies # Check that we can correctly skip the EnergyPlus simulation and reporting measures rb_path = File.join(File.dirname(__FILE__), '..', 'run_simulation.rb') xml = File.join(File.dirname(__FILE__), '..', 'sample_files', 'base.xml') - command = "\"#{OpenStudio.getOpenStudioCLI}\" \"#{rb_path}\" -x \"#{xml}\" --timestep weather --hourly enduses --daily temperatures --monthly ALL --monthly 'Zone People Total Heating Energy'" + command = "\"#{OpenStudio.getOpenStudioCLI}\" \"#{rb_path}\" -x \"#{xml}\" --timestep weather --hourly enduses --daily temperatures --monthly ALL --monthly 'Zone People Total Heating Energy' --daily 'MainsWater:Facility'" system(command, err: File::NULL) # Check for output files @@ -180,7 +194,7 @@ def test_run_simulation_mixed_timeseries_frequencies # Check timeseries columns exist { 'timestep' => ['Weather:'], 'hourly' => ['End Use:'], - 'daily' => ['Temperature:'], + 'daily' => ['Temperature:', 'MainsWater:Facility'], 'monthly' => ['End Use:', 'Fuel Use:', 'Zone People Total Heating Energy:'] }.each do |freq, col_names| timeseries_rows = CSV.read(File.join(File.dirname(xml), 'run', "results_timeseries_#{freq}.csv")) assert_equal(1, timeseries_rows[0].count { |r| r == 'Time' })