Files
Atomizer/docs/physics/ZERNIKE_OPD_METHOD.md
Anto01 ea437d360e docs: Major documentation overhaul - restructure folders, update tagline, add Getting Started guide
- Restructure docs/ folder (remove numeric prefixes):
  - 04_USER_GUIDES -> guides/
  - 05_API_REFERENCE -> api/
  - 06_PHYSICS -> physics/
  - 07_DEVELOPMENT -> development/
  - 08_ARCHIVE -> archive/
  - 09_DIAGRAMS -> diagrams/

- Replace tagline 'Talk, don't click' with 'LLM-driven optimization framework' in 9 files

- Create comprehensive docs/GETTING_STARTED.md:
  - Prerequisites and quick setup
  - Project structure overview
  - First study tutorial (Claude or manual)
  - Dashboard usage guide
  - Neural acceleration introduction

- Rewrite docs/00_INDEX.md with correct paths and modern structure

- Archive obsolete files:
  - 01_PROTOCOLS.md -> archive/historical/01_PROTOCOLS_legacy.md
  - 03_GETTING_STARTED.md -> archive/historical/
  - ATOMIZER_PODCAST_BRIEFING.md -> archive/marketing/

- Update timestamps to 2026-01-20 across all key files

- Update .gitignore to exclude docs/generated/

- Version bump: ATOMIZER_CONTEXT v1.8 -> v2.0
2026-01-20 10:03:45 -05:00

