python-drs 0.1.0__tar.gz
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.
- python_drs-0.1.0/LICENSE +21 -0
- python_drs-0.1.0/PKG-INFO +106 -0
- python_drs-0.1.0/README.md +51 -0
- python_drs-0.1.0/drs/__init__.py +49 -0
- python_drs-0.1.0/drs/_execution_context.py +89 -0
- python_drs-0.1.0/drs/callbacks.py +97 -0
- python_drs-0.1.0/drs/config.py +25 -0
- python_drs-0.1.0/drs/data_source.py +49 -0
- python_drs-0.1.0/drs/engine.py +447 -0
- python_drs-0.1.0/drs/exceptions.py +33 -0
- python_drs-0.1.0/drs/flow.py +24 -0
- python_drs-0.1.0/drs/module.py +497 -0
- python_drs-0.1.0/drs/plot.py +254 -0
- python_drs-0.1.0/drs/serialize.py +408 -0
- python_drs-0.1.0/drs/telemetry.py +172 -0
- python_drs-0.1.0/drs/variables.py +547 -0
- python_drs-0.1.0/pyproject.toml +51 -0
- python_drs-0.1.0/python_drs.egg-info/PKG-INFO +106 -0
- python_drs-0.1.0/python_drs.egg-info/SOURCES.txt +21 -0
- python_drs-0.1.0/python_drs.egg-info/dependency_links.txt +1 -0
- python_drs-0.1.0/python_drs.egg-info/requires.txt +12 -0
- python_drs-0.1.0/python_drs.egg-info/top_level.txt +1 -0
- python_drs-0.1.0/setup.cfg +4 -0
python_drs-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jonathan Lamontagne Kratz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: python-drs
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A PyTorch-inspired, event-driven Discrete Rate Simulation framework.
|
|
5
|
+
Author: Jonathan Lamontagne Kratz
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Jonathan Lamontagne Kratz
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/epicgamer17/python-drs
|
|
29
|
+
Project-URL: Repository, https://github.com/epicgamer17/python-drs
|
|
30
|
+
Project-URL: Documentation, https://github.com/epicgamer17/python-drs/tree/main/docs
|
|
31
|
+
Keywords: simulation,discrete-rate,drs,discrete-event,modeling,mining,continuous-flow
|
|
32
|
+
Classifier: Development Status :: 3 - Alpha
|
|
33
|
+
Classifier: Intended Audience :: Science/Research
|
|
34
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
40
|
+
Classifier: Topic :: Scientific/Engineering
|
|
41
|
+
Requires-Python: >=3.9
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
43
|
+
License-File: LICENSE
|
|
44
|
+
Requires-Dist: numpy>=1.21
|
|
45
|
+
Requires-Dist: pandas>=1.3
|
|
46
|
+
Requires-Dist: matplotlib>=3.4
|
|
47
|
+
Requires-Dist: seaborn>=0.12
|
|
48
|
+
Provides-Extra: progress
|
|
49
|
+
Requires-Dist: rich>=13.0; extra == "progress"
|
|
50
|
+
Provides-Extra: dev
|
|
51
|
+
Requires-Dist: build; extra == "dev"
|
|
52
|
+
Requires-Dist: twine; extra == "dev"
|
|
53
|
+
Requires-Dist: pytest; extra == "dev"
|
|
54
|
+
Dynamic: license-file
|
|
55
|
+
|
|
56
|
+
# python-drs
|
|
57
|
+
|
|
58
|
+
A PyTorch-inspired, event-driven **Discrete Rate Simulation (DRS)** framework for modeling systems where material flows continuously over time.
|
|
59
|
+
|
|
60
|
+
Instead of ticking through time at fixed intervals, DRS calculates *exactly* when the next limit (threshold) will be hit, jumps the simulation clock to that precise moment, and triggers the matching state transition. The result is a simulation that runs in a fraction of the time of fixed-step models — and never misses a limit.
|
|
61
|
+
|
|
62
|
+
## Features
|
|
63
|
+
|
|
64
|
+
- **Event-driven time stepping** — simulate years of operation in seconds
|
|
65
|
+
- **PyTorch-style architecture** — `Module`, `Variable`, and `Level` compose into hierarchies with automatic dependency tracking
|
|
66
|
+
- **Built-in telemetry** — every state change is logged and plotted without custom tracking code
|
|
67
|
+
- **Fail-fast guardrails** — the engine stops you from breaking the physics of your model (e.g. draining an empty stockpile)
|
|
68
|
+
- **Applicable to any continuous-flow system** — mining supply chains, water pipes, electrical grids, traffic, and more
|
|
69
|
+
|
|
70
|
+
## Installation
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install python-drs
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Optional extras:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install python-drs[progress] # Rich progress bar
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Quickstart
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from drs import DRSEngine, Module, Level
|
|
86
|
+
|
|
87
|
+
class Stockpile(Module):
|
|
88
|
+
def forward(self):
|
|
89
|
+
# Fill at 50 units per time step
|
|
90
|
+
self.ore.rate = 50.0
|
|
91
|
+
|
|
92
|
+
model = Stockpile()
|
|
93
|
+
model.ore = Level("Ore", initial_value=100.0)
|
|
94
|
+
|
|
95
|
+
engine = DRSEngine(model)
|
|
96
|
+
result = engine.run(max_time=20.0)
|
|
97
|
+
print(result.summary())
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Documentation
|
|
101
|
+
|
|
102
|
+
Full guides, tutorials, and API reference live in the [`docs/`](https://github.com/epicgamer17/python-drs/tree/main/docs) directory.
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# python-drs
|
|
2
|
+
|
|
3
|
+
A PyTorch-inspired, event-driven **Discrete Rate Simulation (DRS)** framework for modeling systems where material flows continuously over time.
|
|
4
|
+
|
|
5
|
+
Instead of ticking through time at fixed intervals, DRS calculates *exactly* when the next limit (threshold) will be hit, jumps the simulation clock to that precise moment, and triggers the matching state transition. The result is a simulation that runs in a fraction of the time of fixed-step models — and never misses a limit.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Event-driven time stepping** — simulate years of operation in seconds
|
|
10
|
+
- **PyTorch-style architecture** — `Module`, `Variable`, and `Level` compose into hierarchies with automatic dependency tracking
|
|
11
|
+
- **Built-in telemetry** — every state change is logged and plotted without custom tracking code
|
|
12
|
+
- **Fail-fast guardrails** — the engine stops you from breaking the physics of your model (e.g. draining an empty stockpile)
|
|
13
|
+
- **Applicable to any continuous-flow system** — mining supply chains, water pipes, electrical grids, traffic, and more
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install python-drs
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Optional extras:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install python-drs[progress] # Rich progress bar
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quickstart
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from drs import DRSEngine, Module, Level
|
|
31
|
+
|
|
32
|
+
class Stockpile(Module):
|
|
33
|
+
def forward(self):
|
|
34
|
+
# Fill at 50 units per time step
|
|
35
|
+
self.ore.rate = 50.0
|
|
36
|
+
|
|
37
|
+
model = Stockpile()
|
|
38
|
+
model.ore = Level("Ore", initial_value=100.0)
|
|
39
|
+
|
|
40
|
+
engine = DRSEngine(model)
|
|
41
|
+
result = engine.run(max_time=20.0)
|
|
42
|
+
print(result.summary())
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Documentation
|
|
46
|
+
|
|
47
|
+
Full guides, tutorials, and API reference live in the [`docs/`](https://github.com/epicgamer17/python-drs/tree/main/docs) directory.
|
|
48
|
+
|
|
49
|
+
## License
|
|
50
|
+
|
|
51
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
__version__ = "0.1.0"
|
|
4
|
+
|
|
5
|
+
# Configure a NullHandler to prevent "No handler found" warnings
|
|
6
|
+
# Users of the library can configure their own logging handlers
|
|
7
|
+
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
|
8
|
+
|
|
9
|
+
from .module import Module, DataSource
|
|
10
|
+
from .engine import DRSEngine, SimulationResult
|
|
11
|
+
from .variables import Variable, Level, Timer, Expression
|
|
12
|
+
from .data_source import DataPoint
|
|
13
|
+
from .flow import Flow
|
|
14
|
+
from .telemetry import Telemetry
|
|
15
|
+
from .exceptions import StateMutationError, DeadlockError
|
|
16
|
+
from .config import DRSConfig, EngineConfig
|
|
17
|
+
from .callbacks import Callback, ProgressBarCallback
|
|
18
|
+
from .serialize import (
|
|
19
|
+
save_state,
|
|
20
|
+
load_state,
|
|
21
|
+
export_architecture,
|
|
22
|
+
save_checkpoint,
|
|
23
|
+
load_checkpoint,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"DRSEngine",
|
|
28
|
+
"SimulationResult",
|
|
29
|
+
"Callback",
|
|
30
|
+
"ProgressBarCallback",
|
|
31
|
+
"Variable",
|
|
32
|
+
"Level",
|
|
33
|
+
"Timer",
|
|
34
|
+
"Expression",
|
|
35
|
+
"DataPoint",
|
|
36
|
+
"DataSource",
|
|
37
|
+
"Module",
|
|
38
|
+
"Flow",
|
|
39
|
+
"Telemetry",
|
|
40
|
+
"StateMutationError",
|
|
41
|
+
"DeadlockError",
|
|
42
|
+
"DRSConfig",
|
|
43
|
+
"EngineConfig",
|
|
44
|
+
"save_state",
|
|
45
|
+
"load_state",
|
|
46
|
+
"export_architecture",
|
|
47
|
+
"save_checkpoint",
|
|
48
|
+
"load_checkpoint",
|
|
49
|
+
]
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ExecutionContext:
|
|
5
|
+
"""
|
|
6
|
+
[INTERNAL] Thread-local context tracking active modules and engines during evaluation.
|
|
7
|
+
|
|
8
|
+
Power User Note: Enforces state ownership and builds the dependency graph implicitly
|
|
9
|
+
as the simulation steps.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
_local = threading.local()
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def push(cls, module):
|
|
16
|
+
"""
|
|
17
|
+
[INTERNAL] Push a module onto the active execution stack.
|
|
18
|
+
|
|
19
|
+
Power User Note: Used when evaluating a module's forward pass to record active scope.
|
|
20
|
+
"""
|
|
21
|
+
if not hasattr(cls._local, "stack"):
|
|
22
|
+
cls._local.stack = []
|
|
23
|
+
cls._local.flow_edges = []
|
|
24
|
+
cls._local.stack.append(module)
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def set_tracing(cls, enabled: bool = True):
|
|
28
|
+
"""
|
|
29
|
+
[INTERNAL] Toggle symbolic tracing mode.
|
|
30
|
+
|
|
31
|
+
Power User Note: Enables AST expression recording instead of eager evaluation.
|
|
32
|
+
"""
|
|
33
|
+
cls._local.tracing = enabled
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def is_tracing(cls) -> bool:
|
|
37
|
+
"""
|
|
38
|
+
[INTERNAL] Check if symbolic tracing mode is active.
|
|
39
|
+
|
|
40
|
+
Power User Note: Used by Variable to decide whether to return expressions.
|
|
41
|
+
"""
|
|
42
|
+
return getattr(cls._local, "tracing", False)
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def pop(cls):
|
|
46
|
+
"""
|
|
47
|
+
[INTERNAL] Pop the top module from the execution stack.
|
|
48
|
+
|
|
49
|
+
Power User Note: Restores the caller's context after a module's execution completes.
|
|
50
|
+
"""
|
|
51
|
+
cls._local.stack.pop()
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def get_current(cls):
|
|
55
|
+
"""
|
|
56
|
+
[INTERNAL] Retrieve the currently executing module.
|
|
57
|
+
|
|
58
|
+
Power User Note: Returns the module at the top of the stack, or None if executing externally.
|
|
59
|
+
"""
|
|
60
|
+
stack = getattr(cls._local, "stack", [])
|
|
61
|
+
return stack[-1] if stack else None
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def set_engine(cls, engine):
|
|
65
|
+
"""
|
|
66
|
+
[INTERNAL] Bind the current DRSEngine to the context.
|
|
67
|
+
|
|
68
|
+
Power User Note: Used to provide modules access to simulation clock and configs.
|
|
69
|
+
"""
|
|
70
|
+
cls._local.engine = engine
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def get_engine(cls):
|
|
74
|
+
"""
|
|
75
|
+
[INTERNAL] Retrieve the currently active DRSEngine.
|
|
76
|
+
|
|
77
|
+
Power User Note: Accesses thread-local storage to retrieve the running engine instance.
|
|
78
|
+
"""
|
|
79
|
+
return getattr(cls._local, "engine", None)
|
|
80
|
+
|
|
81
|
+
@classmethod
|
|
82
|
+
def record_flow_edge(cls, source, target):
|
|
83
|
+
"""
|
|
84
|
+
[INTERNAL] Record a transient flow edge between a source and target module.
|
|
85
|
+
"""
|
|
86
|
+
if source is not None:
|
|
87
|
+
if not hasattr(cls._local, "flow_edges"):
|
|
88
|
+
cls._local.flow_edges = []
|
|
89
|
+
cls._local.flow_edges.append((source, target))
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Optional
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from .engine import DRSEngine, SimulationResult
|
|
6
|
+
from .variables import Variable
|
|
7
|
+
|
|
8
|
+
# TODO: Should this file be a part of the public api?
|
|
9
|
+
# TODO: Should we replace the "forward" system with a callback system on the levels?
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Callback:
|
|
13
|
+
"""Base class for DRS Engine callbacks.
|
|
14
|
+
|
|
15
|
+
Subclass this to hook into the simulation lifecycle.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def on_simulation_start(self, engine: "DRSEngine") -> None:
|
|
19
|
+
"""Called before the simulation loop begins."""
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
def on_step_start(self, engine: "DRSEngine") -> None:
|
|
23
|
+
"""Called at the beginning of each simulation step, after rates are zeroed and models evaluated."""
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
def on_threshold(
|
|
27
|
+
self, engine: "DRSEngine", trigger_var: "Variable", is_upper: bool
|
|
28
|
+
) -> None:
|
|
29
|
+
"""Called when a variable's threshold is the trigger for the next time step."""
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
def on_deadlock(self, engine: "DRSEngine") -> None:
|
|
33
|
+
"""Called immediately before a DeadlockError is raised."""
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
def on_complete(self, engine: "DRSEngine", result: "SimulationResult") -> None:
|
|
37
|
+
"""Called when the simulation loop has completely finished."""
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ProgressBarCallback(Callback):
|
|
42
|
+
"""A built-in callback that uses rich to display a progress bar for the simulation."""
|
|
43
|
+
|
|
44
|
+
def __init__(self):
|
|
45
|
+
try:
|
|
46
|
+
from rich.progress import (
|
|
47
|
+
Progress,
|
|
48
|
+
TextColumn,
|
|
49
|
+
BarColumn,
|
|
50
|
+
TaskProgressColumn,
|
|
51
|
+
TimeElapsedColumn,
|
|
52
|
+
TimeRemainingColumn,
|
|
53
|
+
)
|
|
54
|
+
except ImportError:
|
|
55
|
+
raise ImportError(
|
|
56
|
+
"The 'rich' package is required for the ProgressBarCallback. Install it with 'pip install rich'."
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
self.progress = Progress(
|
|
60
|
+
TextColumn("[progress.description]{task.description}"),
|
|
61
|
+
BarColumn(),
|
|
62
|
+
TaskProgressColumn(),
|
|
63
|
+
TimeElapsedColumn(),
|
|
64
|
+
TimeRemainingColumn(),
|
|
65
|
+
redirect_stdout=False,
|
|
66
|
+
redirect_stderr=False,
|
|
67
|
+
)
|
|
68
|
+
self.task_id = None
|
|
69
|
+
self.max_time = None
|
|
70
|
+
|
|
71
|
+
def on_simulation_start(self, engine: "DRSEngine") -> None:
|
|
72
|
+
self.progress.start()
|
|
73
|
+
# the engine doesn't technically know its max_time until run() is called,
|
|
74
|
+
# but max_time is passed to run(). We can extract it if we attach it to the engine temporarily,
|
|
75
|
+
# or we just assume we'll get it from engine if we store it.
|
|
76
|
+
# Wait, DRSEngine.run(max_time) takes max_time. So it's not known here unless we store it on the engine.
|
|
77
|
+
# Let's assume engine._current_max_time is set before calling this.
|
|
78
|
+
self.max_time = getattr(engine, "_current_max_time", None)
|
|
79
|
+
|
|
80
|
+
if self.max_time:
|
|
81
|
+
self.task_id = self.progress.add_task(
|
|
82
|
+
"[cyan]Simulating...", total=self.max_time
|
|
83
|
+
)
|
|
84
|
+
else:
|
|
85
|
+
self.task_id = self.progress.add_task("[cyan]Simulating...", total=None)
|
|
86
|
+
|
|
87
|
+
def on_step_start(self, engine: "DRSEngine") -> None:
|
|
88
|
+
if self.task_id is not None and self.max_time:
|
|
89
|
+
self.progress.update(self.task_id, completed=engine.current_time)
|
|
90
|
+
|
|
91
|
+
def on_complete(self, engine: "DRSEngine", result: "SimulationResult") -> None:
|
|
92
|
+
if self.task_id is not None and self.max_time:
|
|
93
|
+
self.progress.update(self.task_id, completed=self.max_time)
|
|
94
|
+
self.progress.stop()
|
|
95
|
+
|
|
96
|
+
def on_deadlock(self, engine: "DRSEngine") -> None:
|
|
97
|
+
self.progress.stop()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Any, Dict
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class DRSConfig:
|
|
7
|
+
"""Base configuration class for DRS Modules.
|
|
8
|
+
|
|
9
|
+
Subclass this using @dataclass to define strictly-typed configuration
|
|
10
|
+
blocks for your models, allowing IDE autocomplete and clear parameterization.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class EngineConfig(DRSConfig):
|
|
18
|
+
"""Configuration for the DRS Engine."""
|
|
19
|
+
|
|
20
|
+
max_step_size: float = (
|
|
21
|
+
0.5 # TODO: should change this to float("inf") but for parity dont, it seems to cause behaviour changes, theory is that its because of precision propogation over longer time horizons. Why do we even need a max step size?
|
|
22
|
+
)
|
|
23
|
+
max_deadlock_steps: int = 20
|
|
24
|
+
max_time: float = None
|
|
25
|
+
strict_mode: bool = False
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Generic data point for data yielded by DataSource.
|
|
5
|
+
|
|
6
|
+
Note on alternatives:
|
|
7
|
+
If you prefer standard Python semantics, you could potentially bypass `DataSource` entirely
|
|
8
|
+
and use a plain Python generator (e.g. `yield DataPoint(...)`). However, the `DataSource`
|
|
9
|
+
class is retained to seamlessly integrate with `Module`, making it a first-class citizen
|
|
10
|
+
in the simulation graph and tracking execution contexts and telemetry.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DataPoint:
|
|
15
|
+
"""A single batch of data yielded by a DataSource.
|
|
16
|
+
|
|
17
|
+
Fields are accessed as attributes. Any keyword argument passed to
|
|
18
|
+
``__init__`` is stored and accessible via ``.name``::
|
|
19
|
+
|
|
20
|
+
point = DataPoint(weight=40000.0, priority=1)
|
|
21
|
+
point.weight # → 40000.0
|
|
22
|
+
point.priority # → 1
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, **kwargs: Any):
|
|
26
|
+
"""
|
|
27
|
+
Initialize a new DataPoint with arbitrary attributes.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
**kwargs: Arbitrary keyword arguments that will be stored and exposed
|
|
31
|
+
as attributes on the DataPoint instance.
|
|
32
|
+
"""
|
|
33
|
+
self._data = dict(kwargs)
|
|
34
|
+
self._source = None
|
|
35
|
+
|
|
36
|
+
def __getattr__(self, name):
|
|
37
|
+
if name.startswith("_"):
|
|
38
|
+
raise AttributeError(name)
|
|
39
|
+
try:
|
|
40
|
+
return self._data[name]
|
|
41
|
+
except KeyError:
|
|
42
|
+
raise AttributeError(
|
|
43
|
+
f"'{type(self).__name__}' has no field '{name}'. "
|
|
44
|
+
f"Available fields: {list(self._data)}"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def __repr__(self):
|
|
48
|
+
items = ", ".join(f"{k}={v}" for k, v in self._data.items())
|
|
49
|
+
return f"DataPoint({items})"
|