## 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
144 lines
5.6 KiB
Python
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.')
|