580 lines
17 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Rigorous OPD-Based Zernike Analysis for Mirror Optimization
**Document Version**: 1.0
**Created**: 2024-12-22
**Author**: Atomizer Framework
**Status**: Active
---
## Executive Summary
This document describes a **rigorous Optical Path Difference (OPD)** method for computing Zernike wavefront error that correctly accounts for **lateral (X, Y) displacements** in addition to axial (Z) displacements.
**The Problem**: Standard Zernike analysis uses only Z-displacement at the original (x, y) node positions. When supports pinch the mirror or lateral forces cause in-plane deformation, nodes shift in X and Y. The standard method is **blind to this**, potentially leading to:
- Optimized designs that appear good but have poor actual optical performance
- Optimizer convergence to non-optimal solutions that "cheat" by distorting laterally
**The Solution**: The OPD method computes the true surface error by accounting for the fact that a laterally-displaced node should be compared against the parabola height **at its new (x+dx, y+dy) position**, not its original position.
---
## Table of Contents
1. [The Optical Physics Problem](#1-the-optical-physics-problem)
2. [Mathematical Formulation](#2-mathematical-formulation)
3. [When This Matters](#3-when-this-matters)
4. [Implementation Details](#4-implementation-details)
5. [Usage Guide](#5-usage-guide)
6. [Validation and Testing](#6-validation-and-testing)
7. [Migration Guide](#7-migration-guide)
---
## 1. The Optical Physics Problem
### 1.1 What Zernike Analysis Does
Zernike polynomials decompose a wavefront error surface into orthogonal modes:
```
W(r, θ) = Σ cⱼ Zⱼ(r, θ)
```
Where:
- `W` = wavefront error (nm)
- `cⱼ` = Zernike coefficient for mode j
- `Zⱼ` = Zernike polynomial (Noll indexing)
For a reflective mirror, the wavefront error is **twice** the surface error:
```
WFE = 2 × surface_error
```
### 1.2 Standard Method (Z-Only)
The standard approach:
1. Read node original positions `(x₀, y₀, z₀)` from BDF/DAT
2. Read displacement vector `(Δx, Δy, Δz)` from OP2
3. Compute surface error = `Δz` (Z-displacement only)
4. Compute WFE = `2 × Δz × nm_scale`
5. Fit Zernike at original coordinates `(x₀, y₀)`
```python
# Standard method (simplified)
for nid, (dx, dy, dz) in displacements:
x, y, z = original_coords[nid]
wfe = dz * 2 * nm_scale # ONLY uses Z-displacement
X.append(x) # Original X
Y.append(y) # Original Y
WFE.append(wfe)
coeffs = fit_zernike(X, Y, WFE)
```
### 1.3 The Problem: Lateral Displacement is Ignored
Consider a node on a parabolic mirror:
- **Original position**: `(x₀, y₀, z₀)` where `z₀ = -r₀²/(4f)` on the parabola
- **Deformed position**: `(x₀+Δx, y₀+Δy, z₀+Δz)`
**Question**: What is the true surface error?
**Standard method says**: surface_error = `Δz`
**But this is wrong!** If the node moved laterally to a new `(x, y)`, the ideal parabola has a **different** Z at that location. The node should be compared against:
```
z_expected = parabola(x₀+Δx, y₀+Δy) = -(x₀+Δx)² + (y₀+Δy)² / (4f)
```
Not against `z₀ = parabola(x₀, y₀)`.
### 1.4 Visual Example
```
Original parabola
___
_/ \_
/ \
/ *A \ A = original node at (x₀, y₀, z₀)
/ ↗ ↘ \ B = deformed position (x₀+Δx, y₀+Δy, z₀+Δz)
/ B C \ C = where node SHOULD be if staying on parabola
/ \
/_____________________\
Standard method: error = z_B - z_A = Δz
(compares B to A vertically)
OPD method: error = z_B - z_C = Δz - Δz_parabola
(compares B to where parabola is at B's (x,y))
```
---
## 2. Mathematical Formulation
### 2.1 Differential OPD Formulation
For a paraboloid with optical axis along Z:
```
z = -r² / (4f) [concave mirror, vertex at origin]
```
Where:
- `r² = x² + y²`
- `f` = focal length
**Key Insight**: We can compute the **change** in parabola Z due to lateral movement:
```
Δz_parabola = z(x₀+Δx, y₀+Δy) - z(x₀, y₀)
= -[(x₀+Δx)² + (y₀+Δy)²] / (4f) - [-( x₀² + y₀²) / (4f)]
= -[r_def² - r₀²] / (4f)
= -Δr² / (4f)
```
Where:
```
Δr² = r_def² - r₀² = (x₀+Δx)² + (y₀+Δy)² - x₀² - y₀²
= 2·x₀·Δx + Δx² + 2·y₀·Δy + Δy²
```
### 2.2 True Surface Error
The true surface error is:
```
surface_error = Δz - Δz_parabola
= Δz - (-Δr² / 4f)
= Δz + Δr² / (4f)
```
**Interpretation**:
- If a node moves **outward** (larger r), it should also move in **-Z** to stay on the concave parabola
- If the FEA says it moved by `Δz`, but staying on the parabola requires `Δz_parabola`, the difference is the true error
- This corrects for the "false error" that the standard method counts when nodes shift laterally
### 2.3 Wavefront Error
```
WFE = 2 × surface_error × nm_scale
= 2 × (Δz - Δz_parabola) × nm_scale
```
### 2.4 Zernike Fitting Coordinates
Another subtlety: the Zernike fit should use the **deformed** coordinates `(x₀+Δx, y₀+Δy)` rather than the original coordinates. This is because the WFE surface represents the error at the positions where the nodes **actually are** after deformation.
```python
# OPD method
X_fit = x0 + dx # Deformed X
Y_fit = y0 + dy # Deformed Y
WFE = surface_error * 2 * nm_scale
coeffs = fit_zernike(X_fit, Y_fit, WFE)
```
---
## 3. When This Matters
### 3.1 Magnitude Analysis
The correction term is:
```
Δz_parabola = -Δr² / (4f) ≈ -(2·x₀·Δx + 2·y₀·Δy) / (4f) [ignoring Δx², Δy²]
≈ -(x₀·Δx + y₀·Δy) / (2f)
```
For a node at radius `r₀` with tangential displacement `Δ_tangential`:
- The correction is approximately: `r₀ · Δ_lateral / (2f)`
**Example**: Mirror with f = 5000 mm, outer radius = 400 mm
- Node at r = 400 mm shifts laterally by Δx = 0.001 mm (1 µm)
- Correction: `400 × 0.001 / (2 × 5000) = 0.00004 mm = 40 nm`
This is **significant** when typical WFE is in the 10-100 nm range!
### 3.2 Classification by Load Case
| Load Case | Lateral Disp. | Method Impact |
|-----------|--------------|---------------|
| **Axial support** (gravity in Z) | Very small | Minimal - both methods similar |
| **Lateral support** (gravity in X/Y) | **Large** | **Significant** - OPD method required |
| **Clamp/fixture forces** | Can be large locally | May be significant at pinch points |
| **Thermal** | Variable | Depends on thermal gradients |
| **Mirror cell deflection** | Variable | Check lateral displacement magnitude |
### 3.3 Diagnostic Thresholds
The `ZernikeOPDExtractor` provides lateral displacement statistics:
| Max Lateral Disp. | Recommendation |
|-------------------|----------------|
| > 10 µm | **CRITICAL**: OPD method required |
| 1 - 10 µm | **RECOMMENDED**: OPD method provides meaningful improvement |
| 0.1 - 1 µm | **OPTIONAL**: OPD method provides minor improvement |
| < 0.1 µm | **EQUIVALENT**: Both methods give essentially identical results |
---
## 4. Implementation Details
### 4.1 Module: `extract_zernike_opd.py`
Location: `optimization_engine/extractors/extract_zernike_opd.py`
**Key Functions**:
```python
def compute_true_opd(x0, y0, z0, dx, dy, dz, focal_length, concave=True):
"""
Compute true surface error accounting for lateral displacement.
Returns:
x_def: Deformed X coordinates
y_def: Deformed Y coordinates
surface_error: True surface error (not just Δz)
lateral_magnitude: |Δx, Δy| for diagnostics
"""
```
```python
def estimate_focal_length_from_geometry(x, y, z, concave=True):
"""
Estimate parabola focal length by fitting z = a·r² + b.
Focal length = 1 / (4·|a|)
"""
```
**Main Class**:
```python
class ZernikeOPDExtractor:
"""
Rigorous OPD-based Zernike extractor.
Key differences from ZernikeExtractor:
- Uses deformed (x, y) coordinates for fitting
- Computes surface error relative to parabola at deformed position
- Provides lateral displacement diagnostics
"""
```
### 4.2 Algorithm Flow
```
1. Load geometry (BDF) and displacements (OP2)
2. For each node:
a. Get original position: (x₀, y₀, z₀)
b. Get displacement: (Δx, Δy, Δz)
c. Compute deformed position: (x_def, y_def) = (x₀+Δx, y₀+Δy)
d. Compute Δr² = r_def² - r₀²
e. Compute Δz_parabola = -Δr² / (4f) [for concave]
f. Compute surface_error = Δz - Δz_parabola
g. Store lateral_disp = √(Δx² + Δy²)
3. Convert to WFE: WFE = 2 × surface_error × nm_scale
4. Fit Zernike coefficients using (x_def, y_def, WFE)
5. Compute RMS metrics:
- Global RMS = √(mean(WFE²))
- Filtered RMS = √(mean((WFE - low_order_fit)²))
```
### 4.3 Focal Length Handling
The extractor can:
1. Use a **provided** focal length (most accurate)
2. **Auto-estimate** from geometry by fitting `z = a·r² + b`
Auto-estimation works well for clean parabolic meshes but may need manual override for:
- Off-axis parabolas
- Aspheric surfaces
- Meshes with significant manufacturing errors
```python
# Explicit focal length
extractor = ZernikeOPDExtractor(op2_file, focal_length=5000.0)
# Auto-estimate (default)
extractor = ZernikeOPDExtractor(op2_file, auto_estimate_focal=True)
```
---
## 5. Usage Guide
### 5.1 Quick Comparison Test
Run the test script to see how much the methods differ for your data:
```bash
conda activate atomizer
python test_zernike_opd_comparison.py
```
Output example:
```
--- Standard Method (Z-only) ---
Global RMS: 171.65 nm
Filtered RMS: 28.72 nm
--- Rigorous OPD Method ---
Global RMS: 171.89 nm
Filtered RMS: 29.15 nm
--- Difference (OPD - Standard) ---
Filtered RMS: +0.43 nm (+1.5%)
--- Lateral Displacement ---
Max: 0.156 µm
RMS: 0.111 µm
>>> OPTIONAL: Small lateral displacements. OPD method provides minor improvement.
```
### 5.2 Using in Optimization
**For new studies**, use the OPD extractor:
```python
from optimization_engine.extractors import extract_zernike_opd_filtered_rms
def objective(trial):
# ... parameter suggestion and FEA solve ...
# Use OPD method instead of standard
rms = extract_zernike_opd_filtered_rms(
op2_file,
subcase='20',
focal_length=5000.0 # Optional: specify or let it auto-estimate
)
return rms
```
**In optimization config** (future enhancement):
```json
{
"objectives": [
{
"name": "filtered_rms",
"extractor": "zernike_opd",
"extractor_config": {
"subcase": "20",
"metric": "filtered_rms_nm",
"focal_length": 5000.0
}
}
]
}
```
### 5.3 Visualization with Insights
Generate the comparison insight for a study:
```bash
python -m optimization_engine.insights generate studies/my_study --type zernike_opd_comparison
```
This creates an HTML visualization showing:
1. **Lateral displacement map** - Where pinching/lateral deformation occurs
2. **WFE surface** - Using the rigorous OPD method
3. **Comparison table** - Quantitative difference between methods
4. **Recommendation** - Whether OPD method is needed for your study
### 5.4 API Reference
```python
from optimization_engine.extractors import (
# Main extractor class
ZernikeOPDExtractor,
# Convenience functions
extract_zernike_opd, # Full metrics dict
extract_zernike_opd_filtered_rms, # Just the filtered RMS (float)
compare_zernike_methods, # Compare standard vs OPD
)
# Full extraction with all metrics
result = extract_zernike_opd(op2_file, subcase='20')
# Returns: {
# 'filtered_rms_nm': float,
# 'global_rms_nm': float,
# 'max_lateral_disp_um': float,
# 'rms_lateral_disp_um': float,
# 'focal_length_used': float,
# 'astigmatism_rms_nm': float,
# 'coma_rms_nm': float,
# ...
# }
# Just the primary metric for optimization
rms = extract_zernike_opd_filtered_rms(op2_file, subcase='20')
# Compare both methods
comparison = compare_zernike_methods(op2_file, subcase='20')
# Returns: {
# 'standard_method': {'filtered_rms_nm': ...},
# 'opd_method': {'filtered_rms_nm': ...},
# 'delta': {'filtered_rms_nm': ..., 'percent_difference_filtered': ...},
# 'lateral_displacement': {'max_um': ..., 'rms_um': ...},
# 'recommendation': str
# }
```
---
## 6. Validation and Testing
### 6.1 Analytical Test Case
For a simple test: apply a known lateral displacement and verify the correction.
**Setup**:
- Parabola: f = 5000 mm
- Node at (x₀, y₀) = (400, 0) mm, so r₀ = 400 mm
- Apply uniform X-displacement: Δx = 0.01 mm, Δy = 0, Δz = 0
**Expected correction**:
```
Δr² = (400.01)² + 0² - 400² - 0² = 8.0001 mm²
Δz_parabola = -8.0001 / (4 × 5000) = -0.0004 mm = -400 nm (surface)
WFE_correction = 2 × 400 nm = 800 nm
```
**Standard method**: WFE = 2 × Δz × 1e6 = 0 nm
**OPD method**: WFE = 2 × (0 - (-0.0004)) × 1e6 = 800 nm
The OPD method correctly identifies that a purely lateral displacement **does** affect the wavefront!
### 6.2 Sanity Checks
The OPD method should:
1. Give **identical** results to standard method when Δx = Δy = 0 everywhere
2. Show **larger** WFE when nodes move outward laterally (positive Δr²)
3. Show **smaller** WFE when nodes move inward laterally (negative Δr²)
4. Scale with 1/f (larger effect for faster mirrors)
### 6.3 Running the Test
```bash
conda activate atomizer
python test_zernike_opd_comparison.py
```
---
## 7. Migration Guide
### 7.1 For Existing Studies
1. **Run comparison test** on a few representative iterations
2. **Check the difference** - if > 5%, consider re-optimizing
3. **For lateral support studies** - strongly recommend re-optimization with OPD method
### 7.2 For New Studies
1. **Use OPD method by default** - it's never worse than standard
2. **Specify focal length** if known (more accurate than auto-estimate)
3. **Monitor lateral displacement** in the insight reports
### 7.3 Code Changes
**Before** (standard method):
```python
from optimization_engine.extractors import extract_zernike_filtered_rms
rms = extract_zernike_filtered_rms(op2_file, subcase='20')
```
**After** (OPD method):
```python
from optimization_engine.extractors import extract_zernike_opd_filtered_rms
rms = extract_zernike_opd_filtered_rms(op2_file, subcase='20', focal_length=5000.0)
```
---
## Appendix A: Derivation Details
### A.1 Full Derivation of Δz_parabola
For a concave paraboloid: `z = -r²/(4f) = -(x² + y²)/(4f)`
Original position: `z₀ = -(x₀² + y₀²)/(4f)`
Deformed position: `z_expected = -((x₀+Δx)² + (y₀+Δy)²)/(4f)`
```
z_expected - z₀ = -[(x₀+Δx)² + (y₀+Δy)² - x₀² - y₀²] / (4f)
= -[x₀² + 2x₀Δx + Δx² + y₀² + 2y₀Δy + Δy² - x₀² - y₀²] / (4f)
= -[2x₀Δx + Δx² + 2y₀Δy + Δy²] / (4f)
= -[r_def² - r₀²] / (4f)
= -Δr² / (4f)
```
This is `Δz_parabola` - the Z change required to stay on the ideal parabola.
### A.2 Sign Convention
For a **concave** mirror (typical telescope primary):
- Surface curves toward -Z (vertex is the highest point)
- `z = -r²/(4f)` (negative coefficient)
- Moving outward (Δr² > 0) requires moving in -Z direction
- `Δz_parabola = -Δr²/(4f)` is negative for outward movement
For a **convex** mirror:
- Surface curves toward +Z
- `z = +r²/(4f)` (positive coefficient)
- `Δz_parabola = +Δr²/(4f)` is positive for outward movement
The `concave` parameter in the code handles this sign flip.
---
## Appendix B: Files Reference
| File | Purpose |
|------|---------|
| `optimization_engine/extractors/extract_zernike_opd.py` | Main OPD extractor implementation |
| `optimization_engine/extractors/extract_zernike.py` | Standard (Z-only) extractor |
| `optimization_engine/insights/zernike_opd_comparison.py` | Visualization insight |
| `test_zernike_opd_comparison.py` | Quick test script |
### Related Documentation
| Document | Purpose |
|----------|---------|
| [ZERNIKE_FUNDAMENTALS.md](ZERNIKE_FUNDAMENTALS.md) | General Zernike usage, RMS calculation, multi-subcase analysis |
| [00_INDEX.md](00_INDEX.md) | Physics documentation index |
| `.claude/skills/modules/extractors-catalog.md` | Quick extractor lookup |
| `.claude/skills/modules/insights-catalog.md` | Quick insight lookup |
| `docs/protocols/system/SYS_12_EXTRACTOR_LIBRARY.md` | Extractor specifications (E8-E10, E20-E21) |
| `docs/protocols/system/SYS_17_STUDY_INSIGHTS.md` | Insight specifications |
---
## Appendix C: Glossary
| Term | Definition |
|------|------------|
| **OPD** | Optical Path Difference - the path length difference experienced by light rays |
| **WFE** | Wavefront Error - deviation of actual wavefront from ideal (WFE = 2 × surface error for reflection) |
| **Zernike polynomials** | Orthogonal basis functions for representing wavefronts over a circular aperture |
| **Noll index** | Standard optical indexing scheme for Zernike modes (j=1 is piston, j=4 is defocus, etc.) |
| **Filtered RMS** | RMS after removing low-order modes (piston, tip, tilt, defocus) that can be corrected by alignment |
| **Lateral displacement** | In-plane (X, Y) movement of nodes, as opposed to axial (Z) movement |
| **Focal length** | Distance from vertex to focus for a parabola; f = R/(2) where R is vertex radius of curvature |
---
*Document maintained by Atomizer Framework. Last updated: 2024-12-22*