Files
Atomizer/optimization_engine/hooks/examples.py
Antoine 602560c46a feat: Add MLP surrogate with Turbo Mode for 100x faster optimization
Neural Acceleration (MLP Surrogate):
- Add run_nn_optimization.py with hybrid FEA/NN workflow
- MLP architecture: 4-layer (64->128->128->64) with BatchNorm/Dropout
- Three workflow modes:
  - --all: Sequential export->train->optimize->validate
  - --hybrid-loop: Iterative Train->NN->Validate->Retrain cycle
  - --turbo: Aggressive single-best validation (RECOMMENDED)
- Turbo mode: 5000 NN trials + 50 FEA validations in ~12 minutes
- Separate nn_study.db to avoid overloading dashboard

Performance Results (bracket_pareto_3obj study):
- NN prediction errors: mass 1-5%, stress 1-4%, stiffness 5-15%
- Found minimum mass designs at boundary (angle~30deg, thick~30mm)
- 100x speedup vs pure FEA exploration

Protocol Operating System:
- Add .claude/skills/ with Bootstrap, Cheatsheet, Context Loader
- Add docs/protocols/ with operations (OP_01-06) and system (SYS_10-14)
- Update SYS_14_NEURAL_ACCELERATION.md with MLP Turbo Mode docs

NX Automation:
- Add optimization_engine/hooks/ for NX CAD/CAE automation
- Add study_wizard.py for guided study creation
- Fix FEM mesh update: load idealized part before UpdateFemodel()

New Study:
- bracket_pareto_3obj: 3-objective Pareto (mass, stress, stiffness)
- 167 FEA trials + 5000 NN trials completed
- Demonstrates full hybrid workflow

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-06 20:01:59 -05:00

400 lines
12 KiB
Python

