diff --git a/buildingspy/CHANGES.txt b/buildingspy/CHANGES.txt index 5ae70327..9b48d7c6 100644 --- a/buildingspy/CHANGES.txt +++ b/buildingspy/CHANGES.txt @@ -1,6 +1,13 @@ BuildingsPy Changelog --------------------- +Version 5.2.1, ... +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Updated pyfunnel to version 2.0.1 to fix plotting issues. + With this change, Python 2 is no longer supported. + (https://github.com/lbl-srg/BuildingsPy/issues/599) + Version 5.2.0, March 14, 2025 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/buildingspy/VERSION b/buildingspy/VERSION index 91ff5727..26d99a28 100644 --- a/buildingspy/VERSION +++ b/buildingspy/VERSION @@ -1 +1 @@ -5.2.0 +5.2.1 diff --git a/buildingspy/development/regressiontest.py b/buildingspy/development/regressiontest.py index e4a3375a..6d42a587 100644 --- a/buildingspy/development/regressiontest.py +++ b/buildingspy/development/regressiontest.py @@ -7,10 +7,6 @@ # MWetter@lbl.gov 2011-02-23 ####################################################### # -from collections import defaultdict -from contextlib import contextmanager -import difflib -import fnmatch import functools import glob import io @@ -24,19 +20,24 @@ import sys import tempfile import time -import webbrowser +from collections import defaultdict +from contextlib import contextmanager + # Third-party module or package imports. import numpy as np -import simplejson + # Code repository sub-package imports. import pyfunnel -from buildingspy.development import error_dictionary_openmodelica -from buildingspy.development import error_dictionary_optimica -from buildingspy.development import error_dictionary_dymola -from buildingspy.io.outputfile import Reader -from buildingspy.io.postprocess import Plotter + import buildingspy.io.outputfile as of import buildingspy.io.reporter as rep +from buildingspy.development import ( + error_dictionary_dymola, + error_dictionary_openmodelica, + error_dictionary_optimica, +) +from buildingspy.io.outputfile import Reader +from buildingspy.io.postprocess import Plotter def runSimulation(worDir, cmd): @@ -2237,6 +2238,13 @@ def _compareResults(self, data_idx, oldRefFulFilNam, y_sim, y_tra, refFilNam, an newStatistics = self._check_statistics( old_results, y_tra, stage, newTrajectories, newStatistics, model_name) + if newTrajectories: + if self._comp_tool == 'legacy': + print("(Close plot window to continue.)") + self._legacy_plot(y_sim, t_ref, y_ref, noOldResults, timOfMaxErr, matFilNam) + else: + self._funnel_plot(model_name) + # If the users selected "Y" or "N" (to not accept or reject any new results) in previous tests, # or if the script is run in batch mode, then don't plot the results. # If we found an error, plot the results, and ask the user to accept or @@ -2256,13 +2264,6 @@ def _compareResults(self, data_idx, oldRefFulFilNam, y_sim, y_tra, refFilNam, an print( f" update reference files with new {self._color_BOLD}trajectories{self._color_ERROR}?{self._color_ENDC}") - if newTrajectories: - if self._comp_tool == 'legacy': - print("(Close plot window to continue.)") - self._legacy_plot(y_sim, t_ref, y_ref, noOldResults, timOfMaxErr, matFilNam) - else: - self._funnel_plot(model_name) - while not (ans == "n" or ans == "y" or ans == "Y" or ans == "N"): ans = input(" Enter: y(yes), n(no), Y(yes for all), N(no for all): ") @@ -2311,7 +2312,7 @@ def _funnel_plot(self, model_name, browser=None): server = pyfunnel.MyHTTPServer(('', 0), pyfunnel.CORSRequestHandler, str_html=content, url_html='funnel') # Start the browser instance. - server.browse(list_files, browser=browser) + server.browse(list_files, browser=browser, timeout=60) def _legacy_plot(self, y_sim, t_ref, y_ref, noOldResults, timOfMaxErr, model_name): """Plot comparison results generated by legacy comparison algorithm.""" @@ -3880,7 +3881,7 @@ def _get(model, key, data): return 0 with open(self._statistics_log, 'r') as f: - staVal = simplejson.loads(f.read()) + staVal = json.loads(f.read()) data = [] for case in staVal['testCase']: if 'translate' in case: @@ -3902,7 +3903,7 @@ def _get(model, key, data): temp['simulation']['state_events'] = case['simulate']['state_events'] if 'state_events' in case['simulate'] else 0 temp['simulation']['success'] = case['simulate']['result'] data.append(temp) - dataJson = simplejson.dumps(data) + dataJson = json.dumps(data) return dataJson def run(self): @@ -4135,7 +4136,7 @@ def run(self): # For Dymola: store available simulation info into # self._comp_info used for reporting. val = self._run_simulation_info() - self._comp_info = simplejson.loads(val) + self._comp_info = json.loads(val) r = self._checkReferencePoints(ans) if r != 0: # In case of comparison error. Comparison warnings are handled @@ -4153,8 +4154,8 @@ def run(self): if not self._skip_verification or self._rewrite_configuration_file: # For OpenModelica and OPTIMICA: store available translation and simulation info # into self._comp_info used for reporting or for rewriting the configuration file. - with open(self._simulator_log_file, 'r') as f: - self._comp_info = simplejson.loads(f.read()) + with open(self._simulator_log_file, 'r', encoding='utf-8-sig') as f: + self._comp_info = json.loads(f.read()) if not self._skip_verification: r = self._checkReferencePoints(ans='N') diff --git a/buildingspy/templates/datatable.html b/buildingspy/templates/datatable.html index 2a3e9f64..053a0c7b 100755 --- a/buildingspy/templates/datatable.html +++ b/buildingspy/templates/datatable.html @@ -1,92 +1,93 @@ - - - - - - - - - + .dataTable tbody td { + word-break: break-word; + vertical-align: top; + } - -
-
- -
-
-

-
-
-
-
-
-
-
- - - - - - - - - - -
ModelVariablesSuccessResults Filecomp_dirs
-

Color Legend

-

Translation, simulation or extracting simulation results failed: see message in - alert box.

-

Funnel comparison failed: see message in alert box.

-

Result verification detected error, see plot.

-

Test passed.

-
-
-
+ .table-responsive { + max-width: 90%; + margin-left: auto; + margin-right: auto; + overflow-x: visible; + } + + + + + - - - + var table2 = $("#myTable2").DataTable({ + autoWidth: false, + aLengthMenu: [ + [10, 50, 100, -1], + [10, 50, 100, "All"], + ], + iDisplayLength: -1, + ajax: { + url: "$SIMULATOR_LOG", + dataSrc: "", + }, + columns: [ + { + data: "model", + width: "30%", + render: function (data, type, full) { + return data.replace(/\./g, "."); + }, + }, + { + data: "translation.cpu_time", + render: function (data, type, row) { + if (data === undefined || data === null) { + return ""; + } + return $.fn.dataTable.render.number(" ", ".", 2).display(data); + }, + }, + { + data: "translation.success", + render: function (data, type, row) { + return data !== undefined ? data : ""; + }, + }, + { + data: "translation.warnings", + width: "40%", + render: function (data, type, row) { + return data || ""; + }, + }, + ], + }); + } + var table3 = $("#myTable3").DataTable({ + autoWidth: false, + aLengthMenu: [ + [10, 50, 100, -1], + [10, 50, 100, "All"], + ], + iDisplayLength: -1, + ajax: { + url: "$SIMULATOR_LOG", + dataSrc: "", + }, + columns: [ + { + data: "model", + width: "30%", + render: function (data, type, full) { + return data.replace(/\./g, "."); + }, + }, + { + data: "comparison.variables", + width: "60%", + render: function (data, type, row, meta) { + var out = ""; + if (typeof data !== "undefined") { + out = data.map(function (e, var_idx) { + var warnings = table3.cell(meta.row, 6).data(); + var color; + if (warnings[0] != null && warnings[0].includes("failed")) { + color = "red"; + } else if (warnings[var_idx] != null) { + if (warnings[var_idx].includes("not found in")) { + color = "red"; + } else if (warnings[var_idx].includes("While processing file")) { + color = "brown"; + } else if (warnings[var_idx].includes("Absolute error")) { + color = "orange"; + } + } + if (color != null) { + return " " + e.fontcolor(color); + } else { + return " " + e; + } + }); + } else { + out = "No simulation results available."; + } + return out; + }, + }, + { + data: "comparison.success_rate", + render: function (data, type, full) { + var out = ""; + if (typeof data !== "undefined") { + out = data.toLocaleString("en-US", { style: "percent" }); + } else { + out = "0%"; + } + return '' + out + ""; + }, + }, + { + data: "comparison.file_name", + render: function (data, type, row) { + return data || ""; + }, + }, + { + data: "comparison.funnel_dirs", + render: function (data, type, row) { + return data || []; + }, + }, + { + data: "comparison.var_groups", + render: function (data, type, row) { + return data || []; + }, + }, + { + data: "comparison.warnings", + render: function (data, type, row) { + return data || []; + }, + }, + ], + columnDefs: [{ visible: false, searchable: false, targets: [3, 4, 5, 6] }], + }); + $("#myTable3").on("click", ".myClass", function () { + var tr = $(this).closest("tr"); + var row_idx = table3.row(tr).index(); + var model = table3.cell(row_idx, 0).data(); + var variables = table3.cell(row_idx, 1).data(); + var file = table3.cell(row_idx, 3).data(); + var dirs = table3.cell(row_idx, 4).data(); + var groups = table3.cell(row_idx, 5).data(); + var warnings = table3.cell(row_idx, 6).data(); + var avail_groups = []; + var navail_vars = []; + var dict_var_info = {}; // dict of array of dicts (to handle multiple plots for one variable) + if (variables != null) { + variables.forEach(function (v, idx) { + if (dirs[idx] != null) { + (dict_var_info[v] = dict_var_info[v] || []).push({ group: groups[idx], dir: dirs[idx] }); + avail_groups.push(groups[idx]); + } else { + navail_vars.push(v); + } + }); + } else { + alert("Funnel comparison could not be executed for this file."); + } + var uniq_groups = [...new Set(avail_groups)]; + var max_plot_per100 = 4; + var height = 100 * (1 + Math.max(0, uniq_groups.length - max_plot_per100) / max_plot_per100); + var err_plot_height = (0.18 * 100) / height; + // Use jQuery's AJAX with timeout and error handling + $.ajax({ + url: "$COMP_DIR/plot.html", + timeout: 5000, + success: function (data) { + if (uniq_groups.length > 0) { + var title = file; + if (navail_vars.length > 0) { + title = title.concat("
No available results for: ").concat(navail_vars.join(", ")); + } + var new_data = data.replace("$DICT_VAR_INFO", JSON.stringify(dict_var_info)); + new_data = new_data.replace("$PAGE_TITLE", title); + new_data = new_data.replace("$TITLE", model); + new_data = new_data.replace("$HEIGHT", height + "%"); + new_data = new_data.replace("$ERR_PLOT_HEIGHT", err_plot_height); + var strWindowFeatures = "menubar=yes, location=yes, resizable=yes, scrollbars=yes, status=yes"; + // Create a Blob containing the HTML content + var blob = new Blob([new_data], { type: "text/html" }); + // Create a URL for the Blob + var blobUrl = URL.createObjectURL(blob); + // Open a new window with the Blob URL + var win = window.open(blobUrl, "_blank", strWindowFeatures); + // Clean up the Blob URL once the window has loaded + if (win) { + win.addEventListener("load", function () { + URL.revokeObjectURL(blobUrl); + }); + } + } else { + alert(warnings.join("\n")); + } + }, + error: function (jqXHR, textStatus, errorThrown) { + console.error("Error loading plot.html:", textStatus, errorThrown); + }, + }); + }); + }); + diff --git a/buildingspy/templates/plot.html b/buildingspy/templates/plot.html index 9a30f66e..bcfea1eb 100644 --- a/buildingspy/templates/plot.html +++ b/buildingspy/templates/plot.html @@ -1,282 +1,370 @@ - - + + $PAGE_TITLE - - - + + + - -
+ +
- - + \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 1f3b570d..8c0b092c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,13 +5,10 @@ gitpython>=3.1.33; python_version>='3.8' --only-binary numpy==1.21.4; python_version>='3.8' --only-binary scipy==1.7.3; python_version>='3.8' pytidylib==0.3.2 -simplejson==3.16.0 -six==1.14.0 -pyfunnel==1.0.2 + +pyfunnel==2.0.1 PyYAML==6.0.1 cerberus==1.3.4 # For documentation and testing. -setuptools==70.0.0; python_version>='3.8' -wheel==0.38.1 autopep8==1.6 diff --git a/setup.py b/setup.py index e27ba806..c76cd5d0 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ import io -import platform import os + from setuptools import setup # Python setup file. @@ -32,6 +32,10 @@ keywords="modelica dymola openmodelica mat", url="http://simulationresearch.lbl.gov/modelica/", python_requires='>=3.8', + setup_requires=[ + 'setuptools>=70.0.0', + 'wheel>=0.37.0', + ], install_requires=[ 'gitpython>=3.1.33', 'jinja2>=3.1.4', @@ -39,9 +43,7 @@ 'numpy>=1.21', 'scipy>=1.7', 'pytidylib>=0.3.2', - 'simplejson>=3.16', - 'six>=1.14', - 'pyfunnel>=1.0.2', + 'pyfunnel>=2.0.1', 'PyYAML>=6.0.1', 'cerberus>=1.3.4', ],