netrun-sim 0.1.2__cp38-abi3-win_amd64.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.
- netrun_sim/__init__.py +56 -0
- netrun_sim/__init__.pyi +901 -0
- netrun_sim/netrun_sim.pyd +0 -0
- netrun_sim/py.typed +0 -0
- netrun_sim-0.1.2.dist-info/METADATA +7 -0
- netrun_sim-0.1.2.dist-info/RECORD +7 -0
- netrun_sim-0.1.2.dist-info/WHEEL +4 -0
netrun_sim/__init__.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Re-export all types from the native extension
|
|
2
|
+
from netrun_sim.netrun_sim import *
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
# Exceptions
|
|
6
|
+
"NetrunError",
|
|
7
|
+
"PacketNotFoundError",
|
|
8
|
+
"EpochNotFoundError",
|
|
9
|
+
"EpochNotRunningError",
|
|
10
|
+
"EpochNotStartableError",
|
|
11
|
+
"CannotFinishNonEmptyEpochError",
|
|
12
|
+
"UnsentOutputSalvoError",
|
|
13
|
+
"PacketNotInNodeError",
|
|
14
|
+
"OutputPortNotFoundError",
|
|
15
|
+
"OutputPortFullError",
|
|
16
|
+
"SalvoConditionNotFoundError",
|
|
17
|
+
"SalvoConditionNotMetError",
|
|
18
|
+
"MaxSalvosExceededError",
|
|
19
|
+
"NodeNotFoundError",
|
|
20
|
+
"PacketNotAtInputPortError",
|
|
21
|
+
"InputPortNotFoundError",
|
|
22
|
+
"InputPortFullError",
|
|
23
|
+
"CannotMovePacketFromRunningEpochError",
|
|
24
|
+
"CannotMovePacketIntoRunningEpochError",
|
|
25
|
+
"EdgeNotFoundError",
|
|
26
|
+
"UnconnectedOutputPortError",
|
|
27
|
+
"GraphValidationError",
|
|
28
|
+
# Graph types
|
|
29
|
+
"PacketCount",
|
|
30
|
+
"PacketCountN",
|
|
31
|
+
"MaxSalvos",
|
|
32
|
+
"MaxSalvosFinite",
|
|
33
|
+
"PortSlotSpec",
|
|
34
|
+
"PortSlotSpecFinite",
|
|
35
|
+
"PortState",
|
|
36
|
+
"PortStateNumeric",
|
|
37
|
+
"SalvoConditionTerm",
|
|
38
|
+
"Port",
|
|
39
|
+
"PortType",
|
|
40
|
+
"PortRef",
|
|
41
|
+
"Edge",
|
|
42
|
+
"EdgeRef",
|
|
43
|
+
"SalvoCondition",
|
|
44
|
+
"Node",
|
|
45
|
+
"Graph",
|
|
46
|
+
# NetSim types
|
|
47
|
+
"PacketLocation",
|
|
48
|
+
"EpochState",
|
|
49
|
+
"Packet",
|
|
50
|
+
"Salvo",
|
|
51
|
+
"Epoch",
|
|
52
|
+
"NetAction",
|
|
53
|
+
"NetEvent",
|
|
54
|
+
"NetActionResponseData",
|
|
55
|
+
"NetSim",
|
|
56
|
+
]
|
netrun_sim/__init__.pyi
ADDED
|
@@ -0,0 +1,901 @@
|
|
|
1
|
+
"""Type stubs for netrun_sim - Flow-based development runtime simulation."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, List, Optional, Tuple, Union
|
|
4
|
+
from ulid import ULID
|
|
5
|
+
|
|
6
|
+
# === Exceptions ===
|
|
7
|
+
|
|
8
|
+
class NetrunError(Exception):
|
|
9
|
+
"""Base exception for all netrun errors."""
|
|
10
|
+
|
|
11
|
+
...
|
|
12
|
+
|
|
13
|
+
class PacketNotFoundError(NetrunError):
|
|
14
|
+
"""Packet with the given ID was not found."""
|
|
15
|
+
|
|
16
|
+
...
|
|
17
|
+
|
|
18
|
+
class EpochNotFoundError(NetrunError):
|
|
19
|
+
"""Epoch with the given ID was not found."""
|
|
20
|
+
|
|
21
|
+
...
|
|
22
|
+
|
|
23
|
+
class EpochNotRunningError(NetrunError):
|
|
24
|
+
"""Epoch exists but is not in Running state."""
|
|
25
|
+
|
|
26
|
+
...
|
|
27
|
+
|
|
28
|
+
class EpochNotStartableError(NetrunError):
|
|
29
|
+
"""Epoch exists but is not in Startable state."""
|
|
30
|
+
|
|
31
|
+
...
|
|
32
|
+
|
|
33
|
+
class CannotFinishNonEmptyEpochError(NetrunError):
|
|
34
|
+
"""Cannot finish epoch because it still contains packets."""
|
|
35
|
+
|
|
36
|
+
...
|
|
37
|
+
|
|
38
|
+
class UnsentOutputSalvoError(NetrunError):
|
|
39
|
+
"""Cannot finish epoch because output port still has unsent packets."""
|
|
40
|
+
|
|
41
|
+
...
|
|
42
|
+
|
|
43
|
+
class PacketNotInNodeError(NetrunError):
|
|
44
|
+
"""Packet is not inside the specified epoch's node location."""
|
|
45
|
+
|
|
46
|
+
...
|
|
47
|
+
|
|
48
|
+
class OutputPortNotFoundError(NetrunError):
|
|
49
|
+
"""Output port does not exist on the node."""
|
|
50
|
+
|
|
51
|
+
...
|
|
52
|
+
|
|
53
|
+
class OutputPortFullError(NetrunError):
|
|
54
|
+
"""Output port has reached its capacity."""
|
|
55
|
+
|
|
56
|
+
...
|
|
57
|
+
|
|
58
|
+
class SalvoConditionNotFoundError(NetrunError):
|
|
59
|
+
"""Salvo condition with the given name was not found."""
|
|
60
|
+
|
|
61
|
+
...
|
|
62
|
+
|
|
63
|
+
class SalvoConditionNotMetError(NetrunError):
|
|
64
|
+
"""Salvo condition exists but its term is not satisfied."""
|
|
65
|
+
|
|
66
|
+
...
|
|
67
|
+
|
|
68
|
+
class MaxSalvosExceededError(NetrunError):
|
|
69
|
+
"""Maximum number of salvos reached for this condition."""
|
|
70
|
+
|
|
71
|
+
...
|
|
72
|
+
|
|
73
|
+
class NodeNotFoundError(NetrunError):
|
|
74
|
+
"""Node with the given name was not found."""
|
|
75
|
+
|
|
76
|
+
...
|
|
77
|
+
|
|
78
|
+
class PacketNotAtInputPortError(NetrunError):
|
|
79
|
+
"""Packet is not at the expected input port."""
|
|
80
|
+
|
|
81
|
+
...
|
|
82
|
+
|
|
83
|
+
class InputPortNotFoundError(NetrunError):
|
|
84
|
+
"""Input port does not exist on the node."""
|
|
85
|
+
|
|
86
|
+
...
|
|
87
|
+
|
|
88
|
+
class InputPortFullError(NetrunError):
|
|
89
|
+
"""Input port has reached its capacity."""
|
|
90
|
+
|
|
91
|
+
...
|
|
92
|
+
|
|
93
|
+
class CannotMovePacketFromRunningEpochError(NetrunError):
|
|
94
|
+
"""Cannot move packet out of a running epoch."""
|
|
95
|
+
|
|
96
|
+
...
|
|
97
|
+
|
|
98
|
+
class CannotMovePacketIntoRunningEpochError(NetrunError):
|
|
99
|
+
"""Cannot move packet into a running epoch."""
|
|
100
|
+
|
|
101
|
+
...
|
|
102
|
+
|
|
103
|
+
class EdgeNotFoundError(NetrunError):
|
|
104
|
+
"""Edge does not exist in the graph."""
|
|
105
|
+
|
|
106
|
+
...
|
|
107
|
+
|
|
108
|
+
class UnconnectedOutputPortError(NetrunError):
|
|
109
|
+
"""Output port is not connected to any edge."""
|
|
110
|
+
|
|
111
|
+
...
|
|
112
|
+
|
|
113
|
+
class GraphValidationError(NetrunError):
|
|
114
|
+
"""Graph validation failed."""
|
|
115
|
+
|
|
116
|
+
...
|
|
117
|
+
|
|
118
|
+
# === Graph Types ===
|
|
119
|
+
|
|
120
|
+
class PacketCount:
|
|
121
|
+
"""Specifies how many packets to take from a port in a salvo."""
|
|
122
|
+
|
|
123
|
+
All: PacketCount
|
|
124
|
+
|
|
125
|
+
@staticmethod
|
|
126
|
+
def all() -> PacketCount: ...
|
|
127
|
+
@staticmethod
|
|
128
|
+
def count(n: int) -> PacketCountN: ...
|
|
129
|
+
|
|
130
|
+
class PacketCountN:
|
|
131
|
+
"""PacketCount with a specific count limit."""
|
|
132
|
+
@property
|
|
133
|
+
def count(self) -> int: ...
|
|
134
|
+
|
|
135
|
+
class MaxSalvos:
|
|
136
|
+
"""Specifies the maximum number of times a salvo condition can trigger."""
|
|
137
|
+
|
|
138
|
+
Infinite: MaxSalvos
|
|
139
|
+
|
|
140
|
+
@staticmethod
|
|
141
|
+
def infinite() -> MaxSalvos: ...
|
|
142
|
+
@staticmethod
|
|
143
|
+
def finite(n: int) -> MaxSalvosFinite: ...
|
|
144
|
+
|
|
145
|
+
class MaxSalvosFinite:
|
|
146
|
+
"""Finite max salvos with a specific limit."""
|
|
147
|
+
@property
|
|
148
|
+
def max(self) -> int: ...
|
|
149
|
+
|
|
150
|
+
class PortSlotSpec:
|
|
151
|
+
"""Port capacity specification."""
|
|
152
|
+
|
|
153
|
+
Infinite: PortSlotSpec
|
|
154
|
+
Finite: PortSlotSpec
|
|
155
|
+
|
|
156
|
+
@staticmethod
|
|
157
|
+
def infinite() -> PortSlotSpec: ...
|
|
158
|
+
@staticmethod
|
|
159
|
+
def finite(n: int) -> PortSlotSpecFinite: ...
|
|
160
|
+
|
|
161
|
+
class PortSlotSpecFinite:
|
|
162
|
+
"""Finite port capacity with a specific limit."""
|
|
163
|
+
@property
|
|
164
|
+
def capacity(self) -> int: ...
|
|
165
|
+
|
|
166
|
+
class PortState:
|
|
167
|
+
"""Port state predicate for salvo conditions."""
|
|
168
|
+
|
|
169
|
+
Empty: PortState
|
|
170
|
+
Full: PortState
|
|
171
|
+
NonEmpty: PortState
|
|
172
|
+
NonFull: PortState
|
|
173
|
+
|
|
174
|
+
@staticmethod
|
|
175
|
+
def empty() -> PortState: ...
|
|
176
|
+
@staticmethod
|
|
177
|
+
def full() -> PortState: ...
|
|
178
|
+
@staticmethod
|
|
179
|
+
def non_empty() -> PortState: ...
|
|
180
|
+
@staticmethod
|
|
181
|
+
def non_full() -> PortState: ...
|
|
182
|
+
@staticmethod
|
|
183
|
+
def equals(n: int) -> PortStateNumeric: ...
|
|
184
|
+
@staticmethod
|
|
185
|
+
def less_than(n: int) -> PortStateNumeric: ...
|
|
186
|
+
@staticmethod
|
|
187
|
+
def greater_than(n: int) -> PortStateNumeric: ...
|
|
188
|
+
@staticmethod
|
|
189
|
+
def equals_or_less_than(n: int) -> PortStateNumeric: ...
|
|
190
|
+
@staticmethod
|
|
191
|
+
def equals_or_greater_than(n: int) -> PortStateNumeric: ...
|
|
192
|
+
|
|
193
|
+
class PortStateNumeric:
|
|
194
|
+
"""Numeric port state predicate."""
|
|
195
|
+
@property
|
|
196
|
+
def kind(self) -> str: ...
|
|
197
|
+
@property
|
|
198
|
+
def value(self) -> int: ...
|
|
199
|
+
|
|
200
|
+
class SalvoConditionTerm:
|
|
201
|
+
"""Boolean expression over port states.
|
|
202
|
+
|
|
203
|
+
Use the static methods to construct terms:
|
|
204
|
+
- `true_()` / `false_()`: Constant boolean values
|
|
205
|
+
- `port(name, state)`: Check if a port matches a state predicate
|
|
206
|
+
- `and_(terms)` / `or_(terms)`: Logical combinations
|
|
207
|
+
- `not_(term)`: Logical negation
|
|
208
|
+
|
|
209
|
+
Example:
|
|
210
|
+
# Trigger when both input ports have packets
|
|
211
|
+
term = SalvoConditionTerm.and_([
|
|
212
|
+
SalvoConditionTerm.port("in1", PortState.non_empty()),
|
|
213
|
+
SalvoConditionTerm.port("in2", PortState.non_empty()),
|
|
214
|
+
])
|
|
215
|
+
|
|
216
|
+
# Source node with no input ports
|
|
217
|
+
term = SalvoConditionTerm.true_()
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
@staticmethod
|
|
221
|
+
def true_() -> SalvoConditionTerm:
|
|
222
|
+
"""Create a term that is always true. Useful for source nodes with no input ports."""
|
|
223
|
+
...
|
|
224
|
+
|
|
225
|
+
@staticmethod
|
|
226
|
+
def false_() -> SalvoConditionTerm:
|
|
227
|
+
"""Create a term that is always false. Useful as a placeholder or with not_()."""
|
|
228
|
+
...
|
|
229
|
+
|
|
230
|
+
@staticmethod
|
|
231
|
+
def port(
|
|
232
|
+
port_name: str, state: Union[PortState, PortStateNumeric]
|
|
233
|
+
) -> SalvoConditionTerm:
|
|
234
|
+
"""Create a term that checks if a port matches a state predicate."""
|
|
235
|
+
...
|
|
236
|
+
|
|
237
|
+
@staticmethod
|
|
238
|
+
def and_(terms: List[SalvoConditionTerm]) -> SalvoConditionTerm:
|
|
239
|
+
"""Create a term that is true when all sub-terms are true."""
|
|
240
|
+
...
|
|
241
|
+
|
|
242
|
+
@staticmethod
|
|
243
|
+
def or_(terms: List[SalvoConditionTerm]) -> SalvoConditionTerm:
|
|
244
|
+
"""Create a term that is true when at least one sub-term is true."""
|
|
245
|
+
...
|
|
246
|
+
|
|
247
|
+
@staticmethod
|
|
248
|
+
def not_(term: SalvoConditionTerm) -> SalvoConditionTerm:
|
|
249
|
+
"""Create a term that is true when the sub-term is false."""
|
|
250
|
+
...
|
|
251
|
+
|
|
252
|
+
@property
|
|
253
|
+
def kind(self) -> str:
|
|
254
|
+
"""Get the term kind: 'True', 'False', 'Port', 'And', 'Or', or 'Not'."""
|
|
255
|
+
...
|
|
256
|
+
|
|
257
|
+
def get_port_name(self) -> Optional[str]:
|
|
258
|
+
"""Get the port name (for Port terms only, None otherwise)."""
|
|
259
|
+
...
|
|
260
|
+
|
|
261
|
+
def get_port_state(self) -> Optional[Union[PortState, PortStateNumeric]]:
|
|
262
|
+
"""Get the port state (for Port terms only, None otherwise)."""
|
|
263
|
+
...
|
|
264
|
+
|
|
265
|
+
def get_terms(self) -> Optional[List[SalvoConditionTerm]]:
|
|
266
|
+
"""Get the sub-terms (for And/Or terms only, None otherwise)."""
|
|
267
|
+
...
|
|
268
|
+
|
|
269
|
+
def get_inner(self) -> Optional[SalvoConditionTerm]:
|
|
270
|
+
"""Get the inner term (for Not terms only, None otherwise)."""
|
|
271
|
+
...
|
|
272
|
+
|
|
273
|
+
class Port:
|
|
274
|
+
"""A port on a node."""
|
|
275
|
+
|
|
276
|
+
def __init__(
|
|
277
|
+
self, slots_spec: Optional[Union[PortSlotSpec, PortSlotSpecFinite]] = None
|
|
278
|
+
) -> None: ...
|
|
279
|
+
@property
|
|
280
|
+
def slots_spec(self) -> Union[PortSlotSpec, PortSlotSpecFinite]: ...
|
|
281
|
+
|
|
282
|
+
class PortType:
|
|
283
|
+
"""Port type: Input or Output."""
|
|
284
|
+
|
|
285
|
+
Input: PortType
|
|
286
|
+
Output: PortType
|
|
287
|
+
|
|
288
|
+
class PortRef:
|
|
289
|
+
"""Reference to a specific port on a node."""
|
|
290
|
+
|
|
291
|
+
def __init__(self, node_name: str, port_type: PortType, port_name: str) -> None: ...
|
|
292
|
+
@property
|
|
293
|
+
def node_name(self) -> str: ...
|
|
294
|
+
@property
|
|
295
|
+
def port_type(self) -> PortType: ...
|
|
296
|
+
@property
|
|
297
|
+
def port_name(self) -> str: ...
|
|
298
|
+
def __eq__(self, other: object) -> bool: ...
|
|
299
|
+
def __hash__(self) -> int: ...
|
|
300
|
+
|
|
301
|
+
class Edge:
|
|
302
|
+
"""A connection between two ports in the graph."""
|
|
303
|
+
|
|
304
|
+
def __init__(self, source: PortRef, target: PortRef) -> None: ...
|
|
305
|
+
@property
|
|
306
|
+
def source(self) -> PortRef: ...
|
|
307
|
+
@property
|
|
308
|
+
def target(self) -> PortRef: ...
|
|
309
|
+
def __eq__(self, other: object) -> bool: ...
|
|
310
|
+
def __hash__(self) -> int: ...
|
|
311
|
+
|
|
312
|
+
# Type alias for ports parameter
|
|
313
|
+
SalvoConditionPorts = Union[str, List[str], Dict[str, Union[PacketCount, PacketCountN]]]
|
|
314
|
+
|
|
315
|
+
class SalvoCondition:
|
|
316
|
+
"""A condition that defines when packets can trigger an epoch or be sent.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
max_salvos: Maximum number of times this condition can trigger.
|
|
320
|
+
Use MaxSalvos.finite(n) for a finite limit or MaxSalvos.Infinite for no limit.
|
|
321
|
+
ports: Which ports' packets are included. Can be:
|
|
322
|
+
- A single port name (str) - defaults to PacketCount.All
|
|
323
|
+
- A list of port names - each defaults to PacketCount.All
|
|
324
|
+
- A dict mapping port names to PacketCount values
|
|
325
|
+
term: The boolean expression that must be satisfied.
|
|
326
|
+
"""
|
|
327
|
+
|
|
328
|
+
def __init__(
|
|
329
|
+
self,
|
|
330
|
+
max_salvos: Union[MaxSalvos, MaxSalvosFinite],
|
|
331
|
+
ports: SalvoConditionPorts,
|
|
332
|
+
term: SalvoConditionTerm,
|
|
333
|
+
) -> None: ...
|
|
334
|
+
@property
|
|
335
|
+
def max_salvos(self) -> Union[MaxSalvos, MaxSalvosFinite]: ...
|
|
336
|
+
@property
|
|
337
|
+
def ports(self) -> Dict[str, Union[PacketCount, PacketCountN]]: ...
|
|
338
|
+
@property
|
|
339
|
+
def term(self) -> SalvoConditionTerm: ...
|
|
340
|
+
|
|
341
|
+
class Node:
|
|
342
|
+
"""A processing node in the graph."""
|
|
343
|
+
|
|
344
|
+
def __init__(
|
|
345
|
+
self,
|
|
346
|
+
name: str,
|
|
347
|
+
in_ports: Optional[Dict[str, Port]] = None,
|
|
348
|
+
out_ports: Optional[Dict[str, Port]] = None,
|
|
349
|
+
in_salvo_conditions: Optional[Dict[str, SalvoCondition]] = None,
|
|
350
|
+
out_salvo_conditions: Optional[Dict[str, SalvoCondition]] = None,
|
|
351
|
+
) -> None: ...
|
|
352
|
+
@property
|
|
353
|
+
def name(self) -> str: ...
|
|
354
|
+
@property
|
|
355
|
+
def in_ports(self) -> Dict[str, Port]: ...
|
|
356
|
+
@property
|
|
357
|
+
def out_ports(self) -> Dict[str, Port]: ...
|
|
358
|
+
@property
|
|
359
|
+
def in_salvo_conditions(self) -> Dict[str, SalvoCondition]: ...
|
|
360
|
+
@property
|
|
361
|
+
def out_salvo_conditions(self) -> Dict[str, SalvoCondition]: ...
|
|
362
|
+
|
|
363
|
+
class Graph:
|
|
364
|
+
"""The static topology of a flow-based network."""
|
|
365
|
+
|
|
366
|
+
def __init__(self, nodes: List[Node], edges: List[Edge]) -> None: ...
|
|
367
|
+
def nodes(self) -> Dict[str, Node]: ...
|
|
368
|
+
def edges(self) -> List[Edge]: ...
|
|
369
|
+
def validate(self) -> List[GraphValidationError]: ...
|
|
370
|
+
|
|
371
|
+
# === NetSim Types ===
|
|
372
|
+
|
|
373
|
+
class PacketLocation:
|
|
374
|
+
"""Where a packet is located in the network.
|
|
375
|
+
|
|
376
|
+
Packets can be in one of five locations:
|
|
377
|
+
- `node(epoch_id)`: Inside a running/startable epoch
|
|
378
|
+
- `input_port(node_name, port_name)`: Waiting at a node's input port
|
|
379
|
+
- `output_port(epoch_id, port_name)`: Loaded into an epoch's output port
|
|
380
|
+
- `edge(edge)`: In transit between nodes
|
|
381
|
+
- `outside_net()`: External to the network
|
|
382
|
+
|
|
383
|
+
Example:
|
|
384
|
+
loc = PacketLocation.input_port("NodeB", "in")
|
|
385
|
+
assert loc.kind == "InputPort"
|
|
386
|
+
assert loc.node_name == "NodeB"
|
|
387
|
+
assert loc.port_name == "in"
|
|
388
|
+
"""
|
|
389
|
+
|
|
390
|
+
@staticmethod
|
|
391
|
+
def node(epoch_id: Union[ULID, str]) -> PacketLocation:
|
|
392
|
+
"""Create a location inside an epoch."""
|
|
393
|
+
...
|
|
394
|
+
|
|
395
|
+
@staticmethod
|
|
396
|
+
def input_port(node_name: str, port_name: str) -> PacketLocation:
|
|
397
|
+
"""Create a location at a node's input port."""
|
|
398
|
+
...
|
|
399
|
+
|
|
400
|
+
@staticmethod
|
|
401
|
+
def output_port(epoch_id: Union[ULID, str], port_name: str) -> PacketLocation:
|
|
402
|
+
"""Create a location at an epoch's output port."""
|
|
403
|
+
...
|
|
404
|
+
|
|
405
|
+
@staticmethod
|
|
406
|
+
def edge(edge: Edge) -> PacketLocation:
|
|
407
|
+
"""Create a location on an edge (in transit)."""
|
|
408
|
+
...
|
|
409
|
+
|
|
410
|
+
@staticmethod
|
|
411
|
+
def outside_net() -> PacketLocation:
|
|
412
|
+
"""Create a location outside the network."""
|
|
413
|
+
...
|
|
414
|
+
|
|
415
|
+
@property
|
|
416
|
+
def kind(self) -> str:
|
|
417
|
+
"""Get the location kind: 'Node', 'InputPort', 'OutputPort', 'Edge', or 'OutsideNet'."""
|
|
418
|
+
...
|
|
419
|
+
|
|
420
|
+
@property
|
|
421
|
+
def node_name(self) -> Optional[str]:
|
|
422
|
+
"""Get the node name (for InputPort locations only, None otherwise)."""
|
|
423
|
+
...
|
|
424
|
+
|
|
425
|
+
@property
|
|
426
|
+
def port_name(self) -> Optional[str]:
|
|
427
|
+
"""Get the port name (for InputPort and OutputPort locations only, None otherwise)."""
|
|
428
|
+
...
|
|
429
|
+
|
|
430
|
+
@property
|
|
431
|
+
def epoch_id(self) -> Optional[str]:
|
|
432
|
+
"""Get the epoch ID (for Node and OutputPort locations only, None otherwise)."""
|
|
433
|
+
...
|
|
434
|
+
|
|
435
|
+
def get_edge(self) -> Optional[Edge]:
|
|
436
|
+
"""Get the edge (for Edge locations only, None otherwise)."""
|
|
437
|
+
...
|
|
438
|
+
|
|
439
|
+
class EpochState:
|
|
440
|
+
"""Epoch lifecycle state."""
|
|
441
|
+
|
|
442
|
+
Startable: EpochState
|
|
443
|
+
Running: EpochState
|
|
444
|
+
Finished: EpochState
|
|
445
|
+
|
|
446
|
+
class Packet:
|
|
447
|
+
"""A packet in the network."""
|
|
448
|
+
|
|
449
|
+
@property
|
|
450
|
+
def id(self) -> str: ...
|
|
451
|
+
@property
|
|
452
|
+
def location(self) -> PacketLocation: ...
|
|
453
|
+
def get_id(self) -> ULID: ...
|
|
454
|
+
|
|
455
|
+
class Salvo:
|
|
456
|
+
"""A collection of packets entering or exiting a node."""
|
|
457
|
+
|
|
458
|
+
def __init__(
|
|
459
|
+
self, salvo_condition: str, packets: List[Tuple[str, str]]
|
|
460
|
+
) -> None: ...
|
|
461
|
+
@property
|
|
462
|
+
def salvo_condition(self) -> str: ...
|
|
463
|
+
@property
|
|
464
|
+
def packets(self) -> List[Tuple[str, str]]: ...
|
|
465
|
+
|
|
466
|
+
class OrphanedPacketInfo:
|
|
467
|
+
"""Information about a packet sent to an unconnected output port.
|
|
468
|
+
|
|
469
|
+
When a packet is sent from an output port that has no connected edge,
|
|
470
|
+
it is moved to OutsideNet and tracked as "orphaned".
|
|
471
|
+
"""
|
|
472
|
+
|
|
473
|
+
@property
|
|
474
|
+
def packet_id(self) -> str:
|
|
475
|
+
"""The ID of the orphaned packet."""
|
|
476
|
+
...
|
|
477
|
+
|
|
478
|
+
@property
|
|
479
|
+
def from_port(self) -> str:
|
|
480
|
+
"""The name of the output port the packet was sent from."""
|
|
481
|
+
...
|
|
482
|
+
|
|
483
|
+
@property
|
|
484
|
+
def salvo_condition(self) -> str:
|
|
485
|
+
"""The salvo condition that triggered the send."""
|
|
486
|
+
...
|
|
487
|
+
|
|
488
|
+
class Epoch:
|
|
489
|
+
"""An execution instance of a node."""
|
|
490
|
+
|
|
491
|
+
@property
|
|
492
|
+
def id(self) -> str: ...
|
|
493
|
+
@property
|
|
494
|
+
def node_name(self) -> str: ...
|
|
495
|
+
@property
|
|
496
|
+
def in_salvo(self) -> Salvo: ...
|
|
497
|
+
@property
|
|
498
|
+
def out_salvos(self) -> List[Salvo]: ...
|
|
499
|
+
@property
|
|
500
|
+
def state(self) -> EpochState: ...
|
|
501
|
+
@property
|
|
502
|
+
def orphaned_packets(self) -> List[OrphanedPacketInfo]:
|
|
503
|
+
"""Packets that were sent to unconnected output ports (moved to OutsideNet)."""
|
|
504
|
+
...
|
|
505
|
+
def get_id(self) -> ULID: ...
|
|
506
|
+
def start_time(self) -> int: ...
|
|
507
|
+
|
|
508
|
+
class NetAction:
|
|
509
|
+
"""An action to perform on the network.
|
|
510
|
+
|
|
511
|
+
Actions are executed via `NetSim.do_action()` and return a tuple of
|
|
512
|
+
(response_data, events).
|
|
513
|
+
|
|
514
|
+
Example:
|
|
515
|
+
# Create and start an epoch manually
|
|
516
|
+
salvo = Salvo(salvo_condition="trigger", packets=[("in", packet_id)])
|
|
517
|
+
response, events = net.do_action(NetAction.create_epoch("NodeB", salvo))
|
|
518
|
+
epoch = response.epoch # CreatedEpoch response
|
|
519
|
+
|
|
520
|
+
response, events = net.do_action(NetAction.start_epoch(epoch.id))
|
|
521
|
+
epoch = response.epoch # StartedEpoch response
|
|
522
|
+
"""
|
|
523
|
+
|
|
524
|
+
@staticmethod
|
|
525
|
+
def run_step() -> NetAction:
|
|
526
|
+
"""Run one step of automatic packet flow.
|
|
527
|
+
|
|
528
|
+
Moves packets from edges to input ports, then checks input salvo
|
|
529
|
+
conditions to create new epochs. Repeats until no more progress can be made.
|
|
530
|
+
"""
|
|
531
|
+
...
|
|
532
|
+
|
|
533
|
+
@staticmethod
|
|
534
|
+
def create_packet(epoch_id: Optional[Union[ULID, str]] = None) -> NetAction:
|
|
535
|
+
"""Create a new packet.
|
|
536
|
+
|
|
537
|
+
Args:
|
|
538
|
+
epoch_id: If provided, create packet inside this epoch.
|
|
539
|
+
If None, create packet outside the network.
|
|
540
|
+
|
|
541
|
+
Returns NetActionResponseData.Packet with the new packet ID.
|
|
542
|
+
"""
|
|
543
|
+
...
|
|
544
|
+
|
|
545
|
+
@staticmethod
|
|
546
|
+
def consume_packet(packet_id: Union[ULID, str]) -> NetAction:
|
|
547
|
+
"""Consume a packet (normal removal from the network)."""
|
|
548
|
+
...
|
|
549
|
+
|
|
550
|
+
@staticmethod
|
|
551
|
+
def destroy_packet(packet_id: Union[ULID, str]) -> NetAction:
|
|
552
|
+
"""Destroy a packet (abnormal removal, e.g., due to error)."""
|
|
553
|
+
...
|
|
554
|
+
|
|
555
|
+
@staticmethod
|
|
556
|
+
def start_epoch(epoch_id: Union[ULID, str]) -> NetAction:
|
|
557
|
+
"""Start a Startable epoch, transitioning it to Running state.
|
|
558
|
+
|
|
559
|
+
Returns NetActionResponseData.StartedEpoch with the epoch.
|
|
560
|
+
"""
|
|
561
|
+
...
|
|
562
|
+
|
|
563
|
+
@staticmethod
|
|
564
|
+
def finish_epoch(epoch_id: Union[ULID, str]) -> NetAction:
|
|
565
|
+
"""Finish a Running epoch.
|
|
566
|
+
|
|
567
|
+
The epoch must be empty (no packets inside) and all output salvos
|
|
568
|
+
must have been sent.
|
|
569
|
+
|
|
570
|
+
Returns NetActionResponseData.FinishedEpoch with the epoch.
|
|
571
|
+
"""
|
|
572
|
+
...
|
|
573
|
+
|
|
574
|
+
@staticmethod
|
|
575
|
+
def cancel_epoch(epoch_id: Union[ULID, str]) -> NetAction:
|
|
576
|
+
"""Cancel an epoch and destroy all packets inside it.
|
|
577
|
+
|
|
578
|
+
Returns NetActionResponseData.CancelledEpoch with the epoch and
|
|
579
|
+
list of destroyed packet IDs.
|
|
580
|
+
"""
|
|
581
|
+
...
|
|
582
|
+
|
|
583
|
+
@staticmethod
|
|
584
|
+
def create_epoch(node_name: str, salvo: Salvo) -> NetAction:
|
|
585
|
+
"""Manually create an epoch with specified packets.
|
|
586
|
+
|
|
587
|
+
Bypasses the normal salvo condition triggering mechanism.
|
|
588
|
+
The epoch is created in Startable state - call start_epoch() to
|
|
589
|
+
begin execution.
|
|
590
|
+
|
|
591
|
+
Args:
|
|
592
|
+
node_name: The node to create the epoch on.
|
|
593
|
+
salvo: The input salvo (packets must be at the node's input ports).
|
|
594
|
+
For source nodes with no inputs, use an empty packets list.
|
|
595
|
+
|
|
596
|
+
Returns NetActionResponseData.CreatedEpoch with the epoch.
|
|
597
|
+
"""
|
|
598
|
+
...
|
|
599
|
+
|
|
600
|
+
@staticmethod
|
|
601
|
+
def load_packet_into_output_port(
|
|
602
|
+
packet_id: Union[ULID, str], port_name: str
|
|
603
|
+
) -> NetAction:
|
|
604
|
+
"""Move a packet from inside an epoch to one of its output ports."""
|
|
605
|
+
...
|
|
606
|
+
|
|
607
|
+
@staticmethod
|
|
608
|
+
def send_output_salvo(
|
|
609
|
+
epoch_id: Union[ULID, str], salvo_condition_name: str
|
|
610
|
+
) -> NetAction:
|
|
611
|
+
"""Send packets from output ports onto edges.
|
|
612
|
+
|
|
613
|
+
The salvo condition must be satisfied for this to succeed.
|
|
614
|
+
"""
|
|
615
|
+
...
|
|
616
|
+
|
|
617
|
+
@staticmethod
|
|
618
|
+
def transport_packet_to_location(
|
|
619
|
+
packet_id: Union[ULID, str], destination: PacketLocation
|
|
620
|
+
) -> NetAction:
|
|
621
|
+
"""Transport a packet to a new location.
|
|
622
|
+
|
|
623
|
+
Cannot move packets into or out of Running epochs.
|
|
624
|
+
"""
|
|
625
|
+
...
|
|
626
|
+
|
|
627
|
+
class NetEvent:
|
|
628
|
+
"""An event that occurred during a network action.
|
|
629
|
+
|
|
630
|
+
Event kinds:
|
|
631
|
+
- `PacketCreated`: A packet was created
|
|
632
|
+
- `PacketConsumed`: A packet was consumed (removed from the network)
|
|
633
|
+
- `PacketDestroyed`: A packet was destroyed (e.g., when epoch cancelled)
|
|
634
|
+
- `EpochCreated`: An epoch was created
|
|
635
|
+
- `EpochStarted`: An epoch transitioned to Running state
|
|
636
|
+
- `EpochFinished`: An epoch completed successfully
|
|
637
|
+
- `EpochCancelled`: An epoch was cancelled
|
|
638
|
+
- `PacketMoved`: A packet moved from one location to another
|
|
639
|
+
- `InputSalvoTriggered`: An input salvo condition was triggered
|
|
640
|
+
- `OutputSalvoTriggered`: An output salvo condition was triggered
|
|
641
|
+
- `PacketOrphaned`: A packet was sent to an unconnected output port
|
|
642
|
+
"""
|
|
643
|
+
|
|
644
|
+
@property
|
|
645
|
+
def kind(self) -> str:
|
|
646
|
+
"""Get the event kind (e.g., 'PacketMoved', 'EpochCreated', 'PacketOrphaned')."""
|
|
647
|
+
...
|
|
648
|
+
|
|
649
|
+
@property
|
|
650
|
+
def timestamp(self) -> int:
|
|
651
|
+
"""Get the timestamp (UTC microseconds) when the event occurred."""
|
|
652
|
+
...
|
|
653
|
+
|
|
654
|
+
@property
|
|
655
|
+
def packet_id(self) -> Optional[str]:
|
|
656
|
+
"""Get the packet ID for packet-related events."""
|
|
657
|
+
...
|
|
658
|
+
|
|
659
|
+
@property
|
|
660
|
+
def epoch_id(self) -> Optional[str]:
|
|
661
|
+
"""Get the epoch ID for epoch-related events."""
|
|
662
|
+
...
|
|
663
|
+
|
|
664
|
+
@property
|
|
665
|
+
def from_location(self) -> Optional[PacketLocation]:
|
|
666
|
+
"""Get the source location for PacketMoved events."""
|
|
667
|
+
...
|
|
668
|
+
|
|
669
|
+
@property
|
|
670
|
+
def to_location(self) -> Optional[PacketLocation]:
|
|
671
|
+
"""Get the destination location for PacketMoved events."""
|
|
672
|
+
...
|
|
673
|
+
|
|
674
|
+
@property
|
|
675
|
+
def salvo_condition(self) -> Optional[str]:
|
|
676
|
+
"""Get the salvo condition name for salvo-triggered and PacketOrphaned events."""
|
|
677
|
+
...
|
|
678
|
+
|
|
679
|
+
@property
|
|
680
|
+
def node_name(self) -> Optional[str]:
|
|
681
|
+
"""Get the node name for PacketOrphaned events."""
|
|
682
|
+
...
|
|
683
|
+
|
|
684
|
+
@property
|
|
685
|
+
def orphaned_port_name(self) -> Optional[str]:
|
|
686
|
+
"""Get the port name for PacketOrphaned events (the output port the packet was sent from)."""
|
|
687
|
+
...
|
|
688
|
+
|
|
689
|
+
class NetActionResponseData:
|
|
690
|
+
"""Response data from a successful action.
|
|
691
|
+
|
|
692
|
+
The response type depends on the action:
|
|
693
|
+
- `create_packet()` -> `Packet`
|
|
694
|
+
- `create_epoch()` -> `CreatedEpoch`
|
|
695
|
+
- `start_epoch()` -> `StartedEpoch`
|
|
696
|
+
- `finish_epoch()` -> `FinishedEpoch`
|
|
697
|
+
- `cancel_epoch()` -> `CancelledEpoch`
|
|
698
|
+
- Other actions -> `Empty`
|
|
699
|
+
|
|
700
|
+
Use `isinstance()` or check the type to determine which response you have.
|
|
701
|
+
"""
|
|
702
|
+
|
|
703
|
+
class Packet:
|
|
704
|
+
"""Response from create_packet()."""
|
|
705
|
+
|
|
706
|
+
packet_id: str
|
|
707
|
+
|
|
708
|
+
class CreatedEpoch:
|
|
709
|
+
"""Response from create_epoch(). Epoch is in Startable state."""
|
|
710
|
+
|
|
711
|
+
epoch: Epoch
|
|
712
|
+
|
|
713
|
+
class StartedEpoch:
|
|
714
|
+
"""Response from start_epoch(). Epoch is in Running state."""
|
|
715
|
+
|
|
716
|
+
epoch: Epoch
|
|
717
|
+
|
|
718
|
+
class FinishedEpoch:
|
|
719
|
+
"""Response from finish_epoch(). Epoch is in Finished state."""
|
|
720
|
+
|
|
721
|
+
epoch: Epoch
|
|
722
|
+
|
|
723
|
+
class CancelledEpoch:
|
|
724
|
+
"""Response from cancel_epoch()."""
|
|
725
|
+
|
|
726
|
+
epoch: Epoch
|
|
727
|
+
destroyed_packets: List[str]
|
|
728
|
+
|
|
729
|
+
class Empty:
|
|
730
|
+
"""Response from actions that don't return specific data."""
|
|
731
|
+
|
|
732
|
+
pass
|
|
733
|
+
|
|
734
|
+
class NetSim:
|
|
735
|
+
"""The runtime state of a flow-based network.
|
|
736
|
+
|
|
737
|
+
NetSim simulates packet flow through a network of nodes. It tracks packet
|
|
738
|
+
locations, validates flow conditions, and manages epoch lifecycles.
|
|
739
|
+
|
|
740
|
+
Example:
|
|
741
|
+
# Create a network
|
|
742
|
+
graph = Graph(nodes=[...], edges=[...])
|
|
743
|
+
net = NetSim(graph)
|
|
744
|
+
|
|
745
|
+
# Create a packet and place it on an edge
|
|
746
|
+
response, events = net.do_action(NetAction.create_packet())
|
|
747
|
+
packet_id = response.packet_id
|
|
748
|
+
|
|
749
|
+
edge = graph.edges()[0]
|
|
750
|
+
net.do_action(NetAction.transport_packet_to_location(
|
|
751
|
+
packet_id,
|
|
752
|
+
PacketLocation.edge(edge)
|
|
753
|
+
))
|
|
754
|
+
|
|
755
|
+
# Run the network - packet will flow to input ports and trigger epochs
|
|
756
|
+
net.do_action(NetAction.run_step())
|
|
757
|
+
|
|
758
|
+
# Check for startable epochs
|
|
759
|
+
startable = net.get_startable_epochs()
|
|
760
|
+
"""
|
|
761
|
+
|
|
762
|
+
def __init__(self, graph: Graph) -> None:
|
|
763
|
+
"""Create a new network simulation from a graph topology."""
|
|
764
|
+
...
|
|
765
|
+
|
|
766
|
+
def do_action(
|
|
767
|
+
self, action: NetAction
|
|
768
|
+
) -> Tuple[NetActionResponseData, List[NetEvent]]:
|
|
769
|
+
"""Execute an action on the network.
|
|
770
|
+
|
|
771
|
+
Args:
|
|
772
|
+
action: The action to perform.
|
|
773
|
+
|
|
774
|
+
Returns:
|
|
775
|
+
A tuple of (response_data, events) where response_data contains
|
|
776
|
+
action-specific results and events is a list of things that happened.
|
|
777
|
+
|
|
778
|
+
Raises:
|
|
779
|
+
Various exceptions depending on the action (see exception classes).
|
|
780
|
+
"""
|
|
781
|
+
...
|
|
782
|
+
|
|
783
|
+
def packet_count_at(self, location: PacketLocation) -> int:
|
|
784
|
+
"""Get the number of packets at a location."""
|
|
785
|
+
...
|
|
786
|
+
|
|
787
|
+
def get_packets_at_location(self, location: PacketLocation) -> List[ULID]:
|
|
788
|
+
"""Get the IDs of all packets at a location."""
|
|
789
|
+
...
|
|
790
|
+
|
|
791
|
+
def get_epoch(self, epoch_id: Union[ULID, str]) -> Optional[Epoch]:
|
|
792
|
+
"""Get an epoch by ID, or None if not found."""
|
|
793
|
+
...
|
|
794
|
+
|
|
795
|
+
def get_startable_epochs(self) -> List[ULID]:
|
|
796
|
+
"""Get IDs of all epochs in Startable state."""
|
|
797
|
+
...
|
|
798
|
+
|
|
799
|
+
def get_packet(self, packet_id: Union[ULID, str]) -> Optional[Packet]:
|
|
800
|
+
"""Get a packet by ID, or None if not found."""
|
|
801
|
+
...
|
|
802
|
+
|
|
803
|
+
@property
|
|
804
|
+
def graph(self) -> Graph:
|
|
805
|
+
"""The graph topology this simulation is running on."""
|
|
806
|
+
...
|
|
807
|
+
|
|
808
|
+
def is_blocked(self) -> bool:
|
|
809
|
+
"""Check if the network is blocked (no progress can be made by run_step).
|
|
810
|
+
|
|
811
|
+
Returns True if no packets can move from edges to input ports.
|
|
812
|
+
"""
|
|
813
|
+
...
|
|
814
|
+
|
|
815
|
+
def run_until_blocked(self) -> List[NetEvent]:
|
|
816
|
+
"""Run the network until blocked, returning all events.
|
|
817
|
+
|
|
818
|
+
Convenience method that repeatedly calls run_step until no more
|
|
819
|
+
progress can be made. Returns all events that occurred.
|
|
820
|
+
"""
|
|
821
|
+
...
|
|
822
|
+
|
|
823
|
+
def run_step(self) -> Tuple[bool, List[NetEvent]]:
|
|
824
|
+
"""Run one step of automatic packet flow.
|
|
825
|
+
|
|
826
|
+
Convenience method equivalent to `do_action(NetAction.run_step())`.
|
|
827
|
+
|
|
828
|
+
Returns:
|
|
829
|
+
A tuple of (made_progress, events) where made_progress indicates
|
|
830
|
+
whether any packets were moved.
|
|
831
|
+
"""
|
|
832
|
+
...
|
|
833
|
+
|
|
834
|
+
def undo_action(self, action: NetAction, events: List[NetEvent]) -> None:
|
|
835
|
+
"""Undo a previously executed action.
|
|
836
|
+
|
|
837
|
+
Args:
|
|
838
|
+
action: The original action that was executed.
|
|
839
|
+
events: The events that were produced by the action.
|
|
840
|
+
|
|
841
|
+
Raises:
|
|
842
|
+
UndoError: If the undo operation fails.
|
|
843
|
+
"""
|
|
844
|
+
...
|
|
845
|
+
|
|
846
|
+
# === Re-exports ===
|
|
847
|
+
|
|
848
|
+
__all__ = [
|
|
849
|
+
# Exceptions
|
|
850
|
+
"NetrunError",
|
|
851
|
+
"PacketNotFoundError",
|
|
852
|
+
"EpochNotFoundError",
|
|
853
|
+
"EpochNotRunningError",
|
|
854
|
+
"EpochNotStartableError",
|
|
855
|
+
"CannotFinishNonEmptyEpochError",
|
|
856
|
+
"UnsentOutputSalvoError",
|
|
857
|
+
"PacketNotInNodeError",
|
|
858
|
+
"OutputPortNotFoundError",
|
|
859
|
+
"OutputPortFullError",
|
|
860
|
+
"SalvoConditionNotFoundError",
|
|
861
|
+
"SalvoConditionNotMetError",
|
|
862
|
+
"MaxSalvosExceededError",
|
|
863
|
+
"NodeNotFoundError",
|
|
864
|
+
"PacketNotAtInputPortError",
|
|
865
|
+
"InputPortNotFoundError",
|
|
866
|
+
"InputPortFullError",
|
|
867
|
+
"CannotMovePacketFromRunningEpochError",
|
|
868
|
+
"CannotMovePacketIntoRunningEpochError",
|
|
869
|
+
"EdgeNotFoundError",
|
|
870
|
+
"UnconnectedOutputPortError",
|
|
871
|
+
"GraphValidationError",
|
|
872
|
+
# Graph types
|
|
873
|
+
"PacketCount",
|
|
874
|
+
"PacketCountN",
|
|
875
|
+
"MaxSalvos",
|
|
876
|
+
"MaxSalvosFinite",
|
|
877
|
+
"PortSlotSpec",
|
|
878
|
+
"PortSlotSpecFinite",
|
|
879
|
+
"PortState",
|
|
880
|
+
"PortStateNumeric",
|
|
881
|
+
"SalvoConditionTerm",
|
|
882
|
+
"Port",
|
|
883
|
+
"PortType",
|
|
884
|
+
"PortRef",
|
|
885
|
+
"Edge",
|
|
886
|
+
"SalvoCondition",
|
|
887
|
+
"SalvoConditionPorts",
|
|
888
|
+
"Node",
|
|
889
|
+
"Graph",
|
|
890
|
+
# NetSim types
|
|
891
|
+
"PacketLocation",
|
|
892
|
+
"EpochState",
|
|
893
|
+
"Packet",
|
|
894
|
+
"Salvo",
|
|
895
|
+
"OrphanedPacketInfo",
|
|
896
|
+
"Epoch",
|
|
897
|
+
"NetAction",
|
|
898
|
+
"NetEvent",
|
|
899
|
+
"NetActionResponseData",
|
|
900
|
+
"NetSim",
|
|
901
|
+
]
|
|
Binary file
|
netrun_sim/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
netrun_sim\__init__.py,sha256=z_JzLqVq1WlfqyWve6qpttrDHlaMSgB37WSszX9mGS0,1347
|
|
2
|
+
netrun_sim\__init__.pyi,sha256=hpk3Ns14656UAJPGntQAvx7iwgQ0LQ5Ct8M6UyfJKY4,26719
|
|
3
|
+
netrun_sim\netrun_sim.pyd,sha256=_8rtM4mdrUymqWMId33nIE51BMMw5TL19eDXk29aG6k,1093632
|
|
4
|
+
netrun_sim\py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
netrun_sim-0.1.2.dist-info\METADATA,sha256=f9TFOJZLbzZOFFP_eTpPhF58YbZpxg07De5YcB52op8,222
|
|
6
|
+
netrun_sim-0.1.2.dist-info\WHEEL,sha256=gPqN4EsdiAyGvmfrYy_ONrF276O8o0hPitI2CKZrEFA,95
|
|
7
|
+
netrun_sim-0.1.2.dist-info\RECORD,,
|