dyon 0.7.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.
- dyon-0.7.0/PKG-INFO +419 -0
- dyon-0.7.0/README.md +371 -0
- dyon-0.7.0/dyon/__init__.py +16 -0
- dyon-0.7.0/dyon/_compat.py +66 -0
- dyon-0.7.0/dyon/autonomous/__init__.py +19 -0
- dyon-0.7.0/dyon/autonomous/base.py +29 -0
- dyon-0.7.0/dyon/autonomous/deployer.py +91 -0
- dyon-0.7.0/dyon/autonomous/gym_env.py +124 -0
- dyon-0.7.0/dyon/autonomous/ooda.py +253 -0
- dyon-0.7.0/dyon/autonomous/overseer.py +310 -0
- dyon-0.7.0/dyon/autonomous/planner.py +80 -0
- dyon-0.7.0/dyon/autonomous/trainer.py +67 -0
- dyon-0.7.0/dyon/cli/__init__.py +1 -0
- dyon-0.7.0/dyon/cli/main.py +285 -0
- dyon-0.7.0/dyon/collection/__init__.py +15 -0
- dyon-0.7.0/dyon/collection/aggregate.py +132 -0
- dyon-0.7.0/dyon/collection/base.py +87 -0
- dyon-0.7.0/dyon/collection/collection_twin.py +106 -0
- dyon-0.7.0/dyon/collection/composite.py +194 -0
- dyon-0.7.0/dyon/collection/network_twin.py +209 -0
- dyon-0.7.0/dyon/collection/orchestrator.py +56 -0
- dyon-0.7.0/dyon/connector/__init__.py +12 -0
- dyon-0.7.0/dyon/connector/api_connector.py +62 -0
- dyon-0.7.0/dyon/connector/base.py +71 -0
- dyon-0.7.0/dyon/connector/ditto_connector.py +109 -0
- dyon-0.7.0/dyon/connector/mqtt_connector.py +107 -0
- dyon-0.7.0/dyon/core/__init__.py +17 -0
- dyon-0.7.0/dyon/core/base.py +132 -0
- dyon-0.7.0/dyon/core/config.py +157 -0
- dyon-0.7.0/dyon/core/events.py +85 -0
- dyon-0.7.0/dyon/core/lifecycle.py +85 -0
- dyon-0.7.0/dyon/core/registry.py +55 -0
- dyon-0.7.0/dyon/core/types.py +57 -0
- dyon-0.7.0/dyon/data/__init__.py +13 -0
- dyon-0.7.0/dyon/data/management/__init__.py +4 -0
- dyon-0.7.0/dyon/data/management/health.py +47 -0
- dyon-0.7.0/dyon/data/management/pipeline.py +107 -0
- dyon-0.7.0/dyon/data/storage/__init__.py +16 -0
- dyon-0.7.0/dyon/data/storage/base.py +119 -0
- dyon-0.7.0/dyon/data/storage/influx.py +227 -0
- dyon-0.7.0/dyon/data/storage/minio_store.py +84 -0
- dyon-0.7.0/dyon/data/storage/mongo.py +117 -0
- dyon-0.7.0/dyon/data/storage/postgres.py +51 -0
- dyon-0.7.0/dyon/data/storage/provenance.py +124 -0
- dyon-0.7.0/dyon/data/storage/redis_store.py +88 -0
- dyon-0.7.0/dyon/data/text_ingestor.py +157 -0
- dyon-0.7.0/dyon/data/writer.py +103 -0
- dyon-0.7.0/dyon/infra/__init__.py +4 -0
- dyon-0.7.0/dyon/infra/docker.py +349 -0
- dyon-0.7.0/dyon/infra/health_check.py +100 -0
- dyon-0.7.0/dyon/intelligent/__init__.py +15 -0
- dyon-0.7.0/dyon/intelligent/agent.py +152 -0
- dyon-0.7.0/dyon/intelligent/base.py +42 -0
- dyon-0.7.0/dyon/intelligent/knowledge_graph.py +220 -0
- dyon-0.7.0/dyon/intelligent/mas.py +228 -0
- dyon-0.7.0/dyon/intelligent/tools.py +100 -0
- dyon-0.7.0/dyon/learning/__init__.py +64 -0
- dyon-0.7.0/dyon/learning/_util.py +62 -0
- dyon-0.7.0/dyon/learning/demonstrations.py +205 -0
- dyon-0.7.0/dyon/learning/features.py +182 -0
- dyon-0.7.0/dyon/learning/imitation_trainers.py +168 -0
- dyon-0.7.0/dyon/learning/irl_trainers.py +174 -0
- dyon-0.7.0/dyon/learning/maxent.py +180 -0
- dyon-0.7.0/dyon/learning/pipeline.py +296 -0
- dyon-0.7.0/dyon/learning/reward.py +126 -0
- dyon-0.7.0/dyon/ml/__init__.py +3 -0
- dyon-0.7.0/dyon/ml/corpus.py +102 -0
- dyon-0.7.0/dyon/network/__init__.py +4 -0
- dyon-0.7.0/dyon/network/ingestor.py +102 -0
- dyon-0.7.0/dyon/network/transport.py +107 -0
- dyon-0.7.0/dyon/notifications/__init__.py +3 -0
- dyon-0.7.0/dyon/notifications/notifier.py +104 -0
- dyon-0.7.0/dyon/physical/__init__.py +4 -0
- dyon-0.7.0/dyon/physical/adapters/__init__.py +1 -0
- dyon-0.7.0/dyon/physical/base.py +34 -0
- dyon-0.7.0/dyon/physical/simulator.py +141 -0
- dyon-0.7.0/dyon/reactive/__init__.py +16 -0
- dyon-0.7.0/dyon/reactive/actions.py +50 -0
- dyon-0.7.0/dyon/reactive/base.py +31 -0
- dyon-0.7.0/dyon/reactive/fsm_engine.py +205 -0
- dyon-0.7.0/dyon/reactive/pid.py +95 -0
- dyon-0.7.0/dyon/reactive/rule_engine.py +211 -0
- dyon-0.7.0/dyon/reactive/rule_repository.py +185 -0
- dyon-0.7.0/dyon/services/__init__.py +4 -0
- dyon-0.7.0/dyon/services/api/__init__.py +3 -0
- dyon-0.7.0/dyon/services/api/app.py +56 -0
- dyon-0.7.0/dyon/services/api/routes.py +94 -0
- dyon-0.7.0/dyon/services/api/streaming.py +64 -0
- dyon-0.7.0/dyon/services/base.py +66 -0
- dyon-0.7.0/dyon/services/composite.py +35 -0
- dyon-0.7.0/dyon/services/ditto/__init__.py +4 -0
- dyon-0.7.0/dyon/services/ditto/client.py +183 -0
- dyon-0.7.0/dyon/services/ditto/sync.py +87 -0
- dyon-0.7.0/dyon/session/__init__.py +3 -0
- dyon-0.7.0/dyon/session/context.py +175 -0
- dyon-0.7.0/dyon/simulation/__init__.py +16 -0
- dyon-0.7.0/dyon/simulation/base.py +22 -0
- dyon-0.7.0/dyon/simulation/discrete_event.py +61 -0
- dyon-0.7.0/dyon/simulation/forecaster.py +55 -0
- dyon-0.7.0/dyon/simulation/ode_model.py +77 -0
- dyon-0.7.0/dyon/simulation/runner.py +110 -0
- dyon-0.7.0/dyon/simulation/surrogate.py +79 -0
- dyon-0.7.0/dyon.egg-info/PKG-INFO +419 -0
- dyon-0.7.0/dyon.egg-info/SOURCES.txt +119 -0
- dyon-0.7.0/dyon.egg-info/dependency_links.txt +1 -0
- dyon-0.7.0/dyon.egg-info/entry_points.txt +3 -0
- dyon-0.7.0/dyon.egg-info/requires.txt +41 -0
- dyon-0.7.0/dyon.egg-info/top_level.txt +1 -0
- dyon-0.7.0/pyproject.toml +96 -0
- dyon-0.7.0/setup.cfg +4 -0
- dyon-0.7.0/tests/test_backward_compat.py +43 -0
- dyon-0.7.0/tests/test_config_thresholds.py +54 -0
- dyon-0.7.0/tests/test_gym_env.py +52 -0
- dyon-0.7.0/tests/test_knowledge_graph.py +37 -0
- dyon-0.7.0/tests/test_learning.py +203 -0
- dyon-0.7.0/tests/test_lifecycle.py +82 -0
- dyon-0.7.0/tests/test_mqtt_topic.py +34 -0
- dyon-0.7.0/tests/test_ooda_decide.py +60 -0
- dyon-0.7.0/tests/test_provenance.py +56 -0
- dyon-0.7.0/tests/test_reactive_fsm.py +144 -0
- dyon-0.7.0/tests/test_rule_repository.py +71 -0
dyon-0.7.0/PKG-INFO
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: dyon
|
|
3
|
+
Version: 0.7.0
|
|
4
|
+
Summary: Domain-agnostic Python framework for digital twin architectures
|
|
5
|
+
License: MIT
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: paho-mqtt>=2.0
|
|
9
|
+
Requires-Dist: influxdb-client>=1.40
|
|
10
|
+
Requires-Dist: pymongo>=4.6
|
|
11
|
+
Requires-Dist: redis>=5.0
|
|
12
|
+
Requires-Dist: minio>=7.2
|
|
13
|
+
Requires-Dist: asyncpg>=0.29
|
|
14
|
+
Requires-Dist: pydantic>=2.5
|
|
15
|
+
Requires-Dist: pydantic-settings>=2.1
|
|
16
|
+
Requires-Dist: python-dotenv>=1.0
|
|
17
|
+
Requires-Dist: fastapi>=0.109
|
|
18
|
+
Requires-Dist: uvicorn[standard]>=0.27
|
|
19
|
+
Requires-Dist: httpx>=0.26
|
|
20
|
+
Requires-Dist: scipy>=1.12
|
|
21
|
+
Requires-Dist: numpy>=1.26
|
|
22
|
+
Requires-Dist: simpy>=4.1
|
|
23
|
+
Requires-Dist: onnxruntime>=1.17
|
|
24
|
+
Requires-Dist: scikit-learn>=1.4
|
|
25
|
+
Requires-Dist: prophet>=1.1
|
|
26
|
+
Requires-Dist: transitions>=0.9
|
|
27
|
+
Requires-Dist: simple-pid>=2.0
|
|
28
|
+
Requires-Dist: neo4j>=5.17
|
|
29
|
+
Requires-Dist: vaderSentiment>=3.3
|
|
30
|
+
Requires-Dist: langchain>=0.3
|
|
31
|
+
Requires-Dist: langchain-classic>=0.1
|
|
32
|
+
Requires-Dist: langchain-community>=0.3
|
|
33
|
+
Requires-Dist: langchain-openai>=0.2
|
|
34
|
+
Requires-Dist: langchain-anthropic>=0.2
|
|
35
|
+
Requires-Dist: langchain-ollama>=0.2
|
|
36
|
+
Requires-Dist: stable-baselines3<2.3,>=2.2
|
|
37
|
+
Requires-Dist: gymnasium<0.30,>=0.29
|
|
38
|
+
Requires-Dist: imitation<2.0,>=1.0
|
|
39
|
+
Requires-Dist: pyyaml>=6.0
|
|
40
|
+
Requires-Dist: click>=8.1
|
|
41
|
+
Requires-Dist: pyserial>=3.5
|
|
42
|
+
Provides-Extra: dev
|
|
43
|
+
Requires-Dist: pytest>=7.4; extra == "dev"
|
|
44
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
45
|
+
Requires-Dist: pytest-cov>=4.1; extra == "dev"
|
|
46
|
+
Requires-Dist: ruff>=0.2; extra == "dev"
|
|
47
|
+
Requires-Dist: mypy>=1.8; extra == "dev"
|
|
48
|
+
|
|
49
|
+
# Dyon
|
|
50
|
+
|
|
51
|
+
Dyon is a domain-agnostic Python framework for building **digital twins** —
|
|
52
|
+
live software models of a real-world asset that ingest its sensor data, store and
|
|
53
|
+
model it, react to it, reason about it, and act on it. The same framework works
|
|
54
|
+
for a pump, a patch of soil, a plant, an HVAC unit, or a manufacturing line,
|
|
55
|
+
because nothing in the core knows anything about your domain. You declare what
|
|
56
|
+
your asset's sensors look like, write a small amount of asset-specific glue, and
|
|
57
|
+
the framework supplies the rest.
|
|
58
|
+
|
|
59
|
+
It does that by organising every twin into **six layers** stacked from raw data
|
|
60
|
+
at the bottom to autonomous decision-making at the top, plus two cross-cutting
|
|
61
|
+
subsystems: **connectors** that let twins talk to each other, and **collection
|
|
62
|
+
twins** that group many twins into one. Every layer is optional — you assemble
|
|
63
|
+
only the ones your asset needs.
|
|
64
|
+
|
|
65
|
+
## The six-layer architecture
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
69
|
+
│ AUTONOMOUS LAYER (Layer 6) │
|
|
70
|
+
│ OODA loop · GoalPlanner · AutonomousOverseer (LLM) │
|
|
71
|
+
│ RL policy (SAC/TD3/PPO/A2C) · PolicyDeployer · HumanNotifier│
|
|
72
|
+
├─────────────────────────────────────────────────────────────┤
|
|
73
|
+
│ INTELLIGENT LAYER (Layer 5) │
|
|
74
|
+
│ MultiAgentSystem · LangChain DiagnosticAgent │
|
|
75
|
+
│ Neo4j KnowledgeGraph · per-agent status & history │
|
|
76
|
+
├─────────────────────────────────────────────────────────────┤
|
|
77
|
+
│ REACTIVE LAYER (Layer 4) │
|
|
78
|
+
│ ThresholdRuleEngine · MultiStateFSMRuleEngine │
|
|
79
|
+
│ simple-pid PIDController · PostgreSQL RuleRepository │
|
|
80
|
+
├─────────────────────────────────────────────────────────────┤
|
|
81
|
+
│ SERVICES LAYER (Layer 3) │
|
|
82
|
+
│ Eclipse Ditto sync · FastAPI REST + SSE chat │
|
|
83
|
+
├─────────────────────────────────────────────────────────────┤
|
|
84
|
+
│ SIMULATION & MODEL LAYER (Layer 2) │
|
|
85
|
+
│ scipy ODE · ONNX/sklearn surrogate · SimPy · Prophet │
|
|
86
|
+
├─────────────────────────────────────────────────────────────┤
|
|
87
|
+
│ DATA LAYER (Layer 1) │
|
|
88
|
+
│ MQTT ingest · TelemetryRouter · DataManagementPipeline │
|
|
89
|
+
│ InfluxDB · MongoDB · Redis · MinIO │
|
|
90
|
+
│ ProvenanceLog · SessionStore · TextIngestor │
|
|
91
|
+
└─────────────────────────────────────────────────────────────┘
|
|
92
|
+
|
|
93
|
+
Cross-cutting: Connectors (MQTT · Ditto · HTTP API)
|
|
94
|
+
Collection twins (Aggregate · Collection ·
|
|
95
|
+
Composite · Network)
|
|
96
|
+
Feeding Layer 6: Learning toolkit (imitation + inverse RL → policies/rewards)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
What each layer does:
|
|
100
|
+
|
|
101
|
+
| Layer | Responsibility | Core building blocks |
|
|
102
|
+
|-------|----------------|----------------------|
|
|
103
|
+
| **1 — Data** | Ingest telemetry off MQTT, route it to the right stores, smooth it, score asset health, keep an audit trail | `MQTTIngestor`, `TelemetryRouter`, `DataManagementPipeline`, the `Influx`/`Mongo`/`Redis`/`MinIO` adapters, `ProvenanceLog`, `SessionStore`, `TextIngestor` |
|
|
104
|
+
| **2 — Simulation** | Predict, forecast and detect drift against a model of the asset | `ODEModel` (scipy), `ONNXSurrogate` / `SKLearnSurrogate`, `SimPyModel`, `ProphetForecaster`, `ModelRunner` |
|
|
105
|
+
| **3 — Services** | Expose the twin: sync canonical state to Eclipse Ditto, serve a REST + SSE API | `DittoSyncService`, the FastAPI app, the SSE chat stream |
|
|
106
|
+
| **4 — Reactive** | Turn readings into actions automatically: thresholds, a state machine, closed-loop control | `ThresholdRuleEngine`, `MultiStateFSMRuleEngine`, `PIDController`, `RuleRepository` |
|
|
107
|
+
| **5 — Intelligent** | Diagnose *why* something is wrong using a knowledge graph and LLM agents | `KnowledgeGraph` (Neo4j), `DiagnosticAgent` (LangChain), `MultiAgentSystem` |
|
|
108
|
+
| **6 — Autonomous** | Decide *what to do next*: observe-orient-decide-act, goal planning, learned RL policies, human escalation | `OODALoop`, `GoalPlanner`, `AutonomousOverseer`, `PolicyTrainer`/`PolicyDeployer`, `HumanNotifier` |
|
|
109
|
+
|
|
110
|
+
A monitoring-only twin needs just the Data layer (plus MQTT ingest). A
|
|
111
|
+
self-managing asset stacks all six. You never edit the framework to choose — you
|
|
112
|
+
simply return the layers you want from one method (`build_layers`).
|
|
113
|
+
|
|
114
|
+
## Collection twins
|
|
115
|
+
|
|
116
|
+
Real systems are rarely a single asset. Four collection patterns let you treat a
|
|
117
|
+
group of twins as one twin, each suited to a different topology:
|
|
118
|
+
|
|
119
|
+
| Pattern | Use it for |
|
|
120
|
+
|----------------|-------------------------------------------------------------------|
|
|
121
|
+
| `AggregateDT` | A fleet of *identical* assets — fused state and shared control |
|
|
122
|
+
| `CollectionDT` | Batch monitoring, outlier detection, statistical comparison |
|
|
123
|
+
| `CompositeDT` | Hierarchical systems that exchange boundary conditions, with swap |
|
|
124
|
+
| `NetworkDT` | Graph-topology systems — cascade risk and bottleneck detection |
|
|
125
|
+
|
|
126
|
+
## Connectors
|
|
127
|
+
|
|
128
|
+
Connectors are the cross-twin transport. A twin can publish to and subscribe
|
|
129
|
+
from another twin over `MQTTConnector`, `DittoConnector`, or `APIConnector`
|
|
130
|
+
(HTTP), all managed through a `ConnectorRegistry`. This is what makes
|
|
131
|
+
*cross-domain* twins possible: a soil twin can feed a plant twin, which can feed
|
|
132
|
+
a yield-forecasting twin.
|
|
133
|
+
|
|
134
|
+
## Installation
|
|
135
|
+
|
|
136
|
+
Dyon requires **Python 3.11+**. The backing services (MQTT broker,
|
|
137
|
+
databases, Ditto, Neo4j) run in Docker, so you also need Docker with the Compose
|
|
138
|
+
plugin.
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
pip install dyon
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
That installs the framework and the `dyon` command-line tool. Current version:
|
|
145
|
+
**0.4.3**.
|
|
146
|
+
|
|
147
|
+
### Coming from `dt-forge`?
|
|
148
|
+
|
|
149
|
+
This framework was previously published as **`dt-forge`** (package `dt_forge`,
|
|
150
|
+
command `dtforge`). It is now **Dyon**, and existing projects keep working with no
|
|
151
|
+
changes: `import dt_forge` and `from dt_forge.… import …` transparently resolve to
|
|
152
|
+
`dyon`, and the `dtforge` command still runs. You will see a one-time
|
|
153
|
+
`DeprecationWarning` pointing you at the new name. To migrate, replace `dt_forge`
|
|
154
|
+
with `dyon` in your imports and `dtforge` with `dyon` on the command line. The
|
|
155
|
+
compatibility shim will be removed in a future major release.
|
|
156
|
+
|
|
157
|
+
## Quick start
|
|
158
|
+
|
|
159
|
+
### 1. Configure
|
|
160
|
+
|
|
161
|
+
All configuration comes from environment variables with the `DT_` prefix.
|
|
162
|
+
Nested fields use a **double-underscore** delimiter — `DT_MQTT__BROKER`, not
|
|
163
|
+
`DT_MQTT_BROKER` (the single-underscore form is silently ignored). Put them in a
|
|
164
|
+
`.env` file in your project directory:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
DT_ASSET_ID=pump_001
|
|
168
|
+
DT_ASSET_TYPE=centrifugal_pump
|
|
169
|
+
DT_ASSET_NAME="Plant A Pump"
|
|
170
|
+
|
|
171
|
+
DT_MQTT__BROKER=localhost
|
|
172
|
+
DT_MQTT__PORT=1883
|
|
173
|
+
|
|
174
|
+
DT_INFLUX__URL=http://localhost:8086
|
|
175
|
+
DT_INFLUX__TOKEN=my-super-secret-token
|
|
176
|
+
DT_INFLUX__ORG=digital_twin
|
|
177
|
+
DT_INFLUX__BUCKET=asset_telemetry
|
|
178
|
+
|
|
179
|
+
DT_MONGO__URI=mongodb://admin:password@localhost:27017
|
|
180
|
+
DT_REDIS__URL=redis://localhost:6379
|
|
181
|
+
DT_DITTO__URL=http://localhost:8080
|
|
182
|
+
|
|
183
|
+
# Optional — only for the intelligent layer
|
|
184
|
+
DT_LLM__PROVIDER=anthropic # openai | anthropic | ollama
|
|
185
|
+
DT_LLM__MODEL=claude-sonnet-4-6
|
|
186
|
+
DT_LLM__API_KEY=sk-ant-...
|
|
187
|
+
DT_NEO4J__URI=bolt://localhost:7687
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Every field has a sensible default (see `dyon/core/config.py`), so you only
|
|
191
|
+
set what differs from the defaults.
|
|
192
|
+
|
|
193
|
+
### 2. Start the infrastructure you need
|
|
194
|
+
|
|
195
|
+
`dyon infra up` writes a `docker-compose.yml` containing exactly the services
|
|
196
|
+
your chosen layers require, then runs `docker compose up -d`:
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
# Minimal monitoring twin (MQTT broker + the four data stores):
|
|
200
|
+
dyon infra up --layers data,network
|
|
201
|
+
|
|
202
|
+
# Add Eclipse Ditto (services) and the Neo4j knowledge graph (intelligent):
|
|
203
|
+
dyon infra up --layers data,network,services,intelligent
|
|
204
|
+
|
|
205
|
+
# Write the compose file without starting anything:
|
|
206
|
+
dyon infra up --layers data,network,services --generate-only
|
|
207
|
+
docker compose up -d
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Each layer maps to a fixed set of containers:
|
|
211
|
+
|
|
212
|
+
| Service | Host port(s) | Provisioned for the layer(s) | Purpose |
|
|
213
|
+
|----------------|--------------|------------------------------|----------------------------------|
|
|
214
|
+
| Mosquitto | 1883, 9001 | `network` | MQTT broker (+ WebSocket) |
|
|
215
|
+
| InfluxDB | 8086 | `data` or `network` | Time-series telemetry |
|
|
216
|
+
| MongoDB | 27017 | `data` or `network` | Events + provenance |
|
|
217
|
+
| Redis | 6379 | `data` or `network` | Cache, FSM state, sessions |
|
|
218
|
+
| MinIO | 9000 | `data` or `network` | Trained models, large objects |
|
|
219
|
+
| Eclipse Ditto | 8080 | `services` | Canonical twin state (+ nginx) |
|
|
220
|
+
| Neo4j | 7474, 7687 | `intelligent` | Knowledge graph |
|
|
221
|
+
| Grafana | 3000 | always | Dashboards / observability |
|
|
222
|
+
|
|
223
|
+
> **PostgreSQL is not provisioned by `infra up`.** The optional
|
|
224
|
+
> `RuleRepository` (versioned, hot-reloadable reactive rules) talks to Postgres
|
|
225
|
+
> through `asyncpg`; if you use it, point it at a Postgres instance you run
|
|
226
|
+
> yourself. Everything else in the reactive layer works without it.
|
|
227
|
+
|
|
228
|
+
The command also records your chosen layers in a `.dyon-layers` file. Once the
|
|
229
|
+
containers are up, verify the twin can reach each one:
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
dyon infra check # reads .dyon-layers automatically
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Every reachable service prints a `✓`.
|
|
236
|
+
|
|
237
|
+
### 3. Scaffold a twin
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
dyon init \
|
|
241
|
+
--asset-type centrifugal_pump \
|
|
242
|
+
--name "Plant A Pump" \
|
|
243
|
+
--asset-id pump_001
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
That writes two files into the output directory:
|
|
247
|
+
|
|
248
|
+
| File | Purpose |
|
|
249
|
+
|-----------|----------------------------------------------------------------|
|
|
250
|
+
| `twin.py` | A runnable twin class — edit `build_layers()` to wire your asset|
|
|
251
|
+
| `.env` | Environment configuration, pre-filled with the defaults |
|
|
252
|
+
|
|
253
|
+
The generated `twin.py` already wires the Data, MQTT-ingest, data-management,
|
|
254
|
+
Ditto-sync and reactive layers, so it runs as soon as you fill in your sensor
|
|
255
|
+
fields. Add the simulation, intelligent and autonomous layers as your twin grows.
|
|
256
|
+
|
|
257
|
+
### 4. Run
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
python twin.py # run the module directly
|
|
261
|
+
# or, from the project directory:
|
|
262
|
+
dyon run twin # imports twin.py and drives its lifecycle
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
If you wired the services layer, the FastAPI app serves these endpoints:
|
|
266
|
+
|
|
267
|
+
| Endpoint | Returns |
|
|
268
|
+
|------------------------------|----------------------------------------------------|
|
|
269
|
+
| `GET /health` | Liveness — `{asset_id, status}` |
|
|
270
|
+
| `GET /api/twin/state` | Canonical twin state from Ditto |
|
|
271
|
+
| `GET /api/twin/telemetry` | Latest telemetry feature from Ditto |
|
|
272
|
+
| `GET /api/twin/health-score`| Current health-score feature |
|
|
273
|
+
| `GET /api/twin/events` | Recent events (needs a doc store) |
|
|
274
|
+
| `POST /api/twin/external` | Inbound push from another twin's `APIConnector` |
|
|
275
|
+
| `POST /api/chat` | SSE stream of the LLM diagnostic agent's replies |
|
|
276
|
+
|
|
277
|
+
## A monitoring twin in ~30 lines
|
|
278
|
+
|
|
279
|
+
```python
|
|
280
|
+
import asyncio, logging
|
|
281
|
+
from dotenv import load_dotenv
|
|
282
|
+
from dyon.core.config import TwinConfig, SensorFieldSpec
|
|
283
|
+
from dyon.core.base import AbstractDigitalTwin
|
|
284
|
+
from dyon.core.lifecycle import TwinLifecycle
|
|
285
|
+
from dyon.data import InfluxAdapter, MongoAdapter, RedisAdapter
|
|
286
|
+
from dyon.data.writer import TelemetryRouter
|
|
287
|
+
from dyon.data.management import DataManagementPipeline
|
|
288
|
+
from dyon.network import MQTTIngestor
|
|
289
|
+
from dyon.reactive import ThresholdRuleEngine
|
|
290
|
+
|
|
291
|
+
load_dotenv()
|
|
292
|
+
logging.basicConfig(level=logging.INFO)
|
|
293
|
+
|
|
294
|
+
config = TwinConfig(sensor_fields=[
|
|
295
|
+
SensorFieldSpec(name="temperature_c", nominal=25.0, noise_std=0.5,
|
|
296
|
+
warn_threshold=60.0, crit_threshold=75.0),
|
|
297
|
+
SensorFieldSpec(name="pressure_bar", nominal=4.0, noise_std=0.05,
|
|
298
|
+
warn_threshold=3.0, crit_threshold=2.0,
|
|
299
|
+
threshold_direction="low"),
|
|
300
|
+
])
|
|
301
|
+
|
|
302
|
+
class PumpTwin(AbstractDigitalTwin):
|
|
303
|
+
def build_layers(self):
|
|
304
|
+
ts, doc, cache = InfluxAdapter(self.config), MongoAdapter(self.config), RedisAdapter(self.config)
|
|
305
|
+
router = TelemetryRouter(self.config, self.bus,
|
|
306
|
+
ts_store=ts, doc_store=doc, cache=cache)
|
|
307
|
+
return {
|
|
308
|
+
"data": router,
|
|
309
|
+
"network": MQTTIngestor(self.config, self.bus, router=router),
|
|
310
|
+
"data_mgmt": DataManagementPipeline(self.config, self.bus,
|
|
311
|
+
ts_store=ts, cache=cache),
|
|
312
|
+
"reactive": ThresholdRuleEngine(self.config, self.bus,
|
|
313
|
+
ts_store=ts, cache=cache, doc_store=doc),
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if __name__ == "__main__":
|
|
317
|
+
lc = TwinLifecycle(); lc.add(PumpTwin(config))
|
|
318
|
+
asyncio.run(lc.run_forever())
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Point a real device (or `dyon.physical.simulator.GenericSimulator`) at the
|
|
322
|
+
topic `dt/pump_001/telemetry`, and the twin will route every reading to InfluxDB
|
|
323
|
+
and MongoDB, smooth it, score health, and raise warning/critical events through
|
|
324
|
+
the threshold engine — all automatically. Layer a simulation model, a knowledge
|
|
325
|
+
graph, an LLM agent and an OODA loop on top of this skeleton when you need them.
|
|
326
|
+
|
|
327
|
+
## Teaching a twin from demonstrations
|
|
328
|
+
|
|
329
|
+
The autonomous layer's RL policies need a reward function. When the behaviour you
|
|
330
|
+
want is a human skill you can't easily write down as a reward, the
|
|
331
|
+
`dyon.learning` toolkit lets you learn it from examples instead:
|
|
332
|
+
|
|
333
|
+
- **Imitation** — clone an expert's actions directly (`BCTrainer`, `DAggerTrainer`).
|
|
334
|
+
- **Inverse RL** — *recover the reward function* behind an expert's behaviour
|
|
335
|
+
(`AIRLTrainer`, `GAILTrainer`, `MaxEntIRLTrainer`).
|
|
336
|
+
- **Plumbing** — `FeatureSpec` (one source of truth for the observation vector),
|
|
337
|
+
`Demonstrations`/`DemonstrationSource`, `LearnedRewardFn` (reuse a recovered
|
|
338
|
+
reward in ordinary RL), and `SkillTransferPipeline` (chain BC → IRL → RL, then
|
|
339
|
+
validate, version and promote).
|
|
340
|
+
|
|
341
|
+
A recovered reward plugs straight back into the same `GenericTwinEnv` and
|
|
342
|
+
`PolicyDeployer` the autonomous layer already uses.
|
|
343
|
+
|
|
344
|
+
## Extension points
|
|
345
|
+
|
|
346
|
+
The framework is built around protocols and abstract base classes — swap any
|
|
347
|
+
piece without touching the rest:
|
|
348
|
+
|
|
349
|
+
| Extend | By |
|
|
350
|
+
|-------------------------|-------------------------------------------------------|
|
|
351
|
+
| Storage backend | Implementing the `TimeSeriesStore` / `DocumentStore` /|
|
|
352
|
+
| | `CacheStore` / `ObjectStore` protocol |
|
|
353
|
+
| Physics model | Subclassing `ODEModel` or implementing `TwinModel` |
|
|
354
|
+
| ML surrogate | Implementing `TwinModel` over ONNX or scikit-learn |
|
|
355
|
+
| Discrete-event model | Wrapping a generator with `SimPyModel` |
|
|
356
|
+
| Forecaster | Wrapping a time-series model with `ProphetForecaster` |
|
|
357
|
+
| Custom reactive rule | Implementing `Rule.evaluate(readings)` |
|
|
358
|
+
| N-state FSM | Subclassing `MultiStateFSMRuleEngine` |
|
|
359
|
+
| Diagnostic agent tools | Overriding `DiagnosticAgent._build_extra_tools()` |
|
|
360
|
+
| LLM provider | Setting `DT_LLM__PROVIDER` (openai/anthropic/ollama) |
|
|
361
|
+
| Goal-based assessment | Subclassing `GoalPlanner.assess()` |
|
|
362
|
+
| Strategic LLM decisions | Wiring an `AutonomousOverseer` into the `OODALoop` |
|
|
363
|
+
| RL policy | `PolicyTrainer` + `PolicyDeployer` (Stable-Baselines3)|
|
|
364
|
+
| Reward function | Passing `reward_fn` to `GenericTwinEnv`, or learning |
|
|
365
|
+
| | one with `dyon.learning` |
|
|
366
|
+
| Notification channel | Implementing a `NotificationBackend` (email/Slack/web)|
|
|
367
|
+
| Cross-twin transport | Implementing `ConnectorProtocol` |
|
|
368
|
+
| New collection pattern | Subclassing `AbstractCollectionTwin` |
|
|
369
|
+
| Domain session state | Subclassing `SessionContext`, using `SessionStore[T]` |
|
|
370
|
+
|
|
371
|
+
## Technology stack
|
|
372
|
+
|
|
373
|
+
| Concern | Library |
|
|
374
|
+
|------------------|----------------------------------------------------|
|
|
375
|
+
| Messaging | Eclipse Mosquitto / paho-mqtt 2.x |
|
|
376
|
+
| Time-series | InfluxDB 2 (`influxdb-client`) |
|
|
377
|
+
| Documents | MongoDB (`pymongo` + `motor`) |
|
|
378
|
+
| Cache / pub-sub | Redis |
|
|
379
|
+
| Object storage | MinIO (S3 API) |
|
|
380
|
+
| Relational | PostgreSQL (`asyncpg`) — optional, for `RuleRepository` |
|
|
381
|
+
| Twin state | Eclipse Ditto |
|
|
382
|
+
| Knowledge graph | Neo4j 5 |
|
|
383
|
+
| Web API | FastAPI + Uvicorn + sse-starlette |
|
|
384
|
+
| Config | Pydantic v2 + pydantic-settings |
|
|
385
|
+
| State machine | `transitions` |
|
|
386
|
+
| PID control | `simple-pid` |
|
|
387
|
+
| Physics | SciPy (`solve_ivp`) |
|
|
388
|
+
| Surrogate / ML | ONNX Runtime + scikit-learn |
|
|
389
|
+
| Forecasting | Prophet |
|
|
390
|
+
| Discrete event | SimPy |
|
|
391
|
+
| Sentiment / NLP | vaderSentiment (swappable for any callable) |
|
|
392
|
+
| Agents | LangChain (+ langchain-classic / -community) |
|
|
393
|
+
| LLM providers | OpenAI · Anthropic · Ollama |
|
|
394
|
+
| RL | Stable-Baselines3 + Gymnasium |
|
|
395
|
+
| Learning-from-demo | `imitation` (BC, DAgger, AIRL, GAIL) + a built-in MaxEnt IRL |
|
|
396
|
+
| CLI | Click |
|
|
397
|
+
|
|
398
|
+
## CLI reference
|
|
399
|
+
|
|
400
|
+
```bash
|
|
401
|
+
dyon init --asset-type TYPE --name NAME --asset-id ID [--out DIR]
|
|
402
|
+
dyon infra up --layers data,network,services[,intelligent] [--out FILE] [--generate-only]
|
|
403
|
+
dyon infra check [--layers ...] # default: read .dyon-layers
|
|
404
|
+
dyon run [TWIN_MODULE] # default module: "twin"
|
|
405
|
+
dyon train [--algorithm SAC|TD3|PPO|A2C] [--timesteps N]
|
|
406
|
+
[--env-module M] [--save NAME]
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
Run any command with `--help` for the full flag list.
|
|
410
|
+
|
|
411
|
+
## Learn more
|
|
412
|
+
|
|
413
|
+
The `guide/` folder in the source repository is the complete developer manual —
|
|
414
|
+
twelve chapters that take you from the mental model to a full worked example,
|
|
415
|
+
layer by layer.
|
|
416
|
+
|
|
417
|
+
## License
|
|
418
|
+
|
|
419
|
+
MIT
|