From 4c7002ac98b9d86a9e5b102021d521bf732a15b3 Mon Sep 17 00:00:00 2001 From: Paul Saxe Date: Mon, 10 Jul 2023 19:24:08 -0400 Subject: [PATCH] Store non-scalar results as JSON in tables and the database. --- HISTORY.rst | 4 ++- seamm/node.py | 80 +++++++++++++++++++++++++++++++++++++++--------- seamm/tk_node.py | 38 ++++++++++------------- 3 files changed, 85 insertions(+), 37 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 5f38186..af013d1 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,7 +1,9 @@ ======= History ======= -2023.7.9 -- Bugfix: errors creating dashboards and projects +2023.7.10 -- Adding JSON for properties in the database and tabels; bugfixes + * Handle non-scalar results using JSON so they can be output to tables + and added to the properties in the database. * Fixed error submitting jobs to Dashboard the user doesn't have a login for. * Ask for credentials when adding a new dashboard to job dialog. * Fixed bug creating a new project. diff --git a/seamm/node.py b/seamm/node.py index 7411356..506bc2c 100644 --- a/seamm/node.py +++ b/seamm/node.py @@ -36,6 +36,17 @@ job = printing.getPrinter() +def scale(data, factor): + """Recursive helper to scale e.g. nested lists by a factor.""" + result = [] + for value in data: + if isinstance(value, list): + result.append(scale(value, factor)) + else: + result.append(value * factor) + return result + + class Node(collections.abc.Hashable): """The base class for nodes (steps) in flowcharts. @@ -1018,12 +1029,30 @@ def store_results( if "units" in result_metadata: current_units = result_metadata["units"] if units != current_units: - tmp = Q_(data[key], current_units) - properties.put(_property, tmp.m_as(units)) + if result_metadata["dimensionality"] == "scalar": + tmp = Q_(data[key], current_units) + properties.put(_property, tmp.m_as(units)) + else: + factor = Q_(1, current_units).m_as(units) + tmp = scale(data[key], factor) + properties.put( + _property, json.dumps(tmp, separators=(",", ":")) + ) else: - properties.put(_property, data[key]) + if result_metadata["dimensionality"] == "scalar": + properties.put(_property, data[key]) + else: + properties.put( + _property, + json.dumps(data[key], separators=(",", ":")), + ) else: - properties.put(_property, data[key]) + if result_metadata["dimensionality"] == "scalar": + properties.put(_property, data[key]) + else: + properties.put( + _property, json.dumps(data[key], separators=(",", ":")) + ) # Store in a variable if "variable" in value: @@ -1077,14 +1106,18 @@ def store_results( # create the column as needed if column not in table.columns: - kind = result_metadata["type"] - if kind == "boolean": - default = False - elif kind == "integer": - default = 0 - elif kind == "float": - default = np.nan + if result_metadata["dimensionality"] == "scalar": + kind = result_metadata["type"] + if kind == "boolean": + default = False + elif kind == "integer": + default = 0 + elif kind == "float": + default = np.nan + else: + default = "" else: + kind = "json" default = "" table_handle["defaults"][column] = default @@ -1097,14 +1130,31 @@ def store_results( if "units" in result_metadata: current_units = result_metadata["units"] if units != current_units: - tmp = Q_(data[key], current_units) - table.at[row_index, column] = tmp.m_as(units) + if result_metadata["dimensionality"] == "scalar": + tmp = Q_(data[key], current_units) + table.at[row_index, column] = tmp.m_as(units) + else: + factor = Q_(1, current_units).m_as(units) + tmp = scale(data[key], factor) + table.at[row_index, column] = json.dumps( + tmp, separators=(",", ":") + ) else: - table.at[row_index, column] = data[key] + if result_metadata["dimensionality"] == "scalar": + table.at[row_index, column] = data[key] + else: + table.at[row_index, column] = json.dumps( + data[key], separators=(",", ":") + ) else: raise RuntimeError("Problem with units handling results!") else: - table.at[row_index, column] = data[key] + if result_metadata["dimensionality"] == "scalar": + table.at[row_index, column] = data[key] + else: + table.at[row_index, column] = json.dumps( + data[key], separators=(",", ":") + ) def create_figure(self, title="", template="line.graph_template", module_path=None): """Create a new figure. diff --git a/seamm/tk_node.py b/seamm/tk_node.py index 75380b3..37c4a12 100644 --- a/seamm/tk_node.py +++ b/seamm/tk_node.py @@ -1034,27 +1034,23 @@ def setup_results(self): e.delete(0, tk.END) e.insert(0, results[key]["variable"]) - if entry["dimensionality"] == "scalar": - # table - w = ttk.Combobox(frame, width=10, values=["", *self._tables]) - table.cell(row, 4, w) - widgets.append(w) - w.bind("<>", self._table_cb) - w.bind("", self._table_cb) - w.bind("", self._table_cb) - e = ttk.Entry(frame, width=15) - e.insert(0, key.lower().replace("_", " ")) - table.cell(row, 5, e) - widgets.append(e) - - if key in results: - if "table" in results[key]: - w.set(results[key]["table"]) - e.delete(0, tk.END) - e.insert(0, results[key]["column"]) - else: - widgets.append(None) - widgets.append(None) + # table + w = ttk.Combobox(frame, width=10, values=["", *self._tables]) + table.cell(row, 4, w) + widgets.append(w) + w.bind("<>", self._table_cb) + w.bind("", self._table_cb) + w.bind("", self._table_cb) + e = ttk.Entry(frame, width=15) + e.insert(0, key.lower().replace("_", " ")) + table.cell(row, 5, e) + widgets.append(e) + + if key in results: + if "table" in results[key]: + w.set(results[key]["table"]) + e.delete(0, tk.END) + e.insert(0, results[key]["column"]) # And units.... if "units" in entry and entry["units"] != "":