diff --git a/buildstockbatch/aws/aws.py b/buildstockbatch/aws/aws.py index 815e8ee2..d80d4e7d 100644 --- a/buildstockbatch/aws/aws.py +++ b/buildstockbatch/aws/aws.py @@ -2032,7 +2032,7 @@ def run_job(cls, job_id, bucket, prefix, job_name, region): fs = S3FileSystem() local_fs = LocalFileSystem() - reporting_measures = cfg.get('reporting_measures', []) + reporting_measures = cls.get_reporting_measures(cfg) dpouts = [] simulation_output_tar_filename = sim_dir.parent / 'simulation_outputs.tar.gz' with tarfile.open(str(simulation_output_tar_filename), 'w:gz') as simout_tar: diff --git a/buildstockbatch/base.py b/buildstockbatch/base.py index c899023e..125e4e15 100644 --- a/buildstockbatch/base.py +++ b/buildstockbatch/base.py @@ -125,6 +125,12 @@ def skip_baseline_sims(self): baseline_skip = self.cfg['baseline'].get('skip_sims', False) return baseline_skip + @classmethod + def get_reporting_measures(cls, cfg): + WorkflowGenerator = cls.get_workflow_generator_class(cfg['workflow_generator']['type']) + wg = WorkflowGenerator(cfg, 1) # Number of datapoints doesn't really matter here + return wg.reporting_measures() + def run_batch(self): raise NotImplementedError @@ -551,7 +557,7 @@ def process_results(self, skip_combine=False, force_upload=False): if 'athena' in aws_conf: postprocessing.create_athena_tables(aws_conf, os.path.basename(self.output_dir), s3_bucket, s3_prefix) - if not self.cfg['eagle'].get('postprocessing', {}).get('keep_intermediate_files', False): + if not self.cfg.get('eagle', {}).get('postprocessing', {}).get('keep_intermediate_files', False): logger.info("Removing intermediate files.") postprocessing.remove_intermediate_files(fs, self.results_dir) else: diff --git a/buildstockbatch/eagle.py b/buildstockbatch/eagle.py index 3b688781..283686b6 100644 --- a/buildstockbatch/eagle.py +++ b/buildstockbatch/eagle.py @@ -378,7 +378,7 @@ def run_building(cls, output_dir, cfg, n_datapoints, i, upgrade_idx=None): i ) - reporting_measures = cfg.get('reporting_measures', []) + reporting_measures = cls.get_reporting_measures(cfg) dpout = postprocessing.read_simulation_outputs(fs, reporting_measures, sim_dir, upgrade_id, i) return dpout diff --git a/buildstockbatch/localdocker.py b/buildstockbatch/localdocker.py index e6d89800..b30fe801 100644 --- a/buildstockbatch/localdocker.py +++ b/buildstockbatch/localdocker.py @@ -150,7 +150,7 @@ def run_building(cls, project_dir, buildstock_dir, weather_dir, docker_image, re ) # Read data_point_out.json - reporting_measures = cfg.get('reporting_measures', []) + reporting_measures = cls.get_reporting_measures(cfg) dpout = postprocessing.read_simulation_outputs(fs, reporting_measures, sim_dir, upgrade_id, i) return dpout diff --git a/buildstockbatch/postprocessing.py b/buildstockbatch/postprocessing.py index 5d75181a..a6253e87 100644 --- a/buildstockbatch/postprocessing.py +++ b/buildstockbatch/postprocessing.py @@ -189,17 +189,11 @@ def clean_up_results_df(df, cfg, keep_upgrade_id=False): simulation_output_cols = sorted([col for col in results_df.columns if col.startswith('simulation_output_report')]) sorted_cols = first_few_cols + build_existing_model_cols + simulation_output_cols - for reporting_measure in cfg.get('reporting_measures', []): - reporting_measure_cols = sorted([col for col in results_df.columns if - col.startswith(to_camelcase(reporting_measure))]) - sorted_cols += reporting_measure_cols + remaining_cols = sorted(set(results_df.columns.values).difference(sorted_cols)) + sorted_cols += remaining_cols results_df = results_df.reindex(columns=sorted_cols, copy=False) - # for col in results_df.columns: - # if col.startswith('simulation_output_report.') and not col == 'simulation_output_report.applicable': - # results_df[col] = pd.to_numeric(results_df[col], errors='coerce') - return results_df diff --git a/buildstockbatch/test/test_eagle.py b/buildstockbatch/test/test_eagle.py index 34b6c4f2..5498cdb5 100644 --- a/buildstockbatch/test/test_eagle.py +++ b/buildstockbatch/test/test_eagle.py @@ -226,6 +226,7 @@ def make_sim_dir_mock(building_id, upgrade_idx, base_dir, overwrite_existing=Fal sampler_prop_mock = mocker.patch.object(EagleBatch, 'sampler', new_callable=mocker.PropertyMock) sampler_mock = mocker.MagicMock() sampler_prop_mock.return_value = sampler_mock + sampler_mock.csv_path = results_dir.parent / 'housing_characteristic2' / 'buildstock.csv' sampler_mock.run_sampling = mocker.MagicMock(return_value='buildstock.csv') b = EagleBatch(project_filename) diff --git a/buildstockbatch/test/test_inputs/enforce-validate-measures-bad-2.yml b/buildstockbatch/test/test_inputs/enforce-validate-measures-bad-2.yml index f23b8530..464ae7eb 100644 --- a/buildstockbatch/test/test_inputs/enforce-validate-measures-bad-2.yml +++ b/buildstockbatch/test/test_inputs/enforce-validate-measures-bad-2.yml @@ -27,7 +27,7 @@ workflow_generator: simulation_output: include_enduse_subcategory: true reporting_measures: - - ReportingMeasure2 + - measure_dir_name: ReportingMeasure2 upgrades: - upgrade_name: good upgrade diff --git a/buildstockbatch/test/test_inputs/enforce-validate-measures-good-2.yml b/buildstockbatch/test/test_inputs/enforce-validate-measures-good-2.yml index 4a2da304..24fce595 100644 --- a/buildstockbatch/test/test_inputs/enforce-validate-measures-good-2.yml +++ b/buildstockbatch/test/test_inputs/enforce-validate-measures-good-2.yml @@ -21,7 +21,7 @@ workflow_generator: output_variables: - Zone Mean Air Temperature reporting_measures: - - ReportingMeasure1 + - measure_dir_name: ReportingMeasure1 upgrades: - upgrade_name: cool upgrade diff --git a/buildstockbatch/workflow_generator/base.py b/buildstockbatch/workflow_generator/base.py index 371ab922..dc999c42 100644 --- a/buildstockbatch/workflow_generator/base.py +++ b/buildstockbatch/workflow_generator/base.py @@ -66,3 +66,10 @@ def validate(cls, cfg): :type cfg: dict """ return True + + def reporting_measures(self): + """Return a list of reporting measures to include in the outputs + + Replace this in your subclass + """ + return [] diff --git a/buildstockbatch/workflow_generator/commercial.py b/buildstockbatch/workflow_generator/commercial.py index 41c33362..5d0ab739 100644 --- a/buildstockbatch/workflow_generator/commercial.py +++ b/buildstockbatch/workflow_generator/commercial.py @@ -13,14 +13,23 @@ import datetime as dt import json import logging +import os import re +from xml.etree import ElementTree import yamale from .base import WorkflowGeneratorBase +from buildstockbatch.exc import ValidationError logger = logging.getLogger(__name__) +def get_measure_xml(xml_path): + tree = ElementTree.parse(xml_path) + root = tree.getroot() + return root + + class CommercialDefaultWorkflowGenerator(WorkflowGeneratorBase): @classmethod @@ -32,7 +41,7 @@ def validate(cls, cfg): """ schema_yml = """ measures: list(include('measure-spec'), required=False) - include_qaqc: bool(required=False) + reporting_measures: list(include('measure-spec'), required=False) --- measure-spec: measure_dir_name: str(required=True) @@ -44,6 +53,159 @@ def validate(cls, cfg): data = yamale.make_data(content=json.dumps(workflow_generator_args), parser='ruamel') return yamale.validate(schema, data, strict=True) + def reporting_measures(self): + """Return a list of reporting measures to include in outputs""" + workflow_args = self.cfg['workflow_generator'].get('args', {}) + + # reporting_measures needs to return the ClassName in measure.rb, but + # measure_dir_name in ComStock doesn't always match the ClassName + buildstock_dir = self.cfg['buildstock_directory'] + measures_dir = os.path.join(buildstock_dir, 'measures') + measure_class_names = [] + for m in workflow_args.get('reporting_measures', []): + measure_dir_name = m['measure_dir_name'] + measure_path = os.path.join(measures_dir, measure_dir_name) + root = get_measure_xml(os.path.join(measure_path, 'measure.xml')) + measure_class_name = root.find('./class_name').text + # Don't include OpenStudioResults, it has too many registerValues for ComStock + if measure_class_name == 'OpenStudioResults': + continue + measure_class_names.append(measure_class_name) + + return measure_class_names + + @staticmethod + def validate_measures_and_arguments(cfg): + + buildstock_dir = cfg["buildstock_directory"] + measures_dir = os.path.join(buildstock_dir, 'measures') + type_map = {'Integer': int, 'Boolean': bool, 'String': str, 'Double': float} + + measure_names = { + 'BuildExistingModel': 'baseline', + 'SimulationOutputReport': 'workflow_generator.args.simulation_output', + 'ApplyUpgrade': 'upgrades', + 'TimeseriesCSVExport': 'workflow_generator.args.timeseries_csv_export' + } + + def cfg_path_exists(cfg_path): + if cfg_path is None: + return False + path_items = cfg_path.split('.') + a = cfg + for path_item in path_items: + try: + a = a[path_item] # noqa F841 + except KeyError: + return False + return True + + def get_cfg_path(cfg_path): + if cfg_path is None: + return None + path_items = cfg_path.split('.') + a = cfg + for path_item in path_items: + try: + a = a[path_item] + except KeyError: + return None + return a + + workflow_args = cfg['workflow_generator'].get('args', {}) + if 'reporting_measures' in workflow_args.keys(): + for reporting_measure in workflow_args['reporting_measures']: + measure_names[reporting_measure['measure_dir_name']] = 'workflow_generator.args.reporting_measures' + + error_msgs = '' + warning_msgs = '' + for measure_name, cfg_key in measure_names.items(): + measure_path = os.path.join(measures_dir, measure_name) + + if cfg_path_exists(cfg_key) or cfg_key == 'workflow_generator.args.residential_simulation_controls': + # if they exist in the cfg, make sure they exist in the buildstock checkout + if not os.path.exists(measure_path): + error_msgs += f"* {measure_name} does not exist in {buildstock_dir}. \n" + + # check the rest only if that measure exists in cfg + if not cfg_path_exists(cfg_key): + continue + + # check argument value types for simulation output report and timeseries csv export measures + if measure_name in ['SimulationOutputReport', 'TimeseriesCSVExport']: + root = get_measure_xml(os.path.join(measure_path, 'measure.xml')) + expected_arguments = {} + required_args_with_default = {} + required_args_no_default = {} + for argument in root.findall('./arguments/argument'): + name = argument.find('./name').text + expected_arguments[name] = [] + required = argument.find('./required').text + default = argument.find('./default_value') + default = default.text if default is not None else None + + if required == 'true' and not default: + required_args_no_default[name] = None + elif required == 'true': + required_args_with_default[name] = default + + if argument.find('./type').text == 'Choice': + for choice in argument.findall('./choices/choice'): + for value in choice.findall('./value'): + expected_arguments[name].append(value.text) + else: + expected_arguments[name] = argument.find('./type').text + + for actual_argument_key in get_cfg_path(measure_names[measure_name]).keys(): + if actual_argument_key not in expected_arguments.keys(): + error_msgs += f"* Found unexpected argument key {actual_argument_key} for "\ + f"{measure_names[measure_name]} in yaml file. The available keys are: " \ + f"{list(expected_arguments.keys())}\n" + continue + + required_args_no_default.pop(actual_argument_key, None) + required_args_with_default.pop(actual_argument_key, None) + + actual_argument_value = get_cfg_path(measure_names[measure_name])[actual_argument_key] + expected_argument_type = expected_arguments[actual_argument_key] + + if type(expected_argument_type) is not list: + try: + if type(actual_argument_value) is not list: + actual_argument_value = [actual_argument_value] + + for val in actual_argument_value: + if not isinstance(val, type_map[expected_argument_type]): + error_msgs += f"* Wrong argument value type for {actual_argument_key} for measure "\ + f"{measure_names[measure_name]} in yaml file. Expected type:" \ + f" {type_map[expected_argument_type]}, got: {val}" \ + f" of type: {type(val)} \n" + except KeyError: + print(f"Found an unexpected argument value type: {expected_argument_type} for argument " + f" {actual_argument_key} in measure {measure_name}.\n") + else: # Choice + if actual_argument_value not in expected_argument_type: + error_msgs += f"* Found unexpected argument value {actual_argument_value} for "\ + f"{measure_names[measure_name]} in yaml file. Valid values are " \ + f"{expected_argument_type}.\n" + + for arg, default in required_args_no_default.items(): + error_msgs += f"* Required argument {arg} for measure {measure_name} wasn't supplied. " \ + f"There is no default for this argument.\n" + + for arg, default in required_args_with_default.items(): + warning_msgs += f"* Required argument {arg} for measure {measure_name} wasn't supplied. " \ + f"Using default value: {default}. \n" + + if warning_msgs: + logger.warning(warning_msgs) + + if not error_msgs: + return True + else: + logger.error(error_msgs) + raise ValidationError(error_msgs) + def create_osw(self, sim_id, building_id, upgrade_idx): """ Generate and return the osw as a python dict @@ -55,8 +217,7 @@ def create_osw(self, sim_id, building_id, upgrade_idx): logger.debug('Generating OSW, sim_id={}'.format(sim_id)) workflow_args = { - 'measures': [], - 'include_qaqc': False + 'measures': [] } workflow_args.update(self.cfg['workflow_generator'].get('args', {})) @@ -70,18 +231,6 @@ def create_osw(self, sim_id, building_id, upgrade_idx): "building_id": int(building_id) }, "measure_type": "ModelMeasure" - }, - { - "measure_dir_name": "add_blinds_to_selected_windows", - "arguments": { - "add_blinds": True - }, - "measure_type": "ModelMeasure" - }, - { - "measure_dir_name": "set_space_type_load_subcategories", - "arguments": {}, - "measure_type": "ModelMeasure" } ], 'created_at': dt.datetime.now().isoformat(), @@ -91,79 +240,10 @@ def create_osw(self, sim_id, building_id, upgrade_idx): 'weather_file': 'weather/empty.epw' } + # Baseline measures (not typically used in ComStock) osw['steps'].extend(workflow_args['measures']) - osw['steps'].extend([ - { - "measure_dir_name": "SimulationOutputReport", - "arguments": {}, - "measure_type": "ReportingMeasure" - }, - { - "measure_dir_name": "f8e23017-894d-4bdf-977f-37e3961e6f42", - "arguments": { - "building_summary_section": True, - "annual_overview_section": True, - "monthly_overview_section": True, - "utility_bills_rates_section": True, - "envelope_section_section": True, - "space_type_breakdown_section": True, - "space_type_details_section": True, - "interior_lighting_section": True, - "plug_loads_section": True, - "exterior_light_section": True, - "water_use_section": True, - "hvac_load_profile": True, - "zone_condition_section": True, - "zone_summary_section": True, - "zone_equipment_detail_section": True, - "air_loops_detail_section": True, - "plant_loops_detail_section": True, - "outdoor_air_section": True, - "cost_summary_section": True, - "source_energy_section": True, - "schedules_overview_section": True - }, - "measure_type": "ReportingMeasure" - }, - { - "measure_dir_name": "TimeseriesCSVExport", - "arguments": { - "reporting_frequency": "Timestep", - "inc_output_variables": False - }, - "measure_type": "ReportingMeasure" - }, - { - "measure_dir_name": "comstock_sensitivity_reports", - "arguments": {}, - "measure_type": "ReportingMeasure" - }, - { - "measure_dir_name": "qoi_report", - "arguments": {}, - "measure_type": "ReportingMeasure" - } - ]) - - if workflow_args['include_qaqc']: - osw['steps'].extend([ - { - 'measure_dir_name': 'la_100_qaqc', - 'arguments': { - 'run_qaqc': True - }, - 'measure_type': 'ReportingMeasure' - }, - { - 'measure_dir_name': 'simulation_settings_check', - 'arguments': { - 'run_sim_settings_checks': True - }, - 'measure_type': 'ReportingMeasure' - } - ]) - + # Upgrades if upgrade_idx is not None: measure_d = self.cfg['upgrades'][upgrade_idx] apply_upgrade_measure = { @@ -195,4 +275,29 @@ def create_osw(self, sim_id, building_id, upgrade_idx): list(map(lambda x: x['measure_dir_name'] == 'BuildExistingModel', osw['steps'])).index(True) osw['steps'].insert(build_existing_model_idx + 1, apply_upgrade_measure) + # Always-added reporting measures + osw['steps'].extend([ + { + "measure_dir_name": "SimulationOutputReport", + "arguments": {}, + "measure_type": "ReportingMeasure" + }, + { + "measure_dir_name": "TimeseriesCSVExport", + "arguments": { + "reporting_frequency": "Timestep", + "inc_output_variables": False + }, + "measure_type": "ReportingMeasure" + } + ]) + + # User-specified reporting measures + if 'reporting_measures' in workflow_args: + for reporting_measure in workflow_args['reporting_measures']: + if 'arguments' not in reporting_measure: + reporting_measure['arguments'] = {} + reporting_measure['measure_type'] = 'ReportingMeasure' + osw['steps'].append(reporting_measure) + return osw diff --git a/buildstockbatch/workflow_generator/residential.py b/buildstockbatch/workflow_generator/residential.py index fb18551c..b2260b89 100644 --- a/buildstockbatch/workflow_generator/residential.py +++ b/buildstockbatch/workflow_generator/residential.py @@ -44,9 +44,9 @@ def validate(cls, cfg): measures_to_ignore: list(str(), required=False) residential_simulation_controls: map(required=False) measures: list(include('measure-spec'), required=False) + reporting_measures: list(include('measure-spec'), required=False) simulation_output: map(required=False) timeseries_csv_export: map(required=False) - reporting_measures: list(str(), required=False) --- measure-spec: measure_dir_name: str(required=True) @@ -59,6 +59,11 @@ def validate(cls, cfg): yamale.validate(schema, data, strict=True) return cls.validate_measures_and_arguments(cfg) + def reporting_measures(self): + """Return a list of reporting measures to include in outputs""" + workflow_args = self.cfg['workflow_generator'].get('args', {}) + return [x['measure_dir_name'] for x in workflow_args.get('reporting_measures', [])] + @staticmethod def validate_measures_and_arguments(cfg): @@ -102,7 +107,7 @@ def get_cfg_path(cfg_path): workflow_args = cfg['workflow_generator'].get('args', {}) if 'reporting_measures' in workflow_args.keys(): for reporting_measure in workflow_args['reporting_measures']: - measure_names[reporting_measure] = 'workflow_generator.args.reporting_measures' + measure_names[reporting_measure['measure_dir_name']] = 'workflow_generator.args.reporting_measures' error_msgs = '' warning_msgs = '' @@ -329,11 +334,10 @@ def create_osw(self, sim_id, building_id, upgrade_idx): osw['steps'].insert(-1, timeseries_measure) # right before ServerDirectoryCleanup if 'reporting_measures' in workflow_args: - for measure_dir_name in workflow_args['reporting_measures']: - reporting_measure = { - 'measure_dir_name': measure_dir_name, - 'arguments': {} - } + for reporting_measure in workflow_args['reporting_measures']: + if 'arguments' not in reporting_measure: + reporting_measure['arguments'] = {} + reporting_measure['measure_type'] = 'ReportingMeasure' osw['steps'].insert(-1, reporting_measure) # right before ServerDirectoryCleanup return osw diff --git a/buildstockbatch/workflow_generator/test_workflow_generator.py b/buildstockbatch/workflow_generator/test_workflow_generator.py index baca1469..a57be8b9 100644 --- a/buildstockbatch/workflow_generator/test_workflow_generator.py +++ b/buildstockbatch/workflow_generator/test_workflow_generator.py @@ -1,5 +1,6 @@ from buildstockbatch.workflow_generator.base import WorkflowGeneratorBase from buildstockbatch.workflow_generator.residential import ResidentialDefaultWorkflowGenerator +from buildstockbatch.workflow_generator.commercial import CommercialDefaultWorkflowGenerator def test_apply_logic_recursion(): @@ -171,18 +172,25 @@ def test_additional_reporting_measures(mocker): 'type': 'residential_default', 'args': { 'reporting_measures': [ - 'ReportingMeasure1', - 'ReportingMeasure2' + {'measure_dir_name': 'ReportingMeasure1'}, + {'measure_dir_name': 'ReportingMeasure2', 'arguments': {'arg1': 'asdf', 'arg2': 'jkl'}} ] } } } + ResidentialDefaultWorkflowGenerator.validate(cfg) osw_gen = ResidentialDefaultWorkflowGenerator(cfg, 10) osw = osw_gen.create_osw(sim_id, building_id, upgrade_idx) reporting_measure_1_step = osw['steps'][-3] assert(reporting_measure_1_step['measure_dir_name'] == 'ReportingMeasure1') + assert(reporting_measure_1_step['arguments'] == {}) + assert(reporting_measure_1_step['measure_type'] == 'ReportingMeasure') reporting_measure_2_step = osw['steps'][-2] assert(reporting_measure_2_step['measure_dir_name'] == 'ReportingMeasure2') + assert(reporting_measure_2_step['arguments']['arg1'] == 'asdf') + assert(reporting_measure_2_step['arguments']['arg2'] == 'jkl') + assert(len(reporting_measure_2_step['arguments'])) + assert(reporting_measure_2_step['measure_type'] == 'ReportingMeasure') def test_ignore_measures_argument(mocker): @@ -286,3 +294,37 @@ def test_simulation_output(mocker): args = osw['steps'][-2]['arguments'] for argname in ('include_enduse_subcategories',): assert(args[argname] != default_args[argname]) + + +def test_com_default_workflow_generator(mocker): + mocker.patch.object(CommercialDefaultWorkflowGenerator, 'validate_measures_and_arguments', return_value=True) + sim_id = 'bldb1up1' + building_id = 1 + upgrade_idx = None + cfg = { + 'baseline': { + 'n_buildings_represented': 100 + }, + 'workflow_generator': { + 'type': 'commercial_default', + 'args': { + 'reporting_measures': [ + {'measure_dir_name': 'ReportingMeasure1'}, + {'measure_dir_name': 'ReportingMeasure2', 'arguments': {'arg1': 'asdf', 'arg2': 'jkl'}} + ] + } + } + } + CommercialDefaultWorkflowGenerator.validate(cfg) + osw_gen = CommercialDefaultWorkflowGenerator(cfg, 10) + osw = osw_gen.create_osw(sim_id, building_id, upgrade_idx) + reporting_measure_1_step = osw['steps'][-2] + assert(reporting_measure_1_step['measure_dir_name'] == 'ReportingMeasure1') + assert(reporting_measure_1_step['arguments'] == {}) + assert(reporting_measure_1_step['measure_type'] == 'ReportingMeasure') + reporting_measure_2_step = osw['steps'][-1] + assert(reporting_measure_2_step['measure_dir_name'] == 'ReportingMeasure2') + assert(reporting_measure_2_step['arguments']['arg1'] == 'asdf') + assert(reporting_measure_2_step['arguments']['arg2'] == 'jkl') + assert(len(reporting_measure_2_step['arguments'])) + assert(reporting_measure_2_step['measure_type'] == 'ReportingMeasure') diff --git a/docs/changelog/changelog_dev.rst b/docs/changelog/changelog_dev.rst index 108d3970..51e3ad9d 100644 --- a/docs/changelog/changelog_dev.rst +++ b/docs/changelog/changelog_dev.rst @@ -60,3 +60,10 @@ Development Changelog The glue crawler was failing when there was a trailing ``/`` character. This fixes that as well as checks to make sure files were uploaded before running the crawler. + + .. change:: + :tags: workflow + :pullreq: + :tickets: + + Adding measure arguments for reporting measures in the workflow generator. \ No newline at end of file diff --git a/docs/changelog/migration_0_20.rst b/docs/changelog/migration_0_20.rst index c8aa46c7..fb98fc5d 100644 --- a/docs/changelog/migration_0_20.rst +++ b/docs/changelog/migration_0_20.rst @@ -169,6 +169,68 @@ New Spec: reporting_frequency: Hourly include_enduse_subcategories: true +Commercial Workflw Generator Hard-Coded Measures +------------------------------------------------ + +The commercial workflow generator has changed to remove most of the hard-coded +reporting measures, allowing them to be added to the config file as-needed. +This should avoid the need to create custom BuildStockBatch environments +for each project that needs to add/remove/modify reporting measures. + +Old hard-coded reporting measures: + +- SimulationOutputReport +- OpenStudio Results (measure_dir_name: f8e23017-894d-4bdf-977f-37e3961e6f42) +- TimeseriesCSVExport +- comstock_sensitivity_reports +- qoi_report +- la_100_qaqc (if include_qaqc = true in config) +- simulation_settings_check (if include_qaqc = true in config) + +New hard-coded reporting measures: + +- SimulationOutputReport (reports annual totals in results.csv) +- TimeseriesCSVExport (generates timeseries results at Timestep frequency) + +Two other hard-coded model measures were removed from the workflow. These will +be added to the workflow via the options-lookup.tsv in ComStock instead. + +Removed hard-coded model measures: + +- add_blinds_to_selected_windows +- set_space_type_load_subcategories + +Reporting Measures in Workflows +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``reporting_measures`` configuration key that now resides under ``workflow_generator.args`` +allows measure arguments to be passed to reporting measures. + +Old Spec: + +.. code-block:: yaml + + schema_version: 0.2 + stock_type: residential + reporting_measures: + - ReportingMeasure1 + - ReportingMeasure2 + +New Spec: + +.. code-block:: yaml + + schema_version: '0.3' + workflow_generator: + type: residential_default + args: + reporting_measures: + - measure_dir_name: ReportingMeasure1 + arguments: + arg1: value + - measure_dir_name: ReportingMeasure2 + + AWS EMR Configuration Name Changes ---------------------------------- diff --git a/docs/index.rst b/docs/index.rst index 9315e98b..1e3ac451 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -28,7 +28,7 @@ supercomputer, Eagle. Updates & Changelog =================== -:doc:`Migration from 0.18 to 0.19 ` +:doc:`Migration from 0.19 to 0.20 ` .. toctree:: :maxdepth: 2 diff --git a/docs/installation.rst b/docs/installation.rst index 0d491ffd..47a08ff5 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -45,6 +45,19 @@ Install the library by doing the following: .. _aws-user-config-local: +.. note:: + + Users using a Windows operating system with Python version 3.8 or higher may encounter the following + error when running simulations locally: + + `docker.errors.DockerException: Install pypiwin32 package to enable npipe:// support` + + Manually running the pywin32 post-install script using the following command may resolve the error: + + :: + + python \Scripts\pywin32_postinstall.py -install + AWS User Configuration ...................... diff --git a/docs/workflow_generators/commercial_default.rst b/docs/workflow_generators/commercial_default.rst index a4c60fa2..00c22ec2 100644 --- a/docs/workflow_generators/commercial_default.rst +++ b/docs/workflow_generators/commercial_default.rst @@ -9,25 +9,57 @@ Configuration Example workflow_generator: type: commercial_default args: - include_qaqc: true - measures: - - measure_dir_name: MyMeasure - arguments: - arg1: val1 - arg2: val2 - - measure_dir_name: MyOtherMeasure - arguments: - arg3: val3 + reporting_measures: + - measure_dir_name: f8e23017-894d-4bdf-977f-37e3961e6f42 # OpenStudio Results + arguments: + building_summary_section: true + annual_overview_section: true + monthly_overview_section: true + utility_bills_rates_section: true + envelope_section_section: true + space_type_breakdown_section: true + space_type_details_section: true + interior_lighting_section: true + plug_loads_section: true + exterior_light_section: true + water_use_section: true + hvac_load_profile: true + zone_condition_section: true + zone_summary_section: true + zone_equipment_detail_section: true + air_loops_detail_section: true + plant_loops_detail_section: true + outdoor_air_section: true + cost_summary_section: true + source_energy_section: true + schedules_overview_section: true + - measure_dir_name: comstock_sensitivity_reports + - measure_dir_name: qoi_report + - measure_dir_name: la_100_qaqc + arguments: + run_qaqc: true + - measure_dir_name: simulation_settings_check + arguments: + run_sim_settings_checks: true Arguments ~~~~~~~~~ - ``measures`` (optional): Add these optional measures to the end of your workflow. + Not typically used by ComStock. - ``measure_dir_name``: Name of measure directory. - ``arguments``: map of key, value arguments to pass to the measure. -- ``include_qaqc``: (optional), when set to ``True`` runs some additional - measures that check a number of key (and often incorrectly configured) part of - the simulation inputs as well as providing additional model QAQC data streams - on the output side. Recommended for test runs but not production analyses. +- ``reporting_measures`` (optional): Add these optional reporting measures to the end of your workflow. + + - ``measure_dir_name``: Name of measure directory. + - ``arguments``: map of key, value arguments to pass to the measure. + +.. note:: + + The registerValues created by the OpenStudio Results measure + (measure_dir_name: `f8e23017-894d-4bdf-977f-37e3961e6f42`, class name: `OpenStudioResults`) + have been hard-coded to be disabled in the workflow because most of the values are not useful in a + ComStock results.csv. If you wish to have these values show up, simply make a copy of the measure and + use a different class name in the measure.rb and measure.xml file. diff --git a/docs/workflow_generators/residential_default.rst b/docs/workflow_generators/residential_default.rst index 4085d357..27c24277 100644 --- a/docs/workflow_generators/residential_default.rst +++ b/docs/workflow_generators/residential_default.rst @@ -48,12 +48,16 @@ Arguments version of resstock you're using. The best thing you can do is to verify that it works with what is in your branch. -- ``reporting_measures`` (optional): a list of reporting measure names to apply - additional reporting measures (that require no arguments) to the workflow. Any - columns reported by these additional measures will be appended to the results - csv. Note: For upgrade runs, do not add ``ApplyUpgrade`` to the list of - reporting measures, doing so will cause run to fail prematurely. - ``ApplyUpgrade`` is applied automatically when the ``upgrades`` key is supplied. +- ``reporting_measures`` (optional): a list of reporting measures to apply + to the workflow. Any columns reported by these additional measures will be + appended to the results csv. Note: For upgrade runs, do not add + ``ApplyUpgrade`` to the list of reporting measures, doing so will cause run + to fail prematurely. ``ApplyUpgrade`` is applied automatically when the + ``upgrades`` key is supplied. + + - ``measure_dir_name``: Name of measure directory. + - ``arguments``: map of key, value arguments to pass to the measure. + .. _ResidentialSimulationControls: https://github.com/NREL/resstock/blob/master/measures/ResidentialSimulationControls/measure.xml .. _SimulationOutputReport: https://github.com/NREL/resstock/blob/master/measures/SimulationOutputReport/measure.xml