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.
- pybrid/cli/redac/__init__.py +356 -0
- pybrid/redac/__init__.py +6 -0
- pybrid/redac/blocks/__init__.py +9 -0
- pybrid/redac/blocks/block.py +61 -0
- pybrid/redac/blocks/cblock.py +22 -0
- pybrid/redac/blocks/iblock.py +38 -0
- pybrid/redac/blocks/mblock.py +37 -0
- pybrid/redac/blocks/ublock.py +47 -0
- pybrid/redac/carrier.py +43 -0
- pybrid/redac/cluster.py +83 -0
- pybrid/redac/computations.py +30 -0
- pybrid/redac/computer.py +42 -0
- pybrid/redac/controller.py +141 -0
- pybrid/redac/data.py +64 -0
- pybrid/redac/display.py +25 -0
- pybrid/redac/elements.py +28 -0
- pybrid/redac/entities.py +200 -0
- pybrid/redac/modules.py +15 -0
- pybrid/redac/protocol/__init__.py +20 -0
- pybrid/redac/protocol/envelope.py +62 -0
- pybrid/redac/protocol/messages.py +726 -0
- pybrid/redac/protocol/protocol.py +180 -0
- pybrid/redac/protocol/serializer.py +52 -0
- pybrid/redac/protocol/types.py +17 -0
- pybrid/redac/run.py +131 -0
- pybrid_computing-0.10.0.dist-info/METADATA +136 -0
- pybrid_computing-0.10.0.dist-info/RECORD +28 -0
- pybrid_computing-0.10.0.dist-info/WHEEL +4 -0
|
@@ -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,,
|