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,180 @@
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 json
7
+ import logging
8
+ import typing
9
+ import uuid
10
+ from typing import Callable
11
+
12
+ from packaging.version import Version
13
+ from pybrid.base.hybrid.protocol import BaseProtocol, ProtocolError, MalformedDataError, UnsuccessfulRequestError
14
+ from pybrid.base.transport import BaseTransport
15
+
16
+ from ..entities import Path, Entity
17
+ from .envelope import Envelope
18
+ from .messages import Message, Request, Response, GetEntitiesRequest, GetConfigRequest, SetConfigRequest, \
19
+ StartRunRequest, HackRequest, SetDAQRequest, ResetRequest
20
+ from .serializer import build_config
21
+ from ..run import RunConfig, DAQConfig
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ class Protocol(BaseProtocol):
27
+
28
+ def __init__(self, transport: BaseTransport, version: Version = None):
29
+ super().__init__(transport, version)
30
+ self._receive_loop_task = None
31
+ self._expected_responses: dict[uuid, (Response, asyncio.futures.Future)] = dict()
32
+ self._callbacks: dict[typing.Type[Message], Callable] = dict()
33
+
34
+ async def start(self):
35
+ assert self._receive_loop_task is None
36
+ self._receive_loop_task = asyncio.create_task(self._receive_loop())
37
+
38
+ async def stop(self):
39
+ assert self._receive_loop_task is not None
40
+ self._receive_loop_task.cancel()
41
+ self.transport.close()
42
+ try:
43
+ await asyncio.gather(self._receive_loop_task, return_exceptions=False)
44
+ except asyncio.exceptions.CancelledError:
45
+ pass
46
+
47
+ # ███████ ███████ ███ ██ ██████ ██ ███ ██ ██████
48
+ # ██ ██ ████ ██ ██ ██ ██ ████ ██ ██
49
+ # ███████ █████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███
50
+ # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
51
+ # ███████ ███████ ██ ████ ██████ ██ ██ ████ ██████
52
+
53
+ async def send_message_and_wait_response(self, message, timeout=3):
54
+ response_fut = await self.send_message(message)
55
+ await asyncio.wait_for(response_fut, timeout=timeout)
56
+ return response_fut.result()
57
+
58
+ async def send_message(self, message):
59
+ # Generate an envelope
60
+ envelope = Envelope.from_message(message)
61
+ # The response to this envelope is a future
62
+ response_future = asyncio.get_event_loop().create_future()
63
+ # A response is only expected for requests
64
+ if isinstance(message, Request):
65
+ self._expected_responses[envelope.id] = (message.get_expected_response_type(), response_future)
66
+ else:
67
+ # But if the message is not a request, no response will ever come, just set result to None here
68
+ response_future.set_result(None)
69
+
70
+ # Send out data
71
+ data = envelope.json().encode('ascii')
72
+ await self.transport.send_line(data)
73
+
74
+ # Return future to response
75
+ return response_future
76
+
77
+ # ██████ ███████ ██████ ███████ ██ ██ ██ ██ ███ ██ ██████
78
+ # ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██
79
+ # ██████ █████ ██ █████ ██ ██ ██ ██ ██ ██ ██ ██ ███
80
+ # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
81
+ # ██ ██ ███████ ██████ ███████ ██ ████ ██ ██ ████ ██████
82
+
83
+ async def _receive_json(self) -> dict:
84
+ data = await self.transport.receive_line()
85
+ try:
86
+ data_json = json.loads(data.decode("ascii"))
87
+ except json.decoder.JSONDecodeError as exc:
88
+ raise MalformedDataError(data) from exc
89
+ else:
90
+ return data_json
91
+
92
+ async def _receive_message_and_process(self):
93
+ data = await self._receive_json()
94
+ envelope = Envelope(**data)
95
+ if envelope.id is not None and envelope.id in self._expected_responses:
96
+ expected_response_type, response_future = self._expected_responses[envelope.id]
97
+ if not envelope.success:
98
+ response_future.set_exception(UnsuccessfulRequestError(envelope.error))
99
+ else:
100
+ message = envelope.get_message()
101
+ if callback := self.get_callback(type(message)):
102
+ response_future.add_done_callback(lambda future: callback(future.result()))
103
+ response_future.set_result(message)
104
+ else:
105
+ message = envelope.get_message()
106
+ if callback := self.get_callback(type(message)):
107
+ callback(message)
108
+
109
+ async def _receive_loop(self):
110
+ while True:
111
+ try:
112
+ await self._receive_message_and_process()
113
+ except asyncio.CancelledError:
114
+ break
115
+ except asyncio.TimeoutError:
116
+ pass
117
+ except ProtocolError as exc:
118
+ logger.exception(
119
+ "Error while receiving or processing envelope: %s.", exc
120
+ )
121
+ except Exception as exc:
122
+ logger.exception(
123
+ "Unknown error: %s.", exc
124
+ )
125
+
126
+ # ██████ █████ ██ ██ ██████ █████ ██████ ██ ██ ███████
127
+ # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
128
+ # ██ ███████ ██ ██ ██████ ███████ ██ █████ ███████
129
+ # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
130
+ # ██████ ██ ██ ███████ ███████ ██████ ██ ██ ██████ ██ ██ ███████
131
+
132
+ def register_callback(self, msg_type: typing.Type[Message], callback: Callable):
133
+ self._callbacks[msg_type] = callback
134
+
135
+ def get_callback(self, msg_type: typing.Type[Message]):
136
+ return self._callbacks.get(msg_type, None)
137
+
138
+ # ██████ ██████ ███ ███ ███ ███ █████ ███ ██ ██████ ███████
139
+ # ██ ██ ██ ████ ████ ████ ████ ██ ██ ████ ██ ██ ██ ██
140
+ # ██ ██ ██ ██ ████ ██ ██ ████ ██ ███████ ██ ██ ██ ██ ██ ███████
141
+ # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
142
+ # ██████ ██████ ██ ██ ██ ██ ██ ██ ██ ████ ██████ ███████
143
+
144
+ async def hack_request(self, cmd: str, data: typing.Any) -> typing.Any:
145
+ response = await self.send_message_and_wait_response(HackRequest(command=cmd, data=data))
146
+ return response.data
147
+
148
+ async def get_entities(self) -> dict:
149
+ response = await self.send_message_and_wait_response(GetEntitiesRequest())
150
+ return response.entities
151
+
152
+ async def get_config(self, entity: Path, recursive: bool = True) -> dict:
153
+ response = await self.send_message_and_wait_response(GetConfigRequest(entity=entity, recursive=recursive))
154
+ return response.config
155
+
156
+ async def set_config_request(self, entity: Path, config: dict, session: uuid.uuid4 = None) -> bool:
157
+ await self.send_message_and_wait_response(
158
+ SetConfigRequest(entity=entity, config=config, session=session))
159
+ return True
160
+
161
+ async def set_config(self, entity: Entity):
162
+ config = dict()
163
+ build_config(entity, config)
164
+ await self.set_config_request(entity=entity.path, config=config)
165
+ return True
166
+
167
+ async def set_daq_request(self, daq: DAQConfig, session: typing.Optional[uuid.UUID] = None):
168
+ await self.send_message_and_wait_response(
169
+ SetDAQRequest(daq=daq)
170
+ )
171
+
172
+ async def start_run_request(self, id_: uuid.UUID, config: RunConfig, daq_config: DAQConfig = None):
173
+ await self.send_message_and_wait_response(
174
+ StartRunRequest(id=id_, config=config, daq_config=daq_config, session=None)
175
+ )
176
+
177
+ async def reset(self, keep_calibration: bool = True, sync: bool = True):
178
+ await self.send_message_and_wait_response(
179
+ ResetRequest(keep_calibration=keep_calibration, sync=sync)
180
+ )
@@ -0,0 +1,52 @@
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 asdict
6
+ from functools import singledispatch
7
+
8
+ from ..blocks import ElementBlock, SwitchingBlock, CBlock
9
+ from ..elements import ComputationElement
10
+ from ..entities import Entity
11
+
12
+
13
+ def exclude(*fields):
14
+ def _dict_factory_excluding(data):
15
+ return {k: v for (k, v) in data if k not in fields}
16
+
17
+ return _dict_factory_excluding
18
+
19
+
20
+ def build_config(entity: Entity, config: dict):
21
+ entity_config, convert_children = to_dict(entity)
22
+ config.update(entity_config)
23
+ if convert_children:
24
+ for child in entity.children:
25
+ config["/" + str(child.id_)] = child_config = dict()
26
+ build_config(child, child_config)
27
+ return config
28
+
29
+
30
+ @singledispatch
31
+ def to_dict(entity: Entity):
32
+ return {}, True
33
+
34
+
35
+ @to_dict.register
36
+ def _(entity: ComputationElement):
37
+ return asdict(entity.computation), False
38
+
39
+
40
+ @to_dict.register
41
+ def _(entity: ElementBlock):
42
+ return {"elements": [to_dict(element)[0] for element in entity.elements]}, False
43
+
44
+
45
+ @to_dict.register
46
+ def _(entity: CBlock):
47
+ return {"elements": [element.computation.factor for element in entity.elements]}, False
48
+
49
+
50
+ @to_dict.register
51
+ def _(entity: SwitchingBlock):
52
+ return asdict(entity, dict_factory=exclude('path')), False
@@ -0,0 +1,17 @@
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
+
7
+ from pydantic import BaseModel
8
+
9
+
10
+ class SuccessInfo(BaseModel):
11
+ """
12
+ Data type used in responses indicating whether the associated request was successful.
13
+ """
14
+ #: :code:`True` if response was successful, :code:`False` otherwise.
15
+ success: bool
16
+ #: An optional error message. Always None if success is :code:`True`.
17
+ error: typing.Optional[str]
pybrid/redac/run.py ADDED
@@ -0,0 +1,131 @@
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 collections import defaultdict
7
+ from dataclasses import dataclass, field
8
+ from enum import Enum
9
+ from uuid import UUID, uuid4
10
+
11
+ from pybrid.base.hybrid import BaseRun, BaseRunConfig, BaseRunFlags, BaseRunState, BaseDAQConfig
12
+ from pybrid.base.utils.descriptors import Validator
13
+
14
+ from .entities import Path
15
+
16
+
17
+ class RunError(Exception):
18
+ """Base exception class for all errors during a :class:`Run`."""
19
+ pass
20
+
21
+
22
+ @dataclass(kw_only=True)
23
+ class RunConfig(BaseRunConfig):
24
+ """Configures parameters related to the execution of one :class:`Run`."""
25
+ #: Duration of IC (initial condition) mode in nanoseconds.
26
+ ic_time: int = 100_000
27
+ #: Duration of OP (operating) mode in nanoseconds.
28
+ op_time: int = 2_000_000
29
+
30
+ #: Whether to halt the computation when the external halt signal is triggered.
31
+ halt_on_external_trigger: bool = False
32
+ #: Whether to halt the computation when it enters an overload.
33
+ halt_on_overload: bool = True
34
+
35
+
36
+ @dataclass(kw_only=True)
37
+ class RunFlags(BaseRunFlags):
38
+ """
39
+ Flags that can be triggered by a :class:`Run`.
40
+ Once triggered, they are persistently `True`, even when the original condition is lost.
41
+ """
42
+ #: Whether the run was halted because of an external halt trigger.
43
+ externally_halted: bool = False
44
+ #: Any element that entered an overload during computation.
45
+ overloaded: typing.Optional[list[Path]] = None
46
+
47
+
48
+ class RunState(BaseRunState, Enum):
49
+ """The state of a :class:`Run`."""
50
+ #: Run has just been created.
51
+ NEW = "NEW"
52
+ #: Run has encountered an error and has been aborted.
53
+ ERROR = "ERROR"
54
+ #: Run has successfully finished.
55
+ DONE = "DONE"
56
+ #: Run is queued for execution.
57
+ QUEUED = "QUEUED"
58
+ #: Run has been selected for execution and is being prepared to start.
59
+ TAKE_OFF = "TAKE_OFF"
60
+ #: Run is in IC (initial condition) mode.
61
+ IC = "IC"
62
+ #: Run is in OP (operating) mode.
63
+ OP = "OP"
64
+ #: Run is principally done, pending final data acquisition or other finalizing tasks.
65
+ OP_END = "OP_END"
66
+ #: Run is temporarily halted and can be resumed.
67
+ TMP_HALT = "TMP_HALT"
68
+
69
+ @classmethod
70
+ def default(cls):
71
+ return cls.NEW
72
+
73
+ @classmethod
74
+ def get_possibly_sampled_states(cls):
75
+ """Return a list of states in which data acquisition is possible."""
76
+ return cls.IC, cls.OP, cls.OP_END
77
+
78
+ def is_done(self):
79
+ return self in (RunState.DONE, RunState.ERROR)
80
+
81
+
82
+ class DAQConfigurationNumChannels(Validator):
83
+
84
+ def set_default(self, instance, name, owner):
85
+ setattr(instance, name, 0)
86
+
87
+ def parse(self, instance, value):
88
+ return int(value)
89
+
90
+ def validate(self, instance, value):
91
+ if value > 0 and (value & (value - 1)) != 0:
92
+ raise ValueError("Value must be a power-of-two.")
93
+
94
+
95
+ @dataclass(kw_only=True)
96
+ class DAQConfig(BaseDAQConfig):
97
+ #: List of channels that should be sampled.
98
+ #: The element corresponding to each channel is implicitly defined by the computer's configuration.
99
+ num_channels: int = field(default=DAQConfigurationNumChannels())
100
+ #: Sample rate to use in samples/second.
101
+ sample_rate: int = 500_000
102
+ #: Whether to sample during IC
103
+ sample_op: bool = True
104
+ #: Whether to sample during OP_END
105
+ sample_op_end: bool = True
106
+
107
+
108
+ @dataclass(kw_only=True)
109
+ class Run(BaseRun):
110
+ """A run is one computation executed by the REDAC."""
111
+ #: A unique identifier for the run.
112
+ id_: UUID = field(default_factory=uuid4)
113
+ #: Possibly the ID of a related run, e.g. one that triggered this one.
114
+ related_to: typing.Optional[UUID] = None
115
+ #: Defines the duration the run should be executed and similar parameters.
116
+ #: Does not contain element configuration.
117
+ config: RunConfig = field(default_factory=RunConfig)
118
+
119
+ #: The current state of the run.
120
+ state: RunState = RunState.NEW
121
+ #: Flags, e.g. overload, the run has triggered. These are persistent once triggered.
122
+ flags: RunFlags = field(default_factory=RunFlags)
123
+
124
+ #: The configuration of the data acquisition for this run.
125
+ daq: DAQConfig = field(default_factory=DAQConfig)
126
+ #: Data captured for this run.
127
+ data: dict[Path | str, list[float]] = field(default_factory=lambda: defaultdict(list))
128
+
129
+ @classmethod
130
+ def get_persistent_attributes(cls) -> set[str]:
131
+ return super().get_persistent_attributes().union({"daq"})
@@ -0,0 +1,136 @@
1
+ Metadata-Version: 2.1
2
+ Name: pybrid-computing
3
+ Version: 0.10.0
4
+ Summary: Library to interact with analog computers and analog-digital hybrid computers, in particular the LUCIDAC and REDAC computers made by anabrid
5
+ Home-page: https://anabrid.com/
6
+ License: MIT OR GPL-2.0-or-later
7
+ Author: Michael Steck
8
+ Author-email: pybrid-code@anabrid.com
9
+ Requires-Python: >=3.10,<4.0
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: End Users/Desktop
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: License :: Other/Proprietary License
16
+ Classifier: Operating System :: POSIX
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Communications
22
+ Classifier: Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator
23
+ Classifier: Topic :: Software Development :: Embedded Systems
24
+ Classifier: Topic :: Terminals :: Serial
25
+ Requires-Dist: asyncclick (>=8.1.3.4,<9.0.0.0)
26
+ Requires-Dist: inflection (>=0.5.1,<0.6.0)
27
+ Requires-Dist: matplotlib[examples] (>=3.5.3,<4.0.0)
28
+ Requires-Dist: pybrid-computing-base (>=0.10.0,<1)
29
+ Requires-Dist: pydantic (>=1.10.5,<2.0.0)
30
+ Requires-Dist: pyqt5-qt5 (==5.15.2) ; sys_platform == "linux"
31
+ Requires-Dist: pyqt5[examples] (>=5.15.2,<6.0.0)
32
+ Description-Content-Type: text/markdown
33
+
34
+ # pybrid-computing: The python hybrid computing code
35
+
36
+ *pybrid* is a code that provides an interface to configure and control analog computers,
37
+ in particular the network-enabled LUCIDAC/REDAC analog-digital hybrid computers made by
38
+ [anabrid](https://anabrid.com/).
39
+ The abstractions can be used by means of
40
+
41
+ * a fully [asyncio](https://docs.python.org/3/library/asyncio.html) library
42
+ * a framework (users providing callbacks in a clean class structure, however giving
43
+ up most of the program flow control) called *Read-Eval-Configure-Loop* (RECL)
44
+ * a command line interface using [click](https://click.palletsprojects.com)
45
+
46
+ This python library provides the reference implementation for the LUCIDAC/REDAC
47
+ communication protocol (a simple ASCII yet fully asynchronous RPC message passing protocol
48
+ ontop of JSONL) by means of [pydantic](https://docs.pydantic.dev/latest/) data type modeling.
49
+ In particular, it is the reference implementation of a client communicating with a
50
+ [LUCIDAC hybrid controller MCU](https://anabrid.dev/docs/hybrid-controller/) via
51
+ USB Serial or TCP/IP.
52
+
53
+ The code is successfully used on modern day GNU/Linux, Mac OS X and MS Windows.
54
+
55
+ This code is written and maintained by [anabrid GmbH](https://anabrid.com/). It is released
56
+ as open source (dual licensed as `MIT` and `GPL>=2`). It is supposed to be released at Github
57
+ (**TODO**).
58
+
59
+ ## Installation
60
+
61
+ Simply run `pip install pybrid-computing`. There is an explicit dependency to
62
+ `pybrid-computing-base` released and maintained by the same authors, all other dependencies
63
+ are widespread and thus relatively standard python packages.
64
+
65
+ Installation should make a command line executable `pybrid` available on your shell. You can
66
+ explore it by calling `pybrid` on your shell, which gives you, for instance:
67
+
68
+ ```
69
+ % pybrid redac
70
+ Usage: pybrid redac [OPTIONS] COMMAND [ARGS]...
71
+
72
+ Entrypoint for all REDAC commands.
73
+
74
+ Use :code:`pybrid redac --help` to list all available sub-commands.
75
+
76
+ Options:
77
+ -h, --host TEXT Network name or address of the REDAC.
78
+ -p, --port INTEGER Network port of the REDAC.
79
+ --reset / --no-reset Whether to reset the REDAC after connecting.
80
+ [default: reset]
81
+ --help Show this message and exit.
82
+
83
+ Commands:
84
+ display Display the hardware structure of the REDAC.
85
+ get-entity-config Get the configuration of an entity.
86
+ hack Collects 'hack' commands, for development purposes...
87
+ reset Reset the REDAC to initial configuration.
88
+ route Route a signal on one cluster from one output of...
89
+ run Start a run (computation) and wait until it is...
90
+ set-alias Define an alias for a path in an interactive...
91
+ set-connection Set one or multiple connections in a U-Block or...
92
+ set-daq Configure data acquisition of subsequent run commands.
93
+ set-element-config Set one ATTRIBUTE to VALUE of the configuration of...
94
+ shell Start an interactive shell and/or execute a REDAC...
95
+ user-program
96
+ ```
97
+
98
+ ## Documentation
99
+
100
+ > **You can read the documentation online at https://anabrid.dev/docs/pybrid/html/**
101
+
102
+ It is generated by the [Sphinx](https://www.sphinx-doc.org/) documentation which
103
+ is part of this repository/package.
104
+
105
+ ## Getting started as developer
106
+
107
+ This project is managed via the [poetry](https://python-poetry.org/) dependency
108
+ manager for python. It allows for easy dependency installation, etc. You should
109
+ make yourself comfortable with poetry (https://python-poetry.org/docs/basic-usage/).
110
+
111
+ Note that the code is distributed amongst the `pybrid-computing` and `pybrid-computing-base`
112
+ repositories. If you enter `poetry install` while being in one of these repositories with
113
+ your shell, you can work on *that* python package "life". If you want to work on both of
114
+ them, go to the `pybrid-computing` and edit the
115
+ [path dependencies](https://python-poetry.org/docs/dependency-specification/#path-dependencies)
116
+ in the `pyproject.toml`, for instance by replacing the line
117
+
118
+ ```
119
+ ...
120
+ [tool.poetry.dependencies]
121
+ ...
122
+ pybrid-computing-base = "^0, >=0.10.0"
123
+ ...
124
+ ```
125
+
126
+ with something as in
127
+
128
+ ```
129
+ pybrid-computing-base = { path = "/path/to/where/you/checked/out/pybrid-computing-base", develop = true } # instead of "^0, >=0.10.0"
130
+ ```
131
+
132
+ After this edit, run `poetry install` again to update the paths.
133
+
134
+ Some IDEs such as PyCharm can check this out by their own.
135
+
136
+
@@ -0,0 +1,28 @@
1
+ pybrid/cli/redac/__init__.py,sha256=dPUoJYWb2KcBzTbwomk1XzKTdKmyp02xBllejHOitS8,13601
2
+ pybrid/redac/__init__.py,sha256=lOZOsXbBUcTO9YRDKBFw-odpvPWV9h3U4tsy3fkejo8,234
3
+ pybrid/redac/blocks/__init__.py,sha256=Ur__3BVOaVR4P16oYHPtOX3Vemgz-9e4UEpKJ6-eV7g,319
4
+ pybrid/redac/blocks/block.py,sha256=np_0lw4uT4W2SoYEcDiEEPNDntBy5h4qwd4fzicIFPo,1718
5
+ pybrid/redac/blocks/cblock.py,sha256=_GJP-Lx_Fx0s15mPvTEl73wGtaFJw8IgCeFtV2LzFcY,945
6
+ pybrid/redac/blocks/iblock.py,sha256=ZOWfJjtiKwwWf7gDDpJO5JzwinBw9bYtJfGRR77ljfQ,1758
7
+ pybrid/redac/blocks/mblock.py,sha256=36Ys9DZlVLkYBzMtkL7celivwGAAqvcQWB0FkrnwxN4,1146
8
+ pybrid/redac/blocks/ublock.py,sha256=-RX7-3ZRHEphyTtyo_xsBviTANiWTCcwzwa7mWI6ejw,2201
9
+ pybrid/redac/carrier.py,sha256=5aNMRpb5hqP_gv1YL7AooJmWfe2FPxxpL3SWsUdUjvo,1471
10
+ pybrid/redac/cluster.py,sha256=KLWH4hSciQtIjYtmMTRQCzLvhf5jDns9cUpADVtmgLM,3615
11
+ pybrid/redac/computations.py,sha256=8sn6xabMSY1jfQLCCu6vfb7zVdfscA18QdQOd2GYoqo,1013
12
+ pybrid/redac/computer.py,sha256=t5td9pCoMMDLO5u9LOsXqbw46Q8hgb63brk51CQxVJs,1145
13
+ pybrid/redac/controller.py,sha256=wmuE03YUW1a9zthEwvZeCwJhW0YPu8b2w4iZgBSrMY0,6192
14
+ pybrid/redac/data.py,sha256=VWQS7OKxa5H2S0jz8dyNauorWK9q8ZgPqW-CW3kzMXs,1796
15
+ pybrid/redac/display.py,sha256=hEhRM4JzX8oN_jv9wa4uuP7oX8WDbSGBXmtYqH6iP_Q,989
16
+ pybrid/redac/elements.py,sha256=AcX7iDYI_f2ZwsfmFCTJClPqI1MUYKBX0ZDhizXQvGE,1107
17
+ pybrid/redac/entities.py,sha256=owgwBn0aASsgI7krRwWrvlDBiqmOH8f3giaTD3-nWNs,7678
18
+ pybrid/redac/modules.py,sha256=5tiw-gVKeos78MUwNTKezVvVTd55_4RPFE-e5IthdOw,320
19
+ pybrid/redac/protocol/__init__.py,sha256=PxRiShb7MZOYkTfrW5yyPBi0NiqYEp3UoBTESGzH5ac,1248
20
+ pybrid/redac/protocol/envelope.py,sha256=2f5yMI_k0MFd6vzXJg7YrV_ed9n_VIXYYt6jnNSdasY,2522
21
+ pybrid/redac/protocol/messages.py,sha256=AOywn4Sfuw7FQ-8kmb0xW4X7EtpnDgOYNgEGBgIIueA,27014
22
+ pybrid/redac/protocol/protocol.py,sha256=b__SNU28cHdJaxD1HK6eLWme7jjuObtE4GTKV9GmBNY,9214
23
+ pybrid/redac/protocol/serializer.py,sha256=-xHfCxtnrhlYCuffJ0UGcOhhUKJLog5Kmj0l1C-KYyg,1374
24
+ pybrid/redac/protocol/types.py,sha256=IePxgxzwSGACVnTlEyuV4otwwLz8bJ_y-TVYP4BNOrM,518
25
+ pybrid/redac/run.py,sha256=rcPHz1C8Pam6Qxac6EiE6qy7ICYEXPJFMGnd1mT8ups,4518
26
+ pybrid_computing-0.10.0.dist-info/METADATA,sha256=I3euYeCOEiy_9KOvK8FXR9tuw346C5n2rAQVitH4Kj4,5907
27
+ pybrid_computing-0.10.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
28
+ pybrid_computing-0.10.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 1.9.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any