ber-equalization-studio 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,186 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+ import matplotlib.pyplot as plt
7
+ import pandas as pd
8
+
9
+
10
+ def _try_plotly():
11
+ try:
12
+ import plotly.express as px
13
+ import plotly.graph_objects as go
14
+
15
+ return px, go
16
+ except Exception:
17
+ return None, None
18
+
19
+
20
+ def history_figure(history: dict[str, list[Any]], model_name: str, metric: str = "ber"):
21
+ """Build a Plotly history figure for notebooks, returning None if Plotly is unavailable."""
22
+
23
+ _, go = _try_plotly()
24
+ if go is None:
25
+ return None
26
+
27
+ epochs = history.get("val_epochs") or list(range(1, len(history.get("train_loss", [])) + 1))
28
+ fig = go.Figure()
29
+ if metric == "loss":
30
+ if history.get("train_loss"):
31
+ fig.add_trace(go.Scatter(x=epochs, y=history["train_loss"], name="train loss", mode="lines"))
32
+ if history.get("val_loss"):
33
+ fig.add_trace(go.Scatter(x=epochs, y=history["val_loss"], name="val loss", mode="lines"))
34
+ fig.update_layout(
35
+ title=f"{model_name}: loss curves",
36
+ xaxis_title="Epoch",
37
+ yaxis_title="Loss",
38
+ template="plotly_white",
39
+ )
40
+ return fig
41
+
42
+ if history.get("val_ber"):
43
+ fig.add_trace(go.Scatter(x=epochs, y=history["val_ber"], name="val BER", mode="lines+markers"))
44
+ if history.get("test_ber"):
45
+ fig.add_trace(
46
+ go.Scatter(
47
+ x=history.get("test_epochs", []),
48
+ y=history["test_ber"],
49
+ name="test BER",
50
+ mode="markers",
51
+ )
52
+ )
53
+ fig.update_layout(
54
+ title=f"{model_name}: BER",
55
+ xaxis_title="Epoch",
56
+ yaxis_title="BER",
57
+ yaxis_type="log",
58
+ template="plotly_white",
59
+ )
60
+ return fig
61
+
62
+
63
+ def comparison_figure(
64
+ df: pd.DataFrame,
65
+ x: str = "model_type",
66
+ y: str = "equalized_ber",
67
+ color: str | None = None,
68
+ kind: str = "bar",
69
+ ):
70
+ """Build a Plotly comparison figure for notebooks, returning None if Plotly is unavailable."""
71
+
72
+ px, _ = _try_plotly()
73
+ if px is None or x not in df.columns or y not in df.columns:
74
+ return None
75
+
76
+ if color is None:
77
+ color = "improvement_rel" if kind == "bar" and "improvement_rel" in df.columns else None
78
+ hover_cols = [column for column in ["trainable_params", "improvement_rel", "samples_per_sec"] if column in df]
79
+ plot_df = df.sort_values(y) if y in df.columns else df
80
+ common = {
81
+ "x": x,
82
+ "y": y,
83
+ "color": color if color in df.columns else None,
84
+ "hover_data": hover_cols,
85
+ "template": "plotly_white",
86
+ }
87
+ if kind == "scatter":
88
+ fig = px.scatter(plot_df, **common)
89
+ else:
90
+ fig = px.bar(plot_df, **common)
91
+ layout = {
92
+ "title": f"{y} by {x}",
93
+ "xaxis_title": x.replace("_", " ").title(),
94
+ "yaxis_title": y.replace("_", " ").title(),
95
+ }
96
+ if "ber" in y.lower():
97
+ layout["yaxis_type"] = "log"
98
+ fig.update_layout(
99
+ **layout,
100
+ )
101
+ return fig
102
+
103
+
104
+ def plot_history(history: dict[str, list[Any]], model_name: str, out_dir: Path) -> list[Path]:
105
+ out_dir.mkdir(parents=True, exist_ok=True)
106
+ paths: list[Path] = []
107
+ px, go = _try_plotly()
108
+ epochs = history.get("val_epochs") or list(range(1, len(history.get("train_loss", [])) + 1))
109
+
110
+ if go is not None:
111
+ fig = history_figure(history, model_name, metric="loss")
112
+ path = out_dir / f"{model_name}_loss.html"
113
+ fig.write_html(path)
114
+ paths.append(path)
115
+
116
+ fig = history_figure(history, model_name, metric="ber")
117
+ path = out_dir / f"{model_name}_ber.html"
118
+ fig.write_html(path)
119
+ paths.append(path)
120
+ return paths
121
+
122
+ plt.figure(figsize=(9, 5))
123
+ if history.get("train_loss"):
124
+ plt.plot(epochs, history["train_loss"], label="train loss")
125
+ if history.get("val_loss"):
126
+ plt.plot(epochs, history["val_loss"], label="val loss")
127
+ plt.xlabel("Epoch")
128
+ plt.ylabel("Loss")
129
+ plt.legend()
130
+ plt.grid(alpha=0.25)
131
+ path = out_dir / f"{model_name}_loss.png"
132
+ plt.savefig(path, dpi=170, bbox_inches="tight")
133
+ plt.close()
134
+ paths.append(path)
135
+ return paths
136
+
137
+
138
+ def plot_comparison(df: pd.DataFrame, out_dir: Path, filename: str = "model_comparison") -> list[Path]:
139
+ out_dir.mkdir(parents=True, exist_ok=True)
140
+ paths: list[Path] = []
141
+ px, _ = _try_plotly()
142
+ if px is not None and {"model_type", "equalized_ber"}.issubset(df.columns):
143
+ hover_cols = [column for column in ["trainable_params", "improvement_rel", "samples_per_sec"] if column in df]
144
+ fig = px.bar(
145
+ df.sort_values("equalized_ber"),
146
+ x="model_type",
147
+ y="equalized_ber",
148
+ color="improvement_rel" if "improvement_rel" in df.columns else None,
149
+ hover_data=hover_cols,
150
+ log_y=True,
151
+ template="plotly_white",
152
+ title="Equalized BER by model",
153
+ )
154
+ fig.update_layout(xaxis_title="Model", yaxis_title="BER")
155
+ path = out_dir / f"{filename}_ber.html"
156
+ fig.write_html(path)
157
+ paths.append(path)
158
+
159
+ if {"trainable_params", "equalized_ber"}.issubset(df.columns):
160
+ if px is not None:
161
+ fig = px.scatter(
162
+ df,
163
+ x="trainable_params",
164
+ y="equalized_ber",
165
+ color="model_type" if "model_type" in df.columns else None,
166
+ hover_name="model_type" if "model_type" in df.columns else None,
167
+ log_y=True,
168
+ template="plotly_white",
169
+ title="Complexity vs BER",
170
+ )
171
+ fig.update_layout(xaxis_title="Trainable parameters", yaxis_title="BER")
172
+ path = out_dir / f"{filename}_complexity.html"
173
+ fig.write_html(path)
174
+ paths.append(path)
175
+ else:
176
+ plt.figure(figsize=(8, 5))
177
+ plt.scatter(df["trainable_params"], df["equalized_ber"])
178
+ plt.yscale("log")
179
+ plt.xlabel("Trainable parameters")
180
+ plt.ylabel("BER")
181
+ plt.grid(alpha=0.25)
182
+ path = out_dir / f"{filename}_complexity.png"
183
+ plt.savefig(path, dpi=170, bbox_inches="tight")
184
+ plt.close()
185
+ paths.append(path)
186
+ return paths
@@ -0,0 +1,266 @@
1
+ Metadata-Version: 2.4
2
+ Name: ber-equalization-studio
3
+ Version: 0.1.0
4
+ Summary: Research studio for BER equalization experiments in nonlinear optical links.
5
+ Keywords: ber,equalization,optical-communications,photonics,pytorch
6
+ Classifier: Development Status :: 3 - Alpha
7
+ Classifier: Intended Audience :: Science/Research
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
13
+ Classifier: Topic :: Scientific/Engineering :: Physics
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ Requires-Dist: matplotlib>=3.8
17
+ Requires-Dist: numpy>=1.24
18
+ Requires-Dist: pandas>=2.0
19
+ Requires-Dist: plotly>=5.18
20
+ Requires-Dist: torch>=2.1
21
+ Provides-Extra: kan
22
+ Provides-Extra: notebook
23
+ Requires-Dist: ipykernel>=6.29; extra == "notebook"
24
+ Requires-Dist: ipython>=8.18; extra == "notebook"
25
+ Requires-Dist: ipywidgets>=8.1; extra == "notebook"
26
+ Requires-Dist: jupyterlab>=4.0; extra == "notebook"
27
+ Requires-Dist: nbformat>=5.9; extra == "notebook"
28
+ Provides-Extra: dev
29
+ Requires-Dist: build>=1.2; extra == "dev"
30
+ Requires-Dist: pytest>=8.0; extra == "dev"
31
+ Requires-Dist: twine>=5.1; extra == "dev"
32
+ Provides-Extra: publish
33
+ Requires-Dist: build>=1.2; extra == "publish"
34
+ Requires-Dist: twine>=5.1; extra == "publish"
35
+
36
+ # BER Equalization Studio
37
+
38
+ Research library for BER equalization experiments in nonlinear fiber-optic links.
39
+
40
+ The studio wraps the existing, validated `BER_minimization_survey/ber_equalization.py`
41
+ backend with a cleaner API:
42
+
43
+ - structured dataclass configs instead of editing one large global `Config`;
44
+ - model registry and aliases;
45
+ - reusable experiment runner;
46
+ - per-model run folders with checkpoints, metrics, histories, and plots;
47
+ - result comparison helpers for paper tables;
48
+ - Plotly HTML visualizations for BER/loss curves and model tradeoffs.
49
+
50
+ ## Layout
51
+
52
+ ```text
53
+ ber-equalization-studio/
54
+ src/ber_equalization_studio/
55
+ config.py # DataConfig, ModelConfig, TrainingConfig, StudioConfig
56
+ legacy.py # adapter to the original ber_equalization.py backend
57
+ models.py # model registry and constructors
58
+ data.py # dataset preparation wrapper
59
+ experiment.py # ExperimentRunner
60
+ api.py # notebook-friendly Studio, RunResult, StudyResult
61
+ results.py # result loading/comparison helpers
62
+ visualization.py # BER/loss/complexity plots
63
+ cli.py # ber-studio command
64
+ examples/
65
+ run_smoke.py
66
+ ```
67
+
68
+ ## Quick Start
69
+
70
+ Install from PyPI:
71
+
72
+ ```powershell
73
+ python -m pip install "ber-equalization-studio[notebook]"
74
+ ```
75
+
76
+ For local development from the repository root:
77
+
78
+ ```powershell
79
+ cd ber-equalization-studio
80
+ python -m pip install -e ".[notebook,dev]"
81
+ ber-studio models
82
+ ```
83
+
84
+ Run a short smoke experiment:
85
+
86
+ ```powershell
87
+ ber-studio run `
88
+ --name smoke_complex_fastkan `
89
+ --models complex_fastkan,mlp `
90
+ --data-dir ..\BER_minimization_survey\symbols_new `
91
+ --epochs 5 `
92
+ --max-test-files 1
93
+ ```
94
+
95
+ If your symbol CSV files live directly under `BER_minimization_survey`, pass that
96
+ folder instead. The backend searches for files named:
97
+
98
+ ```text
99
+ Symbols_1m_1ch_PR_*.csv
100
+ ```
101
+
102
+ The PyPI package includes the legacy training backend and the local EfficientKAN
103
+ implementation used by the studio. You only need to pass data directories with symbol
104
+ CSV files.
105
+
106
+ ## Notebook API
107
+
108
+ The easiest way to work from Jupyter is the high-level `Studio` interface:
109
+
110
+ ```python
111
+ from ber_equalization_studio import Studio
112
+
113
+ studio = Studio(
114
+ data_dirs=["../test"],
115
+ out_dir="notebook_runs",
116
+ device="cuda",
117
+ )
118
+
119
+ studio.models()
120
+ ```
121
+
122
+ Run one experiment:
123
+
124
+ ```python
125
+ run = studio.run(
126
+ name="fastkan_smoke",
127
+ models=["complex_fastkan", "mlp"],
128
+ epochs=5,
129
+ lr=1e-3,
130
+ context_k=32,
131
+ max_test_files=1,
132
+ )
133
+
134
+ run.results
135
+ ```
136
+
137
+ Useful result helpers:
138
+
139
+ ```python
140
+ run.best()
141
+ run.compare()
142
+ run.history("complex_fastkan")
143
+ run.plot_history("complex_fastkan")
144
+ run.plot_comparison()
145
+ run.run_dir
146
+ ```
147
+
148
+ Sweep a small research grid:
149
+
150
+ ```python
151
+ study = studio.sweep(
152
+ name="context_lr_sweep",
153
+ models=["complex_fastkan"],
154
+ grid={
155
+ "context_k": [16, 32, 64],
156
+ "lr": [1e-3, 3e-4],
157
+ },
158
+ epochs=50,
159
+ max_test_files=2,
160
+ )
161
+
162
+ study.results.sort_values("equalized_ber")
163
+ study.best()
164
+ study.plot_tradeoff(x="trainable_params", y="equalized_ber")
165
+ ```
166
+
167
+ Short parameters such as `epochs`, `lr`, `context_k`, `max_test_files`, `data_dirs`,
168
+ and `out_dir` are mapped to the structured config. For advanced settings, use dotted
169
+ keys:
170
+
171
+ ```python
172
+ run = studio.run(
173
+ name="custom_eval",
174
+ models="complex_fastkan",
175
+ **{
176
+ "evaluation.ber_scale_steps": 20,
177
+ "training.early_stopping_patience": 100,
178
+ },
179
+ )
180
+ ```
181
+
182
+ ## Low-Level Python API
183
+
184
+ ```python
185
+ from pathlib import Path
186
+
187
+ from ber_equalization_studio import (
188
+ DataConfig,
189
+ ExperimentConfig,
190
+ ExperimentRunner,
191
+ OutputConfig,
192
+ StudioConfig,
193
+ TrainingConfig,
194
+ )
195
+
196
+ config = StudioConfig(
197
+ data=DataConfig(
198
+ data_dirs=[Path("../BER_minimization_survey/symbols_new")],
199
+ context_k=32,
200
+ max_test_files=1,
201
+ ),
202
+ training=TrainingConfig(epochs=10, learning_rate=1e-3),
203
+ output=OutputConfig(out_dir=Path("studio_outputs"), experiment_name="fastkan_baseline"),
204
+ experiment=ExperimentConfig(models=["complex_fastkan", "efficient_kan_baseline", "mlp"]),
205
+ )
206
+
207
+ result = ExperimentRunner(config).run()
208
+ print(result["results_csv"])
209
+ ```
210
+
211
+ ## Available Models
212
+
213
+ Use:
214
+
215
+ ```powershell
216
+ ber-studio models
217
+ ```
218
+
219
+ Important current models:
220
+
221
+ - `complex_fastkan`: lightweight complex temporal encoder + RBF/FastKAN regression head.
222
+ - `complex_fastkan_classifier`: same encoder + RBF/FastKAN 16-class detector.
223
+ - `efficient_kan_baseline`: flat IQ window + B-spline EfficientKAN regression.
224
+ - `kan_classifier`: flat IQ window + B-spline EfficientKAN classifier.
225
+ - `cnn_kan`: temporal CNN + EfficientKAN head.
226
+ - `mlp`, `cnn`, `tcn`, `lstm`, `transformer`: neural baselines.
227
+
228
+ ## Outputs
229
+
230
+ Each run creates:
231
+
232
+ ```text
233
+ studio_outputs/<experiment_name>/
234
+ config.json
235
+ dataset_summary.json
236
+ results.csv
237
+ model_comparison_ber.html
238
+ model_comparison_complexity.html
239
+ <model_name>/
240
+ metrics.json
241
+ history.json
242
+ final_state_dict.pt
243
+ <model_name>_loss.html
244
+ <model_name>_ber.html
245
+ ```
246
+
247
+ Compare previous runs:
248
+
249
+ ```powershell
250
+ ber-studio compare studio_outputs
251
+ ```
252
+
253
+ ## Research Workflow
254
+
255
+ Recommended progression:
256
+
257
+ 1. In Jupyter, start with `Studio(...).models()` to choose candidate models.
258
+ 2. Run `complex_fastkan`, `efficient_kan_baseline`, and `mlp` as a short smoke test.
259
+ 3. Increase epochs and use the full test split for promising candidates.
260
+ 4. Use `run.results`, `study.results`, and saved `results.csv` files for paper tables.
261
+ 5. Sweep one family at a time with `studio.sweep(...)`, changing only a few parameters per study.
262
+
263
+ The current implementation intentionally keeps the original backend intact. That means
264
+ existing BER computation, Gray labels, file-level split protocol, normalization, KAN
265
+ implementations, and training loop behavior are preserved while the new API makes
266
+ future experiments much easier to compose.
@@ -0,0 +1,19 @@
1
+ ber_equalization_studio/__init__.py,sha256=Q7ViLrj1-HbuMJQYjnyaVKrQR6lHBJ028spHjIb3FSU,1731
2
+ ber_equalization_studio/api.py,sha256=go2sNKiQBP8z3MTAKeXyxPV6NcwKXo5h-6F18Abe740,12440
3
+ ber_equalization_studio/cli.py,sha256=vl0wffZMBqfxyNuin3ZwiqKQswJ8I1esZ684d2qMfNQ,3777
4
+ ber_equalization_studio/config.py,sha256=TanndlwrR3gpxwjDqzl1RKWKhyPsz4fkmHyYNDJRSK4,5142
5
+ ber_equalization_studio/data.py,sha256=AlD5gyvPD_ZE7eyoosgJEHn7iTCM5Oyt6qN6zsIR3Mo,1168
6
+ ber_equalization_studio/experiment.py,sha256=lEtBjujfockDuYMyatKlc2osVZgXtfxAs_obgi-3vFo,3592
7
+ ber_equalization_studio/legacy.py,sha256=1JOmVvVuMSD0BAgILS6WQh6doqRO6jHq-ur7tcElIYE,6351
8
+ ber_equalization_studio/models.py,sha256=Pmc_GD7aJZIT0t3K5cWKePXKMzGgh9ZGjcFuVZxQuhg,3368
9
+ ber_equalization_studio/results.py,sha256=kQzj1m9HaoulJ9LtCJnB5Br6YdikT-BQCLOB5XdadEY,2417
10
+ ber_equalization_studio/visualization.py,sha256=gnezSWiNBP1dIJ527P1pxL1AusWV1bRi7G9eVpjIniA,6288
11
+ ber_equalization_studio/_legacy_backend/__init__.py,sha256=DEvlIvfNzTUPOUQC4VsL_rPtC_sm4Ly3Puush4QcILQ,61
12
+ ber_equalization_studio/_legacy_backend/ber_equalization.py,sha256=9Od0JwoSHkPhARoYGNfW_ChH8FJqmNYz3UyvcGDGXFU,153794
13
+ ber_equalization_studio/_legacy_backend/efficient_kan/__init__.py,sha256=xfwSQFMEZyj4IZzRwvziGYFBTVvtEPa1j9Gftzvx3_s,67
14
+ ber_equalization_studio/_legacy_backend/efficient_kan/kan.py,sha256=6XZS5JDTm4ENYoJiTMwgpGQ4L9p1huoHebhuZAu2RQM,7929
15
+ ber_equalization_studio-0.1.0.dist-info/METADATA,sha256=c8Ny3IUhMT0XYbdKhmbwOkqZmd9X32E9k3a6Qc2_Uic,7477
16
+ ber_equalization_studio-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
17
+ ber_equalization_studio-0.1.0.dist-info/entry_points.txt,sha256=1YV0HoWUnDJ_X9WJsFP1qgP8b9CXst_WK9PtwJDT1PA,64
18
+ ber_equalization_studio-0.1.0.dist-info/top_level.txt,sha256=MoXxAEwvBtpZGfQK3yYI1vhr2k8v9al3D1GK_3KUPlc,24
19
+ ber_equalization_studio-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ ber-studio = ber_equalization_studio.cli:main
@@ -0,0 +1 @@
1
+ ber_equalization_studio