pcb-analysis 0.6.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.
- pcb_analysis-0.6.0/LICENSE +21 -0
- pcb_analysis-0.6.0/PKG-INFO +546 -0
- pcb_analysis-0.6.0/README.md +492 -0
- pcb_analysis-0.6.0/pyproject.toml +67 -0
- pcb_analysis-0.6.0/setup.cfg +4 -0
- pcb_analysis-0.6.0/src/electrical/__init__.py +10 -0
- pcb_analysis-0.6.0/src/electrical/dice_peec/__init__.py +117 -0
- pcb_analysis-0.6.0/src/electrical/dice_peec/_bench_utils.py +70 -0
- pcb_analysis-0.6.0/src/electrical/dice_peec/backends.py +62 -0
- pcb_analysis-0.6.0/src/electrical/dice_peec/cli/__init__.py +5 -0
- pcb_analysis-0.6.0/src/electrical/dice_peec/cli/calibrate.py +64 -0
- pcb_analysis-0.6.0/src/electrical/dice_peec/controller.py +445 -0
- pcb_analysis-0.6.0/src/electrical/dice_peec/cuda_delta.py +213 -0
- pcb_analysis-0.6.0/src/electrical/dice_peec/cuda_pypeec.py +669 -0
- pcb_analysis-0.6.0/src/electrical/dice_peec/cupy_calibrator.py +73 -0
- pcb_analysis-0.6.0/src/electrical/dice_peec/delta_peec.py +113 -0
- pcb_analysis-0.6.0/src/electrical/dice_peec/layout_ops.py +216 -0
- pcb_analysis-0.6.0/src/electrical/dice_peec/lowmem_25d.py +143 -0
- pcb_analysis-0.6.0/src/electrical/dice_peec/lowmem_peec.py +165 -0
- pcb_analysis-0.6.0/src/electrical/dice_peec/multilayer_peec.py +608 -0
- pcb_analysis-0.6.0/src/electrical/dice_peec/plane_opt_contract.py +593 -0
- pcb_analysis-0.6.0/src/electrical/dice_peec/pypeec_memory.py +252 -0
- pcb_analysis-0.6.0/src/electrical/dice_peec/sheet_cuda.py +687 -0
- pcb_analysis-0.6.0/src/electrical/dice_peec/sheet_inductance.py +545 -0
- pcb_analysis-0.6.0/src/electrical/dice_peec/sheet_operator.py +322 -0
- pcb_analysis-0.6.0/src/electrical/dice_peec/sheet_peec.py +676 -0
- pcb_analysis-0.6.0/src/electrical/dice_peec/sheet_results.py +199 -0
- pcb_analysis-0.6.0/src/electrical/dice_peec/skin_filaments.py +387 -0
- pcb_analysis-0.6.0/src/electrical/dice_peec/stackup.py +102 -0
- pcb_analysis-0.6.0/src/electrical/matrix_free_mpir_fem/__init__.py +80 -0
- pcb_analysis-0.6.0/src/electrical/matrix_free_mpir_fem/cuda.py +156 -0
- pcb_analysis-0.6.0/src/electrical/matrix_free_mpir_fem/frequency_domain.py +505 -0
- pcb_analysis-0.6.0/src/electrical/matrix_free_mpir_fem/pcb.py +480 -0
- pcb_analysis-0.6.0/src/electrical/matrix_free_mpir_fem/runtime.py +314 -0
- pcb_analysis-0.6.0/src/electrical/matrix_free_mpir_fem/solver.py +468 -0
- pcb_analysis-0.6.0/src/electrical/py.typed +1 -0
- pcb_analysis-0.6.0/src/emc/__init__.py +11 -0
- pcb_analysis-0.6.0/src/emc/py.typed +0 -0
- pcb_analysis-0.6.0/src/emc/tiled_dipole_superposition/__init__.py +74 -0
- pcb_analysis-0.6.0/src/emc/tiled_dipole_superposition/far_field.py +204 -0
- pcb_analysis-0.6.0/src/emc/tiled_dipole_superposition/fields.py +241 -0
- pcb_analysis-0.6.0/src/emc/tiled_dipole_superposition/limits.py +168 -0
- pcb_analysis-0.6.0/src/emc/tiled_dipole_superposition/moments.py +86 -0
- pcb_analysis-0.6.0/src/emc/tiled_dipole_superposition/sources.py +188 -0
- pcb_analysis-0.6.0/src/multiphysics/__init__.py +11 -0
- pcb_analysis-0.6.0/src/multiphysics/py.typed +0 -0
- pcb_analysis-0.6.0/src/multiphysics/staggered_coupling/__init__.py +71 -0
- pcb_analysis-0.6.0/src/multiphysics/staggered_coupling/electro_thermal.py +373 -0
- pcb_analysis-0.6.0/src/multiphysics/staggered_coupling/emission.py +192 -0
- pcb_analysis-0.6.0/src/multiphysics/staggered_coupling/scenarios.py +213 -0
- pcb_analysis-0.6.0/src/pcb_analysis.egg-info/PKG-INFO +546 -0
- pcb_analysis-0.6.0/src/pcb_analysis.egg-info/SOURCES.txt +83 -0
- pcb_analysis-0.6.0/src/pcb_analysis.egg-info/dependency_links.txt +1 -0
- pcb_analysis-0.6.0/src/pcb_analysis.egg-info/entry_points.txt +2 -0
- pcb_analysis-0.6.0/src/pcb_analysis.egg-info/requires.txt +9 -0
- pcb_analysis-0.6.0/src/pcb_analysis.egg-info/top_level.txt +4 -0
- pcb_analysis-0.6.0/src/thermal/__init__.py +11 -0
- pcb_analysis-0.6.0/src/thermal/matrix_free_mpir_fem/__init__.py +38 -0
- pcb_analysis-0.6.0/src/thermal/matrix_free_mpir_fem/conduction.py +847 -0
- pcb_analysis-0.6.0/src/thermal/matrix_free_mpir_fem/coupling.py +98 -0
- pcb_analysis-0.6.0/src/thermal/matrix_free_mpir_fem/cuda.py +160 -0
- pcb_analysis-0.6.0/src/thermal/matrix_free_mpir_fem/two_level.py +216 -0
- pcb_analysis-0.6.0/src/thermal/py.typed +0 -0
- pcb_analysis-0.6.0/tests/test_controller.py +133 -0
- pcb_analysis-0.6.0/tests/test_cuda.py +563 -0
- pcb_analysis-0.6.0/tests/test_cuda_matrix_free_mpir_fem.py +137 -0
- pcb_analysis-0.6.0/tests/test_cuda_thermal_conduction.py +104 -0
- pcb_analysis-0.6.0/tests/test_delta_peec.py +39 -0
- pcb_analysis-0.6.0/tests/test_electrothermal_coupling.py +129 -0
- pcb_analysis-0.6.0/tests/test_emc_dipole_superposition.py +192 -0
- pcb_analysis-0.6.0/tests/test_emc_sources.py +121 -0
- pcb_analysis-0.6.0/tests/test_fp32_accuracy.py +86 -0
- pcb_analysis-0.6.0/tests/test_frequency_domain_fem.py +201 -0
- pcb_analysis-0.6.0/tests/test_matrix_free_mpir_fem.py +132 -0
- pcb_analysis-0.6.0/tests/test_multilayer_peec.py +318 -0
- pcb_analysis-0.6.0/tests/test_peec_fem_comparison.py +48 -0
- pcb_analysis-0.6.0/tests/test_plane_opt_contract.py +238 -0
- pcb_analysis-0.6.0/tests/test_pypeec_memory.py +140 -0
- pcb_analysis-0.6.0/tests/test_sheet_cuda_device.py +75 -0
- pcb_analysis-0.6.0/tests/test_sheet_inductance.py +181 -0
- pcb_analysis-0.6.0/tests/test_sheet_peec.py +625 -0
- pcb_analysis-0.6.0/tests/test_sheet_results.py +211 -0
- pcb_analysis-0.6.0/tests/test_skin_filaments.py +181 -0
- pcb_analysis-0.6.0/tests/test_staggered_coupling.py +232 -0
- pcb_analysis-0.6.0/tests/test_thermal_conduction.py +329 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hiroyuki Yamada
|
|
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,546 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: pcb-analysis
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: CUDA-accelerated PEEC, matrix-free FEM, and radiated-emission evaluation for electrical, thermal, and EMC PCB analysis
|
|
5
|
+
Author-email: Hiroyuki Yamada <H44bcda@gmail.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Hiroyuki Yamada
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/AFLOY/pcb-analysis
|
|
29
|
+
Project-URL: Repository, https://github.com/AFLOY/pcb-analysis
|
|
30
|
+
Project-URL: Issues, https://github.com/AFLOY/pcb-analysis/issues
|
|
31
|
+
Keywords: peec,fem,maxwell,skin-effect,thermal,heat-conduction,emc,radiated-emission,near-field,multiphysics,electrothermal,pcb,pdn,electromagnetics,cuda,optimization
|
|
32
|
+
Classifier: Development Status :: 3 - Alpha
|
|
33
|
+
Classifier: Intended Audience :: Science/Research
|
|
34
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
35
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
36
|
+
Classifier: Programming Language :: Python :: 3
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
40
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
41
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
42
|
+
Classifier: Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)
|
|
43
|
+
Classifier: Typing :: Typed
|
|
44
|
+
Requires-Python: >=3.11
|
|
45
|
+
Description-Content-Type: text/markdown
|
|
46
|
+
License-File: LICENSE
|
|
47
|
+
Requires-Dist: numpy<2.4,>=1.26
|
|
48
|
+
Requires-Dist: scipy<1.18,>=1.11
|
|
49
|
+
Provides-Extra: cuda
|
|
50
|
+
Requires-Dist: cupy-cuda13x<15,>=14; extra == "cuda"
|
|
51
|
+
Requires-Dist: pypeec==5.8.0; extra == "cuda"
|
|
52
|
+
Provides-Extra: test
|
|
53
|
+
Requires-Dist: pytest<9,>=8; extra == "test"
|
|
54
|
+
|
|
55
|
+
# Accelerated electrical, thermal, EMC, and coupled PCB analysis
|
|
56
|
+
|
|
57
|
+
[](https://github.com/AFLOY/pcb-analysis/actions/workflows/ci.yml)
|
|
58
|
+
[](https://opensource.org/licenses/MIT)
|
|
59
|
+
[](https://www.python.org/downloads/)
|
|
60
|
+
|
|
61
|
+
Accelerated PEEC and matrix-free FEM solvers for electrical and thermal PCB
|
|
62
|
+
analysis, radiated-emission evaluation for EMC, and PDN optimization.
|
|
63
|
+
|
|
64
|
+
The repository holds two solver families under the `electrical` package, one
|
|
65
|
+
under the `thermal` package, one evaluation front end under `emc`, and the
|
|
66
|
+
coupled scenarios that chain them under `multiphysics`. `electrical.dice_peec` provides exact incremental
|
|
67
|
+
delta-scoring for local PCB reroutes, a 2.5D multilayer interaction operator, a
|
|
68
|
+
thin-sheet PEEC field solver, and an adaptive runtime controller for
|
|
69
|
+
topology-optimization loops. `electrical.matrix_free_mpir_fem` provides a
|
|
70
|
+
matrix-free Q1 finite-element solver for DC conduction and 2D frequency-domain
|
|
71
|
+
Maxwell fields, driven by mixed-precision iterative refinement (MPIR) on NumPy
|
|
72
|
+
or CuPy. `thermal.matrix_free_mpir_fem` solves steady heat conduction through
|
|
73
|
+
the whole board stack with the same MPIR solver and runtimes, and takes the
|
|
74
|
+
Joule loss of an electrical solve as its heat load.
|
|
75
|
+
`emc.tiled_dipole_superposition` reads the current distribution of either
|
|
76
|
+
electrical solve as Hertzian dipoles and evaluates near-field scans, far-field
|
|
77
|
+
patterns, radiated power, and margins to CISPR 32 and FCC Part 15 limits.
|
|
78
|
+
`multiphysics.staggered_coupling` runs the scenarios that couple them: the
|
|
79
|
+
electro-thermal fixed point with temperature-dependent copper, and the emission
|
|
80
|
+
of a cold or thermally converged current distribution.
|
|
81
|
+
|
|
82
|
+
## Features
|
|
83
|
+
|
|
84
|
+
- **Exact sparse delta scoring** — `O(s)` + `O(s²)` incremental evaluation instead of `O(N log N)` full FFT per candidate
|
|
85
|
+
- **2.5D multilayer operator** — planar FFT with interlayer kernel matrix and frequency-dependent via model
|
|
86
|
+
- **CUDA acceleration** — CuPy/cuFFT backend with RawKernel batched scoring
|
|
87
|
+
- **Adaptive controller** — memory-bounded, error-driven fidelity cascade with automatic OOM recovery
|
|
88
|
+
- **Low-memory mode** — near/far field splitting for 4–8 GB VRAM devices
|
|
89
|
+
- **Matrix-free MPIR-FEM** — Q1 PCB conduction FEM with an FP32 inner PCG and
|
|
90
|
+
FP64 outer reliable updates
|
|
91
|
+
- **Frequency-domain Maxwell** — 2D scalar-polarisation wave, dielectric-loss,
|
|
92
|
+
eddy-current, and skin-effect solve with complex64 inner GMRES and complex128
|
|
93
|
+
reliable updates
|
|
94
|
+
- **CUDA matrix-free FEM** — fused, node-owned Q1 gather kernel plus batched
|
|
95
|
+
complex64 GMRES; no assembled matrix and no global atomics
|
|
96
|
+
- **Accelerator boundary** — NumPy/CuPy runtimes with a narrow low-precision
|
|
97
|
+
interface that remains suitable for the later Tenstorrent port
|
|
98
|
+
- **Thermal MPIR-FEM** — matrix-free trilinear hexahedral heat conduction
|
|
99
|
+
through the copper/laminate stack with anisotropic laminate conductivity,
|
|
100
|
+
convective faces, fixed-temperature nodes, and a closed heat budget
|
|
101
|
+
- **Electrothermal coupling** — per-element and per-via Joule loss from the DC
|
|
102
|
+
solve mapped onto the thermal stack without interpolation
|
|
103
|
+
- **Radiated emissions** — exact Hertzian-dipole superposition of the solved
|
|
104
|
+
currents for near-field scans and far-field patterns, radiated power,
|
|
105
|
+
dipole-moment proxies, PEC ground-plane images, and margins to CISPR 32 /
|
|
106
|
+
FCC Part 15 limit lines, tiled on NumPy or CuPy
|
|
107
|
+
- **Coupled scenarios** — staggered electro-thermal iteration with `σ(T)` and
|
|
108
|
+
via `R(T)`, warm starts and Aitken relaxation; electro-emission and
|
|
109
|
+
electro-thermal-emission chains; one `run_scenario` entry point
|
|
110
|
+
|
|
111
|
+
## Installation
|
|
112
|
+
|
|
113
|
+
### From PyPI
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
pip install pcb-analysis
|
|
117
|
+
# with CuPy and PyPEEC
|
|
118
|
+
pip install 'pcb-analysis[cuda]'
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Each release is uploaded to [PyPI](https://pypi.org/project/pcb-analysis/)
|
|
122
|
+
by the `Release` workflow with Trusted Publishing, and the same wheel, source
|
|
123
|
+
distribution, and SHA-256 checksums are attached to the GitHub release. To
|
|
124
|
+
install a specific release without PyPI, point pip at the release asset or
|
|
125
|
+
at the tag:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
pip install https://github.com/AFLOY/pcb-analysis/releases/download/v0.6.0/pcb_analysis-0.6.0-py3-none-any.whl
|
|
129
|
+
pip install 'git+https://github.com/AFLOY/pcb-analysis.git@v0.6.0'
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The release tag has to match the version in `pyproject.toml`.
|
|
133
|
+
|
|
134
|
+
### From a checkout, CPU only
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
pip install -e .
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### With CUDA support
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
pip install -e '.[cuda]'
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
> **Note**: Requires an NVIDIA driver and CUDA 13.x. Verify with `nvidia-smi`.
|
|
147
|
+
> The package is installed as `electrical`; the former top-level
|
|
148
|
+
> `peec_fastopt` package now lives at `electrical.dice_peec`, and the thermal
|
|
149
|
+
> and EMC front ends and the coupled scenarios are the separate top-level
|
|
150
|
+
> packages `thermal`, `emc`, and `multiphysics`. Re-run the editable install
|
|
151
|
+
> after pulling these changes so the new packages are importable and the old
|
|
152
|
+
> path is not left on `sys.path`.
|
|
153
|
+
|
|
154
|
+
## Quick start
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from electrical.dice_peec import (
|
|
158
|
+
Stackup,
|
|
159
|
+
FFTInteraction25D,
|
|
160
|
+
MultilayerDeltaScorer,
|
|
161
|
+
CandidateEdit,
|
|
162
|
+
SegmentOp,
|
|
163
|
+
ViaOp,
|
|
164
|
+
compile_candidate,
|
|
165
|
+
)
|
|
166
|
+
import numpy as np
|
|
167
|
+
|
|
168
|
+
stack = Stackup.dual_sided(board_thickness_mm=1.6)
|
|
169
|
+
shape = (64, 64)
|
|
170
|
+
base = np.zeros((stack.n_layers, *shape))
|
|
171
|
+
base[0, 10:20, 10:30] = 1.0
|
|
172
|
+
|
|
173
|
+
op = FFTInteraction25D(shape, stack, cell_size_m=0.2e-3)
|
|
174
|
+
scorer = MultilayerDeltaScorer(op, base, frequency_hz=3e5)
|
|
175
|
+
|
|
176
|
+
edit = CandidateEdit(
|
|
177
|
+
segments=[SegmentOp("add", "B.Cu", ((12, 12), (12, 13), (12, 14)))],
|
|
178
|
+
vias=[ViaOp("add", 12, 14, "F.Cu", "B.Cu", important=True)],
|
|
179
|
+
)
|
|
180
|
+
compiled = compile_candidate(edit, stack)
|
|
181
|
+
score = scorer.energy(compiled.occupancy_delta, vias=compiled.vias)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Matrix-free PCB conduction
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
import numpy as np
|
|
188
|
+
from electrical.matrix_free_mpir_fem import (
|
|
189
|
+
CurrentTerminal,
|
|
190
|
+
LayeredPCBMesh,
|
|
191
|
+
PCBConductionProblem,
|
|
192
|
+
solve_pcb_dc,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
mesh = LayeredPCBMesh(
|
|
196
|
+
element_active=np.ones((1, 1, 32), dtype=bool),
|
|
197
|
+
layer_thickness_m=(35e-6,),
|
|
198
|
+
pitch_x_m=0.2e-3,
|
|
199
|
+
pitch_y_m=0.2e-3,
|
|
200
|
+
)
|
|
201
|
+
problem = PCBConductionProblem(
|
|
202
|
+
mesh=mesh,
|
|
203
|
+
terminals=(
|
|
204
|
+
CurrentTerminal(((0, 0, 0), (0, 1, 0)), 1.0, "source"),
|
|
205
|
+
CurrentTerminal(((0, 0, 32), (0, 1, 32)), -1.0, "sink"),
|
|
206
|
+
),
|
|
207
|
+
reference_node=(0, 0, 32),
|
|
208
|
+
)
|
|
209
|
+
solution = solve_pcb_dc(problem)
|
|
210
|
+
assert solution.solve.converged
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Frequency-domain Maxwell
|
|
214
|
+
|
|
215
|
+
The frequency-domain front end solves the `E_z` Maxwell reduction for
|
|
216
|
+
z-invariant geometries and reports electric field, magnetic field,
|
|
217
|
+
eddy-current density, conductor loss, and dielectric loss. Dirichlet nodes
|
|
218
|
+
are given by a boolean mask over the node grid plus the field value held at
|
|
219
|
+
each masked node.
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
import numpy as np
|
|
223
|
+
from electrical.matrix_free_mpir_fem import (
|
|
224
|
+
ScalarMaxwellMesh2D,
|
|
225
|
+
ScalarMaxwellProblem,
|
|
226
|
+
solve_scalar_maxwell,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
mesh = ScalarMaxwellMesh2D(
|
|
230
|
+
(8, 64), # elements: rows x columns
|
|
231
|
+
pitch_x_m=0.1e-3,
|
|
232
|
+
pitch_y_m=0.1e-3,
|
|
233
|
+
relative_permittivity=4.0,
|
|
234
|
+
dielectric_loss_tangent=0.01,
|
|
235
|
+
)
|
|
236
|
+
mask = np.zeros(mesh.node_shape, dtype=bool)
|
|
237
|
+
mask[:, 0] = mask[:, -1] = True # drive the left edge, ground the right
|
|
238
|
+
value = np.zeros(mesh.node_shape, dtype=np.complex128)
|
|
239
|
+
value[:, 0] = 1.0
|
|
240
|
+
problem = ScalarMaxwellProblem(mesh, 1e9, mask, value)
|
|
241
|
+
|
|
242
|
+
solution = solve_scalar_maxwell(problem) # NumPy complex64 inner solve
|
|
243
|
+
assert solution.solve.converged
|
|
244
|
+
loss_w_per_m = solution.dielectric_loss_w_per_m
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### CUDA full-wave solve
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
solution = solve_scalar_maxwell(
|
|
251
|
+
problem,
|
|
252
|
+
backend="cuda", # or "auto" to fall back to NumPy when no GPU is visible
|
|
253
|
+
device_id=0,
|
|
254
|
+
)
|
|
255
|
+
assert solution.solve.low_runtime == "cupy-complex64"
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
The complex128 reliable residual stays on the CPU. The correction RHS crosses
|
|
259
|
+
to CUDA once per outer iteration, while the complex64 Krylov basis,
|
|
260
|
+
preconditioner, material coefficients, and repeated Q1 actions remain on the
|
|
261
|
+
GPU. On the repository's GTX 1650 audit, the fused operator is 8.8–40.4×
|
|
262
|
+
faster than the NumPy complex64 action for 4,225–66,049 unknowns. The complete
|
|
263
|
+
solve is slower at 4,369 unknowns (0.85×) but 1.99× faster at 16,705 unknowns,
|
|
264
|
+
so CUDA should not be selected solely for tiny meshes.
|
|
265
|
+
|
|
266
|
+
### Steady thermal conduction
|
|
267
|
+
|
|
268
|
+
The thermal front end meshes the whole stack as slabs of hexahedral Q1
|
|
269
|
+
elements. Copper, laminate, and vias are element conductivities; laminates may
|
|
270
|
+
have a different through-plane value. Heat enters per element or per node and
|
|
271
|
+
leaves through convective faces and fixed-temperature nodes. The solution
|
|
272
|
+
reports nodal temperature, element heat flux, and a heat budget whose error is
|
|
273
|
+
the solver residual.
|
|
274
|
+
|
|
275
|
+
```python
|
|
276
|
+
import numpy as np
|
|
277
|
+
from thermal.matrix_free_mpir_fem import (
|
|
278
|
+
ConvectionBoundary,
|
|
279
|
+
HeatSource,
|
|
280
|
+
LayeredThermalMesh,
|
|
281
|
+
ThermalConductionProblem,
|
|
282
|
+
solve_thermal_conduction,
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
# 35 µm copper / 1.5 mm FR-4 / 35 µm copper, 50 mm x 50 mm on a 0.5 mm grid.
|
|
286
|
+
mesh = LayeredThermalMesh(
|
|
287
|
+
slab_thickness_m=(35e-6, 1.5e-3, 35e-6),
|
|
288
|
+
pitch_x_m=0.5e-3,
|
|
289
|
+
pitch_y_m=0.5e-3,
|
|
290
|
+
conductivity_w_per_m_k=(385.0, 0.8, 385.0),
|
|
291
|
+
through_plane_conductivity_w_per_m_k=(385.0, 0.3, 385.0),
|
|
292
|
+
element_shape=(100, 100),
|
|
293
|
+
)
|
|
294
|
+
ambient = 298.15
|
|
295
|
+
problem = ThermalConductionProblem(
|
|
296
|
+
mesh,
|
|
297
|
+
convection=(
|
|
298
|
+
ConvectionBoundary("top", 10.0, ambient),
|
|
299
|
+
ConvectionBoundary("bottom", 10.0, ambient),
|
|
300
|
+
),
|
|
301
|
+
heat_sources=(HeatSource(((3, 50, 50), (3, 50, 51)), 0.5, "regulator"),),
|
|
302
|
+
)
|
|
303
|
+
solution = solve_thermal_conduction(problem, initial_temperature_k=ambient)
|
|
304
|
+
assert solution.solve.converged
|
|
305
|
+
hottest_k = solution.max_temperature_k
|
|
306
|
+
assert abs(solution.heat_balance_error_w) < 1e-9
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
The default preconditioner adds a patch-constant coarse correction to Jacobi
|
|
310
|
+
scaling. A cooled copper plate is stiff in-plane and weakly coupled to the air,
|
|
311
|
+
so plain Jacobi PCG needs hundreds of inner iterations per decade on such a
|
|
312
|
+
stack; the two-level variant needs tens. Pass `preconditioner="jacobi"` to
|
|
313
|
+
compare, and `coarse_block_nodes` to set the patch width.
|
|
314
|
+
|
|
315
|
+
### Electrothermal coupling
|
|
316
|
+
|
|
317
|
+
`element_joule_heat_w` places the per-element copper loss of a
|
|
318
|
+
`PCBConductionSolution` onto the thermal slabs that hold each electrical layer,
|
|
319
|
+
and `via_joule_heat_sources` turns each via's loss into nodal heat at its
|
|
320
|
+
endpoints. Both meshes must share the in-plane element grid.
|
|
321
|
+
|
|
322
|
+
```python
|
|
323
|
+
from thermal.matrix_free_mpir_fem import element_joule_heat_w, via_joule_heat_sources
|
|
324
|
+
|
|
325
|
+
electrical = solve_pcb_dc(pcb_problem) # copper on layers 0 and 1
|
|
326
|
+
layer_slabs = (0, 2) # bottom and top copper slabs
|
|
327
|
+
thermal_problem = ThermalConductionProblem(
|
|
328
|
+
mesh,
|
|
329
|
+
convection=(ConvectionBoundary("top", 10.0, ambient),),
|
|
330
|
+
element_heat_w=element_joule_heat_w(electrical, mesh, layer_slabs),
|
|
331
|
+
heat_sources=via_joule_heat_sources(pcb_problem, electrical, mesh, layer_slabs),
|
|
332
|
+
)
|
|
333
|
+
solution = solve_thermal_conduction(thermal_problem, backend="auto")
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
The thermal operator runs its FP32 inner PCG on CuPy with `backend="cuda"`; the
|
|
337
|
+
FP64 reliable update stays on the host exactly as for the electrical solves.
|
|
338
|
+
|
|
339
|
+
### Radiated emissions
|
|
340
|
+
|
|
341
|
+
Every current element of a solved board is a Hertzian dipole. The EMC front end
|
|
342
|
+
sums their exact fields for a near-field scan and their far-zone terms for the
|
|
343
|
+
pattern at a test-site distance, then compares the maximum with a limit line.
|
|
344
|
+
|
|
345
|
+
```python
|
|
346
|
+
import numpy as np
|
|
347
|
+
from emc.tiled_dipole_superposition import (
|
|
348
|
+
CISPR32_CLASS_B,
|
|
349
|
+
dipole_moments,
|
|
350
|
+
dipoles_from_pcb_dc,
|
|
351
|
+
emission_margin,
|
|
352
|
+
evaluate_fields,
|
|
353
|
+
far_field_pattern,
|
|
354
|
+
scan_plane,
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
electrical = solve_pcb_dc(pcb_problem) # two copper layers
|
|
358
|
+
dipoles = dipoles_from_pcb_dc(
|
|
359
|
+
pcb_problem, electrical, layer_height_m=(0.0, 1.6e-3), close_terminals=True
|
|
360
|
+
)
|
|
361
|
+
frequency_hz = 100e6 # DC pattern used as a phasor
|
|
362
|
+
|
|
363
|
+
probe = scan_plane(np.linspace(0, 0.05, 50), np.linspace(0, 0.02, 20), z_m=6.6e-3)
|
|
364
|
+
near = evaluate_fields(dipoles, probe, frequency_hz, backend="auto")
|
|
365
|
+
h_max_a_per_m = near.magnetic_magnitude_a_per_m.max()
|
|
366
|
+
|
|
367
|
+
pattern = far_field_pattern(dipoles, frequency_hz, distance_m=10.0)
|
|
368
|
+
margin = emission_margin(
|
|
369
|
+
pattern.max_polarised_field_v_per_m, frequency_hz, CISPR32_CLASS_B, distance_m=10.0
|
|
370
|
+
)
|
|
371
|
+
print(pattern.radiated_power_w, margin.predicted_dbuv_per_m, margin.margin_db)
|
|
372
|
+
|
|
373
|
+
moments = dipole_moments(dipoles, frequency_hz) # |P| ≈ 0 once the loop closes
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
`dipole_moments` reports the net electric moment: a non-zero value for a board
|
|
377
|
+
whose terminal currents balance means the return path through the component is
|
|
378
|
+
not in the model, and `close_terminals=True` adds it as a straight element.
|
|
379
|
+
The magnetic near field and the far field are the robust outputs; the electric
|
|
380
|
+
near field of a current-only description is sensitive to how continuously the
|
|
381
|
+
element chain closes. `dipoles_from_sheet_peec` does the same for a
|
|
382
|
+
frequency-resolved sheet-PEEC solve, without the quasi-static assumption.
|
|
383
|
+
|
|
384
|
+
### Coupled scenarios
|
|
385
|
+
|
|
386
|
+
Scenario dataclasses name what is coupled to what; `run_scenario` dispatches on
|
|
387
|
+
the type. The electro-thermal chain iterates the two conduction solves until
|
|
388
|
+
the copper conductivity `σ(T) = σ_ref / (1 + α (T − T_ref))` is self-consistent
|
|
389
|
+
with the temperature it produces, warm-starting both solves and relaxing the
|
|
390
|
+
temperature update with Aitken's Δ² estimate.
|
|
391
|
+
|
|
392
|
+
```python
|
|
393
|
+
from multiphysics.staggered_coupling import (
|
|
394
|
+
ElectroThermalEmissionScenario,
|
|
395
|
+
ElectroThermalScenario,
|
|
396
|
+
EmissionScenario,
|
|
397
|
+
run_scenario,
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
coupled_scenario = ElectroThermalScenario(
|
|
401
|
+
pcb_problem, # PCBConductionProblem, conductivity at 293.15 K
|
|
402
|
+
thermal_mesh, # LayeredThermalMesh on the same (rows, cols)
|
|
403
|
+
layer_slabs=(0, 2), # thermal slab holding each copper layer
|
|
404
|
+
convection=(ConvectionBoundary("top", 10.0, 298.15), ConvectionBoundary("bottom", 10.0, 298.15)),
|
|
405
|
+
)
|
|
406
|
+
coupled = run_scenario(coupled_scenario)
|
|
407
|
+
print(coupled.converged, coupled.iterations, coupled.loss_increase_ratio)
|
|
408
|
+
|
|
409
|
+
chained = run_scenario(
|
|
410
|
+
ElectroThermalEmissionScenario(
|
|
411
|
+
coupled_scenario,
|
|
412
|
+
layer_height_m=(0.0, 1.6e-3),
|
|
413
|
+
emission=EmissionScenario((30e6, 100e6, 300e6)), # CISPR 32 Class B at 10 m
|
|
414
|
+
)
|
|
415
|
+
)
|
|
416
|
+
print(chained.emission.margin_db, chained.heating_shift_db)
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
`ElectricalScenario`, `ThermalScenario`, `ElectroEmissionScenario`, and
|
|
420
|
+
`SheetPeecEmissionScenario` (one sheet-PEEC solve per frequency) complete the
|
|
421
|
+
set; `run_scenarios` runs a list. See
|
|
422
|
+
[MULTIPHYSICS_SCENARIOS.md](docs/MULTIPHYSICS_SCENARIOS.md).
|
|
423
|
+
|
|
424
|
+
## Project structure
|
|
425
|
+
|
|
426
|
+
```text
|
|
427
|
+
src/
|
|
428
|
+
electrical/ Analysis target
|
|
429
|
+
dice_peec/ PEEC + DICE/2.5D FFT acceleration
|
|
430
|
+
matrix_free_mpir_fem/ FEM + matrix-free/MPIR acceleration
|
|
431
|
+
thermal/ Analysis target
|
|
432
|
+
matrix_free_mpir_fem/ Heat-conduction FEM + matrix-free/MPIR acceleration
|
|
433
|
+
emc/ Analysis target
|
|
434
|
+
tiled_dipole_superposition/ Radiated emission by dipole superposition, tiled NumPy/CuPy
|
|
435
|
+
multiphysics/ Coupled analysis
|
|
436
|
+
staggered_coupling/ Partitioned fixed-point coupling with warm starts and Aitken relaxation
|
|
437
|
+
tests/ Test suite (pytest)
|
|
438
|
+
docs/ Design documents, validation reports, raw results
|
|
439
|
+
examples/ Demo scripts and benchmarks
|
|
440
|
+
experiments/ Repeatable accuracy and timing audits
|
|
441
|
+
requirements.txt Editable install with CUDA/test/build tooling
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
Future physics should follow the same rule: the first level names the analysis
|
|
445
|
+
target and the second names the method plus its acceleration strategy.
|
|
446
|
+
|
|
447
|
+
## Electrical solvers
|
|
448
|
+
|
|
449
|
+
`cuda_pypeec` runs PyPEEC's own three-dimensional voxel solve on CUDA. PyPEEC
|
|
450
|
+
owns the physics; this owns the device policy. `pypeec_memory` predicts what a
|
|
451
|
+
model costs before it is attempted, which matters once a model spans a board's
|
|
452
|
+
height instead of one copper layer: on a two-sided 34.7mm board the prepared
|
|
453
|
+
operators go from 4.5 MiB to 541 MiB while the conductor merely doubles.
|
|
454
|
+
|
|
455
|
+
`sheet_peec` solves the same physics on a mesh built for what a PCB is -- a few
|
|
456
|
+
thin sheets at heights the stackup states. Its inductance operator is a
|
|
457
|
+
two-dimensional transform per layer pair rather than a three-dimensional one
|
|
458
|
+
over the board's height, which brings the same board's operators to 5.6 MiB.
|
|
459
|
+
At zero frequency it reproduces an independent resistor network to machine
|
|
460
|
+
precision; above it, the transform path matches a dense assembly of the same
|
|
461
|
+
operator. See [docs/SHEET_PEEC.md](docs/SHEET_PEEC.md).
|
|
462
|
+
|
|
463
|
+
`multilayer_peec` is neither. It is a scalar interaction proxy for ranking many
|
|
464
|
+
candidate shapes cheaply, and it solves for no current or potential.
|
|
465
|
+
|
|
466
|
+
`skin_filaments` extends `sheet_peec` to conductors thick against the skin
|
|
467
|
+
depth by cutting them into graded filaments joined through the thickness, so
|
|
468
|
+
the solve itself decides how the current divides between the faces and the
|
|
469
|
+
interior.
|
|
470
|
+
|
|
471
|
+
`matrix_free_mpir_fem` solves real SPD DC conduction on layered Q1 PCB meshes
|
|
472
|
+
and complex 2D scalar-polarised frequency-domain Maxwell fields. Its global
|
|
473
|
+
matrix is never assembled. Low-precision inner corrections contain most
|
|
474
|
+
operator applications, while high-precision host residuals determine final
|
|
475
|
+
convergence. Scope and the Blackhole port boundary are documented in
|
|
476
|
+
[docs/MATRIX_FREE_MPIR_FEM.md](docs/MATRIX_FREE_MPIR_FEM.md).
|
|
477
|
+
|
|
478
|
+
## Experiments and audits
|
|
479
|
+
|
|
480
|
+
The scripts in `experiments/` regenerate the numbers quoted in `docs/`. They
|
|
481
|
+
run from the repository root because the FP32 audit is also imported by the
|
|
482
|
+
test suite. Results default to the ignored `benchmark-results/` directory;
|
|
483
|
+
pass `--output` (or `--json` for the FP32 audit) to write elsewhere.
|
|
484
|
+
|
|
485
|
+
```bash
|
|
486
|
+
# What single precision costs the sheet-PEEC solver (host stage, then CUDA)
|
|
487
|
+
python experiments/fp32_accuracy.py --cells 128 --json benchmark-results/fp32.json
|
|
488
|
+
|
|
489
|
+
# Maxwell accuracy against closed-form 1D solutions, plus CPU timing
|
|
490
|
+
python experiments/maxwell_small_benchmark.py
|
|
491
|
+
|
|
492
|
+
# Fused CUDA operator throughput and complete-solve timing (needs a GPU)
|
|
493
|
+
python experiments/maxwell_cuda_benchmark.py --operator-sides 64 128 256
|
|
494
|
+
|
|
495
|
+
# Sheet PEEC versus matrix-free FEM on matched DC strips and a skin-effect slab.
|
|
496
|
+
# Both methods solve both scenarios; the PEEC skin bar takes ~90 s at 64 cells.
|
|
497
|
+
python experiments/peec_fem_comparison.py --no-cuda --peec-skin-lengths 16 32
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
The skin-effect scenario is the same 0.5 mm copper slab at 1 MHz for both
|
|
501
|
+
methods. FEM solves the field through the slab's thickness. Sheet PEEC has no
|
|
502
|
+
infinite slab, so it solves a bar of graded filaments and reads the AC/DC
|
|
503
|
+
resistance ratio from the filament current division at the middle of the bar,
|
|
504
|
+
where it is slab-like. The closed-form slab impedance is the shared reference.
|
|
505
|
+
|
|
506
|
+
## Development
|
|
507
|
+
|
|
508
|
+
```bash
|
|
509
|
+
# CPU-only, as CI runs it
|
|
510
|
+
pip install -e '.[test]'
|
|
511
|
+
|
|
512
|
+
# Or: the ignored local environment with CUDA, test, and build tooling
|
|
513
|
+
python -m venv .venv
|
|
514
|
+
source .venv/bin/activate
|
|
515
|
+
python -m pip install -r requirements.txt
|
|
516
|
+
|
|
517
|
+
# Run tests from the repository root
|
|
518
|
+
python -m pytest tests/ -v
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
CI runs the suite on Python 3.11–3.13, then builds a wheel and source
|
|
522
|
+
distribution with the oldest supported setuptools and imports both
|
|
523
|
+
subpackages from a clean environment, so a module missing from the
|
|
524
|
+
distribution fails the build rather than a user's install.
|
|
525
|
+
|
|
526
|
+
## Documentation
|
|
527
|
+
|
|
528
|
+
- [Architecture & Design](docs/DESIGN.md)
|
|
529
|
+
- [CUDA Backend Handoff](docs/CUDA_HANDOFF.md)
|
|
530
|
+
- [Matrix-free MPIR-FEM](docs/MATRIX_FREE_MPIR_FEM.md)
|
|
531
|
+
- [Thermal MPIR-FEM](docs/THERMAL_MPIR_FEM.md)
|
|
532
|
+
- [EMC radiated emissions](docs/EMC_DIPOLE_SUPERPOSITION.md)
|
|
533
|
+
- [Coupled analysis scenarios](docs/MULTIPHYSICS_SCENARIOS.md)
|
|
534
|
+
- [Sheet PEEC](docs/SHEET_PEEC.md) and [Sheet PEEC CUDA results](docs/SHEET_CUDA_RESULTS.md)
|
|
535
|
+
- [Requirements](docs/REQUIREMENTS.md)
|
|
536
|
+
- [Sheet PEEC / matrix-free FEM comparison](docs/PEEC_FEM_COMPARISON_REPORT.html)
|
|
537
|
+
([raw data](docs/PEEC_FEM_COMPARISON_RESULTS.json))
|
|
538
|
+
- [Maxwell accuracy and CPU timing report](docs/MAXWELL_VALIDATION_REPORT.html)
|
|
539
|
+
([raw data](docs/MAXWELL_SMALL_RESULTS.json))
|
|
540
|
+
- [Maxwell CUDA benchmark data](docs/MAXWELL_CUDA_RESULTS.json) and
|
|
541
|
+
[CUDA scaling data](docs/MAXWELL_CUDA_SCALE_RESULTS.json)
|
|
542
|
+
- [Benchmark Results](docs/RESULTS.md)
|
|
543
|
+
|
|
544
|
+
## License
|
|
545
|
+
|
|
546
|
+
[MIT](LICENSE)
|