photonics-helper 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.
Files changed (112) hide show
  1. photonics_helper-0.1.0/PKG-INFO +710 -0
  2. photonics_helper-0.1.0/README.md +661 -0
  3. photonics_helper-0.1.0/photonics_helper/__init__.py +255 -0
  4. photonics_helper-0.1.0/photonics_helper/_fftw.py +499 -0
  5. photonics_helper-0.1.0/photonics_helper/base.py +1128 -0
  6. photonics_helper-0.1.0/photonics_helper/breathers.py +268 -0
  7. photonics_helper-0.1.0/photonics_helper/chi2.py +486 -0
  8. photonics_helper-0.1.0/photonics_helper/dashboard.py +285 -0
  9. photonics_helper-0.1.0/photonics_helper/dbr.py +541 -0
  10. photonics_helper-0.1.0/photonics_helper/extras.py +35 -0
  11. photonics_helper-0.1.0/photonics_helper/fiber.py +798 -0
  12. photonics_helper-0.1.0/photonics_helper/gnlse.py +2958 -0
  13. photonics_helper-0.1.0/photonics_helper/looks.py +15 -0
  14. photonics_helper-0.1.0/photonics_helper/materials.db +0 -0
  15. photonics_helper-0.1.0/photonics_helper/materials.py +515 -0
  16. photonics_helper-0.1.0/photonics_helper/noise.py +343 -0
  17. photonics_helper-0.1.0/photonics_helper/phase_matching.py +1818 -0
  18. photonics_helper-0.1.0/photonics_helper/phonon.py +834 -0
  19. photonics_helper-0.1.0/photonics_helper/pulse.py +1889 -0
  20. photonics_helper-0.1.0/photonics_helper/py.typed +0 -0
  21. photonics_helper-0.1.0/photonics_helper/raman/__init__.py +44 -0
  22. photonics_helper-0.1.0/photonics_helper/raman/dashboard.py +593 -0
  23. photonics_helper-0.1.0/photonics_helper/raman/db.py +806 -0
  24. photonics_helper-0.1.0/photonics_helper/raman/explorer.py +1526 -0
  25. photonics_helper-0.1.0/photonics_helper/raman/reference.py +583 -0
  26. photonics_helper-0.1.0/photonics_helper/raman/response.py +1096 -0
  27. photonics_helper-0.1.0/photonics_helper/raman/spec.py +519 -0
  28. photonics_helper-0.1.0/photonics_helper/soliton.py +654 -0
  29. photonics_helper-0.1.0/photonics_helper/structured.py +571 -0
  30. photonics_helper-0.1.0/photonics_helper/wave_breaking.py +248 -0
  31. photonics_helper-0.1.0/photonics_helper.egg-info/PKG-INFO +710 -0
  32. photonics_helper-0.1.0/photonics_helper.egg-info/SOURCES.txt +110 -0
  33. photonics_helper-0.1.0/photonics_helper.egg-info/dependency_links.txt +1 -0
  34. photonics_helper-0.1.0/photonics_helper.egg-info/requires.txt +40 -0
  35. photonics_helper-0.1.0/photonics_helper.egg-info/top_level.txt +2 -0
  36. photonics_helper-0.1.0/pyproject.toml +116 -0
  37. photonics_helper-0.1.0/reproductions/__init__.py +0 -0
  38. photonics_helper-0.1.0/reproductions/dudley_2006_cherenkov_dw/__init__.py +0 -0
  39. photonics_helper-0.1.0/reproductions/dudley_2006_cherenkov_dw/reproduce.py +170 -0
  40. photonics_helper-0.1.0/reproductions/dudley_2006_scg/__init__.py +6 -0
  41. photonics_helper-0.1.0/reproductions/dudley_2006_scg/common.py +1088 -0
  42. photonics_helper-0.1.0/reproductions/dudley_2006_scg/fig03_basic_scg.py +355 -0
  43. photonics_helper-0.1.0/reproductions/dudley_2006_scg/fig04_output_features.py +171 -0
  44. photonics_helper-0.1.0/reproductions/dudley_2006_scg/fig05_ideal_soliton_period.py +167 -0
  45. photonics_helper-0.1.0/reproductions/dudley_2006_scg/fig06_raman_fission.py +167 -0
  46. photonics_helper-0.1.0/reproductions/dudley_2006_scg/fig07_fission_detail.py +177 -0
  47. photonics_helper-0.1.0/reproductions/dudley_2006_scg/fig08_dispersive_wave.py +185 -0
  48. photonics_helper-0.1.0/reproductions/dudley_2006_scg/fig09_dw_energy.py +135 -0
  49. photonics_helper-0.1.0/reproductions/dudley_2006_scg/fig10_spectrogram.py +184 -0
  50. photonics_helper-0.1.0/reproductions/dudley_2006_scg/fig19_coherence.py +114 -0
  51. photonics_helper-0.1.0/reproductions/dudley_2006_scg/fig23_mi_gain.py +199 -0
  52. photonics_helper-0.1.0/reproductions/gordon_1986_ssfs/__init__.py +0 -0
  53. photonics_helper-0.1.0/reproductions/gordon_1986_ssfs/reproduce.py +151 -0
  54. photonics_helper-0.1.0/reproductions/kuznetsov_ma_2012_breather/__init__.py +1 -0
  55. photonics_helper-0.1.0/reproductions/kuznetsov_ma_2012_breather/reproduce.py +478 -0
  56. photonics_helper-0.1.0/reproductions/macleod_quarter_wave_dbr/__init__.py +0 -0
  57. photonics_helper-0.1.0/reproductions/macleod_quarter_wave_dbr/reproduce.py +172 -0
  58. photonics_helper-0.1.0/reproductions/narhi_2016_mi_breathers/__init__.py +1 -0
  59. photonics_helper-0.1.0/reproductions/narhi_2016_mi_breathers/reproduce.py +596 -0
  60. photonics_helper-0.1.0/reproductions/shg_textbook/__init__.py +0 -0
  61. photonics_helper-0.1.0/reproductions/shg_textbook/reproduce.py +185 -0
  62. photonics_helper-0.1.0/reproductions/stolen_lin_1978_spm/__init__.py +0 -0
  63. photonics_helper-0.1.0/reproductions/stolen_lin_1978_spm/reproduce.py +158 -0
  64. photonics_helper-0.1.0/reproductions/tomlinson_1985_wave_breaking/__init__.py +1 -0
  65. photonics_helper-0.1.0/reproductions/tomlinson_1985_wave_breaking/reproduce.py +372 -0
  66. photonics_helper-0.1.0/setup.cfg +4 -0
  67. photonics_helper-0.1.0/setup.py +11 -0
  68. photonics_helper-0.1.0/tests/test_base.py +526 -0
  69. photonics_helper-0.1.0/tests/test_breathers.py +152 -0
  70. photonics_helper-0.1.0/tests/test_chi2.py +212 -0
  71. photonics_helper-0.1.0/tests/test_dashboard.py +104 -0
  72. photonics_helper-0.1.0/tests/test_dbr.py +352 -0
  73. photonics_helper-0.1.0/tests/test_extras.py +29 -0
  74. photonics_helper-0.1.0/tests/test_fftw_backend.py +302 -0
  75. photonics_helper-0.1.0/tests/test_fiber.py +183 -0
  76. photonics_helper-0.1.0/tests/test_frog.py +431 -0
  77. photonics_helper-0.1.0/tests/test_gnlse_beta_units.py +202 -0
  78. photonics_helper-0.1.0/tests/test_gnlse_effects.py +86 -0
  79. photonics_helper-0.1.0/tests/test_gnlse_engine.py +132 -0
  80. photonics_helper-0.1.0/tests/test_gnlse_fiber.py +40 -0
  81. photonics_helper-0.1.0/tests/test_gnlse_regression.py +540 -0
  82. photonics_helper-0.1.0/tests/test_gnlse_shock_energy.py +162 -0
  83. photonics_helper-0.1.0/tests/test_gnlse_solver.py +96 -0
  84. photonics_helper-0.1.0/tests/test_gnlse_visualize.py +248 -0
  85. photonics_helper-0.1.0/tests/test_helper.py +108 -0
  86. photonics_helper-0.1.0/tests/test_looks.py +21 -0
  87. photonics_helper-0.1.0/tests/test_material_data_integrity.py +62 -0
  88. photonics_helper-0.1.0/tests/test_materials.py +168 -0
  89. photonics_helper-0.1.0/tests/test_meep_units.py +95 -0
  90. photonics_helper-0.1.0/tests/test_nk_database.py +219 -0
  91. photonics_helper-0.1.0/tests/test_noise.py +133 -0
  92. photonics_helper-0.1.0/tests/test_phase_matching.py +1148 -0
  93. photonics_helper-0.1.0/tests/test_phonon.py +316 -0
  94. photonics_helper-0.1.0/tests/test_pulse.py +1269 -0
  95. photonics_helper-0.1.0/tests/test_pulse_hygiene.py +60 -0
  96. photonics_helper-0.1.0/tests/test_raman.py +2525 -0
  97. photonics_helper-0.1.0/tests/test_raman_import_surface.py +106 -0
  98. photonics_helper-0.1.0/tests/test_raman_noise.py +138 -0
  99. photonics_helper-0.1.0/tests/test_raman_ssfs.py +91 -0
  100. photonics_helper-0.1.0/tests/test_readiness_beta2.py +111 -0
  101. photonics_helper-0.1.0/tests/test_reproductions.py +273 -0
  102. photonics_helper-0.1.0/tests/test_self_steepening.py +104 -0
  103. photonics_helper-0.1.0/tests/test_soliton.py +458 -0
  104. photonics_helper-0.1.0/tests/test_structured.py +260 -0
  105. photonics_helper-0.1.0/tests/test_tapered_gnlse_engine.py +487 -0
  106. photonics_helper-0.1.0/tests/test_tmm.py +133 -0
  107. photonics_helper-0.1.0/tests/test_tmm_physics.py +290 -0
  108. photonics_helper-0.1.0/tests/test_typing.py +26 -0
  109. photonics_helper-0.1.0/tests/test_units.py +377 -0
  110. photonics_helper-0.1.0/tests/test_wave_breaking.py +110 -0
  111. photonics_helper-0.1.0/tests/test_waveguide_import.py +224 -0
  112. photonics_helper-0.1.0/tests/test_z_dependent_dispersion.py +282 -0
