Files
Atomizer/docs/06_PHYSICS/ZERNIKE_FUNDAMENTALS.md
Anto01 f13563d7ab feat: Major update - Physics docs, Zernike OPD, insights, NX journals, tools
Documentation:
- Add docs/06_PHYSICS/ with Zernike fundamentals and OPD method docs
- Add docs/guides/CMA-ES_EXPLAINED.md optimization guide
- Update CLAUDE.md and ATOMIZER_CONTEXT.md with current architecture
- Update OP_01_CREATE_STUDY protocol

Planning:
- Add DYNAMIC_RESPONSE plans for random vibration/PSD support
- Add OPTIMIZATION_ENGINE_MIGRATION_PLAN for code reorganization

Insights System:
- Update design_space, modal_analysis, stress_field, thermal_field insights
- Improve error handling and data validation

NX Journals:
- Add analyze_wfe_zernike.py for Zernike WFE analysis
- Add capture_study_images.py for automated screenshots
- Add extract_expressions.py and introspect_part.py utilities
- Add user_generated_journals/journal_top_view_image_taking.py

Tests & Tools:
- Add comprehensive Zernike OPD test suite
- Add audit_v10 tests for WFE validation
- Add tools for Pareto graphs and mirror data extraction
- Add migrate_studies_to_topics.py utility

Knowledge Base:
- Initialize LAC (Learning Atomizer Core) with failure/success patterns

Dashboard:
- Update Setup.tsx and launch_dashboard.py
- Add restart-dev.bat helper script

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-23 19:47:37 -05:00

8.9 KiB

Zernike Wavefront Analysis Integration

This document describes how to use Atomizer's Zernike analysis capabilities for telescope mirror optimization.

Overview

Atomizer includes a full Zernike polynomial decomposition system for analyzing wavefront errors (WFE) in telescope mirror FEA simulations. The system:

  • Extracts nodal displacements from NX Nastran OP2 files
  • Fits Zernike polynomials using Noll indexing (optical standard)
  • Computes RMS metrics (global and filtered)
  • Analyzes individual aberrations (astigmatism, coma, trefoil, etc.)
  • Supports multi-subcase analysis (different gravity orientations)

Quick Start

Simple Extraction

from optimization_engine.extractors import extract_zernike_from_op2

# Extract Zernike metrics for a single subcase
result = extract_zernike_from_op2(
    op2_file="model-solution_1.op2",
    subcase="20"  # 20 degree elevation
)

print(f"Global RMS: {result['global_rms_nm']:.2f} nm")
print(f"Filtered RMS: {result['filtered_rms_nm']:.2f} nm")
print(f"Astigmatism: {result['astigmatism_rms_nm']:.2f} nm")

In Optimization Objective

from optimization_engine.extractors.zernike_helpers import create_zernike_objective

# Create objective function
zernike_obj = create_zernike_objective(
    op2_finder=lambda: sim_dir / "model-solution_1.op2",
    subcase="20",
    metric="filtered_rms_nm"
)

# Use in Optuna trial
def objective(trial):
    # ... suggest parameters ...
    # ... run simulation ...

    rms = zernike_obj()
    return rms

RMS Calculation Method

IMPORTANT: Atomizer uses the correct surface-based RMS calculation matching optical standards:

# Global RMS = sqrt(mean(W^2)) - RMS of actual WFE surface values
global_rms = sqrt(mean(W_nm ** 2))

# Filtered RMS = sqrt(mean(W_residual^2))
# where W_residual = W_nm - Z[:, :4] @ coeffs[:4] (low-order fit subtracted)
filtered_rms = sqrt(mean(W_residual ** 2))

This is different from summing Zernike coefficients! The RMS is computed from the actual WFE surface values, not from sqrt(sum(coeffs^2)).

Available Metrics

RMS Metrics

Metric Description
global_rms_nm RMS of entire WFE surface: sqrt(mean(W^2))
filtered_rms_nm RMS after removing modes 1-4 (piston, tip, tilt, defocus)
rms_filter_j1to3_nm RMS after removing only modes 1-3 (keeps defocus) - "optician workload"

Aberration Magnitudes

Metric Zernike Modes Description
defocus_nm J4 Focus error
astigmatism_rms_nm J5 + J6 Combined astigmatism
coma_rms_nm J7 + J8 Combined coma
trefoil_rms_nm J9 + J10 Combined trefoil
spherical_nm J11 Primary spherical

Multi-Subcase Analysis

For telescope mirrors, gravity orientation affects surface shape. Standard subcases:

Subcase Description
20 Low elevation (operational)
40 Mid-low elevation
60 Mid-high elevation
90 Horizontal (polishing orientation)

Extract All Subcases

from optimization_engine.extractors import ZernikeExtractor

extractor = ZernikeExtractor("model.op2")
results = extractor.extract_all_subcases(reference_subcase="20")

for label, metrics in results.items():
    print(f"Subcase {label}: {metrics['filtered_rms_nm']:.1f} nm")

Relative Analysis

Compare deformation between orientations:

from optimization_engine.extractors.zernike_helpers import create_relative_zernike_objective

