From 4161ed2353a06b3eaba55ab50373a17d41cd8cfd Mon Sep 17 00:00:00 2001 From: Quentin Wach Date: Sun, 17 May 2026 13:35:56 +0200 Subject: [PATCH 1/6] Add physics model and mode solver methods documentation - Introduced detailed explanations of the physics model and mathematical formulations in README.md. - Added a new documentation file for mode solver methods, outlining the main entry point, material grids, solver controls, eigenpair selection, and result helpers. - Enhanced clarity and comprehensiveness of the documentation for better user understanding. --- README.md | 36 ++++++++++ docs/mode-solver-methods.md | 69 +++++++++++++++++++ docs/physics-model.md | 133 ++++++++++++++++++++++++++++++++++++ 3 files changed, 238 insertions(+) create mode 100644 docs/mode-solver-methods.md create mode 100644 docs/physics-model.md diff --git a/README.md b/README.md index 66dcd41..817d1f2 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,42 @@ data.to_hdf5("modes.h5") ``` +## Physics Model + +MicroMode solves source-free, frequency-domain Maxwell modes on a rasterized +Yee mode plane. It assumes modal fields + +$$ +\mathbf{E}(x,y,z)=\mathbf{e}(x,y)e^{i k_0 n_\mathrm{eff} z}, +\qquad +\mathbf{H}(x,y,z)=\mathbf{h}(x,y)e^{i k_0 n_\mathrm{eff} z}, +$$ + +then uses either the diagonal-media reduction + +$$ +A_\mathrm{diag} +\begin{bmatrix}E_x\\E_y\end{bmatrix} += +-n_\mathrm{eff}^2 +\begin{bmatrix}E_x\\E_y\end{bmatrix} +$$ + +or the full tensorial first-order form + +$$ +A_\mathrm{tensor} +\begin{bmatrix}E_x\\E_y\\H_x\\H_y\end{bmatrix} += +n_\mathrm{eff} +\begin{bmatrix}E_x\\E_y\\H_x\\H_y\end{bmatrix}. +$$ + +The detailed derivation is in [docs/physics-model.md](docs/physics-model.md), +and the public solver controls are summarized in +[docs/mode-solver-methods.md](docs/mode-solver-methods.md). + + ## High Performance MicroMode is designed to make high-performance mode solving available without diff --git a/docs/mode-solver-methods.md b/docs/mode-solver-methods.md new file mode 100644 index 0000000..9f73a30 --- /dev/null +++ b/docs/mode-solver-methods.md @@ -0,0 +1,69 @@ +# Mode Solver Methods + +`solve_modes(...)` is the main entry point. It validates the `Materials` grid, +resolves frequencies or wavelengths, builds the Yee derivative matrices, chooses +the diagonal or tensorial Rust backend, solves one frequency at a time, and +returns a coordinate-aware `Result`. + +## Material Grids + +- `Materials.from_diagonal(...)` builds scalar or diagonal-anisotropic grids. + These usually use the faster diagonal sparse formulation. +- `Materials.from_components(...)` accepts full \(3\times3\) material tensors. + Any off-diagonal component routes the solve through the tensorial sparse + formulation. +- `Materials.from_slice(...)` creates a 1D mode-plane slice with an invariant + width so integrations and overlaps still have physical weights. +- `Materials.from_subpixel_diagonal(...)` downsamples a high-resolution + diagonal raster with arithmetic, harmonic, geometric, min, or max averaging. + +## Solver Controls + +- `num_modes`: number of modes returned near the requested target. +- `target_neff`: center of the shift-invert search. If omitted, MicroMode uses + the largest local material index as a practical guided-mode default. +- `pml`: absorbing boundary thickness and stretch profile via `PmlSpec`. +- `boundary`: low-edge PEC/PMC symmetry settings via `BoundarySpec`. +- `direction`: `"+"` or `"-"` propagation; the backward solve flips the + appropriate magnetic and longitudinal electric signs. +- `components`: optional subset of returned field components. +- `krylov_dim`: dimension of the Arnoldi search space. +- `angle_theta`, `angle_phi`, `bend_radius`, `bend_axis`: transformation-optics + controls that update \(\epsilon\) and \(\mu\) before the sparse solve. + +## Eigenpair Selection + +Internally, eigenpairs are selected with sparse shift-invert Arnoldi. For a +matrix \(A\) and shift \(\sigma\), Arnoldi is applied to + +$$ +(A-\sigma I)^{-1}, +\qquad +\lambda = \sigma + 1/\theta, +$$ + +where \(\theta\) is a Ritz value of the inverse-shifted operator. The diagonal +backend uses \(\sigma=-\texttt{target_neff}^2\); the tensorial backend uses +\(\sigma=\texttt{target_neff}\). + +Returned modes are sorted by decreasing real effective index, normalized to +unit transverse power, + +$$ +\int (\mathbf{E}\times\mathbf{H}^*)\cdot\hat{\mathbf{n}}\,dA, +$$ + +and orthogonalized with the unconjugated Lorentz product + +$$ +L(a,b)=\frac{1}{2}\int +\left[(\mathbf{E}_a\times\mathbf{H}_b) ++(\mathbf{E}_b\times\mathbf{H}_a)\right]\cdot\hat{\mathbf{n}}\,dA. +$$ + +## Result Helpers + +`Result` exposes the post-processing methods users normally need: `n_eff`, +`k_eff`, `mode_area`, `pol_fraction`, `pol_fraction_waveguide`, `modes_info`, +`to_dataframe()`, `overlap()`, `overlap_matrix()`, `plot_field()`, +`plot_field_components()`, `to_hdf5()`, and `Result.from_hdf5()`. diff --git a/docs/physics-model.md b/docs/physics-model.md new file mode 100644 index 0000000..03bf047 --- /dev/null +++ b/docs/physics-model.md @@ -0,0 +1,133 @@ +# Physics Model + +MicroMode solves source-free, frequency-domain Maxwell modes on a rasterized +mode plane. Fields are assumed to vary as + +$$ +\mathbf{E}(x, y, z) = \mathbf{e}(x, y) e^{i k_0 n_\mathrm{eff} z}, +\qquad +\mathbf{H}(x, y, z) = \mathbf{h}(x, y) e^{i k_0 n_\mathrm{eff} z}, +$$ + +where \(k_0 = 2\pi / \lambda_0\). The Rust kernels use relative material +tensors \(\epsilon_r(x,y)\), \(\mu_r(x,y)\) and scale transverse derivatives by +\(1/k_0\), so the sparse operators are dimensionless. On the local Yee grid, +the four derivative matrices are + +$$ +D_{xf}, D_{xb}, D_{yf}, D_{yb} +\approx +\frac{1}{k_0}\partial_x^\mathrm{forward/backward}, +\frac{1}{k_0}\partial_y^\mathrm{forward/backward}. +$$ + +For diagonal material tensors, MicroMode reduces Maxwell's equations to a +transverse electric eigenproblem. With + +$$ +\mathbf{e}_t = +\begin{bmatrix} E_x \\ E_y \end{bmatrix}, +\qquad +A_\mathrm{diag} = +P_\mu Q + P_\partial Q_\epsilon, +$$ + +the solved eigenproblem is + +$$ +A_\mathrm{diag}\mathbf{e}_t = -n_\mathrm{eff}^2 \mathbf{e}_t. +$$ + +The block operators are assembled from the Yee derivatives and diagonal tensor +components: + +$$ +P_\mu = +\begin{bmatrix} +0 & \mu_{yy} \\ +-\mu_{xx} & 0 +\end{bmatrix}, +\qquad +Q_\epsilon = +\begin{bmatrix} +0 & \epsilon_{yy} \\ +-\epsilon_{xx} & 0 +\end{bmatrix}, +$$ + +$$ +P_\partial = +\begin{bmatrix} +-D_{xf}\epsilon_{zz}^{-1}D_{yb} & D_{xf}\epsilon_{zz}^{-1}D_{xb} \\ +-D_{yf}\epsilon_{zz}^{-1}D_{yb} & D_{yf}\epsilon_{zz}^{-1}D_{xb} +\end{bmatrix}, +$$ + +$$ +Q_\partial = +\begin{bmatrix} +-D_{xb}\mu_{zz}^{-1}D_{yf} & D_{xb}\mu_{zz}^{-1}D_{xf} \\ +-D_{yb}\mu_{zz}^{-1}D_{yf} & D_{yb}\mu_{zz}^{-1}D_{xf} +\end{bmatrix}, +\qquad +Q = Q_\epsilon + Q_\partial. +$$ + +After the transverse solve, the remaining field components are reconstructed +from the curl equations: + +$$ +\begin{bmatrix} H_x \\ H_y \end{bmatrix} +\propto +\frac{1}{i n_\mathrm{eff}}Q\mathbf{e}_t, +\qquad +H_z \propto \mu_{zz}^{-1}(D_{xf}E_y - D_{yf}E_x), +$$ + +$$ +E_z \propto \epsilon_{zz}^{-1}(D_{xb}H_y - D_{yb}H_x). +$$ + +For full tensor media, including off-diagonal \(\epsilon\)/\(\mu\) terms and +angle or bend coordinate transforms, MicroMode switches to a first-order +tensorial eigenproblem: + +$$ +A_\mathrm{tensor} +\begin{bmatrix} E_x \\ E_y \\ H_x \\ H_y \end{bmatrix} += +n_\mathrm{eff} +\begin{bmatrix} E_x \\ E_y \\ H_x \\ H_y \end{bmatrix}. +$$ + +The longitudinal tensor couplings are eliminated through local Schur +complements such as + +$$ +\epsilon^{(s)}_{\alpha\beta} += +\epsilon_{\alpha\beta} +- +\epsilon_{\alpha z}\epsilon_{z\beta}/\epsilon_{zz}, +\qquad +\mu^{(s)}_{\alpha\beta} += +\mu_{\alpha\beta} +- +\mu_{\alpha z}\mu_{z\beta}/\mu_{zz}, +$$ + +then \(E_z\) and \(H_z\) are reconstructed with the off-diagonal coupling terms +included. This is the path used automatically for `Materials.from_components`, +angled solves, and bend solves whenever the transformed tensors are no longer +diagonal. + +PMLs are implemented as complex coordinate stretching. When `pml` is enabled, +each derivative is premultiplied by a diagonal stretch matrix: + +$$ +D \leftarrow S^{-1}D,\qquad +s(u) = \kappa(u) + i\frac{\sigma(u)}{\omega\epsilon_0}, +$$ + +with polynomial \(\kappa\) and \(\sigma\) profiles controlled by `PmlSpec`. From b30b6e40a442c913b994d500b4847184ac9ced96 Mon Sep 17 00:00:00 2001 From: Quentin Wach Date: Sun, 17 May 2026 13:40:18 +0200 Subject: [PATCH 2/6] Enhance physics model documentation in README.md and physics-model.md - Clarified the description of the MicroMode solver, emphasizing its source-free nature and the mathematical formulations of Maxwell's equations. - Expanded the documentation to include details on discretization, boundary settings, and the treatment of diagonal and tensorial materials. - Improved overall structure and readability for better user comprehension. --- README.md | 31 ++++++++++----------- docs/physics-model.md | 63 ++++++++++++++++++++++++++++++++----------- 2 files changed, 61 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 817d1f2..fa47fab 100644 --- a/README.md +++ b/README.md @@ -58,37 +58,34 @@ data.to_hdf5("modes.h5") ## Physics Model -MicroMode solves source-free, frequency-domain Maxwell modes on a rasterized -Yee mode plane. It assumes modal fields +MicroMode solves the source-free frequency-domain Maxwell equations on a +rasterized Yee mode plane, $$ -\mathbf{E}(x,y,z)=\mathbf{e}(x,y)e^{i k_0 n_\mathrm{eff} z}, +\nabla\times\mathbf{E}=-i\omega\mu\mathbf{H}, \qquad -\mathbf{H}(x,y,z)=\mathbf{h}(x,y)e^{i k_0 n_\mathrm{eff} z}, +\nabla\times\mathbf{H}=i\omega\epsilon\mathbf{E}, $$ -then uses either the diagonal-media reduction +with modal fields $$ -A_\mathrm{diag} -\begin{bmatrix}E_x\\E_y\end{bmatrix} -= --n_\mathrm{eff}^2 -\begin{bmatrix}E_x\\E_y\end{bmatrix} +\mathbf{E},\mathbf{H}\propto e^{i k_0 n_\mathrm{eff} z}. $$ -or the full tensorial first-order form +On diagonal material grids this becomes a transverse eigenproblem, $$ -A_\mathrm{tensor} -\begin{bmatrix}E_x\\E_y\\H_x\\H_y\end{bmatrix} +A_\mathrm{diag} +\begin{bmatrix}E_x\\E_y\end{bmatrix} = -n_\mathrm{eff} -\begin{bmatrix}E_x\\E_y\\H_x\\H_y\end{bmatrix}. +-n_\mathrm{eff}^2 +\begin{bmatrix}E_x\\E_y\end{bmatrix} $$ -The detailed derivation is in [docs/physics-model.md](docs/physics-model.md), -and the public solver controls are summarized in +while full tensor or transformed grids use a first-order tensorial form. The +detailed derivation is in [docs/physics-model.md](docs/physics-model.md), and +the public solver controls are summarized in [docs/mode-solver-methods.md](docs/mode-solver-methods.md). diff --git a/docs/physics-model.md b/docs/physics-model.md index 03bf047..1d4449d 100644 --- a/docs/physics-model.md +++ b/docs/physics-model.md @@ -1,7 +1,28 @@ # Physics Model -MicroMode solves source-free, frequency-domain Maxwell modes on a rasterized -mode plane. Fields are assumed to vary as +MicroMode solves source-free, frequency-domain Maxwell's equations on a +rasterized mode plane: + +$$ +\nabla \times \mathbf{E}(\mathbf{r}) += +-i\omega\mu(\mathbf{r},\omega)\mathbf{H}(\mathbf{r}), +\qquad +\nabla \times \mathbf{H}(\mathbf{r}) += +i\omega\epsilon(\mathbf{r},\omega)\mathbf{E}(\mathbf{r}). +$$ + +Here: + +- \(\mathbf{r}\) is position in the local mode-coordinate system; +- \(\mathbf{E}\) and \(\mathbf{H}\) are the electric and magnetic mode fields; +- \(\omega\) is the angular frequency; +- \(\epsilon\) and \(\mu\) are the supplied material tensors. + +Unlike a driven FDFD field solve, MicroMode is a mode solver: there are no +electric or magnetic current sources. It assumes fields vary along the local +propagation axis as $$ \mathbf{E}(x, y, z) = \mathbf{e}(x, y) e^{i k_0 n_\mathrm{eff} z}, @@ -9,10 +30,16 @@ $$ \mathbf{H}(x, y, z) = \mathbf{h}(x, y) e^{i k_0 n_\mathrm{eff} z}, $$ -where \(k_0 = 2\pi / \lambda_0\). The Rust kernels use relative material -tensors \(\epsilon_r(x,y)\), \(\mu_r(x,y)\) and scale transverse derivatives by -\(1/k_0\), so the sparse operators are dimensionless. On the local Yee grid, -the four derivative matrices are +where \(k_0 = 2\pi / \lambda_0\) and \(n_\mathrm{eff}\) is the unknown complex +effective index. The transverse fields are discretized by the +finite-difference frequency-domain method on a regular Yee grid. + +## Discretization + +The Rust kernels use relative material tensors \(\epsilon_r(x,y)\), +\(\mu_r(x,y)\) and scale transverse derivatives by \(1/k_0\), so the sparse +operators are dimensionless. On the local Yee grid, the four derivative +matrices are $$ D_{xf}, D_{xb}, D_{yf}, D_{yb} @@ -21,6 +48,18 @@ D_{xf}, D_{xb}, D_{yf}, D_{yb} \frac{1}{k_0}\partial_y^\mathrm{forward/backward}. $$ +Low-edge PEC/PMC boundary settings modify the derivative stencils, and PMLs +premultiply derivatives by complex stretch matrices: + +$$ +D \leftarrow S^{-1}D,\qquad +s(u) = \kappa(u) + i\frac{\sigma(u)}{\omega\epsilon_0}. +$$ + +The stretch profiles are polynomial functions controlled by `PmlSpec`. + +## Diagonal Materials + For diagonal material tensors, MicroMode reduces Maxwell's equations to a transverse electric eigenproblem. With @@ -88,6 +127,8 @@ $$ E_z \propto \epsilon_{zz}^{-1}(D_{xb}H_y - D_{yb}H_x). $$ +## Tensorial Materials + For full tensor media, including off-diagonal \(\epsilon\)/\(\mu\) terms and angle or bend coordinate transforms, MicroMode switches to a first-order tensorial eigenproblem: @@ -121,13 +162,3 @@ then \(E_z\) and \(H_z\) are reconstructed with the off-diagonal coupling terms included. This is the path used automatically for `Materials.from_components`, angled solves, and bend solves whenever the transformed tensors are no longer diagonal. - -PMLs are implemented as complex coordinate stretching. When `pml` is enabled, -each derivative is premultiplied by a diagonal stretch matrix: - -$$ -D \leftarrow S^{-1}D,\qquad -s(u) = \kappa(u) + i\frac{\sigma(u)}{\omega\epsilon_0}, -$$ - -with polynomial \(\kappa\) and \(\sigma\) profiles controlled by `PmlSpec`. From bed7446f8c93bae8e7a3dc11432d3716a4910374 Mon Sep 17 00:00:00 2001 From: Quentin Wach Date: Sun, 17 May 2026 14:23:37 +0200 Subject: [PATCH 3/6] Enhance documentation for mode solver methods and physics model - Added references for eigenpair selection methods in mode-solver-methods.md to provide context and credibility. - Clarified the description of the finite-difference frequency-domain method and its application in the physics model documentation. - Included additional references to relevant literature for better understanding of the methods used in the solver. --- docs/mode-solver-methods.md | 16 ++++++++++++++-- docs/physics-model.md | 23 ++++++++++++++++++++--- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/docs/mode-solver-methods.md b/docs/mode-solver-methods.md index 9f73a30..a54cd3c 100644 --- a/docs/mode-solver-methods.md +++ b/docs/mode-solver-methods.md @@ -33,8 +33,8 @@ returns a coordinate-aware `Result`. ## Eigenpair Selection -Internally, eigenpairs are selected with sparse shift-invert Arnoldi. For a -matrix \(A\) and shift \(\sigma\), Arnoldi is applied to +Internally, eigenpairs are selected with sparse shift-invert Arnoldi [1, 2]. +For a matrix \(A\) and shift \(\sigma\), Arnoldi is applied to $$ (A-\sigma I)^{-1}, @@ -67,3 +67,15 @@ $$ `k_eff`, `mode_area`, `pol_fraction`, `pol_fraction_waveguide`, `modes_info`, `to_dataframe()`, `overlap()`, `overlap_matrix()`, `plot_field()`, `plot_field_components()`, `to_hdf5()`, and `Result.from_hdf5()`. + +## References + +[1] W. E. Arnoldi, "The principle of minimized iterations in the solution of the +matrix eigenvalue problem," *Quarterly of Applied Mathematics*, vol. 9, no. 1, +pp. 17-29, 1951. +[AMS record](https://www.ams.org/qam/1951-09-01/S0033-569X-1951-42792-9/). + +[2] R. B. Lehoucq, D. C. Sorensen, and C. Yang, *ARPACK Users' Guide: Solution +of Large-Scale Eigenvalue Problems with Implicitly Restarted Arnoldi Methods*, +SIAM, 1998. +doi:[10.1137/1.9780898719628](https://doi.org/10.1137/1.9780898719628). diff --git a/docs/physics-model.md b/docs/physics-model.md index 1d4449d..7bd531c 100644 --- a/docs/physics-model.md +++ b/docs/physics-model.md @@ -1,7 +1,8 @@ # Physics Model MicroMode solves source-free, frequency-domain Maxwell's equations on a -rasterized mode plane: +rasterized mode plane, following the same FDFD starting point used by +MaxwellFDFD [1]: $$ \nabla \times \mathbf{E}(\mathbf{r}) @@ -32,7 +33,7 @@ $$ where \(k_0 = 2\pi / \lambda_0\) and \(n_\mathrm{eff}\) is the unknown complex effective index. The transverse fields are discretized by the -finite-difference frequency-domain method on a regular Yee grid. +finite-difference frequency-domain method on a regular Yee grid [2]. ## Discretization @@ -56,7 +57,9 @@ D \leftarrow S^{-1}D,\qquad s(u) = \kappa(u) + i\frac{\sigma(u)}{\omega\epsilon_0}. $$ -The stretch profiles are polynomial functions controlled by `PmlSpec`. +The stretch profiles are polynomial functions controlled by `PmlSpec`. The +stretched-coordinate PML form follows the frequency-domain Maxwell literature +summarized by Shin and Fan [3]. ## Diagonal Materials @@ -162,3 +165,17 @@ then \(E_z\) and \(H_z\) are reconstructed with the off-diagonal coupling terms included. This is the path used automatically for `Materials.from_components`, angled solves, and bend solves whenever the transformed tensors are no longer diagonal. + +## References + +[1] W. Shin, [MaxwellFDFD webpage](https://www.mit.edu/~wsshin/maxwellfdfd.html), 2015. + +[2] K. S. Yee, "Numerical solution of initial boundary value problems involving +Maxwell's equations in isotropic media," *IEEE Transactions on Antennas and +Propagation*, vol. 14, no. 3, pp. 302-307, 1966. +doi:[10.1109/TAP.1966.1138693](https://doi.org/10.1109/TAP.1966.1138693). + +[3] W. Shin and S. Fan, "Choice of the perfectly matched layer boundary +condition for frequency-domain Maxwell's equations solvers," *Journal of +Computational Physics*, vol. 231, no. 8, pp. 3406-3431, 2012. +doi:[10.1016/j.jcp.2012.01.013](https://doi.org/10.1016/j.jcp.2012.01.013). From 2d2468e39d8be8bec450c2bc0ad239a1aae7d7ce Mon Sep 17 00:00:00 2001 From: Quentin Wach Date: Sun, 17 May 2026 14:29:35 +0200 Subject: [PATCH 4/6] Update README.md to enhance project description and add new badges - Improved the description of the electromagnetic mode solver, emphasizing its use of the FDFD method and Yee-grid. - Added new badges for PyPI version and project status to provide users with more information at a glance. --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index fa47fab..fbf7348 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,17 @@ # micromode -An electromagnetic mode solver using the FDFD method on a regular Yee-grid, written in native Rust. - -[![License](https://img.shields.io/github/license/QuentinWach/micromode)](LICENSE) -[![Tests](https://img.shields.io/github/actions/workflow/status/QuentinWach/micromode/tests.yml?branch=main&label=tests)](https://github.com/QuentinWach/micromode/actions/workflows/tests.yml) -![Coverage](https://img.shields.io/badge/coverage-87%25-brightgreen) +An **electromagnetic mode solver** using the **[FDFD method](https://en.wikipedia.org/wiki/Finite-difference_frequency-domain_method)** on a **[regular Yee-grid](https://en.wikipedia.org/wiki/Finite-difference_time-domain_method)**, written in native **[Rust](https://rust-lang.org/)**. ```bash pip install micromode ``` +[![License](https://img.shields.io/github/license/QuentinWach/micromode)](LICENSE) +[![Tests](https://img.shields.io/github/actions/workflow/status/QuentinWach/micromode/tests.yml?branch=main&label=tests)](https://github.com/QuentinWach/micromode/actions/workflows/tests.yml) +![Coverage](https://img.shields.io/badge/coverage-87%25-brightgreen) +[![PyPI](https://img.shields.io/pypi/v/micromode)](https://pypi.org/project/micromode/) +![Status](https://img.shields.io/badge/status-alpha-orange) + ## Why Use It? From 3a93c8021434cb21d46cda3748db28e726b0d6b9 Mon Sep 17 00:00:00 2001 From: Quentin Wach Date: Sun, 17 May 2026 14:45:34 +0200 Subject: [PATCH 5/6] Update README.md, CI workflows, and documentation for coverage tracking - Revised the README.md to clarify the use of a rectilinear Yee-grid. - Added a new script to generate a local SVG coverage badge and updated CI workflows to automate badge generation and upload. - Enhanced documentation for mode solver methods to specify the heuristic for target_neff based on permittivity. - Introduced a new coverage.svg file to visually represent test coverage in the project. --- .github/workflows/tests.yml | 45 ++++++++++++++++- README.md | 22 ++++---- docs/assets/coverage.svg | 21 ++++++++ docs/mode-solver-methods.md | 2 +- scripts/generate_coverage_badge.py | 81 ++++++++++++++++++++++++++++++ 5 files changed, 158 insertions(+), 13 deletions(-) create mode 100644 docs/assets/coverage.svg create mode 100644 scripts/generate_coverage_badge.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4b4f4ab..c804f15 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [main, master] +permissions: + contents: read + jobs: test-portable: name: Portable tests (Python ${{ matrix.python-version }}) @@ -50,14 +53,54 @@ jobs: run: uv run python scripts/smoke_dist.py --python "$(uv python find ${{ matrix.python-version }})" - name: Upload coverage to Codecov + id: codecov_upload uses: codecov/codecov-action@v4 if: matrix.python-version == '3.13' with: files: ./coverage.xml - fail_ci_if_error: false + fail_ci_if_error: true env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + - name: Generate local coverage badge + if: matrix.python-version == '3.13' && steps.codecov_upload.outcome == 'success' + run: uv run python scripts/generate_coverage_badge.py coverage.xml docs/assets/coverage.svg + + - name: Upload local coverage badge + if: matrix.python-version == '3.13' && steps.codecov_upload.outcome == 'success' + uses: actions/upload-artifact@v4 + with: + name: coverage-badge + path: docs/assets/coverage.svg + + update-coverage-badge: + name: Update coverage badge + needs: test-portable + runs-on: ubuntu-latest + if: github.event_name == 'push' + permissions: + contents: write + + steps: + - uses: actions/checkout@v4 + + - name: Download local coverage badge + uses: actions/download-artifact@v4 + with: + name: coverage-badge + path: tmp/coverage-badge + + - name: Commit local coverage badge + run: | + cp tmp/coverage-badge/coverage.svg docs/assets/coverage.svg + if ! git diff --quiet -- docs/assets/coverage.svg; then + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add docs/assets/coverage.svg + git commit -m "Update coverage badge [skip ci]" + git push + fi + test-full: name: Full portable tests runs-on: ubuntu-latest diff --git a/README.md b/README.md index fbf7348..95aceba 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # micromode -An **electromagnetic mode solver** using the **[FDFD method](https://en.wikipedia.org/wiki/Finite-difference_frequency-domain_method)** on a **[regular Yee-grid](https://en.wikipedia.org/wiki/Finite-difference_time-domain_method)**, written in native **[Rust](https://rust-lang.org/)**. +An **electromagnetic mode solver** using the **[FDFD method](https://en.wikipedia.org/wiki/Finite-difference_frequency-domain_method)** on a **[rectilinear Yee-grid](https://en.wikipedia.org/wiki/Finite-difference_time-domain_method)**, written in native **[Rust](https://rust-lang.org/)**. ```bash pip install micromode @@ -8,7 +8,7 @@ pip install micromode [![License](https://img.shields.io/github/license/QuentinWach/micromode)](LICENSE) [![Tests](https://img.shields.io/github/actions/workflow/status/QuentinWach/micromode/tests.yml?branch=main&label=tests)](https://github.com/QuentinWach/micromode/actions/workflows/tests.yml) -![Coverage](https://img.shields.io/badge/coverage-87%25-brightgreen) +![Coverage](docs/assets/coverage.svg) [![PyPI](https://img.shields.io/pypi/v/micromode)](https://pypi.org/project/micromode/) ![Status](https://img.shields.io/badge/status-alpha-orange) @@ -121,12 +121,12 @@ around the requested effective index. The Arnoldi stage uses stopping once requested modes are stable, and selective Ritz vector reconstruction so work is spent on the modes that will actually be returned. -On the repository benchmark problem, the **pure Rust backend** solves larger grids -in the same performance class as the previous optional UMFPACK-backed path while -remaining much easier to install and distribute. For example, a release build on -an Apple Silicon development machine solves an `80x50` diagonal benchmark grid -in roughly **`90 ms` for two modes** with residuals around **`1e-12`**. Exact -timings depend on hardware and problem shape, but the important point is -architectural: MicroMode keeps the **deployability of a pure Rust package** -without giving up the sparse-solver performance expected for practical -waveguide grids. +For local performance checks, use the backend benchmark: + +```bash +uv run python benchmarks/micromode_backend_benchmark.py --grid 20x14 --grid 32x22 +``` + +It records backend name, operator size, nonzero count, residuals, elapsed time, +and solved effective indices so timing claims can be tied to a specific machine, +commit, and problem shape. diff --git a/docs/assets/coverage.svg b/docs/assets/coverage.svg new file mode 100644 index 0000000..66e4f30 --- /dev/null +++ b/docs/assets/coverage.svg @@ -0,0 +1,21 @@ + + coverage: 87% + + + + + + + + + + + + + + coverage + coverage + 87% + 87% + + diff --git a/docs/mode-solver-methods.md b/docs/mode-solver-methods.md index a54cd3c..d144284 100644 --- a/docs/mode-solver-methods.md +++ b/docs/mode-solver-methods.md @@ -21,7 +21,7 @@ returns a coordinate-aware `Result`. - `num_modes`: number of modes returned near the requested target. - `target_neff`: center of the shift-invert search. If omitted, MicroMode uses - the largest local material index as a practical guided-mode default. + a heuristic based on the largest absolute permittivity component. - `pml`: absorbing boundary thickness and stretch profile via `PmlSpec`. - `boundary`: low-edge PEC/PMC symmetry settings via `BoundarySpec`. - `direction`: `"+"` or `"-"` propagation; the backward solve flips the diff --git a/scripts/generate_coverage_badge.py b/scripts/generate_coverage_badge.py new file mode 100644 index 0000000..a55b12e --- /dev/null +++ b/scripts/generate_coverage_badge.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +"""Generate a small local SVG coverage badge from coverage.py XML output.""" + +from __future__ import annotations + +import argparse +import html +from pathlib import Path +import xml.etree.ElementTree as ET + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("coverage_xml", type=Path, nargs="?", default=Path("coverage.xml")) + parser.add_argument("output_svg", type=Path, nargs="?", default=Path("docs/assets/coverage.svg")) + args = parser.parse_args() + + percent = coverage_percent(args.coverage_xml) + args.output_svg.parent.mkdir(parents=True, exist_ok=True) + args.output_svg.write_text(render_badge("coverage", f"{percent:.0f}%", color_for_percent(percent)), encoding="utf-8") + + +def coverage_percent(path: Path) -> float: + root = ET.parse(path).getroot() + line_rate = root.attrib.get("line-rate") + if line_rate is not None: + return float(line_rate) * 100.0 + + lines_valid = int(root.attrib["lines-valid"]) + lines_covered = int(root.attrib["lines-covered"]) + if lines_valid == 0: + return 100.0 + return lines_covered / lines_valid * 100.0 + + +def color_for_percent(percent: float) -> str: + if percent >= 90.0: + return "#4c1" + if percent >= 80.0: + return "#97ca00" + if percent >= 70.0: + return "#a4a61d" + if percent >= 60.0: + return "#dfb317" + return "#e05d44" + + +def render_badge(label: str, message: str, color: str) -> str: + label = html.escape(label) + message = html.escape(message) + label_width = max(50, len(label) * 7 + 10) + message_width = max(36, len(message) * 7 + 10) + width = label_width + message_width + label_center = label_width / 2 + message_center = label_width + message_width / 2 + return f""" + {label}: {message} + + + + + + + + + + + + + + {label} + {label} + {message} + {message} + + +""" + + +if __name__ == "__main__": + main() From e988c4f40a56b5a0685a1576e7b56d2f499bbd5e Mon Sep 17 00:00:00 2001 From: Quentin Wach Date: Sun, 17 May 2026 14:50:56 +0200 Subject: [PATCH 6/6] Refactor README.md for clarity and structure - Updated section headers for improved readability, changing "Physics Model" to "Physics" and "High Performance" to "Performance." - Streamlined the description of modal fields and removed redundant line breaks for better flow. - Retained essential details while enhancing the overall presentation of the documentation. --- README.md | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 95aceba..1704672 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ data.to_hdf5("modes.h5") ``` -## Physics Model +## Physics MicroMode solves the source-free frequency-domain Maxwell equations on a rasterized Yee mode plane, @@ -68,12 +68,7 @@ $$ \qquad \nabla\times\mathbf{H}=i\omega\epsilon\mathbf{E}, $$ - -with modal fields - -$$ -\mathbf{E},\mathbf{H}\propto e^{i k_0 n_\mathrm{eff} z}. -$$ +with modal fields $\mathbf{E},\mathbf{H}\propto e^{i k_0 n_\mathrm{eff} z}.$ On diagonal material grids this becomes a transverse eigenproblem, @@ -91,7 +86,7 @@ the public solver controls are summarized in [docs/mode-solver-methods.md](docs/mode-solver-methods.md). -## High Performance +## Performance MicroMode is designed to make high-performance mode solving available without requiring users to install external solver stacks. The production backend is a @@ -119,14 +114,4 @@ around the requested effective index. The Arnoldi stage uses **shift-invert**, adaptive [Ritz-pair](https://en.wikipedia.org/wiki/Ritz_method) checkpointing, early stopping once requested modes are stable, and selective Ritz vector -reconstruction so work is spent on the modes that will actually be returned. - -For local performance checks, use the backend benchmark: - -```bash -uv run python benchmarks/micromode_backend_benchmark.py --grid 20x14 --grid 32x22 -``` - -It records backend name, operator size, nonzero count, residuals, elapsed time, -and solved effective indices so timing claims can be tied to a specific machine, -commit, and problem shape. +reconstruction so work is spent on the modes that will actually be returned. \ No newline at end of file