diff --git a/README.md b/README.md index dde040088..788cf4ef1 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ Example RESTful interaction: | Receive control signal point names (u) and metadata. | GET ``inputs`` | | Receive test result data for the given point names between the start and final time in seconds. | PUT ``results`` with required arguments ``point_names=``, ``start_time=``, ``final_time=``| | Receive test KPIs. | GET ``kpi`` | +| Receive test KPIs disaggregated into contributing components (e.g. each equipment or zone) ...| GET ``kpi_disaggregated`` | | Receive test case name. | GET ``name`` | | Receive boundary condition forecast from current communication step for the given point names for the horizon and at the interval in seconds. | PUT ``forecast`` with required arguments ``point_names=``, ``horizon=``, ``interval=``| | Receive boundary condition forecast available point names and metadata. | GET ``forecast_points`` | diff --git a/docs/design/source/core_kpi.rst b/docs/design/source/core_kpi.rst index b51dacdb2..72be58e08 100644 --- a/docs/design/source/core_kpi.rst +++ b/docs/design/source/core_kpi.rst @@ -227,6 +227,27 @@ Maximum allowed capital cost relies on the BOPTEST user, who can use the objective quantification of this KPI to take the decision. +Core KPIs disaggregation +--------------------------- + +The main purpose of the core KPIs is to enable benchmarking across different controllers. +That is the why these KPIs are provided in an aggregated format with all contributions +from each source added up to a unique value which is normlized by floor area or number of zones. +Returning a unique value facilitates comparisons between controllers. +However, there may be some cases where getting all contributions from each source separately +could be useful to gain insights and anlyze results. +We might be interested, for example, in what is the specific contribution of a pump and +a heat pump to the overal energy use of a building to decide whether we can +neglect the pump operation in our controller logic. + +The GET ´´kpi_disaggregated´´ API call gives this information as it returns the value of the core +KPIs disaggregated by the contribution of each source element. +The returned results are in absolute values, that is, they are not normalized by floor area or +by number of zones. +In the case of peak power KPIs it should be noted that what is returned is the contribution of +each element to the total peak when it is reached (instead of providing the peak power of each +individual element separately). + Calculation Module --------------------- diff --git a/kpis/kpi_calculator.py b/kpis/kpi_calculator.py index a86cbf8e4..60e2d6ac5 100644 --- a/kpis/kpi_calculator.py +++ b/kpis/kpi_calculator.py @@ -215,6 +215,40 @@ def get_core_kpis(self, price_scenario='Constant'): return ckpi + def get_kpis_disaggregated(self, price_scenario='Constant'): + '''Return the core KPIs of a test case disaggregated and + with absolute values (not normalized by area or zone) + to see the contributions of each element to each KPI. + + Parameters + ---------- + price_scenario : str, optional + Price scenario for cost kpi calculation. + 'Constant' or 'Dynamic' or 'HighlyDynamic'. + Default is 'Constant'. + + Returns + ------- + dkpi = dict + Dictionary with the core KPIs disaggregated and + with absolute values. + + ''' + + _ = self.get_core_kpis(price_scenario=price_scenario) + + dkpi = OrderedDict() + dkpi['tdis'] = self.tdis_dict + dkpi['idis'] = self.idis_dict + dkpi['ener'] = self.ener_dict + dkpi['cost'] = self.cost_dict + dkpi['emis'] = self.emis_dict + dkpi['pele'] = self.pele_dict + dkpi['pgas'] = self.pgas_dict + dkpi['pdih'] = self.pdih_dict + + return dkpi + def get_thermal_discomfort(self): '''The thermal discomfort is the integral of the deviation of the temperature with respect to the predefined comfort @@ -333,11 +367,10 @@ def get_energy(self): if 'Power' in source: for signal in self.case.kpi_json[source]: pow_data = np.array(self._get_data_from_last_index(signal,self.i_last_ener)) - self.ener_dict[signal] += \ - trapz(pow_data, - self._get_data_from_last_index('time',self.i_last_ener))*2.77778e-7 # Convert to kWh - self.ener_dict_by_source[source+'_'+signal] += \ - self.ener_dict[signal] + integral = trapz(pow_data, + self._get_data_from_last_index('time',self.i_last_ener))*2.77778e-7 # Convert to kWh + self.ener_dict[signal] += integral + self.ener_dict_by_source[source+'_'+signal] += integral self.ener_tot = self.ener_tot + self.ener_dict[signal]/self.case._get_area() # Normalize total by floor area # Assign to case @@ -382,10 +415,10 @@ def get_peak_electricity(self): df_pow_data_all = pd.concat([df_pow_data_all, df_pow_data], axis=1) df_pow_data_all.index = pd.TimedeltaIndex(df_pow_data_all.index, unit='s') df_pow_data_all['total_demand'] = df_pow_data_all.sum(axis=1) - df_pow_data_all = df_pow_data_all.resample('15T').mean()/self.case._get_area()/1000. + df_pow_data_all = df_pow_data_all.resample('15T').mean()/1000. i = df_pow_data_all['total_demand'].idxmax() peak = df_pow_data_all.loc[i,'total_demand'] - self.pele_tot = peak + self.pele_tot = peak/self.case._get_area() # Find contributions to peak by each signal for signal in self.case.kpi_json[source]: self.pele_dict[signal] = df_pow_data_all.loc[i,signal] @@ -429,10 +462,10 @@ def get_peak_gas(self): df_pow_data_all = pd.concat([df_pow_data_all, df_pow_data], axis=1) df_pow_data_all.index = pd.TimedeltaIndex(df_pow_data_all.index, unit='s') df_pow_data_all['total_demand'] = df_pow_data_all.sum(axis=1) - df_pow_data_all = df_pow_data_all.resample('15T').mean()/self.case._get_area()/1000. + df_pow_data_all = df_pow_data_all.resample('15T').mean()/1000. i = df_pow_data_all['total_demand'].idxmax() peak = df_pow_data_all.loc[i,'total_demand'] - self.pgas_tot = peak + self.pgas_tot = peak/self.case._get_area() # Find contributions to peak by each signal for signal in self.case.kpi_json[source]: self.pgas_dict[signal] = df_pow_data_all.loc[i,signal] @@ -476,10 +509,10 @@ def get_peak_district_heating(self): df_pow_data_all = pd.concat([df_pow_data_all, df_pow_data], axis=1) df_pow_data_all.index = pd.TimedeltaIndex(df_pow_data_all.index, unit='s') df_pow_data_all['total_demand'] = df_pow_data_all.sum(axis=1) - df_pow_data_all = df_pow_data_all.resample('15T').mean()/self.case._get_area()/1000. + df_pow_data_all = df_pow_data_all.resample('15T').mean()/1000. i = df_pow_data_all['total_demand'].idxmax() peak = df_pow_data_all.loc[i,'total_demand'] - self.pdih_tot = peak + self.pdih_tot = peak/self.case._get_area() # Find contributions to peak by each signal for signal in self.case.kpi_json[source]: self.pdih_dict[signal] = df_pow_data_all.loc[i,signal] @@ -541,11 +574,10 @@ def get_cost(self, scenario='Constant'): # Calculate costs for signal in self.case.kpi_json[source]: pow_data = np.array(self._get_data_from_last_index(signal,self.i_last_cost)) - self.cost_dict[signal] += \ - trapz(np.multiply(source_price_data,pow_data), + integral = trapz(np.multiply(source_price_data,pow_data), self._get_data_from_last_index('time',self.i_last_cost))*factor - self.cost_dict_by_source[source+'_'+signal] += \ - self.cost_dict[signal] + self.cost_dict[signal] += integral + self.cost_dict_by_source[source+'_'+signal] += integral self.cost_tot = self.cost_tot + self.cost_dict[signal]/self.case._get_area() # Normalize total by floor area # Assign to case @@ -585,11 +617,10 @@ def get_emissions(self): ['Emissions'+source]) for signal in self.case.kpi_json[source]: pow_data = np.array(self._get_data_from_last_index(signal,self.i_last_emis)) - self.emis_dict[signal] += \ - trapz(np.multiply(source_emissions_data,pow_data), + integral = trapz(np.multiply(source_emissions_data,pow_data), self._get_data_from_last_index('time',self.i_last_emis))*2.77778e-7 # Convert to kWh - self.emis_dict_by_source[source+'_'+signal] += \ - self.emis_dict[signal] + self.emis_dict[signal] += integral + self.emis_dict_by_source[source+'_'+signal] += integral self.emis_tot = self.emis_tot + self.emis_dict[signal]/self.case._get_area() # Normalize total by floor area # Update last integration index diff --git a/releasenotes.md b/releasenotes.md index 6b83b8ec7..6b4948a89 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -13,6 +13,7 @@ Released on xx/xx/xxxx. - Specify version of scipy to 1.13.0 in test case Dockerfile. This is for [#657](https://github.com/ibpsa/project1-boptest/issues/657). - Remove javascript controller example. This is for [#664](https://github.com/ibpsa/project1-boptest/issues/664). - Add a new directory ``/baselines``, containing baseline testing scripts and associated KPI results for the baseline controllers of all the testcases. This is for [#495](https://github.com/ibpsa/project1-boptest/issues/495). +- Implement method to get disaggregated KPIs with absolute values. This enables to make a more comprehensive analysis of which elements are contributing to each KPI. This is for [#604](https://github.com/ibpsa/project1-boptest/issues/604). ## BOPTEST v0.6.0 @@ -33,7 +34,6 @@ Released on 04/03/2024. - Add ``activate`` control inputs to all test case documentation and update ``get_html_IO.py`` to print one file with all inputs, outputs, and forecasts. This is for [#555](https://github.com/ibpsa/project1-boptest/issues/555). - Add storing of scenario result trajectories, kpis, and test information to simulation directory within test case docker container. This is for [#626](https://github.com/ibpsa/project1-boptest/issues/626). - ## BOPTEST v0.5.0 Released on 10/04/2023. diff --git a/restapi.py b/restapi.py index 50f6655c1..5f89f1643 100644 --- a/restapi.py +++ b/restapi.py @@ -191,6 +191,13 @@ def get(self): status, message, payload = case.get_kpis() return construct(status, message, payload) +class KPI_Disaggregated(Resource): + '''Interface to test case KPIs disaggregated and with absolute values.''' + + def get(self): + '''GET request to receive KPIs disaggregated and with absolute values.''' + status, message, payload = case.get_kpis_disaggregated() + return construct(status, message, payload) class Forecast(Resource): '''Interface to test case forecast data.''' @@ -267,6 +274,7 @@ def post(self): api.add_resource(Forecast_Points, '/forecast_points') api.add_resource(Results, '/results') api.add_resource(KPI, '/kpi') +api.add_resource(KPI_Disaggregated, '/kpi_disaggregated') api.add_resource(Forecast, '/forecast') api.add_resource(Scenario, '/scenario') api.add_resource(Name, '/name') diff --git a/testcase.py b/testcase.py index 1c23c6457..53d466157 100644 --- a/testcase.py +++ b/testcase.py @@ -792,6 +792,50 @@ def get_kpis(self): return status, message, payload + def get_kpis_disaggregated(self): + '''Returns KPIs disaggregated and with absolute values. + Requires standard sensor signals. + + Parameters + ---------- + None + + Returns + ------- + status: int + Indicates whether a request for querying the KPIs has been completed. + If 200, the KPIs were successfully queried. + If 500, an internal error occured. + message: str + Includes detailed debugging information + payload : dict + Dictionary containing KPIs disaggregated and with absolute values. + {:} + Returns None if error during calculation. + + ''' + + status = 200 + message = "Queried disaggregated KPIs successfully." + try: + # Set correct price scenario for cost + if self.scenario['electricity_price'] == 'constant': + price_scenario = 'Constant' + elif self.scenario['electricity_price'] == 'dynamic': + price_scenario = 'Dynamic' + elif self.scenario['electricity_price'] == 'highly_dynamic': + price_scenario = 'HighlyDynamic' + # Calculate the disaggregated kpis + payload = self.cal.get_kpis_disaggregated(price_scenario=price_scenario) + except: + payload = None + status = 500 + message = "Failed to query disaggregated KPIs: {}".format(traceback.format_exc()) + logging.error(message) + logging.info(message) + + return status, message, payload + def get_forecast_points(self): '''Returns a dictionary of available forecast points and their meta-data. diff --git a/testing/references/bestest_hydronic/submit.json b/testing/references/bestest_hydronic/submit.json index d8bc463af..aff0c3a99 100644 --- a/testing/references/bestest_hydronic/submit.json +++ b/testing/references/bestest_hydronic/submit.json @@ -1 +1 @@ -{"dash_url": "https://dashboard.boptest.net//api/results", "payload": {"results": [{"account": {"apiKey": "valid_api_key"}, "boptestVersion": "0.6.0-dev\n", "buildingType": {"uid": "bestest_hydronic"}, "controlStep": "86400.0", "dateRun": "2020-05-17 00:00:00", "forecastParameters": {}, "isShared": true, "kpis": {"cost_tot": 0.4660775943925745, "emis_tot": 1.6291465071977262, "ener_tot": 9.00349952561408, "idis_tot": 0.0, "pdih_tot": null, "pele_tot": 0.00025517153990852024, "pgas_tot": 0.11798036181564837, "tdis_tot": 18.21783776691252, "time_rat": 0}, "scenario": {"electricityPrice": "dynamic", "timePeriod": "peak_heat_day", "weatherForecastUncertainty": "deterministic"}, "tags": ["baseline", "unit_test"], "uid": "1"}]}} +{"dash_url": "https://dashboard.boptest.net//api/results", "payload": {"results": [{"account": {"apiKey": "valid_api_key"}, "boptestVersion": "0.6.0-dev\n", "buildingType": {"uid": "bestest_hydronic"}, "controlStep": "86400.0", "dateRun": "2020-05-17 00:00:00", "forecastParameters": {}, "isShared": true, "kpis": {"cost_tot": 0.4660775943925745, "emis_tot": 1.6291465071977262, "ener_tot": 9.00349952561408, "idis_tot": 0.0, "pdih_tot": null, "pele_tot": 0.0002551715399085203, "pgas_tot": 0.11798036181564837, "tdis_tot": 18.21783776691252, "time_rat": 0}, "scenario": {"electricityPrice": "dynamic", "timePeriod": "peak_heat_day", "weatherForecastUncertainty": "deterministic"}, "tags": ["baseline", "unit_test"], "uid": "1"}]}} diff --git a/testing/references/kpis/all_ckpis_MultiZone.csv b/testing/references/kpis/all_ckpis_MultiZone.csv new file mode 100644 index 000000000..0a2794d7e --- /dev/null +++ b/testing/references/kpis/all_ckpis_MultiZone.csv @@ -0,0 +1 @@ +{"tdis_tot": 10.866116056527034, "idis_tot": 515.1749482042578, "ener_tot": 2.1905882633882148, "cost_tot": 0.15334117843717504, "emis_tot": 0.438117652677643, "pele_tot": null, "pgas_tot": 0.10097014409038281, "pdih_tot": null, "time_rat": null} \ No newline at end of file diff --git a/testing/references/kpis/all_ckpis_SingleZone.csv b/testing/references/kpis/all_ckpis_SingleZone.csv new file mode 100644 index 000000000..f15d06a2e --- /dev/null +++ b/testing/references/kpis/all_ckpis_SingleZone.csv @@ -0,0 +1 @@ +{"tdis_tot": 6.044280949869132, "idis_tot": 365.6911873402533, "ener_tot": 3.06717186709752, "cost_tot": 0.613434373419504, "emis_tot": 1.53358593354876, "pele_tot": 0.11118336992336571, "pgas_tot": null, "pdih_tot": null, "time_rat": null} \ No newline at end of file diff --git a/testing/references/kpis/all_dkpis_MultiZone.csv b/testing/references/kpis/all_dkpis_MultiZone.csv new file mode 100644 index 000000000..90d03775b --- /dev/null +++ b/testing/references/kpis/all_dkpis_MultiZone.csv @@ -0,0 +1 @@ +{"tdis": {"TRooAirSou_dTlower_y": 6.861372185606556, "TRooAirSou_dTupper_y": 3.771997257241026, "TRooAirNor_dTlower_y": 8.2055849580535, "TRooAirNor_dTupper_y": 2.893277712152985}, "idis": {"CO2RooAirSou_dIupper_y": 1016.9440316099603, "CO2RooAirNor_dIupper_y": 13.40586479855533}, "ener": {"PHeaNor_y": 22.33656655950071, "PHeaSou_y": 21.475198708263587}, "cost": {"PHeaNor_y": 1.5635596591650494, "PHeaSou_y": 1.503263909578451}, "emis": {"PHeaNor_y": 4.467313311900141, "PHeaSou_y": 4.295039741652719}, "pele": null, "pgas": {"PHeaNor_y": 1.057801489003348, "PHeaSou_y": 0.9616013928043511}, "pdih": null} \ No newline at end of file diff --git a/testing/references/kpis/all_dkpis_SingleZone.csv b/testing/references/kpis/all_dkpis_SingleZone.csv new file mode 100644 index 000000000..d8524a17c --- /dev/null +++ b/testing/references/kpis/all_dkpis_SingleZone.csv @@ -0,0 +1 @@ +{"tdis": {"TRooAir_dTlower_y": 5.174733104689715, "TRooAir_dTupper_y": 0.8695478451794166}, "idis": {"CO2RooAir_dIupper_y": 365.6911873402533}, "ener": {"PCoo_y": 2.5790120993548213, "PFan_y": 1.2243750151212227, "PHea_y": 143.38535826054658, "PPum_y": 0.035504245658337034}, "cost": {"PCoo_y": 0.5158024198709642, "PFan_y": 0.2448750030242446, "PHea_y": 28.677071652109316, "PPum_y": 0.007100849131667407}, "emis": {"PCoo_y": 1.2895060496774107, "PFan_y": 0.6121875075606114, "PHea_y": 71.69267913027329, "PPum_y": 0.017752122829168517}, "pele": {"PCoo_y": 0.0, "PFan_y": 0.005231953892668188, "PHea_y": 5.33156980242889, "PPum_y": 0.0}, "pgas": null, "pdih": null} \ No newline at end of file diff --git a/testing/references/kpis/pele_dict_SingleZone.csv b/testing/references/kpis/pele_dict_SingleZone.csv index 3521a3477..38e467ed1 100644 --- a/testing/references/kpis/pele_dict_SingleZone.csv +++ b/testing/references/kpis/pele_dict_SingleZone.csv @@ -1,5 +1,5 @@ keys,value PCoo_y,0.0 -PFan_y,0.000108999039431 -PHea_y,0.111074370884 +PFan_y,0.00523195389267 +PHea_y,5.33156980243 PPum_y,0.0 diff --git a/testing/references/kpis/pgas_dict_MultiZone.csv b/testing/references/kpis/pgas_dict_MultiZone.csv index efdef174c..23a9b4be7 100644 --- a/testing/references/kpis/pgas_dict_MultiZone.csv +++ b/testing/references/kpis/pgas_dict_MultiZone.csv @@ -1,3 +1,3 @@ keys,value -PHeaNor_y,0.0528900744502 -PHeaSou_y,0.0480800696402 +PHeaNor_y,1.057801489 +PHeaSou_y,0.961601392804 diff --git a/testing/references/multizone_residential_hydronic/submit.json b/testing/references/multizone_residential_hydronic/submit.json index fd37a4837..d23d689b4 100644 --- a/testing/references/multizone_residential_hydronic/submit.json +++ b/testing/references/multizone_residential_hydronic/submit.json @@ -1 +1 @@ -{"dash_url": "https://dashboard.boptest.net//api/results", "payload": {"results": [{"account": {"apiKey": "valid_api_key"}, "boptestVersion": "0.6.0-dev\n", "buildingType": {"uid": "multizone_residential_hydronic"}, "controlStep": "86400.0", "dateRun": "2020-05-17 00:00:00", "forecastParameters": {}, "isShared": true, "kpis": {"cost_tot": 0.7911663939870216, "emis_tot": 1.4254044254077105, "ener_tot": 8.140200481262813, "idis_tot": 9114.558476792468, "pdih_tot": null, "pele_tot": 0.0017390231869758264, "pgas_tot": 0.09592720495532532, "tdis_tot": 22.00196096128404, "time_rat": 0}, "scenario": {"electricityPrice": "dynamic", "timePeriod": "peak_heat_day", "weatherForecastUncertainty": "deterministic"}, "tags": ["baseline", "unit_test"], "uid": "1"}]}} +{"dash_url": "https://dashboard.boptest.net//api/results", "payload": {"results": [{"account": {"apiKey": "valid_api_key"}, "boptestVersion": "0.6.0-dev\n", "buildingType": {"uid": "multizone_residential_hydronic"}, "controlStep": "86400.0", "dateRun": "2020-05-17 00:00:00", "forecastParameters": {}, "isShared": true, "kpis": {"cost_tot": 0.7911663939870216, "emis_tot": 1.4254044254077105, "ener_tot": 8.140200481262813, "idis_tot": 9114.558476792468, "pdih_tot": null, "pele_tot": 0.0017390231869758262, "pgas_tot": 0.09592720495532533, "tdis_tot": 22.00196096128404, "time_rat": 0}, "scenario": {"electricityPrice": "dynamic", "timePeriod": "peak_heat_day", "weatherForecastUncertainty": "deterministic"}, "tags": ["baseline", "unit_test"], "uid": "1"}]}} diff --git a/testing/references/testcase3/submit.json b/testing/references/testcase3/submit.json index 81b3ebed4..2272860cd 100644 --- a/testing/references/testcase3/submit.json +++ b/testing/references/testcase3/submit.json @@ -1 +1 @@ -{"dash_url": "https://dashboard.boptest.net//api/results", "payload": {"results": [{"account": {"apiKey": "valid_api_key"}, "boptestVersion": "0.6.0-dev\n", "buildingType": {"uid": "testcase3"}, "controlStep": "86400.0", "dateRun": "2020-05-17 00:00:00", "forecastParameters": {}, "isShared": true, "kpis": {"cost_tot": 0.9674593335412667, "emis_tot": 2.7641695244036195, "ener_tot": 13.820847622018096, "idis_tot": 3606.22674562425, "pdih_tot": null, "pele_tot": null, "pgas_tot": 0.12017492256526957, "tdis_tot": 443.7163025810678, "time_rat": 0}, "scenario": {"electricityPrice": "dynamic", "timePeriod": "test_day", "weatherForecastUncertainty": "deterministic"}, "tags": ["baseline", "unit_test"], "uid": "1"}]}} +{"dash_url": "https://dashboard.boptest.net//api/results", "payload": {"results": [{"account": {"apiKey": "valid_api_key"}, "boptestVersion": "0.6.0-dev\n", "buildingType": {"uid": "testcase3"}, "controlStep": "86400.0", "dateRun": "2020-05-17 00:00:00", "forecastParameters": {}, "isShared": true, "kpis": {"cost_tot": 0.9674593335412667, "emis_tot": 2.7641695244036195, "ener_tot": 13.820847622018096, "idis_tot": 3606.22674562425, "pdih_tot": null, "pele_tot": null, "pgas_tot": 0.12017492256526958, "tdis_tot": 443.7163025810678, "time_rat": 0}, "scenario": {"electricityPrice": "dynamic", "timePeriod": "test_day", "weatherForecastUncertainty": "deterministic"}, "tags": ["baseline", "unit_test"], "uid": "1"}]}} diff --git a/testing/test_kpis.py b/testing/test_kpis.py index 1fac92732..d8695f103 100644 --- a/testing/test_kpis.py +++ b/testing/test_kpis.py @@ -262,6 +262,36 @@ def test_change_scenario_with_warmup(self): # Check results self._perform_test(self.case.cost_tot, self.case.cost_dict, 'cost_warmup_highly_dynamic') + def test_get_kpis(self): + '''Check for getting all core KPIs. + + ''' + + # Reset kpi calculator + self.cal.initialize() + + # Calculate all core KPIs + ckpis = self.cal.get_core_kpis() + + # Check results + ref_filepath = os.path.join(utilities.get_root_path(), 'testing', 'references', 'kpis', 'all_ckpis_{0}.csv'.format(self.name)) + self.compare_ref_json(ckpis, ref_filepath) + + def test_get_kpis_disaggregated(self): + '''Check for getting all core KPIs in a disaggregated format. + + ''' + + # Reset kpi calculator + self.cal.initialize() + + # Calculate all core KPIs + dkpis = self.cal.get_kpis_disaggregated() + + # Check disaggregated results + ref_filepath = os.path.join(utilities.get_root_path(), 'testing', 'references', 'kpis', 'all_dkpis_{0}.csv'.format(self.name)) + self.compare_ref_json(dkpis, ref_filepath) + def _perform_test(self, tot, dictionary, label): '''Common function for performing the tests. diff --git a/testing/utilities.py b/testing/utilities.py index 688ba9325..67163ba84 100644 --- a/testing/utilities.py +++ b/testing/utilities.py @@ -210,12 +210,15 @@ def compare_ref_json(self, json_test, ref_filepath): ''' + # Allow unlimited diff output + self.maxDiff = None + # Perform test if os.path.exists(ref_filepath): # If reference exists, check it with open(ref_filepath, 'r') as f: json_ref = json.load(f) - self.assertTrue(json_test==json_ref, 'json_test:\n{0}\ndoes not equal\njson_ref:\n{1}'.format(json_test, json_ref)) + self.assertEqual(json_test,json_ref, 'json_test:\n{0}\ndoes not equal\njson_ref:\n{1}'.format(json_test, json_ref)) else: # Otherwise, save as reference with open(ref_filepath, 'w') as f: