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.
Files changed (86) hide show
  1. cwt3d-1.0.0/LICENSE +21 -0
  2. cwt3d-1.0.0/MANIFEST.in +10 -0
  3. cwt3d-1.0.0/PKG-INFO +70 -0
  4. cwt3d-1.0.0/README.md +190 -0
  5. cwt3d-1.0.0/README.pypi.md +47 -0
  6. cwt3d-1.0.0/benchmarks/bench_coupling.py +74 -0
  7. cwt3d-1.0.0/benchmarks/bench_finite.py +58 -0
  8. cwt3d-1.0.0/benchmarks/common.py +58 -0
  9. cwt3d-1.0.0/calculator/_calc.py +242 -0
  10. cwt3d-1.0.0/docs/api.md +189 -0
  11. cwt3d-1.0.0/docs/benchmarks/coupling.csv +7 -0
  12. cwt3d-1.0.0/docs/benchmarks/coupling.json +516 -0
  13. cwt3d-1.0.0/docs/benchmarks/finite.csv +4 -0
  14. cwt3d-1.0.0/docs/benchmarks/finite.json +507 -0
  15. cwt3d-1.0.0/docs/benchmarks/historical-3bccd999a638/coupling.csv +7 -0
  16. cwt3d-1.0.0/docs/benchmarks/historical-3bccd999a638/coupling.json +516 -0
  17. cwt3d-1.0.0/docs/benchmarks/historical-3bccd999a638/finite.csv +4 -0
  18. cwt3d-1.0.0/docs/benchmarks/historical-3bccd999a638/finite.json +507 -0
  19. cwt3d-1.0.0/docs/benchmarks/historical-f8bd39fa2326/coupling.csv +7 -0
  20. cwt3d-1.0.0/docs/benchmarks/historical-f8bd39fa2326/coupling.json +516 -0
  21. cwt3d-1.0.0/docs/benchmarks/historical-f8bd39fa2326/finite.csv +4 -0
  22. cwt3d-1.0.0/docs/benchmarks/historical-f8bd39fa2326/finite.json +507 -0
  23. cwt3d-1.0.0/docs/examples/liang-70um/modes.csv +2 -0
  24. cwt3d-1.0.0/docs/examples/liang-70um/refinement.json +192 -0
  25. cwt3d-1.0.0/docs/examples/liang-70um/summary.json +194 -0
  26. cwt3d-1.0.0/docs/examples/liang-70um.md +151 -0
  27. cwt3d-1.0.0/docs/images/liang-70um.png +0 -0
  28. cwt3d-1.0.0/docs/literature_validation.md +84 -0
  29. cwt3d-1.0.0/docs/performance.md +126 -0
  30. cwt3d-1.0.0/docs/plans/v3-refactor-plan.md +156 -0
  31. cwt3d-1.0.0/docs/publishing.md +83 -0
  32. cwt3d-1.0.0/docs/usage.md +132 -0
  33. cwt3d-1.0.0/docs/validation.md +1615 -0
  34. cwt3d-1.0.0/examples/liang_70um.py +279 -0
  35. cwt3d-1.0.0/examples/plot_liang_70um.py +89 -0
  36. cwt3d-1.0.0/examples/square_finite.py +130 -0
  37. cwt3d-1.0.0/model/_Model.py +1009 -0
  38. cwt3d-1.0.0/pyproject.toml +31 -0
  39. cwt3d-1.0.0/scripts/check_release.py +221 -0
  40. cwt3d-1.0.0/scripts/package_delivery.py +133 -0
  41. cwt3d-1.0.0/scripts/validate_convergence.py +158 -0
  42. cwt3d-1.0.0/setup.cfg +4 -0
  43. cwt3d-1.0.0/src/cwt3d/__init__.py +9 -0
  44. cwt3d-1.0.0/src/cwt3d/cache.py +324 -0
  45. cwt3d-1.0.0/src/cwt3d/conventions.py +128 -0
  46. cwt3d-1.0.0/src/cwt3d/coupling.py +186 -0
  47. cwt3d-1.0.0/src/cwt3d/finite/__init__.py +16 -0
  48. cwt3d-1.0.0/src/cwt3d/finite/assembly.py +180 -0
  49. cwt3d-1.0.0/src/cwt3d/finite/eigensolver.py +443 -0
  50. cwt3d-1.0.0/src/cwt3d/finite/fields.py +205 -0
  51. cwt3d-1.0.0/src/cwt3d/finite/grid.py +236 -0
  52. cwt3d-1.0.0/src/cwt3d/finite/tracking.py +298 -0
  53. cwt3d-1.0.0/src/cwt3d/fourier.py +132 -0
  54. cwt3d-1.0.0/src/cwt3d/geometry.py +173 -0
  55. cwt3d-1.0.0/src/cwt3d/greens.py +542 -0
  56. cwt3d-1.0.0/src/cwt3d/infinite.py +32 -0
  57. cwt3d-1.0.0/src/cwt3d/quadrature.py +46 -0
  58. cwt3d-1.0.0/src/cwt3d/waveguide.py +302 -0
  59. cwt3d-1.0.0/src/cwt3d.egg-info/PKG-INFO +70 -0
  60. cwt3d-1.0.0/src/cwt3d.egg-info/SOURCES.txt +84 -0
  61. cwt3d-1.0.0/src/cwt3d.egg-info/dependency_links.txt +1 -0
  62. cwt3d-1.0.0/src/cwt3d.egg-info/requires.txt +14 -0
  63. cwt3d-1.0.0/src/cwt3d.egg-info/top_level.txt +1 -0
  64. cwt3d-1.0.0/tests/regressions/__init__.py +1 -0
  65. cwt3d-1.0.0/tests/regressions/_legacy.py +49 -0
  66. cwt3d-1.0.0/tests/regressions/test_chi_prefactor.py +53 -0
  67. cwt3d-1.0.0/tests/regressions/test_fourier_dft.py +55 -0
  68. cwt3d-1.0.0/tests/regressions/test_green_operator.py +58 -0
  69. cwt3d-1.0.0/tests/test_assembly.py +302 -0
  70. cwt3d-1.0.0/tests/test_backend_parity.py +105 -0
  71. cwt3d-1.0.0/tests/test_cache.py +372 -0
  72. cwt3d-1.0.0/tests/test_conventions.py +100 -0
  73. cwt3d-1.0.0/tests/test_coupling.py +226 -0
  74. cwt3d-1.0.0/tests/test_eigensolver.py +524 -0
  75. cwt3d-1.0.0/tests/test_end_to_end.py +66 -0
  76. cwt3d-1.0.0/tests/test_finite_modes.py +391 -0
  77. cwt3d-1.0.0/tests/test_fourier.py +180 -0
  78. cwt3d-1.0.0/tests/test_geometry.py +55 -0
  79. cwt3d-1.0.0/tests/test_greens_homogeneous.py +184 -0
  80. cwt3d-1.0.0/tests/test_greens_layered.py +250 -0
  81. cwt3d-1.0.0/tests/test_grid.py +208 -0
  82. cwt3d-1.0.0/tests/test_import.py +30 -0
  83. cwt3d-1.0.0/tests/test_liang_70um.py +170 -0
  84. cwt3d-1.0.0/tests/test_readme_example.py +54 -0
  85. cwt3d-1.0.0/tests/test_release_packaging.py +196 -0
  86. 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.
@@ -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
+ ![70 µm PCSEL envelope](https://raw.githubusercontent.com/maple367/3D-CWT/latest/docs/images/liang-70um.png)
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
+ ![Calculated envelope intensity in the 70 µm square cavity](docs/images/liang-70um.png)
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
+ ![70 µm PCSEL envelope](https://raw.githubusercontent.com/maple367/3D-CWT/latest/docs/images/liang-70um.png)
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)