From 5fffc69bcaae6b81ba570e2295f40914c24c5c6e Mon Sep 17 00:00:00 2001 From: Angus Lothian <anglo547@student.liu.se> Date: Thu, 5 Mar 2020 10:57:04 +0100 Subject: [PATCH] Change so that Basic Operation also inherits from abstract graph component and removed name property implementation --- b_asic/__init__.py | 7 +- b_asic/abstract_graph_component.py | 25 ++++++ ...sic_operation.py => abstract_operation.py} | 14 ++-- b_asic/core_operations.py | 42 +++++----- b_asic/graph_component.py | 34 ++++++++ b_asic/operation.py | 41 +++++----- b_asic/port.py | 16 ++-- b_asic/signal.py | 28 +++++-- b_asic/signal_flow_graph.py | 80 ++++++++++++------- .../test_signal_flow_graph.py | 7 ++ 10 files changed, 197 insertions(+), 97 deletions(-) create mode 100644 b_asic/abstract_graph_component.py rename b_asic/{basic_operation.py => abstract_operation.py} (90%) create mode 100644 b_asic/graph_component.py diff --git a/b_asic/__init__.py b/b_asic/__init__.py index 752bac07..4ae6652b 100644 --- a/b_asic/__init__.py +++ b/b_asic/__init__.py @@ -2,9 +2,11 @@ Better ASIC Toolbox. TODO: More info. """ -from _b_asic import * -from b_asic.basic_operation import * +from b_asic.abstract_graph_component import * +from b_asic.abstract_operation import * from b_asic.core_operations import * +from b_asic.graph_component import * +from b_asic.graph_id import * from b_asic.operation import * from b_asic.precedence_chart import * from b_asic.port import * @@ -12,3 +14,4 @@ from b_asic.schema import * from b_asic.signal_flow_graph import * from b_asic.signal import * from b_asic.simulation import * +from b_asic.traverse_tree import * diff --git a/b_asic/abstract_graph_component.py b/b_asic/abstract_graph_component.py new file mode 100644 index 00000000..6efb94e3 --- /dev/null +++ b/b_asic/abstract_graph_component.py @@ -0,0 +1,25 @@ +"""@package docstring +B-ASIC module for Graph Components of a signal flow graph. +TODO: More info. +""" + +from b_asic.graph_component import GraphComponent, Name + +class AbstractGraphComponent(GraphComponent): + """Abstract Graph Component class which is a component of a signal flow graph. + + TODO: More info. + """ + + _name: Name + + def __init__(self, name: Name = ""): + self._name = name + + @property + def name(self) -> Name: + return self._name + + @name.setter + def name(self, name: Name) -> None: + self._name = name diff --git a/b_asic/basic_operation.py b/b_asic/abstract_operation.py similarity index 90% rename from b_asic/basic_operation.py rename to b_asic/abstract_operation.py index 93a27222..ab8438a5 100644 --- a/b_asic/basic_operation.py +++ b/b_asic/abstract_operation.py @@ -1,5 +1,5 @@ """@package docstring -B-ASIC Basic Operation Module. +B-ASIC Abstract Operation Module. TODO: More info. """ @@ -11,9 +11,9 @@ from b_asic.port import InputPort, OutputPort from b_asic.signal import Signal from b_asic.operation import Operation from b_asic.simulation import SimulationState, OperationState +from b_asic.abstract_graph_component import AbstractGraphComponent - -class BasicOperation(Operation): +class AbstractOperation(Operation, AbstractGraphComponent): """Generic abstract operation class which most implementations will derive from. TODO: More info. """ @@ -22,8 +22,8 @@ class BasicOperation(Operation): _output_ports: List[OutputPort] _parameters: Dict[str, Optional[Any]] - def __init__(self): - """Construct a BasicOperation.""" + def __init__(self, **kwds): + super().__init__(**kwds) self._input_ports = [] self._output_ports = [] self._parameters = {} @@ -31,7 +31,7 @@ class BasicOperation(Operation): @abstractmethod def evaluate(self, inputs: list) -> list: """Evaluate the operation and generate a list of output values given a list of input values.""" - pass + raise NotImplementedError def inputs(self) -> List[InputPort]: return self._input_ports.copy() @@ -68,7 +68,7 @@ class BasicOperation(Operation): assert input_count == len(self._input_ports) # TODO: Error message. assert output_count == len(self._output_ports) # TODO: Error message. - self_state: OperationState = state.operation_states[self.identifier()] + self_state: OperationState = state.operation_states[self] while self_state.iteration < state.iteration: input_values: List[Number] = [0] * input_count diff --git a/b_asic/core_operations.py b/b_asic/core_operations.py index 45919b8b..52f18361 100644 --- a/b_asic/core_operations.py +++ b/b_asic/core_operations.py @@ -7,62 +7,69 @@ from numbers import Number from b_asic.port import InputPort, OutputPort from b_asic.operation import Operation -from b_asic.basic_operation import BasicOperation -from b_asic.graph_id import GraphIDType +from b_asic.abstract_operation import AbstractOperation +from b_asic.abstract_graph_component import AbstractGraphComponent +from b_asic.graph_component import Name, TypeName -class Input(Operation): +class Input(Operation, AbstractGraphComponent): """Input operation. TODO: More info. """ # TODO: Implement all functions. - pass + @property + def type_name(self) -> TypeName: + return "in" -class Constant(BasicOperation): + +class Constant(AbstractOperation): """Constant value operation. TODO: More info. """ - def __init__(self, value: Number): + def __init__(self, value: Number, **kwds): """Construct a Constant.""" - super().__init__() + super().__init__(**kwds) self._output_ports = [OutputPort(1, self)] # TODO: Generate appropriate ID for ports. self._parameters["value"] = value def evaluate(self, inputs: list) -> list: return [self.param("value")] - def type_name(self) -> GraphIDType: + @property + def type_name(self) -> TypeName: return "const" -class Addition(BasicOperation): + +class Addition(AbstractOperation): """Binary addition operation. TODO: More info. """ - def __init__(self): + def __init__(self, **kwds): """Construct an Addition.""" - super().__init__() + super().__init__(**kwds) self._input_ports = [InputPort(1, self), InputPort(1, self)] # TODO: Generate appropriate ID for ports. self._output_ports = [OutputPort(1, self)] # TODO: Generate appropriate ID for ports. def evaluate(self, inputs: list) -> list: return [inputs[0] + inputs[1]] - def type_name(self) -> GraphIDType: + @property + def type_name(self) -> TypeName: return "add" -class ConstantMultiplication(BasicOperation): +class ConstantMultiplication(AbstractOperation): """Unary constant multiplication operation. TODO: More info. """ - def __init__(self, coefficient: Number): + def __init__(self, coefficient: Number, **kwds): """Construct a ConstantMultiplication.""" - super().__init__() + super().__init__(**kwds) self._input_ports = [InputPort(1), self] # TODO: Generate appropriate ID for ports. self._output_ports = [OutputPort(1, self)] # TODO: Generate appropriate ID for ports. self._parameters["coefficient"] = coefficient @@ -70,7 +77,6 @@ class ConstantMultiplication(BasicOperation): def evaluate(self, inputs: list) -> list: return [inputs[0] * self.param("coefficient")] - def type_name(self) -> GraphIDType: + @property + def type_name(self) -> TypeName: return "const_mul" - -# TODO: More operations. diff --git a/b_asic/graph_component.py b/b_asic/graph_component.py new file mode 100644 index 00000000..4bce63f2 --- /dev/null +++ b/b_asic/graph_component.py @@ -0,0 +1,34 @@ +"""@package docstring +B-ASIC Operation Module. +TODO: More info. +""" + +from abc import ABC, abstractmethod +from typing import NewType + +Name = NewType("Name", str) +TypeName = NewType("TypeName", str) + + +class GraphComponent(ABC): + """Graph component interface. + TODO: More info. + """ + + @property + @abstractmethod + def type_name(self) -> TypeName: + """Returns the type name of the graph component""" + raise NotImplementedError + + @property + @abstractmethod + def name(self) -> Name: + """Returns the name of the graph component.""" + raise NotImplementedError + + @name.setter + @abstractmethod + def name(self, name: Name) -> None: + """Sets the name of the graph component to the entered name.""" + raise NotImplementedError diff --git a/b_asic/operation.py b/b_asic/operation.py index 923690aa..5d4b404d 100644 --- a/b_asic/operation.py +++ b/b_asic/operation.py @@ -3,17 +3,17 @@ B-ASIC Operation Module. TODO: More info. """ -from abc import ABC, abstractmethod +from abc import abstractmethod from numbers import Number from typing import List, Dict, Optional, Any, TYPE_CHECKING +from b_asic.graph_component import GraphComponent + if TYPE_CHECKING: from b_asic.port import InputPort, OutputPort from b_asic.simulation import SimulationState - from b_asic.graph_id import GraphIDType - -class Operation(ABC): +class Operation(GraphComponent): """Operation interface. TODO: More info. """ @@ -21,75 +21,72 @@ class Operation(ABC): @abstractmethod def inputs(self) -> "List[InputPort]": """Get a list of all input ports.""" - pass + raise NotImplementedError @abstractmethod def outputs(self) -> "List[OutputPort]": """Get a list of all output ports.""" - pass + raise NotImplementedError @abstractmethod def input_count(self) -> int: """Get the number of input ports.""" - pass + raise NotImplementedError @abstractmethod def output_count(self) -> int: """Get the number of output ports.""" - pass + raise NotImplementedError @abstractmethod def input(self, i: int) -> "InputPort": """Get the input port at index i.""" - pass + raise NotImplementedError + @abstractmethod def output(self, i: int) -> "OutputPort": """Get the output port at index i.""" - pass + raise NotImplementedError + @abstractmethod def params(self) -> Dict[str, Optional[Any]]: """Get a dictionary of all parameter values.""" - pass + raise NotImplementedError @abstractmethod def param(self, name: str) -> Optional[Any]: """Get the value of a parameter. Returns None if the parameter is not defined. """ - pass + raise NotImplementedError @abstractmethod def set_param(self, name: str, value: Any) -> None: """Set the value of a parameter. The parameter must be defined. """ - pass + raise NotImplementedError @abstractmethod def evaluate_outputs(self, state: "SimulationState") -> List[Number]: """Simulate the circuit until its iteration count matches that of the simulation state, then return the resulting output vector. """ - pass + raise NotImplementedError @abstractmethod def split(self) -> "List[Operation]": """Split the operation into multiple operations. If splitting is not possible, this may return a list containing only the operation itself. """ - pass - - @abstractmethod - def type_name(self) -> "GraphIDType": - """Returns a string representing the operation name of the operation.""" - pass + raise NotImplementedError + @property @abstractmethod def neighbours(self) -> "List[Operation]": """Return all operations that are connected by signals to this operation. If no neighbours are found this returns an empty list """ - - # TODO: More stuff. + raise NotImplementedError diff --git a/b_asic/port.py b/b_asic/port.py index 4c6fb244..a8a062fc 100644 --- a/b_asic/port.py +++ b/b_asic/port.py @@ -38,31 +38,27 @@ class Port(ABC): @abstractmethod def signals(self) -> List[Signal]: """Get a list of all connected signals.""" - pass + raise NotImplementedError - @property @abstractmethod def signal(self, i: int = 0) -> Signal: """Get the connected signal at index i.""" - pass + raise NotImplementedError @abstractmethod def signal_count(self) -> int: """Get the number of connected signals.""" - pass + raise NotImplementedError @abstractmethod def connect(self, signal: Signal) -> None: """Connect a signal.""" - pass + raise NotImplementedError @abstractmethod def disconnect(self, i: int = 0) -> None: """Disconnect a signal.""" - pass - - - # TODO: More stuff. + raise NotImplementedError class InputPort(Port): @@ -79,7 +75,6 @@ class InputPort(Port): def signals(self) -> List[Signal]: return [] if self._source_signal is None else [self._source_signal] - @property def signal(self, i: int = 0) -> Signal: assert 0 <= i < self.signal_count() # TODO: Error message. assert self._source_signal is not None # TODO: Error message. @@ -115,7 +110,6 @@ class OutputPort(Port): def signals(self) -> List[Signal]: return self._destination_signals.copy() - @property def signal(self, i: int = 0) -> Signal: assert 0 <= i < self.signal_count() # TODO: Error message. return self._destination_signals[i] diff --git a/b_asic/signal.py b/b_asic/signal.py index 17078138..810c00dc 100644 --- a/b_asic/signal.py +++ b/b_asic/signal.py @@ -2,37 +2,51 @@ B-ASIC Signal Module. """ from typing import TYPE_CHECKING, Optional + +from b_asic.graph_component import TypeName +from b_asic.abstract_graph_component import AbstractGraphComponent + if TYPE_CHECKING: from b_asic import OutputPort, InputPort -class Signal: +class Signal(AbstractGraphComponent): """A connection between two ports.""" _source: "OutputPort" _destination: "InputPort" - def __init__(self, src: Optional["OutputPort"] = None, dest: Optional["InputPort"] = None): + def __init__(self, src: Optional["OutputPort"] = None, dest: Optional["InputPort"] = None, **kwds): + super().__init__(**kwds) self._source = src self._destination = dest - @property - def source(self) -> "InputPort": + @property + def source(self) -> "OutputPort": + """Returns the source OutputPort of the signal.""" return self._source @property - def destination(self) -> "OutputPort": + def destination(self) -> "InputPort": + """Returns the destination InputPort of the signal.""" return self._destination @source.setter - def source(self, src: "Outputport") -> None: + def source(self, src: "OutputPort") -> None: + """Sets the value of the source OutputPort of the signal.""" self._source = src @destination.setter def destination(self, dest: "InputPort") -> None: + """Sets the value of the destination InputPort of the signal.""" self._destination = dest + @property + def type_name(self) -> TypeName: + return "s" + def disconnect_source(self) -> None: + """Disconnects the source OutputPort of the signal.""" self._source = None def disconnect_destination(self) -> None: + """Disconnects the destination InputPort of the signal.""" self._destination = None - diff --git a/b_asic/signal_flow_graph.py b/b_asic/signal_flow_graph.py index f7d4be64..38c46697 100644 --- a/b_asic/signal_flow_graph.py +++ b/b_asic/signal_flow_graph.py @@ -3,69 +3,89 @@ B-ASIC Signal Flow Graph Module. TODO: More info. """ -from typing import List, Dict, Union, Optional +from typing import List, Dict, Optional, DefaultDict +from collections import defaultdict from b_asic.operation import Operation -from b_asic.basic_operation import BasicOperation +from b_asic.abstract_operation import AbstractOperation from b_asic.signal import Signal -from b_asic.simulation import SimulationState, OperationState -from typing import List from b_asic.graph_id import GraphIDGenerator, GraphID +from b_asic.graph_component import GraphComponent, Name, TypeName -class SFG(BasicOperation): +class SFG(AbstractOperation): """Signal flow graph. TODO: More info. """ - _graph_objects_by_id: Dict[GraphID, Union[Operation, Signal]] + _graph_components_by_id: Dict[GraphID, GraphComponent] + _graph_components_by_name: DefaultDict[Name, List[GraphComponent]] _graph_id_generator: GraphIDGenerator - def __init__(self, input_destinations: List[Signal], output_sources: List[Signal]): - super().__init__() - # TODO: Allocate input/output ports with appropriate IDs. - - self._graph_objects_by_id = dict # Map Operation ID to Operation objects + def __init__(self, input_signals: List[Signal] = None, output_signals: List[Signal] = None, \ + ops: List[Operation] = None, **kwds): + super().__init__(**kwds) + if input_signals is None: + input_signals = [] + if output_signals is None: + output_signals = [] + if ops is None: + ops = [] + + self._graph_components_by_id = dict() # Maps Graph ID to objects + self._graph_components_by_name = defaultdict(list) # Maps Name to objects self._graph_id_generator = GraphIDGenerator() + for operation in ops: + self._add_graph_component(operation) + + for input_signal in input_signals: + self._add_graph_component(input_signal) + + # TODO: Construct SFG based on what inputs that were given # TODO: Traverse the graph between the inputs/outputs and add to self._operations. # TODO: Connect ports with signals with appropriate IDs. def evaluate(self, inputs: list) -> list: return [] # TODO: Implement - def add_operation(self, operation: Operation) -> GraphID: - """Adds the entered operation to the SFG's dictionary of graph objects and + def _add_graph_component(self, graph_component: GraphComponent) -> GraphID: + """Adds the entered graph component to the SFG's dictionary of graph objects and returns a generated GraphID for it. Keyword arguments: - operation: Operation to add to the graph. + graph_component: Graph component to add to the graph. """ - return self._add_graph_obj(operation, operation.type_name()) + # Add to name dict + self._graph_components_by_name[graph_component.name].append(graph_component) - def add_signal(self, signal: Signal) -> GraphID: - """Adds the entered signal to the SFG's dictionary of graph objects and returns - a generated GraphID for it. - - Keyword argumentst: - signal: Signal to add to the graph. - """ - return self._add_graph_obj(signal, 'sig') + # Add to ID dict + graph_id: GraphID = self._graph_id_generator.get_next_id(graph_component.type_name) + self._graph_components_by_id[graph_id] = graph_component + return graph_id - def find_by_id(self, graph_id: GraphID) -> Optional[Operation]: + def find_by_id(self, graph_id: GraphID) -> Optional[GraphComponent]: """Finds a graph object based on the entered Graph ID and returns it. If no graph object with the entered ID was found then returns None. Keyword arguments: graph_id: Graph ID of the wanted object. """ - if graph_id in self._graph_objects_by_id: - return self._graph_objects_by_id[graph_id] + if graph_id in self._graph_components_by_id: + return self._graph_components_by_id[graph_id] return None - def _add_graph_obj(self, obj: Union[Operation, Signal], operation_id_type: str): - graph_id = self._graph_id_generator.get_next_id(operation_id_type) - self._graph_objects_by_id[graph_id] = obj - return graph_id + def find_by_name(self, name: Name) -> List[GraphComponent]: + """Finds all graph objects that have the entered name and returns them + in a list. If no graph object with the entered name was found then returns an + empty list. + + Keyword arguments: + name: Name of the wanted object. + """ + return self._graph_components_by_name[name] + @property + def type_name(self) -> TypeName: + return "sfg" diff --git a/test/signal_flow_graph/test_signal_flow_graph.py b/test/signal_flow_graph/test_signal_flow_graph.py index 921e8906..d18d2da5 100644 --- a/test/signal_flow_graph/test_signal_flow_graph.py +++ b/test/signal_flow_graph/test_signal_flow_graph.py @@ -1,3 +1,10 @@ from b_asic.signal_flow_graph import SFG from b_asic.core_operations import Addition, Constant from b_asic.signal import Signal +from b_asic.signal_flow_graph import SFG + +import pytest + +def test_adding_to_sfg(): + pass + -- GitLab