"""
NX Open Hooks - Usage Examples
==============================
This file contains practical examples of using the NX Open hooks
for common optimization tasks.
Run examples:
python -m optimization_engine.hooks.examples
Or import specific examples:
from optimization_engine.hooks.examples import design_exploration_example
"""
import os
import sys
from pathlib import Path
# Add project root to path
project_root = Path(__file__).parent.parent.parent
sys.path.insert(0, str(project_root))
from optimization_engine.hooks.nx_cad import (
part_manager,
expression_manager,
geometry_query,
feature_manager,
)
# =============================================================================
# Example 1: Basic Expression Query
# =============================================================================
def basic_expression_query(part_path: str):
"""
Example: Query all expressions from an NX part.
This is useful for discovering available design parameters
before setting up an optimization study.
"""
print("\n" + "=" * 60)
print("Example 1: Basic Expression Query")
print("=" * 60)
result = expression_manager.get_expressions(part_path)
if not result["success"]:
print(f"ERROR: {result['error']}")
return None
data = result["data"]
print(f"\nFound {data['count']} expressions:\n")
# Print in a nice table format
print(f"{'Name':<25} {'Value':>12} {'Units':<15} {'RHS'}")
print("-" * 70)
for name, expr in data["expressions"].items():
units = expr.get("units") or ""
rhs = expr.get("rhs", "")
# Truncate RHS if it's a formula reference
if len(rhs) > 20:
rhs = rhs[:17] + "..."
print(f"{name:<25} {expr['value']:>12.4f} {units:<15} {rhs}")
return data["expressions"]
# =============================================================================
# Example 2: Mass Properties Extraction
# =============================================================================
def mass_properties_example(part_path: str):
"""
Example: Extract mass properties from an NX part.
This is useful for mass optimization objectives.
"""
print("\n" + "=" * 60)
print("Example 2: Mass Properties Extraction")
print("=" * 60)
result = geometry_query.get_mass_properties(part_path)
if not result["success"]:
print(f"ERROR: {result['error']}")
return None
data = result["data"]
print(f"\nMass Properties:")
print("-" * 40)
print(f" Mass: {data['mass']:.6f} {data['mass_unit']}")
print(f" Volume: {data['volume']:.2f} {data['volume_unit']}")
print(f" Surface Area: {data['surface_area']:.2f} {data['area_unit']}")
print(f" Material: {data['material'] or 'Not assigned'}")
centroid = data["centroid"]
print(f"\nCentroid (mm):")
print(f" X: {centroid['x']:.4f}")
print(f" Y: {centroid['y']:.4f}")
print(f" Z: {centroid['z']:.4f}")
if data.get("principal_moments"):
pm = data["principal_moments"]
print(f"\nPrincipal Moments of Inertia ({pm['unit']}):")
print(f" Ixx: {pm['Ixx']:.4f}")
print(f" Iyy: {pm['Iyy']:.4f}")
print(f" Izz: {pm['Izz']:.4f}")
return data
# =============================================================================
# Example 3: Design Parameter Update
# =============================================================================
def design_update_example(part_path: str, dry_run: bool = True):
"""
Example: Update design parameters in an NX part.
This demonstrates the workflow for parametric optimization:
1. Read current values
2. Compute new values
3. Update the model
Args:
part_path: Path to the NX part
dry_run: If True, only shows what would be changed (default)
"""
print("\n" + "=" * 60)
print("Example 3: Design Parameter Update")
print("=" * 60)
# Step 1: Get current expressions
result = expression_manager.get_expressions(part_path)
if not result["success"]:
print(f"ERROR: {result['error']}")
return None
expressions = result["data"]["expressions"]
# Step 2: Find numeric expressions (potential design variables)
design_vars = {}
for name, expr in expressions.items():
# Skip linked expressions (RHS contains another expression name)
if expr.get("rhs") and not expr["rhs"].replace(".", "").replace("-", "").isdigit():
continue
# Only include length/angle expressions
if expr.get("units") in ["MilliMeter", "Degrees", None]:
design_vars[name] = expr["value"]
print(f"\nIdentified {len(design_vars)} potential design variables:")
for name, value in design_vars.items():
print(f" {name}: {value}")
if dry_run:
print("\n[DRY RUN] Would update expressions (no changes made)")
# Example: increase all dimensions by 10%
new_values = {name: value * 1.1 for name, value in design_vars.items()}
print("\nProposed changes:")
for name, new_val in new_values.items():
old_val = design_vars[name]
print(f" {name}: {old_val:.4f} -> {new_val:.4f} (+10%)")
return new_values
else:
# Actually update the model
new_values = {name: value * 1.1 for name, value in design_vars.items()}
print("\nUpdating expressions...")
result = expression_manager.set_expressions(part_path, new_values)
if result["success"]:
print(f"SUCCESS: Updated {result['data']['update_count']} expressions")
if result["data"].get("errors"):
print(f"Warnings: {result['data']['errors']}")
else:
print(f"ERROR: {result['error']}")
return result
# =============================================================================
# Example 4: Feature Exploration
# =============================================================================
def feature_exploration_example(part_path: str):
"""
Example: Explore and manipulate features.
This is useful for topological optimization where features
can be suppressed/unsuppressed to explore design space.
"""
print("\n" + "=" * 60)
print("Example 4: Feature Exploration")
print("=" * 60)
result = feature_manager.get_features(part_path)
if not result["success"]:
print(f"ERROR: {result['error']}")
return None
data = result["data"]
print(f"\nFound {data['count']} features ({data['suppressed_count']} suppressed):\n")
print(f"{'Name':<30} {'Type':<20} {'Status'}")
print("-" * 60)
for feat in data["features"]:
status = "SUPPRESSED" if feat["is_suppressed"] else "Active"
print(f"{feat['name']:<30} {feat['type']:<20} {status}")
# Group by type
print("\n\nFeatures by type:")
print("-" * 40)
type_counts = {}
for feat in data["features"]:
feat_type = feat["type"]
type_counts[feat_type] = type_counts.get(feat_type, 0) + 1
for feat_type, count in sorted(type_counts.items(), key=lambda x: -x[1]):
print(f" {feat_type}: {count}")
return data
# =============================================================================
# Example 5: Optimization Objective Evaluation
# =============================================================================
def evaluate_design_point(part_path: str, parameters: dict) -> dict:
"""
Example: Complete design evaluation workflow.
This demonstrates how hooks integrate into an optimization loop:
1. Update parameters
2. Extract objectives (mass, volume)
3. Return metrics
Args:
part_path: Path to the NX part
parameters: Dict of parameter_name -> new_value
Returns:
Dict with mass_kg, volume_mm3, surface_area_mm2
"""
print("\n" + "=" * 60)
print("Example 5: Optimization Objective Evaluation")
print("=" * 60)
print(f"\nParameters to set:")
for name, value in parameters.items():
print(f" {name} = {value}")
# Step 1: Update parameters
print("\n[1/2] Updating design parameters...")
result = expression_manager.set_expressions(part_path, parameters)
if not result["success"]:
raise RuntimeError(f"Failed to set expressions: {result['error']}")
print(f" Updated {result['data']['update_count']} expressions")
# Step 2: Extract objectives
print("\n[2/2] Extracting mass properties...")
result = geometry_query.get_mass_properties(part_path)
if not result["success"]:
raise RuntimeError(f"Failed to get mass properties: {result['error']}")
data = result["data"]
# Return metrics
metrics = {
"mass_kg": data["mass"],
"volume_mm3": data["volume"],
"surface_area_mm2": data["surface_area"],
"material": data.get("material"),
}
print(f"\nObjective metrics:")
print(f" Mass: {metrics['mass_kg']:.6f} kg")
print(f" Volume: {metrics['volume_mm3']:.2f} mm^3")
print(f" Surface Area: {metrics['surface_area_mm2']:.2f} mm^2")
return metrics
# =============================================================================
# Example 6: Batch Processing Multiple Parts
# =============================================================================
def batch_mass_extraction(part_paths: list) -> list:
"""
Example: Extract mass from multiple parts.
Useful for comparing variants or processing a design library.
"""
print("\n" + "=" * 60)
print("Example 6: Batch Processing Multiple Parts")
print("=" * 60)
results = []
for i, part_path in enumerate(part_paths, 1):
print(f"\n[{i}/{len(part_paths)}] Processing: {Path(part_path).name}")
result = geometry_query.get_mass_properties(part_path)
if result["success"]:
data = result["data"]
results.append({
"part": Path(part_path).name,
"mass_kg": data["mass"],
"volume_mm3": data["volume"],
"material": data.get("material"),
"success": True,
})
print(f" Mass: {data['mass']:.4f} kg, Material: {data.get('material')}")
else:
results.append({
"part": Path(part_path).name,
"error": result["error"],
"success": False,
})
print(f" ERROR: {result['error']}")
# Summary
print("\n" + "-" * 60)
print("Summary:")
successful = [r for r in results if r["success"]]
print(f" Processed: {len(successful)}/{len(part_paths)} parts")
if successful:
total_mass = sum(r["mass_kg"] for r in successful)
print(f" Total mass: {total_mass:.4f} kg")
return results
# =============================================================================
# Main - Run All Examples
# =============================================================================
def main():
"""Run all examples with a test part."""
# Default test part
default_part = project_root / "studies/bracket_stiffness_optimization_V3/1_setup/model/Bracket.prt"
if len(sys.argv) > 1:
part_path = sys.argv[1]
else:
part_path = str(default_part)
print("\n" + "=" * 60)
print("NX OPEN HOOKS - EXAMPLES")
print("=" * 60)
print(f"\nUsing part: {Path(part_path).name}")
if not os.path.exists(part_path):
print(f"\nERROR: Part file not found: {part_path}")
print("\nUsage: python -m optimization_engine.hooks.examples [part_path]")
sys.exit(1)
# Run examples
try:
# Example 1: Query expressions
basic_expression_query(part_path)
# Example 2: Get mass properties
mass_properties_example(part_path)
# Example 3: Design update (dry run)
design_update_example(part_path, dry_run=True)
# Example 4: Feature exploration
feature_exploration_example(part_path)
print("\n" + "=" * 60)
print("ALL EXAMPLES COMPLETED SUCCESSFULLY!")
print("=" * 60)
except Exception as e:
print(f"\nEXAMPLE FAILED: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()