diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 805b299f..117f0f28 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -62,7 +62,23 @@ "Bash(xargs -I{} git ls-tree -r -l HEAD {})", "Bash(sort:*)", "Bash(C:Usersantoianaconda3envsatomizerpython.exe introspect_model.py)", - "Bash(xargs:*)" + "Bash(xargs:*)", + "Bash(ping:*)", + "Bash(C:Usersantoianaconda3envsatomizerpython.exe -c \"import requests; r = requests.post\\(''http://127.0.0.1:8001/api/claude/sessions'', json={''mode'': ''user''}\\); print\\(f''Status: {r.status_code}''\\); print\\(f''Response: {r.text}''\\)\")", + "Bash(start \"Atomizer Backend\" cmd /k C:UsersantoiAtomizerrestart_backend.bat)", + "Bash(start \"Test Backend\" cmd /c \"cd /d C:\\\\Users\\\\antoi\\\\Atomizer\\\\atomizer-dashboard\\\\backend && C:\\\\Users\\\\antoi\\\\anaconda3\\\\Scripts\\\\activate.bat atomizer && python -m uvicorn api.main:app --port 8002\")", + "Bash(C:Usersantoianaconda3envsatomizerpython.exe C:UsersantoiAtomizertest_backend.py)", + "Bash(start \"Backend 8002\" C:UsersantoiAtomizerstart_backend_8002.bat)", + "Bash(C:Usersantoianaconda3envsatomizerpython.exe -c \"from api.main import app; print\\(''Import OK''\\)\")", + "Bash(find:*)", + "Bash(npx tailwindcss:*)", + "Bash(C:Usersantoianaconda3envsatomizerpython.exe -c \"from pathlib import Path; p = Path\\(''C:/Users/antoi/Atomizer/studies''\\) / ''M1_Mirror/m1_mirror_cost_reduction_lateral''; print\\(''exists:'', p.exists\\(\\), ''path:'', p\\)\")", + "Bash(C:Usersantoianaconda3envsatomizerpython.exe -c \"import sys, json; d=json.load\\(sys.stdin\\); print\\(''Study:'', d.get\\(''meta'',{}\\).get\\(''study_name'',''N/A''\\)\\); print\\(''Design Variables:''\\); [print\\(f'' - {dv[\"\"name\"\"]} \\({dv[\"\"expression_name\"\"]}\\)''\\) for dv in d.get\\(''design_variables'',[]\\)]\")", + "Bash(C:Usersantoianaconda3envsatomizerpython.exe -m py_compile:*)", + "Skill(ralph-loop:ralph-loop)", + "Skill(ralph-loop:ralph-loop:*)", + "mcp__Claude_in_Chrome__computer", + "mcp__Claude_in_Chrome__navigate" ], "deny": [], "ask": [] diff --git a/CUsersantoiAtomizeropenapi_dump.json b/CUsersantoiAtomizeropenapi_dump.json new file mode 100644 index 00000000..6ff1772c --- /dev/null +++ b/CUsersantoiAtomizeropenapi_dump.json @@ -0,0 +1 @@ +{"openapi":"3.1.0","info":{"title":"Atomizer Dashboard API","description":"Real-time optimization monitoring and control","version":"2.0.0"},"paths":{"/api/optimization/studies":{"get":{"tags":["optimization"],"summary":"List Studies","description":"List all available optimization studies.\n\nSupports both flat and nested folder structures:\n- Flat: studies/study_name/\n- Nested: studies/Topic/study_name/\n\nReturns studies with 'topic' field for frontend grouping.","operationId":"list_studies_api_optimization_studies_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"tags":["optimization"],"summary":"Create Study","description":"Create a new optimization study\nAccepts:\n- config: JSON string with study configuration\n- prt_file: NX part file (optional if using existing study)\n- sim_file: NX simulation file (optional)\n- fem_file: NX FEM file (optional)","operationId":"create_study_api_optimization_studies_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_create_study_api_optimization_studies_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/optimization/studies/{study_id}/status":{"get":{"tags":["optimization"],"summary":"Get Study Status","description":"Get detailed status of a specific study","operationId":"get_study_status_api_optimization_studies__study_id__status_get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/optimization/studies/{study_id}/history":{"get":{"tags":["optimization"],"summary":"Get Optimization History","description":"Get optimization history (all trials)","operationId":"get_optimization_history_api_optimization_studies__study_id__history_get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/optimization/studies/{study_id}/pruning":{"get":{"tags":["optimization"],"summary":"Get Pruning History","description":"Get pruning diagnostics from Optuna database or legacy JSON file","operationId":"get_pruning_history_api_optimization_studies__study_id__pruning_get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/optimization/studies/{study_id}/metadata":{"get":{"tags":["optimization"],"summary":"Get Study Metadata","description":"Read optimization_config.json for objectives, design vars, units (Protocol 13)","operationId":"get_study_metadata_api_optimization_studies__study_id__metadata_get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/optimization/studies/{study_id}/optimizer-state":{"get":{"tags":["optimization"],"summary":"Get Optimizer State","description":"Read realtime optimizer state from multiple sources:\n1. dashboard_state.json (written by optimization scripts)\n2. intelligent_optimizer/optimizer_state.json (Protocol 13)\n3. Computed state from database (fallback)","operationId":"get_optimizer_state_api_optimization_studies__study_id__optimizer_state_get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/optimization/studies/{study_id}/pareto-front":{"get":{"tags":["optimization"],"summary":"Get Pareto Front","description":"Get Pareto-optimal solutions for multi-objective studies (Protocol 13)","operationId":"get_pareto_front_api_optimization_studies__study_id__pareto_front_get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/optimization/studies/{study_id}/nn-pareto-front":{"get":{"tags":["optimization"],"summary":"Get Nn Pareto Front","description":"Get NN surrogate Pareto front from nn_pareto_front.json","operationId":"get_nn_pareto_front_api_optimization_studies__study_id__nn_pareto_front_get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/optimization/studies/{study_id}/nn-state":{"get":{"tags":["optimization"],"summary":"Get Nn Optimization State","description":"Get NN optimization state/summary from nn_optimization_state.json","operationId":"get_nn_optimization_state_api_optimization_studies__study_id__nn_state_get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/optimization/studies/{study_id}/convert-mesh":{"post":{"tags":["optimization"],"summary":"Convert Study Mesh","description":"Convert study mesh to GLTF for 3D visualization\nCreates a web-viewable 3D model with FEA results as vertex colors","operationId":"convert_study_mesh_api_optimization_studies__study_id__convert_mesh_post","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/optimization/studies/{study_id}/mesh/{filename}":{"get":{"tags":["optimization"],"summary":"Get Mesh File","description":"Serve GLTF mesh files and metadata\nSupports .gltf, .bin, and .json files","operationId":"get_mesh_file_api_optimization_studies__study_id__mesh__filename__get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}},{"name":"filename","in":"path","required":true,"schema":{"type":"string","title":"Filename"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/optimization/studies/{study_id}/optuna-url":{"get":{"tags":["optimization"],"summary":"Get Optuna Dashboard Url","description":"Get the Optuna dashboard URL for a specific study.\nReturns the URL to access the study in Optuna dashboard.\n\nThe Optuna dashboard should be started with a relative path from the Atomizer root:\nsqlite:///studies/{study_id}/2_results/study.db","operationId":"get_optuna_dashboard_url_api_optimization_studies__study_id__optuna_url_get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/optimization/studies/{study_id}/generate-report":{"post":{"tags":["optimization"],"summary":"Generate Report","description":"Generate an optimization report in the specified format\n\nArgs:\n study_id: Study identifier\n format: Report format ('markdown', 'html', or 'pdf')\n include_llm_summary: Whether to include LLM-generated executive summary\n\nReturns:\n Information about the generated report including download URL","operationId":"generate_report_api_optimization_studies__study_id__generate_report_post","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","default":"markdown","title":"Format"}},{"name":"include_llm_summary","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Llm Summary"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/optimization/studies/{study_id}/reports/{filename}":{"get":{"tags":["optimization"],"summary":"Download Report","description":"Download a generated report file\n\nArgs:\n study_id: Study identifier\n filename: Report filename\n\nReturns:\n Report file for download","operationId":"download_report_api_optimization_studies__study_id__reports__filename__get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}},{"name":"filename","in":"path","required":true,"schema":{"type":"string","title":"Filename"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/optimization/studies/{study_id}/console":{"get":{"tags":["optimization"],"summary":"Get Console Output","description":"Get the latest console output/logs from the optimization run\n\nArgs:\n study_id: Study identifier\n lines: Number of lines to return (default: 200)\n\nReturns:\n JSON with console output lines","operationId":"get_console_output_api_optimization_studies__study_id__console_get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}},{"name":"lines","in":"query","required":false,"schema":{"type":"integer","default":200,"title":"Lines"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/optimization/studies/{study_id}/report":{"get":{"tags":["optimization"],"summary":"Get Study Report","description":"Get the STUDY_REPORT.md file content for a study\n\nArgs:\n study_id: Study identifier\n\nReturns:\n JSON with the markdown content","operationId":"get_study_report_api_optimization_studies__study_id__report_get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/optimization/studies/{study_id}/readme":{"get":{"tags":["optimization"],"summary":"Get Study Readme","description":"Get the README.md file content for a study (from 1_setup folder)\n\nArgs:\n study_id: Study identifier\n\nReturns:\n JSON with the markdown content","operationId":"get_study_readme_api_optimization_studies__study_id__readme_get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/optimization/studies/{study_id}/image/{image_path}":{"get":{"tags":["optimization"],"summary":"Get Study Image","description":"Serve images from a study directory.\n\nSupports images in:\n- study_dir/image.png\n- study_dir/1_setup/image.png\n- study_dir/3_results/image.png\n- study_dir/assets/image.png\n\nArgs:\n study_id: Study identifier\n image_path: Relative path to the image within the study\n\nReturns:\n FileResponse with the image","operationId":"get_study_image_api_optimization_studies__study_id__image__image_path__get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}},{"name":"image_path","in":"path","required":true,"schema":{"type":"string","title":"Image Path"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/optimization/studies/{study_id}/config":{"get":{"tags":["optimization"],"summary":"Get Study Config","description":"Get the full optimization_config.json for a study\n\nArgs:\n study_id: Study identifier\n\nReturns:\n JSON with the complete configuration","operationId":"get_study_config_api_optimization_studies__study_id__config_get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["optimization"],"summary":"Update Study Config","description":"Update the optimization_config.json for a study\n\nArgs:\n study_id: Study identifier\n request: New configuration data\n\nReturns:\n JSON with success status","operationId":"update_study_config_api_optimization_studies__study_id__config_put","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateConfigRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/optimization/studies/{study_id}/process":{"get":{"tags":["optimization"],"summary":"Get Process Status","description":"Get the process status for a study's optimization run\n\nArgs:\n study_id: Study identifier\n\nReturns:\n JSON with process status (is_running, pid, iteration counts)","operationId":"get_process_status_api_optimization_studies__study_id__process_get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/optimization/studies/{study_id}/start":{"post":{"tags":["optimization"],"summary":"Start Optimization","description":"Start the optimization process for a study\n\nArgs:\n study_id: Study identifier\n request: Optional start options\n\nReturns:\n JSON with process info","operationId":"start_optimization_api_optimization_studies__study_id__start_post","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartOptimizationRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/optimization/studies/{study_id}/stop":{"post":{"tags":["optimization"],"summary":"Stop Optimization","description":"Stop the optimization process for a study (hard kill by default)\n\nArgs:\n study_id: Study identifier\n request.force: If True (default), immediately kill. If False, try graceful first.\n\nReturns:\n JSON with result","operationId":"stop_optimization_api_optimization_studies__study_id__stop_post","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StopRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/optimization/studies/{study_id}/pause":{"post":{"tags":["optimization"],"summary":"Pause Optimization","description":"Pause the optimization process for a study.\n\nUses psutil.Process.suspend() which sends SIGSTOP on Unix or suspends on Windows.\n\nArgs:\n study_id: Study identifier\n\nReturns:\n JSON with result","operationId":"pause_optimization_api_optimization_studies__study_id__pause_post","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/optimization/studies/{study_id}/resume":{"post":{"tags":["optimization"],"summary":"Resume Optimization","description":"Resume a paused optimization process for a study.\n\nUses psutil.Process.resume() which sends SIGCONT on Unix or resumes on Windows.\n\nArgs:\n study_id: Study identifier\n\nReturns:\n JSON with result","operationId":"resume_optimization_api_optimization_studies__study_id__resume_post","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/optimization/studies/{study_id}/validate":{"post":{"tags":["optimization"],"summary":"Validate Optimization","description":"Run final FEA validation on top NN predictions\n\nArgs:\n study_id: Study identifier\n request: Validation options (topN)\n\nReturns:\n JSON with process info","operationId":"validate_optimization_api_optimization_studies__study_id__validate_post","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/optimization/studies/{study_id}/optuna-dashboard":{"post":{"tags":["optimization"],"summary":"Launch Optuna Dashboard","description":"Launch Optuna dashboard for a specific study\n\nArgs:\n study_id: Study identifier\n\nReturns:\n JSON with dashboard URL and process info","operationId":"launch_optuna_dashboard_api_optimization_studies__study_id__optuna_dashboard_post","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/optimization/optuna-dashboard/check":{"get":{"tags":["optimization"],"summary":"Check Optuna Dashboard Available","description":"Check if optuna-dashboard is installed and available.\n\nReturns:\n JSON with availability status and installation instructions if not available.","operationId":"check_optuna_dashboard_available_api_optimization_optuna_dashboard_check_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/optimization/studies/{study_id}/model-files":{"get":{"tags":["optimization"],"summary":"Get Model Files","description":"Get list of NX model files (.prt, .sim, .fem, .bdf, .dat, .op2) for a study\n\nArgs:\n study_id: Study identifier\n\nReturns:\n JSON with list of model files and their paths","operationId":"get_model_files_api_optimization_studies__study_id__model_files_get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/optimization/studies/{study_id}/open-folder":{"post":{"tags":["optimization"],"summary":"Open Model Folder","description":"Open the model folder in system file explorer\n\nArgs:\n study_id: Study identifier\n folder_type: Type of folder to open (model, results, setup)\n\nReturns:\n JSON with success status","operationId":"open_model_folder_api_optimization_studies__study_id__open_folder_post","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}},{"name":"folder_type","in":"query","required":false,"schema":{"type":"string","default":"model","title":"Folder Type"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/optimization/studies/{study_id}/best-solution":{"get":{"tags":["optimization"],"summary":"Get Best Solution","description":"Get the best trial(s) for a study with improvement metrics","operationId":"get_best_solution_api_optimization_studies__study_id__best_solution_get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/optimization/studies/{study_id}/runs":{"get":{"tags":["optimization"],"summary":"Get Study Runs","description":"Get all optimization runs/studies in the database for comparison.\nMany studies have multiple Optuna studies (e.g., v11_fea, v11_iter1_nn, v11_iter2_nn).\nThis endpoint returns metrics for each sub-study.","operationId":"get_study_runs_api_optimization_studies__study_id__runs_get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/optimization/studies/{study_id}/zernike-available":{"get":{"tags":["optimization"],"summary":"Get Zernike Available Trials","description":"Get list of trial numbers that have Zernike analysis available (OP2 files).\n\nReturns:\n JSON with list of trial numbers that have iteration folders with OP2 files","operationId":"get_zernike_available_trials_api_optimization_studies__study_id__zernike_available_get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/optimization/studies/{study_id}/trials/{trial_number}/zernike":{"get":{"tags":["optimization"],"summary":"Get Trial Zernike","description":"Generate or retrieve Zernike analysis HTML for a specific trial.\n\nThis endpoint generates interactive Zernike wavefront analysis for mirror\noptimization trials. It produces 3D surface residual plots, RMS metrics,\nand coefficient bar charts for each angle comparison (40_vs_20, 60_vs_20, 90_vs_20).\n\nArgs:\n study_id: Study identifier\n trial_number: Trial/iteration number\n\nReturns:\n JSON with HTML content for each comparison, or error if OP2 not found","operationId":"get_trial_zernike_api_optimization_studies__study_id__trials__trial_number__zernike_get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}},{"name":"trial_number","in":"path","required":true,"schema":{"type":"integer","title":"Trial Number"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/optimization/studies/{study_id}/export/{format}":{"get":{"tags":["optimization"],"summary":"Export Study Data","description":"Export study data in various formats: csv, json, excel","operationId":"export_study_data_api_optimization_studies__study_id__export__format__get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}},{"name":"format","in":"path","required":true,"schema":{"type":"string","title":"Format"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/claude/sessions":{"post":{"tags":["claude"],"summary":"Create Session","description":"Create or resume a Claude session with MCP tools.\n\nArgs:\n mode: \"user\" for safe operations, \"power\" for full access\n study_id: Optional study to provide context\n resume_session_id: Optional session ID to resume\n\nReturns:\n Session info including session_id, mode, study_id","operationId":"create_session_api_claude_sessions_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSessionRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/claude/sessions/{session_id}":{"get":{"tags":["claude"],"summary":"Get Session","description":"Get session info","operationId":"get_session_api_claude_sessions__session_id__get","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/claude/sessions/{session_id}/mode":{"post":{"tags":["claude"],"summary":"Switch Session Mode","description":"Switch session mode (requires session restart).\n\nArgs:\n session_id: Session to update\n mode: New mode (\"user\" or \"power\")","operationId":"switch_session_mode_api_claude_sessions__session_id__mode_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SwitchModeRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/claude/sessions/{session_id}/study":{"post":{"tags":["claude"],"summary":"Set Session Study","description":"Update study context for a session","operationId":"set_session_study_api_claude_sessions__session_id__study_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"study_id","in":"query","required":true,"schema":{"type":"string","title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/claude/status":{"get":{"tags":["claude"],"summary":"Get Claude Status","description":"Check if Claude CLI is available\n\nReturns:\n JSON with CLI status","operationId":"get_claude_status_api_claude_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/claude/chat":{"post":{"tags":["claude"],"summary":"Chat With Claude","description":"Send a message to Claude via CLI with Atomizer context\n\nArgs:\n request: ChatRequest with message, optional study_id, and conversation history\n\nReturns:\n ChatResponse with Claude's response","operationId":"chat_with_claude_api_claude_chat_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/claude/chat/stream":{"post":{"tags":["claude"],"summary":"Chat Stream","description":"Stream a response from Claude CLI token by token\n\nArgs:\n request: ChatRequest with message and optional context\n\nReturns:\n StreamingResponse with text/event-stream","operationId":"chat_stream_api_claude_chat_stream_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/claude/suggestions":{"get":{"tags":["claude"],"summary":"Get Chat Suggestions","description":"Get contextual chat suggestions based on current study\n\nArgs:\n study_id: Optional study to get suggestions for\n\nReturns:\n List of suggested prompts","operationId":"get_chat_suggestions_api_claude_suggestions_get","parameters":[{"name":"study_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/terminal/status":{"get":{"tags":["terminal"],"summary":"Terminal Status","description":"Check if Claude Code CLI is available.","operationId":"terminal_status_api_terminal_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/terminal/context":{"get":{"tags":["terminal"],"summary":"Get Context","description":"Get the context prompt for a Claude session without starting a terminal.\n\nUseful for displaying context in the UI or preparing prompts.\n\nQuery params:\n study_id: Optional study ID to include study-specific context","operationId":"get_context_api_terminal_context_get","parameters":[{"name":"study_id","in":"query","required":false,"schema":{"type":"string","title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/insights/studies/{study_id}/available":{"get":{"tags":["insights"],"summary":"List Available Insights","description":"List all insight types that can be generated for this study.","operationId":"list_available_insights_api_insights_studies__study_id__available_get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/insights/studies/{study_id}/all":{"get":{"tags":["insights"],"summary":"List All Insights","description":"List all registered insight types (regardless of availability for any study).","operationId":"list_all_insights_api_insights_studies__study_id__all_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/insights/studies/{study_id}/iterations":{"get":{"tags":["insights"],"summary":"List Iterations","description":"List available iterations/trials with OP2 files for insight generation.\n\nReturns iterations sorted by modification time (newest first).\nOnly includes iterations with valid (non-corrupted) OP2 files.","operationId":"list_iterations_api_insights_studies__study_id__iterations_get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/insights/studies/{study_id}/generate/{insight_type}":{"post":{"tags":["insights"],"summary":"Generate Insight","description":"Generate a specific insight visualization.\n\nArgs:\n study_id: Study identifier\n insight_type: Type of insight (e.g., 'zernike_wfe', 'stress_field', 'design_space')\n request: Optional generation config with iteration selection\n\nReturns:\n JSON with plotly_figure data and summary statistics","operationId":"generate_insight_api_insights_studies__study_id__generate__insight_type__post","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}},{"name":"insight_type","in":"path","required":true,"schema":{"type":"string","title":"Insight Type"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/GenerateInsightRequest"},{"type":"null"}],"title":"Request"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/insights/studies/{study_id}/view/{insight_type}":{"get":{"tags":["insights"],"summary":"View Insight Html","description":"Get the HTML content for an insight (for iframe embedding).\n\nReturns the most recent generated HTML file for this insight type,\nor generates one if none exists.","operationId":"view_insight_html_api_insights_studies__study_id__view__insight_type__get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}},{"name":"insight_type","in":"path","required":true,"schema":{"type":"string","title":"Insight Type"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/insights/studies/{study_id}/generated":{"get":{"tags":["insights"],"summary":"List Generated Insights","description":"List all previously generated insight HTML files for a study.","operationId":"list_generated_insights_api_insights_studies__study_id__generated_get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/insights/studies/{study_id}/configured":{"get":{"tags":["insights"],"summary":"Get Configured Insights Endpoint","description":"Get insights configured in the study's optimization_config.json.","operationId":"get_configured_insights_endpoint_api_insights_studies__study_id__configured_get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/insights/studies/{study_id}/recommend":{"get":{"tags":["insights"],"summary":"Recommend Insights Endpoint","description":"Get AI recommendations for insights based on study objectives.","operationId":"recommend_insights_endpoint_api_insights_studies__study_id__recommend_get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/insights/studies/{study_id}/report":{"post":{"tags":["insights"],"summary":"Generate Report","description":"Generate comprehensive HTML report with all insights.\n\nArgs:\n study_id: Study identifier\n request: Report configuration with optional insight specs\n\nReturns:\n JSON with report path and generation results","operationId":"generate_report_api_insights_studies__study_id__report_post","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateReportRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/insights/studies/{study_id}/report/view":{"get":{"tags":["insights"],"summary":"View Report","description":"Get the latest generated report HTML for embedding.","operationId":"view_report_api_insights_studies__study_id__report_view_get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/insights/studies/{study_id}/summary":{"get":{"tags":["insights"],"summary":"Get Insights Summary","description":"Get insights summary JSON for Results page integration.","operationId":"get_insights_summary_api_insights_studies__study_id__summary_get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/insights/studies/{study_id}/new-best":{"get":{"tags":["insights"],"summary":"Check New Best","description":"Check if there's a new best solution that needs insight generation.\n\nThe optimization script writes new_best.json when a new best is found.\nDashboard can poll this endpoint to auto-generate insights.","operationId":"check_new_best_api_insights_studies__study_id__new_best_get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/insights/studies/{study_id}/auto-insights":{"post":{"tags":["insights"],"summary":"Generate Auto Insights","description":"Generate insights for the current best solution based on config.\n\nReads insights config from optimization_config.json and generates\nall enabled insights for the best (or latest new best) iteration.\nClears the needs_insights flag after generation.","operationId":"generate_auto_insights_api_insights_studies__study_id__auto_insights_post","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/context/playbook":{"get":{"tags":["context"],"summary":"Get Playbook Summary","description":"Get playbook summary statistics.","operationId":"get_playbook_summary_api_context_playbook_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaybookSummary"}}}}}}},"/api/context/playbook/items":{"get":{"tags":["context"],"summary":"Get Playbook Items","description":"Get playbook items with optional filtering.\n\nCategories:\n- str: Strategy\n- mis: Mistake\n- tool: Tool usage\n- cal: Calculation\n- dom: Domain knowledge\n- wf: Workflow","operationId":"get_playbook_items_api_context_playbook_items_get","parameters":[{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by category (str, mis, tool, etc.)","title":"Category"},"description":"Filter by category (str, mis, tool, etc.)"},{"name":"min_score","in":"query","required":false,"schema":{"type":"integer","description":"Minimum net score","default":0,"title":"Min Score"},"description":"Minimum net score"},{"name":"min_confidence","in":"query","required":false,"schema":{"type":"number","description":"Minimum confidence (0.0-1.0)","default":0.0,"title":"Min Confidence"},"description":"Minimum confidence (0.0-1.0)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","description":"Maximum items to return","default":50,"title":"Limit"},"description":"Maximum items to return"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","description":"Pagination offset","default":0,"title":"Offset"},"description":"Pagination offset"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PlaybookItemResponse"},"title":"Response Get Playbook Items Api Context Playbook Items Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/context/playbook/items/{item_id}":{"get":{"tags":["context"],"summary":"Get Playbook Item","description":"Get a specific playbook item by ID.","operationId":"get_playbook_item_api_context_playbook_items__item_id__get","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","title":"Item Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaybookItemResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["context"],"summary":"Delete Playbook Item","description":"Delete a playbook item.","operationId":"delete_playbook_item_api_context_playbook_items__item_id__delete","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","title":"Item Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/context/playbook/feedback":{"post":{"tags":["context"],"summary":"Record Feedback","description":"Record feedback on a playbook item.\n\nThis is how the system learns:\n- helpful=true increases the item's score\n- helpful=false decreases the item's score","operationId":"record_feedback_api_context_playbook_feedback_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/context/playbook/insights":{"post":{"tags":["context"],"summary":"Add Insight","description":"Add a new insight to the playbook.\n\nCategories:\n- str: Strategy - Optimization strategies that work\n- mis: Mistake - Common mistakes to avoid\n- tool: Tool - Tool usage patterns\n- cal: Calculation - Formulas and calculations\n- dom: Domain - Domain-specific knowledge (FEA, NX)\n- wf: Workflow - Workflow patterns","operationId":"add_insight_api_context_playbook_insights_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InsightRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/context/playbook/prune":{"post":{"tags":["context"],"summary":"Prune Playbook","description":"Prune harmful items from the playbook.\n\nItems with net_score <= threshold will be removed.","operationId":"prune_playbook_api_context_playbook_prune_post","parameters":[{"name":"threshold","in":"query","required":false,"schema":{"type":"integer","description":"Net score threshold for pruning","default":-3,"title":"Threshold"},"description":"Net score threshold for pruning"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/context/playbook/context":{"get":{"tags":["context"],"summary":"Get Playbook Context","description":"Get playbook context string formatted for LLM consumption.\n\nThis is what gets injected into the LLM context window.","operationId":"get_playbook_context_api_context_playbook_context_get","parameters":[{"name":"task_type","in":"query","required":false,"schema":{"type":"string","description":"Task type for context filtering","default":"optimization","title":"Task Type"},"description":"Task type for context filtering"},{"name":"max_items","in":"query","required":false,"schema":{"type":"integer","description":"Maximum items to include","default":15,"title":"Max Items"},"description":"Maximum items to include"},{"name":"min_confidence","in":"query","required":false,"schema":{"type":"number","description":"Minimum confidence threshold","default":0.5,"title":"Min Confidence"},"description":"Minimum confidence threshold"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/context/session":{"get":{"tags":["context"],"summary":"Get Session State","description":"Get current session state.","operationId":"get_session_state_api_context_session_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionStateResponse"}}}}}}},"/api/context/session/context":{"get":{"tags":["context"],"summary":"Get Session Context","description":"Get session context string for LLM consumption.","operationId":"get_session_context_api_context_session_context_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/context/cache/stats":{"get":{"tags":["context"],"summary":"Get Cache Stats","description":"Get KV-cache efficiency statistics.","operationId":"get_cache_stats_api_context_cache_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/context/learning/report":{"get":{"tags":["context"],"summary":"Get Learning Report","description":"Get a comprehensive learning report.","operationId":"get_learning_report_api_context_learning_report_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/":{"get":{"summary":"Root","description":"Serve the enhanced dashboard HTML","operationId":"root__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/health":{"get":{"summary":"Health Check","description":"Health check endpoint","operationId":"health_check_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}}},"components":{"schemas":{"Body_create_study_api_optimization_studies_post":{"properties":{"config":{"type":"string","title":"Config"},"prt_file":{"anyOf":[{"type":"string","format":"binary"},{"type":"null"}],"title":"Prt File"},"sim_file":{"anyOf":[{"type":"string","format":"binary"},{"type":"null"}],"title":"Sim File"},"fem_file":{"anyOf":[{"type":"string","format":"binary"},{"type":"null"}],"title":"Fem File"}},"type":"object","required":["config"],"title":"Body_create_study_api_optimization_studies_post"},"ChatRequest":{"properties":{"message":{"type":"string","title":"Message"},"study_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Study Id"},"conversation_history":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Conversation History"}},"type":"object","required":["message"],"title":"ChatRequest"},"ChatResponse":{"properties":{"response":{"type":"string","title":"Response"},"tool_calls":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Tool Calls"},"study_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Study Id"}},"type":"object","required":["response"],"title":"ChatResponse"},"CreateSessionRequest":{"properties":{"mode":{"type":"string","enum":["user","power"],"title":"Mode","default":"user"},"study_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Study Id"},"resume_session_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resume Session Id"}},"type":"object","title":"CreateSessionRequest"},"FeedbackRequest":{"properties":{"item_id":{"type":"string","title":"Item Id"},"helpful":{"type":"boolean","title":"Helpful"}},"type":"object","required":["item_id","helpful"],"title":"FeedbackRequest"},"GenerateInsightRequest":{"properties":{"iteration":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Iteration"},"trial_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Trial Id"},"config":{"additionalProperties":true,"type":"object","title":"Config","default":{}}},"type":"object","title":"GenerateInsightRequest","description":"Request model for insight generation."},"GenerateReportRequest":{"properties":{"specs":{"anyOf":[{"items":{"$ref":"#/components/schemas/InsightSpecRequest"},"type":"array"},{"type":"null"}],"title":"Specs"},"include_appendix":{"type":"boolean","title":"Include Appendix","default":true}},"type":"object","title":"GenerateReportRequest","description":"Request model for report generation."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"InsightRequest":{"properties":{"category":{"type":"string","title":"Category"},"content":{"type":"string","title":"Content"},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags"},"source_trial":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Source Trial"}},"type":"object","required":["category","content"],"title":"InsightRequest"},"InsightSpecRequest":{"properties":{"type":{"type":"string","title":"Type"},"name":{"type":"string","title":"Name"},"enabled":{"type":"boolean","title":"Enabled","default":true},"linked_objective":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Linked Objective"},"config":{"additionalProperties":true,"type":"object","title":"Config","default":{}},"include_in_report":{"type":"boolean","title":"Include In Report","default":true}},"type":"object","required":["type","name"],"title":"InsightSpecRequest","description":"Request model for insight specification."},"PlaybookItemResponse":{"properties":{"id":{"type":"string","title":"Id"},"category":{"type":"string","title":"Category"},"content":{"type":"string","title":"Content"},"helpful_count":{"type":"integer","title":"Helpful Count"},"harmful_count":{"type":"integer","title":"Harmful Count"},"net_score":{"type":"integer","title":"Net Score"},"confidence":{"type":"number","title":"Confidence"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"},"created_at":{"type":"string","title":"Created At"},"last_used":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Used"}},"type":"object","required":["id","category","content","helpful_count","harmful_count","net_score","confidence","tags","created_at","last_used"],"title":"PlaybookItemResponse"},"PlaybookSummary":{"properties":{"total_items":{"type":"integer","title":"Total Items"},"by_category":{"additionalProperties":true,"type":"object","title":"By Category"},"version":{"type":"integer","title":"Version"},"last_updated":{"type":"string","title":"Last Updated"},"avg_score":{"type":"number","title":"Avg Score"},"top_score":{"type":"integer","title":"Top Score"},"lowest_score":{"type":"integer","title":"Lowest Score"}},"type":"object","required":["total_items","by_category","version","last_updated","avg_score","top_score","lowest_score"],"title":"PlaybookSummary"},"SessionStateResponse":{"properties":{"session_id":{"type":"string","title":"Session Id"},"task_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Task Type"},"study_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Study Name"},"study_status":{"type":"string","title":"Study Status"},"trials_completed":{"type":"integer","title":"Trials Completed"},"trials_total":{"type":"integer","title":"Trials Total"},"best_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Best Value"},"recent_actions":{"items":{"type":"string"},"type":"array","title":"Recent Actions"},"recent_errors":{"items":{"type":"string"},"type":"array","title":"Recent Errors"}},"type":"object","required":["session_id","task_type","study_name","study_status","trials_completed","trials_total","best_value","recent_actions","recent_errors"],"title":"SessionStateResponse"},"StartOptimizationRequest":{"properties":{"freshStart":{"type":"boolean","title":"Freshstart","default":false},"maxIterations":{"type":"integer","title":"Maxiterations","default":100},"trials":{"type":"integer","title":"Trials","default":300},"feaBatchSize":{"type":"integer","title":"Feabatchsize","default":5},"tuneTrials":{"type":"integer","title":"Tunetrials","default":30},"ensembleSize":{"type":"integer","title":"Ensemblesize","default":3},"patience":{"type":"integer","title":"Patience","default":5}},"type":"object","title":"StartOptimizationRequest"},"StopRequest":{"properties":{"force":{"type":"boolean","title":"Force","default":true}},"type":"object","title":"StopRequest"},"SwitchModeRequest":{"properties":{"mode":{"type":"string","enum":["user","power"],"title":"Mode"}},"type":"object","required":["mode"],"title":"SwitchModeRequest"},"UpdateConfigRequest":{"properties":{"config":{"additionalProperties":true,"type":"object","title":"Config"}},"type":"object","required":["config"],"title":"UpdateConfigRequest"},"ValidateRequest":{"properties":{"topN":{"type":"integer","title":"Topn","default":5}},"type":"object","title":"ValidateRequest"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}}} \ No newline at end of file diff --git a/atomizer-dashboard/backend/api/routes/claude_code.py b/atomizer-dashboard/backend/api/routes/claude_code.py index b4452b46..cd0a32a4 100644 --- a/atomizer-dashboard/backend/api/routes/claude_code.py +++ b/atomizer-dashboard/backend/api/routes/claude_code.py @@ -83,23 +83,49 @@ async def generate_extractor_code(request: ExtractorGenerationRequest): # Build focused system prompt for extractor generation system_prompt = """You are generating a Python custom extractor function for Atomizer FEA optimization. -The function MUST: -1. Have signature: def extract(op2_path: str, fem_path: str, params: dict, subcase_id: int = 1) -> dict -2. Return a dict with extracted values (e.g., {"max_stress": 150.5, "mass": 2.3}) -3. Use pyNastran.op2.op2.OP2 for reading OP2 results -4. Handle missing data gracefully with try/except blocks +IMPORTANT: Choose the appropriate function signature based on what data is needed: -Available imports (already available, just use them): -- from pyNastran.op2.op2 import OP2 -- import numpy as np -- from pathlib import Path +## Option 1: FEA Results (OP2) - Use for stresses, displacements, frequencies, forces +```python +def extract(op2_path: str, fem_path: str, params: dict, subcase_id: int = 1) -> dict: + from pyNastran.op2.op2 import OP2 + op2 = OP2() + op2.read_op2(op2_path) + # Access: op2.displacements[subcase_id], op2.cquad4_stress[subcase_id], etc. + return {"max_stress": value} +``` -Common patterns: -- Displacement: op2.displacements[subcase_id].data[0, :, 1:4] (x,y,z components) +## Option 2: Expression/Computed Values (no FEA needed) - Use for dimensions, volumes, derived values +```python +def extract(trial_dir: str, config: dict, context: dict) -> dict: + import json + from pathlib import Path + + # Read mass properties (if available from model introspection) + mass_file = Path(trial_dir) / "mass_properties.json" + if mass_file.exists(): + with open(mass_file) as f: + props = json.load(f) + mass = props.get("mass_kg", 0) + + # Or use config values directly (e.g., expression values) + length_mm = config.get("length_expression", 100) + + # context has results from other extractors + other_value = context.get("other_extractor_output", 0) + + return {"computed_value": length_mm * 2} +``` + +Available imports: pyNastran.op2.op2.OP2, numpy, pathlib.Path, json + +Common OP2 patterns: +- Displacement: op2.displacements[subcase_id].data[0, :, 1:4] (x,y,z) - Stress: op2.cquad4_stress[subcase_id] or op2.ctria3_stress[subcase_id] - Eigenvalues: op2.eigenvalues[subcase_id] +- Mass: op2.grid_point_weight (if available) -Return ONLY the complete Python code wrapped in ```python ... ```. No explanations outside the code block.""" +Return ONLY the complete Python code wrapped in ```python ... ```. No explanations.""" # Build user prompt with context user_prompt = f"Generate a custom extractor that: {request.prompt}" diff --git a/atomizer-dashboard/backend/sessions.db b/atomizer-dashboard/backend/sessions.db index 4e22eae3..5602a7fc 100644 Binary files a/atomizer-dashboard/backend/sessions.db and b/atomizer-dashboard/backend/sessions.db differ diff --git a/atomizer-dashboard/frontend/src/components/canvas/SpecRenderer.tsx b/atomizer-dashboard/frontend/src/components/canvas/SpecRenderer.tsx index 5951240d..7c8853fa 100644 --- a/atomizer-dashboard/frontend/src/components/canvas/SpecRenderer.tsx +++ b/atomizer-dashboard/frontend/src/components/canvas/SpecRenderer.tsx @@ -104,18 +104,24 @@ function getDefaultNodeData(type: AddableNodeType, position: { x: number; y: num name: `objective_${timestamp}`, direction: 'minimize', weight: 1.0, - source_extractor_id: null, - source_output: null, + // Source is required - use placeholder that user must configure + source: { + extractor_id: 'ext_001', // Placeholder - user needs to configure + output_name: 'value', + }, canvas_position: position, }; case 'constraint': return { name: `constraint_${timestamp}`, - constraint_type: 'hard', // Must be 'hard' or 'soft' + type: 'hard', // Must be 'hard' or 'soft' (field is 'type' not 'constraint_type') operator: '<=', - limit: 1.0, - source_extractor_id: null, - source_output: null, + threshold: 1.0, // Field is 'threshold' not 'limit' + // Source is required + source: { + extractor_id: 'ext_001', // Placeholder - user needs to configure + output_name: 'value', + }, enabled: true, canvas_position: position, }; diff --git a/atomizer-dashboard/frontend/src/hooks/useSpecUndoRedo.ts b/atomizer-dashboard/frontend/src/hooks/useSpecUndoRedo.ts index a9e5345e..d79f96ff 100644 --- a/atomizer-dashboard/frontend/src/hooks/useSpecUndoRedo.ts +++ b/atomizer-dashboard/frontend/src/hooks/useSpecUndoRedo.ts @@ -16,7 +16,7 @@ import { useEffect, useRef } from 'react'; import { useUndoRedo, UndoRedoResult } from './useUndoRedo'; -import { useSpecStore, useSpec, useSpecIsDirty } from './useSpecStore'; +import { useSpecStore, useSpec } from './useSpecStore'; import { AtomizerSpec } from '../types/atomizer-spec'; const STORAGE_KEY_PREFIX = 'atomizer-spec-history-'; @@ -28,7 +28,6 @@ export interface SpecUndoRedoResult extends UndoRedoResult export function useSpecUndoRedo(): SpecUndoRedoResult { const spec = useSpec(); - const isDirty = useSpecIsDirty(); const studyId = useSpecStore((state) => state.studyId); const lastSpecRef = useRef(null); @@ -56,13 +55,21 @@ export function useSpecUndoRedo(): SpecUndoRedoResult { }, }); - // Record snapshot when spec changes (and is dirty) + // Record snapshot when spec changes + // Note: We removed the isDirty check because with auto-save, isDirty is always false + // after the API call completes. Instead, we compare the spec directly. useEffect(() => { - if (spec && isDirty && spec !== lastSpecRef.current) { - lastSpecRef.current = spec; - undoRedo.recordSnapshot(); + if (spec && spec !== lastSpecRef.current) { + // Deep compare to avoid recording duplicate snapshots + const specStr = JSON.stringify(spec); + const lastStr = lastSpecRef.current ? JSON.stringify(lastSpecRef.current) : ''; + + if (specStr !== lastStr) { + lastSpecRef.current = spec; + undoRedo.recordSnapshot(); + } } - }, [spec, isDirty, undoRedo]); + }, [spec, undoRedo]); // Clear history when study changes useEffect(() => { diff --git a/docs/reviews/ARCHITECTURE_REVIEW.md b/docs/reviews/ARCHITECTURE_REVIEW.md index 1f871062..71230820 100644 --- a/docs/reviews/ARCHITECTURE_REVIEW.md +++ b/docs/reviews/ARCHITECTURE_REVIEW.md @@ -19,14 +19,14 @@ Atomizer is a structural optimization platform that enables engineers to optimiz ### Architecture Quality Score: **8.5/10** -| Aspect | Score | Notes | -|--------|-------|-------| -| Data Integrity | 9/10 | Single source of truth, hash-based conflict detection | -| Type Safety | 9/10 | Pydantic models throughout backend | -| Extensibility | 8/10 | Custom extractors, algorithms supported | -| Performance | 8/10 | Optimistic updates, WebSocket streaming | -| Maintainability | 8/10 | Clear separation of concerns | -| Documentation | 7/10 | Good inline docs, needs more high-level guides | +| Aspect | Score | Notes | +| --------------- | ----- | ----------------------------------------------------- | +| Data Integrity | 9/10 | Single source of truth, hash-based conflict detection | +| Type Safety | 9/10 | Pydantic models throughout backend | +| Extensibility | 8/10 | Custom extractors, algorithms supported | +| Performance | 8/10 | Optimistic updates, WebSocket streaming | +| Maintainability | 8/10 | Clear separation of concerns | +| Documentation | 7/10 | Good inline docs, needs more high-level guides | --- diff --git a/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/model/ASSY_M1.prt b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/model/ASSY_M1.prt new file mode 100644 index 00000000..6b7d7e3e Binary files /dev/null and b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/model/ASSY_M1.prt differ diff --git a/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/model/ASSY_M1_assyfem1.afm b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/model/ASSY_M1_assyfem1.afm new file mode 100644 index 00000000..2b72fb5d Binary files /dev/null and b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/model/ASSY_M1_assyfem1.afm differ diff --git a/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/model/ASSY_M1_assyfem1_sim1.sim b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/model/ASSY_M1_assyfem1_sim1.sim new file mode 100644 index 00000000..432e43c2 Binary files /dev/null and b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/model/ASSY_M1_assyfem1_sim1.sim differ diff --git a/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/model/M1_Blank.prt b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/model/M1_Blank.prt new file mode 100644 index 00000000..aabdbc5b Binary files /dev/null and b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/model/M1_Blank.prt differ diff --git a/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/model/M1_Blank_fem1.fem b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/model/M1_Blank_fem1.fem new file mode 100644 index 00000000..17c56f50 Binary files /dev/null and b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/model/M1_Blank_fem1.fem differ diff --git a/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/model/M1_Blank_fem1_i.prt b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/model/M1_Blank_fem1_i.prt new file mode 100644 index 00000000..c1970a03 Binary files /dev/null and b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/model/M1_Blank_fem1_i.prt differ diff --git a/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/model/M1_Vertical_Support_Skeleton_fem1.fem b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/model/M1_Vertical_Support_Skeleton_fem1.fem new file mode 100644 index 00000000..8d8cfe68 Binary files /dev/null and b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/model/M1_Vertical_Support_Skeleton_fem1.fem differ diff --git a/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/model/_introspection_cache.json b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/model/_introspection_cache.json new file mode 100644 index 00000000..6f141559 --- /dev/null +++ b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/model/_introspection_cache.json @@ -0,0 +1,195 @@ +{ + "part_file": "ASSY_M1.prt", + "part_path": "C:\\Users\\antoi\\Atomizer\\studies\\M1_Mirror\\m1_mirror_cost_reduction_lateral\\1_setup\\model\\ASSY_M1.prt", + "success": true, + "error": null, + "expressions": { + "user": [ + { + "name": "p7_CircularPattern_pattern_Circular_Dir_count", + "value": 3.0, + "rhs": "3", + "units": null, + "type": "Number" + }, + { + "name": "p8_CircularPattern_pattern_Circular_Dir_offset_angle", + "value": 120.0, + "rhs": "120", + "units": "Degrees", + "type": "Number" + }, + { + "name": "p66_x", + "value": 0.0, + "rhs": "0.00000000000", + "units": "MilliMeter", + "type": "Number" + }, + { + "name": "p68_z", + "value": 0.0, + "rhs": "0.00000000000", + "units": "MilliMeter", + "type": "Number" + }, + { + "name": "p67_y", + "value": 0.0, + "rhs": "0.00000000000", + "units": "MilliMeter", + "type": "Number" + } + ], + "internal": [ + { + "name": "p14", + "value": 0.0, + "rhs": "0", + "units": "Degrees", + "type": "Number" + }, + { + "name": "p64", + "value": 120.0, + "rhs": "120", + "units": "Degrees", + "type": "Number" + }, + { + "name": "p9", + "value": 10.0, + "rhs": "10", + "units": "MilliMeter", + "type": "Number" + }, + { + "name": "p10", + "value": 240.0, + "rhs": "240", + "units": "Degrees", + "type": "Number" + }, + { + "name": "p11", + "value": 1.0, + "rhs": "1", + "units": null, + "type": "Number" + }, + { + "name": "p12", + "value": 10.0, + "rhs": "10", + "units": "MilliMeter", + "type": "Number" + }, + { + "name": "p13", + "value": 0.0, + "rhs": "0", + "units": "MilliMeter", + "type": "Number" + } + ], + "total_count": 12, + "user_count": 5 + }, + "mass_properties": { + "mass_kg": 0.0, + "mass_g": 0.0, + "volume_mm3": 0.0, + "surface_area_mm2": 0.0, + "center_of_gravity_mm": [ + 0.0, + 0.0, + 0.0 + ], + "num_bodies": 0, + "success": false + }, + "materials": { + "assigned": [], + "available": [], + "library": [], + "pmm_error": "'NXOpen.Part' object has no attribute 'PhysicalMaterialManager'" + }, + "bodies": { + "solid_bodies": [], + "sheet_bodies": [], + "counts": { + "solid": 0, + "sheet": 0, + "total": 0 + } + }, + "attributes": [ + { + "title": "NX_Arrangement", + "type": "5", + "value": "Arrangement 1" + }, + { + "title": "NX_ComponentGroup", + "type": "5", + "value": "AllComponents" + }, + { + "title": "NX_ReferenceSet", + "type": "5", + "value": "Empty" + }, + { + "title": "NX_MaterialMissingAssignments", + "type": "5", + "value": "TRUE" + }, + { + "title": "NX_MaterialMultipleAssigned", + "type": "5", + "value": "FALSE" + } + ], + "groups": [], + "features": { + "total_count": 0, + "by_type": {}, + "first_10": [] + }, + "datums": { + "planes": [], + "csys": [], + "axes": [] + }, + "units": { + "base_units": { + "Length": "MilliMeter", + "Mass": "Kilogram", + "Time": "Second", + "Temperature": "Kelvin", + "Angle": "Radian", + "Area": "SquareMilliMeter", + "Volume": "CubicMilliMeter", + "Force": "MilliNewton", + "Pressure": "PressureMilliNewtonPerSquareMilliMeter" + }, + "system": "Metric (mm)" + }, + "linked_parts": { + "loaded_parts": [ + { + "name": "M1_Blank", + "path": "C:\\Users\\antoi\\Atomizer\\studies\\M1_Mirror\\m1_mirror_cost_reduction_lateral\\1_setup\\model\\M1_Blank.prt", + "leaf_name": "M1_Blank" + }, + { + "name": "ASSY_M1", + "path": "C:\\Users\\antoi\\Atomizer\\studies\\M1_Mirror\\m1_mirror_cost_reduction_lateral\\1_setup\\model\\ASSY_M1.prt", + "leaf_name": "ASSY_M1" + } + ], + "fem_parts": [], + "sim_parts": [], + "idealized_parts": [] + } +} \ No newline at end of file diff --git a/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/model/_temp_introspection.json b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/model/_temp_introspection.json new file mode 100644 index 00000000..6f141559 --- /dev/null +++ b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/model/_temp_introspection.json @@ -0,0 +1,195 @@ +{ + "part_file": "ASSY_M1.prt", + "part_path": "C:\\Users\\antoi\\Atomizer\\studies\\M1_Mirror\\m1_mirror_cost_reduction_lateral\\1_setup\\model\\ASSY_M1.prt", + "success": true, + "error": null, + "expressions": { + "user": [ + { + "name": "p7_CircularPattern_pattern_Circular_Dir_count", + "value": 3.0, + "rhs": "3", + "units": null, + "type": "Number" + }, + { + "name": "p8_CircularPattern_pattern_Circular_Dir_offset_angle", + "value": 120.0, + "rhs": "120", + "units": "Degrees", + "type": "Number" + }, + { + "name": "p66_x", + "value": 0.0, + "rhs": "0.00000000000", + "units": "MilliMeter", + "type": "Number" + }, + { + "name": "p68_z", + "value": 0.0, + "rhs": "0.00000000000", + "units": "MilliMeter", + "type": "Number" + }, + { + "name": "p67_y", + "value": 0.0, + "rhs": "0.00000000000", + "units": "MilliMeter", + "type": "Number" + } + ], + "internal": [ + { + "name": "p14", + "value": 0.0, + "rhs": "0", + "units": "Degrees", + "type": "Number" + }, + { + "name": "p64", + "value": 120.0, + "rhs": "120", + "units": "Degrees", + "type": "Number" + }, + { + "name": "p9", + "value": 10.0, + "rhs": "10", + "units": "MilliMeter", + "type": "Number" + }, + { + "name": "p10", + "value": 240.0, + "rhs": "240", + "units": "Degrees", + "type": "Number" + }, + { + "name": "p11", + "value": 1.0, + "rhs": "1", + "units": null, + "type": "Number" + }, + { + "name": "p12", + "value": 10.0, + "rhs": "10", + "units": "MilliMeter", + "type": "Number" + }, + { + "name": "p13", + "value": 0.0, + "rhs": "0", + "units": "MilliMeter", + "type": "Number" + } + ], + "total_count": 12, + "user_count": 5 + }, + "mass_properties": { + "mass_kg": 0.0, + "mass_g": 0.0, + "volume_mm3": 0.0, + "surface_area_mm2": 0.0, + "center_of_gravity_mm": [ + 0.0, + 0.0, + 0.0 + ], + "num_bodies": 0, + "success": false + }, + "materials": { + "assigned": [], + "available": [], + "library": [], + "pmm_error": "'NXOpen.Part' object has no attribute 'PhysicalMaterialManager'" + }, + "bodies": { + "solid_bodies": [], + "sheet_bodies": [], + "counts": { + "solid": 0, + "sheet": 0, + "total": 0 + } + }, + "attributes": [ + { + "title": "NX_Arrangement", + "type": "5", + "value": "Arrangement 1" + }, + { + "title": "NX_ComponentGroup", + "type": "5", + "value": "AllComponents" + }, + { + "title": "NX_ReferenceSet", + "type": "5", + "value": "Empty" + }, + { + "title": "NX_MaterialMissingAssignments", + "type": "5", + "value": "TRUE" + }, + { + "title": "NX_MaterialMultipleAssigned", + "type": "5", + "value": "FALSE" + } + ], + "groups": [], + "features": { + "total_count": 0, + "by_type": {}, + "first_10": [] + }, + "datums": { + "planes": [], + "csys": [], + "axes": [] + }, + "units": { + "base_units": { + "Length": "MilliMeter", + "Mass": "Kilogram", + "Time": "Second", + "Temperature": "Kelvin", + "Angle": "Radian", + "Area": "SquareMilliMeter", + "Volume": "CubicMilliMeter", + "Force": "MilliNewton", + "Pressure": "PressureMilliNewtonPerSquareMilliMeter" + }, + "system": "Metric (mm)" + }, + "linked_parts": { + "loaded_parts": [ + { + "name": "M1_Blank", + "path": "C:\\Users\\antoi\\Atomizer\\studies\\M1_Mirror\\m1_mirror_cost_reduction_lateral\\1_setup\\model\\M1_Blank.prt", + "leaf_name": "M1_Blank" + }, + { + "name": "ASSY_M1", + "path": "C:\\Users\\antoi\\Atomizer\\studies\\M1_Mirror\\m1_mirror_cost_reduction_lateral\\1_setup\\model\\ASSY_M1.prt", + "leaf_name": "ASSY_M1" + } + ], + "fem_parts": [], + "sim_parts": [], + "idealized_parts": [] + } +} \ No newline at end of file diff --git a/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/model_expressions.json b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/model_expressions.json new file mode 100644 index 00000000..4be6c7db --- /dev/null +++ b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/model_expressions.json @@ -0,0 +1,58 @@ +{ + "blank_backface_angle": 4.0, + "lateral_inner_angle": 31.93, + "whiffle_p1": 390.0, + "whiffle_p2": 135.0, + "whiffle_p3": 80.0, + "mirror_face_thickness": 15.0, + "blank_mass": 66.7514321518352, + "lateral_second_row_angle": 10.0, + "p1049_x": 0.0, + "blank_backface_max_radius": 582.0, + "p1051_z": 0.0, + "rib_thickness_lataral": 10.0, + "hole_count": 10.0, + "Pocket_Radius": 10.05, + "lateral_closeness": 7.89, + "offset_whiffle": 35.0, + "inner_circular_rib_dia": 537.86, + "whiffle_min": 56.65, + "Pattern_p5610": 60.0, + "whiffle_triangle_closeness": 69.24, + "beam_face_thickness": 20.0, + "offset_lateral_support_contact": 5.0, + "Pattern_p2656": 360.0, + "rib_thickness_lateral_truss": 12.06, + "whiffle_max": 8.0, + "outer_post_distance": 551.7504884773748, + "Pattern_p2653": 3.0, + "Pattern_p2654": 120.0, + "Pattern_p2883": 3.0, + "Pattern_p2884": 120.0, + "Pattern_p2886": 360.0, + "ribs_circular_thk": 6.81, + "support_cone_angle": 0.0, + "rib_thickness": 8.07, + "rib_lin_1": 60.0, + "rib_lin_2": 80.0, + "rib_lin_4": 80.0, + "in_between_u": 0.5, + "beam_half_core_thickness": 20.0, + "lateral_middle_pivot": 21.07, + "Pattern_p5609": 6.0, + "lateral_inner_u": 0.3, + "center_thickness": 85.0, + "rib_pocket_bottom_radius": 10.0, + "lateral_outer_pivot": 8.615999999999998, + "Pattern_p5612": 360.0, + "Pattern_p3829": 3.0, + "Pattern_p3830": 120.0, + "Pattern_p3832": 360.0, + "p1050_y": 0.0, + "holes_diameter": 400.0, + "lateral_outer_angle": 10.77, + "vertical_support_diameter_seat_offset": 10.0, + "lateral_outer_u": 0.8, + "vertical_support_seat_depth": 20.0, + "lateral_inner_pivot": 9.578999999999997 +} \ No newline at end of file diff --git a/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/optimization_config.json b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/optimization_config.json new file mode 100644 index 00000000..0c8c6379 --- /dev/null +++ b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/optimization_config.json @@ -0,0 +1,131 @@ +{ + "$schema": "Atomizer M1 Mirror Cost Reduction - Lateral Supports Optimization", + "study_name": "m1_mirror_cost_reduction_lateral", + "study_tag": "CMA-ES-100", + "description": "Lateral support optimization with new U-joint expressions (lateral_inner_u, lateral_outer_u) for cost reduction model. Focus on WFE and MFG only - no mass objective.", + "business_context": { + "purpose": "Optimize lateral support geometry using new U-joint parameterization on cost reduction model", + "benefit": "Improved lateral support performance with cleaner parameterization", + "goal": "Minimize WFE at 40/60 deg and MFG at 90 deg" + }, + "optimization": { + "algorithm": "CMA-ES", + "n_trials": 100, + "n_startup_trials": 0, + "sigma0": 0.3, + "notes": "CMA-ES is optimal for 5D continuous optimization - fast convergence, robust" + }, + "extraction_method": { + "type": "zernike_opd", + "class": "ZernikeOPDExtractor", + "method": "extract_relative", + "inner_radius": 135.75, + "description": "OPD-based Zernike with ANNULAR aperture (271.5mm central hole excluded)" + }, + "design_variables": [ + { + "name": "lateral_inner_u", + "expression_name": "lateral_inner_u", + "min": 0.2, + "max": 0.95, + "baseline": 0.3, + "units": "unitless", + "enabled": true, + "notes": "U-joint ratio for inner lateral support (replaces lateral_inner_pivot)" + }, + { + "name": "lateral_outer_u", + "expression_name": "lateral_outer_u", + "min": 0.2, + "max": 0.95, + "baseline": 0.8, + "units": "unitless", + "enabled": true, + "notes": "U-joint ratio for outer lateral support (replaces lateral_outer_pivot)" + }, + { + "name": "lateral_middle_pivot", + "expression_name": "lateral_middle_pivot", + "min": 15.0, + "max": 27.0, + "baseline": 21.07, + "units": "mm", + "enabled": true, + "notes": "Middle pivot position on lateral support" + }, + { + "name": "lateral_inner_angle", + "expression_name": "lateral_inner_angle", + "min": 25.0, + "max": 35.0, + "baseline": 31.93, + "units": "degrees", + "enabled": true, + "notes": "Inner lateral support angle" + }, + { + "name": "lateral_outer_angle", + "expression_name": "lateral_outer_angle", + "min": 8.0, + "max": 17.0, + "baseline": 10.77, + "units": "degrees", + "enabled": true, + "notes": "Outer lateral support angle" + } + ], + "fixed_parameters": [], + "constraints": [ + { + "name": "blank_mass_max", + "type": "hard", + "expression": "mass_kg <= 120.0", + "description": "Maximum blank mass constraint (still enforced even though mass not optimized)", + "penalty_weight": 1000.0 + } + ], + "objectives": [ + { + "name": "wfe_40_20", + "description": "Filtered RMS WFE at 40 deg relative to 20 deg (annular)", + "direction": "minimize", + "weight": 6.0, + "target": 4.0, + "units": "nm" + }, + { + "name": "wfe_60_20", + "description": "Filtered RMS WFE at 60 deg relative to 20 deg (annular)", + "direction": "minimize", + "weight": 5.0, + "target": 10.0, + "units": "nm" + }, + { + "name": "mfg_90", + "description": "Manufacturing deformation at 90 deg polishing (J1-J3 filtered, annular)", + "direction": "minimize", + "weight": 3.0, + "target": 20.0, + "units": "nm" + } + ], + "weighted_sum_formula": "6*wfe_40_20 + 5*wfe_60_20 + 3*mfg_90", + "zernike_settings": { + "n_modes": 50, + "filter_low_orders": 4, + "displacement_unit": "mm", + "subcases": ["1", "2", "3", "4"], + "subcase_labels": {"1": "90deg", "2": "20deg", "3": "40deg", "4": "60deg"}, + "reference_subcase": "2", + "method": "opd", + "inner_radius": 135.75 + }, + "nx_settings": { + "nx_install_path": "C:\\Program Files\\Siemens\\DesigncenterNX2512", + "sim_file": "ASSY_M1_assyfem1_sim1.sim", + "solution_name": "Solution 1", + "op2_pattern": "*-solution_1.op2", + "simulation_timeout_s": 600 + } +} diff --git a/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/3_results/optimization_summary.json b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/3_results/optimization_summary.json new file mode 100644 index 00000000..c45b4351 --- /dev/null +++ b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/3_results/optimization_summary.json @@ -0,0 +1,28 @@ +{ + "study_name": "m1_mirror_cost_reduction_lateral", + "algorithm": "CMA-ES", + "extraction_method": "ZernikeOPD_Annular", + "inner_radius_mm": 135.75, + "objectives_note": "Mass NOT in objective - WFE only", + "total_trials": 169, + "feasible_trials": 167, + "best_trial": { + "number": 163, + "weighted_sum": 181.1220151783071, + "objectives": { + "wfe_40_20": 5.901179945313834, + "wfe_60_20": 13.198682506114679, + "mfg_90": 26.573840991950224, + "mass_kg": 96.75011491846891 + }, + "params": { + "lateral_inner_u": 0.32248417341983515, + "lateral_outer_u": 0.9038210727913156, + "lateral_middle_pivot": 21.25398896032501, + "lateral_inner_angle": 30.182447933329243, + "lateral_outer_angle": 15.08932828662093 + }, + "iter_folder": "C:\\Users\\antoi\\Atomizer\\studies\\M1_Mirror\\m1_mirror_cost_reduction_lateral\\2_iterations\\iter164" + }, + "timestamp": "2026-01-14T17:59:38.649254" +} \ No newline at end of file diff --git a/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/3_results/study.db b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/3_results/study.db new file mode 100644 index 00000000..4b9d68da Binary files /dev/null and b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/3_results/study.db differ diff --git a/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/README.md b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/README.md new file mode 100644 index 00000000..8ac15c41 --- /dev/null +++ b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/README.md @@ -0,0 +1,136 @@ +# M1 Mirror Cost Reduction - Lateral Supports Optimization + +> See [../README.md](../README.md) for project overview and optical specifications. + +## Study Overview + +| Field | Value | +|-------|-------| +| **Study Name** | m1_mirror_cost_reduction_lateral | +| **Algorithm** | CMA-ES | +| **Status** | Ready to run | +| **Created** | 2026-01-13 | +| **Trials** | 100 planned | +| **Focus** | Lateral support geometry only | + +## Purpose + +Optimize **lateral support parameters only** for the COST REDUCTION model using the new U-joint parameterization: +- `lateral_inner_u` and `lateral_outer_u` replace the old `lateral_inner_pivot` and `lateral_outer_pivot` +- All other parameters (whiffle, ribs, thickness) are **fixed at baseline values** +- **Mass is NOT an objective** - only WFE and MFG are optimized + +## Design Variables (5) + +| Variable | Min | Max | Baseline | Units | Notes | +|----------|-----|-----|----------|-------|-------| +| `lateral_inner_u` | 0.2 | 0.95 | TBD | unitless | U-joint ratio for inner lateral (NEW) | +| `lateral_outer_u` | 0.2 | 0.95 | TBD | unitless | U-joint ratio for outer lateral (NEW) | +| `lateral_middle_pivot` | 15.0 | 27.0 | TBD | mm | Middle pivot position | +| `lateral_inner_angle` | 25.0 | 35.0 | TBD | degrees | Inner lateral angle | +| `lateral_outer_angle` | 8.0 | 17.0 | TBD | degrees | Outer lateral angle | + +**Note:** Baselines marked TBD will be updated after model introspection. + +## Objectives + +| Objective | Weight | Target | Description | +|-----------|--------|--------|-------------| +| `wfe_40_20` | 6.0 | 4.0 nm | Filtered RMS WFE at 40 deg relative to 20 deg | +| `wfe_60_20` | 5.0 | 10.0 nm | Filtered RMS WFE at 60 deg relative to 20 deg | +| `mfg_90` | 3.0 | 20.0 nm | Manufacturing deformation at 90 deg polishing | + +**Weighted Sum Formula:** `6*wfe_40_20 + 5*wfe_60_20 + 3*mfg_90` + +**Note:** Mass is **NOT** in the objective function. A hard constraint (mass <= 120 kg) is still enforced. + +## Fixed Parameters (Baked in Model) + +All non-lateral parameters are fixed at the model's current values. These are **not pushed** during optimization - the model already contains the correct values. + +## Why CMA-ES? + +| Factor | This Problem | Why CMA-ES | +|--------|--------------|------------| +| Dimensions | 5 variables | CMA-ES optimal for 5-50D | +| Variable type | All continuous | CMA-ES designed for continuous | +| Landscape | Smooth (physics-based) | CMA-ES exploits gradient structure | +| Correlation | Lateral params likely correlated | CMA-ES learns correlations automatically | +| Convergence | 100 trials budget | CMA-ES converges 2-3x faster than TPE | + +### CMA-ES Settings +- `sigma0`: 0.3 (30% of range for initial exploration) +- `restart_strategy`: IPOP (restarts with larger population if stuck) +- `seed`: 42 + +## Model Files + +Place the following files in `1_setup/model/`: + +| File | Purpose | +|------|---------| +| `ASSY_M1_assyfem1_sim1.sim` | Simulation file | +| `*.fem` | FEM mesh files | +| `*.prt` | Geometry parts | +| `*_i.prt` | Idealized part (critical for mesh updates) | + +## Extraction Method + +- **Type:** ZernikeOPDExtractor with ANNULAR aperture +- **Inner radius:** 135.75 mm (271.5 mm central hole excluded) +- **Zernike modes:** 50 +- **Filter orders:** J1-J4 removed for WFE, J1-J3 for MFG +- **Subcases:** 90 deg (1), 20 deg (2), 40 deg (3), 60 deg (4) +- **Reference:** 20 deg (subcase 2) + +## Usage + +```bash +# Single test trial +python run_optimization.py --test + +# Full optimization (100 trials) - auto-launches dashboard +python run_optimization.py --start + +# Custom trial count +python run_optimization.py --start --trials 50 + +# Resume interrupted run +python run_optimization.py --start --resume + +# Without dashboard +python run_optimization.py --start --no-dashboard +``` + +## Directory Structure + +``` +m1_mirror_cost_reduction_lateral/ +|-- 1_setup/ +| |-- model/ # NX model files (user to add) +| `-- optimization_config.json # Study configuration +|-- 2_iterations/ # FEA iteration folders +|-- 3_results/ # Results database & summaries +| |-- study.db # Optuna SQLite database +| |-- optimization.log # Run log +| `-- optimization_summary.json # Final results +|-- run_optimization.py # Main optimization script +|-- README.md # This file +`-- STUDY_REPORT.md # Results template +``` + +## Setup Checklist + +- [ ] Copy model files to `1_setup/model/` +- [ ] Run introspection to get baseline values +- [ ] Update `optimization_config.json` with correct baselines +- [ ] Run `--test` to verify setup +- [ ] Run full optimization + +## Results + +*Study not yet run. Results will be updated after optimization completes.* + +## References + +- Sister study: [m1_mirror_flatback_lateral](../m1_mirror_flatback_lateral/) (same approach, flat back model) diff --git a/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/STUDY_REPORT.md b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/STUDY_REPORT.md new file mode 100644 index 00000000..169d1e22 --- /dev/null +++ b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/STUDY_REPORT.md @@ -0,0 +1,126 @@ +# Study Report: m1_mirror_cost_reduction_lateral + +## Executive Summary + +| Metric | Value | +|--------|-------| +| **Study Name** | m1_mirror_cost_reduction_lateral | +| **Algorithm** | CMA-ES | +| **Trials Completed** | _pending_ | +| **Best Weighted Sum** | _pending_ | +| **Constraint Satisfaction** | _pending_ | + +## Optimization Focus + +This study optimizes **lateral support parameters only** for the COST REDUCTION model using the new U-joint parameterization: +- `lateral_inner_u`, `lateral_outer_u` (NEW - replace pivot params) +- `lateral_middle_pivot`, `lateral_inner_angle`, `lateral_outer_angle` + +**Key Difference**: Mass is NOT an objective - only WFE and MFG are optimized. + +## Optimization Progress + +_To be filled after optimization run_ + +### Convergence Plot + +_Insert convergence plot here_ + +### Parameter Evolution + +_Insert parameter evolution plots here_ + +## Best Designs Found + +### Top 5 Designs + +| Rank | Trial | WS | WFE 40/20 | WFE 60/20 | MFG 90 | Mass | +|------|-------|-------|-----------|-----------|--------|------| +| 1 | _pending_ | _pending_ | _pending_ | _pending_ | _pending_ | _pending_ | +| 2 | _pending_ | _pending_ | _pending_ | _pending_ | _pending_ | _pending_ | +| 3 | _pending_ | _pending_ | _pending_ | _pending_ | _pending_ | _pending_ | +| 4 | _pending_ | _pending_ | _pending_ | _pending_ | _pending_ | _pending_ | +| 5 | _pending_ | _pending_ | _pending_ | _pending_ | _pending_ | _pending_ | + +### Best Design Parameters + +| Parameter | Baseline | Best | Change | +|-----------|----------|------|--------| +| lateral_inner_u | _TBD_ | _pending_ | _pending_ | +| lateral_outer_u | _TBD_ | _pending_ | _pending_ | +| lateral_middle_pivot | _TBD_ | _pending_ | _pending_ | +| lateral_inner_angle | _TBD_ | _pending_ | _pending_ | +| lateral_outer_angle | _TBD_ | _pending_ | _pending_ | + +## Parameter Sensitivity + +_To be filled after analysis_ + +### Most Influential Parameters + +1. _pending_ +2. _pending_ +3. _pending_ + +### Parameter Correlations + +_Insert correlation analysis_ + +## Comparison to Baseline + +| Metric | Baseline | Best | Improvement | +|--------|----------|------|-------------| +| Weighted Sum | _pending_ | _pending_ | _pending_ | +| WFE 40/20 | _pending_ | _pending_ | _pending_ | +| WFE 60/20 | _pending_ | _pending_ | _pending_ | +| MFG 90 | _pending_ | _pending_ | _pending_ | + +## Comparison to Flat Back Lateral Study + +| Metric | Flat Back | Cost Reduction | Difference | +|--------|-----------|----------------|------------| +| Best WS | _pending_ | _pending_ | _pending_ | +| Best WFE 40/20 | _pending_ | _pending_ | _pending_ | +| Best WFE 60/20 | _pending_ | _pending_ | _pending_ | + +## Key Learnings + +_To be filled after analysis_ + +1. _pending_ +2. _pending_ +3. _pending_ + +## Recommendations + +_To be filled after analysis_ + +### For Next Study + +- [ ] _pending_ + +### For Production + +- [ ] _pending_ + +## Appendix + +### Run Configuration + +```json +Algorithm: CMA-ES +Trials: 100 +sigma0: 0.3 +restart_strategy: IPOP +``` + +### Files Generated + +- `3_results/study.db` - Optuna database +- `3_results/optimization.log` - Run log +- `3_results/optimization_summary.json` - Final results +- `3_results/best_design_archive/` - Archived best designs + +--- + +*Report generated: _pending_* diff --git a/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/atomizer_spec.json b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/atomizer_spec.json index be6f15a4..a6d57b69 100644 --- a/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/atomizer_spec.json +++ b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/atomizer_spec.json @@ -2,9 +2,9 @@ "meta": { "version": "2.0", "created": "2026-01-17T15:35:12.024432Z", - "modified": "2026-01-20T19:01:36.016065Z", + "modified": "2026-01-20T20:05:28.197219Z", "created_by": "migration", - "modified_by": "canvas", + "modified_by": "test", "study_name": "m1_mirror_cost_reduction_lateral", "description": "Lateral support optimization with new U-joint expressions (lateral_inner_u, lateral_outer_u) for cost reduction model. Focus on WFE and MFG only - no mass objective.", "tags": [ @@ -151,6 +151,38 @@ "x": 50, "y": 580 } + }, + { + "name": "variable_1768938898079", + "expression_name": "expr_1768938898079", + "type": "continuous", + "bounds": { + "min": 0, + "max": 1 + }, + "baseline": 0.5, + "enabled": true, + "canvas_position": { + "x": -185.06035488622524, + "y": 91.62521000204346 + }, + "id": "dv_008" + }, + { + "name": "test_dv", + "expression_name": "test_expr", + "type": "continuous", + "bounds": { + "min": 0, + "max": 1 + }, + "baseline": 0.5, + "enabled": true, + "id": "dv_009", + "canvas_position": { + "x": 50, + "y": 680 + } } ], "extractors": [ @@ -228,11 +260,11 @@ "name": "extract_volume", "module": null, "signature": null, - "source_code": "def extract_volume(trial_dir, config, context):\n \"\"\"\n Extract volume from mass using material density.\n Volume = Mass / Density\n \n For Zerodur glass-ceramic: density ~ 2530 kg/m³\n \"\"\"\n import json\n from pathlib import Path\n \n # Get mass from the mass extractor results\n results_file = Path(trial_dir) / 'results.json'\n if results_file.exists():\n with open(results_file) as f:\n results = json.load(f)\n mass_kg = results.get('mass_kg', 0)\n else:\n # If no results yet, try to get from context\n mass_kg = context.get('mass_kg', 0)\n \n density = config.get('density_kg_m3', 2530.0) # Zerodur default\n \n # Volume in m³\n volume_m3 = mass_kg / density if density > 0 else 0\n \n # Also calculate in liters for convenience (1 m³ = 1000 L)\n volume_liters = volume_m3 * 1000000\n \n return {\n 'volume_m3': volume_m3,\n 'volume_liters': volume_liters\n }\n" + "source_code": "\"\"\"Extract modal mass matrix from Nastran OP2 file\"\"\"\n\nfrom pyNastran.op2.op2 import OP2\nimport numpy as np\n\ndef extract(op2_path: str, fem_path: str, params: dict, subcase_id: int = 1) -> dict:\n \"\"\"\n Extract modal mass matrix from Nastran modal analysis results.\n \n The modal mass matrix (generalized mass) is typically diagonal for\n mass-normalized modes, with each diagonal entry representing the\n effective mass participation for that mode.\n \n Returns:\n dict with keys:\n - modal_mass_1, modal_mass_2, modal_mass_3: First three modal masses\n - total_modal_mass: Sum of all modal masses\n - modal_mass_matrix: Full diagonal modal mass array (as list)\n \"\"\"\n op2 = OP2()\n op2.read_op2(op2_path)\n \n # Initialize outputs\n modal_mass_1 = 0.0\n modal_mass_2 = 0.0\n modal_mass_3 = 0.0\n total_modal_mass = 0.0\n modal_mass_matrix = []\n \n try:\n # Method 1: Check for modal participation factors / effective mass\n # This is stored in op2.modal_contribution if available\n if hasattr(op2, 'modal_contribution') and op2.modal_contribution:\n mc = op2.modal_contribution\n if subcase_id in mc:\n modal_data = mc[subcase_id]\n if hasattr(modal_data, 'effective_mass'):\n eff_mass = modal_data.effective_mass\n modal_mass_matrix = eff_mass.tolist() if hasattr(eff_mass, 'tolist') else list(eff_mass)\n \n # Method 2: Check eigenvalues for generalized mass\n # pyNastran stores generalized mass in eigenvalues object\n if not modal_mass_matrix and subcase_id in op2.eigenvalues:\n eig = op2.eigenvalues[subcase_id]\n \n # Generalized mass is typically stored as 'generalized_mass' attribute\n if hasattr(eig, 'generalized_mass'):\n gen_mass = eig.generalized_mass\n modal_mass_matrix = gen_mass.tolist() if hasattr(gen_mass, 'tolist') else list(gen_mass)\n \n # Alternative: mass-normalized modes have unit modal mass\n # Check the mode_cycle attribute for mass normalization info\n elif hasattr(eig, 'mass'):\n mass = eig.mass\n modal_mass_matrix = mass.tolist() if hasattr(mass, 'tolist') else list(mass)\n \n # Method 3: For mass-normalized eigenvectors, modal mass = 1.0\n # Check if eigenvectors exist and compute modal mass from them\n if not modal_mass_matrix and subcase_id in op2.eigenvectors:\n eigvec = op2.eigenvectors[subcase_id]\n n_modes = eigvec.data.shape[1] if len(eigvec.data.shape) > 1 else 1\n # For mass-normalized modes, modal mass is unity\n modal_mass_matrix = [1.0] * n_modes\n \n # Extract individual modal masses\n if modal_mass_matrix:\n if len(modal_mass_matrix) >= 1:\n modal_mass_1 = float(modal_mass_matrix[0])\n if len(modal_mass_matrix) >= 2:\n modal_mass_2 = float(modal_mass_matrix[1])\n if len(modal_mass_matrix) >= 3:\n modal_mass_3 = float(modal_mass_matrix[2])\n total_modal_mass = float(sum(modal_mass_matrix))\n \n except Exception as e:\n # Log error but return zeros gracefully\n print(f\"Warning: Could not extract modal mass matrix: {e}\")\n \n return {\n 'modal_mass_1': modal_mass_1,\n 'modal_mass_2': modal_mass_2,\n 'modal_mass_3': modal_mass_3,\n 'total_modal_mass': total_modal_mass,\n 'modal_mass_matrix': modal_mass_matrix,\n }" } }, { - "name": "a1768934465995fsfdadd", + "name": "extractor_1768938758443", "type": "custom_function", "builtin": false, "enabled": true, @@ -247,31 +279,31 @@ } ], "canvas_position": { - "x": 661.4703818070815, - "y": 655.713625352519 - }, - "id": "ext_004" - }, - { - "name": "extractor_1768934622682", - "type": "custom_function", - "builtin": false, - "enabled": true, - "function": { - "name": "extract", - "source_code": "def extract(op2_path: str, config: dict = None) -> dict:\n \"\"\"\n Custom extractor function.\n \n Args:\n op2_path: Path to the OP2 results file\n config: Optional configuration dict\n \n Returns:\n Dictionary with extracted values\n \"\"\"\n # TODO: Implement extraction logic\n return {'value': 0.0}\n" - }, - "outputs": [ - { - "name": "value", - "metric": "custom" - } - ], - "canvas_position": { - "x": 588.8370255010856, - "y": 516.8654070156841 + "x": 522.740988960073, + "y": 560.0208026883463 }, "id": "ext_005" + }, + { + "name": "extractor_1768938897219", + "type": "custom_function", + "builtin": false, + "enabled": true, + "function": { + "name": "extract", + "source_code": "def extract(op2_path: str, config: dict = None) -> dict:\n \"\"\"\n Custom extractor function.\n \n Args:\n op2_path: Path to the OP2 results file\n config: Optional configuration dict\n \n Returns:\n Dictionary with extracted values\n \"\"\"\n # TODO: Implement extraction logic\n return {'value': 0.0}\n" + }, + "outputs": [ + { + "name": "value", + "metric": "custom" + } + ], + "canvas_position": { + "x": -197.5451097726711, + "y": 262.2501934501369 + }, + "id": "ext_004" } ], "objectives": [ @@ -418,10 +450,6 @@ "source": "ext_001", "target": "obj_003" }, - { - "source": "ext_002", - "target": "con_001" - }, { "source": "obj_001", "target": "optimization" @@ -437,6 +465,10 @@ { "source": "con_001", "target": "optimization" + }, + { + "source": "ext_002", + "target": "con_001" } ], "layout_version": "2.0" diff --git a/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/introspect_model.py b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/introspect_model.py new file mode 100644 index 00000000..31bd6058 --- /dev/null +++ b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/introspect_model.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +"""Introspect M1_Blank.prt to get current expression values.""" +import sys +import json +sys.path.insert(0, 'c:/Users/antoi/Atomizer') + +from optimization_engine.extractors.introspect_part import introspect_part, get_expressions_dict + +MODEL_PATH = 'c:/Users/antoi/Atomizer/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/model/M1_Blank.prt' +OUTPUT_DIR = 'c:/Users/antoi/Atomizer/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/model' + +# Expressions we care about +LATERAL_VARS = [ + 'lateral_inner_u', + 'lateral_outer_u', + 'lateral_middle_pivot', + 'lateral_inner_angle', + 'lateral_outer_angle', + 'lateral_closeness', +] + +print("Running NX introspection on M1_Blank.prt (cost reduction model)...") +result = introspect_part(MODEL_PATH, OUTPUT_DIR, verbose=True) + +if result.get('success'): + exprs = get_expressions_dict(result) + + print() + print("=" * 60) + print("DESIGN VARIABLES (lateral) - current values in model:") + print("=" * 60) + for name in LATERAL_VARS: + if name in exprs: + print(f" {name}: {exprs[name]}") + else: + print(f" {name}: NOT FOUND") + + # Save all expressions to JSON for easy reference + output_json = 'c:/Users/antoi/Atomizer/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/1_setup/model_expressions.json' + with open(output_json, 'w') as f: + json.dump(exprs, f, indent=2) + print(f"\nAll expressions saved to: {output_json}") +else: + print(f"Introspection failed: {result.get('error', 'Unknown error')}") diff --git a/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/run_optimization.py b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/run_optimization.py new file mode 100644 index 00000000..c9e00f6a --- /dev/null +++ b/studies/M1_Mirror/m1_mirror_cost_reduction_lateral/run_optimization.py @@ -0,0 +1,695 @@ +#!/usr/bin/env python3 +""" +M1 Mirror Cost Reduction Lateral - Lateral Supports Optimization (CMA-ES) +========================================================================== + +Lateral support optimization for COST REDUCTION model with new U-joint expressions: +- lateral_inner_u (replaces lateral_inner_pivot) +- lateral_outer_u (replaces lateral_outer_pivot) +- lateral_middle_pivot (unchanged) +- lateral_inner_angle (unchanged) +- lateral_outer_angle (unchanged) + +Key Features: +1. CMA-ES sampler - ideal for 5D continuous optimization +2. ANNULAR APERTURE - excludes 271.5mm central hole from Zernike fitting +3. Uses ZernikeOPDExtractor.extract_relative() with inner_radius=135.75mm +4. Weighted sum: 6*wfe_40_20 + 5*wfe_60_20 + 3*mfg_90 (NO mass objective) +5. Hard constraint: mass <= 120 kg (still enforced) + +Usage: + python run_optimization.py --start + python run_optimization.py --start --trials 100 + python run_optimization.py --start --trials 100 --resume + python run_optimization.py --test # Single trial test + +Author: Atomizer +Created: 2026-01-13 +""" + +import sys +import os +import subprocess + +LICENSE_SERVER = "28000@dalidou;28000@100.80.199.40" +os.environ['SPLM_LICENSE_SERVER'] = LICENSE_SERVER +print(f"[LICENSE] SPLM_LICENSE_SERVER set to: {LICENSE_SERVER}") + +# Add Atomizer root to path (study is at studies/M1_Mirror/study_name/) +STUDY_DIR = os.path.dirname(os.path.abspath(__file__)) +PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(STUDY_DIR))) +sys.path.insert(0, PROJECT_ROOT) + +import json +import time +import argparse +import logging +import shutil +import re +from pathlib import Path +from typing import Dict, Optional, Any +from datetime import datetime + + +# ============================================================================ +# Dashboard Auto-Launch +# ============================================================================ + +def launch_dashboard(): + """Launch the Atomizer dashboard in background.""" + dashboard_dir = Path(PROJECT_ROOT) / "atomizer-dashboard" + start_script = dashboard_dir / "start-dashboard.bat" + + if not start_script.exists(): + print(f"[DASHBOARD] Warning: start-dashboard.bat not found at {start_script}") + return False + + try: + # Launch dashboard in background (detached process) + subprocess.Popen( + ["cmd", "/c", "start", "/min", str(start_script)], + cwd=str(dashboard_dir), + shell=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL + ) + print("[DASHBOARD] Launched in background") + print("[DASHBOARD] Frontend: http://localhost:5173") + print("[DASHBOARD] Backend: http://localhost:8000") + return True + except Exception as e: + print(f"[DASHBOARD] Failed to launch: {e}") + return False + +import optuna +from optuna.samplers import CmaEsSampler + +# Atomizer imports +from optimization_engine.nx.solver import NXSolver +from optimization_engine.extractors import ZernikeOPDExtractor # Supports annular apertures + +# ============================================================================ +# Paths +# ============================================================================ + +STUDY_DIR = Path(__file__).parent +SETUP_DIR = STUDY_DIR / "1_setup" +MODEL_DIR = SETUP_DIR / "model" +ITERATIONS_DIR = STUDY_DIR / "2_iterations" +RESULTS_DIR = STUDY_DIR / "3_results" +CONFIG_PATH = SETUP_DIR / "optimization_config.json" + +# Ensure directories exist +ITERATIONS_DIR.mkdir(exist_ok=True) +RESULTS_DIR.mkdir(exist_ok=True) + +# Logging +LOG_FILE = RESULTS_DIR / "optimization.log" +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s | %(levelname)-8s | %(message)s', + handlers=[ + logging.StreamHandler(sys.stdout), + logging.FileHandler(LOG_FILE, mode='a') + ] +) +logger = logging.getLogger(__name__) + + +# ============================================================================ +# Configuration +# ============================================================================ + +with open(CONFIG_PATH) as f: + CONFIG = json.load(f) + +STUDY_NAME = CONFIG["study_name"] + +# Objective weights from config (NO MASS!) +OBJ_WEIGHTS = { + 'wfe_40_20': 6.0, + 'wfe_60_20': 5.0, + 'mfg_90': 3.0 +} + +# Hard constraint: blank_mass <= 120kg (still enforced even though not optimizing) +MAX_BLANK_MASS_KG = 120.0 +CONSTRAINT_PENALTY = 1e6 + +# ANNULAR APERTURE: 271.5mm central hole diameter -> 135.75mm radius +INNER_RADIUS_MM = CONFIG.get('extraction_method', {}).get('inner_radius', 135.75) + + +def compute_weighted_sum(objectives: Dict[str, float]) -> float: + """Compute weighted sum of objectives (NO MASS!).""" + return (OBJ_WEIGHTS['wfe_40_20'] * objectives.get('wfe_40_20', 1000.0) + + OBJ_WEIGHTS['wfe_60_20'] * objectives.get('wfe_60_20', 1000.0) + + OBJ_WEIGHTS['mfg_90'] * objectives.get('mfg_90', 1000.0)) + + +def check_mass_constraint(mass_kg: float) -> tuple: + """Check if mass constraint is satisfied.""" + if mass_kg <= MAX_BLANK_MASS_KG: + return True, 0.0 + else: + return False, mass_kg - MAX_BLANK_MASS_KG + + +# ============================================================================ +# FEA Runner with Annular Zernike Extraction +# ============================================================================ + +class FEARunner: + """Runs FEA simulations with annular aperture Zernike extraction.""" + + def __init__(self, config: Dict[str, Any]): + self.config = config + self.nx_solver = None + self.master_model_dir = MODEL_DIR + + # Get fixed parameter values to apply on every run + self.fixed_params = {} + for fp in config.get('fixed_parameters', []): + self.fixed_params[fp['name']] = fp['value'] + + def setup(self): + """Setup NX solver (assumes NX is already running).""" + study_name = self.config.get('study_name', 'm1_mirror_flatback_lateral') + + nx_settings = self.config.get('nx_settings', {}) + nx_install_dir = nx_settings.get('nx_install_path', 'C:\\Program Files\\Siemens\\DesigncenterNX2512') + version_match = re.search(r'NX(\d+)|DesigncenterNX(\d+)', nx_install_dir) + nastran_version = (version_match.group(1) or version_match.group(2)) if version_match else "2512" + + self.nx_solver = NXSolver( + master_model_dir=str(self.master_model_dir), + nx_install_dir=nx_install_dir, + nastran_version=nastran_version, + timeout=nx_settings.get('simulation_timeout_s', 600), + use_iteration_folders=True, + study_name=study_name + ) + logger.info(f"[NX] Solver ready (Nastran {nastran_version})") + + def run_fea(self, params: Dict[str, float], trial_num: int) -> Optional[Dict]: + """Run FEA and extract objectives using ZernikeOPDExtractor with annular aperture.""" + if self.nx_solver is None: + self.setup() + + logger.info(f" [FEA {trial_num}] Running simulation...") + + # Build expressions: start with fixed params, then add optimization params + expressions = {} + + # Fixed parameters + for name, value in self.fixed_params.items(): + expressions[name] = value + + # Optimization variables (the ones we're actually varying) + for var in self.config['design_variables']: + if var.get('enabled', True) and var['name'] in params: + expressions[var['expression_name']] = params[var['name']] + + iter_folder = self.nx_solver.create_iteration_folder( + iterations_base_dir=ITERATIONS_DIR, + iteration_number=trial_num, + expression_updates=expressions + ) + + try: + nx_settings = self.config.get('nx_settings', {}) + sim_file = iter_folder / nx_settings.get('sim_file', 'ASSY_M1_assyfem1_sim1.sim') + + t_start = time.time() + + result = self.nx_solver.run_simulation( + sim_file=sim_file, + working_dir=iter_folder, + expression_updates=expressions, + solution_name=nx_settings.get('solution_name', 'Solution 1'), + cleanup=False + ) + + solve_time = time.time() - t_start + + if not result['success']: + logger.error(f" [FEA {trial_num}] Solve failed: {result.get('error')}") + return None + + logger.info(f" [FEA {trial_num}] Solved in {solve_time:.1f}s") + + # Extract objectives using ZernikeOPDExtractor with ANNULAR APERTURE + op2_path = Path(result['op2_file']) + objectives, lateral_diag = self._extract_objectives_annular(op2_path, iter_folder) + + if objectives is None: + return None + + # Check constraint + mass_kg = objectives['mass_kg'] + is_feasible, violation = check_mass_constraint(mass_kg) + + if is_feasible: + weighted_sum = compute_weighted_sum(objectives) + constraint_status = "OK" + else: + weighted_sum = compute_weighted_sum(objectives) + CONSTRAINT_PENALTY * violation + constraint_status = f"VIOLATED (+{violation:.1f}kg)" + + logger.info(f" [FEA {trial_num}] 40-20: {objectives['wfe_40_20']:.2f} nm (Annular OPD)") + logger.info(f" [FEA {trial_num}] 60-20: {objectives['wfe_60_20']:.2f} nm (Annular OPD)") + logger.info(f" [FEA {trial_num}] Mfg: {objectives['mfg_90']:.2f} nm (Annular OPD)") + logger.info(f" [FEA {trial_num}] Mass: {objectives['mass_kg']:.3f} kg [Constraint: {constraint_status}]") + logger.info(f" [FEA {trial_num}] Weighted Sum: {weighted_sum:.2f}") + + return { + 'trial_num': trial_num, + 'params': params, + 'objectives': objectives, + 'weighted_sum': weighted_sum, + 'is_feasible': is_feasible, + 'constraint_violation': violation, + 'source': 'FEA_ZernikeOPD_Annular', + 'solve_time': solve_time, + 'iter_folder': str(iter_folder), + 'lateral_diagnostics': lateral_diag + } + + except Exception as e: + logger.error(f" [FEA {trial_num}] Error: {e}") + import traceback + traceback.print_exc() + return None + + def _extract_objectives_annular(self, op2_path: Path, iter_folder: Path) -> tuple: + """ + Extract objectives using ZernikeOPDExtractor with ANNULAR APERTURE. + + The central hole (271.5mm diameter, inner_radius=135.75mm) is EXCLUDED + from Zernike fitting and RMS calculations. + """ + try: + zernike_settings = self.config.get('zernike_settings', {}) + + # Create ZernikeOPDExtractor with ANNULAR APERTURE + extractor = ZernikeOPDExtractor( + op2_path, + figure_path=None, # Uses BDF geometry + bdf_path=None, # Auto-detected + displacement_unit=zernike_settings.get('displacement_unit', 'mm'), + n_modes=zernike_settings.get('n_modes', 50), + filter_orders=zernike_settings.get('filter_low_orders', 4), + inner_radius=INNER_RADIUS_MM # ANNULAR APERTURE! + ) + + ref = zernike_settings.get('reference_subcase', '2') + + # Extract RELATIVE metrics with annular masking + rel_40 = extractor.extract_relative("3", ref) # 40 deg vs 20 deg + rel_60 = extractor.extract_relative("4", ref) # 60 deg vs 20 deg + rel_90 = extractor.extract_relative("1", ref) # 90 deg vs 20 deg (for MFG) + + # Log annular info + if 'obscuration_ratio' in rel_40: + logger.info(f" [Annular] Inner R={INNER_RADIUS_MM:.1f}mm, Obscuration={rel_40['obscuration_ratio']*100:.1f}%") + logger.info(f" [Annular] Using {rel_40.get('n_annular_nodes', '?')} nodes (excl. central hole)") + + # Extract mass from temp file + mass_kg = 0.0 + mass_file = iter_folder / "_temp_mass.txt" + if mass_file.exists(): + try: + with open(mass_file, 'r') as f: + mass_kg = float(f.read().strip()) + except Exception as mass_err: + logger.warning(f" Could not read mass file: {mass_err}") + + # Also check _temp_part_properties.json + if mass_kg == 0: + props_file = iter_folder / "_temp_part_properties.json" + if props_file.exists(): + try: + with open(props_file, 'r') as f: + props = json.load(f) + mass_kg = props.get('mass_kg', 0) + except Exception: + pass + + lateral_diag = { + 'max_um': rel_40.get('max_lateral_displacement_um', 0), + 'rms_um': rel_40.get('rms_lateral_displacement_um', 0), + } + + objectives = { + 'wfe_40_20': rel_40['relative_filtered_rms_nm'], + 'wfe_60_20': rel_60['relative_filtered_rms_nm'], + 'mfg_90': rel_90['relative_rms_filter_j1to3'], + 'mass_kg': mass_kg + } + + return objectives, lateral_diag + + except Exception as e: + logger.error(f"Annular Zernike extraction failed: {e}") + import traceback + traceback.print_exc() + return None, {} + + +# ============================================================================ +# CMA-ES Optimizer +# ============================================================================ + +class CMAESOptimizer: + """CMA-ES optimizer for lateral support parameters.""" + + def __init__(self, config: Dict[str, Any], resume: bool = False): + self.config = config + self.resume = resume + self.fea_runner = FEARunner(config) + + # Load design variable bounds (only enabled variables) + self.design_vars = { + v['name']: {'min': v['min'], 'max': v['max'], 'baseline': v.get('baseline')} + for v in config['design_variables'] + if v.get('enabled', True) + } + + # CMA-ES settings + opt_settings = config.get('optimization', {}) + self.sigma0 = opt_settings.get('sigma0', 0.3) + self.seed = opt_settings.get('seed', 42) + + # Study + self.study_name = config.get('study_name', 'm1_mirror_flatback_lateral') + self.db_path = RESULTS_DIR / "study.db" + + # Track best + self.best_weighted_sum = float('inf') + self.best_trial_info = None + + # Track FEA count + self._count_existing_iterations() + + def _count_existing_iterations(self): + """Count existing iteration folders.""" + self.fea_count = 0 + if ITERATIONS_DIR.exists(): + for d in ITERATIONS_DIR.iterdir(): + if d.is_dir() and d.name.startswith('iter'): + try: + num = int(d.name.replace('iter', '')) + self.fea_count = max(self.fea_count, num) + except ValueError: + pass + logger.info(f"Existing FEA iterations: {self.fea_count}") + + def create_study(self) -> optuna.Study: + """Create or load Optuna study with CMA-ES sampler.""" + # Get baseline values for x0 (starting point) + x0 = {} + for name, bounds in self.design_vars.items(): + x0[name] = bounds['baseline'] + + sampler = CmaEsSampler( + x0=x0, + sigma0=self.sigma0, + seed=self.seed, + restart_strategy='ipop' + ) + + storage = f"sqlite:///{self.db_path}" + + if self.resume: + try: + study = optuna.load_study( + study_name=self.study_name, + storage=storage, + sampler=sampler + ) + logger.info(f"Resumed study with {len(study.trials)} existing trials") + + # Find current best + completed = [t for t in study.trials if t.state == optuna.trial.TrialState.COMPLETE] + feasible = [t for t in completed if t.user_attrs.get('is_feasible', False)] + if feasible: + best = min(feasible, key=lambda t: t.value if t.value else float('inf')) + if best.value: + self.best_weighted_sum = best.value + logger.info(f"Current best (feasible): {self.best_weighted_sum:.2f}") + + return study + except KeyError: + logger.info("No existing study found, creating new one") + + study = optuna.create_study( + study_name=self.study_name, + storage=storage, + sampler=sampler, + direction="minimize", + load_if_exists=True + ) + + # Enqueue baseline as first trial + if len(study.trials) == 0: + logger.info("Enqueueing baseline as trial 0...") + study.enqueue_trial(x0) + + return study + + def objective(self, trial: optuna.Trial) -> float: + """CMA-ES objective function.""" + # Sample parameters + params = {} + for name, bounds in self.design_vars.items(): + params[name] = trial.suggest_float(name, bounds['min'], bounds['max']) + + # Increment FEA counter + self.fea_count += 1 + iter_num = self.fea_count + + logger.info(f"Trial {trial.number} -> iter{iter_num}") + for name, value in params.items(): + logger.info(f" {name} = {value:.3f}") + + # Run FEA + result = self.fea_runner.run_fea(params, iter_num) + + if result is None: + trial.set_user_attr('source', 'FEA_FAILED') + trial.set_user_attr('iter_num', iter_num) + trial.set_user_attr('is_feasible', False) + return 1e6 + + # Store metadata + trial.set_user_attr('source', 'FEA_ZernikeOPD_Annular') + trial.set_user_attr('iter_num', iter_num) + trial.set_user_attr('iter_folder', result['iter_folder']) + trial.set_user_attr('wfe_40_20', result['objectives']['wfe_40_20']) + trial.set_user_attr('wfe_60_20', result['objectives']['wfe_60_20']) + trial.set_user_attr('mfg_90', result['objectives']['mfg_90']) + trial.set_user_attr('mass_kg', result['objectives']['mass_kg']) + trial.set_user_attr('solve_time', result['solve_time']) + trial.set_user_attr('is_feasible', result['is_feasible']) + + weighted_sum = result['weighted_sum'] + + # Check if new best + if result['is_feasible'] and weighted_sum < self.best_weighted_sum: + logger.info(f" NEW BEST! {weighted_sum:.2f} (was {self.best_weighted_sum:.2f})") + self.best_weighted_sum = weighted_sum + self.best_trial_info = { + 'trial_number': trial.number, + 'iter_num': iter_num, + 'iter_folder': result['iter_folder'], + 'weighted_sum': weighted_sum, + 'objectives': result['objectives'], + 'params': params + } + self._archive_best_design() + + return weighted_sum + + def _archive_best_design(self): + """Archive current best design.""" + if self.best_trial_info is None: + return + + try: + archive_dir = RESULTS_DIR / "best_design_archive" + archive_dir.mkdir(exist_ok=True) + + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + dest_dir = archive_dir / timestamp + + src_dir = Path(self.best_trial_info['iter_folder']) + if src_dir.exists(): + shutil.copytree(src_dir, dest_dir) + + info = { + 'study_name': self.study_name, + 'trial_number': self.best_trial_info['trial_number'], + 'iteration_folder': f"iter{self.best_trial_info['iter_num']}", + 'weighted_sum': self.best_trial_info['weighted_sum'], + 'objectives': self.best_trial_info['objectives'], + 'params': self.best_trial_info['params'], + 'extraction_method': 'ZernikeOPD_Annular (271.5mm central hole excluded)', + 'inner_radius_mm': INNER_RADIUS_MM, + 'archived_at': datetime.now().isoformat() + } + with open(dest_dir / '_archive_info.json', 'w') as f: + json.dump(info, f, indent=2) + + logger.info(f" Archived to: {dest_dir.name}") + + except Exception as e: + logger.warning(f"Could not archive best design: {e}") + + def run(self, n_trials: int): + """Run CMA-ES optimization.""" + study = self.create_study() + + logger.info("=" * 70) + logger.info("M1 MIRROR FLAT BACK - LATERAL SUPPORTS OPTIMIZATION (CMA-ES)") + logger.info("=" * 70) + logger.info("*** ANNULAR APERTURE: 271.5mm central hole EXCLUDED ***") + logger.info(f"*** Inner radius: {INNER_RADIUS_MM} mm ***") + logger.info(f"Study: {self.study_name}") + logger.info("*** OBJECTIVES: WFE only (mass NOT in objective) ***") + logger.info(f"Total trials in DB: {len(study.trials)}") + logger.info(f"New FEA trials to run: {n_trials}") + logger.info(f"Active Design Variables: {len(self.design_vars)}") + for name, bounds in self.design_vars.items(): + baseline = bounds.get('baseline', 'N/A') + logger.info(f" - {name}: [{bounds['min']}, {bounds['max']}] (baseline: {baseline})") + logger.info(f"CONSTRAINT: blank_mass <= {MAX_BLANK_MASS_KG} kg") + logger.info(f"CMA-ES sigma0: {self.sigma0}") + logger.info("=" * 70) + + try: + study.optimize( + self.objective, + n_trials=n_trials, + show_progress_bar=True, + gc_after_trial=True + ) + except KeyboardInterrupt: + logger.info("Optimization interrupted by user") + + self._report_results(study) + return study + + def _report_results(self, study: optuna.Study): + """Report optimization results.""" + logger.info("\n" + "=" * 70) + logger.info("OPTIMIZATION RESULTS (Annular Aperture)") + logger.info("=" * 70) + + completed = [t for t in study.trials if t.state == optuna.trial.TrialState.COMPLETE and t.value < 1e5] + feasible = [t for t in completed if t.user_attrs.get('is_feasible', False)] + + logger.info(f"\nTotal completed: {len(completed)}") + logger.info(f"Feasible (mass <= {MAX_BLANK_MASS_KG}kg): {len(feasible)}") + + if not feasible: + logger.warning("No feasible trials found!") + return + + sorted_trials = sorted(feasible, key=lambda t: t.value) + + print(f"\n{'Trial':>6} | {'WS':>10} | {'40vs20':>10} | {'60vs20':>10} | {'MFG':>10} | {'Mass':>10} | Iter") + print("-" * 85) + + for t in sorted_trials[:15]: + obj_40 = t.user_attrs.get('wfe_40_20', 0) + obj_60 = t.user_attrs.get('wfe_60_20', 0) + obj_mfg = t.user_attrs.get('mfg_90', 0) + obj_mass = t.user_attrs.get('mass_kg', 0) + iter_num = t.user_attrs.get('iter_num', '?') + print(f"{t.number:>6} | {t.value:>10.2f} | {obj_40:>10.2f} | {obj_60:>10.2f} | {obj_mfg:>10.2f} | {obj_mass:>10.3f} | iter{iter_num}") + + best = sorted_trials[0] + logger.info(f"\nBEST FEASIBLE TRIAL: #{best.number}") + logger.info(f" Weighted Sum: {best.value:.2f}") + logger.info(f" 40-20: {best.user_attrs.get('wfe_40_20', 0):.2f} nm") + logger.info(f" 60-20: {best.user_attrs.get('wfe_60_20', 0):.2f} nm") + logger.info(f" MFG: {best.user_attrs.get('mfg_90', 0):.2f} nm") + logger.info(f" Mass: {best.user_attrs.get('mass_kg', 0):.3f} kg") + logger.info(f"\n Best Lateral Parameters:") + for k, v in best.params.items(): + logger.info(f" {k}: {v:.3f}") + + # Save summary + results_summary = { + 'study_name': self.study_name, + 'algorithm': 'CMA-ES', + 'extraction_method': 'ZernikeOPD_Annular', + 'inner_radius_mm': INNER_RADIUS_MM, + 'objectives_note': 'Mass NOT in objective - WFE only', + 'total_trials': len(study.trials), + 'feasible_trials': len(feasible), + 'best_trial': { + 'number': best.number, + 'weighted_sum': best.value, + 'objectives': { + 'wfe_40_20': best.user_attrs.get('wfe_40_20'), + 'wfe_60_20': best.user_attrs.get('wfe_60_20'), + 'mfg_90': best.user_attrs.get('mfg_90'), + 'mass_kg': best.user_attrs.get('mass_kg') + }, + 'params': dict(best.params), + 'iter_folder': best.user_attrs.get('iter_folder') + }, + 'timestamp': datetime.now().isoformat() + } + + with open(RESULTS_DIR / 'optimization_summary.json', 'w') as f: + json.dump(results_summary, f, indent=2) + + logger.info(f"\nResults saved to: {RESULTS_DIR / 'optimization_summary.json'}") + + +# ============================================================================ +# Main +# ============================================================================ + +def main(): + parser = argparse.ArgumentParser(description="M1 Mirror Cost Reduction Lateral - Lateral Supports Optimization") + parser.add_argument("--start", action="store_true", help="Start optimization") + parser.add_argument("--trials", type=int, default=100, help="Number of FEA trials") + parser.add_argument("--resume", action="store_true", help="Resume interrupted run") + parser.add_argument("--test", action="store_true", help="Run single test trial") + parser.add_argument("--no-dashboard", action="store_true", help="Don't auto-launch dashboard") + + args = parser.parse_args() + + if not args.start and not args.test: + parser.print_help() + print("\nUse --start to begin optimization or --test for single trial") + print("\n*** Optimizing LATERAL SUPPORT parameters only ***") + print("*** Design Variables: lateral_inner_u, lateral_outer_u, lateral_middle_pivot, ***") + print("*** lateral_inner_angle, lateral_outer_angle ***") + print("*** Objectives: WFE only (mass NOT included) ***") + print("*** Using ANNULAR APERTURE - central hole excluded from Zernike fitting ***") + return + + if not CONFIG_PATH.exists(): + print(f"Error: Config not found at {CONFIG_PATH}") + sys.exit(1) + + # Auto-launch dashboard (unless disabled) + if not args.no_dashboard: + launch_dashboard() + time.sleep(2) # Give dashboard time to start + + with open(CONFIG_PATH) as f: + config = json.load(f) + + optimizer = CMAESOptimizer(config, resume=args.resume) + + n_trials = 1 if args.test else args.trials + optimizer.run(n_trials=n_trials) + + +if __name__ == "__main__": + main() diff --git a/studies/M1_Mirror/m1_mirror_flatback_lateral/3_results/optimization_summary.json b/studies/M1_Mirror/m1_mirror_flatback_lateral/3_results/optimization_summary.json index e6408b02..8d733577 100644 --- a/studies/M1_Mirror/m1_mirror_flatback_lateral/3_results/optimization_summary.json +++ b/studies/M1_Mirror/m1_mirror_flatback_lateral/3_results/optimization_summary.json @@ -4,25 +4,25 @@ "extraction_method": "ZernikeOPD_Annular", "inner_radius_mm": 135.75, "objectives_note": "Mass NOT in objective - WFE only", - "total_trials": 1, - "feasible_trials": 1, + "total_trials": 101, + "feasible_trials": 100, "best_trial": { - "number": 0, - "weighted_sum": 341.40717511411987, + "number": 76, + "weighted_sum": 220.12317796085603, "objectives": { - "wfe_40_20": 9.738648075724171, - "wfe_60_20": 24.138392317227122, - "mfg_90": 54.09444169121308, - "mass_kg": 102.89579477048632 + "wfe_40_20": 7.033921022459454, + "wfe_60_20": 16.109562572565014, + "mfg_90": 32.457279654424745, + "mass_kg": 102.89579477048622 }, "params": { - "lateral_inner_u": 0.4, - "lateral_outer_u": 0.4, - "lateral_middle_pivot": 22.42, - "lateral_inner_angle": 31.96, - "lateral_outer_angle": 9.08 + "lateral_inner_u": 0.40304412850085514, + "lateral_outer_u": 0.9043062289622721, + "lateral_middle_pivot": 25.869245488671304, + "lateral_inner_angle": 32.008659765295675, + "lateral_outer_angle": 13.952742709877848 }, - "iter_folder": "C:\\Users\\antoi\\Atomizer\\studies\\M1_Mirror\\m1_mirror_flatback_lateral\\2_iterations\\iter1" + "iter_folder": "C:\\Users\\antoi\\Atomizer\\studies\\M1_Mirror\\m1_mirror_flatback_lateral\\2_iterations\\iter77" }, - "timestamp": "2026-01-13T11:01:22.360549" + "timestamp": "2026-01-13T18:41:14.992549" } \ No newline at end of file diff --git a/studies/M1_Mirror/m1_mirror_flatback_lateral/3_results/study.db b/studies/M1_Mirror/m1_mirror_flatback_lateral/3_results/study.db index 78a5c6c6..3ccd97dd 100644 Binary files a/studies/M1_Mirror/m1_mirror_flatback_lateral/3_results/study.db and b/studies/M1_Mirror/m1_mirror_flatback_lateral/3_results/study.db differ diff --git a/studies/M1_Mirror/m1_mirror_flatback_lateral/atomizer_spec.json b/studies/M1_Mirror/m1_mirror_flatback_lateral/atomizer_spec.json index 28ce5b26..baabd96c 100644 --- a/studies/M1_Mirror/m1_mirror_flatback_lateral/atomizer_spec.json +++ b/studies/M1_Mirror/m1_mirror_flatback_lateral/atomizer_spec.json @@ -2,9 +2,9 @@ "meta": { "version": "2.0", "created": "2026-01-17T15:35:12.034330Z", - "modified": "2026-01-17T15:35:12.034330Z", + "modified": "2026-01-20T18:24:29.805432Z", "created_by": "migration", - "modified_by": "migration", + "modified_by": "canvas", "study_name": "m1_mirror_flatback_lateral", "description": "Lateral support optimization with new U-joint expressions (lateral_inner_u, lateral_outer_u). Focus on WFE and MFG only - no mass objective.", "tags": [ @@ -104,10 +104,10 @@ "expression_name": "lateral_outer_angle", "type": "continuous", "bounds": { - "min": 8.0, - "max": 17.0 + "min": 8, + "max": 17 }, - "baseline": 9.08, + "baseline": 10, "units": "degrees", "enabled": true, "description": "Outer lateral support angle", diff --git a/studies/new_study/1_model/Beam_sim1.sim b/studies/new_study/1_model/Beam_sim1.sim new file mode 100644 index 00000000..764b7269 Binary files /dev/null and b/studies/new_study/1_model/Beam_sim1.sim differ diff --git a/studies/new_study/1_model/Bracket_sim1.sim b/studies/new_study/1_model/Bracket_sim1.sim new file mode 100644 index 00000000..3df374b7 Binary files /dev/null and b/studies/new_study/1_model/Bracket_sim1.sim differ