22
33** An open-source, modular simulator for optical communication links and photonic systems.**
44
5- ![ status] ( https://img.shields.io/badge/status-design%20phase-orange )
5+ [ ![ CI] ( https://github.com/ehsun-sh/OpenOptisim/actions/workflows/ci.yml/badge.svg )] ( https://github.com/ehsun-sh/OpenOptisim/actions/workflows/ci.yml )
6+ ![ status] ( https://img.shields.io/badge/status-pre--alpha-orange )
67![ license] ( https://img.shields.io/badge/license-Apache--2.0-blue )
78![ python] ( https://img.shields.io/badge/python-3.11%2B-blue )
89
910---
1011
11- > ### ⚠️ Project status: design phase — no code yet
12+ > ### ⚠️ Project status: pre-alpha, Phase 0
1213>
13- > This repository currently contains ** the architecture and roadmap only** . There is no working
14- > simulator here. It is published early so that the design can be reviewed and criticised before
15- > implementation starts — which is the cheapest possible time to find out that something is wrong.
14+ > The engine core exists and is tested: simulation context, multi-band optical signal model,
15+ > typed ports, component/parameter system, and the block-mode scheduler. Four components are
16+ > implemented — CW laser, fiber (attenuation only), combiner/attenuator, power meter — and the
17+ > physics is validated against closed-form results in CI.
1618>
17- > If you are here to evaluate the idea, the document to read is
18- > ** [ the architecture & roadmap] ( docs/ARCHITECTURE.md ) ** .
19- > Feedback on §3 (data model) and §4 (execution engine) is worth more than feedback on anything else.
19+ > ** Not implemented yet:** modulators, photodetectors, dispersion, SSFM/nonlinearity, DSP,
20+ > BER/eye analysis, and the GUI. Those are Phases 1–2; see the [ roadmap] ( #roadmap ) .
21+ >
22+ > This is not yet a useful simulator. It is a foundation with the expensive decisions made and
23+ > tested. Criticism of those decisions is worth more right now than any feature —
24+ > ** [ the architecture document] ( docs/ARCHITECTURE.md ) ** is where they are argued out.
2025
2126---
2227
28+ ## Try it
29+
30+ ``` bash
31+ pip install -e " .[dev]" && pytest
32+ ```
33+
34+ ``` python
35+ from oosim import SimulationContext, Graph
36+ from oosim.components import CWLaser, Combiner, Fiber, PowerMeter
37+
38+ ctx = SimulationContext(bit_rate = 10e9 , samples_per_symbol = 16 , sequence_length = 64 )
39+ g = Graph(ctx)
40+
41+ laser = g.add(CWLaser(power = 0.0 , wavelength = 1550.0 )) # 0 dBm
42+ fiber = g.add(Fiber(length = 80.0 , attenuation = 0.2 )) # 80 km, 0.2 dB/km
43+ meter = g.add(PowerMeter())
44+ g.chain(laser, fiber, meter)
45+
46+ print (g.run()[meter]) # PowerReading(-16.000 dBm; 1550.00nm=-16.000dBm)
47+ ```
48+
49+ Two carriers stay two independently sampled bands, which is the point of the signal model:
50+
51+ ``` python
52+ g = Graph(ctx)
53+ ch1 = g.add(CWLaser(wavelength = 1550.0 , label = " ch1" ))
54+ ch2 = g.add(CWLaser(wavelength = 1551.0 , label = " ch2" ))
55+ mux = g.add(Combiner(2 ))
56+ fiber = g.add(Fiber(length = 80.0 , attenuation = 0.2 ))
57+ meter = g.add(PowerMeter())
58+
59+ g.connect(ch1, mux[" in0" ])
60+ g.connect(ch2, mux[" in1" ])
61+ g.chain(mux, fiber, meter)
62+
63+ print (g.run()[meter])
64+ # PowerReading(-12.990 dBm; 1551.00nm=-16.000dBm, 1550.00nm=-16.000dBm)
65+ ```
66+
67+ Each band carries its own centre frequency, so channel spacing never enters the sample rate.
68+ Put those two lasers 6 THz apart instead of 125 GHz and nothing about the run changes — which is
69+ exactly what a single-carrier signal model cannot do.
70+
2371## What this is
2472
2573A block-diagram simulator for optical systems: drop components on a canvas, wire a link, run it,
@@ -109,29 +157,39 @@ reachable from Python, it does not exist.**
109157
110158## The core data model
111159
112- The part most worth reviewing. An optical signal is not one array of numbers:
160+ The part most worth reviewing — see [ ` src/oosim/signals.py ` ] ( src/oosim/signals.py ) . An optical
161+ signal is not one array of numbers:
113162
114163``` python
115- @dataclass
164+ @dataclass ( frozen = True )
116165class Band :
117166 """ One sampled band: complex envelope in two orthogonal polarizations (Jones vector)."""
118- Ex: np.ndarray # complex64, shape (N,)
167+
168+ Ex: np.ndarray # complex64, shape (N,), read-only
119169 Ey: np.ndarray
120- f0: float # band center frequency [Hz]
121- fs: float # band sample rate [Hz]
170+ f0: float # band centre frequency [Hz]
171+ fs: float # band sample rate [Hz]
172+
122173
123- @dataclass
174+ @dataclass ( frozen = True )
124175class NoiseBin :
125176 """ Spectrally-resolved noise, carried separately from the sampled bands."""
126- f_start: float ; f_end: float
127- psd_x: float ; psd_y: float # [W/Hz] per polarization
128177
129- @dataclass
178+ f_start: float
179+ f_end: float
180+ psd_x: float # [W/Hz] per polarization
181+ psd_y: float
182+
183+
184+ @dataclass (frozen = True )
130185class OpticalSignal :
131- bands: list [Band]
132- noise: list [NoiseBin]
186+ bands: tuple [Band, ... ]
187+ noise: tuple [NoiseBin, ... ]
133188```
134189
190+ Fields are ` sqrt(W) ` , so instantaneous power is ` |Ex|**2 + |Ey|**2 ` . Arrays are read-only, which
191+ is what lets metadata-only blocks share buffers instead of copying a span at a time.
192+
135193Global run parameters (bit rate, oversampling, sequence length, RNG seed) live in a shared
136194` SimulationContext ` , not in individual signals — so blocks cannot silently disagree about the
137195time window, and results are reproducible.
@@ -140,7 +198,7 @@ time window, and results are reproducible.
140198
141199| Phase | Scope | Estimate¹ |
142200| :--- | :--- | :--- |
143- | ** 0 — Foundations** | Signal model, context, port types, component base, scheduler, project format, CI | ~ 1 month |
201+ | ** 0 — Foundations** * (in progress) * | ✅ Signal model, context, port types, component base, scheduler, CI · ⬜ project file format, sweeps | ~ 1 month |
144202| ** 1 — MVP: linear link** | PRBS → NRZ → CW laser → MZM → fiber (α + CD) → PIN → eye/BER. ** Python only, no GUI.** Full analytical validation suite. | ~ 2–3 months |
145203| ** 1.5 — Nonlinear & amplified** | Adaptive-step SSFM, Kerr, PMD, EDFA (gain/NF/saturation/ASE), APD | ~ 2 months |
146204| ** 2 — GUI & DSP** | Graph editor, plots, pulse shaping, FIR, equalizers (LMS/CMA), OSA, constellation, sweeps | ~ 3–4 months |
@@ -154,38 +212,51 @@ are all pushed out of it. Shipping a *validated* linear link quickly matters mor
154212
155213## Validation
156214
157- Every physics block ships with a test against a closed-form result, run in CI:
158-
159- | Case | Expected |
160- | :--- | :--- |
161- | Lossless, dispersionless, linear fiber | Output bit-identical to input |
162- | Attenuation only | ` P_out = P_in · exp(-αL) ` |
163- | Gaussian pulse, CD only | ` T(z) = T₀·√(1 + (z/L_D)²) ` , ` L_D = T₀²/\|β₂\| ` |
164- | Lossless SSFM | Energy conserved (Parseval) |
165- | Fundamental soliton (N=1) | Envelope magnitude invariant along propagation |
166- | Ideal push-pull MZM | ` P_out/P_in = cos²(πV / 2V_π) ` |
167- | PIN detector | ` I = R·P ` ; shot ` σ² = 2qIB ` ; thermal ` σ² = 4kTB/R_L ` |
168- | Ideal OOK, Gaussian noise | ` BER = ½·erfc(Q/√2) ` |
169- | EDFA | ` P_ASE = 2·n_sp·hν·(G−1)·B_o ` |
215+ Every physics block ships with a test against a closed-form result, run in CI
216+ ([ ` tests/test_physics.py ` ] ( tests/test_physics.py ) ):
217+
218+ | Case | Expected | |
219+ | :--- | :--- | :-- |
220+ | Attenuation | ` P_out = P_in · 10^(-αL/10) ` | ✅ |
221+ | Cascaded spans | Loss is additive in dB | ✅ |
222+ | Source power | Independent of the simulated time window | ✅ |
223+ | Phase noise | Broadens the line, conserves average power | ✅ |
224+ | Multi-carrier | Channels stay separate bands; spacing does not drive ` Fs ` | ✅ |
225+ | Gaussian pulse, CD only | ` T(z) = T₀·√(1 + (z/L_D)²) ` , ` L_D = T₀²/\|β₂\| ` | ⬜ |
226+ | Lossless SSFM | Energy conserved (Parseval) | ⬜ |
227+ | Fundamental soliton (N=1) | Envelope magnitude invariant along propagation | ⬜ |
228+ | Ideal push-pull MZM | ` P_out/P_in = cos²(πV / 2V_π) ` | ⬜ |
229+ | PIN detector | ` I = R·P ` ; shot ` σ² = 2qIB ` ; thermal ` σ² = 4kTB/R_L ` | ⬜ |
230+ | Ideal OOK, Gaussian noise | ` BER = ½·erfc(Q/√2) ` | ⬜ |
231+ | EDFA | ` P_ASE = 2·n_sp·hν·(G−1)·B_o ` | ⬜ |
170232
171233Component models are derived from published literature and standards (Agrawal, * Nonlinear Fiber
172234Optics* ; ITU-T G.652 / G.694.1; relevant IEEE 802.3 clauses), cited in each component's
173235docstring — never from inspection of commercial tools.
174236
175237## Contributing
176238
177- Not open for code contributions yet — there is no code. What is genuinely useful right now:
239+ The core is small enough that changing it is still cheap, which makes right now the most useful
240+ time to push back on it. Most valuable first:
178241
179- * ** Review the [ architecture document] ( docs/ARCHITECTURE.md ) ** , especially the
180- signal data model (§3) and the execution engine (§4). If something there is wrong, now is when
181- it is cheap to fix.
242+ * ** Review the signal model and scheduler** — [ ` src/oosim/signals.py ` ] ( src/oosim/signals.py ) ,
243+ [ ` src/oosim/graph.py ` ] ( src/oosim/graph.py ) , and §3–§4 of the
244+ [ architecture document] ( docs/ARCHITECTURE.md ) . If something there is wrong, it is far cheaper
245+ to fix now than after fifty components depend on it.
182246* ** Tell us if this duplicates existing work.** If a project already does this well, that is worth
183247 knowing before several months go into it.
184248* ** Describe your use case.** Which components, which measurements, what you currently use and
185249 what frustrates you about it.
250+ * ** Add a component.** A component is a Python class with declared parameters and typed ports —
251+ see [ ` src/oosim/components/ ` ] ( src/oosim/components/ ) for the pattern. Every physics block needs
252+ a test against a closed-form result; a component without one will not be merged.
186253
187254Open an issue for any of the above.
188255
256+ ``` bash
257+ pip install -e " .[dev]" && ruff check . && ruff format --check . && mypy && pytest
258+ ```
259+
189260## License
190261
191262[ Apache-2.0] ( LICENSE ) — permissive enough for industrial adoption, with an explicit patent grant.
0 commit comments