-
Notifications
You must be signed in to change notification settings - Fork 0
roadmap
Where this project is, where it is going, and why. Start here.
An open platform for grating groove metrology and efficiency analysis. Not "another RCWA" — open RCWA is a crowded field (S4, grcwa, nannos, torcwa, inkstone, RETICOLO). What does not exist openly is:
- A neutral cross-method harness with one problem spec and rigorous conventions.
- An open C-method (Chandezon) solver.
- An open integral-method solver.
- A path from a measured surface to a rigorous efficiency, open end to end.
The fourth arrived with M17, and is the one with no open equivalent at all. It also immediately earned its keep by falsifying something — see the TASTE entry under Open questions.
The organising idea: the physical problem and the numerical method are
separate, and the problem spec is serializable. One Problem reaches every
solver, so disagreement between methods becomes visible instead of hidden behind
incompatible conventions.
Deliberate consequence: RCWA is a reference backend, not the product.
- Soft X-ray, extreme off-plane (γ ≈ 1.5°) — the application. Where the commercial integral-method codes dominate and general-purpose open RCWA is weakest.
- UV–Vis–NIR, in-plane — the validation regime. Nearly all published benchmark data (Moharam & Gaylord, Li, Popov/Nevière) lives here. You cannot easily validate an off-plane X-ray solver against literature; you can validate the solver in the visible and then apply it at 2 nm.
| Component | Status |
|---|---|
Conventions (conventions.md) |
normative, settled |
| Geometry — grating equation, orders, blaze relations | done |
| Illumination — direction cosines, three mount constructors | done |
| Profiles — 4 representations for 4 methods, undercut support | done |
Problem, Solver protocol, registry |
done |
| Scalar solver (theory) | done, validated; M19 added the reduced-wavelength validity guard, cast-shadow visibility (visibility="horizon", opt-in, sub-cell boundaries), and the s/p degeneracy price tag |
| Efficiency-table importer (external codes) | done |
.ggp boundary profiles |
done, and now a single implementation |
| Groove metrology (theory) — AFM scans, blaze angles, boundary profiles | done, absorbed M17 |
| Measured groove → solver, in-process | done, BoundaryProfile.to_problem
|
| Comparison harness | done; tests/test_cross_method.py commits the scalar-vs-integral agreement and divergence ladders, no corpus needed |
| Physics self-checks — reciprocity, energy balance | done |
Convergence harness (convergence.py) |
done |
Progress + cancellation on the Solver protocol |
done |
Mutation testing (mutation-testing.md) |
done, on demand; checks.py and geometry.py at 100% |
| Materials — CXRO constants, Fresnel, roughness, absolute efficiency | done |
Resolving power (resolution.py) |
done: `R = |
Tip deconvolution (core/tip.py, theory §4b) |
done at M22: Villarrubia erosion with the certainty map, opt-in via tip_correction='erosion', validated pointwise on a committed dilated fixture. Ran on the real TASTE scan and narrowed the rounded-groove open question: a nominal tip is excluded (findings.md) |
| GUI (Qt/PySide6) — tabs, geometry dock, 3D conical view | done |
| CI (Linux + macOS, py3.11/3.12) | green |
| RCWA | contributed, not yet integrated |
| C-method | not started |
| Integral method (theory) |
perfect-conductivity and finite-conductivity milestones done: conical from day one, undercut-capable, GUI tab, validated against M&P Table 4.1 and orderwise against PCGrate to 8e-4 (which also identified the TASTE profile). M20 added conductivity="tabulated" — the coupled conical system of Goray & Schmidt (2010), absolute efficiencies with absorption recorded, flat limit = Fresnel to 6e-5, their Table 3 reproduced per order, and both boundary conditions selectable in the GUI. Graded corner mesh is the named successor |
Roughly 1300 tests, 11 skipped (the skips need the private reference corpus).
Rounded on purpose: this line previously claimed 328 and was wrong by 400, so
pytest -q is the authority and this is only the order of magnitude. The jump
from ~945 is the metrology suite arriving with its package, not new coverage.
An earlier revision of this file said "do not attempt the integral method before the first release" and slotted it as phase 5. That ordering was reversed, for three reasons the earlier revision could not have known:
- The corpus can only validate an integral solver. The one rigorous reference dataset on disk is perfect-conductivity integral-method output (PCGrate). Scalar could never test more than the diffraction pattern against it; a like-for-like comparison needed a like method.
- The off-plane X-ray application is the project's point, and the integral method is the method of record there. The conical decoupling theorem makes the perfectly conducting milestone cheaper at grazing incidence, not harder — the reduced wavelength λ/sin γ is what the mesh resolves.
- RCWA is contributed work, blocked on a contributor. The second-implementation stress test of the solver protocol (Capabilities, progress, tabs, convergence knob) happened with this backend instead — and the protocol held; see the current-state table.
The gamble paid off immediately: the first rigorous-vs-rigorous comparison
reproduced PCGrate orderwise to 8e-4 and identified the TASTE run's
previously unconfirmed groove profile (findings.md).
Remaining integral milestones, in order:
-
Finite conductivity— done at M20: the coupled conical system of Goray & Schmidt (2010),conductivity="tabulated", absolute efficiencies with the absorbed fraction recorded andR + A = 1a checkable theorem (theory/integral.md§8). The Leontovich rung was deliberately skipped — it requires |n| ≫ 1 and is invalid in the soft-X-ray regime the milestone exists for, and it builds no operators the full system reuses. Validated without a PCGrate reference run (see the narrowed open question below): flat interface = Fresnel exactly, the energy theorems two-sided, the perfect-conductor limit against the already-banked 8e-4 PCGrate agreement, and Goray & Schmidt's own Table 3 reproduced within 6e-5 per order — which also exposed their Table 4 as a publisher's duplication (findings.md). -
Graded corner mesh (M&P §4.6.6) — TM on cornered profiles currently
converges first-order, and with finite conductivity both polarizations
do; see
theory/integral.md§5. -
A solver-contract conformance test— done, see the note under RCWA below.
Not ours to write. fgrise has RCWA code. The physics list below is
therefore a set of requirements to state to a contributor rather than a plan
of work, and the more useful half of this section is what any backend has to
satisfy to register at all.
Physics, unchanged and non-negotiable:
- Li's Fourier factorization rules from day one, or TM metallic cases converge badly and the comparison plots will misrepresent the method.
- S-matrix / enhanced transmittance-matrix propagation, never plain T-matrix.
- Full vectorial conical formulation — off-plane is not an afterthought.
What the framework asks:
| Requirement | Why, and what reads it |
|---|---|
A Capabilities declaration |
conical, polarizations, handles_undercut. Capabilities.check refuses an out-of-scope case before solving rather than approximating it |
accuracy_knob naming the truncation parameter |
convergence.check_convergence sweeps it. Without one, a result can never report converged=True
|
reports_progress, if solve takes a progress keyword |
Opt-in, so a backend predating the hook is never handed a keyword its signature cannot take. It also makes the solver cancellable — that callback is the only check point a NumPy-bound solve has |
UnsupportedConfiguration rather than a quiet approximation |
The rule the whole comparison rests on: a method silently smoothing a vertical facet returns a plausible, wrong number |
One entry in _TAB_FACTORIES
|
Tabs are generated from available_solvers(); the GUI needs nothing else |
The seams have now met their second implementation — the integral
backend. What the earlier warning here predicted mostly did not happen: the
Capabilities declaration, the progress/cancellation contract, the
convergence knob, and the per-solver tab pattern all took a slow, rigorous,
polarization-resolving backend without changing shape. Two things did give:
the window's "solve on open" needed to prefer the fast solver once a
minutes-long one existed, and the tab layer is now two near-identical files
— the evidence the base-class extraction was waiting for.
A conformance test every registered solver must pass is now
written and parametrised over
available_solvers(), so a contributed backend is held to the declaration,
refusal, scan and progress contracts the moment it registers — before anyone
writes a line of test for it specifically.
gratinglab.resolution now answers the ideal-grating question: R = |m|·N
by the Rayleigh criterion, and the finite-N line profile it is derived from
(interference_factor, moved from the scalar solver to geometry where
non-solver consumers can reach it). The measured groove count arrives through
BoundaryProfile.to_problem, so an AFM scan flows to a spectrograph number
in-process. A problem without n_grooves is refused, not treated as infinite.
The successor is groove-error-degraded resolving power: the line-spread of
a real groove ensemble — period jitter and placement errors, the quantities
metrology already half-holds (y_std_nm, groove_centers) — with the
Rayleigh criterion applied to the degraded profile. That is the link between
groove error metrology and spectrograph performance, and it is deliberately
not approximated by the ideal formula now in the API.
PyXFocus (the sequential X-ray raytracer this project's author forked) stays
where it is: unpackaged, numpy<2-pinned, Fortran-built — absorbing it would
buy a liability, not a capability. What is worth porting is small and
analytic, from its grating.py:
-
blazeYaw— the Littrow condition, asgeometry.littrow_alphaplus anIllumination.littrow(order, wavelength, period, graze)constructor. -
blazeAngle/blazeAngle2— the facet angle Littrow requires at (λ, m). First verify numerically that the two agree and pin the frame mapping (itscos inc↔ oursin γ) againstIllumination.offplane, the §10-of-conventions discipline: transcribe, test, then port one form. -
litBetaAlpha,eta,yaw— already representable withIlluminationmachinery; cover with tests rather than porting. -
radialApproxEffect— not ported: radial/variable-line-space physics thatProblemcannot represent (and a livepdb.set_trace()in the source). Radial/VLS gratings are a "Later" item.
Its raytraced resolving-power estimator (order-centroid spacing over spot
width) is the instrument-level cross-check of resolution.py's grating-level
number; that comparison belongs with any future instrument layer, not here.
Villarrubia (1997) morphological erosion with the certainty map, exactly
as planned: a parametric cone-plus-spherical-apex tip, a stage in
metrology/core/tip.py on the 2-D array after image flattening in both
branches, opt-in through AnalysisSettings (tip_correction,
tip_radius_nm, tip_half_angle_deg), the certain fraction recorded in the
metrics, the sidecar and the log. Validated on a committed dilated fixture —
the sharp synthetic scan imaged through a known worn tip with the identical
noise field, so recovery is asserted pointwise: machine precision on clean
data, the noise floor (0.18 nm mean) on the fixture, against a 9 nm mean
uncorrected error. theory/metrology.md §4b carries
the three load-bearing statements (a bound, not a resurrection; the certainty
map is the deliverable; a facet steeper than the flank is gone outright).
The run on the real TASTE scan produced the milestone's finding
(findings.md, "A nominal tip does not explain the rounded
groove"): erosion with the nominal probe (R ≈ 1–2 nm, 18°) recovers no
depth — the measured surface is already tip-reachable — and reproducing the
depth deficit forward requires an apex worn to ~80 nm. The open question
narrows from "grating or tip?" to "grating, or a severely worn tip?", which
is answerable outside the software: the probe's condition at scan time, a
re-scan with a fresh tip, or a cross-section.
What deliberately did not land: measured tip images as input (blind tip
estimation is the natural successor if a worn tip becomes the suspect), and
any coupling from the certainty map into to_problem — an uncertain-trough
profile still converts, and the defensibility warning remains the
depth-vs-facet-angle diagnostic named in the finding.
.ggp cannot carry the period in nm, provenance, undercut boundaries, or a
format version. The missing period is exactly why
benchmarks/corpus.toml has to exist as a sidecar.
JSON, since the profile classes are already pydantic models with tested
round-tripping.
M17 changed the urgency, not the need. With metrology in the package, a
measured groove reaches a solver through BoundaryProfile.to_problem without
touching disk, carrying its measured period and optionally a fitted facet
angle. That is the whole gap closed — for profiles that never leave the
process. Everything that has to be saved, sent, archived or diffed still goes
through a format that drops the period on the way out and cannot say where the
profile came from. Undercut boundaries remain unrepresentable in either
direction.
Shipping something citable and correct early is what recruits collaborators. The release story is stronger now, not weaker, for the reordering above: a rigorous open solver validated orderwise against the commercial standard is a headline, and the scalar/integral comparison plots need no PCGrate licence.
C-method, measured-profile fitting, crossed/2D gratings, radial/VLS gratings.
See references.md for the literature mapped to each.
The sibling als_eff_meas package reduces ALS beamline scans to absolute
measured efficiency per order, uncertainties carried end-to-end. It is the
measured-data half this platform still lacks — and it is also genuinely
specific to X-ray/EUV testing at one facility, so the integration is a
split, not an absorption:
-
Moves here, when taken up: a measured-efficiency data model
(
MeasuredEfficiency, a newmeasured.py) thatcompare.align/recordsaccept besideEfficiencyScan— measured vs scalar vs integral on one plot, with error bars. Not a secondEfficiencyTable(that name is taken by the modelled-table importer inio/efficiency_table.py), and not uncertainty fields grafted ontoEfficiencyScan, whose dense-grid invariants are model-shaped; measured data is sparse per order with per-point σ. Parallelvalue/sigmafloat arrays, so theuncertaintiespackage never becomes a dependency here. Also the PCGrate XML dialect from itsmodels.py, whichio/efficiency_table.py(text dialects) lacks. -
Stays there: everything beamline-shaped — file pairing, dark
subtraction, three-point flux sums, the ODR arc fit and cone-angle
recovery, TOML campaigns.
als_eff_measgrows a gratinglab dependency, deletes its duplicated Fresnel/Névot–Croce/CXRO code in favour ofgratinglab.materials, and exports ato_measured(). -
The scipy pin stays out:
als_eff_measpinsscipy<1.19becausescipy.odrbacks its arc fit and is scheduled for removal. The arc fit does not move, so the pin does not propagate — and that package must eventually migrate toscipy.optimize.least_squaresregardless; this project should not inherit the debt in the meantime.
The other siblings are settled: afm_blaze_meas is already absorbed (M17),
endstation stays the shared scaffolding dependency, ebl_fracture stays a
separate fabrication-layout tool (no optics; its own roadmap excludes blazed
and VLS geometry), and PyXFocus is §3 above — formulas, not the raytracer.
Answers to these unblock real work.
Recovered from the reference data with no assumptions:
-
period × sin γ = 9.8450 nm(±0.005) -
α = +32.92°(±0.1)
These two cannot be separated without one more fact. Candidates:
| γ | period |
|---|---|
| 1.5000° | 376.1 nm |
| 1.7902° | 315.15 nm (same grating as TASTE) |
| 2.0000° | 282.1 nm |
The title string in the original plotting script reads "η = 1.5° & φ = 0.97°".
The 1.5° is consistent with γ if the period is ~376 nm, but 0.97° is not the
azimuth — the data says α ≈ 33°, which is also far more plausible given the
~39°/27° facet angles of panter1.ggp (near-Littrow). So "φ" there means
something else, and knowing what would help read the other legacy scripts.
AFM_TASTE_test.ggp and TASTE_a205_TiPt.ggp are both candidates for the TASTE
efficiency run — and neither is present in the corpus directory, which holds
AFM_test.ggp, AFM_real_echelle.ggp and PCGrateProjects/panter1.ggp. The
filename in corpus.toml therefore does not resolve, which is itself a thing to
settle. Until it is, the comparison uses an idealised Blazed(29.5°, 70.5°),
which is probably why scalar's total sits near 0.55 rather than closer to unity.
M16 remeasured this: the total is 0.5431 for the idealised sawtooth and 0.6138
for AFM_real_echelle.ggp, against a PCGrate total of 1.0005. The flux
obliquity of M16-C moved it in the right direction but only slightly, because
this mount is near enough to Littrow that the factor is ~0.99 on the orders
carrying the power. The residual is profile mismatch, not normalisation — see
findings.md, "The corpus can test the diffraction but not the reflectivity".
M17 tested that and it failed. The metrology merge brought an AFM scan of
what is plausibly the same grating into the repo
(data/TASTE_ALS_A205_Ti_Pt_flatten.txt), so the idealised sawtooth could be
swapped for a measured groove. The total moved the wrong way: 0.5420 idealised
to 0.3362 measured, against PCGrate's 1.0005. The groove's facet fit (27.91° ±
2.13°) and its depth (implying 20.33°) disagree by 3.6σ: it is rounded rather
than faceted, and the solver reads the depth. A flat land would explain the same
deficit and was excluded (it would need 25–30% of the period; 2.9% is within 2°
of flat), as was smearing from the groove averaging (0.4%). Full evidence in
findings.md, "The measured groove is rounded, not faceted".
So the open question is no longer only "which .ggp". Whether the rounding is
the grating or the tip is undetermined, and either way an absolute efficiency
from a raw AFM boundary is not defensible yet. Nothing in the pipeline warns
about it. The arbiter has now run (§4 above, M22): erosion with the nominal
tip explains none of the 27.91°/20.33° gap, and reproducing it forward
needs an ~80 nm apex. The question is narrowed, not closed — "grating, or a
severely worn tip?" — and what settles the remainder is outside the software:
the probe's condition at scan time, a re-scan with a fresh tip, or a
cross-section. See findings.md, "A nominal tip does not explain the
rounded groove".
Narrowed at M20, not closed. The corpus still has no usable
finite-conductivity table (panter1_finite stays usable = false), but the
rigorous solver no longer waits on one: conductivity="tabulated" is
validated on the Fresnel flat limit, the two-sided energy theorems, the
perfect-conductor limit, and Goray & Schmidt's published Table 3. What a
fresh PCGrate run would still buy is a like-for-like corpus item — the
recommended recipe: TE, Au, the TASTE geometry and the identified
AFM_test.ggp groove, a wavelength grid avoiding ±0.05 nm around every
order-passing-off point (the documented failure locus of panter1_finite),
and the same .ari optical constants exported by write_ari so both codes
eat identical n(λ). Gate it with check_energy_balance before admitting it
to corpus.toml. It would also finally arbitrate the scalar solver's
groove-resolved reflectivity model, which still rests on internal
consistency plus (now) cross-checks against the rigorous absolute mode.
- Is the vendor console solver runnable (Windows VM)? Generating fresh reference cases on demand, rather than only reusing exported tables, is worth an afternoon.
- Any synchrotron or Panter measured efficiency beyond the current corpus that
could seed the benchmark suite? Partially answered:
als_eff_measholds one fully ported ALS campaign (ogre laminar, Oct 2021) with absolute per-order efficiencies and uncertainties, plus ~15 legacy per-experiment scripts in its archive that could be ported the same way. Reaching it from the comparison harness is the measured-efficiency split under "Later".
- Refuse rather than approximate. A solver asked for something outside its declared capabilities raises. Silently approximating is what makes cross-method plots lie.
- Report, never rescale. Where an approximate theory violates a conservation law, that is information. Normalising it away destroys the validity map.
-
Provenance is mandatory. A result whose convergence was never demonstrated
reports itself as not defensible — and
gratinglab.convergenceis how it stops being that.converged=Falseis a result, not a missing one. -
A control that cannot do what it says must not be offered. Cancel stops
the solver now; before it could only stop the waiting, and the panel said so
rather than implying more. A backend declaring no
reports_progressis simply not cancellable, and that is a fact to surface, not to paper over. - One agreement is not a plateau. Refinement error is not always monotone; the harness requires three consecutive knob values to agree, because the blazed case measurably violates the tempting one-agreement rule (findings).
-
Commit before mutating. Mutation scripts revert with
git checkout --, which silently fails on untracked files.