Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/development/source-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ research cases; inspect its scope before launching a long calculation.
| --- | --- | --- |
| Material response or SI conversion | [test_common_material_models.py](https://github.com/fieldalgo/wavebit/blob/main/tests/materials/test_common_material_models.py), [test_lifecycle.py](https://github.com/fieldalgo/wavebit/blob/main/tests/fdtd/test_lifecycle.py) | Equivalent geometry units, SI conductivity/poles, supported causal material admission and intact derivative graphs. |
| RCWA preparation, live values or result ownership | [test_unified_rcwa.py](https://github.com/fieldalgo/wavebit/blob/main/tests/api/test_unified_rcwa.py) | Reuse versus fresh preparation, independent Fresnel fields/powers, invalid transformed inputs and earlier-result independence. |
| RCWA solve path (pure 1D TE/TM, conical or 2D) | [test_unified_rcwa_pure_1d.py](https://github.com/fieldalgo/wavebit/blob/main/tests/api/test_unified_rcwa_pure_1d.py), [test_tetm_off_the_pure_path.py](https://github.com/fieldalgo/wavebit/blob/main/tests/rcwa/test_tetm_off_the_pure_path.py) | A polarization word against the pair it names: every observable and derivative to round-off where the word takes the pure path, bit for bit where it falls back, and the reported path against the kernel actually called. |
| Coherent sources and absolute power | [test_unified_rcwa_sources.py](https://github.com/fieldalgo/wavebit/blob/main/tests/api/test_unified_rcwa_sources.py) | SI source strength, finite observation regions and independently evaluated fields. |
| FDTD domain, source/monitor or reuse | [test_lifecycle.py](https://github.com/fieldalgo/wavebit/blob/main/tests/fdtd/test_lifecycle.py) | PML outside the physical domain, unchanged laboratory coordinates, live source/material reuse and resolved duration guards. |
| RCWA modal or reconstruction algebra | [test_smm_field_state.py](https://github.com/fieldalgo/wavebit/blob/main/tests/rcwa/test_smm_field_state.py), [test_lossy_media.py](https://github.com/fieldalgo/wavebit/blob/main/tests/rcwa/test_lossy_media.py) | Independent interior fields and interface responses rather than a regenerated solver golden. |
Expand Down
16 changes: 11 additions & 5 deletions docs/guide/rcwa/anisotropic-materials.md
Original file line number Diff line number Diff line change
Expand Up @@ -492,14 +492,15 @@ on 2026-08-13 noted against each part, because the reasoning is what a future ax
need and the code alone does not carry it.

**The principle is already in the codebase.** `engine.py::_assign_grating_type` reads the `structure`
shape, `tetm` and `phi` and picks the cheapest formulation that can express the problem — its
shape, the incidence's declaration (`tetm`, or a polarization word `'s'` / `'p'`) and `phi` and `ky`,
and picks the cheapest formulation that can express the problem — its
docstring opens *"Pick the formulation this scene needs: pure 1D TE/TM, 1D conical, or 2D."*
1D TE/TM splits into scalars, 1D conical does not, 2D couples everything. Anisotropy adds axes to
that same decision rather than a new kind of decision.

| axis | read from | branches |
|---|---|---|
| grating dimensionality *(exists)* | `structure` shape, `tetm`, `phi` | 1D TE/TM, 1D conical, 2D |
| grating dimensionality *(exists)* | `structure` shape, `tetm` or a polarization word, `phi`, `ky` | 1D TE/TM, 1D conical, 2D |
| **z row of the tensor** *(exists)* | whether the four z-row slots were given | second-order, first-order |
| **permeability** | `mu != 1` | existing operator, magnetic operator |
| **patterned or uniform** | layer has in-plane structure | factorization, or scaled identities |
Expand All @@ -521,8 +522,11 @@ answered for s light whichever of the two it named. The message names the pair t
the same light, `polarization=(1, 0)` / `(0, 1)` at `phi = 0`, and
`tests/rcwa/test_tetm_off_the_pure_path.py` checks that it does: beside an all-zero z row, and under
a forced `'first_order'`, the pair reproduces the pure `tetm` solve. `tetm` belongs to the
earlier interface only: `rcwa.PlaneWave` always names the polarization (`'s'`, `'p'` or a Jones
pair), so the current lifecycle never meets this refusal.
earlier interface only, so the current lifecycle never meets this refusal. It reaches the pure
path through a polarization word instead (`PlaneWave(polarization='s')` / `'p'`, since
2026-11-03), which declares the polarization fixed rather than absent and so is never refused:
beside a z row, under `'first_order'`, on a skewed cell or with a free azimuth it solves as the
pair it names, on the path that carries it (`engine.py::_polarization_word_holds`).

### Why it has to be automatic

Expand Down Expand Up @@ -576,7 +580,9 @@ exactly this reason. A silent 8x is indistinguishable from a slow machine, and a
cannot say which formulation it measured is not a benchmark. The attribute is
`solver.formulation_assigned`, `'second_order'` or `'first_order'`, set by every 2D solve and `None`
before the first one. Both are attributes of the earlier interface's solver. The current
`rcwa.Result` does not report the formulation or the path that ran yet, and the check report's
`rcwa.Result` reports the path each wavelength's solve took, `diagnostics['solve_path']`
(`'1d_te'`, `'1d_tm'`, `'1d_conical'` or `'2d'`), and the check report names the one a run will
take, `resolved['solve_path']`. It does not report which formulation a 2D solve assigned yet:
`resolved['formulation']` is the requested value (`'auto'` by default), not the one assigned.

## References
Expand Down
11 changes: 9 additions & 2 deletions docs/guide/rcwa/fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,19 @@ The time convention is `e^{-iωt}`: the instantaneous real field is
`real(E * exp(-1j*omega*t))`. `abs(E)` and `angle(E)` give each component's magnitude
and phase. See [conventions](../../reference/common/conventions.md) for the source phase reference.

Every input is an electric amplitude: coupled s/p, `PlaneWave`, and the earlier interface's
pure 1D TM solve included. (Until 2026-09-27 the pure 1D TM solve was lit by a unit magnetic
Every input is an electric amplitude: coupled s/p, `PlaneWave`, and the pure 1D TM solve
included. (Until 2026-09-27 the pure 1D TM solve was lit by a unit magnetic
input, so its field was `mu_inc/n_inc` of the conical p field for the same light.) The
result API preserves the solved input's scale; it does not rescale the field by incident
power.

A `PlaneWave` written `'s'` or `'p'` on a one-row raster solves on that pure TE/TM path
([the light guide](light.md#the-words-are-a-declaration-the-pair-is-a-value) says when), and its
`field` and `boundary_fields` carry the same six components as the conical path's, to round-off.
The three the polarization does not excite -- `E_x`, `E_z` and `H_y` for `'s'`, `E_y`, `H_x` and
`H_z` for `'p'` -- are exact zeros there, where the conical path returns round-off (1e-16 of the
excited components).

## Boundary waves and polarization variants

`side='top'` or `'bottom'` selects the outside half-space limit at that interface.
Expand Down
65 changes: 56 additions & 9 deletions docs/guide/rcwa/light.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ Plane-wave amplitude is in V/m. Polarization is `"s"`, `"p"`, or a complex `(s,p
The [reference](../../reference/rcwa/index.md) lists their constructors. The sections below give the details of direction, polarization and incidence side.

:::{note}
Three features below are not in the current API yet, so their passages use the earlier
`wavebit.compat.legacy` interface and say so: the pure 1D `tetm` declaration, the `psi`/`chi`
polarization coordinates, and the one-solve s/p responses `s_inc`/`p_inc`.
Two features below are not in the current API yet, so their passages use the earlier
`wavebit.compat.legacy` interface and say so: the `psi`/`chi` polarization coordinates, and the
one-solve s/p responses `s_inc`/`p_inc`. A third, the pure 1D `tetm` declaration, is now the
words `'s'` and `'p'` (below).
:::

Specify the incident direction of each source with either angles or transverse wavevector
Expand Down Expand Up @@ -178,8 +179,10 @@ that reaches the far side.

## The polarization comes in three kinds

The current `PlaneWave` takes the third kind, `polarization='s'`, `'p'` or `(a_s, a_p)`. The
first two are earlier-interface declarations that the current API does not have yet.
The current `PlaneWave` takes `polarization='s'`, `'p'` or `(a_s, a_p)`. The pair is the third
kind below, and the words now do the first kind's work: they declare the polarization fixed (see
"The words are a declaration" below). The second kind, the coordinates, is an earlier-interface
form the current API does not have yet.

:::{admonition} Earlier interface
:class: earlier-interface
Expand All @@ -203,7 +206,8 @@ rcwa.PlaneWave(polarization=(1, -1j)) # the pair, straight from anothe
Retired with `wavebit.compat` on 2026-10-15; this runs only on a historical checkout.

```python
# Earlier interface: the current API has no tetm or psi/chi declaration yet.
# Earlier interface: the current API has no psi/chi declaration yet, and tetm's pure path
# is taken by polarization='s' / 'p'.
import numpy as np
from wavebit.compat import legacy as wavebit

Expand All @@ -213,9 +217,52 @@ wavebit.rcwa(psi=0.4, chi=np.pi / 4, ...) # circular
```
:::

The current `polarization='s'` / `'p'` at `phi=0` runs the conical formulation: on a one-row
raster grating its amplitudes and efficiencies match `tetm='te'` / `'tm'` to 5e-15 (measured
2026-10-15), without the pure path's speed.
### The words are a declaration, the pair is a value

`polarization='s'` or `'p'` says the polarization is fixed: not a parameter anyone sweeps or
differentiates. That is what the pure 1D TE/TM formulation needs, so since 2026-11-03 a word
takes it wherever it answers the same light: every layer a one-row raster (`Layer(epsilon=...)`
of shape `(1, Nx)`), an axis-aligned cell, and `phi` and `ky` absent or a zero that nothing
traces or differentiates. `'s'` is TE and `'p'` is TM. Before that date every one-row raster
solved on the conical formulation.

Everywhere else a word solves exactly as the pair it names, `(1, 0)` or `(0, 1)`, and nothing is
refused:

- a skewed cell, whose grooves do not run along y, solves on the 2D path;
- a nonzero `phi` or `ky`, or one that is traced or differentiated (`jax.jit`, `jax.grad`,
`vmap`, a `requires_grad` tensor), solves on the conical path, the one that carries an azimuth
and its derivative;
- `Numerics(formulation='first_order')` solves on the 2D layout it is written on;
- `Outputs(scattering=True)` stays conical, because the requested matrix keeps both
polarizations' ports (`2N x 2N`, channel `'sp'`) and the pure path's has one polarization's.

A Jones pair is a value, and always keeps the conical path, `(1, 0)` included: it may be swept
or differentiated. The two formulations agree to round-off, 2e-13 relative or better on every
amplitude, efficiency, power, field and boundary field across the cases in
`tests/api/test_unified_rcwa_pure_1d.py`, including their JAX and PyTorch gradients. The pure
one is faster and smaller; see "Cost" below. `check(...).resolved['solve_path']` names the
path a run will take, and `result.diagnostics['solve_path']` the one each wavelength took:
`'1d_te'`, `'1d_tm'`, `'1d_conical'` or `'2d'`. The first is read from the concrete problem
`check` or `prepare` was given, so a prepared run that traces `phi` -- inside `jax.jit`, say --
reports the plan `'1d_te'` and records `'1d_conical'`, the path it took.

#### Cost

Measured on 2026-11-03 through the public `rcwa.run`: NumPy on a 10-core laptop, a
1000 nm cell holding a 560 nm bar of permittivity 12.25 in air, 200 nm thick, at 1363 nm and
normal incidence, a 50-pixel raster with `fourier_coefficients='integrated'`, `'p'` against
`(0, 1)`. Times are the best of 7, 3 and 1 runs. Memory is the process's peak footprint above its
baseline during the run.

| `orders` | `'p'` (pure TM) | `(0, 1)` (conical) | time ratio | peak memory, pure / conical | `sum(R)` apart |
| --- | --- | --- | --- | --- | --- |
| `(50, 0)` | 0.017 s | 0.045 s | 2.7x | 1.8 / 26 MB | 5e-13 |
| `(200, 0)` | 0.29 s | 1.22 s | 4.1x | 31 / 251 MB | 8e-12 |
| `(800, 0)` | 10.8 s | 57.8 s | 5.3x | 0.39 / 5.9 GB | 4e-10 |

The conical layout solves matrices twice the side of the pure one's, so the gap in time and
memory widens with the truncation, and so does the last-digit difference between the two answers.

:::{admonition} Earlier interface
:class: earlier-interface
Expand Down
2 changes: 2 additions & 0 deletions docs/guide/rcwa/numerics.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ numerics = rcwa.Numerics(

`orders=(Mu, Mv)` retains orders −Mu…Mu along the first lattice vector and −Mv…Mv along the second; it is always an explicit pair of nonnegative integers (`numerics.orders` refuses a bare integer). Result arrays place the wavelength axis first, then v, then u. Algorithms are `"smm"` or `"etm"`; formulations are `"auto"`, `"first_order"` or `"second_order"`. Factorization choices are explicit, with support checked against the material and geometry. The example's small settings are for a quick calculation, not a convergence recommendation.

The solve path is not a setting: the structure and the source decide whether a run takes the pure 1D TE/TM path, the 1D conical path or the 2D one ([the light guide](light.md#the-words-are-a-declaration-the-pair-is-a-value) says how). `rcwa.check(...).resolved['solve_path']` names the path a run will take, and `result.diagnostics['solve_path']` records the one each wavelength took: `'1d_te'`, `'1d_tm'`, `'1d_conical'` or `'2d'`.

Rayleigh detuning is opt-in: `detuning=None` requests the exact physical input. A positive `detuning` declares a relative step and the result records the changed coordinates. Never interpret a detuned result as the unmodified requested wavelength without reading that metadata.

`topology_check='device'` uses available array guards and refuses other traced geometry.
Expand Down
4 changes: 3 additions & 1 deletion docs/guide/rcwa/scene.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,4 +217,6 @@ result = rcwa.run(problem, numerics=rcwa.Numerics(orders=(10, 0)))
Each value has one place, so there is no second spelling for it to collide with: the cell,
the half-spaces and the layers in `Stack`, the wavelength in `Excitation`, direction and
polarization in the source, truncation in `Numerics`. The earlier `tetm='te'` is
`polarization='s'` at `phi=0.` here.
`polarization='s'` here: on a one-row `raster` it takes the same pure TE/TM path
(`result.diagnostics['solve_path']` reads `('1d_te',)`), where a Jones pair would take the
conical one. See [the light guide](light.md#the-words-are-a-declaration-the-pair-is-a-value).
3 changes: 2 additions & 1 deletion docs/learn/fundamentals/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
:::{note}
The downloadable teaching scripts, the text and the snippets of every chapter use the current
`rcwa.Problem` / `rcwa.run` API. A few passages describe features the current API does not have
yet, such as the `psi`/`chi` polarization angles and the pure 1D `tetm` path; they sit in boxes
yet, such as the `psi`/`chi` polarization angles and the earlier `tetm` declaration (whose pure 1D
path the words `"s"` and `"p"` now take); they sit in boxes
titled "Earlier interface" (retired with `wavebit.compat` on 2026-10-15). Some figures keep the
earlier labels, and the chapters that show them name the current equivalents. Compare the
[current getting-started guide](../../getting-started/index.md) and
Expand Down
4 changes: 2 additions & 2 deletions docs/learn/fundamentals/light.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,12 +257,12 @@ te_result = rcwa.run(grating("s"), numerics=numerics) # TE
tm_result = rcwa.run(grating("p"), numerics=numerics) # TM
```

These run the general (conical) formulation, which also handles 2D patterns and nonzero `ky`. On a one-row raster grating at `phi=0` its amplitudes and efficiencies match a dedicated TE/TM solve to 5e-15 (measured 2026-10-15); it does not have that solve's speed.
On a one-row raster grating like this one, with `phi` and `ky` absent or zero, these run the dedicated TE/TM solve: the words `"s"` and `"p"` declare the polarization fixed, and that is what the solve needs (since 2026-11-03; `result.diagnostics["solve_path"]` reads `("1d_te",)` or `("1d_tm",)`). Written as a Jones pair, `(1, 0)` or `(0, 1)`, the same light runs the general (conical) formulation, which also handles 2D patterns and nonzero `ky`; the two agree to round-off, and the dedicated solve is several times faster on a fine truncation. A skewed cell, a nonzero or differentiated azimuth, and a requested scattering matrix keep the general formulation for the words too.

:::{admonition} Earlier interface
:class: earlier-interface

The earlier interface (retired with `wavebit.compat` on 2026-10-15) had a dedicated declaration for this case: `tetm="te"` or `tetm="tm"` solved TE or TM alone, 2 to 4 times faster than the conical formulation. It required `ky` to be zero (omitted, or explicitly `ky=0.`); nonzero `ky` raised an error. Because it declared a problem with no polarization degree of freedom, it could not be combined with `psi`, `chi` or `polarization`. The current API has no `tetm` declaration yet.
The earlier interface (retired with `wavebit.compat` on 2026-10-15) had a dedicated declaration for this case: `tetm="te"` or `tetm="tm"` solved TE or TM alone, 2 to 4 times faster than the conical formulation. It required `ky` to be zero (omitted, or explicitly `ky=0.`); nonzero `ky` raised an error. Because it declared a problem with no polarization degree of freedom, it could not be combined with `psi`, `chi` or `polarization`. In the current API the words `"s"` and `"p"` take that solve where it applies, and refuse nothing where it does not.

```python
# Earlier interface only; runs on a historical checkout.
Expand Down
3 changes: 2 additions & 1 deletion docs/learn/fundamentals/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
:::{note}
The downloadable teaching scripts, the text and the snippets of every chapter use the current
`rcwa.Problem` / `rcwa.run` API. A few passages describe features the current API does not have
yet, such as the `psi`/`chi` polarization angles and the pure 1D `tetm` path; they sit in boxes
yet, such as the `psi`/`chi` polarization angles and the earlier `tetm` declaration (whose pure 1D
path the words `"s"` and `"p"` now take); they sit in boxes
titled "Earlier interface" (retired with `wavebit.compat` on 2026-10-15). Some figures keep the
earlier labels, and the chapters that show them name the current equivalents. Compare the
[current getting-started guide](../../getting-started/index.md) and
Expand Down
1 change: 1 addition & 0 deletions docs/locales/ko/pages/development/source-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
| --- | --- | --- |
| 재료 응답 또는 SI 변환 | [test_common_material_models.py](https://github.com/fieldalgo/wavebit/blob/main/tests/materials/test_common_material_models.py), [test_lifecycle.py](https://github.com/fieldalgo/wavebit/blob/main/tests/fdtd/test_lifecycle.py) | 다른 기하 단위로 표현한 동일 문제, SI 전도도·극점, 지원하는 인과적 재료와 미분 연결을 확인합니다. |
| RCWA 준비, 실행 시점 값 또는 결과 소유권 | [test_unified_rcwa.py](https://github.com/fieldalgo/wavebit/blob/main/tests/api/test_unified_rcwa.py) | 재사용과 새 준비의 일치, 독립 Fresnel 장·전력, 변환 중 잘못된 입력, 이전 결과의 독립성을 검사합니다. |
| RCWA 풀이 경로(순수 1D TE/TM, conical, 2D) | [test_unified_rcwa_pure_1d.py](https://github.com/fieldalgo/wavebit/blob/main/tests/api/test_unified_rcwa_pure_1d.py), [test_tetm_off_the_pure_path.py](https://github.com/fieldalgo/wavebit/blob/main/tests/rcwa/test_tetm_off_the_pure_path.py) | 편광 낱말과 그 낱말이 가리키는 쌍을 비교합니다. 낱말이 순수 경로를 타면 모든 관측량과 미분이 반올림 오차 안에서 같고, 물러나면 비트 단위로 같으며, 보고한 경로가 실제로 호출된 커널과 맞는지 확인합니다. |
| 결맞는 소스와 절대 전력 | [test_unified_rcwa_sources.py](https://github.com/fieldalgo/wavebit/blob/main/tests/api/test_unified_rcwa_sources.py) | SI 소스 세기, 유한한 관측 영역과 독립적으로 평가한 장을 확인합니다. |
| FDTD 영역, 소스·모니터 또는 재사용 | [test_lifecycle.py](https://github.com/fieldalgo/wavebit/blob/main/tests/fdtd/test_lifecycle.py) | 물리 영역 바깥 PML, 고정된 실험실 좌표, 소스·재료 값 재사용, 실제 실행 시간 제약을 확인합니다. |
| RCWA 모드 또는 장 복원 연산 | [test_smm_field_state.py](https://github.com/fieldalgo/wavebit/blob/main/tests/rcwa/test_smm_field_state.py), [test_lossy_media.py](https://github.com/fieldalgo/wavebit/blob/main/tests/rcwa/test_lossy_media.py) | 솔버 golden을 다시 만드는 대신 독립적인 내부 장과 계면 응답으로 확인합니다. |
Expand Down
Loading
Loading