cwt3d 1.0.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.
- cwt3d-1.0.0/LICENSE +21 -0
- cwt3d-1.0.0/MANIFEST.in +10 -0
- cwt3d-1.0.0/PKG-INFO +70 -0
- cwt3d-1.0.0/README.md +190 -0
- cwt3d-1.0.0/README.pypi.md +47 -0
- cwt3d-1.0.0/benchmarks/bench_coupling.py +74 -0
- cwt3d-1.0.0/benchmarks/bench_finite.py +58 -0
- cwt3d-1.0.0/benchmarks/common.py +58 -0
- cwt3d-1.0.0/calculator/_calc.py +242 -0
- cwt3d-1.0.0/docs/api.md +189 -0
- cwt3d-1.0.0/docs/benchmarks/coupling.csv +7 -0
- cwt3d-1.0.0/docs/benchmarks/coupling.json +516 -0
- cwt3d-1.0.0/docs/benchmarks/finite.csv +4 -0
- cwt3d-1.0.0/docs/benchmarks/finite.json +507 -0
- cwt3d-1.0.0/docs/benchmarks/historical-3bccd999a638/coupling.csv +7 -0
- cwt3d-1.0.0/docs/benchmarks/historical-3bccd999a638/coupling.json +516 -0
- cwt3d-1.0.0/docs/benchmarks/historical-3bccd999a638/finite.csv +4 -0
- cwt3d-1.0.0/docs/benchmarks/historical-3bccd999a638/finite.json +507 -0
- cwt3d-1.0.0/docs/benchmarks/historical-f8bd39fa2326/coupling.csv +7 -0
- cwt3d-1.0.0/docs/benchmarks/historical-f8bd39fa2326/coupling.json +516 -0
- cwt3d-1.0.0/docs/benchmarks/historical-f8bd39fa2326/finite.csv +4 -0
- cwt3d-1.0.0/docs/benchmarks/historical-f8bd39fa2326/finite.json +507 -0
- cwt3d-1.0.0/docs/examples/liang-70um/modes.csv +2 -0
- cwt3d-1.0.0/docs/examples/liang-70um/refinement.json +192 -0
- cwt3d-1.0.0/docs/examples/liang-70um/summary.json +194 -0
- cwt3d-1.0.0/docs/examples/liang-70um.md +151 -0
- cwt3d-1.0.0/docs/images/liang-70um.png +0 -0
- cwt3d-1.0.0/docs/literature_validation.md +84 -0
- cwt3d-1.0.0/docs/performance.md +126 -0
- cwt3d-1.0.0/docs/plans/v3-refactor-plan.md +156 -0
- cwt3d-1.0.0/docs/publishing.md +83 -0
- cwt3d-1.0.0/docs/usage.md +132 -0
- cwt3d-1.0.0/docs/validation.md +1615 -0
- cwt3d-1.0.0/examples/liang_70um.py +279 -0
- cwt3d-1.0.0/examples/plot_liang_70um.py +89 -0
- cwt3d-1.0.0/examples/square_finite.py +130 -0
- cwt3d-1.0.0/model/_Model.py +1009 -0
- cwt3d-1.0.0/pyproject.toml +31 -0
- cwt3d-1.0.0/scripts/check_release.py +221 -0
- cwt3d-1.0.0/scripts/package_delivery.py +133 -0
- cwt3d-1.0.0/scripts/validate_convergence.py +158 -0
- cwt3d-1.0.0/setup.cfg +4 -0
- cwt3d-1.0.0/src/cwt3d/__init__.py +9 -0
- cwt3d-1.0.0/src/cwt3d/cache.py +324 -0
- cwt3d-1.0.0/src/cwt3d/conventions.py +128 -0
- cwt3d-1.0.0/src/cwt3d/coupling.py +186 -0
- cwt3d-1.0.0/src/cwt3d/finite/__init__.py +16 -0
- cwt3d-1.0.0/src/cwt3d/finite/assembly.py +180 -0
- cwt3d-1.0.0/src/cwt3d/finite/eigensolver.py +443 -0
- cwt3d-1.0.0/src/cwt3d/finite/fields.py +205 -0
- cwt3d-1.0.0/src/cwt3d/finite/grid.py +236 -0
- cwt3d-1.0.0/src/cwt3d/finite/tracking.py +298 -0
- cwt3d-1.0.0/src/cwt3d/fourier.py +132 -0
- cwt3d-1.0.0/src/cwt3d/geometry.py +173 -0
- cwt3d-1.0.0/src/cwt3d/greens.py +542 -0
- cwt3d-1.0.0/src/cwt3d/infinite.py +32 -0
- cwt3d-1.0.0/src/cwt3d/quadrature.py +46 -0
- cwt3d-1.0.0/src/cwt3d/waveguide.py +302 -0
- cwt3d-1.0.0/src/cwt3d.egg-info/PKG-INFO +70 -0
- cwt3d-1.0.0/src/cwt3d.egg-info/SOURCES.txt +84 -0
- cwt3d-1.0.0/src/cwt3d.egg-info/dependency_links.txt +1 -0
- cwt3d-1.0.0/src/cwt3d.egg-info/requires.txt +14 -0
- cwt3d-1.0.0/src/cwt3d.egg-info/top_level.txt +1 -0
- cwt3d-1.0.0/tests/regressions/__init__.py +1 -0
- cwt3d-1.0.0/tests/regressions/_legacy.py +49 -0
- cwt3d-1.0.0/tests/regressions/test_chi_prefactor.py +53 -0
- cwt3d-1.0.0/tests/regressions/test_fourier_dft.py +55 -0
- cwt3d-1.0.0/tests/regressions/test_green_operator.py +58 -0
- cwt3d-1.0.0/tests/test_assembly.py +302 -0
- cwt3d-1.0.0/tests/test_backend_parity.py +105 -0
- cwt3d-1.0.0/tests/test_cache.py +372 -0
- cwt3d-1.0.0/tests/test_conventions.py +100 -0
- cwt3d-1.0.0/tests/test_coupling.py +226 -0
- cwt3d-1.0.0/tests/test_eigensolver.py +524 -0
- cwt3d-1.0.0/tests/test_end_to_end.py +66 -0
- cwt3d-1.0.0/tests/test_finite_modes.py +391 -0
- cwt3d-1.0.0/tests/test_fourier.py +180 -0
- cwt3d-1.0.0/tests/test_geometry.py +55 -0
- cwt3d-1.0.0/tests/test_greens_homogeneous.py +184 -0
- cwt3d-1.0.0/tests/test_greens_layered.py +250 -0
- cwt3d-1.0.0/tests/test_grid.py +208 -0
- cwt3d-1.0.0/tests/test_import.py +30 -0
- cwt3d-1.0.0/tests/test_liang_70um.py +170 -0
- cwt3d-1.0.0/tests/test_readme_example.py +54 -0
- cwt3d-1.0.0/tests/test_release_packaging.py +196 -0
- cwt3d-1.0.0/tests/test_waveguide.py +250 -0
cwt3d-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Hai HUANG
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
cwt3d-1.0.0/MANIFEST.in
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
include LICENSE README.md README.pypi.md pyproject.toml
|
|
2
|
+
recursive-include src/cwt3d *.py
|
|
3
|
+
recursive-include examples *.py
|
|
4
|
+
recursive-include tests *.py
|
|
5
|
+
recursive-include benchmarks *.py
|
|
6
|
+
recursive-include scripts *.py
|
|
7
|
+
recursive-include docs *.md *.json *.csv *.png
|
|
8
|
+
# These two files are parsed, not imported, by historical-defect tests.
|
|
9
|
+
include model/_Model.py calculator/_calc.py
|
|
10
|
+
global-exclude __pycache__ *.py[cod] .DS_Store
|
cwt3d-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cwt3d
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Independent numerical core for three-dimensional coupled-wave theory
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/maple367/3D-CWT
|
|
7
|
+
Project-URL: Documentation, https://github.com/maple367/3D-CWT/blob/latest/docs/api.md
|
|
8
|
+
Project-URL: Issues, https://github.com/maple367/3D-CWT/issues
|
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: numpy>=1.24
|
|
14
|
+
Requires-Dist: scipy>=1.10
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
17
|
+
Requires-Dist: tomli>=2; python_version < "3.11" and extra == "dev"
|
|
18
|
+
Provides-Extra: plot
|
|
19
|
+
Requires-Dist: matplotlib>=3.6; extra == "plot"
|
|
20
|
+
Provides-Extra: numba
|
|
21
|
+
Requires-Dist: numba>=0.61; extra == "numba"
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# cwt3d
|
|
25
|
+
|
|
26
|
+
A Python implementation of three-dimensional coupled-wave theory for
|
|
27
|
+
square-lattice photonic-crystal surface-emitting lasers (PCSELs).
|
|
28
|
+
|
|
29
|
+
The package calculates normalized vertical TE waveguide modes, Fourier
|
|
30
|
+
coupling coefficients, and finite-cavity envelopes using sparse staggered-grid
|
|
31
|
+
eigenproblems. Runtime dependencies are NumPy and SciPy; COMSOL is not required.
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
python -m pip install cwt3d
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Matplotlib is optional:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
python -m pip install "cwt3d[plot]"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Examples and Python interfaces
|
|
46
|
+
|
|
47
|
+
The [source repository](https://github.com/maple367/3D-CWT) includes a complete
|
|
48
|
+
70 × 70 µm² circular-hole example based on Chapter 4 of Yong Liang's thesis.
|
|
49
|
+
It contains the layer stack, runnable calculation and plotting scripts, and
|
|
50
|
+
numerical refinement results.
|
|
51
|
+
|
|
52
|
+

|
|
53
|
+
|
|
54
|
+
The [Python API guide](https://github.com/maple367/3D-CWT/blob/latest/docs/api.md)
|
|
55
|
+
shows how to construct a different geometry, calculate its coupling matrix,
|
|
56
|
+
solve a finite cavity, and access the complex fields. Examples are included
|
|
57
|
+
in the source distribution and repository; the wheel installs the `cwt3d`
|
|
58
|
+
library.
|
|
59
|
+
|
|
60
|
+
## Scope
|
|
61
|
+
|
|
62
|
+
The current model uses lossless scalar TE waves on a square lattice.
|
|
63
|
+
It returns modes near a selected complex shift, not the complete cavity
|
|
64
|
+
spectrum. The example's auxiliary-harmonic refinement is still finite, as
|
|
65
|
+
reported in its notes. Carrier transport and above-threshold dynamics from
|
|
66
|
+
the historical application are not part of this package.
|
|
67
|
+
|
|
68
|
+
The code is distributed under the MIT license. The formulation follows
|
|
69
|
+
[Yong Liang's doctoral thesis](https://doi.org/10.14989/doctor.k18283);
|
|
70
|
+
references and conventions are listed in the repository documentation.
|
cwt3d-1.0.0/README.md
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# 3D-CWT
|
|
2
|
+
|
|
3
|
+
Three-dimensional coupled-wave calculations for square-lattice photonic-crystal
|
|
4
|
+
surface-emitting lasers (PCSELs). The Python package calculates the vertical
|
|
5
|
+
waveguide mode, the four-wave coupling matrix, and finite-cavity modes on a
|
|
6
|
+
staggered grid.
|
|
7
|
+
|
|
8
|
+
The current optical solver uses NumPy and SciPy. It does not require COMSOL.
|
|
9
|
+
The original COMSOL-based code and its version notes are retained below.
|
|
10
|
+
|
|
11
|
+
## Example: a 70 µm square cavity
|
|
12
|
+
|
|
13
|
+
This example uses the circular-hole (CC) structure from Chapter 4 of
|
|
14
|
+
[Yong Liang's doctoral thesis](https://doi.org/10.14989/doctor.k18283),
|
|
15
|
+
Table 4.1: a 295 nm square lattice, 16% air filling fraction, and a
|
|
16
|
+
70 × 70 µm² cavity. The figure shows a calculated single-lobed mode and its
|
|
17
|
+
center-line intensity profiles.
|
|
18
|
+
|
|
19
|
+

|
|
20
|
+
|
|
21
|
+
The plotted quantity is the slowly varying envelope
|
|
22
|
+
`|Rx|² + |Sx|² + |Ry|² + |Sy|²`, divided by its peak. The numerical results,
|
|
23
|
+
layer stack, and refinement checks are in the
|
|
24
|
+
[70 µm example notes](docs/examples/liang-70um.md).
|
|
25
|
+
|
|
26
|
+
## Install and run
|
|
27
|
+
|
|
28
|
+
Run these commands from the repository root.
|
|
29
|
+
Python 3.10 or later is required. The `plot` extra installs Matplotlib for the
|
|
30
|
+
figure; the calculation itself only needs NumPy and SciPy.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
python -m pip install -e ".[plot]"
|
|
34
|
+
python examples/liang_70um.py --N 96 --D 32 --q 64 --output results/liang-70um
|
|
35
|
+
python examples/plot_liang_70um.py results/liang-70um
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The first script writes `summary.json`, `modes.csv`, and `fields.npz`. The second
|
|
39
|
+
writes `selected_mode.png`. Choose a new output directory for each calculation;
|
|
40
|
+
the solver refuses to overwrite an existing result.
|
|
41
|
+
|
|
42
|
+
For a quicker first run, use the lower-order defaults:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
python examples/liang_70um.py --output results/liang-70um-quick
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`--N` is the number of envelope-grid cells per side, `--D` is the auxiliary
|
|
49
|
+
Fourier cutoff, and `--q` is the vertical integration order. They control
|
|
50
|
+
different numerical errors. The quick run and the plotted run use the same
|
|
51
|
+
physical structure.
|
|
52
|
+
|
|
53
|
+
## Use the calculation from Python
|
|
54
|
+
|
|
55
|
+
The example exposes the calculation as a function. Run this from the repository
|
|
56
|
+
root after installation:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from examples.liang_70um import run_case
|
|
60
|
+
|
|
61
|
+
case = run_case(N=96, D=32, q=64)
|
|
62
|
+
mode = case.modes[0]
|
|
63
|
+
|
|
64
|
+
print("delta L:", mode.delta * 70)
|
|
65
|
+
print("alpha L:", mode.alpha * 70)
|
|
66
|
+
print("eigenpair residual:", mode.residual)
|
|
67
|
+
|
|
68
|
+
intensity = mode.intensity # shape: (Ny, Nx)
|
|
69
|
+
Rx = mode.centers.Rx # complex envelope at cell centers
|
|
70
|
+
C = case.coupling.C # 4 x 4 coupling matrix, in µm^-1
|
|
71
|
+
A, B = case.pencil.A, case.pencil.B
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`run_case` returns the waveguide reference, coupling matrix, sparse pencil,
|
|
75
|
+
eigenpairs, reconstructed fields, and a summary dictionary. Pass `output=...`
|
|
76
|
+
to save them. To build a different structure or reuse a coupling matrix, follow
|
|
77
|
+
the [complete Python API example](docs/api.md).
|
|
78
|
+
|
|
79
|
+
The default search targets one mode near the lowest-frequency periodic band
|
|
80
|
+
edge. `--k` and `--sigma` select a larger set or a different part of the
|
|
81
|
+
spectrum; one search is not a survey of all competing modes. Eigenvalues are
|
|
82
|
+
`delta + 1j*alpha`, where `alpha` is an amplitude-loss coefficient. Power loss
|
|
83
|
+
is `2*alpha`.
|
|
84
|
+
|
|
85
|
+
## Documentation
|
|
86
|
+
|
|
87
|
+
- [Running examples and reading the output](docs/usage.md)
|
|
88
|
+
- [Python interfaces and a complete custom calculation](docs/api.md)
|
|
89
|
+
- [70 µm structure, results, and numerical checks](docs/examples/liang-70um.md)
|
|
90
|
+
- [Model conventions and earlier literature checks](docs/literature_validation.md)
|
|
91
|
+
- [Numerical tests](docs/validation.md) and [performance measurements](docs/performance.md)
|
|
92
|
+
- [Publishing releases to PyPI](docs/publishing.md)
|
|
93
|
+
|
|
94
|
+
The current model covers lossless scalar TE waves on a square lattice. The
|
|
95
|
+
70 µm calculation uses a layered outgoing Green function and halfspace
|
|
96
|
+
claddings, so it is a calculation with the thesis structure rather than an
|
|
97
|
+
exact reproduction of the thesis curves. Carrier transport and above-threshold
|
|
98
|
+
laser dynamics are not included in the new package.
|
|
99
|
+
|
|
100
|
+
To run the tests:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
python -m pip install -e ".[dev,plot]"
|
|
104
|
+
python -m pytest -q
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Previous versions (v2)
|
|
110
|
+
|
|
111
|
+
# 3D-CWT
|
|
112
|
+
Three Dimension Coupled Wave Theory
|
|
113
|
+
|
|
114
|
+
# Requirements
|
|
115
|
+
- Python 3.12 (my environment)
|
|
116
|
+
- numpy=1.26.4
|
|
117
|
+
- scipy=1.14.0
|
|
118
|
+
- matplotlib
|
|
119
|
+
- mph
|
|
120
|
+
- dill
|
|
121
|
+
- Comsol Multiphysics
|
|
122
|
+
## Installation
|
|
123
|
+
```bash
|
|
124
|
+
conda env create -f environment.yml
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
# TODO
|
|
128
|
+
- [x] Add support for titled photonic crystal
|
|
129
|
+
- [x] Add support for finite size photonic crystal
|
|
130
|
+
- [ ] Add find_beta0 and find_k0 to class TMM
|
|
131
|
+
- [ ] Pure python implementation
|
|
132
|
+
|
|
133
|
+
# Release Notes
|
|
134
|
+
## Latest - Version 2.0.0
|
|
135
|
+
- Organize the code.
|
|
136
|
+
## Version 1.3.0
|
|
137
|
+
- Use high accuracy Green's function.
|
|
138
|
+
## Version 1.2.0
|
|
139
|
+
- Consider the symmetry of the fourier coefficients, so that the calculation of the fourier coefficients is faster.
|
|
140
|
+
## Version 1.0.0
|
|
141
|
+
- Add support for finite size photonic crystal. Powered by [Comsol Multiphysics](https://www.comsol.com/).
|
|
142
|
+
## Version 0.4.1
|
|
143
|
+
- Encapsulated into a function.
|
|
144
|
+
## Version 0.4.0
|
|
145
|
+
- Add support for titled photonic crystal.
|
|
146
|
+
- Add comsol semiconductor simulation.
|
|
147
|
+
## Version 0.3.0
|
|
148
|
+
- Turn back to Python as numerical computing environment.
|
|
149
|
+
## Version 0.2.0
|
|
150
|
+
- Use Matlab as numerical computing environment.
|
|
151
|
+
|
|
152
|
+
# Citing 3D-CWT
|
|
153
|
+
If you use this code in your research, please cite the following papers, this will help me a lot.
|
|
154
|
+
In BibTeX format:
|
|
155
|
+
```bibtex
|
|
156
|
+
@article{huang_Efficient_2026,
|
|
157
|
+
title = {Towards Efficient {{PCSEL}} Design: A Fully {{AI-driven}} Approach},
|
|
158
|
+
shorttitle = {Towards Efficient {{PCSEL}} Design},
|
|
159
|
+
author = {Huang, Hai and Xu, Ziteng and Xin, Qi and Zhang, Zhaoyu},
|
|
160
|
+
journal = {Chinese Optics Letters},
|
|
161
|
+
volume = {24},
|
|
162
|
+
number = {4},
|
|
163
|
+
pages = {043602},
|
|
164
|
+
year = 2026,
|
|
165
|
+
doi = {10.3788/COL202624.043602},
|
|
166
|
+
langid = {english}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
@article{huang_Unveiling_2025,
|
|
170
|
+
title = {Unveiling the Potential of Photonic Crystal Surface Emitting Lasers: A Concise Review},
|
|
171
|
+
shorttitle = {Unveiling the Potential of Photonic Crystal Surface Emitting Lasers},
|
|
172
|
+
author = {Huang, Hai and Yang, Chuanyi and Li, Hui and Zhang, Zhaoyu},
|
|
173
|
+
year = 2025,
|
|
174
|
+
month = apr,
|
|
175
|
+
journal = {Semiconductor Science and Technology},
|
|
176
|
+
volume = {40},
|
|
177
|
+
number = {4},
|
|
178
|
+
pages = {43001},
|
|
179
|
+
publisher = {IOP Publishing},
|
|
180
|
+
issn = {0268-1242, 1361-6641},
|
|
181
|
+
doi = {10.1088/1361-6641/adb7fd},
|
|
182
|
+
langid = {english}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
or in following IEEE format:
|
|
186
|
+
[1] H. Huang, C. Yang, H. Li, and Z. Zhang, ‘Unveiling the potential of photonic crystal surface emitting lasers: a concise review’, Semicond. Sci. Technol., vol. 40, no. 4, p. 43001, Apr. 2025, doi: 10.1088/1361-6641/adb7fd.
|
|
187
|
+
[2] H. Huang, Z. Xu, Q. Xin, and Z. Zhang, ‘Towards efficient PCSEL design: a fully AI-driven approach’, Chinese Optics Letters, vol. 24, no. 4, Art. no. 043602, 2026, doi: 10.3788/COL202624.043602.
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# cwt3d
|
|
2
|
+
|
|
3
|
+
A Python implementation of three-dimensional coupled-wave theory for
|
|
4
|
+
square-lattice photonic-crystal surface-emitting lasers (PCSELs).
|
|
5
|
+
|
|
6
|
+
The package calculates normalized vertical TE waveguide modes, Fourier
|
|
7
|
+
coupling coefficients, and finite-cavity envelopes using sparse staggered-grid
|
|
8
|
+
eigenproblems. Runtime dependencies are NumPy and SciPy; COMSOL is not required.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
python -m pip install cwt3d
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Matplotlib is optional:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
python -m pip install "cwt3d[plot]"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Examples and Python interfaces
|
|
23
|
+
|
|
24
|
+
The [source repository](https://github.com/maple367/3D-CWT) includes a complete
|
|
25
|
+
70 × 70 µm² circular-hole example based on Chapter 4 of Yong Liang's thesis.
|
|
26
|
+
It contains the layer stack, runnable calculation and plotting scripts, and
|
|
27
|
+
numerical refinement results.
|
|
28
|
+
|
|
29
|
+

|
|
30
|
+
|
|
31
|
+
The [Python API guide](https://github.com/maple367/3D-CWT/blob/latest/docs/api.md)
|
|
32
|
+
shows how to construct a different geometry, calculate its coupling matrix,
|
|
33
|
+
solve a finite cavity, and access the complex fields. Examples are included
|
|
34
|
+
in the source distribution and repository; the wheel installs the `cwt3d`
|
|
35
|
+
library.
|
|
36
|
+
|
|
37
|
+
## Scope
|
|
38
|
+
|
|
39
|
+
The current model uses lossless scalar TE waves on a square lattice.
|
|
40
|
+
It returns modes near a selected complex shift, not the complete cavity
|
|
41
|
+
spectrum. The example's auxiliary-harmonic refinement is still finite, as
|
|
42
|
+
reported in its notes. Carrier transport and above-threshold dynamics from
|
|
43
|
+
the historical application are not part of this package.
|
|
44
|
+
|
|
45
|
+
The code is distributed under the MIT license. The formulation follows
|
|
46
|
+
[Yong Liang's doctoral thesis](https://doi.org/10.14989/doctor.k18283);
|
|
47
|
+
references and conventions are listed in the repository documentation.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Run from repository: .venv/Scripts/python.exe benchmarks/bench_coupling.py --cache-dir <explicit path>."""
|
|
2
|
+
from common import *
|
|
3
|
+
import argparse
|
|
4
|
+
from scipy.optimize import linear_sum_assignment
|
|
5
|
+
from cwt3d.coupling import build_coupling
|
|
6
|
+
from cwt3d.greens import LayeredGreen, PreparedLayeredGreen
|
|
7
|
+
from cwt3d.cache import CouplingCache
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def eigen_error(a,b):
|
|
11
|
+
x,y=np.linalg.eigvals(a),np.linalg.eigvals(b)
|
|
12
|
+
i,j=linear_sum_assignment(abs(x[:,None]-y))
|
|
13
|
+
return float(max(abs(x[i]-y[j])))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def main():
|
|
17
|
+
parser=argparse.ArgumentParser(description=__doc__)
|
|
18
|
+
parser.add_argument('--cache-dir',required=True)
|
|
19
|
+
parser.add_argument('--output',default='docs/benchmarks')
|
|
20
|
+
parser.add_argument('--repeats',type=int,default=3)
|
|
21
|
+
args=parser.parse_args()
|
|
22
|
+
if args.repeats < 1: parser.error('repeats must be positive')
|
|
23
|
+
rows=[]
|
|
24
|
+
cache=CouplingCache(args.cache_dir)
|
|
25
|
+
for dual in (False,True):
|
|
26
|
+
mode,a,holes=physical(dual)
|
|
27
|
+
# Explicit, measured initialization and steady evaluation, not JIT compile.
|
|
28
|
+
z=np.linspace(.01,.12,12)[:,None]; s=np.linspace(.015,.125,12)[None,:]
|
|
29
|
+
kernel_times={}
|
|
30
|
+
for name,cls in [('reference',LayeredGreen),('prepared',PreparedLayeredGreen)]:
|
|
31
|
+
kernel,init=timed(lambda:cls.from_structure(mode.guide),args.repeats)
|
|
32
|
+
kernel(z,s)
|
|
33
|
+
_,steady=timed(lambda:kernel(z,s),args.repeats)
|
|
34
|
+
_,scalar=timed(lambda:cls.from_structure(mode.guide)(.05,.07),args.repeats)
|
|
35
|
+
kernel_times[name]=dict(init_samples_seconds=init,init_median_seconds=float(np.median(init)),
|
|
36
|
+
steady_samples_seconds=steady,steady_median_seconds=float(np.median(steady)),
|
|
37
|
+
scalar_cold_samples_seconds=scalar,scalar_cold_median_seconds=float(np.median(scalar)))
|
|
38
|
+
for D in (1,3,10):
|
|
39
|
+
options=dict(cutoff=D,quadrature_order=12,fourier_order=64)
|
|
40
|
+
call=lambda backend,q=12:build_coupling(mode,a,holes,**dict(options,quadrature_order=q),backend=backend)
|
|
41
|
+
# Each backend gets one unrecorded warm-up; all reported samples retained.
|
|
42
|
+
call('reference'); call('prepared')
|
|
43
|
+
ref,rs=timed(lambda:call('reference'),args.repeats)
|
|
44
|
+
fast,fs=timed(lambda:call('prepared'),args.repeats)
|
|
45
|
+
# Independent higher-q reference, never lower accuracy to gain speed.
|
|
46
|
+
high,hs=timed(lambda:call('reference',24),1)
|
|
47
|
+
middle=call('reference',18)
|
|
48
|
+
key=cache.key(mode,a,holes,backend='prepared',**options)
|
|
49
|
+
cache.clear(key) # exact owned key only, ensures actual cold miss
|
|
50
|
+
cold,cs=timed(lambda:cache.build(mode,a,holes,backend='prepared',**options),1)
|
|
51
|
+
hit,ch=timed(lambda:cache.build(mode,a,holes,backend='prepared',**options),args.repeats)
|
|
52
|
+
assert cold == hit == fast
|
|
53
|
+
row=dict(geometry='dual_circle_triangle' if dual else 'single_circle',cutoff=D,
|
|
54
|
+
quadrature_order=12,fourier_order=64,convergence_reference_q=24,
|
|
55
|
+
repeats=args.repeats,warmup_per_backend=1,
|
|
56
|
+
reference_samples_seconds=rs,prepared_samples_seconds=fs,
|
|
57
|
+
reference_median_seconds=float(np.median(rs)),prepared_median_seconds=float(np.median(fs)),
|
|
58
|
+
speedup=float(np.median(rs)/np.median(fs)),
|
|
59
|
+
matrix_max_error=float(np.max(abs(ref.C-fast.C))),eigen_set_max_error=eigen_error(ref.C,fast.C),
|
|
60
|
+
q12_vs_q24_matrix_max_error=float(np.max(abs(ref.C-high.C))),
|
|
61
|
+
q18_vs_q24_matrix_max_error=float(np.max(abs(middle.C-high.C))),
|
|
62
|
+
q12_vs_q24_eigen_set_max_error=eigen_error(ref.C,high.C),
|
|
63
|
+
q24_reference_samples_seconds=hs,
|
|
64
|
+
cache_miss_seconds=cs[0],cache_hit_samples_seconds=ch,cache_hit_median_seconds=float(np.median(ch)),
|
|
65
|
+
kernel_timings=kernel_times,radial_count=len(ref.radial_orders),
|
|
66
|
+
cache_cleanup='exact key removed after measurement')
|
|
67
|
+
rows.append(row)
|
|
68
|
+
cache.clear(key)
|
|
69
|
+
save(args.output,'coupling',rows,accuracy='Same D/q/F/geometry and Duffy triangles; eigenvalues Hungarian matched.',
|
|
70
|
+
cache_policy='Explicit directory only; measured keys deleted. Scratch host policy prunes entries idle 24h.')
|
|
71
|
+
print(json.dumps({k:row[k] for k in ('geometry','cutoff','speedup','matrix_max_error','q12_vs_q24_matrix_max_error')}),flush=True)
|
|
72
|
+
assert len(rows)==6
|
|
73
|
+
|
|
74
|
+
if __name__=='__main__': main()
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Sparse finite-domain scaling; no dense or legacy mathematical baseline."""
|
|
2
|
+
from common import *
|
|
3
|
+
import argparse
|
|
4
|
+
import tracemalloc
|
|
5
|
+
from cwt3d.coupling import build_coupling
|
|
6
|
+
from cwt3d.finite.grid import StaggeredGrid
|
|
7
|
+
from cwt3d.finite.assembly import FinitePencil
|
|
8
|
+
from cwt3d.finite.eigensolver import solve_finite
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def run(grid,coupling):
|
|
12
|
+
start=perf_counter(); pencil=FinitePencil(grid,coupling); assembly=perf_counter()-start
|
|
13
|
+
start=perf_counter()
|
|
14
|
+
eigen=solve_finite(pencil,sigma=.03+.03j,k=6,tol=1e-9,residual_tol=1e-9,seed=33,ncv=30,maxiter=5000)
|
|
15
|
+
solve=perf_counter()-start
|
|
16
|
+
a,b=pencil.A,pencil.B
|
|
17
|
+
shifted=(a-(.03+.03j)*b).tocsc()
|
|
18
|
+
return dict(assembly_seconds=assembly,solve_total_seconds=solve,
|
|
19
|
+
lu_seconds=eigen.lu_seconds,iteration_seconds=eigen.iteration_seconds,
|
|
20
|
+
max_residual=float(max(eigen.residuals)),nnz_A=a.nnz,nnz_B=b.nnz,
|
|
21
|
+
nnz_shift=shifted.nnz,lu_nnz=eigen.lu_nnz,lu_fill_ratio=eigen.lu_nnz/shifted.nnz,
|
|
22
|
+
linear_solves=eigen.linear_solves,status=eigen.status,
|
|
23
|
+
eigenvalues=[[float(x.real),float(x.imag)] for x in eigen.eigenvalues])
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def main():
|
|
27
|
+
parser=argparse.ArgumentParser(description=__doc__)
|
|
28
|
+
parser.add_argument('--output',default='docs/benchmarks')
|
|
29
|
+
parser.add_argument('--repeats',type=int,default=3)
|
|
30
|
+
args=parser.parse_args()
|
|
31
|
+
if args.repeats < 1: parser.error('repeats must be positive')
|
|
32
|
+
m,a,h=physical(True)
|
|
33
|
+
c=build_coupling(m,a,h,cutoff=3,quadrature_order=12,fourier_order=64,backend='prepared')
|
|
34
|
+
rows=[]
|
|
35
|
+
for n in (8,16,32):
|
|
36
|
+
grid=StaggeredGrid(n,n,40.,40.)
|
|
37
|
+
run(grid,c) # warm-up independent of timing/memory samples
|
|
38
|
+
samples=[run(grid,c) for _ in range(args.repeats)]
|
|
39
|
+
tracemalloc.start()
|
|
40
|
+
memory_run=run(grid,c)
|
|
41
|
+
_,peak=tracemalloc.get_traced_memory(); tracemalloc.stop()
|
|
42
|
+
row=dict(Nx=n,Ny=n,n_dof=grid.n_dof,Lx_um=40.,Ly_um=40.,sigma=[.03,.03],
|
|
43
|
+
k=6,tol=1e-9,residual_tol=1e-9,seed=33,ncv=30,maxiter=5000,
|
|
44
|
+
repeats=args.repeats,warmup=1,samples=samples,
|
|
45
|
+
tracemalloc_peak_bytes=peak,rss_peak_bytes=None,
|
|
46
|
+
memory_note='Separate traced run: Python/registered NumPy allocations, NOT process RSS or full native LU memory.',
|
|
47
|
+
memory_run_max_residual=memory_run['max_residual'])
|
|
48
|
+
for key in ('assembly_seconds','solve_total_seconds','lu_seconds','iteration_seconds','lu_fill_ratio'):
|
|
49
|
+
row[key+'_median']=float(np.median([sample[key] for sample in samples]))
|
|
50
|
+
for key in ('nnz_A','nnz_B','nnz_shift','lu_nnz','linear_solves','max_residual','status'):
|
|
51
|
+
row[key]=samples[len(samples)//2][key]
|
|
52
|
+
rows.append(row)
|
|
53
|
+
save(args.output,'finite',rows,coupling=dict(geometry='dual_circle_triangle',cutoff=3,quadrature_order=12,fourier_order=64,backend='prepared'),
|
|
54
|
+
scope='Sparse scaling only, same target controls; no dense/legacy speed claim, no cross-grid mode matching or mesh-convergence claim.')
|
|
55
|
+
print(json.dumps({k:row[k] for k in ('Nx','assembly_seconds_median','lu_seconds_median','iteration_seconds_median','max_residual','tracemalloc_peak_bytes')}),flush=True)
|
|
56
|
+
assert len(rows)==3
|
|
57
|
+
|
|
58
|
+
if __name__=='__main__': main()
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Shared deterministic fixtures and non-identifying benchmark output."""
|
|
2
|
+
import os
|
|
3
|
+
# Set before importing NumPy/SciPy. Override inherited multi-thread settings.
|
|
4
|
+
THREAD_VARIABLES = ('OPENBLAS_NUM_THREADS','MKL_NUM_THREADS','OMP_NUM_THREADS',
|
|
5
|
+
'VECLIB_MAXIMUM_THREADS','NUMEXPR_NUM_THREADS','BLIS_NUM_THREADS')
|
|
6
|
+
for name in THREAD_VARIABLES:
|
|
7
|
+
os.environ[name] = '1'
|
|
8
|
+
import csv
|
|
9
|
+
import json
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
import platform
|
|
12
|
+
from time import perf_counter
|
|
13
|
+
import numpy as np
|
|
14
|
+
import scipy
|
|
15
|
+
from cwt3d.geometry import CircleHole, RightTriangleHole
|
|
16
|
+
from cwt3d.waveguide import TEWaveguide
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def physical(dual=False):
|
|
20
|
+
means = [8.,10.] if dual else [9.]
|
|
21
|
+
guide = TEWaveguide([1., *means, 1.], [.13,.19] if dual else [.32], k0=6.)
|
|
22
|
+
mode = guide.solve_mode()
|
|
23
|
+
a = 2*np.pi/mode.beta
|
|
24
|
+
fill = np.pi*.13**2
|
|
25
|
+
first = CircleHole(.13*a,(a,a),(.36*a,.47*a), means[0]+2*fill, means[0]-2*(1-fill))
|
|
26
|
+
holes = {1:first}
|
|
27
|
+
if dual:
|
|
28
|
+
fill = .35*.27/2
|
|
29
|
+
holes[2] = RightTriangleHole((.35*a,.27*a),(a,a),(.63*a,.54*a),
|
|
30
|
+
means[1]+2*fill,means[1]-2*(1-fill))
|
|
31
|
+
return mode,a,holes
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def environment():
|
|
35
|
+
from cwt3d.cache import algorithm_digest
|
|
36
|
+
return dict(source_digest=algorithm_digest(),python=platform.python_version(),numpy=np.__version__,scipy=scipy.__version__,
|
|
37
|
+
system=platform.system(),release=platform.release(),machine=platform.machine(),
|
|
38
|
+
cpu=platform.processor(),logical_cpus=os.cpu_count(),
|
|
39
|
+
threads={name:os.environ[name] for name in THREAD_VARIABLES},
|
|
40
|
+
precision='complex128',jit='N/A (no JIT)',rss_peak_bytes=None,
|
|
41
|
+
note='Thread environment fixed before importing NumPy; RSS not measured.')
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def timed(call, repeats):
|
|
45
|
+
samples=[]
|
|
46
|
+
for _ in range(repeats):
|
|
47
|
+
start=perf_counter(); result=call(); samples.append(perf_counter()-start)
|
|
48
|
+
return result,samples
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def save(output, kind, rows, **extra):
|
|
52
|
+
output=Path(output); output.mkdir(parents=True,exist_ok=True)
|
|
53
|
+
data=dict(kind=kind,environment=environment(),rows=rows,**extra)
|
|
54
|
+
(output/f'{kind}.json').write_text(json.dumps(data,indent=2,allow_nan=False)+'\n',encoding='utf-8')
|
|
55
|
+
keys=list(dict.fromkeys(k for row in rows for k in row))
|
|
56
|
+
with (output/f'{kind}.csv').open('w',newline='',encoding='utf-8') as stream:
|
|
57
|
+
writer=csv.DictWriter(stream,fieldnames=keys); writer.writeheader()
|
|
58
|
+
writer.writerows({k:json.dumps(v) if isinstance(v,(list,dict)) else v for k,v in row.items()} for row in rows)
|