@@ -0,0 +1,710 @@
1
+ Metadata-Version: 2.4
2
+ Name: photonics-helper
3
+ Version: 0.1.0
4
+ Summary: A comprehensive helper library for photonics and optics calculations
5
+ Author-email: Hitaishi V <hitaishi2222@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/hitaishi2222/photonics_helper
8
+ Project-URL: Documentation, https://github.com/hitaishi2222/photonics_helper#readme
9
+ Keywords: photonics,optics,wavelength,frequency,conversion
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Scientific/Engineering :: Physics
16
+ Requires-Python: >=3.12
17
+ Description-Content-Type: text/markdown
18
+ Requires-Dist: matplotlib>=3.10.3
19
+ Requires-Dist: numpy>=2.2.6
20
+ Requires-Dist: pydantic>=2.12.5
21
+ Requires-Dist: rich>=14.3.3
22
+ Requires-Dist: scipy>=1.15.3
23
+ Provides-Extra: plotting
24
+ Requires-Dist: plotly>=5.0; extra == "plotting"
25
+ Provides-Extra: webapp
26
+ Requires-Dist: dash>=2.18.0; extra == "webapp"
27
+ Provides-Extra: examples
28
+ Requires-Dist: tqdm>=4.66; extra == "examples"
29
+ Provides-Extra: extras
30
+ Requires-Dist: imageio>=2.33; extra == "extras"
31
+ Provides-Extra: fftw
32
+ Requires-Dist: pyfftw>=0.15.1; extra == "fftw"
33
+ Provides-Extra: mode-export
34
+ Requires-Dist: femwell>=0.1.12; extra == "mode-export"
35
+ Requires-Dist: tidy3d>=2.12; extra == "mode-export"
36
+ Provides-Extra: docs
37
+ Requires-Dist: mkdocs-material>=9.5; extra == "docs"
38
+ Requires-Dist: mkdocstrings[python]>=0.26; extra == "docs"
39
+ Provides-Extra: dev
40
+ Requires-Dist: pytest>=8.0; extra == "dev"
41
+ Requires-Dist: ruff>=0.8; extra == "dev"
42
+ Requires-Dist: mypy>=1.13; extra == "dev"
43
+ Provides-Extra: all
44
+ Requires-Dist: plotly>=5.0; extra == "all"
45
+ Requires-Dist: dash>=2.18.0; extra == "all"
46
+ Requires-Dist: tqdm>=4.66; extra == "all"
47
+ Requires-Dist: pyfftw>=0.15.1; extra == "all"
48
+ Requires-Dist: imageio>=2.33; extra == "all"
49
+
50
+ # Photonics Helper
51
+
52
+ <p align="center">
53
+ <img src="docs/img/icon.png" width="160" alt="Photonics Helper logo" />
54
+ </p>
55
+
56
+ > ⚠️ **Disclaimer**: This library is under active development. APIs, interfaces, and internals may change between versions without notice. It is intended as a learning resource and research aid — not a production-grade simulation tool. If you rely on it for published results, please verify all outputs independently and cite the underlying physical models rather than this library.
57
+
58
+ A comprehensive helper library for photonics and optics calculations, providing easy-to-use tools for wavelength, frequency, and angular frequency conversions.
59
+
60
+ # Installation
61
+
62
+ The package is not yet on PyPI. Install from the repository:
63
+
64
+ ```bash
65
+ pip install git+https://github.com/hitaishi2222/photonics_helper
66
+ ```
67
+
68
+ For the **FFTW3-accelerated GNLSE / Raman solver** (recommended for large grids
69
+ and long propagation runs):
70
+
71
+ ```bash
72
+ pip install "photonics-helper[fftw] @ git+https://github.com/hitaishi2222/photonics_helper"
73
+ ```
74
+
75
+ When `pyfftw` is present, every FFT in `gnlse.py` and `raman.py` executes on
76
+ the system FFTW3 library with cached plans; otherwise the solver transparently
77
+ falls back to `numpy.fft`. See `photonics_helper._fftw` (env vars
78
+ `PHOTONICS_FFTW_PLANNER`, `PHOTONICS_FFTW_THREADS`).
79
+
80
+ For very large grids an optional **cupy (GPU)** backend can be forced with
81
+ `PHOTONICS_FFT_BACKEND=cupy`. It is never auto-selected and falls back to the
82
+ CPU chain with a warning when cupy or a GPU is unavailable. Benchmark it with
83
+ `python benchmarks/fft_backend_benchmark.py`.
84
+
85
+ # Key Features
86
+
87
+ - **Type Safety**: Full inline type hints (PEP 561 `py.typed`)
88
+ - **Unit Conversions**:
89
+ - Wavelength (nm, μm, m)
90
+ - Frequency (THz, GHz, MHz, Hz)
91
+ - Angular Frequency (rad/s, rad/ps)
92
+ - **Array Operations**: NumPy-based array operations for batch processing
93
+ - **Pulse Visualization**: Interactive 2D/3D pulse envelope plots
94
+ - Gaussian, sech, chirped, Airy, and custom pulse shapes
95
+ - Temporal intensity, spectral intensity, phase, and polar plots
96
+ - FWHM markers and pulse width (T₀) annotations
97
+ - Light/dark theme support
98
+ - Interactive Plotly HTML or static Matplotlib PNG export
99
+ - **DBR Simulation**: Transfer Matrix Method for multilayer stacks
100
+ - **FROG**: SHG-FROG trace generation and PCGPA pulse retrieval
101
+ - Generate FROG traces from electric fields
102
+ - Retrieve pulse shape, chirp, and phase from measured traces
103
+ - Fidelity metric for retrieval quality assessment
104
+ - **Raman Modeling**: Full Raman response physics for 40+ materials
105
+ - Time-domain response (electronic Kerr + delayed lattice oscillation)
106
+ - Frequency-domain gain spectrum
107
+ - Stokes / anti-Stokes wavelength calculation
108
+ - Pump-wavelength explorer and material comparison overlays
109
+ - SQLite material database (`materials.db`) with 44 materials
110
+ - Interactive Dash dashboard for comparing Raman properties
111
+ - **Comprehensive Documentation**: Clear documentation with examples
112
+ - **Easy to Use**: Intuitive API design
113
+
114
+ # Units are classes — an intentional, opinionated choice
115
+
116
+ > **TL;DR:** Passing units as bare `float`s is how silent factor-of-1000 bugs are
117
+ > born. This library deliberately routes every physical quantity through typed
118
+ > unit classes in `photonics_helper/base.py`.
119
+
120
+ This is a **design decision, not a bug**. The maintainer's stance is simple: you
121
+ should not have to keep unit conversions in your head (or in your comments)
122
+ while doing photonics. Construct a quantity once in whatever unit is natural,
123
+ and the library converts, stores, and returns it in the units you ask for.
124
+
125
+ Every module — dispersion (`fiber.py`), pulses (`pulse.py`), the GNLSE solver
126
+ (`gnlse.py`), materials (`materials.py`), phase matching (`phase_matching.py`),
127
+ and the DBR/TMM stack (`dbr.py`) — accepts and returns these classes at its
128
+ public boundaries instead of raw numbers.
129
+
130
+ | Quantity | Class (scalar / array) | Example |
131
+ |----------|-----------------------|---------|
132
+ | Wavelength | `Wavelength` / `WavelengthArray` | `Wavelength(1550, "nm")` |
133
+ | Frequency | `Frequency` / `FrequencyArray` | `Frequency(193.4, "THz")` |
134
+ | Angular frequency | `AngularFrequency` / `AngularFrequencyArray` | `AngularFrequency(1.2, "rad/ps")` |
135
+ | Wavenumber | `Wavenumber` / `WavenumberArray` | `Wavenumber(6450, "1/cm")` |
136
+ | Length | `Length` | `Length(5, "mm")` |
137
+ | Time | `Time` | `Time(50, "fs")` |
138
+ | Energy | `Energy` | `Energy(1.24, "eV")` |
139
+ | Power | `Power` | `Power(100, "mW")` |
140
+ | Area | `Area` | `Area(0.2, "um^2")` |
141
+
142
+ The pattern is always the same — construct in any unit, read out in any unit:
143
+
144
+ ```python
145
+ from photonics_helper.base import Wavelength
146
+
147
+ wl = Wavelength(1550, "nm") # constructed in nm
148
+ wl.as_um # 1.55 -> view in μm
149
+ wl.as_m # 1.55e-06 -> internally stored in SI (m)
150
+ wl.to_freq().as_THz # 193.41 -> convert and read out in THz
151
+ ```
152
+
153
+ Internally everything is normalised to SI on construction, so downstream
154
+ arithmetic and comparisons stay consistent; the `.as_*` properties are simply
155
+ views on the same value. `*Array` variants mirror the scalars for vectorised
156
+ work (dispersion tables, spectral grids, …), and MEEP-style conversions are
157
+ available through `from_meep` / `as_meep`.
158
+
159
+ **The trade-off, stated honestly:** you write `Length(5, "mm")` instead of
160
+ `5e-3`, and functions return `Time` objects rather than floats. In exchange,
161
+ unit mismatches surface as explicit conversions instead of propagating silently
162
+ through a simulation — which, for photonics, is almost always the better deal.
163
+
164
+ # Quick Start
165
+
166
+ To get started, import the library and use its functions:
167
+
168
+ ```python
169
+ from photonics_helper import Wavelength
170
+
171
+ # Convert wavelength to frequency
172
+ wl = Wavelength(1550, "nm")
173
+ freq = wl.to_freq()
174
+ print(f"Frequency: {freq.as_THz:.2f} THz")
175
+
176
+ # Convert frequency to angular frequency
177
+ omega = freq.to_omega()
178
+ print(f"Angular frequency: {omega.as_rad_ps:.2f} rad/ps")
179
+ ```
180
+
181
+ # Pulse Visualization
182
+
183
+ Visualize pulse envelopes with interactive 2D/3D plots:
184
+
185
+ ```python
186
+ from photonics_helper.pulse import Envelope
187
+
188
+ # Create a 50 fs Gaussian pulse
189
+ pulse = Envelope(shape="gaussian", peak_amplitude=1.0, pulse_width=50e-15)
190
+
191
+ # Generate interactive Plotly HTML
192
+ fig = pulse.visualize_2d(backend="plotly", theme="light")
193
+ fig.write_html("pulse.html")
194
+
195
+ # Or generate static Matplotlib PNG
196
+ fig = pulse.visualize_2d(backend="matplotlib", figsize=(14, 10))
197
+ fig.savefig("pulse.png", dpi=150, bbox_inches="tight")
198
+
199
+ # 3D spectrogram
200
+ fig_3d = pulse.visualize_3d()
201
+ fig_3d.write_html("spectrogram.html")
202
+ ```
203
+
204
+ The visualization shows:
205
+ - **Temporal intensity** with FWHM and T₀ markers
206
+ - **Spectral intensity** (Fourier transform)
207
+ - **Instantaneous phase** (for chirped pulses)
208
+ - **Polar plot** (Re vs Im of the field)
209
+ - **Parameters box**: shape, T₀, FWHM, chirp
210
+
211
+ # Pulse Trains
212
+
213
+ Model mode-locked laser output with pulse trains:
214
+
215
+ ```python
216
+ from photonics_helper.pulse import Envelope, Wave
217
+ from photonics_helper.base import Wavelength, Frequency
218
+
219
+ # Single pulse
220
+ pulse = Envelope(shape="gaussian", peak_amplitude=1.0, pulse_width=50e-15)
221
+
222
+ # Mode-locked laser: 1 GHz rep rate, 10 pulses
223
+ wave = Wave.from_pulse_train(
224
+ envelope=pulse,
225
+ central_wavelength=Wavelength(1550, "nm"),
226
+ grid=Envelope._make_grid(pulse, N=2**14),
227
+ repetition_rate=Frequency(1, "GHz"),
228
+ n_pulses=10,
229
+ )
230
+
231
+ # Visualize
232
+ fig = wave.visualize(t_unit="ns", w_unit="THz", t_scale=1e9, w_scale=1e12)
233
+ fig.savefig("pulse_train.png", dpi=150, bbox_inches="tight")
234
+ ```
235
+
236
+ # Physical Power & Energy
237
+
238
+ `Wave.peak_power()` and `Wave.pulse_energy()` operate on the **normalized**
239
+ envelope `A(t)` by default, so their values are in field-units², not watts or
240
+ joules. Calling them without an effective area emits a one-time `UserWarning`,
241
+ and the `Wave.visualize()` summary labels the value as `(normalized units)`.
242
+
243
+ To get physical units, attach an effective mode area:
244
+
245
+ ```python
246
+ from photonics_helper import Area, Frequency, Wavelength, Time
247
+ from photonics_helper.pulse import Envelope, TemporalGrid, Wave
248
+
249
+ A0 = 2.0 # envelope peak amplitude (V/m)
250
+ T0 = 50e-15 # pulse width
251
+ env = Envelope(shape="gaussian", peak_amplitude=A0, pulse_width=Time(T0, "s"))
252
+ grid = TemporalGrid(N=2**14, Tmax=Time(20 * T0, "s"))
253
+ wave = Wave(
254
+ grid=grid,
255
+ envelope=env,
256
+ central_wavelength=Wavelength(800, "nm"),
257
+ refractive_index=1.44,
258
+ )
259
+
260
+ wave = wave.with_effective_area(Area(80, "um^2"))
261
+
262
+ wave.peak_power() # W = ½·n·c·ε₀·A_eff·A₀²
263
+ wave.pulse_energy() # J = ∫ P dt
264
+ wave.average_power(Frequency(80, "MHz")) # W
265
+ ```
266
+
267
+ For one-shot conversions without a `Wave`, use
268
+ `PeakPower.from_envelope(A, A_eff, n, lambda0)`:
269
+
270
+ ```python
271
+ from photonics_helper import Area, PeakPower, Wavelength
272
+
273
+ PeakPower.from_envelope(A0, Area(80, "um^2"), n=1.44, lambda0=Wavelength(800, "nm"))
274
+ # → Power -> ... W
275
+ ```
276
+
277
+ The conversion follows the plane-wave intensity relation
278
+ `I = ½·n·c·ε₀·|A|²`, evaluated at the envelope peak.
279
+
280
+ # Waveguide Mode Import (FEM)
281
+
282
+ Bring an effective-index table from an external eigenmode solver (Lumerical
283
+ MODE, COMSOL Wave Optics, …) into the library's dispersion objects with
284
+ `WaveguideMode`.
285
+
286
+ **CSV** — comma-separated, optional header, columns `wavelength_um, neff`
287
+ (optionally a third `ng` column):
288
+
289
+ ```csv
290
+ wavelength_um, neff
291
+ 1.50, 2.4310
292
+ 1.55, 2.4205
293
+ 1.60, 2.4102
294
+ ```
295
+
296
+ **NPZ** — `np.savez` archive with `wavelength_um` (µm) and `neff`, optionally
297
+ `ng` and `central_wavelength_nm`:
298
+
299
+ ```python
300
+ import numpy as np
301
+ np.savez("mode.npz", wavelength_um=wl_um, neff=neff, central_wavelength_nm=1550.0)
302
+ ```
303
+
304
+ ```python
305
+ from photonics_helper import WaveguideMode, Wavelength
306
+
307
+ mode = WaveguideMode.from_csv("mode.csv") # or .from_npz("mode.npz")
308
+ pc = mode.to_propagation_constant() # β = n_eff·ω/c
309
+ D = mode.to_dispersion() # D(λ) = -λ/c · d²n_eff/dλ²
310
+
311
+ mode.neff_at(Wavelength(1550, "nm")) # interpolated n_eff
312
+ pc.beta2(Wavelength(1550, "nm")) # d²β/dω² (s²/m)
313
+ ```
314
+
315
+ The table is validated on load: a finite, positive, strictly increasing
316
+ wavelength grid with at least four points (the cubic-spline minimum), mirroring
317
+ `RefractiveIndex`.
318
+
319
+ Runnable example: [`examples/29_waveguide_mode_import.py`](examples/29_waveguide_mode_import.py)
320
+ (fabricates a synthetic FEM export, round-trips CSV/NPZ, and checks `beta2`
321
+ against the analytic derivative).
322
+
323
+ ### Real solver exports (femwell + Tidy3D)
324
+
325
+ [`examples/generate_waveguide_mode_data.py`](examples/generate_waveguide_mode_data.py)
326
+ solves a canonical Si strip (500 × 220 nm on SiO₂) with **femwell** (FEM) and
327
+ **Tidy3D**'s local mode solver, and writes `n_eff(λ)` tables in the conventions
328
+ above to `examples/data/`. The committed exports let the consumer example run
329
+ without either solver installed:
330
+
331
+ ```bash
332
+ pip install femwell tidy3d # or: pip install -e '.[mode-export]'
333
+ python examples/generate_waveguide_mode_data.py # regenerate examples/data/
334
+ python examples/31_waveguide_mode_import_femwell_tidy3d.py
335
+ ```
336
+
337
+ The two independent solvers agree on `n_eff` to **0.76 %** over 1.5–1.6 µm;
338
+ the import example cross-checks them, builds `PropagationConstant` /
339
+ `Dispersion`, and feeds the result into `phase_matching`.
340
+
341
+ # χ⁽²⁾ Nonlinear Optics (SHG / SFG / DFG)
342
+
343
+ `photonics_helper.chi2` integrates scalar, long-pulse three-wave mixing in a
344
+ waveguide with a fourth-order Runge–Kutta step in the interaction picture
345
+ (RK4IP). Envelopes are normalized so `|A|²` is power in watts. At perfect
346
+ phase matching the pump-depleted SHG solution is exact,
347
+ `η = tanh²(κL)` with `κ = σ√P₀` (see `reproductions/shg_textbook/`).
348
+
349
+ ```python
350
+ from photonics_helper import Area, Wavelength
351
+ from photonics_helper.chi2 import Lambda_qpm, shg_coupling, solve_shg
352
+
353
+ wl = Wavelength(1550, "nm")
354
+ sigma = shg_coupling(wl, d_eff=10e-12, n=2.0, A_eff=Area(1.0, "um^2"))
355
+
356
+ result = solve_shg(length=4e-3, P0=0.1, sigma=sigma, n_steps=4000)
357
+ result.efficiency()[-1] # η at the output
358
+ result.power("sh") # SH power vs z (W)
359
+
360
+ # Quasi-phase-matching: Λ = 2π/|Δk| from the phase mismatch
361
+ period = Lambda_qpm(delta_k)
362
+ result = solve_shg(length=4e-3, P0=0.1, sigma=sigma, delta_k=delta_k, qpm_period=period)
363
+
364
+ # SFG / DFG (generic ω₃ = ω₁ + ω₂)
365
+ from photonics_helper.chi2 import solve_sfg, solve_dfg
366
+ solve_sfg(length=5e-3, P1=0.1, P2=0.1, sigma=sigma)
367
+ solve_dfg(length=5e-3, Ppump=0.1, Psignal=1e-3, sigma=sigma)
368
+ ```
369
+
370
+ Scope: scalar envelopes, no group-velocity mismatch, dispersion, walk-off or
371
+ loss — suitable for CW / long-pulse efficiency estimates.
372
+ `delta_k_shg(beta_fn, omega)` computes `Δk = β(2ω) − 2β(ω)` from any
373
+ `phase_matching` adaptor, and `qpm_grating(z, Λ)` gives the square-wave poling
374
+ sign.
375
+
376
+ # FROG (Frequency-Resolved Optical Gating)
377
+
378
+ Generate FROG traces and retrieve ultrashort pulse shapes using PCGPA.
379
+
380
+ ```python
381
+ import numpy as np
382
+ from photonics_helper import generate_trace, retrieve, fidelity
383
+
384
+ # Create a chirped pulse
385
+ T0 = 50e-15 # 50 fs
386
+ N = 2**10
387
+ dt = 10 * T0 / N
388
+ t = np.arange(N) * dt - N * dt / 2
389
+ E = np.exp(-t**2 / (2 * T0**2)) * np.exp(1j * 0.5 * 2.0 * (t / T0)**2)
390
+
391
+ # Generate FROG trace
392
+ trace = generate_trace(E, dt=dt)
393
+
394
+ # Retrieve pulse using PCGPA
395
+ result = retrieve(trace, max_iter=100)
396
+ f = fidelity(trace, result) # → ~1.0 for good retrieval
397
+
398
+ # Visualize trace + retrieved field
399
+ fig = trace.visualize(retrieved=result)
400
+ fig.savefig("frog.png", dpi=150, bbox_inches="tight")
401
+ ```
402
+
403
+ # Raman Material Database
404
+
405
+ Load Raman material parameters from the built-in SQLite database (44 materials):
406
+
407
+ ```python
408
+ from photonics_helper.raman import RamanSpec
409
+
410
+ # Load from bundled materials.db (falls back to hardcoded dict)
411
+ silica = RamanSpec.from_database("Silica")
412
+ print(silica.summary())
413
+ # Material: Silica
414
+ # Raman shift: 440.0 cm⁻¹ = 13.19 THz
415
+ # Linewidth: 45.0 cm⁻¹ = 1.35 THz
416
+ # fR: 0.18
417
+ # n₂: 3.2e-20 m²/W
418
+ ```
419
+
420
+ All 44 available materials:
421
+
422
+ | Category | Materials |
423
+ |----------|-----------|
424
+ | Glasses | Silica, GeO₂, As₂S₃, As₂Se₃, ZBLAN |
425
+ | Semiconductors | Si, Ge, GaAs, GaN, AlN, InP, InGaAs, AlGaAs, SiC, Si₃N₄, Si₃N₄-Ligentec |
426
+ | II-VI | CdS, CdTe, ZnO, ZnSe |
427
+ | Oxides | Ga₂O₃, Al₂O₃ (sapphire), BaTiO₃, LiNbO₃, LiTaO₃, KTP, YVO₄ |
428
+ | Chalcogenides | GeAsSe |
429
+ | Crystals & Hosts | Diamond, YAG, YLF, Zerodur |
430
+ | NLO Crystals | LBO, AgGaS₂, AgGaSe₂ |
431
+ | Optical substrates | BaF₂, CaF₂, MgF₂, KBr, F₂, N-BK7, N-F2, N-SF11, PMMA |
432
+
433
+ ```python
434
+ # Stokes / anti-Stokes for a given pump
435
+ from photonics_helper.base import Wavelength
436
+
437
+ pump = Wavelength(800, "nm")
438
+ stokes = silica.stokes_wavelength(pump)
439
+ anti = silica.anti_stokes_wavelength(pump)
440
+ print(f"Stokes: {stokes.as_nm:.1f} nm, Anti-Stokes: {anti.as_nm:.1f} nm")
441
+ ```
442
+
443
+ # Soliton Analysis
444
+
445
+ Analyze soliton dynamics from GNLSE simulation results:
446
+
447
+ ```python
448
+ from photonics_helper.gnlse import GNLSESolver, FiberProfile
449
+ from photonics_helper.pulse import Wave, Envelope, TemporalGrid
450
+ from photonics_helper.soliton import SolitonAnalyzer, plot_soliton_trajectories
451
+ from photonics_helper.base import Wavelength, Time, Area, Length
452
+ import numpy as np
453
+
454
+ # Create pulse and fiber (waveguide with confinement factor)
455
+ grid = TemporalGrid(N=256, Tmax=Time(20e-12, "s"))
456
+ pulse = Wave(
457
+ grid=grid,
458
+ envelope=Envelope(shape="sech", peak_amplitude=1.0, pulse_width=Time(100e-15, "s")),
459
+ central_wavelength=Wavelength(1550, "nm"),
460
+ )
461
+ fiber = FiberProfile(
462
+ n2=6.0e-18, # GeAsSe
463
+ alpha=0.0,
464
+ A_eff=Area(0.2e-12, "m^2"),
465
+ length=Length(5e-3, "m"),
466
+ confinement_factor=0.8, # waveguide
467
+ )
468
+
469
+ # Run GNLSE
470
+ solver = GNLSESolver(
471
+ pulse, fiber,
472
+ betas=np.array([-0.2, 0.001]), # β₂=-0.2 ps²/m, β₃=0.001 ps³/m
473
+ include_raman=True,
474
+ )
475
+ solver.propagate(num_steps=200)
476
+
477
+ # Analyze soliton dynamics
478
+ analyzer = SolitonAnalyzer(
479
+ pulse, fiber, solver.betas, solver.z_array, solver.spectra_vs_z
480
+ )
481
+ print(f"Soliton order: {analyzer.soliton_order():.2f}")
482
+ print(f"Fission length: {analyzer.fission_length():.2f} mm")
483
+ print(f"DW wavelength: {analyzer.dispersive_wave_wavelength()*1e9:.1f} nm")
484
+
485
+ # Plot results
486
+ fig = plot_soliton_trajectories(solver)
487
+ fig.savefig("trajectories.png", dpi=150)
488
+ ```
489
+
490
+ # Breathers, Noise and Wave Breaking
491
+
492
+ Three modules cover physics that deterministic pulse solvers cannot express on
493
+ their own: exact NLSE breather solutions, stochastic seeding, and the
494
+ normal-dispersion wave-breaking regime.
495
+
496
+ ## Analytic breathers (`breathers.py`)
497
+
498
+ Exact soliton-on-finite-background solutions of the focusing NLSE — the
499
+ Akhmediev breather, the Peregrine soliton and the Kuznetsov–Ma soliton — plus a
500
+ physical-parameter mapping that turns fibre parameters into an exact initial
501
+ field:
502
+
503
+ ```python
504
+ from photonics_helper.base import Time
505
+ from photonics_helper.breathers import (
506
+ SolitonOnBackground,
507
+ peregrine_soliton,
508
+ akhmediev_breather,
509
+ general_sfb,
510
+ )
511
+ from photonics_helper.pulse import TemporalGrid
512
+
513
+ # SMF-28 at 1550 nm, 0.7 W background (Kibler et al. 2012 parameters)
514
+ sob = SolitonOnBackground(beta2=-21.8e-27, gamma=1.3e-3, P0=0.7)
515
+ print(f"L_NL = {sob.L_NL:.1f} m, T0 = {sob.T0*1e12:.3f} ps")
516
+ print(f"KM period = {sob.spatial_period_m(0.66)/1e3:.3f} km")
517
+ print(f"KM peak = {sob.peak_power(0.66):.3f} W")
518
+
519
+ grid = TemporalGrid(N=4096, Tmax=Time(80e-12, "s"))
520
+ wave = sob.initial_wave(grid, a=0.66) # exact Kuznetsov-Ma field
521
+
522
+ # Or use the dimensionless solutions directly:
523
+ psi = peregrine_soliton(xi=0.0, tau=grid.t / sob.T0) # peak |psi|^2 = 9
524
+ psi_ab = akhmediev_breather(xi=0.0, tau=grid.t / sob.T0, a=0.25)
525
+ psi_any = general_sfb(xi=0.0, tau=grid.t / sob.T0, a=0.66)
526
+ ```
527
+
528
+ ## Stochastic noise (`noise.py`)
529
+
530
+ Reproducible (seedable) noise sources, needed for spontaneous MI and
531
+ supercontinuum coherence studies:
532
+
533
+ ```python
534
+ from photonics_helper.noise import add_noise, add_ase_noise, complex_gaussian_noise
535
+
536
+ noisy = add_noise(cw_wave, rms_relative=0.01, seed=1) # ~1 % amplitude noise
537
+ seeded = add_ase_noise(cw_wave, level_dB=-50.0, seed=1) # -50 dB ASE background
538
+ n = complex_gaussian_noise(grid, rms=1e-3, seed=0) # raw time-domain noise
539
+ ```
540
+
541
+ `add_ase_noise` handles the `TemporalGrid.fft`/`ifft` `dt` scaling internally, so
542
+ the requested dB level is exact.
543
+
544
+ ## Wave breaking (`wave_breaking.py`)
545
+
546
+ Analytic wave-breaking distance and diagnostics for normal dispersion:
547
+
548
+ ```python
549
+ from photonics_helper.wave_breaking import WaveBreaking, wave_breaking_distance
550
+
551
+ wb = WaveBreaking(beta2=20e-27, gamma=1.5e-3, P0=10.0, T0=10e-12)
552
+ print(f"z_WB = {wb.z_WB:.1f} m, sqrt(L_D L_NL) = {wb.sqrt_LD_LNL:.1f} m")
553
+ # after propagating a Gaussian:
554
+ # result = wb.analyze(solver.z_array, [w.envelope_field for w in solver.evolution], grid.t)
555
+ # result["z_onset_m"], result["z_oscillation_m"], result["peak_steepness"]
556
+ ```
557
+
558
+ # Structured light — Laguerre-Gaussian / OAM (`structured.py`)
559
+
560
+ Analytic Laguerre-Gaussian transverse modes carrying orbital angular momentum
561
+ (OAM), Gaussian-beam propagation helpers and discrete overlap integrals. Modes
562
+ are normalized to unit power, so ``overlap`` gives the modal overlap directly:
563
+
564
+ ```python
565
+ from photonics_helper.structured import (
566
+ LaguerreGaussianMode,
567
+ overlap,
568
+ rayleigh_range,
569
+ beam_waist,
570
+ )
571
+
572
+ w0, lam = 1e-3, 1064e-9 # 1 mm waist at 1064 nm
573
+ lg01 = LaguerreGaussianMode(p=0, l=1, w0=w0, wavelength=lam) # OAM = hbar
574
+ lg02 = LaguerreGaussianMode(p=0, l=2, w0=w0, wavelength=lam)
575
+
576
+ print(overlap(lg01, lg01)) # 1.0 (normalized)
577
+ print(overlap(lg01, lg02)) # ~ 0 (orthogonal OAM)
578
+
579
+ z_r = rayleigh_range(w0, lam) # pi w0^2 / lambda
580
+ print(beam_waist(w0, z_r, lam)) # sqrt(2) * w0
581
+
582
+ field = lg01.structured() # StructuredField on (x, y)
583
+ print(field.power, field.second_moment_radius())
584
+ fig = field.plot() # intensity + phase panels
585
+ ```
586
+
587
+ ``plot_transverse_profile`` mirrors the pulse backend pattern
588
+ (``backend="matplotlib"`` default, ``backend="plotly"`` when plotly is
589
+ installed). The ``OAM`` winding of a mode is ``2 pi l`` around any loop that
590
+ encloses the optical axis; see `examples/28_structured_light.py`.
591
+
592
+ # Reproductions
593
+
594
+ The `reproductions/` directory validates the library against published results,
595
+ not against other software. Each folder has a `parameters.json`, a script that
596
+ asserts its result against an analytic/closed-form reference, a figure, and a
597
+ README with the DOI, findings and open issues.
598
+
599
+ | Reproduction | Reference (DOI) |
600
+ |---|---|
601
+ | `stolen_lin_1978_spm` | Stolen & Lin, *Phys. Rev. A* **17**, 1448 (1978) · [10.1103/PhysRevA.17.1448](https://doi.org/10.1103/PhysRevA.17.1448) |
602
+ | `macleod_quarter_wave_dbr` | Macleod, *Thin-Film Optical Filters* (textbook) |
603
+ | `gordon_1986_ssfs` | Gordon, *Opt. Lett.* **11**, 662 (1986) · [10.1364/OL.11.000662](https://doi.org/10.1364/OL.11.000662) |
604
+ | `dudley_2006_cherenkov_dw` | Akhmediev & Karlsson, *Phys. Rev. A* **51**, 2602 (1995) · [10.1103/PhysRevA.51.2602](https://doi.org/10.1103/PhysRevA.51.2602) |
605
+ | `dudley_2006_scg` | Dudley, Genty & Coen, *Rev. Mod. Phys.* **78**, 1135 (2006) · [10.1103/RevModPhys.78.1135](https://doi.org/10.1103/RevModPhys.78.1135) |
606
+ | `kuznetsov_ma_2012_breather` | Kibler et al., *Sci. Rep.* **2**, 463 (2012) · [10.1038/srep00463](https://doi.org/10.1038/srep00463) |
607
+ | `narhi_2016_mi_breathers` | Närhi et al., *Nat. Commun.* **7**, 13675 (2016) · [10.1038/ncomms13675](https://doi.org/10.1038/ncomms13675) |
608
+ | `tomlinson_1985_wave_breaking` | Tomlinson, Stolen & Johnson, *Opt. Lett.* **10**, 457 (1985) · [10.1364/OL.10.000457](https://doi.org/10.1364/OL.10.000457) |
609
+
610
+ See [`reproductions/README.md`](reproductions/README.md) for the full status,
611
+ cross-cutting findings and issues, and
612
+ [`reproductions/dudley_2006_scg/PAPER_ANALYSIS.md`](reproductions/dudley_2006_scg/PAPER_ANALYSIS.md)
613
+ for the figure-by-figure reproducibility map of the Dudley review.
614
+
615
+ ```sh
616
+ python -m pytest tests/test_reproductions.py # all reproduction regressions
617
+ python reproductions/dudley_2006_scg/fig05_ideal_soliton_period.py # one figure
618
+ ```
619
+
620
+ # Dashboard & API docs
621
+
622
+ One Dash app hosts both the Raman explorer and a GNLSE result viewer:
623
+
624
+ ```python
625
+ from photonics_helper.dashboard import app
626
+
627
+ app().run(debug=False, port=8050) # Raman Explorer | GNLSE Viewer
628
+ ```
629
+
630
+ Requires the `webapp` (Dash) and `plotting` (Plotly) extras; see
631
+ `examples/32_unified_dashboard.py`. The standalone Raman app remains available
632
+ as `photonics_helper.raman.app()`.
633
+
634
+ API documentation is built with mkdocs + mkdocstrings from the docstrings:
635
+
636
+ ```bash
637
+ pip install -e ".[docs]"
638
+ mkdocs serve # http://127.0.0.1:8000
639
+ ```
640
+
641
+ `.github/workflows/` also provides the CI matrix, the mkdocs build/deploy, and
642
+ the PyPI Trusted Publishing release (cut a `v*` tag to publish).
643
+
644
+ # Development
645
+
646
+ To install for development:
647
+
648
+ ```sh
649
+ git clone https://github.com/hitaishi2222/photonics_helper
650
+ cd photonics_helper
651
+ python -m venv .venv
652
+ source .venv/bin/activate # On Linux/Mac
653
+ pip install -e .
654
+ ```
655
+
656
+ # Roadmap
657
+
658
+ - ~~Add methods to convert wavelengths to energy (in eV)~~
659
+ - ~~Add functionality for dispersion calculations~~
660
+ - ~~Modeling Envelopes~~
661
+ - ~~Modeling Pulse~~
662
+ - **Transfer Matrix Method (TMM)** — available (see `dbr.py`)
663
+ - DBR multilayer stack simulation
664
+ - Characteristic-matrix (Macleod) formalism with correct layer ordering
665
+ - Spectral response (R, T) including absorbing and oblique stacks, plus `TMM.plot_spectrum`
666
+ - Electric-field profiling via `TMM.field_profile`
667
+ - `Pattern` layer stack
668
+ - **FROG** ✅
669
+ - SHG-FROG trace generation
670
+ - PCGPA pulse retrieval
671
+ - Fidelity metric
672
+ - **Raman Modeling** ✅
673
+ - Time-domain Raman response h_R(t)
674
+ - Frequency-domain gain spectrum H(Ω)
675
+ - Raman pulse interaction (R(t) ⊗ |E|²)
676
+ - Material comparison overlays with 6 panel types
677
+ - Pump wavelength explorer (Stokes/anti-Stokes)
678
+ - SQLite material database (44 entries)
679
+ - Interactive Dash dashboard
680
+ - Catalog explorer example (`examples/11_raman_material_catalog.py`)
681
+ - **GNLSE** ✅
682
+ - Dispersion (arbitrary-order β_k)
683
+ - Kerr effect
684
+ - Raman scattering (delayed response)
685
+ - Self-steepening (optional; default off — set `include_self_steepening=True` to match laserfun's `shock=True`)
686
+ - Two-photon absorption (TPA)
687
+ - Adaptive step-size (SSFM)
688
+ - Soliton propagation, fission, supercontinuum
689
+ - **Soliton Analysis** ✅
690
+ - Soliton order, dispersion/nonlinear/fission lengths
691
+ - Dispersive wave (Cherenkov) wavelength
692
+ - Soliton trajectory extraction and RSFS rate
693
+ - Soliton counting via peak detection
694
+ - Publication-ready visualization (trajectories, fission dynamics, DW spectrum)
695
+ - **Waveguide Support** ✅
696
+ - Confinement factor Γ in γ formula: `γ = n₂·ω₀·Γ/(c·A_eff)`
697
+ - Backward compatible (Γ=1.0 recovers fiber behavior)
698
+ - **Waveguide mode import (FEM)** ✅ — `WaveguideMode.from_csv/from_npz` → `PropagationConstant`/`Dispersion`, `PropagationConstant.beta2`
699
+ - **χ⁽²⁾ nonlinear optics** ✅ — `chi2` SHG/SFG/DFG RK4IP solver, QPM grating, `Lambda_qpm`, textbook `tanh²(κL)` reproduction
700
+ - **GPU FFT backend** ✅ — opt-in cupy path (`PHOTONICS_FFT_BACKEND=cupy`) with transparent CPU fallback and a benchmark
701
+ - **Unified dashboard** ✅ — `photonics_helper.dashboard.app()` combines the Raman Explorer and an interactive GNLSE result viewer
702
+ - **Release engineering** ✅ — PyPI Trusted Publishing, mkdocs API docs, and a Python 3.12/3.13 × ±pyfftw CI matrix (ruff + mypy gates)
703
+ - **Chalcogenide Materials** ✅
704
+ - GeAsSe added (n₂=6e-18 m²/W, 44 materials total)
705
+ - Suitable for soliton fission in chalcogenide waveguides
706
+ - **Structured Light** ✅ — Laguerre–Gaussian / OAM modes, Gaussian-beam propagation helpers, modal overlap integrals and transverse-profile plotting (`structured.py`)
707
+ - ~~Add methods for bandwidth calculations~~
708
+ - ~~Add methods for power/intensity conversions~~ (physical scaling via `Wave.with_effective_area` / `PeakPower.from_envelope`)
709
+
710
+ > ⚠️ **Note**: The TMM / DBR module (`dbr.py`) is still under active development. The API and internals may change.