Accelerated electrical, thermal, EMC, and coupled PCB analysis
Accelerated PEEC and matrix-free FEM solvers for electrical and thermal PCB analysis, radiated-emission evaluation for EMC, and PDN optimization.
The repository has three solver families: electrical, thermal and emc.
These solvers are designed to analyse PCBs faster, so that they can be embedded in an automated, LLM-driven PCB design workflow.
Features
- electrical: 2.5D sheet PEEC for current density, voltage drop and skin effect on multilayer copper; matrix-free FEM for DC conduction and 2D frequency-domain Maxwell; 3D voxel PEEC (PyPEEC) for thick conductors; DICE delta scoring to rank many candidate edits without a full solve.
- thermal: steady and transient heat conduction through the board stack, with convection, radiation and fixed-temperature boundaries.
- emc: near-field scans, far-field patterns and CISPR 32 / FCC Part 15 margins computed from a solved current distribution.
- multiphysics: electro-thermal fixed point with temperature-dependent copper, and emission of the cold or heated current.
- geometry: reads a KiCad (or any mechanical) STEP export into the solvers' grids, keeping the plated via and through-hole barrels.
- NumPy on the CPU everywhere; CUDA (CuPy) and OpenMP C++ kernels where they pay off.
Install
pip install pcb-analysis # CPU only
pip install 'pcb-analysis[cuda]' # adds CuPy for the CUDA paths
Requirements:
- Python 3.11 or later. The base install includes OpenCASCADE (
cadquery-ocp) and PyPEEC. - Optional: an NVIDIA driver with CUDA 13.x for
[cuda]. Runnvidia-smito check it. kicad-cli(KiCad 8 or later) for the KiCad path below.
Each release is also attached to its GitHub release (wheel, sdist and SHA-256 checksums). To install one without PyPI:
pip install 'git+https://github.com/AFLOY/pcb-analysis.git@v0.9.0'
How to use: start from KiCad
KiCad is the recommended environment. Design the board there, export it as STEP with its copper, and let pcb-analysis read the copper back onto a grid.
1. Export the board with its copper.
--include-tracks, --include-pads and --include-zones write the copper
as solids. Vias and through-holes then come out as the plated barrels KiCad
models, and pcb-analysis reads those barrels as they are, so do not add
--fill-all-vias.
kicad-cli pcb export step --include-tracks --include-pads --include-zones \
--include-inner-copper --no-extra-pad-thickness --no-components --force \
--output power_module.step power_module.kicad_pcb
2. Solve it. The stackup comes from the .kicad_pcb. The body map sorts
the solids into the board, one copper sheet per layer and the barrels. Two
pads act as the input faces: 1 A enters at the input connector's VIN pin
(J1 pad 3) and leaves at the MOSFET's source pad (Q1 pad 3). The API has no
pad object: each pad's centre is read from the KiCad board (the pad
properties, in mm), and the copper cells under it become the terminal's cells.
current_field_problem_mapping collects the grid, copper, barrels and terminals
into one current-field problem.
import numpy as np
from geometry.cad_import import (
board_barrels, board_vias, kicad_step_body_map, layers_from_kicad_stackup,
load_step, current_field_problem_mapping, rasterize_board, read_kicad_stackup, resolve_bodies,
)
from electrical.sheet_peec import solve_current_field_problem
board = "power_module.kicad_pcb"
layers, board_top_mm = layers_from_kicad_stackup(read_kicad_stackup(board))
body_map = kicad_step_body_map(layers, board_top_z_mm=board_top_mm) # board, copper layers, barrels
resolved = resolve_bodies(load_step("power_module.step"), body_map)
raster = rasterize_board(resolved, body_map.board, pitch_mm=0.25, y_down=True)
def pad_cells(layer, x_mm, y_mm, half_width_mm=0.5):
"""Copper cells of `layer` around a pad centre given in KiCad coordinates."""
row, col = raster.cell_of(x_mm * 1e-3, -y_mm * 1e-3) # the STEP export negates KiCad's y
k = [spec.name for spec in raster.layers].index(layer)
r = int(np.ceil(half_width_mm / raster.pitch_mm))
rows, cols = raster.shape
return [{"layer": layer, "x": c, "y": y}
for y in range(max(row - r, 0), min(row + r + 1, rows))
for c in range(max(col - r, 0), min(col + r + 1, cols)) if raster.occupancy[k, y, c] > 0]
terminals = [
{"name": "VIN", "pad": "J1.3", "current_a": 1.0, "cells": pad_cells("F.Cu", 129.0, 97.58)},
{"name": "SRC", "pad": "Q1.3", "current_a": -1.0, "cells": pad_cells("F.Cu", 149.26, 95.675)},
]
problem = current_field_problem_mapping(raster, terminals=terminals, frequency_hz=0.0,
vias=board_vias(resolved, raster), barrels=board_barrels(resolved, raster))
result = solve_current_field_problem(problem) # DC sheet PEEC on the CPU
print(f"voltage span {result.metrics['voltage_span_v'] * 1e3:.3f} mV, "
f"peak current density {result.metrics['max_current_density_a_per_mm2']:.2f} A/mm2")
On the example board (two layers, a 139 × 139 grid) this takes a few seconds on a desktop CPU.
Next steps
Each package README continues from the same export, one step at a time:
| Package | What you get |
|---|---|
| electrical | terminals and current cases, DC and AC sheet PEEC, the matrix-free FEM |
| thermal | board thermal mesh, heat sources, convection, temperature rise |
| emc | radiated field and limit margin from the solved current |
| multiphysics | electro-thermal coupling and emission of the heated board |
| geometry | body maps, rasterising, plated barrels, the current-field problem |
Documentation
The methods, their limits and the measurements behind each design decision:
- Architecture and design and requirements
- Sheet PEEC, matrix-free MPIR-FEM, 3D voxel PEEC
- Thermal MPIR-FEM
- EMC dipole superposition
- Coupled scenarios
- STEP and KiCad input
- Reports: sheet PEEC versus matrix-free FEM, Maxwell accuracy and CPU timing
Release and migration notes are in CHANGELOG.md.
Development
pip install -e '.[test]' # from a clone; CPU only, as CI runs it
python -m pytest tests/
pip install -e '.[native]' # optional C++ kernels (pybind11, OpenMP)
cmake -S . -B build/native -DCMAKE_BUILD_TYPE=Release
cmake --build build/native
Tests that need the C++ kernels are skipped until they are built.
CMakeLists.txt lists the native targets; -DPCB_NATIVE_OPENMP=OFF drops
OpenMP and -DPCB_NATIVE_MARCH=x86-64-v3 (for example) replaces
-march=native. The scripts in experiments/ regenerate the numbers quoted
in docs/; run them from the repository root.
License
Release files for pcb-analysis 0.9.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pcb_analysis-0.9.0.tar.gz | 308.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pcb_analysis-0.9.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 584.5 kB
Release files / pcb_analysis-0.9.0.tar.gz
| Download URL | pcb_analysis-0.9.0.tar.gz |
|---|---|
| Size | 308.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9f73cb0ed352952ee6afb54eb5598fb7d2fadabad34b58e3b77eaa68e05b2524
|
|
BLAKE2b-256 checksum How to use checksums |
dc896d51bda3728bc3d2e6901f8b0da35b56383ccc3364cc46b642bcec6846ff
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / pcb_analysis-0.9.0-py3-none-any.whl
| Download URL | pcb_analysis-0.9.0-py3-none-any.whl |
|---|---|
| Size | 276.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3e4dadd535473f765bbfded295e715f2000f8949e00a079827f6934e4dbc16bf
|
|
BLAKE2b-256 checksum How to use checksums |
08baa15c49245d413e9e44eda0d9ee19e9a68b5527adc147005fd1740a17d4fb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency log