Files
Atomizer/tests/audit_v10_wfe.py
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

144 lines
5.6 KiB
Python

"""Audit V10 WFE values - independent verification."""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
from optimization_engine.extractors import ZernikeOPDExtractor, ZernikeExtractor
print('='*70)
print('AUDIT: V10 WFE Values - Independent Verification')
print('='*70)
# V10 iter1 (baseline trial)
op2_v10 = Path('studies/M1_Mirror/m1_mirror_cost_reduction_V10/2_iterations/iter1/assy_m1_assyfem1_sim1-solution_1.op2')
if not op2_v10.exists():
print('V10 OP2 file not found!')
sys.exit(1)
print(f'OP2 file: {op2_v10}')
print(f'Size: {op2_v10.stat().st_size / 1024 / 1024:.1f} MB')
# Test with ZernikeOPDExtractor (what V10 uses)
print()
print('='*70)
print('Method 1: ZernikeOPDExtractor (what V10 uses)')
print('='*70)
extractor_opd = ZernikeOPDExtractor(op2_v10, n_modes=50, filter_orders=4)
result_20_opd = extractor_opd.extract_subcase('2') # Reference
result_40_opd = extractor_opd.extract_subcase('3') # 40 deg
result_60_opd = extractor_opd.extract_subcase('4') # 60 deg
result_90_opd = extractor_opd.extract_subcase('1') # 90 deg MFG
print()
print('ABSOLUTE values (ZernikeOPD):')
print(f' 20 deg: filtered_rms = {result_20_opd["filtered_rms_nm"]:.2f} nm')
print(f' 40 deg: filtered_rms = {result_40_opd["filtered_rms_nm"]:.2f} nm')
print(f' 60 deg: filtered_rms = {result_60_opd["filtered_rms_nm"]:.2f} nm')
print(f' 90 deg: filtered_rms = {result_90_opd["filtered_rms_nm"]:.2f} nm')
print()
print('RELATIVE values (target - ref) as V10 computes:')
rel_40_opd = result_40_opd['filtered_rms_nm'] - result_20_opd['filtered_rms_nm']
rel_60_opd = result_60_opd['filtered_rms_nm'] - result_20_opd['filtered_rms_nm']
rel_mfg_opd = result_90_opd['rms_filter_j1to3_nm'] - result_20_opd['rms_filter_j1to3_nm']
print(f' 40-20: {rel_40_opd:.2f} nm (abs: {abs(rel_40_opd):.2f})')
print(f' 60-20: {rel_60_opd:.2f} nm (abs: {abs(rel_60_opd):.2f})')
print(f' 90-20 (j1to3): {rel_mfg_opd:.2f} nm (abs: {abs(rel_mfg_opd):.2f})')
print()
print('V10 uses abs() -> stores:')
print(f' rel_filtered_rms_40_vs_20: {abs(rel_40_opd):.2f}')
print(f' rel_filtered_rms_60_vs_20: {abs(rel_60_opd):.2f}')
print(f' mfg_90_optician_workload: {abs(rel_mfg_opd):.2f}')
# Test with Standard ZernikeExtractor (what V9 uses)
print()
print('='*70)
print('Method 2: Standard ZernikeExtractor (what V9 likely uses)')
print('='*70)
# Find the BDF file
bdf_files = list(op2_v10.parent.glob('*.dat'))
bdf_path = bdf_files[0] if bdf_files else None
print(f'BDF file: {bdf_path}')
extractor_std = ZernikeExtractor(op2_v10, bdf_path=bdf_path, n_modes=50, filter_orders=4)
result_20_std = extractor_std.extract_subcase('2')
result_40_std = extractor_std.extract_subcase('3')
result_60_std = extractor_std.extract_subcase('4')
result_90_std = extractor_std.extract_subcase('1')
print()
print('ABSOLUTE values (Standard Z-only):')
print(f' 20 deg: filtered_rms = {result_20_std["filtered_rms_nm"]:.2f} nm')
print(f' 40 deg: filtered_rms = {result_40_std["filtered_rms_nm"]:.2f} nm')
print(f' 60 deg: filtered_rms = {result_60_std["filtered_rms_nm"]:.2f} nm')
print(f' 90 deg: filtered_rms = {result_90_std["filtered_rms_nm"]:.2f} nm')
print()
print('RELATIVE values (Standard):')
rel_40_std = result_40_std['filtered_rms_nm'] - result_20_std['filtered_rms_nm']
rel_60_std = result_60_std['filtered_rms_nm'] - result_20_std['filtered_rms_nm']
print(f' 40-20: {rel_40_std:.2f} nm (abs: {abs(rel_40_std):.2f})')
print(f' 60-20: {rel_60_std:.2f} nm (abs: {abs(rel_60_std):.2f})')
# Compare
print()
print('='*70)
print('COMPARISON: OPD vs Standard')
print('='*70)
print()
print(f'40-20: OPD={abs(rel_40_opd):.2f} nm vs Standard={abs(rel_40_std):.2f} nm')
print(f'60-20: OPD={abs(rel_60_opd):.2f} nm vs Standard={abs(rel_60_std):.2f} nm')
print()
print('Lateral displacement (OPD method):')
print(f' Max: {result_40_opd.get("max_lateral_displacement_um", 0):.3f} um')
print(f' RMS: {result_40_opd.get("rms_lateral_displacement_um", 0):.3f} um')
# Now check what V9 reports
print()
print('='*70)
print('V9 COMPARISON (iter12 from best archive)')
print('='*70)
op2_v9 = Path('studies/M1_Mirror/m1_mirror_cost_reduction_V9/2_iterations/iter12/assy_m1_assyfem1_sim1-solution_1.op2')
if op2_v9.exists():
extractor_v9_opd = ZernikeOPDExtractor(op2_v9, n_modes=50, filter_orders=4)
extractor_v9_std = ZernikeExtractor(op2_v9, n_modes=50, filter_orders=4)
r20_v9_opd = extractor_v9_opd.extract_subcase('2')
r40_v9_opd = extractor_v9_opd.extract_subcase('3')
r60_v9_opd = extractor_v9_opd.extract_subcase('4')
r20_v9_std = extractor_v9_std.extract_subcase('2')
r40_v9_std = extractor_v9_std.extract_subcase('3')
r60_v9_std = extractor_v9_std.extract_subcase('4')
rel_40_v9_opd = abs(r40_v9_opd['filtered_rms_nm'] - r20_v9_opd['filtered_rms_nm'])
rel_60_v9_opd = abs(r60_v9_opd['filtered_rms_nm'] - r20_v9_opd['filtered_rms_nm'])
rel_40_v9_std = abs(r40_v9_std['filtered_rms_nm'] - r20_v9_std['filtered_rms_nm'])
rel_60_v9_std = abs(r60_v9_std['filtered_rms_nm'] - r20_v9_std['filtered_rms_nm'])
print()
print('V9 iter12 relative values:')
print(f' 40-20: OPD={rel_40_v9_opd:.2f} nm vs Standard={rel_40_v9_std:.2f} nm')
print(f' 60-20: OPD={rel_60_v9_opd:.2f} nm vs Standard={rel_60_v9_std:.2f} nm')
else:
print('V9 OP2 not found')
print()
print('='*70)
print('SUMMARY')
print('='*70)
print()
print('V10 reports: 40-20=1.99nm, 60-20=6.82nm (using OPD method)')
print('V9 reports: 40-20=6.10nm, 60-20=12.76nm (likely Standard method)')
print()
print('If both studies have SIMILAR geometry, the OPD method should NOT')
print('give such dramatically different values. This needs investigation.')