diff --git a/docs/07_DEVELOPMENT/Phase_1_2_Implementation_Plan.md b/docs/07_DEVELOPMENT/Phase_1_2_Implementation_Plan.md new file mode 100644 index 00000000..831a803e --- /dev/null +++ b/docs/07_DEVELOPMENT/Phase_1_2_Implementation_Plan.md @@ -0,0 +1,334 @@ +# Phase 1.2: Configuration Management Overhaul - Implementation Plan + +**Status**: In Progress +**Started**: January 2025 +**Target Completion**: 2 days + +--- + +## ✅ Completed (January 24, 2025) + +### 1. Configuration Inventory +- Found 4 `optimization_config.json` files +- Found 5 `workflow_config.json` files +- Analyzed bracket_V3 (old format) vs drone_gimbal (new format) + +### 2. Schema Analysis +Documented critical inconsistencies: +- Objectives: `"goal"` (new) vs `"type"` (old) +- Design vars: `"parameter"` + `"bounds": [min, max]` (new) vs `"name"` + `"min"/"max"` (old) +- Constraints: `"threshold"` (new) vs `"value"` (old) +- Location: `1_setup/` (correct) vs root directory (incorrect) + +### 3. JSON Schema Design +Created [`optimization_engine/schemas/optimization_config_schema.json`](../../optimization_engine/schemas/optimization_config_schema.json): +- Based on drone_gimbal format (cleaner, matches create-study skill) +- Validates all required fields +- Supports Protocol 10 (single-objective) and Protocol 11 (multi-objective) +- Includes extraction spec validation + +--- + +## 🔨 Remaining Implementation Tasks + +### Task 1: Implement ConfigManager Class +**Priority**: HIGH +**File**: `optimization_engine/config_manager.py` + +```python +"""Configuration validation and management for Atomizer studies.""" + +import json +from pathlib import Path +from typing import Dict, List, Any, Optional +import jsonschema + +class ConfigValidationError(Exception): + """Raised when configuration validation fails.""" + pass + +class ConfigManager: + """Manages and validates optimization configuration files.""" + + def __init__(self, config_path: Path): + """ + Initialize ConfigManager with path to optimization_config.json. + + Args: + config_path: Path to optimization_config.json file + """ + self.config_path = Path(config_path) + self.schema_path = Path(__file__).parent / "schemas" / "optimization_config_schema.json" + self.config: Optional[Dict[str, Any]] = None + self.validation_errors: List[str] = [] + + def load_schema(self) -> Dict[str, Any]: + """Load JSON schema for validation.""" + with open(self.schema_path, 'r') as f: + return json.load(f) + + def load_config(self) -> Dict[str, Any]: + """Load configuration file.""" + if not self.config_path.exists(): + raise FileNotFoundError(f"Config file not found: {self.config_path}") + + with open(self.config_path, 'r') as f: + self.config = json.load(f) + return self.config + + def validate(self) -> bool: + """ + Validate configuration against schema. + + Returns: + True if valid, False otherwise + """ + if self.config is None: + self.load_config() + + schema = self.load_schema() + self.validation_errors = [] + + try: + jsonschema.validate(instance=self.config, schema=schema) + # Additional custom validations + self._validate_design_variable_bounds() + self._validate_multi_objective_consistency() + self._validate_file_locations() + return True + except jsonschema.ValidationError as e: + self.validation_errors.append(str(e)) + return False + + def _validate_design_variable_bounds(self): + """Ensure bounds are valid (min < max).""" + for dv in self.config.get("design_variables", []): + bounds = dv.get("bounds", []) + if len(bounds) == 2 and bounds[0] >= bounds[1]: + self.validation_errors.append( + f"Design variable '{dv['parameter']}': min ({bounds[0]}) must be < max ({bounds[1]})" + ) + + def _validate_multi_objective_consistency(self): + """Validate multi-objective settings consistency.""" + n_objectives = len(self.config.get("objectives", [])) + protocol = self.config.get("optimization_settings", {}).get("protocol") + sampler = self.config.get("optimization_settings", {}).get("sampler") + + if n_objectives > 1: + # Multi-objective must use protocol_11 and NSGA-II + if protocol != "protocol_11_multi_objective": + self.validation_errors.append( + f"Multi-objective optimization ({n_objectives} objectives) requires protocol_11_multi_objective" + ) + if sampler != "NSGAIISampler": + self.validation_errors.append( + f"Multi-objective optimization requires NSGAIISampler (got {sampler})" + ) + + def _validate_file_locations(self): + """Check if config is in correct location (1_setup/).""" + if "1_setup" not in str(self.config_path.parent): + self.validation_errors.append( + f"Config should be in '1_setup/' directory, found in {self.config_path.parent}" + ) + + def get_validation_report(self) -> str: + """Get human-readable validation report.""" + if not self.validation_errors: + return "✓ Configuration is valid" + + report = "✗ Configuration validation failed:\n" + for i, error in enumerate(self.validation_errors, 1): + report += f" {i}. {error}\n" + return report + + # Type-safe accessor methods + + def get_design_variables(self) -> List[Dict[str, Any]]: + """Get design variables with validated structure.""" + if self.config is None: + self.load_config() + return self.config.get("design_variables", []) + + def get_objectives(self) -> List[Dict[str, Any]]: + """Get objectives with validated structure.""" + if self.config is None: + self.load_config() + return self.config.get("objectives", []) + + def get_constraints(self) -> List[Dict[str, Any]]: + """Get constraints with validated structure.""" + if self.config is None: + self.load_config() + return self.config.get("constraints", []) + + def get_simulation_settings(self) -> Dict[str, Any]: + """Get simulation settings.""" + if self.config is None: + self.load_config() + return self.config.get("simulation", {}) + + +# CLI tool for validation +if __name__ == "__main__": + import sys + + if len(sys.argv) < 2: + print("Usage: python config_manager.py ") + sys.exit(1) + + config_path = Path(sys.argv[1]) + manager = ConfigManager(config_path) + + try: + manager.load_config() + is_valid = manager.validate() + print(manager.get_validation_report()) + sys.exit(0 if is_valid else 1) + except Exception as e: + print(f"Error: {e}") + sys.exit(1) +``` + +**Dependencies**: Add to requirements.txt: +``` +jsonschema>=4.17.0 +``` + +--- + +### Task 2: Create Configuration Migration Tool +**Priority**: MEDIUM +**File**: `optimization_engine/config_migrator.py` + +Tool to automatically migrate old-format configs to new format: +- Convert `"type"` → `"goal"` in objectives +- Convert `"min"/"max"` → `"bounds": [min, max]` in design variables +- Convert `"name"` → `"parameter"` in design variables +- Convert `"value"` → `"threshold"` in constraints +- Move config files to `1_setup/` if in wrong location + +--- + +### Task 3: Integration with run_optimization.py +**Priority**: HIGH + +Add validation to optimization runners: +```python +# At start of run_optimization.py +from optimization_engine.config_manager import ConfigManager + +# Load and validate config +config_manager = ConfigManager(Path(__file__).parent / "1_setup" / "optimization_config.json") +config_manager.load_config() + +if not config_manager.validate(): + print(config_manager.get_validation_report()) + sys.exit(1) + +print("✓ Configuration validated successfully") +``` + +--- + +### Task 4: Update create-study Claude Skill +**Priority**: HIGH +**File**: `.claude/skills/create-study.md` + +Update skill to reference the JSON schema: +- Add link to schema documentation +- Emphasize validation after generation +- Include validation command in "Next Steps" + +--- + +### Task 5: Create Configuration Documentation +**Priority**: HIGH +**File**: `docs/CONFIGURATION_GUIDE.md` + +Comprehensive documentation covering: +1. Standard configuration format (with drone_gimbal example) +2. Field-by-field descriptions +3. Validation rules and how to run validation +4. Common validation errors and fixes +5. Migration guide for old configs +6. Protocol selection (10 vs 11) +7. Extractor mapping table + +--- + +### Task 6: Validate All Existing Studies +**Priority**: MEDIUM + +Run validation on all existing studies: +```bash +# Test validation tool +python optimization_engine/config_manager.py studies/drone_gimbal_arm_optimization/1_setup/optimization_config.json +python optimization_engine/config_manager.py studies/bracket_stiffness_optimization_V3/optimization_config.json + +# Expected: drone passes, bracket_V3 fails with specific errors +``` + +Create migration plan for failing configs. + +--- + +### Task 7: Migrate Legacy Configs +**Priority**: LOW (can defer to Phase 1.3) + +Migrate all legacy configs to new format: +- bracket_stiffness_optimization_V3 +- bracket_stiffness_optimization_V2 +- bracket_stiffness_optimization + +Keep old versions in `archive/` for reference. + +--- + +## Success Criteria + +Phase 1.2 is complete when: +- [x] JSON schema created and comprehensive +- [ ] ConfigManager class implemented with all validation methods +- [ ] Validation integrated into at least 1 study (drone_gimbal) +- [ ] Configuration documentation written +- [ ] create-study skill updated with schema reference +- [ ] Migration tool created (basic version) +- [ ] All tests pass on drone_gimbal study +- [ ] Phase 1.2 changes committed with clear message + +--- + +## Testing Plan + +1. **Schema Validation Test**: + - Valid config passes ✓ + - Invalid configs fail with clear errors ✓ + +2. **ConfigManager Test**: + - Load valid config + - Validate and get clean report + - Load invalid config + - Validate and get error details + +3. **Integration Test**: + - Run drone_gimbal study with validation enabled + - Verify no performance impact + - Check validation messages appear correctly + +4. **Migration Test**: + - Migrate bracket_V3 config + - Validate migrated config + - Compare before/after + +--- + +## Next Phase Preview + +**Phase 1.3: Error Handling & Logging** will build on this by: +- Adding structured logging with configuration context +- Error recovery using validated configurations +- Checkpoint system that validates config before saving + +The clean configuration management from Phase 1.2 enables reliable error handling in Phase 1.3. diff --git a/optimization_engine/config_manager.py b/optimization_engine/config_manager.py new file mode 100644 index 00000000..4ca0660f --- /dev/null +++ b/optimization_engine/config_manager.py @@ -0,0 +1,244 @@ +"""Configuration validation and management for Atomizer studies. + +This module provides schema-based validation for optimization configuration files, +ensuring consistency across all studies. + +Usage: + # In run_optimization.py + from optimization_engine.config_manager import ConfigManager + + config_manager = ConfigManager(Path(__file__).parent / "1_setup" / "optimization_config.json") + config_manager.load_config() + + if not config_manager.validate(): + print(config_manager.get_validation_report()) + sys.exit(1) + + # Access validated configuration + design_vars = config_manager.get_design_variables() + objectives = config_manager.get_objectives() +""" + +import json +from pathlib import Path +from typing import Dict, List, Any, Optional + +try: + import jsonschema + JSONSCHEMA_AVAILABLE = True +except ImportError: + JSONSCHEMA_AVAILABLE = False + print("Warning: jsonschema not installed. Install with: pip install jsonschema>=4.17.0") + + +class ConfigValidationError(Exception): + """Raised when configuration validation fails.""" + pass + + +class ConfigManager: + """Manages and validates optimization configuration files.""" + + def __init__(self, config_path: Path): + """ + Initialize ConfigManager with path to optimization_config.json. + + Args: + config_path: Path to optimization_config.json file + """ + self.config_path = Path(config_path) + self.schema_path = Path(__file__).parent / "schemas" / "optimization_config_schema.json" + self.config: Optional[Dict[str, Any]] = None + self.validation_errors: List[str] = [] + + def load_schema(self) -> Dict[str, Any]: + """Load JSON schema for validation.""" + if not self.schema_path.exists(): + raise FileNotFoundError(f"Schema file not found: {self.schema_path}") + + with open(self.schema_path, 'r') as f: + return json.load(f) + + def load_config(self) -> Dict[str, Any]: + """Load configuration file.""" + if not self.config_path.exists(): + raise FileNotFoundError(f"Config file not found: {self.config_path}") + + with open(self.config_path, 'r') as f: + self.config = json.load(f) + return self.config + + def validate(self, strict: bool = True) -> bool: + """ + Validate configuration against schema. + + Args: + strict: If True, enforce all validations. If False, only warn on non-critical issues. + + Returns: + True if valid, False otherwise + """ + if self.config is None: + self.load_config() + + self.validation_errors = [] + + # JSON Schema validation + if JSONSCHEMA_AVAILABLE: + schema = self.load_schema() + try: + jsonschema.validate(instance=self.config, schema=schema) + except jsonschema.ValidationError as e: + self.validation_errors.append(f"Schema validation failed: {e.message}") + if strict: + return False + else: + self.validation_errors.append("jsonschema not installed - schema validation skipped") + + # Custom validations + self._validate_design_variable_bounds() + self._validate_multi_objective_consistency() + self._validate_file_locations() + self._validate_extraction_consistency() + + return len(self.validation_errors) == 0 + + def _validate_design_variable_bounds(self): + """Ensure bounds are valid (min < max).""" + for dv in self.config.get("design_variables", []): + bounds = dv.get("bounds", []) + if len(bounds) == 2 and bounds[0] >= bounds[1]: + self.validation_errors.append( + f"Design variable '{dv.get('parameter', 'unknown')}': " + f"min ({bounds[0]}) must be < max ({bounds[1]})" + ) + + def _validate_multi_objective_consistency(self): + """Validate multi-objective settings consistency.""" + n_objectives = len(self.config.get("objectives", [])) + protocol = self.config.get("optimization_settings", {}).get("protocol") + sampler = self.config.get("optimization_settings", {}).get("sampler") + + if n_objectives > 1: + # Multi-objective should use protocol_11 and NSGA-II + if protocol and protocol != "protocol_11_multi_objective": + self.validation_errors.append( + f"Multi-objective optimization ({n_objectives} objectives) " + f"should use protocol_11_multi_objective (got {protocol})" + ) + if sampler and sampler != "NSGAIISampler": + self.validation_errors.append( + f"Multi-objective optimization should use NSGAIISampler (got {sampler})" + ) + elif n_objectives == 1: + # Single-objective should not use NSGA-II + if sampler == "NSGAIISampler": + self.validation_errors.append( + "Single-objective optimization should not use NSGAIISampler " + "(use TPESampler or CmaEsSampler)" + ) + + def _validate_file_locations(self): + """Check if config is in correct location (1_setup/).""" + if "1_setup" not in str(self.config_path.parent): + self.validation_errors.append( + f"Warning: Config should be in '1_setup/' directory, " + f"found in {self.config_path.parent}" + ) + + def _validate_extraction_consistency(self): + """Validate extraction specifications.""" + # Check objectives have extraction specs + for obj in self.config.get("objectives", []): + if "extraction" not in obj: + self.validation_errors.append( + f"Objective '{obj.get('name', 'unknown')}' missing extraction specification" + ) + + # Check constraints have extraction specs + for constraint in self.config.get("constraints", []): + if "extraction" not in constraint: + self.validation_errors.append( + f"Constraint '{constraint.get('name', 'unknown')}' missing extraction specification" + ) + + def get_validation_report(self) -> str: + """Get human-readable validation report.""" + if not self.validation_errors: + return "[OK] Configuration is valid" + + report = "[FAIL] Configuration validation failed:\n" + for i, error in enumerate(self.validation_errors, 1): + report += f" {i}. {error}\n" + return report + + # Type-safe accessor methods + + def get_design_variables(self) -> List[Dict[str, Any]]: + """Get design variables with validated structure.""" + if self.config is None: + self.load_config() + return self.config.get("design_variables", []) + + def get_objectives(self) -> List[Dict[str, Any]]: + """Get objectives with validated structure.""" + if self.config is None: + self.load_config() + return self.config.get("objectives", []) + + def get_constraints(self) -> List[Dict[str, Any]]: + """Get constraints with validated structure.""" + if self.config is None: + self.load_config() + return self.config.get("constraints", []) + + def get_simulation_settings(self) -> Dict[str, Any]: + """Get simulation settings.""" + if self.config is None: + self.load_config() + return self.config.get("simulation", {}) + + def get_optimization_settings(self) -> Dict[str, Any]: + """Get optimization settings.""" + if self.config is None: + self.load_config() + return self.config.get("optimization_settings", {}) + + +# CLI tool for validation +if __name__ == "__main__": + import sys + + if len(sys.argv) < 2: + print("Usage: python config_manager.py ") + print("\nExample:") + print(" python config_manager.py studies/drone_gimbal_arm_optimization/1_setup/optimization_config.json") + sys.exit(1) + + config_path = Path(sys.argv[1]) + + print(f"Validating configuration: {config_path}") + print("=" * 60) + + manager = ConfigManager(config_path) + + try: + manager.load_config() + print("[OK] Config loaded successfully") + + is_valid = manager.validate() + print(manager.get_validation_report()) + + if is_valid: + print("\n" + "=" * 60) + print("Configuration Summary:") + print(f" Study: {manager.config.get('study_name')}") + print(f" Protocol: {manager.get_optimization_settings().get('protocol')}") + print(f" Design Variables: {len(manager.get_design_variables())}") + print(f" Objectives: {len(manager.get_objectives())}") + print(f" Constraints: {len(manager.get_constraints())}") + + sys.exit(0 if is_valid else 1) + except Exception as e: + print(f"[ERROR] {e}") + sys.exit(1) diff --git a/optimization_engine/schemas/README.md b/optimization_engine/schemas/README.md new file mode 100644 index 00000000..d4adf5cc --- /dev/null +++ b/optimization_engine/schemas/README.md @@ -0,0 +1,213 @@ +# Atomizer Configuration Schemas + +This directory contains JSON Schema files for validating Atomizer optimization study configurations. + +## Files + +- **optimization_config_schema.json** - Schema for `optimization_config.json` files + - Validates study configuration including objectives, constraints, design variables + - Ensures consistency across all studies + - Based on Protocol 10 (single-objective) and Protocol 11 (multi-objective) standards + +## Usage + +### Validate a Configuration + +Use the ConfigManager CLI tool: + +```bash +python optimization_engine/config_manager.py studies/your_study/1_setup/optimization_config.json +``` + +### In Python Code + +```python +from pathlib import Path +from optimization_engine.config_manager import ConfigManager + +config_path = Path("studies/my_study/1_setup/optimization_config.json") +manager = ConfigManager(config_path) +manager.load_config() + +if manager.validate(): + print("Configuration is valid") + design_vars = manager.get_design_variables() + objectives = manager.get_objectives() +else: + print(manager.get_validation_report()) +``` + +## Schema Standards + +### Standard Configuration Format + +Based on the drone_gimbal_arm_optimization study, the standard format is: + +```json +{ + "study_name": "study_name_lowercase_with_underscores", + "description": "Brief description of optimization problem", + "engineering_context": "Real-world scenario and requirements", + + "optimization_settings": { + "protocol": "protocol_11_multi_objective", // or protocol_10_single_objective + "n_trials": 30, + "sampler": "NSGAIISampler", // or TPESampler, CmaEsSampler + "pruner": null, + "timeout_per_trial": 600 + }, + + "design_variables": [ + { + "parameter": "nx_expression_name", + "bounds": [min, max], + "description": "What this parameter controls" + } + ], + + "objectives": [ + { + "name": "objective_name", + "goal": "minimize", // or "maximize" + "weight": 1.0, + "description": "What this measures", + "target": 100.0, // optional + "extraction": { + "action": "extract_mass", // or extract_stress, extract_displacement, etc. + "domain": "result_extraction", + "params": { + "result_type": "mass", + "metric": "total" + } + } + } + ], + + "constraints": [ + { + "name": "constraint_name", + "type": "less_than", // or "greater_than" + "threshold": 100.0, + "description": "Engineering justification", + "extraction": { + "action": "extract_displacement", + "domain": "result_extraction", + "params": { + "result_type": "displacement", + "metric": "max" + } + } + } + ], + + "simulation": { + "model_file": "Model.prt", + "sim_file": "Model_sim1.sim", + "fem_file": "Model_fem1.fem", + "solver": "nastran", + "analysis_types": ["static", "modal"] + }, + + "reporting": { + "generate_plots": true, + "save_incremental": true, + "llm_summary": false + } +} +``` + +## Field Name Standards + +**IMPORTANT**: Use these field names (not legacy alternatives): + +| Field | Standard Name | Legacy (DON'T USE) | +|-------|--------------|-------------------| +| Objective direction | `"goal"` | `"type"` | +| Design var bounds | `"bounds": [min, max]` | `"min": X, "max": Y"` | +| Design var name | `"parameter"` | `"name"` | +| Constraint limit | `"threshold"` | `"value"` | + +## File Location Standard + +Configuration files **MUST** be in the `1_setup/` directory: + +``` +studies/your_study/ +├── 1_setup/ +│ ├── optimization_config.json ← HERE +│ └── workflow_config.json +├── 2_results/ +└── run_optimization.py +``` + +## Validation Rules + +The schema enforces: + +1. **Study Name**: Lowercase with underscores, 3-100 characters +2. **Design Variable Bounds**: min < max +3. **Multi-Objective Consistency**: + - 2-3 objectives → protocol_11_multi_objective + NSGAIISampler + - 1 objective → protocol_10_single_objective + TPESampler/CmaEsSampler +4. **Extraction Specs**: All objectives and constraints must have extraction blocks +5. **File Extensions**: .prt, .sim, .fem +6. **Analysis Types**: static, modal, thermal, or buckling + +## Common Validation Errors + +### Error: "min must be < max" +```json +// BAD +{"parameter": "thickness", "bounds": [10, 5]} + +// GOOD +{"parameter": "thickness", "bounds": [5, 10]} +``` + +### Error: "Multi-objective should use NSGAIISampler" +```json +// BAD +{ + "objectives": [{...}, {...}], // 2 objectives + "optimization_settings": {"sampler": "TPESampler"} // Wrong sampler! +} + +// GOOD +{ + "objectives": [{...}, {...}], + "optimization_settings": {"sampler": "NSGAIISampler"} +} +``` + +### Error: "Config should be in 1_setup/" +Move your configuration file from study root to `1_setup/` directory. + +## Dependencies + +The ConfigManager requires: +```bash +pip install jsonschema>=4.17.0 +``` + +## Migration from Legacy Format + +If you have old configuration files with: +- `"type"` instead of `"goal"` in objectives +- `"min"/"max"` instead of `"bounds"` in design variables +- `"name"` instead of `"parameter"` in design variables +- `"value"` instead of `"threshold"` in constraints + +See the migration tool (coming in Phase 1.2.1): +```bash +python optimization_engine/config_migrator.py studies/old_study/optimization_config.json +``` + +## References + +- [Phase 1.2 Implementation Plan](../../docs/07_DEVELOPMENT/Phase_1_2_Implementation_Plan.md) +- [create-study Claude Skill](../../.claude/skills/create-study.md) +- [drone_gimbal_arm Example](../../studies/drone_gimbal_arm_optimization/1_setup/optimization_config.json) + +## Questions? + +For MVP development questions, refer to [DEVELOPMENT.md](../../DEVELOPMENT.md) or the MVP plan in `docs/07_DEVELOPMENT/Today_Todo.md`. diff --git a/optimization_engine/schemas/optimization_config_schema.json b/optimization_engine/schemas/optimization_config_schema.json new file mode 100644 index 00000000..c2eeb345 --- /dev/null +++ b/optimization_engine/schemas/optimization_config_schema.json @@ -0,0 +1,279 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://atomizer.io/schemas/optimization_config.json", + "title": "Atomizer Optimization Configuration", + "description": "Schema for Atomizer optimization study configuration files", + "type": "object", + "required": ["study_name", "description", "optimization_settings", "design_variables", "objectives", "simulation"], + + "properties": { + "study_name": { + "type": "string", + "description": "Unique identifier for this optimization study", + "pattern": "^[a-z0-9_]+$", + "minLength": 3, + "maxLength": 100 + }, + + "description": { + "type": "string", + "description": "Brief description of the optimization problem", + "minLength": 10, + "maxLength": 500 + }, + + "engineering_context": { + "type": "string", + "description": "Real-world engineering scenario and requirements (optional but recommended)", + "minLength": 20 + }, + + "optimization_settings": { + "type": "object", + "required": ["protocol", "n_trials"], + "properties": { + "protocol": { + "type": "string", + "description": "Optimization protocol to use", + "enum": [ + "protocol_10_single_objective", + "protocol_11_multi_objective", + "legacy" + ] + }, + "n_trials": { + "type": "integer", + "description": "Number of optimization trials to run", + "minimum": 1, + "maximum": 10000 + }, + "sampler": { + "type": "string", + "description": "Optuna sampler algorithm", + "enum": ["TPESampler", "NSGAIISampler", "CmaEsSampler", "RandomSampler"] + }, + "pruner": { + "type": ["string", "null"], + "description": "Optuna pruner (null for no pruning)", + "enum": ["MedianPruner", "HyperbandPruner", "SuccessiveHalvingPruner", null] + }, + "timeout_per_trial": { + "type": "integer", + "description": "Maximum time per trial in seconds", + "minimum": 60, + "maximum": 7200 + } + } + }, + + "design_variables": { + "type": "array", + "description": "List of design variables (NX expressions) to optimize", + "minItems": 1, + "maxItems": 50, + "items": { + "type": "object", + "required": ["parameter", "bounds", "description"], + "properties": { + "parameter": { + "type": "string", + "description": "NX expression name", + "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$" + }, + "bounds": { + "type": "array", + "description": "[min, max] bounds for this parameter", + "minItems": 2, + "maxItems": 2, + "items": { + "type": "number" + } + }, + "description": { + "type": "string", + "description": "What this parameter controls" + } + } + } + }, + + "objectives": { + "type": "array", + "description": "Optimization objectives (1 for single-objective, 2-3 for multi-objective)", + "minItems": 1, + "maxItems": 3, + "items": { + "type": "object", + "required": ["name", "goal", "weight", "description", "extraction"], + "properties": { + "name": { + "type": "string", + "description": "Objective name (e.g., 'mass', 'stiffness', 'frequency')" + }, + "goal": { + "type": "string", + "description": "Optimization direction", + "enum": ["minimize", "maximize"] + }, + "weight": { + "type": "number", + "description": "Relative importance weight", + "minimum": 0, + "maximum": 100 + }, + "description": { + "type": "string", + "description": "What this objective measures" + }, + "target": { + "type": ["number", "null"], + "description": "Target value (optional)" + }, + "extraction": { + "$ref": "#/definitions/extraction_spec" + } + } + } + }, + + "constraints": { + "type": "array", + "description": "Constraints that must be satisfied", + "items": { + "type": "object", + "required": ["name", "type", "threshold", "description", "extraction"], + "properties": { + "name": { + "type": "string", + "description": "Constraint name" + }, + "type": { + "type": "string", + "description": "Constraint type", + "enum": ["less_than", "greater_than", "equal_to"] + }, + "threshold": { + "type": "number", + "description": "Threshold value for constraint" + }, + "description": { + "type": "string", + "description": "Engineering justification for this constraint" + }, + "extraction": { + "$ref": "#/definitions/extraction_spec" + } + } + } + }, + + "simulation": { + "type": "object", + "required": ["model_file", "sim_file", "fem_file", "solver"], + "properties": { + "model_file": { + "type": "string", + "description": "NX Part file name (e.g., 'Beam.prt')", + "pattern": "\\.(prt|PRT)$" + }, + "sim_file": { + "type": "string", + "description": "NX Simulation file name (e.g., 'Beam_sim1.sim')", + "pattern": "\\.(sim|SIM)$" + }, + "fem_file": { + "type": "string", + "description": "FEM mesh file name (e.g., 'Beam_fem1.fem')", + "pattern": "\\.(fem|FEM)$" + }, + "solver": { + "type": "string", + "description": "FEA solver type", + "enum": ["nastran", "NX_Nastran"] + }, + "analysis_types": { + "type": "array", + "description": "Types of analysis required", + "items": { + "type": "string", + "enum": ["static", "modal", "thermal", "buckling"] + } + } + } + }, + + "reporting": { + "type": "object", + "description": "Result reporting settings (optional)", + "properties": { + "generate_plots": { + "type": "boolean", + "default": true + }, + "save_incremental": { + "type": "boolean", + "default": true + }, + "llm_summary": { + "type": "boolean", + "default": false, + "description": "Generate LLM summaries (experimental, requires API setup)" + } + } + } + }, + + "definitions": { + "extraction_spec": { + "type": "object", + "required": ["action", "domain", "params"], + "properties": { + "action": { + "type": "string", + "description": "Extractor function to call", + "enum": [ + "extract_mass", + "extract_stress", + "extract_displacement", + "extract_frequency", + "extract_stiffness" + ] + }, + "domain": { + "type": "string", + "description": "Extraction domain (always 'result_extraction' for MVP)", + "enum": ["result_extraction"] + }, + "params": { + "type": "object", + "description": "Parameters for the extractor function", + "required": ["result_type"], + "properties": { + "result_type": { + "type": "string", + "description": "Type of result to extract", + "enum": ["mass", "stress", "displacement", "frequency", "stiffness"] + }, + "metric": { + "type": "string", + "description": "Specific metric to extract (e.g., 'max', 'total', 'max_von_mises')" + }, + "mode_number": { + "type": "integer", + "description": "Mode number for frequency extraction", + "minimum": 1 + }, + "subcase": { + "type": "integer", + "description": "Subcase number for result extraction" + }, + "element_type": { + "type": "string", + "description": "Element type for stress extraction (e.g., 'CTETRA', 'CHEXA')" + } + } + } + } + } + } +}