From 46c3d063d7f6f580930004d76835b232ce38f809 Mon Sep 17 00:00:00 2001 From: Petter <petter.kallstrom@liu.se> Date: Thu, 27 Apr 2023 15:23:21 +0200 Subject: [PATCH] Added support for plotting result from many simulations. #209 --- b_asic/gui_utils/plot_window.py | 104 +++++++++++++++++++++++++++++--- test/test_simulation_gui.py | 46 ++++++++++++++ 2 files changed, 141 insertions(+), 9 deletions(-) diff --git a/b_asic/gui_utils/plot_window.py b/b_asic/gui_utils/plot_window.py index c156411e..446c500e 100644 --- a/b_asic/gui_utils/plot_window.py +++ b/b_asic/gui_utils/plot_window.py @@ -23,6 +23,7 @@ from qtpy.QtWidgets import ( # QFrame,; QScrollArea,; QLineEdit,; QSizePolicy,; QWidget, ) +from b_asic import Simulation from b_asic.gui_utils.icons import get_icon from b_asic.operation import ResultKey from b_asic.types import Num @@ -30,12 +31,13 @@ from b_asic.types import Num class PlotWindow(QWidget): """ - Dialog for plotting the result of a simulation. + Dialog for plotting the result of simulations. Parameters ---------- sim_result : dict Simulation results of the form obtained from :attr:`~b_asic.simulation.Simulation.results`. + Alternative, sim_result can be a list, ['name1', sim_result1, 'name2', sim_result2, ...] sfg_name : str, optional The name of the SFG. parent : optional @@ -62,8 +64,60 @@ class PlotWindow(QWidget): self.setWindowTitle(title) self._auto_redraw = False + ########### Flattening sim_result, if it is a list of results ####### + # take: sim_result (possibly on form ['name1', simres1, 'name2', simres2, ...] + # generate: sim_result (dict) + if type(sim_result) == Simulation: + sim_result = sim_result._results + assert type(sim_result) == dict, TypeError( + "Parsing sim_result as a Simulation, but the _result seems broken." + ) + elif type(sim_result) == list: + new_result = dict() + nr = 0 # value number. Used for automatic names. + name = None + for element in sim_result: + if type(element) == str: + assert not name, Exception( + "Parsing sim_result as a list. Did you provide two names after" + " each other?" + ) + name = element + else: + if not name: + nr = nr + 1 + name = "(res" + str(nr) + ")" + if type(element) == dict: + res = element + elif type(element) == Simulation: + res = element._results + assert type(res) == dict, TypeError( + f"Parsing sim_result as a list. Result '{name}' is a" + " Simulation, and its _result seems broken." + ) + else: + raise TypeError( + "Parsing sim_result as a list. Supported results are dict" + f" and Simulation, but result '{name}' is {type(element)}" + ) + for key, result in res.items(): + if re.fullmatch("[0-9]+", key): # it's an output + key = "out" + key + new_result[name + "." + key] = result + name = None + assert not name, Exception( + "Parsing sim_result as a list. Did you provide a name as the last item" + " in the list?" + ) + sim_result = new_result + elif type(sim_result) != dict: + raise TypeError( + "sim_result must be a dict, Simulation or list. Found" + f" {type(sim_result)}" + ) + ########### Categorizing/sorting/renaming sim_results: ############## - # take: sim_results + # take: sim_result # generate: key_order, initially_checked # generate: updated_result initially_checked = [] @@ -72,12 +126,16 @@ class PlotWindow(QWidget): n = 0 for key, result in sim_result.items(): key2 = key # in most cases - if re.fullmatch("in[0-9]+", key): + if re.fullmatch(r"(.*\.)?in[0-9]+", key): m = 4 - elif re.fullmatch("[0-9]+", key): + elif re.fullmatch(r"[0-9]+", key): # It's an output as just a number. m = 3 - key2 = 'o' + key - elif re.fullmatch("t[0-9]+", key): + key2 = 'out' + key + elif re.fullmatch( + r"(.*\.)?out[0-9]+", key + ): # It's an output already formulated. + m = 3 + elif re.fullmatch(r"(.*\.)?t[0-9]+", key): m = 2 else: m = 1 @@ -130,8 +188,16 @@ class PlotWindow(QWidget): self._plot_axes.xaxis.set_major_locator(MaxNLocator(integer=True)) self._lines = {} + markers = ".ov<^>s+*xd|_" + fmt = '-' + ix = 0 # index bytes in markers for key in key_order: - line = self._plot_axes.plot(updated_result[key], label=key) + if len(updated_result[key]) <= 100: + fmt = markers[ix] + '-' + ix = (ix + 1) % len(markers) + else: + fmt = '-' + line = self._plot_axes.plot(updated_result[key], fmt, label=key) self._lines[key] = line[0] self._plot_canvas = FigureCanvas(self._plot_fig) @@ -254,6 +320,7 @@ def start_simulation_dialog( ---------- sim_results : dict Simulation results of the form obtained from :attr:`~b_asic.simulation.Simulation.results`. + Alternative, sim_result can be a list, ['name1', sim_result1, 'name2', sim_result2, ...] sfg_name : str, optional The name of the SFG. """ @@ -268,7 +335,19 @@ def start_simulation_dialog( # Simple test of the dialog if __name__ == "__main__": - sim_res = { + sim_res1 = { + '0': [1.5, 1.6, 1.5, 1], + '1': [1.0, 2.0, 1.5, 1.1], + 'add1': [1.5, 1.5, 1, 1], + 'cmul1': [1, 1.5, 1, 1], + 'cmul2': [1.5, 1, 1, 1], + 'in1': [2, 1, 1, 1], + 'in2': [1.1, 3, 1, 1], + 't1': [1, 2, 1, 1], + 't2': [1, 1, 2, 1], + 't3': [1, 1, 1, 2], + } + sim_res2 = { '0': [0.5, 0.6, 0.5, 0], '1': [0.0, 1.0 + 0.3j, 0.5, 0.1j], 'add1': [0.5, 0.5, 0, 0], @@ -280,4 +359,11 @@ if __name__ == "__main__": 't2': [0, 0, 1, 0], 't3': [0, 0, 0, 1], } - start_simulation_dialog(sim_res, "Test data") + sim_res3 = { + '0': np.random.rand(200).tolist(), + '1': np.random.rand(200).tolist(), + } + # start_simulation_dialog(sim_res3) + start_simulation_dialog( + ['simReal', sim_res1, 'simCpx', sim_res2, sim_res3], "Test data" + ) diff --git a/test/test_simulation_gui.py b/test/test_simulation_gui.py index cd7599e6..2f5ee5b1 100644 --- a/test/test_simulation_gui.py +++ b/test/test_simulation_gui.py @@ -28,6 +28,52 @@ def test_start_with_data(qtbot): assert len(widget._plot_axes.lines) == 1 +def test_start_with_complex_data(qtbot): + sim_res = { + '0': [0.5, 0.5, 0, 0], + '1': [0.5, 0.5 + 0.4j, 1, 0], + 'add1': [0.5, 0.5, 0, 0], + 'cmul1': [0, 0.5, 0, 0], + 'cmul2': [0.5, 0, 0, 0], + 'in1': [1, 0, 0, 0], + 't1': [0, 1, 0, 0], + } + widget = PlotWindow(sim_res) + qtbot.addWidget(widget) + + assert widget._checklist.count() == 10 + assert len(widget._plot_axes.lines) == 3 + + +def test_start_with_multi_data(qtbot): + sim_res1 = { + '0': [1.5, 1.6, 1.5, 1], + '1': [1.0, 2.0, 1.5, 1.1], + 'add1': [1.5, 1.5, 1, 1], + 'cmul1': [1, 1.5, 1, 1], + 'in1': [2, 1, 1, 1], + 't3': [1, 1, 1, 2], + } + sim_res2 = { + '1': [ + 0.0, + 1.0 + 0.3j, + 0.5, + 0.1j, + ], # Each complex generates four signals, whereof two are shown (for outs). + 'add1': [0.5, 0.5, 0, 0], + 'cmul1': [0, 0.5, 0, 0], + 'cmul2': [0.5, 0, 0, 0], + 'in1': [1, 0, 0, 0], + 'in2': [0.1, 2, 0, 0], + } + widget = PlotWindow(['simReal', sim_res1, 'simCpx', sim_res2], "Test vector") + qtbot.addWidget(widget) + + assert widget._checklist.count() == 15 + assert len(widget._plot_axes.lines) == 4 + + def test_click_buttons(qtbot): sim_res = { '0': [0.5, 0.5, 0, 0], -- GitLab