Major changes: - Dashboard: WebSocket-based chat with session management - Dashboard: New chat components (ChatPane, ChatInput, ModeToggle) - Dashboard: Enhanced UI with parallel coordinates chart - MCP Server: New atomizer-tools server for Claude integration - Extractors: Enhanced Zernike OPD extractor - Reports: Improved report generator New studies (configs and scripts only): - M1 Mirror: Cost reduction campaign studies - Simple Beam, Simple Bracket, UAV Arm studies Note: Large iteration data (2_iterations/, best_design_archive/) excluded via .gitignore - kept on local Gitea only. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
408 lines
12 KiB
TypeScript
408 lines
12 KiB
TypeScript
/**
|
|
* 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<string, string> = {
|
|
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<string, { method: string; reason: string }> = {
|
|
"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,
|
|
};
|
|
}
|
|
},
|
|
},
|
|
];
|