diff --git a/b_asic/GUI/main_window.py b/b_asic/GUI/main_window.py index 1f97c2c9454816805322a094672e95a8531c97da..dcf10df8792d5544a77c5acb6fbdbe7626f2dd2c 100644 --- a/b_asic/GUI/main_window.py +++ b/b_asic/GUI/main_window.py @@ -40,7 +40,7 @@ from b_asic.GUI.show_pc_window import ShowPCWindow # from b_asic.GUI.simulate_sfg_window import Plot, SimulateSFGWindow from b_asic.GUI.simulate_sfg_window import SimulateSFGWindow -from b_asic.GUI.util_dialogs import FaqWindow, KeybindsWindow +from b_asic.GUI.util_dialogs import FaqWindow, KeybindingsWindow from b_asic.GUI.utils import decorate_class, handle_error from b_asic.gui_utils.about_window import AboutWindow from b_asic.gui_utils.plot_window import PlotWindow @@ -83,7 +83,21 @@ class MainWindow(QMainWindow): self.sfg_dict = {} self._window = self self.logger = logging.getLogger(__name__) - self.init_ui() + + # Create Graphics View + self.graphic_view = QGraphicsView(self.scene, self) + self.graphic_view.setRenderHint(QPainter.Antialiasing) + self.graphic_view.setGeometry( + self.ui.operation_box.width(), 20, self.width(), self.height() + ) + self.graphic_view.setDragMode(QGraphicsView.RubberBandDrag) + + # Create toolbar + self.toolbar = self.addToolBar("Toolbar") + self.toolbar.addAction("Create SFG", self.create_sfg_from_toolbar) + self.toolbar.addAction("Clear workspace", self.clear_workspace) + + # Add operations self.add_operations_from_namespace( b_asic.core_operations, self.ui.core_operations_list ) @@ -110,7 +124,7 @@ class MainWindow(QMainWindow): self.ui.actionSimulateSFG.triggered.connect(self.simulate_sfg) self.ui.faqBASIC.triggered.connect(self.display_faq_page) self.ui.aboutBASIC.triggered.connect(self.display_about_page) - self.ui.keybindsBASIC.triggered.connect(self.display_keybinds_page) + self.ui.keybindsBASIC.triggered.connect(self.display_keybindings_page) self.ui.core_operations_list.itemClicked.connect( self.on_list_widget_item_clicked ) @@ -133,6 +147,10 @@ class MainWindow(QMainWindow): self.shortcut_signal = QShortcut(QKeySequence(Qt.Key_Space), self) self.shortcut_signal.activated.connect(self._connect_callback) + self._keybindings_page = None + self._about_page = None + self._faq_page = None + self.logger.info("Finished setting up GUI") self.logger.info( "For questions please refer to 'Ctrl+?', or visit the 'Help' " @@ -141,23 +159,6 @@ class MainWindow(QMainWindow): self.cursor = QCursor() - def init_ui(self) -> None: - self.create_toolbar_view() - self.create_graphics_view() - - def create_graphics_view(self) -> None: - self.graphic_view = QGraphicsView(self.scene, self) - self.graphic_view.setRenderHint(QPainter.Antialiasing) - self.graphic_view.setGeometry( - self.ui.operation_box.width(), 20, self.width(), self.height() - ) - self.graphic_view.setDragMode(QGraphicsView.RubberBandDrag) - - def create_toolbar_view(self) -> None: - self.toolbar = self.addToolBar("Toolbar") - self.toolbar.addAction("Create SFG", self.create_sfg_from_toolbar) - self.toolbar.addAction("Clear workspace", self.clear_workspace) - def resizeEvent(self, event) -> None: ui_width = self.ui.operation_box.width() self.ui.operation_box.setGeometry(10, 10, ui_width, self.height()) @@ -565,8 +566,8 @@ class MainWindow(QMainWindow): def _create_operation_item(self, item) -> None: self.logger.info("Creating operation of type: %s" % str(item.text())) try: - attr_oper = self._operations_from_name[item.text()]() - self.create_operation(attr_oper) + attr_operation = self._operations_from_name[item.text()]() + self.create_operation(attr_operation) except Exception as e: self.logger.error( "Unexpected error occurred while creating operation: " + str(e) @@ -712,16 +713,19 @@ class MainWindow(QMainWindow): self._simulation_dialog.simulate.connect(self._simulate_sfg) def display_faq_page(self, event=None) -> None: - self._faq_page = FaqWindow(self) + if self._faq_page is None: + self._faq_page = FaqWindow(self) self._faq_page.scroll_area.show() def display_about_page(self, event=None) -> None: - self._about_page = AboutWindow(self) + if self._about_page is None: + self._about_page = AboutWindow(self) self._about_page.show() - def display_keybinds_page(self, event=None) -> None: - self._keybinds_page = KeybindsWindow(self) - self._keybinds_page.show() + def display_keybindings_page(self, event=None) -> None: + if self._keybindings_page is None: + self._keybindings_page = KeybindingsWindow(self) + self._keybindings_page.show() def start_gui(): diff --git a/b_asic/GUI/util_dialogs.py b/b_asic/GUI/util_dialogs.py index e5a8d3f39fc828f0b08f540f5ebbb7eb7acf3ca9..f7f148a364a1f468fcca6f68b9fabf1fa38dac1e 100644 --- a/b_asic/GUI/util_dialogs.py +++ b/b_asic/GUI/util_dialogs.py @@ -65,7 +65,7 @@ _QUESTIONS = { } -class KeybindsWindow(QDialog): +class KeybindingsWindow(QDialog): def __init__(self, window): super().__init__() self._window = window @@ -88,7 +88,7 @@ class KeybindsWindow(QDialog): frame.setFrameShadow(QFrame.Sunken) self.dialog_layout.addWidget(frame) - keybinds_label = QLabel( + keybindings_label = QLabel( "'Ctrl+R' - Reload the operation list to add any new operations " "created.\n" "'Ctrl+Q' - Quit the application.\n" @@ -104,7 +104,7 @@ class KeybindsWindow(QDialog): self.dialog_layout.addLayout(information_layout) self.dialog_layout.addWidget(frame) - self.dialog_layout.addWidget(keybinds_label) + self.dialog_layout.addWidget(keybindings_label) class FaqWindow(QDialog): diff --git a/b_asic/core_operations.py b/b_asic/core_operations.py index 34b6c7d0d384392c7d2140d3a4fa50eaff1c71cb..958b3e258ab3ba7c121fc8afa04be3d0fe94f668 100644 --- a/b_asic/core_operations.py +++ b/b_asic/core_operations.py @@ -360,7 +360,7 @@ class Multiplication(AbstractOperation): @property def is_linear(self) -> bool: return any( - input.connected_source.operation.is_constant for input in self.inputs + input_.connected_source.operation.is_constant for input_ in self.inputs ) diff --git a/b_asic/graph_component.py b/b_asic/graph_component.py index f4725892dbaf3c7fcc9173f42283471d2d20cb46..46d26a80e94c21f56766ca2cdd4070a979844dd2 100644 --- a/b_asic/graph_component.py +++ b/b_asic/graph_component.py @@ -130,10 +130,7 @@ class AbstractGraphComponent(GraphComponent): f"id: {self.graph_id if self.graph_id else 'no_id'}, \tname:" f" {self.name if self.name else 'no_name'}" + "".join( - ( - f", \t{key}: {str(param)}" - for key, param in self._parameters.items() - ) + (f", \t{key}: {str(param)}" for key, param in self._parameters.items()) ) ) @@ -176,12 +173,12 @@ class AbstractGraphComponent(GraphComponent): def traverse(self) -> Generator[GraphComponent, None, None]: # Breadth first search. visited = {self} - fontier = deque([self]) - while fontier: - component = fontier.popleft() + frontier = deque([self]) + while frontier: + component = frontier.popleft() yield component for neighbor in component.neighbors: neighbor = cast(AbstractGraphComponent, neighbor) if neighbor not in visited: visited.add(neighbor) - fontier.append(neighbor) + frontier.append(neighbor) diff --git a/b_asic/gui_utils/plot_window.py b/b_asic/gui_utils/plot_window.py index 8f4b33493276b786382f29b47f4eeb2ea52ed154..d33033eed7e650679d7574923eae121075364cce 100644 --- a/b_asic/gui_utils/plot_window.py +++ b/b_asic/gui_utils/plot_window.py @@ -1,20 +1,14 @@ """PlotWindow is a window in which simulation results are plotted.""" -# TODO's: -# * Solve the legend update. That isn't working at all. -# * Add a function to run this as a "stand-alone". - import re import sys -from typing import Dict, List, Optional, Tuple +from typing import Dict, List, Optional from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar from matplotlib.figure import Figure from matplotlib.ticker import MaxNLocator from qtpy.QtCore import Qt - -# Intereme imports for the Plot class: from qtpy.QtWidgets import ( # QFrame,; QScrollArea,; QLineEdit,; QSizePolicy,; QLabel,; QFileDialog,; QShortcut, QApplication, QCheckBox, @@ -144,18 +138,18 @@ class PlotWindow(QDialog): # listlayout.addWidget(self.ontop_checkbox) # Add "Close" buttons - buttonClose = QPushButton("&Close", self) - buttonClose.clicked.connect(self.close) - listlayout.addWidget(buttonClose) + button_close = QPushButton("&Close", self) + button_close.clicked.connect(self.close) + listlayout.addWidget(button_close) # Done. Tell the functions below to redraw the canvas when needed. # self.plotcanvas.draw() self._auto_redraw = True - def _legend_checkbox_change(self, checkState): - self._legend.set(visible=(checkState == Qt.CheckState.Checked)) + def _legend_checkbox_change(self, check_state): + self._legend.set(visible=(check_state == Qt.CheckState.Checked)) if self._auto_redraw: - if checkState == Qt.CheckState.Checked: + if check_state == Qt.CheckState.Checked: self._legend = self._plot_axes.legend() self._plot_canvas.draw() diff --git a/b_asic/operation.py b/b_asic/operation.py index 3762c1b467eb41f34f5f027ef2762b98175f38df..673fb4c5dcd71f68a6046fb24ecd8328e61661ba 100644 --- a/b_asic/operation.py +++ b/b_asic/operation.py @@ -856,9 +856,9 @@ class AbstractOperation(Operation, AbstractGraphComponent): operation_copy: Operation = cast(Operation, self.copy_component()) inputs = [] for i in range(self.input_count): - _input = Input() - operation_copy.input(i).connect(_input) - inputs.append(_input) + input_ = Input() + operation_copy.input(i).connect(input_) + inputs.append(input_) outputs = [Output(operation_copy)] @@ -868,8 +868,8 @@ class AbstractOperation(Operation, AbstractGraphComponent): new_component: Operation = cast( Operation, super().copy_component(*args, **kwargs) ) - for i, input in enumerate(self.inputs): - new_component.input(i).latency_offset = input.latency_offset + for i, _input in enumerate(self.inputs): + new_component.input(i).latency_offset = _input.latency_offset for i, output in enumerate(self.outputs): new_component.output(i).latency_offset = output.latency_offset new_component.execution_time = self._execution_time @@ -974,8 +974,8 @@ class AbstractOperation(Operation, AbstractGraphComponent): return max( ( - (cast(int, output.latency_offset) - cast(int, input.latency_offset)) - for output, input in it.product(self.outputs, self.inputs) + (cast(int, output.latency_offset) - cast(int, input_.latency_offset)) + for output, input_ in it.product(self.outputs, self.inputs) ) ) @@ -983,8 +983,8 @@ class AbstractOperation(Operation, AbstractGraphComponent): def latency_offsets(self) -> Dict[str, Optional[int]]: latency_offsets = {} - for i, input in enumerate(self.inputs): - latency_offsets[f"in{i}"] = input.latency_offset + for i, input_ in enumerate(self.inputs): + latency_offsets[f"in{i}"] = input_.latency_offset for i, output in enumerate(self.outputs): latency_offsets[f"out{i}"] = output.latency_offset @@ -992,7 +992,7 @@ class AbstractOperation(Operation, AbstractGraphComponent): return latency_offsets def _check_all_latencies_set(self) -> None: - """Raises an exception of an input or output does not have its latency offset set + """Raises an exception if an input or output does not have its latency offset set """ self.input_latency_offsets() self.output_latency_offsets() @@ -1159,5 +1159,5 @@ class AbstractOperation(Operation, AbstractGraphComponent): @property def is_constant(self) -> bool: return all( - input.connected_source.operation.is_constant for input in self.inputs + input_.connected_source.operation.is_constant for input_ in self.inputs ) diff --git a/b_asic/process.py b/b_asic/process.py index 797af4fa710004c3b2d187011e371686b2a8c830..4c8f004d1c28f492de105066e107b71a9de45ef7 100644 --- a/b_asic/process.py +++ b/b_asic/process.py @@ -72,7 +72,7 @@ class OperatorProcess(Process): ========== start_time : int Start time of process. - operation : Operation + operation : :class:`~b_asic.operation.Operation` Operation that the process corresponds to. name : str, optional The name of the process. @@ -106,11 +106,11 @@ class MemoryVariable(Process): write_time : int Time when the memory variable is written. - write_port : OutputPort + write_port : :class:`~b_asic.port.OutputPort` The OutputPort that the memory variable originates from. - reads : {InputPort: int, ...} - Dictionary with the InputPorts that reads the memory variable and - for how long after the *write_time* they will read. + reads : dict + Dictionary with :class:`~b_asic.port.InputPort` that reads the memory variable as key and + for how long after the *write_time* it will read. name : str, optional The name of the process. """ diff --git a/b_asic/research/interleaver.py b/b_asic/research/interleaver.py index ae87931847830b1865f34eafa6584a4d52893ff4..77763c025b546d770ffe0894fd22fb80d5dacfe6 100644 --- a/b_asic/research/interleaver.py +++ b/b_asic/research/interleaver.py @@ -3,13 +3,15 @@ Functions to generate memory-variable test data that are used for research. """ import random -from typing import Optional, Set +from typing import List, Optional, Tuple from b_asic.process import PlainMemoryVariable from b_asic.resources import ProcessCollection -def _insert_delays(inputorder, outputorder, min_lifetime, cyclic): +def _insert_delays( + inputorder: List[int], outputorder: List[int], min_lifetime: int, cyclic: bool +) -> Tuple[List[int], List[int]]: size = len(inputorder) maxdiff = min(outputorder[i] - inputorder[i] for i in range(size)) outputorder = [o - maxdiff + min_lifetime for o in outputorder] diff --git a/b_asic/resources.py b/b_asic/resources.py index 9a56b514dca3192d7b7623d95f22ebca749f9f18..1245138993bd3d90a7c05c632f1c4ba87839e8fd 100644 --- a/b_asic/resources.py +++ b/b_asic/resources.py @@ -376,10 +376,12 @@ class ProcessCollection: ---------- heuristic : {'graph_color', 'left_edge'}, default: 'graph_color' The heuristic used when splitting based on execution times. + coloring_strategy : str, default: 'saturation_largest_first' Node ordering strategy passed to :func:`networkx.coloring.greedy_color`. This parameter is only considered if *heuristic* is set to 'graph_color'. One of + * 'largest_first' * 'random_sequential' * 'smallest_last' @@ -418,14 +420,18 @@ class ProcessCollection: heuristic : str, default: "graph_color" The heuristic used when splitting this ProcessCollection. Valid options are: + * "graph_color" * "..." + read_ports : int, optional The number of read ports used when splitting process collection based on memory variable access. + write_ports : int, optional The number of write ports used when splitting process collection based on memory variable access. + total_ports : int, optional The total number of ports used when splitting process collection based on memory variable access. diff --git a/b_asic/save_load_structure.py b/b_asic/save_load_structure.py index a6bdf27a8b77e17c6e479af76d73bb3659d94980..74d531312112425011bcf5ad065ec81287ca53cb 100644 --- a/b_asic/save_load_structure.py +++ b/b_asic/save_load_structure.py @@ -23,7 +23,7 @@ def sfg_to_python( Parameters ---------- - sfg : SFG + sfg : :class:`~b_asic.signal_flow_graph.SFG` The SFG to serialize. counter : int, default: 0 Number used for naming the SFG. Enables SFGs in SFGs. @@ -167,7 +167,7 @@ def schedule_to_python(schedule: Schedule) -> str: Parameters ---------- - schedule : Schedule + schedule : :class:`~b_asic.schedule.Schedule` The schedule to serialize. """ if not isinstance(schedule, Schedule): diff --git a/b_asic/schedule.py b/b_asic/schedule.py index 8390942eebf598015bf41fa603d0fd31f910b5d9..a5f298f84b007a4b6f78edcbd3f85c1554f9ad2a 100644 --- a/b_asic/schedule.py +++ b/b_asic/schedule.py @@ -48,7 +48,7 @@ class Schedule: Parameters ---------- - sfg : SFG + sfg : :class:`~b_asic.signal_flow_graph.SFG` The signal flow graph to schedule. schedule_time : int, optional The schedule time. If not provided, it will be determined by the scheduling @@ -899,7 +899,7 @@ class Schedule: Parameters ---------- - ax : matplotlib.axes.Axes + ax : :class:`~matplotlib.axes.Axes` The :class:`matplotlib.axes.Axes` to plot in. operation_gap : float, optional The vertical distance between operations in the schedule. The height of diff --git a/b_asic/scheduler_gui/compile.py b/b_asic/scheduler_gui/compile.py index 388c884c5f3e90971b4ac9fb2a84e68793b1cb21..88c8f9837ca5c0d6388a7aef384ae4e30704d06c 100644 --- a/b_asic/scheduler_gui/compile.py +++ b/b_asic/scheduler_gui/compile.py @@ -46,7 +46,7 @@ def _check_qt_version() -> None: def replace_qt_bindings(filename: str) -> None: - """Raplaces qt-binding api in *filename* from PySide2/6 or PyQt5/6 to qtpy.""" + """Replaces qt-binding API in *filename* from PySide2/6 or PyQt5/6 to qtpy.""" with open(f"{filename}", "r") as file: filedata = file.read() filedata = filedata.replace("from PyQt5", "from qtpy") @@ -64,7 +64,7 @@ def compile_rc(*filenames: str) -> None: """ _check_qt_version() - def compile(filename: str) -> None: + def _compile(filename: str) -> None: outfile = f"{os.path.splitext(filename)[0]}_rc.py" rcc = shutil.which("pyside2-rcc") arguments = f"-g python -o {outfile} {filename}" @@ -117,12 +117,12 @@ def compile_rc(*filenames: str) -> None: ] for filename in rc_files: - compile(filename) + _compile(filename) else: _check_filenames(*filenames) for filename in filenames: - compile(filename) + _compile(filename) def compile_ui(*filenames: str) -> None: @@ -132,7 +132,7 @@ def compile_ui(*filenames: str) -> None: """ _check_qt_version() - def compile(filename: str) -> None: + def _compile(filename: str) -> None: directory, file = os.path.split(filename) file = os.path.splitext(file)[0] directory = directory if directory else "." @@ -219,11 +219,11 @@ def compile_ui(*filenames: str) -> None: if name.endswith(".ui") ] for filename in ui_files: - compile(filename) + _compile(filename) else: _check_filenames(*filenames) for filename in filenames: - compile(filename) + _compile(filename) def compile_all() -> None: diff --git a/b_asic/scheduler_gui/logger.py b/b_asic/scheduler_gui/logger.py index d8bd4a526511a3821e7f5113ec0e948b563c0a52..d4c55287912ec444aec372895a1913e9c1331228 100644 --- a/b_asic/scheduler_gui/logger.py +++ b/b_asic/scheduler_gui/logger.py @@ -9,7 +9,7 @@ on the :mod:`logging` module and has predefined levels of logging. Usage: ------ - >>> import logger + >>> import b_asic.scheduler_gui.logger as logger >>> log = logger.getLogger() >>> log.info('This is a log post with level INFO') @@ -87,7 +87,7 @@ def getLogger(filename: str = "scheduler-gui.log", loglevel: str = "INFO") -> Lo loglevel = getattr(logging, loglevel.upper(), logging.INFO) logger.setLevel(loglevel) - # setup the console logger + # set up the console logger c_fmt_date = "%T" c_fmt = ( "[%(process)d] %(asctime)s %(filename)18s:%(lineno)-4s" diff --git a/b_asic/signal_flow_graph.py b/b_asic/signal_flow_graph.py index 988673cdc31535522d943502c67b31a07f844831..fc12e9193eee84bf3581deaad593dbcafd3a9658 100644 --- a/b_asic/signal_flow_graph.py +++ b/b_asic/signal_flow_graph.py @@ -617,13 +617,13 @@ class SFG(AbstractOperation): if component_copy.input_count != component.input_count: raise TypeError("The input count may not differ between the operations") - for index_in, inp in enumerate(component_copy.inputs): - for signal in inp.signals: + for index_in, input_ in enumerate(component_copy.inputs): + for signal in input_.signals: signal.remove_destination() signal.set_destination(component.input(index_in)) - for index_out, outp in enumerate(component_copy.outputs): - for signal in outp.signals: + for index_out, output in enumerate(component_copy.outputs): + for signal in output.signals: signal.remove_source() signal.set_source(component.output(index_out)) @@ -1410,8 +1410,8 @@ class SFG(AbstractOperation): # and outputs of all operations for layer, op_list in enumerate(new_ops): for op_idx, op in enumerate(op_list): - for input in op.inputs: - input.clear() + for input_ in op.inputs: + input_.clear() for output in op.outputs: output.clear() @@ -1458,7 +1458,7 @@ class SFG(AbstractOperation): for out_signal in op.outputs[0].signals: sink_port = out_signal.destination if sink_port is None: - # It would be weird if we found a signal but it wasn't connected anywere + # It would be weird if we found a signal that wasn't connected anywhere raise ValueError("Dangling output port in sfg") sink_op_idx = id_idx_map[sink_port.operation.graph_id] diff --git a/b_asic/signal_generator.py b/b_asic/signal_generator.py index 9f8f199147a245bc09d422f64c507db79f42ed15..ec3ceb6b17b9810a0ba8fd023b818088b1aafb72 100644 --- a/b_asic/signal_generator.py +++ b/b_asic/signal_generator.py @@ -13,7 +13,6 @@ if you want more information. from math import pi, sin from numbers import Number -from pathlib import Path from typing import Optional, Sequence import numpy as np @@ -300,7 +299,7 @@ class Delay(SignalGenerator): """ Signal generator that delays the value of another signal generator. - This can used to easily delay a sequence during simulation. + This can be used to easily delay a sequence during simulation. .. note:: Although the purpose is to delay, it is also possible to look ahead by providing a negative delay. diff --git a/test/test_gui.py b/test/test_gui.py index ebe7dcd681de61d809add1d08e3059ce3e1a166a..7e339634fbce95e1fc5da811c5a1ed83e8060194 100644 --- a/test/test_gui.py +++ b/test/test_gui.py @@ -139,11 +139,11 @@ def test_help_dialogs(qtbot): widget.display_faq_page() widget.display_about_page() - widget.display_keybinds_page() + widget.display_keybindings_page() qtbot.wait(100) widget._faq_page.close() widget._about_page.close() - widget._keybinds_page.close() + widget._keybindings_page.close() widget.exit_app()