/** * Physics Tools * * Tools for FEA explanations and method recommendations. */ import { readdir, readFile } from "fs/promises"; import { resolve } from "path"; import { AtomizerTool } from "../index.js"; import { ATOMIZER_ROOT, OPTIMIZATION_ENGINE_DIR } from "../utils/paths.js"; // Physics concepts knowledge base const PHYSICS_CONCEPTS: Record = { displacement: ` **Displacement** is the change in position of a point in a structure under load. In FEA, nodal displacements are the primary unknowns solved for. Key metrics: - Maximum displacement: Largest movement in any direction - RMS displacement: Root-mean-square of all nodal displacements - Directional displacement: Movement in a specific axis (X, Y, or Z) Units: millimeters (mm) or inches (in) Atomizer extractors: extract_displacement, extract_max_displacement `, stress: ` **Stress** is the internal force per unit area within a material. In FEA, stress is computed from strain (displacement gradients). Types: - Von Mises stress: Combined stress for ductile materials (most common) - Principal stresses: Maximum/minimum normal stresses - Shear stress: Stress parallel to a surface Key considerations: - Compare to material yield strength for safety factor - Stress concentrations occur at corners, holes, and thickness changes Units: MPa (N/mm²) or psi Atomizer extractors: extract_solid_stress, extract_von_mises_stress `, frequency: ` **Natural Frequency** is the rate at which a structure vibrates when disturbed. Modal analysis finds these frequencies and their corresponding mode shapes. Key concepts: - Fundamental frequency: Lowest natural frequency (Mode 1) - Mode shape: Pattern of vibration at each frequency - Resonance: When excitation frequency matches natural frequency (dangerous!) Design goals: - Keep natural frequencies above or below operating frequencies - Separate modes to avoid coupling Units: Hz (cycles/second) Atomizer extractors: extract_frequency, extract_modal_participation `, mass: ` **Mass** is the total material quantity in a structure. In optimization, mass is often a key objective to minimize. Calculation methods: - From CAD: NX expression-based calculation (most accurate) - From FEM: Sum of element masses (approximation) - From BDF: Calculated from element properties Related metrics: - Center of gravity (CG): Balance point - Moments of inertia: Rotational mass distribution Units: kg or lbm Atomizer extractors: extract_mass_from_expression, extract_mass_from_bdf `, zernike: ` **Zernike Polynomials** are mathematical functions for describing optical aberrations. Used to characterize mirror surface deformation in precision optics. Key coefficients: - Z4 (Defocus): Curvature change - Z5, Z6 (Astigmatism): Cylindrical deformation - Z7, Z8 (Coma): Comet-like aberration - Z11 (Spherical): Radial curvature variation Fitting method: 1. Extract nodal displacements from FEA 2. Fit to Zernike polynomial basis 3. Report RMS WFE (wavefront error) Units: nanometers (nm) or waves Atomizer extractors: extract_zernike_figure, extract_zernike_annular `, convergence: ` **Optimization Convergence** indicates when the search has found a good solution. Signs of convergence: - Best value stops improving for many trials - Low variance in recent trial results - Improvement rate drops below threshold When to stop: - Converged: Best hasn't improved in 20+ trials - Plateau: Improvement rate < 1% per 10 trials - Budget exhausted: Reached max_trials Tips: - Don't stop too early - TPE needs ~50 trials to learn - Consider switching to local optimizer after convergence - Check multiple random seeds for robustness `, }; // Method recommendation logic const METHOD_RECOMMENDATIONS: Record = { "1_obj_few_dv": { method: "TPE", reason: "Tree-structured Parzen Estimator works well for single-objective with few design variables. It balances exploration and exploitation efficiently.", }, "1_obj_many_dv": { method: "CMA-ES", reason: "Covariance Matrix Adaptation is effective for high-dimensional single-objective problems. It learns correlations between variables.", }, "multi_obj": { method: "NSGA-II", reason: "Non-dominated Sorting Genetic Algorithm II finds Pareto-optimal solutions for multi-objective problems. Good for exploring trade-offs.", }, "noisy": { method: "RandomSearch + averaging", reason: "For noisy objectives, random search with trial averaging is robust. Consider multiple FEA runs per trial.", }, "expensive": { method: "TPE + surrogate", reason: "For expensive FEA (>5 min/eval), use TPE with neural network surrogate for pre-screening. See SYS_14 Neural Acceleration.", }, }; export const physicsTools: AtomizerTool[] = [ { definition: { name: "explain_physics", description: "Explain FEA physics concepts relevant to optimization. Use this to help users understand displacement, stress, frequency, mass, or other physics quantities.", inputSchema: { type: "object" as const, properties: { concept: { type: "string", enum: [ "displacement", "stress", "frequency", "mass", "zernike", "convergence", ], description: "The physics concept to explain", }, context: { type: "string", description: "Optional context about the user's situation for tailored explanation", }, }, required: ["concept"], }, }, handler: async (args) => { const concept = args.concept as string; const context = args.context as string | undefined; const explanation = PHYSICS_CONCEPTS[concept]; if (!explanation) { return { content: [ { type: "text", text: `Unknown concept: ${concept}. Available concepts: ${Object.keys(PHYSICS_CONCEPTS).join(", ")}`, }, ], isError: true, }; } let response = explanation.trim(); if (context) { response += `\n\n---\n\n**For your context** (${context}):\n`; response += "Consider how this concept applies to your specific optimization goals."; } return { content: [ { type: "text", text: response, }, ], }; }, }, { definition: { name: "recommend_method", description: "Recommend an optimization method based on problem characteristics. Considers number of objectives, design variables, noise level, and evaluation cost.", inputSchema: { type: "object" as const, properties: { num_objectives: { type: "number", description: "Number of objectives (1 for single-objective)", }, num_design_variables: { type: "number", description: "Number of design variables", }, evaluation_time_minutes: { type: "number", description: "Approximate time for one FEA evaluation in minutes", }, is_noisy: { type: "boolean", description: "Whether the objective has noise/variability", }, budget_trials: { type: "number", description: "Maximum number of trials you can afford", }, }, required: ["num_objectives", "num_design_variables"], }, }, handler: async (args) => { const numObjectives = args.num_objectives as number; const numDV = args.num_design_variables as number; const evalTime = (args.evaluation_time_minutes as number) || 1; const isNoisy = args.is_noisy as boolean; const budget = (args.budget_trials as number) || 100; let key: string; if (isNoisy) { key = "noisy"; } else if (numObjectives > 1) { key = "multi_obj"; } else if (evalTime > 5) { key = "expensive"; } else if (numDV > 10) { key = "1_obj_many_dv"; } else { key = "1_obj_few_dv"; } const rec = METHOD_RECOMMENDATIONS[key]; const response = { recommended_method: rec.method, reason: rec.reason, problem_characteristics: { objectives: numObjectives, design_variables: numDV, evaluation_time_minutes: evalTime, is_noisy: isNoisy, budget: budget, }, estimated_trials_needed: numObjectives === 1 ? Math.min(budget, 50 + numDV * 5) : Math.min(budget, 100 + numDV * 10), tips: [ `With ${numDV} design variables, expect ~${Math.min(budget, 30 + numDV * 3)} trials before meaningful convergence.`, evalTime > 5 ? "Consider using neural surrogate acceleration (see SYS_14)." : "Standard optimization should work well.", budget < 50 ? "Low budget - consider random search for baseline first." : "", ].filter(Boolean), }; return { content: [ { type: "text", text: JSON.stringify(response, null, 2), }, ], }; }, }, { definition: { name: "query_extractors", description: "List available physics extractors in Atomizer for extracting results from FEA solutions.", inputSchema: { type: "object" as const, properties: { physics_type: { type: "string", enum: [ "displacement", "stress", "frequency", "mass", "zernike", "all", ], description: "Filter by physics type, or 'all' for complete list", }, }, }, }, handler: async (args) => { const physicsType = (args.physics_type as string) || "all"; const extractorsDir = resolve(OPTIMIZATION_ENGINE_DIR, "extractors"); try { const files = await readdir(extractorsDir); const extractorFiles = files.filter( (f) => f.startsWith("extract_") && f.endsWith(".py") ); const extractors: { name: string; physics: string; file: string }[] = []; for (const file of extractorFiles) { const name = file.replace("extract_", "").replace(".py", ""); // Determine physics type from name let physics: string; if (name.includes("displacement") || name.includes("deform")) { physics = "displacement"; } else if (name.includes("stress") || name.includes("mises")) { physics = "stress"; } else if (name.includes("freq") || name.includes("modal")) { physics = "frequency"; } else if (name.includes("mass") || name.includes("weight")) { physics = "mass"; } else if (name.includes("zernike") || name.includes("wfe")) { physics = "zernike"; } else { physics = "other"; } if (physicsType === "all" || physics === physicsType) { extractors.push({ name, physics, file }); } } // Read __init__.py for exported functions const initPath = resolve(extractorsDir, "__init__.py"); let exports: string[] = []; try { const initContent = await readFile(initPath, "utf-8"); const match = initContent.match(/__all__\s*=\s*\[([\s\S]*?)\]/); if (match) { exports = match[1] .split(",") .map((s) => s.trim().replace(/['"]/g, "")) .filter(Boolean); } } catch { // Ignore if __init__.py not readable } return { content: [ { type: "text", text: JSON.stringify( { filter: physicsType, count: extractors.length, extractors, exported_functions: exports, usage_example: "from optimization_engine.extractors import extract_displacement", }, null, 2 ), }, ], }; } catch (error) { const message = error instanceof Error ? error.message : String(error); return { content: [ { type: "text", text: `Error listing extractors: ${message}`, }, ], isError: true, }; } }, }, ];