pybrid-computing 0.10.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,83 @@
1
+ # Copyright (c) 2022-2024 anabrid GmbH
2
+ # Contact: https://www.anabrid.com/licensing/
3
+ # SPDX-License-Identifier: MIT OR GPL-2.0-or-later
4
+
5
+ import typing
6
+ from dataclasses import dataclass
7
+
8
+ from .entities import Entity, Path, EntityType, EntityClass
9
+ from .blocks import FunctionBlock, MBlock, UBlock, CBlock, IBlock
10
+
11
+
12
+ @dataclass(kw_only=True)
13
+ class Cluster(Entity):
14
+ """
15
+ A REDAC computation cluster.
16
+
17
+ The cluster is the smallest unit capable of an analog computation.
18
+ It always consists of two optional :class:`.blocks.MBlock` objects
19
+ and one mandatory :class:`.blocks.UBlock`, :class:`.blocks.CBlock` and :class:`.blocks.IBlock` each.
20
+ """
21
+ #: The first :class:`.blocks.MBlock` in this cluster. May be ``None`` if the slot is not filled.
22
+ m0block: typing.Optional[MBlock]
23
+ #: The second :class:`.blocks.MBlock` in this cluster. May be ``None`` if the slot is not filled.
24
+ m1block: typing.Optional[MBlock]
25
+ #: The :class:`.blocks.UBlock` in this cluster.
26
+ ublock: UBlock
27
+ #: The :class:`.blocks.CBlock` in this cluster.
28
+ cblock: CBlock
29
+ #: The :class:`.blocks.IBlock` in this cluster.
30
+ iblock: IBlock
31
+
32
+ @property
33
+ def children(self):
34
+ """
35
+ Generator iterating through child entities of type :class:`.blocks.FunctionBlock`.
36
+ Only returns blocks that are actually present (i.e. not ``None``).
37
+ """
38
+ yield from (block for block in self.blocks if block is not None)
39
+
40
+ @property
41
+ def blocks(self) -> tuple[typing.Optional[MBlock], typing.Optional[MBlock], UBlock, CBlock, IBlock]:
42
+ """
43
+ List of :class:`.blocks.FunctionBlock` objects in this cluster.
44
+ Returns ``None`` elements for blocks that are not present.
45
+ """
46
+ return self.m0block, self.m1block, self.ublock, self.cblock, self.iblock
47
+
48
+ @classmethod
49
+ def create_from_entity_type_tree(cls, path, tree):
50
+ # TODO: Refactor out common code
51
+ # Check information on self
52
+ this_entity_type = EntityType.pop_from_dict(tree)
53
+ assert this_entity_type.class_ is EntityClass.CLUSTER
54
+
55
+ # Generate child entities
56
+ blocks = []
57
+ for sub_path, sub_tree in tree.items():
58
+ if not sub_path.startswith('/'):
59
+ raise ValueError('Unexpected entities tree element. Expected only sub-paths to be left.')
60
+ path_ = path / Path((sub_path.removeprefix('/'),))
61
+ block = FunctionBlock.create_from_entity_type_tree(path_, sub_tree)
62
+ blocks.append(block)
63
+
64
+ # TODO: Less hard-coding :)
65
+ return cls(path=path, m0block=blocks[0], m1block=blocks[1], ublock=blocks[2], cblock=blocks[3],
66
+ iblock=blocks[4])
67
+
68
+ def route(self, m_out: int, u_out: int, c_factor: float, m_in: int):
69
+ """
70
+ Convenience function to connect a signal from before the :class:`.blocks.UBlock` through a coefficient
71
+ on the :class:`.blocks.CBlock` and through the :class:`.blocks.IBlock` to an input on one of the
72
+ :class:`.blocks.MBlock` slots.
73
+
74
+ :param int m_out: Output index from one of the MBlocks, respectively input index of the UBlock.
75
+ :param int u_out: Output index of the UBlock, respectively index of the coefficient.
76
+ :param float c_factor: Factor of the coefficient.
77
+ :param int m_in: Input index of one of the MBlocks, respectively output index of the IBlock.
78
+ :return: ``None``
79
+ """
80
+ # TODO: Sanity checks and error handling
81
+ self.ublock.connect(m_out, u_out)
82
+ self.cblock.elements[u_out].factor = c_factor
83
+ self.iblock.connect(u_out, m_in)
@@ -0,0 +1,30 @@
1
+ # Copyright (c) 2022-2024 anabrid GmbH
2
+ # Contact: https://www.anabrid.com/licensing/
3
+ # SPDX-License-Identifier: MIT OR GPL-2.0-or-later
4
+
5
+ from dataclasses import dataclass, field
6
+
7
+ from pybrid.base.analog import Integration as BaseIntegration
8
+ from pybrid.base.analog import ScalarMultiplication as BaseScalarMultiplication, \
9
+ ScalarMultiplicationFactor
10
+ from pybrid.base.analog import Multiplication # noqa
11
+
12
+
13
+ @dataclass(kw_only=True)
14
+ class Integration(BaseIntegration):
15
+ #: Initial value. Must be in range [-1.0, 1.0].
16
+ ic: float = 0.0
17
+ #: Time constant in :math:`\frac{1}{\mathrm{s}}`. Must be one of {100, 10000}.
18
+ k: int = 10_000
19
+
20
+ # Inherit __doc__
21
+ __doc__ = BaseIntegration.__doc__
22
+
23
+
24
+ @dataclass(kw_only=True)
25
+ class ScalarMultiplication(BaseScalarMultiplication):
26
+ #: Scalar factor α. Must be in range [-20.0, 20.0].
27
+ factor: float = field(default=ScalarMultiplicationFactor(min=-20.0, max=+20.0, default=1.0))
28
+
29
+ # Inherit __doc__
30
+ __doc__ = BaseScalarMultiplication.__doc__
@@ -0,0 +1,42 @@
1
+ # Copyright (c) 2022-2024 anabrid GmbH
2
+ # Contact: https://www.anabrid.com/licensing/
3
+ # SPDX-License-Identifier: MIT OR GPL-2.0-or-later
4
+
5
+ from pybrid.base.hybrid import AnalogComputer
6
+
7
+ from .blocks import FunctionBlock
8
+ from .carrier import Carrier
9
+ from .cluster import Cluster
10
+ from .elements import ComputationElement
11
+ from .entities import Path
12
+
13
+
14
+ class REDAC(AnalogComputer):
15
+ """
16
+ Representation of the REDAC analog computer and its structure.
17
+ """
18
+ hierarchy = (Carrier, Cluster, FunctionBlock, ComputationElement)
19
+ entities: list[Carrier]
20
+
21
+ @property
22
+ def name(self) -> str:
23
+ return "REDAC"
24
+
25
+ @property
26
+ def carriers(self) -> list[Carrier]:
27
+ """The list of :class:`.Carrier` boards in this REDAC."""
28
+ return self.entities
29
+
30
+ @classmethod
31
+ def create_from_entity_type_tree(cls, type_tree):
32
+ carriers = []
33
+ for sub_path, sub_tree in type_tree.items():
34
+ carrier = Carrier.create_from_entity_type_tree(Path((sub_path,)), sub_tree)
35
+ carriers.append(carrier)
36
+ return cls(entities=carriers)
37
+
38
+ def __repr__(self):
39
+ return repr(self.entities)
40
+
41
+
42
+
@@ -0,0 +1,141 @@
1
+ # Copyright (c) 2022-2024 anabrid GmbH
2
+ # Contact: https://www.anabrid.com/licensing/
3
+ # SPDX-License-Identifier: MIT OR GPL-2.0-or-later
4
+
5
+ import asyncio
6
+ import logging
7
+ import typing
8
+ from asyncio import Future
9
+ from uuid import UUID
10
+
11
+ from pybrid.base.hybrid import BaseController
12
+
13
+ from .computer import REDAC
14
+ from .entities import Entity, Path
15
+ from .protocol.messages import RunStateChangeMessage, RunDataMessage
16
+ from .protocol.protocol import Protocol
17
+ from .run import Run, RunState
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class Controller(BaseController):
23
+ """
24
+ Abstraction of the REDAC hybrid controller.
25
+
26
+ The hybrid controller is an interface to all relevant functions to configure and control the REDAC.
27
+ It also collects all :class:`Run` instances started with it.
28
+
29
+ The controller object also holds references to the underlying protocol and transport objects and manages them.
30
+ """
31
+ computer: REDAC
32
+ protocol: Protocol
33
+ #: List of all runs started by this controller.
34
+ runs: dict[UUID, Run] = dict()
35
+ _ongoing_runs: dict[UUID, Future] = dict()
36
+
37
+ @classmethod
38
+ def get_run_implementation(cls) -> typing.Type[Run]:
39
+ """Returns the specific :class:`.Run` implementation used by the REDAC."""
40
+ return Run
41
+
42
+ async def start(self) -> None:
43
+ await super().start()
44
+ self.protocol.register_callback(RunStateChangeMessage, self.handle_run_state_change)
45
+ self.protocol.register_callback(RunDataMessage, self.handle_run_data)
46
+
47
+ def handle_run_state_change(self, msg: RunStateChangeMessage):
48
+ """A handler for incoming :class:`.RunStateChangeMessage` messages."""
49
+ logger.debug("Received run state change: %s.", msg)
50
+ if run := self.runs.get(msg.id, None):
51
+ run.state = RunState(msg.new)
52
+ if run.state.is_done():
53
+ self._ongoing_runs.pop(run.id_).set_result(run)
54
+ else:
55
+ logger.warning("Received run state change with unknown id %s.", msg.id)
56
+
57
+ def handle_run_data(self, msg: RunDataMessage):
58
+ """A handler for incoming :class:`.RunDataMessage` messages."""
59
+ if run := self.runs.get(msg.id, None):
60
+ adc_paths = [Path(msg.entity).join(f"ADC{idx}") for idx in range(run.daq.num_channels)]
61
+ for data_pkg in msg.data:
62
+ for channel, data_point in zip(adc_paths, data_pkg):
63
+ run.data[channel].append(data_point)
64
+ last_t = len(run.data["t"])
65
+ run.data["t"].extend(range(last_t, last_t + len(msg.data)))
66
+
67
+ # ██████ ██████ ███ ███ ███ ███ █████ ███ ██ ██████ ███████
68
+ # ██ ██ ██ ████ ████ ████ ████ ██ ██ ████ ██ ██ ██ ██
69
+ # ██ ██ ██ ██ ████ ██ ██ ████ ██ ███████ ██ ██ ██ ██ ██ ███████
70
+ # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
71
+ # ██████ ██████ ██ ██ ██ ██ ██ ██ ██ ████ ██████ ███████
72
+
73
+ async def hack(self, cmd: str, data: typing.Any) -> typing.Any:
74
+ """
75
+ Send the passed data as a 'hack' request, only used during development.
76
+ It allows to pass and receive arbitrary data to and from the hybrid controller.
77
+ """
78
+ return await self.protocol.hack_request(cmd, data)
79
+
80
+ async def get_computer(self) -> REDAC:
81
+ """
82
+ Retrieve the current hardware configuration of the REDAC.
83
+ """
84
+ entities = await self.protocol.get_entities()
85
+ computer = REDAC.create_from_entity_type_tree(entities)
86
+ return computer
87
+
88
+ async def set_computer(self, computer: REDAC):
89
+ """
90
+ Change the configuration of all carrier boards and sub-entities on the REDAC.
91
+
92
+ :param computer: The :class:`.REDAC` object containing the configuration to be set.
93
+ :return: None
94
+ """
95
+ for carrier in computer.carriers:
96
+ await self.set_config(carrier)
97
+
98
+ async def start_run(self, run: typing.Optional[Run] = None) -> Future:
99
+ """
100
+ Start a run (computation) on the REDAC.
101
+
102
+ :param run: The :class:`.Run` to be started, including its configuration. If None, a new run is created.
103
+ :return: An :class:`asyncio.Future` which can be awaited and will return the run object once it is done.
104
+ """
105
+ if run is None:
106
+ run = await self.create_run()
107
+ self.runs[run.id_] = run
108
+ self._ongoing_runs[run.id_] = run_future = asyncio.get_event_loop().create_future()
109
+ await self.protocol.start_run_request(run.id_, run.config, run.daq)
110
+ return run_future
111
+
112
+ async def start_and_await_run(self, run: typing.Optional[Run] = None, timeout=5) -> Run:
113
+ """
114
+ A convenience function which starts a run, blocks until it is completed and returns it.
115
+
116
+ :param run: The :class:`.Run` to be started, including its configuration. If None, a new run is created.
117
+ :param timeout: Timeout
118
+ :return: The completed :class:`.Run`.
119
+ """
120
+ run_future = await self.start_run(run)
121
+ await asyncio.wait_for(run_future, timeout=timeout)
122
+ return run_future.result()
123
+
124
+ async def set_config(self, entity: Entity):
125
+ """
126
+ Change the configuration of a singe entity.
127
+
128
+ :param entity: The entity to change.
129
+ :return: None
130
+ """
131
+ await self.protocol.set_config(entity)
132
+
133
+ async def reset(self, keep_calibration: bool = True, sync: bool = True):
134
+ """
135
+ Reset the hybrid controller and the analog computer to its initial configuration.
136
+
137
+ :param keep_calibration: Whether to keep the calibration.
138
+ :param sync: Whether to write the reset values to the hardware.
139
+ :return: None
140
+ """
141
+ await self.protocol.reset(keep_calibration=keep_calibration, sync=sync)
pybrid/redac/data.py ADDED
@@ -0,0 +1,64 @@
1
+ # Copyright (c) 2022-2024 anabrid GmbH
2
+ # Contact: https://www.anabrid.com/licensing/
3
+ # SPDX-License-Identifier: MIT OR GPL-2.0-or-later
4
+
5
+ # TODO: Refactor out common code with Model-1 data exporters and make abstraction for command line
6
+
7
+ import dataclasses
8
+ import typing
9
+
10
+ from pybrid.redac.run import Run
11
+
12
+
13
+ # TODO: Implement a lookup for different data types
14
+ # _REGISTRY = {}
15
+ # def get_exporter(format):
16
+ # return _REGISTRY[format]
17
+
18
+
19
+ class BaseExporter:
20
+ FORMAT = None
21
+
22
+ def export(self, run, **kwargs):
23
+ ...
24
+
25
+
26
+ class DatExporter(BaseExporter):
27
+ FORMAT = "dat"
28
+
29
+ def __init__(self, file: typing.IO):
30
+ self._file = file
31
+
32
+ def _write_line(self, line=""):
33
+ self._file.write(line)
34
+ self._file.write("\n")
35
+
36
+ def _write_header_line(self, key, value):
37
+ self._write_line(f"# {key} = {value}")
38
+
39
+ def _write_header(self, run):
40
+ self._write_line("# Run result")
41
+ for key in ("id_", "created"):
42
+ self._write_header_line(key, getattr(run, key))
43
+ for flag in dataclasses.fields(run.flags):
44
+ flag_set = "Yes" if getattr(run.flags, flag.name) else "No"
45
+ self._write_header_line("flag " + flag.name, flag_set)
46
+
47
+ def _write_data_line(self, idx, data_pkg):
48
+ self._write_line(str(idx) + "\t" + "\t".join(map(str, data_pkg)))
49
+
50
+ def _write_data(self, run: Run):
51
+ if not run.data:
52
+ self._write_line("# No data.")
53
+ return
54
+
55
+ data_header = "# idx\t" + "\t".join(map(str, run.data.keys()))
56
+ self._write_line(data_header)
57
+
58
+ for idx, data_pkg in enumerate(zip(*run.data.values())):
59
+ self._write_data_line(idx, data_pkg)
60
+
61
+ def export(self, run):
62
+ self._write_header(run)
63
+ self._write_line("#")
64
+ self._write_data(run)
@@ -0,0 +1,25 @@
1
+ # Copyright (c) 2022-2024 anabrid GmbH
2
+ # Contact: https://www.anabrid.com/licensing/
3
+ # SPDX-License-Identifier: MIT OR GPL-2.0-or-later
4
+
5
+ from .computer import REDAC
6
+
7
+
8
+ class TreeDisplay:
9
+
10
+ @staticmethod
11
+ def render(redac: REDAC):
12
+ buffer = ""
13
+ buffer += "REDAC Analag Computer\n"
14
+
15
+ # TODO: Do it better :)
16
+ for carrier in redac.carriers:
17
+ buffer += "├── " + carrier.__class__.__name__ + " @ " + str(carrier.path) + "\n"
18
+ for cluster in carrier.children:
19
+ buffer += "│ ├── " + cluster.__class__.__name__ + " @ " + str(cluster.path) + "\n"
20
+ for block in cluster.children:
21
+ buffer += "│ │ ├── " + block.__class__.__name__ + " @ " + str(block.path) + "\n"
22
+ for element in block.children:
23
+ buffer += "│ │ │ ├── " + element.__class__.__name__ + " @ " + str(element.path) + "\n"
24
+
25
+ return buffer
@@ -0,0 +1,28 @@
1
+ # Copyright (c) 2022-2024 anabrid GmbH
2
+ # Contact: https://www.anabrid.com/licensing/
3
+ # SPDX-License-Identifier: MIT OR GPL-2.0-or-later
4
+
5
+ from pybrid.base.hybrid import ComputationElement as BaseComputationElement
6
+
7
+ from .entities import Entity
8
+
9
+
10
+ class ComputationElement(BaseComputationElement, Entity):
11
+ """
12
+ A REDAC computation element (a function).
13
+
14
+ Each computation element implements one of the available
15
+ analog computations in :doc:`configurations`.
16
+ """
17
+
18
+ def generate_partial_configuration(self, attribute):
19
+ if self.computation_class.__dataclass_fields__.get(attribute, None):
20
+ return {attribute: getattr(self.computation, attribute)}
21
+ else:
22
+ raise ValueError("Unknown attribute %s for %s." % (attribute, self.__class__))
23
+
24
+ def apply_partial_configuration(self, attribute, value):
25
+ if field := self.computation_class.__dataclass_fields__.get(attribute, None):
26
+ setattr(self.computation, attribute, field.type(value))
27
+ else:
28
+ raise ValueError("Unknown attribute %s for %s." % (attribute, self.__class__))
@@ -0,0 +1,200 @@
1
+ # Copyright (c) 2022-2024 anabrid GmbH
2
+ # Contact: https://www.anabrid.com/licensing/
3
+ # SPDX-License-Identifier: MIT OR GPL-2.0-or-later
4
+
5
+ """
6
+ The REDAC analog computer consists of a hierarchical structure of hardware modules.
7
+
8
+ This structure is represented by a tree-like structure of :class:`Entity` objects and their sub-classes.
9
+ Each entity has a unique :class:`Path` defining its position in this hierarchy.
10
+ You can iterate over the children of an entity via its :attr:`Entity.children` property.
11
+ As described in :class:`Path`, the hierarchy represented is as follows.
12
+
13
+ #. Carrier boards implemented by :class:`pybrid.redac.carrier.Carrier`
14
+ #. Clusters implemented by :class:`pybrid.redac.cluster.Cluster`
15
+ #. Function blocks implemented by :class:`pybrid.redac.blocks.FunctionBlock`, see :doc:`configurations`
16
+ #. Functions (Elements) implemented by :class:`pybrid.redac.elements.ComputationElement`
17
+ """
18
+
19
+ from dataclasses import dataclass, fields, replace
20
+ from enum import Enum
21
+ import typing
22
+
23
+ from pybrid.base.hybrid import Path as BasePath
24
+ from pybrid.base.hybrid import Entity as BaseEntity
25
+
26
+
27
+ class UnknownEntityTypeError(ValueError):
28
+ """Exception thrown when trying to get an unknown :class:`EntityType` instance."""
29
+ pass
30
+
31
+
32
+ class EntityTypeRegistryError(ValueError):
33
+ """Exception for errors inside the :class:`EntityType` registry."""
34
+ pass
35
+
36
+
37
+ class EntityClass(Enum):
38
+ """Entity class differentiates between carrier boards, different function blocks and so on. Max 5bit = 31."""
39
+ CARRIER = 0
40
+ CLUSTER = 1 # mostly unused
41
+ MBLOCK = 2
42
+ UBLOCK = 3
43
+ CBLOCK = 4
44
+ IBLOCK = 5
45
+ OTHER = 31
46
+
47
+
48
+ _ENTITY_TYPE_REGISTRY: dict["EntityType", object] = dict()
49
+
50
+
51
+ @dataclass(kw_only=True, eq=True, frozen=True)
52
+ class EntityType:
53
+ """
54
+ A unique identifier for an entity type.
55
+
56
+ Each hardware module in a REDAC analog computer contains hardware version information
57
+ in their EEPROM. When the client library connects to an analog computer,
58
+ it generally requests the tree of hardware modules present.
59
+ This is given as a tree-like list of :class:`EntityType` objects.
60
+ Since it is also automatically converted to the respective python objects,
61
+ you typically do not need to handle :class:`EntityType` objects directly.
62
+
63
+ One notable exception is registering a python class for the auto-conversion
64
+ from an :class:`EntityType` to its respective :class:`Entity` class as follows.
65
+
66
+ .. code-block::
67
+
68
+ @EntityType.register(EntityClass.MBLOCK, 17, 3, 1)
69
+ class ACustomMBlock(ElementBlock):
70
+ ...
71
+ """
72
+ #: The class of the entity, see :class:`EntityClass`.
73
+ #: Different classes of entities can only be placed at their expected slots and can not be interchanged.
74
+ class_: EntityClass
75
+ #: The type of the entity, mostly relevant for :class:`pybrid.redac.blocks.MBlock`.
76
+ #: Different types of an entity have significantly different functionality, but may be placed in the same slots.
77
+ type_: typing.Optional[int] = None
78
+ #: The variant of an entity.
79
+ #: Different variants of an entity have similar functionality, but may differ in certain implementation details.
80
+ variant: typing.Optional[int] = None
81
+ #: The version on an entity.
82
+ #: Different versions of an entity are basically identical, but may contain different hardware elements or bugfixes.
83
+ version: typing.Optional[int] = None
84
+
85
+ @classmethod
86
+ def pop_from_dict(cls, d):
87
+ return cls(class_=EntityClass(d.pop("class")), type_=d.pop("type"), variant=d.pop("variant"),
88
+ version=d.pop("version"))
89
+
90
+ def fallback_type(self):
91
+ """Return a copy of this :class:`EntityType` with one more field set to None."""
92
+ for field in reversed(fields(self)[1:]):
93
+ if getattr(self, field.name) is not None:
94
+ return replace(self, **{field.name: None})
95
+ raise ValueError("Can not further decay.")
96
+
97
+ @classmethod
98
+ def register(cls, class_: EntityClass, type_, variant, version):
99
+ """Register a class as an implementation of an :class:`EntityType`."""
100
+ entity_type = cls(class_=class_, type_=type_, variant=variant, version=version)
101
+
102
+ def register_(obj):
103
+ if entity_type in _ENTITY_TYPE_REGISTRY:
104
+ raise EntityTypeRegistryError("Entity type is already registered.")
105
+ _ENTITY_TYPE_REGISTRY[entity_type] = obj
106
+ return obj
107
+
108
+ return register_
109
+
110
+ @classmethod
111
+ def lookup(cls, type_, decay=False):
112
+ """
113
+ Lookup the implementation of an :class:`EntityType`.
114
+ Use the ``decay`` parameter if you want to allow finding a more generic implementation.
115
+ """
116
+ try:
117
+ return _ENTITY_TYPE_REGISTRY[type_]
118
+ except KeyError:
119
+ if not decay:
120
+ raise UnknownEntityTypeError("Entity type %s not registered." % type_)
121
+ else:
122
+ try:
123
+ return cls.lookup(type_.fallback_type(), True)
124
+ except ValueError:
125
+ raise UnknownEntityTypeError("Neither entity type %s nor any fallbacks are registered." % type_)
126
+
127
+
128
+ class Path(BasePath):
129
+ """
130
+ A tuple uniquely identifying an entity in the REDAC.
131
+
132
+ The path to an entity is a hierarchical combination of paths to its parent entities.
133
+ Its structure in the REDAC is :code:`(<carrier board>, <cluster>, <block>, <function>)`.
134
+ Carrier boards are defined by their MAC address, e.g. "04-E9-E5-14-74-BF".
135
+ Clusters are defined by their index sent as a string, e.g. "0".
136
+ Function blocks on them are identified by their abbreviation, one of "M0", "M1", "U", "C", "I".
137
+ Functions on blocks are defined by their index as integer, e.g. 7.
138
+ The blocks' functions are usually not directly accessed, but instead configured via their block.
139
+
140
+ :Usage: Combine the identifiers to the required depth
141
+
142
+ .. code-block::
143
+
144
+ path_to_a_carrier_board = Path("00:00:5e:00:53:af")
145
+ path_to_second_cluster_on_it = Path("00:00:5e:00:53:af", "1")
146
+ path_to_m0_block_in_cluster0 = Path("00:00:5e:00:53:af", "0", "M0")
147
+ path_to_first_func_on_block = Path("00:00:5e:00:53:af", "0", "M0", 0)
148
+ """
149
+ #: The schema defining the data types for the path's subcomponents.
150
+ SCHEMA = (str, str, str, int)
151
+
152
+ def to_carrier(self):
153
+ """Returns the path until the carrier board level."""
154
+ return Path(self[:1])
155
+
156
+ def to_cluster(self):
157
+ """
158
+ Returns the path until the cluster level.
159
+
160
+ Raises IndexError if path is not of sufficient depth.
161
+ """
162
+ return Path(self[:2])
163
+
164
+ def to_block(self):
165
+ """
166
+ Returns the path until the block level.
167
+
168
+ Raises IndexError if path is not of sufficient depth.
169
+ """
170
+ return Path(self[:3])
171
+
172
+ def to_function(self):
173
+ """
174
+ Returns the path until the function level.
175
+
176
+ Raises IndexError if path is not of sufficient depth.
177
+ """
178
+ return Path(self[:4])
179
+
180
+
181
+ @dataclass
182
+ class Entity(BaseEntity):
183
+ """
184
+ Base class for all entities inside a REDAC.
185
+ """
186
+ #: Unique path to this entity.
187
+ path: Path
188
+
189
+ @classmethod
190
+ def create_from_entity_type_tree(cls, sub_path, sub_tree):
191
+ raise NotImplementedError
192
+
193
+ def generate_partial_configuration(self, attribute):
194
+ if self.__dataclass_fields__.get(attribute, None):
195
+ return {attribute: getattr(self, attribute)}
196
+ else:
197
+ raise ValueError("Unknown attribute %s for %s." % (attribute, self.__class__))
198
+
199
+ def apply_partial_configuration(self, attribute, value):
200
+ raise NotImplementedError
@@ -0,0 +1,15 @@
1
+ # Copyright (c) 2022-2024 anabrid GmbH
2
+ # Contact: https://www.anabrid.com/licensing/
3
+ # SPDX-License-Identifier: MIT OR GPL-2.0-or-later
4
+
5
+ from dataclasses import dataclass
6
+
7
+
8
+ class ModuleType(str):
9
+ """Should probably be roughly equal to what we save in metadata storage"""
10
+ pass
11
+
12
+
13
+ @dataclass
14
+ class Module:
15
+ pass
@@ -0,0 +1,20 @@
1
+ """
2
+ The REDAC network protocol is a request-response message based protocol.
3
+ Messages are encoded as JSON strings and delimited by newline characters.
4
+ The message definition can be found in the :mod:`.messages` namespace.
5
+ Please refer to their documentations and the therein contained sequence diagrams
6
+ for details about the messaging process.
7
+
8
+ A usual sequence of messages for running one or multiple analog computations is:
9
+
10
+ #. Get the current hardware configuration via a :class:`~.messages.GetEntitiesRequest`.
11
+ #. Start a new user session (if required) via a :class:`~.messages.StartSessionRequest`.
12
+ #. Configure the analog computation via a series of :class:`~.messages.SetConfigRequest`.
13
+ #. Start the analog computation via a :class:`~.messages.StartRunRequest`.
14
+ #. Monitor the analog computation by listening to incoming :class:`~.messages.RunStateChangeMessage`
15
+ and :class:`~.messages.RunDataMessage` notifications.
16
+ #. The analog computation is done once you receive a :class:`~.messages.RunStateChangeMessage`
17
+ with :attr:`~.messages.RunStateChangeMessage.new` set to :attr:`~..run.RunState.DONE`.
18
+ #. Evaluate the data received and repeat from step 3 if necessary.
19
+ #. End your session with a :class:`~.messages.EndSessionRequest`.
20
+ """