Files
Atomizer/tools/analysis/audit_v10_wfe.py
Anto01 a3f18dc377 chore: Project cleanup and Canvas UX improvements (Phase 7-9)
## Cleanup (v0.5.0)
- Delete 102+ orphaned MCP session temp files
- Remove build artifacts (htmlcov, dist, __pycache__)
- Archive superseded plan docs (RALPH_LOOP V2/V3, CANVAS V3, etc.)
- Move debug/analysis scripts from tests/ to tools/analysis/
- Archive redundant NX journals to archive/nx_journals/
- Archive monolithic PROTOCOL.md to docs/archive/
- Update .gitignore with missing patterns
- Clean old study files (optimization_log_old.txt, run_optimization_old.py)

## Canvas UX (Phases 7-9)
- Phase 7: Resizable panels with localStorage persistence
  - Left sidebar: 200-400px, Right panel: 280-600px
  - New useResizablePanel hook and ResizeHandle component
- Phase 8: Enable all palette items
  - All 8 node types now draggable
  - Singleton logic for model/solver/algorithm/surrogate
- Phase 9: Solver configuration
  - Add SolverEngine type (nxnastran, mscnastran, python, etc.)
  - Add NastranSolutionType (SOL101-SOL200)
  - Engine/solution dropdowns in config panel
  - Python script path support

## Documentation
- Update CHANGELOG.md with recent versions
- Update docs/00_INDEX.md
- Create examples/README.md
- Add docs/plans/CANVAS_UX_IMPROVEMENTS.md
2026-01-24 15:17:34 -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.')