PyDecisionGraph 0.1.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.

Potentially problematic release.


This version of PyDecisionGraph might be problematic. Click here for more details.

@@ -0,0 +1,307 @@
1
+ from __future__ import annotations
2
+
3
+ import enum
4
+ import uuid
5
+ from typing import Literal, Any, Self
6
+
7
+ from . import AttrExpression, LogicMapping
8
+ from .abc import LogicGroup, SkipContextsBlock
9
+
10
+ __all__ = ['SignalLogicGroup', 'InstantConfirmationLogicGroup', 'RequestAction', 'PendingRequest', 'DelayedConfirmationLogicGroup', 'RacingConfirmationLogicGroup', 'BarrierConfirmationLogicGroup']
11
+
12
+
13
+ class SignalLogicGroup(LogicGroup):
14
+ def __init__(self, name: str, parent: Self = None, contexts: dict[str, Any] = None):
15
+ super().__init__(name=name, parent=parent, contexts=contexts)
16
+
17
+ def get(self, attr: str, dtype: type = None, repr: str = None):
18
+ """
19
+ Retrieve an attribute as a LogicExpression.
20
+ """
21
+ return AttrExpression(attr=attr, logic_group=self, dtype=dtype, repr=repr)
22
+
23
+ def reset(self):
24
+ self.signal = 0
25
+
26
+ @property
27
+ def signal(self):
28
+ return self.contexts.get('signal', 0)
29
+
30
+ @signal.setter
31
+ def signal(self, value: int):
32
+ self.contexts['signal'] = value
33
+
34
+
35
+ class InstantConfirmationLogicGroup(SignalLogicGroup):
36
+ def __init__(self, parent: SignalLogicGroup, name: str = None):
37
+ super().__init__(
38
+ name=f'{parent.name}.Instant' if name is None else name,
39
+ parent=parent
40
+ )
41
+
42
+ def reset(self):
43
+ pass
44
+
45
+ def confirm(self, sig: Literal[-1, 1]):
46
+ self.signal = sig
47
+ return
48
+
49
+ @property
50
+ def signal(self):
51
+ return self.parent.signal
52
+
53
+ @signal.setter
54
+ def signal(self, value: int):
55
+ self.parent.signal = value
56
+
57
+
58
+ class RequestAction(enum.StrEnum):
59
+ open = enum.auto()
60
+ unwind = enum.auto()
61
+ idle = enum.auto()
62
+
63
+
64
+ class PendingRequest(dict):
65
+ class Skip(Exception):
66
+ pass
67
+
68
+ def __init__(
69
+ self,
70
+ name: str | RequestAction,
71
+ timestamp: float,
72
+ sig: Literal[-1, 1] | int,
73
+ action: str,
74
+ timeout: float,
75
+ logic_group: LogicGroup = None,
76
+ uid: uuid.UUID = None,
77
+ **kwargs
78
+ ):
79
+ super().__init__(
80
+ name=name,
81
+ timestamp=timestamp,
82
+ sig=sig,
83
+ timeout=timeout,
84
+ action=RequestAction(action),
85
+ uid=uuid.uuid4() if uid is None else uid,
86
+ **kwargs
87
+ )
88
+
89
+ self.logic_group = logic_group
90
+
91
+ @classmethod
92
+ def empty(cls) -> PendingRequest:
93
+ return PendingRequest(
94
+ name='DummyRequest',
95
+ timestamp=0,
96
+ sig=0,
97
+ action=RequestAction.idle,
98
+ timeout=0,
99
+ uid=uuid.UUID(int=0)
100
+ )
101
+
102
+ def __bool__(self):
103
+ if not self.sig:
104
+ return False
105
+ return True
106
+
107
+ @property
108
+ def name(self) -> str:
109
+ return self['name']
110
+
111
+ @property
112
+ def timestamp(self) -> float:
113
+ return self['timestamp']
114
+
115
+ @property
116
+ def sig(self) -> int:
117
+ return self['sig']
118
+
119
+ @property
120
+ def timeout(self) -> float:
121
+ return self['timeout']
122
+
123
+ @property
124
+ def action(self) -> RequestAction:
125
+ return self['action']
126
+
127
+ @property
128
+ def uid(self) -> uuid.UUID:
129
+ return self['uid']
130
+
131
+
132
+ class DelayedConfirmationLogicGroup(SignalLogicGroup):
133
+ def __init__(self, parent: SignalLogicGroup, name: str = None):
134
+ super().__init__(
135
+ name=f'{parent.name}.Delayed' if name is None else name,
136
+ parent=parent
137
+ )
138
+
139
+ def register(self, name: str, timestamp: float, sig: Literal[1, -1], timeout: float | None, action: Literal['open', 'unwind'] = 'open', uid: uuid.UUID = None, **kwargs):
140
+ req = self.pending_request = PendingRequest(
141
+ name=name,
142
+ logic_group=self,
143
+ timestamp=timestamp,
144
+ sig=sig,
145
+ timeout=timeout,
146
+ action=action,
147
+ uid=uid,
148
+ **kwargs
149
+ )
150
+
151
+ return req
152
+
153
+ def confirm(self):
154
+ req = self.contexts.get('pending_request')
155
+ sig = 0 if req is None else req.sig
156
+ self.reset()
157
+ self.signal = sig
158
+ return sig
159
+
160
+ def deny(self):
161
+ # denying all the pending request
162
+ self.reset()
163
+ return 0
164
+
165
+ def reset(self):
166
+ # self.pending_request = PendingRequest.empty()
167
+ self.contexts.pop('pending_request', None)
168
+ super().reset()
169
+
170
+ @property
171
+ def action(self) -> RequestAction:
172
+ if 'pending_request' in self.contexts:
173
+ return self.pending_request.action
174
+
175
+ return RequestAction.idle
176
+
177
+ @property
178
+ def pending_request(self) -> LogicMapping | SkipContextsBlock:
179
+
180
+ if (req := self.contexts.get('pending_request')) is None:
181
+ return SkipContextsBlock(True)
182
+
183
+ m = LogicMapping(
184
+ data=req,
185
+ name=f'{self.name}.PendingRequest.{req.uid.hex}',
186
+ logic_group=self
187
+ )
188
+
189
+ return m
190
+
191
+ @pending_request.setter
192
+ def pending_request(self, value: PendingRequest):
193
+ assert isinstance(value, PendingRequest)
194
+ self.contexts['pending_request'] = value
195
+
196
+ @property
197
+ def signal(self):
198
+ return self.parent.signal
199
+
200
+ @signal.setter
201
+ def signal(self, value: Literal[-1, 0, 1]):
202
+ assert isinstance(value, (int, float))
203
+ self.parent.signal = value
204
+
205
+
206
+ class RacingConfirmationLogicGroup(DelayedConfirmationLogicGroup):
207
+
208
+ def __init__(self, parent: SignalLogicGroup, name: str = None):
209
+ super().__init__(
210
+ name=f'{parent.name}.Racing' if name is None else name,
211
+ parent=parent
212
+ )
213
+
214
+ def __getitem__(self, uid: uuid.UUID | str | bytes | int) -> PendingRequest:
215
+ if not (request_pool := self.pending_request):
216
+ raise KeyError(f'uid {uid} not found!')
217
+
218
+ match uid:
219
+ case uuid.UUID():
220
+ for _pending_request in request_pool:
221
+ if _pending_request.uid == uid:
222
+ return _pending_request
223
+ raise KeyError(f'uid {uid} not found!')
224
+ case str():
225
+ for _pending_request in request_pool:
226
+ if _pending_request.uid.hex == uid:
227
+ return _pending_request
228
+ raise KeyError(f'uid {uid} not found!')
229
+ case bytes():
230
+ for _pending_request in request_pool:
231
+ if _pending_request.uid.bytes == uid:
232
+ return _pending_request
233
+ raise KeyError(f'uid {uid} not found!')
234
+ case int():
235
+ return request_pool[uid]
236
+ case _:
237
+ raise TypeError(f'Invalid uid {uid}! Expected UUID or bytes or str!')
238
+
239
+ def register(self, name: str, timestamp: float, sig: Literal[1, -1], timeout: float, action: Literal['open', 'unwind'] = 'open', uid: uuid.UUID = None, **kwargs):
240
+ self.pending_request.append(
241
+ PendingRequest(
242
+ name=name,
243
+ timestamp=timestamp,
244
+ sig=sig,
245
+ timeout=timeout,
246
+ action=action,
247
+ uid=uid,
248
+ **kwargs
249
+ )
250
+ )
251
+
252
+ def confirm(self, pending_request: PendingRequest = None, uid: uuid.UUID = None):
253
+ if pending_request is None and uid is None:
254
+ assert len(self.pending_request) == 1, ValueError('Multiple pending requests found! Must assign uid or pending_request instance!')
255
+ pending_request = self.pending_request[0]
256
+ elif pending_request is None:
257
+ pending_request = self.__getitem__(uid=uid)
258
+
259
+ sig = pending_request.sig
260
+ self.reset()
261
+ self.signal = sig
262
+ return sig
263
+
264
+ def deny(self, pending_request: PendingRequest = None, uid: uuid.UUID = None):
265
+ # denying all the pending request
266
+ if pending_request is None and uid is None:
267
+ self.pending_request.clear()
268
+ self.signal = 0
269
+ return
270
+
271
+ if pending_request is not None:
272
+ self.pending_request.remove(pending_request)
273
+ self.signal = 0
274
+
275
+ if uid is not None:
276
+ pending_request = self.__getitem__(uid=uid)
277
+ self.pending_request.remove(pending_request)
278
+ self.signal = 0
279
+
280
+ @property
281
+ def pending_request(self) -> list[PendingRequest]:
282
+ return self.contexts.setdefault('pending_request', [])
283
+
284
+
285
+ class BarrierConfirmationLogicGroup(RacingConfirmationLogicGroup):
286
+
287
+ def __init__(self, parent: SignalLogicGroup, name: str = None):
288
+ super().__init__(
289
+ name=f'{parent.name}.Barrier' if name is None else name,
290
+ parent=parent
291
+ )
292
+
293
+ def confirm(self, pending_request: PendingRequest = None, uid: uuid.UUID = None):
294
+ if pending_request is None and uid is None:
295
+ assert len(self.pending_request) == 1, ValueError('Multiple pending requests found! Must assign uid or pending_request instance!')
296
+ pending_request = self.pending_request[0]
297
+ elif pending_request is None:
298
+ pending_request = self.__getitem__(uid=uid)
299
+
300
+ self.pending_request.remove(pending_request)
301
+
302
+ if self.pending_request:
303
+ return 0
304
+
305
+ sig = pending_request.sig
306
+ self.signal = sig
307
+ return sig
decision_tree/node.py ADDED
@@ -0,0 +1,180 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABCMeta
4
+ from collections.abc import Callable
5
+ from typing import Any, Self
6
+
7
+ from .abc import LogicGroup, LogicNode, ActionNode, LGM, NO_CONDITION
8
+ from .exc import TooManyChildren, TooFewChildren
9
+ from .expression import ContextLogicExpression as _CLE, AttrExpression as _AE, MathExpression as _ME, ComparisonExpression as _CE, LogicalExpression as _LE
10
+
11
+ __all__ = ['NoAction', 'LongAction', 'ShortAction', 'RootLogicNode', 'ContextLogicExpression', 'AttrExpression', 'MathExpression', 'ComparisonExpression', 'LogicalExpression']
12
+
13
+
14
+ class NoAction(ActionNode):
15
+ def __init__(self, auto_connect: bool = True):
16
+ super().__init__(
17
+ action=None,
18
+ repr='<NoAction>',
19
+ auto_connect=auto_connect
20
+ )
21
+
22
+ def eval(self, enforce_dtype: bool = False) -> ActionNode:
23
+ return self
24
+
25
+
26
+ class LongAction(ActionNode):
27
+ def __init__(self, sig: int = 1, auto_connect: bool = True):
28
+ super().__init__(
29
+ action=sig,
30
+ repr=f'<LongAction>(sig = {sig})',
31
+ auto_connect=auto_connect
32
+ )
33
+
34
+ def eval(self, enforce_dtype: bool = False) -> ActionNode:
35
+ return self
36
+
37
+
38
+ class ShortAction(ActionNode):
39
+ def __init__(self, sig: int = -1, auto_connect: bool = True):
40
+ super().__init__(
41
+ action=sig,
42
+ repr=f'<ShortAction>(sig = {sig})',
43
+ auto_connect=auto_connect
44
+ )
45
+
46
+ def eval(self, enforce_dtype: bool = False) -> ActionNode:
47
+ return self
48
+
49
+
50
+ class RootLogicNode(LogicNode):
51
+ def __init__(self):
52
+ super().__init__(
53
+ expression=True,
54
+ repr=f'Entry Point'
55
+ )
56
+
57
+ def _entry_check(self):
58
+ return True
59
+
60
+ def _on_enter(self):
61
+ self._inspection_mode = LGM.inspection_mode
62
+ LGM.inspection_mode = True
63
+ LGM._active_nodes.clear()
64
+ LGM.enter_expression(node=self)
65
+
66
+ def _on_exit(self):
67
+ LGM.exit_expression(node=self)
68
+ if hasattr(self, '_inspection_mode'):
69
+ LGM.inspection_mode = self._inspection_mode
70
+ else:
71
+ LGM.inspection_mode = False
72
+
73
+ def append(self, expression: Self, edge_condition: Any = None):
74
+ if self.nodes:
75
+ raise TooManyChildren()
76
+ super().append(expression=expression, edge_condition=NO_CONDITION)
77
+
78
+ def eval_recursively(self, **kwargs):
79
+ return self.child.eval_recursively(**kwargs)
80
+
81
+ def to_html(self, with_group=True, dry_run=True, filename="decision_tree.html", **kwargs):
82
+ return self.child.to_html(with_group=with_group, dry_run=dry_run, filename=filename, **kwargs)
83
+
84
+ @property
85
+ def child(self) -> LogicNode:
86
+ if self.nodes:
87
+ return self.last_node
88
+
89
+ raise TooFewChildren()
90
+
91
+
92
+ class ContextLogicExpression(_CLE, LogicNode, metaclass=ABCMeta):
93
+ def __init__(self, expression: float | int | bool | Exception | Callable[[], Any], dtype: type = None, repr: str = None, logic_group: LogicGroup = None):
94
+ super().__init__(expression=expression, dtype=dtype, repr=repr, logic_group=logic_group)
95
+ LogicNode.__init__(self=self, expression=expression, dtype=dtype, repr=repr)
96
+
97
+ # magic method to invoke AttrExpression
98
+ def __getitem__(self, key: str) -> AttrExpression:
99
+ return AttrExpression(attr=key, logic_group=self.logic_group)
100
+
101
+ def __getattr__(self, key: str) -> AttrExpression:
102
+ return AttrExpression(attr=key, logic_group=self.logic_group)
103
+
104
+ # math operation to invoke MathExpression
105
+
106
+ def __add__(self, other: int | float | bool | Self) -> Self:
107
+ return MathExpression(left=self, op=MathExpression.Operator.add, right=other, logic_group=self.logic_group)
108
+
109
+ def __sub__(self, other: int | float | bool | Self) -> Self:
110
+ return MathExpression(left=self, op=MathExpression.Operator.sub, right=other, logic_group=self.logic_group)
111
+
112
+ def __mul__(self, other: int | float | bool | Self) -> Self:
113
+ return MathExpression(left=self, op=MathExpression.Operator.mul, right=other, logic_group=self.logic_group)
114
+
115
+ def __truediv__(self, other: int | float | bool | Self) -> Self:
116
+ return MathExpression(left=self, op=MathExpression.Operator.truediv, right=other, logic_group=self.logic_group)
117
+
118
+ def __floordiv__(self, other: int | float | bool | Self) -> Self:
119
+ return MathExpression(left=self, op=MathExpression.Operator.floordiv, right=other, logic_group=self.logic_group)
120
+
121
+ def __pow__(self, other: int | float | bool | Self) -> Self:
122
+ return MathExpression(left=self, op=MathExpression.Operator.pow, right=other, logic_group=self.logic_group)
123
+
124
+ def __neg__(self):
125
+ return MathExpression(left=self, op=MathExpression.Operator.neg, repr=f'-{self.repr}', logic_group=self.logic_group)
126
+
127
+ # Comparison operation to invoke ComparisonExpression
128
+
129
+ def __eq__(self, other: int | float | bool | str | Self) -> Self:
130
+ return ComparisonExpression(left=self, op=ComparisonExpression.Operator.eq, right=other, logic_group=self.logic_group)
131
+
132
+ def __ne__(self, other: int | float | bool | str | Self) -> Self:
133
+ return ComparisonExpression(left=self, op=ComparisonExpression.Operator.ne, right=other, logic_group=self.logic_group)
134
+
135
+ def __gt__(self, other: int | float | bool | Self) -> Self:
136
+ return ComparisonExpression(left=self, op=ComparisonExpression.Operator.gt, right=other, logic_group=self.logic_group)
137
+
138
+ def __ge__(self, other: int | float | bool | Self) -> Self:
139
+ return ComparisonExpression(left=self, op=ComparisonExpression.Operator.ge, right=other, logic_group=self.logic_group)
140
+
141
+ def __lt__(self, other: int | float | bool | Self) -> Self:
142
+ return ComparisonExpression(left=self, op=ComparisonExpression.Operator.lt, right=other, logic_group=self.logic_group)
143
+
144
+ def __le__(self, other: int | float | bool | Self) -> Self:
145
+ return ComparisonExpression(left=self, op=ComparisonExpression.Operator.le, right=other, logic_group=self.logic_group)
146
+
147
+ # Logical operation to invoke LogicalExpression
148
+
149
+ def __and__(self, other: int | float | bool | Self) -> Self:
150
+ return LogicalExpression(left=self, op=LogicalExpression.Operator.and_, right=other, logic_group=self.logic_group)
151
+
152
+ def __or__(self, other: Self | bool) -> Self:
153
+ return LogicalExpression(left=self, op=LogicalExpression.Operator.or_, right=other, logic_group=self.logic_group)
154
+
155
+ def __invert__(self) -> Self:
156
+ return LogicalExpression(left=self, op=LogicalExpression.Operator.not_, repr=f'~{self.repr}', logic_group=self.logic_group)
157
+
158
+
159
+ class AttrExpression(_AE, ContextLogicExpression):
160
+ def __init__(self, attr: str | int, dtype: type = None, repr: str = None, logic_group: LogicGroup = None, edge_condition: Any = True):
161
+ super().__init__(attr=attr, repr=repr, logic_group=logic_group)
162
+ ContextLogicExpression.__init__(self=self, expression=self._eval, dtype=self.dtype, repr=self.repr, logic_group=self.logic_group)
163
+
164
+
165
+ class MathExpression(_ME, ContextLogicExpression):
166
+ def __init__(self, left: ContextLogicExpression | int | float, op: _ME.Operator, right: ContextLogicExpression | int | float = None, dtype: type = None, repr: str = None, logic_group: LogicGroup = None, edge_condition: Any = True):
167
+ super().__init__(left=left, op=op, right=right, dtype=dtype, repr=repr, logic_group=logic_group)
168
+ ContextLogicExpression.__init__(self=self, expression=self._eval, dtype=self.dtype, repr=self.repr, logic_group=self.logic_group)
169
+
170
+
171
+ class ComparisonExpression(_CE, ContextLogicExpression):
172
+ def __init__(self, left: ContextLogicExpression | int | float, op: _CE.Operator, right: ContextLogicExpression | int | float = None, dtype: type = None, repr: str = None, logic_group: LogicGroup = None, edge_condition: Any = True):
173
+ super().__init__(left=left, op=op, right=right, dtype=dtype, repr=repr, logic_group=logic_group)
174
+ ContextLogicExpression.__init__(self=self, expression=self._eval, dtype=self.dtype, repr=self.repr, logic_group=self.logic_group)
175
+
176
+
177
+ class LogicalExpression(_LE, ContextLogicExpression):
178
+ def __init__(self, left: ContextLogicExpression | int | float, op: _LE.Operator, right: ContextLogicExpression | int | float = None, dtype: type = None, repr: str = None, logic_group: LogicGroup = None, edge_condition: Any = True):
179
+ super().__init__(left=left, op=op, right=right, dtype=dtype, repr=repr, logic_group=logic_group)
180
+ ContextLogicExpression.__init__(self=self, expression=self._eval, dtype=self.dtype, repr=self.repr, logic_group=self.logic_group)