# Minimize deformation at 20 deg relative to polishing position (90 deg)
relative_obj = create_relative_zernike_objective(
    op2_finder=lambda: sim_dir / "model.op2",
    target_subcase="20",
    reference_subcase="90"
)

relative_rms = relative_obj()

Optimization Configuration

Example: Single Objective (Filtered RMS)

{
  "objectives": [
    {
      "name": "filtered_rms",
      "direction": "minimize",
      "extractor": "zernike",
      "extractor_config": {
        "subcase": "20",
        "metric": "filtered_rms_nm"
      }
    }
  ]
}

Example: Multi-Objective (RMS + Mass)

{
  "objectives": [
    {
      "name": "filtered_rms_20deg",
      "direction": "minimize",
      "extractor": "zernike",
      "extractor_config": {
        "subcase": "20",
        "metric": "filtered_rms_nm"
      }
    },
    {
      "name": "mass",
      "direction": "minimize",
      "extractor": "mass_from_expression"
    }
  ],
  "optimization_settings": {
    "sampler": "NSGA-II",
    "protocol": 11
  }
}

Example: Constrained (Stress + Aberration Limits)

{
  "constraints": [
    {
      "name": "astigmatism_limit",
      "type": "upper_bound",
      "threshold": 50.0,
      "extractor": "zernike",
      "extractor_config": {
        "subcase": "90",
        "metric": "astigmatism_rms_nm"
      }
    }
  ]
}

Advanced: ZernikeObjectiveBuilder

For complex multi-subcase objectives:

from optimization_engine.extractors.zernike_helpers import ZernikeObjectiveBuilder

builder = ZernikeObjectiveBuilder(
    op2_finder=lambda: sim_dir / "model.op2"
)

# Weight operational positions more heavily
builder.add_subcase_objective("20", "filtered_rms_nm", weight=1.0)
builder.add_subcase_objective("40", "filtered_rms_nm", weight=0.5)
builder.add_subcase_objective("60", "filtered_rms_nm", weight=0.5)

# Create combined objective (weighted sum)
objective = builder.build_weighted_sum()

# Or: worst-case across subcases
worst_case_obj = builder.build_max()

Zernike Settings

Configuration Options

Setting Default Description
n_modes 50 Number of Zernike modes to fit
filter_orders 4 Low-order modes to filter (1-4 = piston through defocus)
displacement_unit "mm" Unit of displacement in OP2 ("mm", "m", "um", "nm")

Unit Conversions

Wavefront error (WFE) is computed as:

WFE_nm = 2 * displacement * unit_conversion

Where unit_conversion converts to nanometers:

  • mm: 1e6
  • m: 1e9
  • um: 1e3

The factor of 2 accounts for the optical convention (surface error doubles as wavefront error for reflection).

NX Nastran Setup

Required Subcases

Your NX Nastran model should have subcases for each gravity orientation:

SUBCASE 20
  SUBTITLE=20 deg elevation
  LOAD = ...

SUBCASE 40
  SUBTITLE=40 deg elevation
  LOAD = ...

The extractor identifies subcases by:

  1. Numeric value in SUBTITLE (preferred)
  2. SUBCASE ID number

Output Requests

Ensure displacement output is requested:

SET 999 = ALL
DISPLACEMENT(SORT1,REAL) = 999

Migration from Legacy Scripts

If you were using zernike_Post_Script_NX.py:

Old Approach Atomizer Equivalent
Manual OP2 parsing ZernikeExtractor
compute_zernike_coeffs_chunked() compute_zernike_coefficients()
write_exp_file() Configure as objective/constraint
HTML reports Dashboard visualization (TBD)
RMS log CSV Optuna database + export

Key Differences

  1. Integration: Zernike is now an extractor like displacement/stress
  2. Optimization: Direct use as objectives/constraints in Optuna
  3. Multi-objective: Native NSGA-II support for RMS + mass Pareto optimization
  4. Neural Acceleration: Can train surrogate on Zernike metrics (Protocol 12)

Example Study Structure

studies/
  mirror_optimization/
    1_setup/
      optimization_config.json
      model/
        ASSY_M1.prt
        ASSY_M1_assyfem1.afm
        ASSY_M1_assyfem1_sim1.sim
    2_results/
      study.db
      zernike_analysis/
        trial_001_zernike.json
        trial_002_zernike.json
        ...
    run_optimization.py

See Also

  • ZERNIKE_OPD_METHOD.md - Rigorous OPD method for lateral displacement correction (critical for lateral support optimization)

Protocol Documentation

  • docs/protocols/system/SYS_12_EXTRACTOR_LIBRARY.md - Extractor specifications (E8-E10: Standard Zernike, E20-E21: OPD method)
  • docs/protocols/system/SYS_16_STUDY_INSIGHTS.md - Insight specifications (zernike_wfe, zernike_opd_comparison)

Skill Modules (Quick Lookup)

  • .claude/skills/modules/extractors-catalog.md - Quick extractor reference
  • .claude/skills/modules/insights-catalog.md - Quick insight reference

Code Implementation

Example Configurations