From 10b6cc2f012c988aceee1c4fd0a89556da471aa4 Mon Sep 17 00:00:00 2001 From: Noel Merket Date: Thu, 15 Apr 2021 16:25:21 -0600 Subject: [PATCH 01/12] wiring up reporting measures to the workflow generator --- buildstockbatch/aws/aws.py | 2 +- buildstockbatch/base.py | 6 ++++++ buildstockbatch/eagle.py | 2 +- buildstockbatch/localdocker.py | 2 +- buildstockbatch/postprocessing.py | 10 ++-------- buildstockbatch/workflow_generator/base.py | 7 +++++++ buildstockbatch/workflow_generator/commercial.py | 6 ++++++ buildstockbatch/workflow_generator/residential.py | 4 ++++ 8 files changed, 28 insertions(+), 11 deletions(-) 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..4f02e032 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 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/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..c74c3ec9 100644 --- a/buildstockbatch/workflow_generator/commercial.py +++ b/buildstockbatch/workflow_generator/commercial.py @@ -32,6 +32,7 @@ def validate(cls, cfg): """ schema_yml = """ measures: list(include('measure-spec'), required=False) + reporting_measures: list(include('measure-spec'), required=False) include_qaqc: bool(required=False) --- measure-spec: @@ -44,6 +45,11 @@ 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', {}) + return [x['measure_dir_name'] for x in workflow_args.get('reporting_measures', [])] + def create_osw(self, sim_id, building_id, upgrade_idx): """ Generate and return the osw as a python dict diff --git a/buildstockbatch/workflow_generator/residential.py b/buildstockbatch/workflow_generator/residential.py index fb18551c..01e95856 100644 --- a/buildstockbatch/workflow_generator/residential.py +++ b/buildstockbatch/workflow_generator/residential.py @@ -59,6 +59,10 @@ 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""" + return self.cfg['workflow_generator'].get('args', {}).get('reporting_measures', []) + @staticmethod def validate_measures_and_arguments(cfg): From ef00524aa6c7fac5d50c4e4501f5a4cc5a387f6b Mon Sep 17 00:00:00 2001 From: Noel Merket Date: Thu, 15 Apr 2021 16:46:43 -0600 Subject: [PATCH 02/12] adding optional arguments to the reporting measures for residential --- .../enforce-validate-measures-bad-2.yml | 2 +- .../enforce-validate-measures-good-2.yml | 2 +- buildstockbatch/workflow_generator/commercial.py | 1 + .../workflow_generator/residential.py | 16 ++++++++-------- .../test_workflow_generator.py | 11 +++++++++-- 5 files changed, 20 insertions(+), 12 deletions(-) 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/commercial.py b/buildstockbatch/workflow_generator/commercial.py index c74c3ec9..12b95789 100644 --- a/buildstockbatch/workflow_generator/commercial.py +++ b/buildstockbatch/workflow_generator/commercial.py @@ -152,6 +152,7 @@ def create_osw(self, sim_id, building_id, upgrade_idx): } ]) + # FIXME: Insert the reporting measures somewhere around here if workflow_args['include_qaqc']: osw['steps'].extend([ { diff --git a/buildstockbatch/workflow_generator/residential.py b/buildstockbatch/workflow_generator/residential.py index 01e95856..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) @@ -61,7 +61,8 @@ def validate(cls, cfg): def reporting_measures(self): """Return a list of reporting measures to include in outputs""" - return self.cfg['workflow_generator'].get('args', {}).get('reporting_measures', []) + 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): @@ -106,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 = '' @@ -333,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..a3d6759e 100644 --- a/buildstockbatch/workflow_generator/test_workflow_generator.py +++ b/buildstockbatch/workflow_generator/test_workflow_generator.py @@ -171,18 +171,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): From dc2cbe61a9ca1b40d8c35b448c3f4753cbfc6dd0 Mon Sep 17 00:00:00 2001 From: Noel Merket Date: Thu, 15 Apr 2021 17:01:57 -0600 Subject: [PATCH 03/12] updating docs --- docs/changelog/changelog_dev.rst | 7 +++++ docs/changelog/migration_0_20.rst | 31 +++++++++++++++++++ docs/index.rst | 2 +- .../residential_default.rst | 16 ++++++---- 4 files changed, 49 insertions(+), 7 deletions(-) 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..62f55dd8 100644 --- a/docs/changelog/migration_0_20.rst +++ b/docs/changelog/migration_0_20.rst @@ -169,6 +169,37 @@ New Spec: reporting_frequency: Hourly include_enduse_subcategories: true +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/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 From 22b77fa630af05b5e25331d48dedc992d6271172 Mon Sep 17 00:00:00 2001 From: Andrew Parker Date: Thu, 22 Apr 2021 15:27:32 -0600 Subject: [PATCH 04/12] Removes hard-coded reporting measures from commercial workflow generator --- .../workflow_generator/commercial.py | 103 +++++------------- 1 file changed, 28 insertions(+), 75 deletions(-) diff --git a/buildstockbatch/workflow_generator/commercial.py b/buildstockbatch/workflow_generator/commercial.py index 12b95789..0e10684a 100644 --- a/buildstockbatch/workflow_generator/commercial.py +++ b/buildstockbatch/workflow_generator/commercial.py @@ -33,7 +33,6 @@ def validate(cls, cfg): schema_yml = """ measures: list(include('measure-spec'), required=False) reporting_measures: list(include('measure-spec'), required=False) - include_qaqc: bool(required=False) --- measure-spec: measure_dir_name: str(required=True) @@ -61,8 +60,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', {})) @@ -97,80 +95,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" - } - ]) - - # FIXME: Insert the reporting measures somewhere around here - 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 = { @@ -202,4 +130,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 From 0a9a9199cb2e598feeced84aadcbe5a90a26a6f2 Mon Sep 17 00:00:00 2001 From: Andrew Parker Date: Fri, 23 Apr 2021 22:26:16 -0600 Subject: [PATCH 05/12] Makes com reporting measures pull class name from measure.xml and adds validation --- .../workflow_generator/commercial.py | 161 +++++++++++++++++- 1 file changed, 160 insertions(+), 1 deletion(-) diff --git a/buildstockbatch/workflow_generator/commercial.py b/buildstockbatch/workflow_generator/commercial.py index 0e10684a..fa844330 100644 --- a/buildstockbatch/workflow_generator/commercial.py +++ b/buildstockbatch/workflow_generator/commercial.py @@ -10,17 +10,27 @@ :license: BSD-3 """ +from collections import Counter 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 @@ -47,7 +57,156 @@ def validate(cls, 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', [])] + + # 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): """ From 2e86f44bc82f5964062fde03b34bbcb1f5c47454 Mon Sep 17 00:00:00 2001 From: Andrew Parker Date: Fri, 23 Apr 2021 22:27:21 -0600 Subject: [PATCH 06/12] Adds com workflow generator test --- .../test_workflow_generator.py | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/buildstockbatch/workflow_generator/test_workflow_generator.py b/buildstockbatch/workflow_generator/test_workflow_generator.py index a3d6759e..a9691abd 100644 --- a/buildstockbatch/workflow_generator/test_workflow_generator.py +++ b/buildstockbatch/workflow_generator/test_workflow_generator.py @@ -1,6 +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(): @@ -293,3 +293,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') From 3ddc6af6913b2ade19ee018b55c989e4ff7ac659 Mon Sep 17 00:00:00 2001 From: Andrew Parker Date: Fri, 23 Apr 2021 22:27:50 -0600 Subject: [PATCH 07/12] Adds note for windows-specific dependency error and fix --- docs/installation.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) 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 ...................... From 82552eda1c35968a4e77c58cba176736c9772041 Mon Sep 17 00:00:00 2001 From: Andrew Parker Date: Mon, 26 Apr 2021 06:13:04 -0600 Subject: [PATCH 08/12] Removes hard-coded model measures from commercial workflow generator --- buildstockbatch/workflow_generator/commercial.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/buildstockbatch/workflow_generator/commercial.py b/buildstockbatch/workflow_generator/commercial.py index fa844330..3d1f1ed2 100644 --- a/buildstockbatch/workflow_generator/commercial.py +++ b/buildstockbatch/workflow_generator/commercial.py @@ -233,18 +233,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(), From 782d1f4fd828fe0ab56392db1635ff4e111763aa Mon Sep 17 00:00:00 2001 From: Andrew Parker Date: Mon, 26 Apr 2021 06:31:36 -0600 Subject: [PATCH 09/12] Updates com workflow generator docs and associated migration docs --- docs/changelog/migration_0_20.rst | 31 ++++++++++ .../commercial_default.rst | 58 ++++++++++++++----- 2 files changed, 76 insertions(+), 13 deletions(-) diff --git a/docs/changelog/migration_0_20.rst b/docs/changelog/migration_0_20.rst index 62f55dd8..fb98fc5d 100644 --- a/docs/changelog/migration_0_20.rst +++ b/docs/changelog/migration_0_20.rst @@ -169,6 +169,37 @@ 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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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. From 3d421d10972a3ab0f574821e6307b58daf1805e4 Mon Sep 17 00:00:00 2001 From: Andrew Parker Date: Mon, 26 Apr 2021 13:03:26 -0600 Subject: [PATCH 10/12] Fixes flake8 errors in com workflow changes --- buildstockbatch/workflow_generator/commercial.py | 2 -- buildstockbatch/workflow_generator/test_workflow_generator.py | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/buildstockbatch/workflow_generator/commercial.py b/buildstockbatch/workflow_generator/commercial.py index 3d1f1ed2..5d0ab739 100644 --- a/buildstockbatch/workflow_generator/commercial.py +++ b/buildstockbatch/workflow_generator/commercial.py @@ -10,7 +10,6 @@ :license: BSD-3 """ -from collections import Counter import datetime as dt import json import logging @@ -75,7 +74,6 @@ def reporting_measures(self): return measure_class_names - @staticmethod def validate_measures_and_arguments(cfg): diff --git a/buildstockbatch/workflow_generator/test_workflow_generator.py b/buildstockbatch/workflow_generator/test_workflow_generator.py index a9691abd..a57be8b9 100644 --- a/buildstockbatch/workflow_generator/test_workflow_generator.py +++ b/buildstockbatch/workflow_generator/test_workflow_generator.py @@ -2,6 +2,7 @@ from buildstockbatch.workflow_generator.residential import ResidentialDefaultWorkflowGenerator from buildstockbatch.workflow_generator.commercial import CommercialDefaultWorkflowGenerator + def test_apply_logic_recursion(): apply_logic = WorkflowGeneratorBase.make_apply_logic_arg(['one', 'two', 'three']) From a659efed027f891d736e90a5c8656063085173e8 Mon Sep 17 00:00:00 2001 From: Noel Merket Date: Mon, 26 Apr 2021 13:46:16 -0600 Subject: [PATCH 11/12] fixing messed up mock in tests --- buildstockbatch/test/test_eagle.py | 1 + 1 file changed, 1 insertion(+) 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) From 645c5f53110670223cb90838f053a8060ebc4c71 Mon Sep 17 00:00:00 2001 From: asparke2 Date: Tue, 27 Apr 2021 05:44:26 -0600 Subject: [PATCH 12/12] Fixes missing optional eagle key crash Eagle key is optional per schema, but this code crashed if it was missing from the config file. --- buildstockbatch/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildstockbatch/base.py b/buildstockbatch/base.py index 4f02e032..125e4e15 100644 --- a/buildstockbatch/base.py +++ b/buildstockbatch/base.py @@ -557,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: