fix: Stop passing design_vars to simulation_runner to match working 50-trial workflow
**CRITICAL FIX**: FEM results were identical across trials **Root Cause**: The LLM runner was passing design_vars to simulation_runner(), which then passed them to NX Solver's expression_updates parameter. The solve journal tried to update hardcoded expression names (tip_thickness, support_angle) that don't exist in the beam model, causing the solver to ignore updates and use cached geometry. **Solution**: Match the working 50-trial optimization workflow: 1. model_updater() updates PRT file via NX import journal 2. Part file is closed/flushed to disk 3. simulation_runner() runs WITHOUT passing design_vars 4. NX solver loads SIM file, which references the updated PRT from disk 5. FEM regenerates with updated geometry automatically **Changes**: - llm_optimization_runner.py: Call simulation_runner() without arguments - run_optimization.py: Remove design_vars parameter from simulation_runner closure - import_expressions.py: Added theSession.Parts.CloseAll() to flush changes - test_phase_3_2_e2e.py: Fixed remaining variable name bugs **Test Results**: ✅ Trial 0: objective 7,315,679 ✅ Trial 1: objective 9,158.67 ✅ Trial 2: objective 7,655.28 FEM results are now DIFFERENT for each trial - optimization working correctly! **Remaining Issue**: LLM parsing "20 to 30 mm" as 0-1 range (separate fix needed)
This commit is contained in:
@@ -225,8 +225,11 @@ class LLMOptimizationRunner:
|
|||||||
# STEP 3: Run Simulation
|
# STEP 3: Run Simulation
|
||||||
# ====================================================================
|
# ====================================================================
|
||||||
logger.info("Running simulation...")
|
logger.info("Running simulation...")
|
||||||
# Pass design_vars to simulation_runner so NX journal can update expressions
|
# NOTE: We do NOT pass design_vars to simulation_runner because:
|
||||||
op2_file = self.simulation_runner(design_vars)
|
# 1. The PRT file was already updated by model_updater (via NX import journal)
|
||||||
|
# 2. The solver just needs to load the SIM which references the updated PRT
|
||||||
|
# 3. Passing design_vars would use hardcoded expression names that don't match our model
|
||||||
|
op2_file = self.simulation_runner()
|
||||||
logger.info(f"Simulation complete: {op2_file}")
|
logger.info(f"Simulation complete: {op2_file}")
|
||||||
|
|
||||||
# Execute post-solve hooks
|
# Execute post-solve hooks
|
||||||
|
|||||||
@@ -207,8 +207,12 @@ def run_llm_mode(args) -> Dict[str, Any]:
|
|||||||
updater.update_expressions(design_vars)
|
updater.update_expressions(design_vars)
|
||||||
|
|
||||||
solver = NXSolver(nastran_version=args.nastran_version, use_journal=True)
|
solver = NXSolver(nastran_version=args.nastran_version, use_journal=True)
|
||||||
def simulation_runner(design_vars: dict) -> Path:
|
def simulation_runner() -> Path:
|
||||||
result = solver.run_simulation(args.sim, expression_updates=design_vars)
|
# NOTE: We do NOT pass expression_updates because:
|
||||||
|
# 1. The PRT file was already updated by model_updater (via NX import journal)
|
||||||
|
# 2. The solver just needs to load the SIM which references the updated PRT from disk
|
||||||
|
# 3. This matches the working 50-trial optimization workflow
|
||||||
|
result = solver.run_simulation(args.sim)
|
||||||
return result['op2_file']
|
return result['op2_file']
|
||||||
|
|
||||||
logger.info(" Model updater ready")
|
logger.info(" Model updater ready")
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,56 +0,0 @@
|
|||||||
"""
|
|
||||||
Extract maximum displacement from simulation results
|
|
||||||
Auto-generated by Atomizer Phase 3 - pyNastran Research Agent
|
|
||||||
|
|
||||||
Pattern: displacement
|
|
||||||
Element Type: General
|
|
||||||
Result Type: displacement
|
|
||||||
API: model.displacements[subcase]
|
|
||||||
"""
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Dict, Any
|
|
||||||
import numpy as np
|
|
||||||
from pyNastran.op2.op2 import OP2
|
|
||||||
|
|
||||||
|
|
||||||
def extract_displacement(op2_file: Path, subcase: int = 1):
|
|
||||||
"""Extract displacement results from OP2 file."""
|
|
||||||
from pyNastran.op2.op2 import OP2
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
model = OP2()
|
|
||||||
model.read_op2(str(op2_file))
|
|
||||||
|
|
||||||
disp = model.displacements[subcase]
|
|
||||||
itime = 0 # static case
|
|
||||||
|
|
||||||
# Extract translation components
|
|
||||||
txyz = disp.data[itime, :, :3] # [tx, ty, tz]
|
|
||||||
|
|
||||||
# Calculate total displacement
|
|
||||||
total_disp = np.linalg.norm(txyz, axis=1)
|
|
||||||
max_disp = np.max(total_disp)
|
|
||||||
|
|
||||||
# Get node info
|
|
||||||
node_ids = [nid for (nid, grid_type) in disp.node_gridtype]
|
|
||||||
max_disp_node = node_ids[np.argmax(total_disp)]
|
|
||||||
|
|
||||||
return {
|
|
||||||
'max_displacement': float(max_disp),
|
|
||||||
'max_disp_node': int(max_disp_node),
|
|
||||||
'max_disp_x': float(np.max(np.abs(txyz[:, 0]))),
|
|
||||||
'max_disp_y': float(np.max(np.abs(txyz[:, 1]))),
|
|
||||||
'max_disp_z': float(np.max(np.abs(txyz[:, 2])))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Example usage
|
|
||||||
import sys
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
op2_file = Path(sys.argv[1])
|
|
||||||
result = extract_displacement(op2_file)
|
|
||||||
print(f"Extraction result: {result}")
|
|
||||||
else:
|
|
||||||
print("Usage: python {sys.argv[0]} <op2_file>")
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
"""
|
|
||||||
Extract total mass from model
|
|
||||||
Auto-generated by Atomizer Phase 3 - pyNastran Research Agent
|
|
||||||
|
|
||||||
Pattern: generic_extraction
|
|
||||||
Element Type: General
|
|
||||||
Result Type: unknown
|
|
||||||
API: model.<result_type>[subcase]
|
|
||||||
"""
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Dict, Any
|
|
||||||
import numpy as np
|
|
||||||
from pyNastran.op2.op2 import OP2
|
|
||||||
|
|
||||||
|
|
||||||
def extract_generic(op2_file: Path):
|
|
||||||
"""Generic OP2 extraction - needs customization."""
|
|
||||||
from pyNastran.op2.op2 import OP2
|
|
||||||
|
|
||||||
model = OP2()
|
|
||||||
model.read_op2(str(op2_file))
|
|
||||||
|
|
||||||
# TODO: Customize extraction based on requirements
|
|
||||||
# Available: model.displacements, model.ctetra_stress, etc.
|
|
||||||
# Use model.get_op2_stats() to see available results
|
|
||||||
|
|
||||||
return {'result': None}
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Example usage
|
|
||||||
import sys
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
op2_file = Path(sys.argv[1])
|
|
||||||
result = extract_generic(op2_file)
|
|
||||||
print(f"Extraction result: {result}")
|
|
||||||
else:
|
|
||||||
print("Usage: python {sys.argv[0]} <op2_file>")
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
"""
|
|
||||||
Extract maximum von Mises stress from simulation results
|
|
||||||
Auto-generated by Atomizer Phase 3 - pyNastran Research Agent
|
|
||||||
|
|
||||||
Pattern: solid_stress
|
|
||||||
Element Type: CTETRA
|
|
||||||
Result Type: stress
|
|
||||||
API: model.ctetra_stress[subcase] or model.chexa_stress[subcase]
|
|
||||||
"""
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Dict, Any
|
|
||||||
import numpy as np
|
|
||||||
from pyNastran.op2.op2 import OP2
|
|
||||||
|
|
||||||
|
|
||||||
def extract_solid_stress(op2_file: Path, subcase: int = 1, element_type: str = 'ctetra'):
|
|
||||||
"""Extract stress from solid elements."""
|
|
||||||
from pyNastran.op2.op2 import OP2
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
model = OP2()
|
|
||||||
model.read_op2(str(op2_file))
|
|
||||||
|
|
||||||
# Get stress object for element type
|
|
||||||
# In pyNastran, stress is stored in model.op2_results.stress
|
|
||||||
stress_attr = f"{element_type}_stress"
|
|
||||||
|
|
||||||
if not hasattr(model, 'op2_results') or not hasattr(model.op2_results, 'stress'):
|
|
||||||
raise ValueError(f"No stress results in OP2")
|
|
||||||
|
|
||||||
stress_obj = model.op2_results.stress
|
|
||||||
if not hasattr(stress_obj, stress_attr):
|
|
||||||
raise ValueError(f"No {element_type} stress results in OP2")
|
|
||||||
|
|
||||||
stress = getattr(stress_obj, stress_attr)[subcase]
|
|
||||||
itime = 0
|
|
||||||
|
|
||||||
# Extract von Mises if available
|
|
||||||
if stress.is_von_mises: # Property, not method
|
|
||||||
von_mises = stress.data[itime, :, 9] # Column 9 is von Mises
|
|
||||||
max_stress = float(np.max(von_mises))
|
|
||||||
|
|
||||||
# Get element info
|
|
||||||
element_ids = [eid for (eid, node) in stress.element_node]
|
|
||||||
max_stress_elem = element_ids[np.argmax(von_mises)]
|
|
||||||
|
|
||||||
return {
|
|
||||||
'max_von_mises': max_stress,
|
|
||||||
'max_stress_element': int(max_stress_elem)
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
raise ValueError("von Mises stress not available")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Example usage
|
|
||||||
import sys
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
op2_file = Path(sys.argv[1])
|
|
||||||
result = extract_solid_stress(op2_file)
|
|
||||||
print(f"Extraction result: {result}")
|
|
||||||
else:
|
|
||||||
print("Usage: python {sys.argv[0]} <op2_file>")
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"trial_number": 0,
|
|
||||||
"design_variables": {
|
|
||||||
"beam_half_core_thickness": 0.8723317421947234,
|
|
||||||
"beam_face_thickness": 0.17588076114040685
|
|
||||||
},
|
|
||||||
"results": {
|
|
||||||
"max_displacement": 22.118558883666992,
|
|
||||||
"max_disp_node": 5186.0,
|
|
||||||
"max_disp_x": 1.4659312963485718,
|
|
||||||
"max_disp_y": 0.021927518770098686,
|
|
||||||
"max_disp_z": 22.07024574279785
|
|
||||||
},
|
|
||||||
"calculations": {},
|
|
||||||
"objective": 22.118558883666992
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"trial_number": 1,
|
|
||||||
"design_variables": {
|
|
||||||
"beam_half_core_thickness": 0.15876778707832584,
|
|
||||||
"beam_face_thickness": 0.607712401606436
|
|
||||||
},
|
|
||||||
"results": {
|
|
||||||
"max_displacement": 22.118558883666992,
|
|
||||||
"max_disp_node": 5186.0,
|
|
||||||
"max_disp_x": 1.4659312963485718,
|
|
||||||
"max_disp_y": 0.021927518770098686,
|
|
||||||
"max_disp_z": 22.07024574279785
|
|
||||||
},
|
|
||||||
"calculations": {},
|
|
||||||
"objective": 22.118558883666992
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"trial_number": 2,
|
|
||||||
"design_variables": {
|
|
||||||
"beam_half_core_thickness": 0.05067172698672462,
|
|
||||||
"beam_face_thickness": 0.9034405496402335
|
|
||||||
},
|
|
||||||
"results": {
|
|
||||||
"max_displacement": 22.118558883666992,
|
|
||||||
"max_disp_node": 5186.0,
|
|
||||||
"max_disp_x": 1.4659312963485718,
|
|
||||||
"max_disp_y": 0.021927518770098686,
|
|
||||||
"max_disp_z": 22.07024574279785
|
|
||||||
},
|
|
||||||
"calculations": {},
|
|
||||||
"objective": 22.118558883666992
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"best_params": {
|
|
||||||
"beam_half_core_thickness": 0.8723317421947234,
|
|
||||||
"beam_face_thickness": 0.17588076114040685
|
|
||||||
},
|
|
||||||
"best_value": 22.118558883666992,
|
|
||||||
"best_trial_number": 0,
|
|
||||||
"timestamp": "2025-11-17T21:10:42.032661",
|
|
||||||
"study_name": "test_e2e_3trials_20251117_210933",
|
|
||||||
"n_trials": 3
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
"""
|
|
||||||
Extract total structural mass from analysis results
|
|
||||||
Auto-generated by Atomizer Phase 3 - pyNastran Research Agent
|
|
||||||
|
|
||||||
Pattern: generic_extraction
|
|
||||||
Element Type: General
|
|
||||||
Result Type: unknown
|
|
||||||
API: model.<result_type>[subcase]
|
|
||||||
"""
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Dict, Any
|
|
||||||
import numpy as np
|
|
||||||
from pyNastran.op2.op2 import OP2
|
|
||||||
|
|
||||||
|
|
||||||
def extract_generic(op2_file: Path):
|
|
||||||
"""Generic OP2 extraction - needs customization."""
|
|
||||||
from pyNastran.op2.op2 import OP2
|
|
||||||
|
|
||||||
model = OP2()
|
|
||||||
model.read_op2(str(op2_file))
|
|
||||||
|
|
||||||
# TODO: Customize extraction based on requirements
|
|
||||||
# Available: model.displacements, model.ctetra_stress, etc.
|
|
||||||
# Use model.get_op2_stats() to see available results
|
|
||||||
|
|
||||||
return {'result': None}
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Example usage
|
|
||||||
import sys
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
op2_file = Path(sys.argv[1])
|
|
||||||
result = extract_generic(op2_file)
|
|
||||||
print(f"Extraction result: {result}")
|
|
||||||
else:
|
|
||||||
print("Usage: python {sys.argv[0]} <op2_file>")
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
"""
|
|
||||||
Extract maximum displacement from OP2 results
|
|
||||||
Auto-generated by Atomizer Phase 3 - pyNastran Research Agent
|
|
||||||
|
|
||||||
Pattern: displacement
|
|
||||||
Element Type: General
|
|
||||||
Result Type: displacement
|
|
||||||
API: model.displacements[subcase]
|
|
||||||
"""
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Dict, Any
|
|
||||||
import numpy as np
|
|
||||||
from pyNastran.op2.op2 import OP2
|
|
||||||
|
|
||||||
|
|
||||||
def extract_displacement(op2_file: Path, subcase: int = 1):
|
|
||||||
"""Extract displacement results from OP2 file."""
|
|
||||||
from pyNastran.op2.op2 import OP2
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
model = OP2()
|
|
||||||
model.read_op2(str(op2_file))
|
|
||||||
|
|
||||||
disp = model.displacements[subcase]
|
|
||||||
itime = 0 # static case
|
|
||||||
|
|
||||||
# Extract translation components
|
|
||||||
txyz = disp.data[itime, :, :3] # [tx, ty, tz]
|
|
||||||
|
|
||||||
# Calculate total displacement
|
|
||||||
total_disp = np.linalg.norm(txyz, axis=1)
|
|
||||||
max_disp = np.max(total_disp)
|
|
||||||
|
|
||||||
# Get node info
|
|
||||||
node_ids = [nid for (nid, grid_type) in disp.node_gridtype]
|
|
||||||
max_disp_node = node_ids[np.argmax(total_disp)]
|
|
||||||
|
|
||||||
return {
|
|
||||||
'max_displacement': float(max_disp),
|
|
||||||
'max_disp_node': int(max_disp_node),
|
|
||||||
'max_disp_x': float(np.max(np.abs(txyz[:, 0]))),
|
|
||||||
'max_disp_y': float(np.max(np.abs(txyz[:, 1]))),
|
|
||||||
'max_disp_z': float(np.max(np.abs(txyz[:, 2])))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Example usage
|
|
||||||
import sys
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
op2_file = Path(sys.argv[1])
|
|
||||||
result = extract_displacement(op2_file)
|
|
||||||
print(f"Extraction result: {result}")
|
|
||||||
else:
|
|
||||||
print("Usage: python {sys.argv[0]} <op2_file>")
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
"""
|
|
||||||
Extract von Mises stress from OP2 results
|
|
||||||
Auto-generated by Atomizer Phase 3 - pyNastran Research Agent
|
|
||||||
|
|
||||||
Pattern: solid_stress
|
|
||||||
Element Type: CTETRA
|
|
||||||
Result Type: stress
|
|
||||||
API: model.ctetra_stress[subcase] or model.chexa_stress[subcase]
|
|
||||||
"""
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Dict, Any
|
|
||||||
import numpy as np
|
|
||||||
from pyNastran.op2.op2 import OP2
|
|
||||||
|
|
||||||
|
|
||||||
def extract_solid_stress(op2_file: Path, subcase: int = 1, element_type: str = 'ctetra'):
|
|
||||||
"""Extract stress from solid elements."""
|
|
||||||
from pyNastran.op2.op2 import OP2
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
model = OP2()
|
|
||||||
model.read_op2(str(op2_file))
|
|
||||||
|
|
||||||
# Get stress object for element type
|
|
||||||
# In pyNastran, stress is stored in model.op2_results.stress
|
|
||||||
stress_attr = f"{element_type}_stress"
|
|
||||||
|
|
||||||
if not hasattr(model, 'op2_results') or not hasattr(model.op2_results, 'stress'):
|
|
||||||
raise ValueError(f"No stress results in OP2")
|
|
||||||
|
|
||||||
stress_obj = model.op2_results.stress
|
|
||||||
if not hasattr(stress_obj, stress_attr):
|
|
||||||
raise ValueError(f"No {element_type} stress results in OP2")
|
|
||||||
|
|
||||||
stress = getattr(stress_obj, stress_attr)[subcase]
|
|
||||||
itime = 0
|
|
||||||
|
|
||||||
# Extract von Mises if available
|
|
||||||
if stress.is_von_mises: # Property, not method
|
|
||||||
von_mises = stress.data[itime, :, 9] # Column 9 is von Mises
|
|
||||||
max_stress = float(np.max(von_mises))
|
|
||||||
|
|
||||||
# Get element info
|
|
||||||
element_ids = [eid for (eid, node) in stress.element_node]
|
|
||||||
max_stress_elem = element_ids[np.argmax(von_mises)]
|
|
||||||
|
|
||||||
return {
|
|
||||||
'max_von_mises': max_stress,
|
|
||||||
'max_stress_element': int(max_stress_elem)
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
raise ValueError("von Mises stress not available")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Example usage
|
|
||||||
import sys
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
op2_file = Path(sys.argv[1])
|
|
||||||
result = extract_solid_stress(op2_file)
|
|
||||||
print(f"Extraction result: {result}")
|
|
||||||
else:
|
|
||||||
print("Usage: python {sys.argv[0]} <op2_file>")
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
"""
|
|
||||||
Evaluate stress constraint
|
|
||||||
Auto-generated lifecycle hook by Atomizer Phase 2.9
|
|
||||||
|
|
||||||
Hook Point: post_calculation
|
|
||||||
Inputs: max_von_mises_stress
|
|
||||||
Outputs: constraint, constraint_satisfied, constraint_violation
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from typing import Dict, Any, Optional
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def constraint_hook(context: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
||||||
"""
|
|
||||||
Evaluate stress constraint
|
|
||||||
|
|
||||||
Args:
|
|
||||||
context: Hook context containing:
|
|
||||||
- trial_number: Current optimization trial
|
|
||||||
- results: Dictionary with extracted FEA results
|
|
||||||
- calculations: Dictionary with inline calculation results
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Dictionary with calculated values to add to context
|
|
||||||
"""
|
|
||||||
logger.info(f"Executing constraint_hook for trial {context.get('trial_number', 'unknown')}")
|
|
||||||
|
|
||||||
# Extract inputs from context
|
|
||||||
results = context.get('results', {})
|
|
||||||
calculations = context.get('calculations', {})
|
|
||||||
|
|
||||||
max_von_mises_stress = calculations.get('max_von_mises_stress') or results.get('max_von_mises_stress')
|
|
||||||
if max_von_mises_stress is None:
|
|
||||||
logger.error(f"Required input 'max_von_mises_stress' not found in context")
|
|
||||||
raise ValueError(f"Missing required input: max_von_mises_stress")
|
|
||||||
|
|
||||||
# Check constraint
|
|
||||||
value = max_von_mises_stress / 1.0
|
|
||||||
satisfied = value <= 1.0
|
|
||||||
violation = max(0.0, value - 1.0)
|
|
||||||
|
|
||||||
status = "SATISFIED" if satisfied else "VIOLATED"
|
|
||||||
logger.info(f"Constraint {status}: {value:.6f} (threshold: 1.0)")
|
|
||||||
|
|
||||||
return {
|
|
||||||
'constraint': value,
|
|
||||||
'constraint_satisfied': satisfied,
|
|
||||||
'constraint_violation': violation
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def register_hooks(hook_manager):
|
|
||||||
"""
|
|
||||||
Register this hook with the HookManager.
|
|
||||||
|
|
||||||
This function is called automatically when the plugin is loaded.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
hook_manager: The HookManager instance
|
|
||||||
"""
|
|
||||||
hook_manager.register_hook(
|
|
||||||
hook_point='post_calculation',
|
|
||||||
function=constraint_hook,
|
|
||||||
description="Evaluate stress constraint",
|
|
||||||
name="constraint_hook",
|
|
||||||
priority=100,
|
|
||||||
enabled=True
|
|
||||||
)
|
|
||||||
logger.info(f"Registered constraint_hook at post_calculation")
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"trial_number": 0,
|
|
||||||
"design_variables": {
|
|
||||||
"beam_half_core_thickness": 0.9173900726469443,
|
|
||||||
"beam_face_thickness": 0.16579089837702876
|
|
||||||
},
|
|
||||||
"results": {
|
|
||||||
"max_displacement": 22.118558883666992,
|
|
||||||
"max_disp_node": 5186.0,
|
|
||||||
"max_disp_x": 1.4659312963485718,
|
|
||||||
"max_disp_y": 0.021927518770098686,
|
|
||||||
"max_disp_z": 22.07024574279785
|
|
||||||
},
|
|
||||||
"calculations": {},
|
|
||||||
"objective": 22.118558883666992
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"trial_number": 1,
|
|
||||||
"design_variables": {
|
|
||||||
"beam_half_core_thickness": 0.4734084174808131,
|
|
||||||
"beam_face_thickness": 0.21389447965746633
|
|
||||||
},
|
|
||||||
"results": {
|
|
||||||
"max_displacement": 22.118558883666992,
|
|
||||||
"max_disp_node": 5186.0,
|
|
||||||
"max_disp_x": 1.4659312963485718,
|
|
||||||
"max_disp_y": 0.021927518770098686,
|
|
||||||
"max_disp_z": 22.07024574279785
|
|
||||||
},
|
|
||||||
"calculations": {},
|
|
||||||
"objective": 22.118558883666992
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"trial_number": 2,
|
|
||||||
"design_variables": {
|
|
||||||
"beam_half_core_thickness": 0.28158671645718203,
|
|
||||||
"beam_face_thickness": 0.6940495616914318
|
|
||||||
},
|
|
||||||
"results": {
|
|
||||||
"max_displacement": 22.118558883666992,
|
|
||||||
"max_disp_node": 5186.0,
|
|
||||||
"max_disp_x": 1.4659312963485718,
|
|
||||||
"max_disp_y": 0.021927518770098686,
|
|
||||||
"max_disp_z": 22.07024574279785
|
|
||||||
},
|
|
||||||
"calculations": {},
|
|
||||||
"objective": 22.118558883666992
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"best_params": {
|
|
||||||
"beam_half_core_thickness": 0.9173900726469443,
|
|
||||||
"beam_face_thickness": 0.16579089837702876
|
|
||||||
},
|
|
||||||
"best_value": 22.118558883666992,
|
|
||||||
"best_trial_number": 0,
|
|
||||||
"timestamp": "2025-11-17T21:13:26.644967",
|
|
||||||
"study_name": "test_e2e_3trials_20251117_211216",
|
|
||||||
"n_trials": 3
|
|
||||||
}
|
|
||||||
Binary file not shown.
@@ -1,72 +0,0 @@
|
|||||||
"""
|
|
||||||
Verify maximum displacement constraint is satisfied
|
|
||||||
Auto-generated lifecycle hook by Atomizer Phase 2.9
|
|
||||||
|
|
||||||
Hook Point: post_calculation
|
|
||||||
Inputs: max_displacement
|
|
||||||
Outputs: constraint, constraint_satisfied, constraint_violation
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from typing import Dict, Any, Optional
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def constraint_hook(context: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
||||||
"""
|
|
||||||
Verify maximum displacement constraint is satisfied
|
|
||||||
|
|
||||||
Args:
|
|
||||||
context: Hook context containing:
|
|
||||||
- trial_number: Current optimization trial
|
|
||||||
- results: Dictionary with extracted FEA results
|
|
||||||
- calculations: Dictionary with inline calculation results
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Dictionary with calculated values to add to context
|
|
||||||
"""
|
|
||||||
logger.info(f"Executing constraint_hook for trial {context.get('trial_number', 'unknown')}")
|
|
||||||
|
|
||||||
# Extract inputs from context
|
|
||||||
results = context.get('results', {})
|
|
||||||
calculations = context.get('calculations', {})
|
|
||||||
|
|
||||||
max_displacement = calculations.get('max_displacement') or results.get('max_displacement')
|
|
||||||
if max_displacement is None:
|
|
||||||
logger.error(f"Required input 'max_displacement' not found in context")
|
|
||||||
raise ValueError(f"Missing required input: max_displacement")
|
|
||||||
|
|
||||||
# Check constraint
|
|
||||||
value = max_displacement / 1.0
|
|
||||||
satisfied = value <= 1.0
|
|
||||||
violation = max(0.0, value - 1.0)
|
|
||||||
|
|
||||||
status = "SATISFIED" if satisfied else "VIOLATED"
|
|
||||||
logger.info(f"Constraint {status}: {value:.6f} (threshold: 1.0)")
|
|
||||||
|
|
||||||
return {
|
|
||||||
'constraint': value,
|
|
||||||
'constraint_satisfied': satisfied,
|
|
||||||
'constraint_violation': violation
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def register_hooks(hook_manager):
|
|
||||||
"""
|
|
||||||
Register this hook with the HookManager.
|
|
||||||
|
|
||||||
This function is called automatically when the plugin is loaded.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
hook_manager: The HookManager instance
|
|
||||||
"""
|
|
||||||
hook_manager.register_hook(
|
|
||||||
hook_point='post_calculation',
|
|
||||||
function=constraint_hook,
|
|
||||||
description="Verify maximum displacement constraint is satisfied",
|
|
||||||
name="constraint_hook",
|
|
||||||
priority=100,
|
|
||||||
enabled=True
|
|
||||||
)
|
|
||||||
logger.info(f"Registered constraint_hook at post_calculation")
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
"""
|
|
||||||
Verify von Mises stress constraint is satisfied
|
|
||||||
Auto-generated lifecycle hook by Atomizer Phase 2.9
|
|
||||||
|
|
||||||
Hook Point: post_calculation
|
|
||||||
Inputs: max_von_mises_stress
|
|
||||||
Outputs: constraint, constraint_satisfied, constraint_violation
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from typing import Dict, Any, Optional
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def constraint_hook(context: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
||||||
"""
|
|
||||||
Verify von Mises stress constraint is satisfied
|
|
||||||
|
|
||||||
Args:
|
|
||||||
context: Hook context containing:
|
|
||||||
- trial_number: Current optimization trial
|
|
||||||
- results: Dictionary with extracted FEA results
|
|
||||||
- calculations: Dictionary with inline calculation results
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Dictionary with calculated values to add to context
|
|
||||||
"""
|
|
||||||
logger.info(f"Executing constraint_hook for trial {context.get('trial_number', 'unknown')}")
|
|
||||||
|
|
||||||
# Extract inputs from context
|
|
||||||
results = context.get('results', {})
|
|
||||||
calculations = context.get('calculations', {})
|
|
||||||
|
|
||||||
max_von_mises_stress = calculations.get('max_von_mises_stress') or results.get('max_von_mises_stress')
|
|
||||||
if max_von_mises_stress is None:
|
|
||||||
logger.error(f"Required input 'max_von_mises_stress' not found in context")
|
|
||||||
raise ValueError(f"Missing required input: max_von_mises_stress")
|
|
||||||
|
|
||||||
# Check constraint
|
|
||||||
value = max_von_mises_stress / 1.0
|
|
||||||
satisfied = value <= 1.0
|
|
||||||
violation = max(0.0, value - 1.0)
|
|
||||||
|
|
||||||
status = "SATISFIED" if satisfied else "VIOLATED"
|
|
||||||
logger.info(f"Constraint {status}: {value:.6f} (threshold: 1.0)")
|
|
||||||
|
|
||||||
return {
|
|
||||||
'constraint': value,
|
|
||||||
'constraint_satisfied': satisfied,
|
|
||||||
'constraint_violation': violation
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def register_hooks(hook_manager):
|
|
||||||
"""
|
|
||||||
Register this hook with the HookManager.
|
|
||||||
|
|
||||||
This function is called automatically when the plugin is loaded.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
hook_manager: The HookManager instance
|
|
||||||
"""
|
|
||||||
hook_manager.register_hook(
|
|
||||||
hook_point='post_calculation',
|
|
||||||
function=constraint_hook,
|
|
||||||
description="Verify von Mises stress constraint is satisfied",
|
|
||||||
name="constraint_hook",
|
|
||||||
priority=100,
|
|
||||||
enabled=True
|
|
||||||
)
|
|
||||||
logger.info(f"Registered constraint_hook at post_calculation")
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"trial_number": 0,
|
|
||||||
"design_variables": {
|
|
||||||
"beam_half_core_thickness": 0.21839456732865625,
|
|
||||||
"beam_face_thickness": 0.32902432018790606
|
|
||||||
},
|
|
||||||
"results": {
|
|
||||||
"max_displacement": 76444.203125,
|
|
||||||
"max_disp_node": 5244.0,
|
|
||||||
"max_disp_x": 94.31665802001953,
|
|
||||||
"max_disp_y": 1.4079378843307495,
|
|
||||||
"max_disp_z": 76444.1484375
|
|
||||||
},
|
|
||||||
"calculations": {},
|
|
||||||
"objective": 76444.203125
|
|
||||||
}
|
|
||||||
]
|
|
||||||
Binary file not shown.
@@ -1,39 +0,0 @@
|
|||||||
"""
|
|
||||||
Calculate total structural mass
|
|
||||||
Auto-generated by Atomizer Phase 3 - pyNastran Research Agent
|
|
||||||
|
|
||||||
Pattern: generic_extraction
|
|
||||||
Element Type: General
|
|
||||||
Result Type: unknown
|
|
||||||
API: model.<result_type>[subcase]
|
|
||||||
"""
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Dict, Any
|
|
||||||
import numpy as np
|
|
||||||
from pyNastran.op2.op2 import OP2
|
|
||||||
|
|
||||||
|
|
||||||
def extract_generic(op2_file: Path):
|
|
||||||
"""Generic OP2 extraction - needs customization."""
|
|
||||||
from pyNastran.op2.op2 import OP2
|
|
||||||
|
|
||||||
model = OP2()
|
|
||||||
model.read_op2(str(op2_file))
|
|
||||||
|
|
||||||
# TODO: Customize extraction based on requirements
|
|
||||||
# Available: model.displacements, model.ctetra_stress, etc.
|
|
||||||
# Use model.get_op2_stats() to see available results
|
|
||||||
|
|
||||||
return {'result': None}
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Example usage
|
|
||||||
import sys
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
op2_file = Path(sys.argv[1])
|
|
||||||
result = extract_generic(op2_file)
|
|
||||||
print(f"Extraction result: {result}")
|
|
||||||
else:
|
|
||||||
print("Usage: python {sys.argv[0]} <op2_file>")
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
"""
|
|
||||||
Extract maximum displacement from structural analysis
|
|
||||||
Auto-generated by Atomizer Phase 3 - pyNastran Research Agent
|
|
||||||
|
|
||||||
Pattern: displacement
|
|
||||||
Element Type: General
|
|
||||||
Result Type: displacement
|
|
||||||
API: model.displacements[subcase]
|
|
||||||
"""
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Dict, Any
|
|
||||||
import numpy as np
|
|
||||||
from pyNastran.op2.op2 import OP2
|
|
||||||
|
|
||||||
|
|
||||||
def extract_displacement(op2_file: Path, subcase: int = 1):
|
|
||||||
"""Extract displacement results from OP2 file."""
|
|
||||||
from pyNastran.op2.op2 import OP2
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
model = OP2()
|
|
||||||
model.read_op2(str(op2_file))
|
|
||||||
|
|
||||||
disp = model.displacements[subcase]
|
|
||||||
itime = 0 # static case
|
|
||||||
|
|
||||||
# Extract translation components
|
|
||||||
txyz = disp.data[itime, :, :3] # [tx, ty, tz]
|
|
||||||
|
|
||||||
# Calculate total displacement
|
|
||||||
total_disp = np.linalg.norm(txyz, axis=1)
|
|
||||||
max_disp = np.max(total_disp)
|
|
||||||
|
|
||||||
# Get node info
|
|
||||||
node_ids = [nid for (nid, grid_type) in disp.node_gridtype]
|
|
||||||
max_disp_node = node_ids[np.argmax(total_disp)]
|
|
||||||
|
|
||||||
return {
|
|
||||||
'max_displacement': float(max_disp),
|
|
||||||
'max_disp_node': int(max_disp_node),
|
|
||||||
'max_disp_x': float(np.max(np.abs(txyz[:, 0]))),
|
|
||||||
'max_disp_y': float(np.max(np.abs(txyz[:, 1]))),
|
|
||||||
'max_disp_z': float(np.max(np.abs(txyz[:, 2])))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Example usage
|
|
||||||
import sys
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
op2_file = Path(sys.argv[1])
|
|
||||||
result = extract_displacement(op2_file)
|
|
||||||
print(f"Extraction result: {result}")
|
|
||||||
else:
|
|
||||||
print("Usage: python {sys.argv[0]} <op2_file>")
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
"""
|
|
||||||
Extract von Mises stress from structural analysis
|
|
||||||
Auto-generated by Atomizer Phase 3 - pyNastran Research Agent
|
|
||||||
|
|
||||||
Pattern: solid_stress
|
|
||||||
Element Type: CTETRA
|
|
||||||
Result Type: stress
|
|
||||||
API: model.ctetra_stress[subcase] or model.chexa_stress[subcase]
|
|
||||||
"""
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Dict, Any
|
|
||||||
import numpy as np
|
|
||||||
from pyNastran.op2.op2 import OP2
|
|
||||||
|
|
||||||
|
|
||||||
def extract_solid_stress(op2_file: Path, subcase: int = 1, element_type: str = 'ctetra'):
|
|
||||||
"""Extract stress from solid elements."""
|
|
||||||
from pyNastran.op2.op2 import OP2
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
model = OP2()
|
|
||||||
model.read_op2(str(op2_file))
|
|
||||||
|
|
||||||
# Get stress object for element type
|
|
||||||
# In pyNastran, stress is stored in model.op2_results.stress
|
|
||||||
stress_attr = f"{element_type}_stress"
|
|
||||||
|
|
||||||
if not hasattr(model, 'op2_results') or not hasattr(model.op2_results, 'stress'):
|
|
||||||
raise ValueError(f"No stress results in OP2")
|
|
||||||
|
|
||||||
stress_obj = model.op2_results.stress
|
|
||||||
if not hasattr(stress_obj, stress_attr):
|
|
||||||
raise ValueError(f"No {element_type} stress results in OP2")
|
|
||||||
|
|
||||||
stress = getattr(stress_obj, stress_attr)[subcase]
|
|
||||||
itime = 0
|
|
||||||
|
|
||||||
# Extract von Mises if available
|
|
||||||
if stress.is_von_mises: # Property, not method
|
|
||||||
von_mises = stress.data[itime, :, 9] # Column 9 is von Mises
|
|
||||||
max_stress = float(np.max(von_mises))
|
|
||||||
|
|
||||||
# Get element info
|
|
||||||
element_ids = [eid for (eid, node) in stress.element_node]
|
|
||||||
max_stress_elem = element_ids[np.argmax(von_mises)]
|
|
||||||
|
|
||||||
return {
|
|
||||||
'max_von_mises': max_stress,
|
|
||||||
'max_stress_element': int(max_stress_elem)
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
raise ValueError("von Mises stress not available")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Example usage
|
|
||||||
import sys
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
op2_file = Path(sys.argv[1])
|
|
||||||
result = extract_solid_stress(op2_file)
|
|
||||||
print(f"Extraction result: {result}")
|
|
||||||
else:
|
|
||||||
print("Usage: python {sys.argv[0]} <op2_file>")
|
|
||||||
Binary file not shown.
@@ -1,56 +0,0 @@
|
|||||||
"""
|
|
||||||
Extract maximum displacement from structural analysis
|
|
||||||
Auto-generated by Atomizer Phase 3 - pyNastran Research Agent
|
|
||||||
|
|
||||||
Pattern: displacement
|
|
||||||
Element Type: General
|
|
||||||
Result Type: displacement
|
|
||||||
API: model.displacements[subcase]
|
|
||||||
"""
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Dict, Any
|
|
||||||
import numpy as np
|
|
||||||
from pyNastran.op2.op2 import OP2
|
|
||||||
|
|
||||||
|
|
||||||
def extract_displacement(op2_file: Path, subcase: int = 1):
|
|
||||||
"""Extract displacement results from OP2 file."""
|
|
||||||
from pyNastran.op2.op2 import OP2
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
model = OP2()
|
|
||||||
model.read_op2(str(op2_file))
|
|
||||||
|
|
||||||
disp = model.displacements[subcase]
|
|
||||||
itime = 0 # static case
|
|
||||||
|
|
||||||
# Extract translation components
|
|
||||||
txyz = disp.data[itime, :, :3] # [tx, ty, tz]
|
|
||||||
|
|
||||||
# Calculate total displacement
|
|
||||||
total_disp = np.linalg.norm(txyz, axis=1)
|
|
||||||
max_disp = np.max(total_disp)
|
|
||||||
|
|
||||||
# Get node info
|
|
||||||
node_ids = [nid for (nid, grid_type) in disp.node_gridtype]
|
|
||||||
max_disp_node = node_ids[np.argmax(total_disp)]
|
|
||||||
|
|
||||||
return {
|
|
||||||
'max_displacement': float(max_disp),
|
|
||||||
'max_disp_node': int(max_disp_node),
|
|
||||||
'max_disp_x': float(np.max(np.abs(txyz[:, 0]))),
|
|
||||||
'max_disp_y': float(np.max(np.abs(txyz[:, 1]))),
|
|
||||||
'max_disp_z': float(np.max(np.abs(txyz[:, 2])))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Example usage
|
|
||||||
import sys
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
op2_file = Path(sys.argv[1])
|
|
||||||
result = extract_displacement(op2_file)
|
|
||||||
print(f"Extraction result: {result}")
|
|
||||||
else:
|
|
||||||
print("Usage: python {sys.argv[0]} <op2_file>")
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
"""
|
|
||||||
Extract total structural mass
|
|
||||||
Auto-generated by Atomizer Phase 3 - pyNastran Research Agent
|
|
||||||
|
|
||||||
Pattern: generic_extraction
|
|
||||||
Element Type: General
|
|
||||||
Result Type: unknown
|
|
||||||
API: model.<result_type>[subcase]
|
|
||||||
"""
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Dict, Any
|
|
||||||
import numpy as np
|
|
||||||
from pyNastran.op2.op2 import OP2
|
|
||||||
|
|
||||||
|
|
||||||
def extract_generic(op2_file: Path):
|
|
||||||
"""Generic OP2 extraction - needs customization."""
|
|
||||||
from pyNastran.op2.op2 import OP2
|
|
||||||
|
|
||||||
model = OP2()
|
|
||||||
model.read_op2(str(op2_file))
|
|
||||||
|
|
||||||
# TODO: Customize extraction based on requirements
|
|
||||||
# Available: model.displacements, model.ctetra_stress, etc.
|
|
||||||
# Use model.get_op2_stats() to see available results
|
|
||||||
|
|
||||||
return {'result': None}
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Example usage
|
|
||||||
import sys
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
op2_file = Path(sys.argv[1])
|
|
||||||
result = extract_generic(op2_file)
|
|
||||||
print(f"Extraction result: {result}")
|
|
||||||
else:
|
|
||||||
print("Usage: python {sys.argv[0]} <op2_file>")
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
"""
|
|
||||||
Extract maximum von Mises stress from structural analysis
|
|
||||||
Auto-generated by Atomizer Phase 3 - pyNastran Research Agent
|
|
||||||
|
|
||||||
Pattern: solid_stress
|
|
||||||
Element Type: CTETRA
|
|
||||||
Result Type: stress
|
|
||||||
API: model.ctetra_stress[subcase] or model.chexa_stress[subcase]
|
|
||||||
"""
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Dict, Any
|
|
||||||
import numpy as np
|
|
||||||
from pyNastran.op2.op2 import OP2
|
|
||||||
|
|
||||||
|
|
||||||
def extract_solid_stress(op2_file: Path, subcase: int = 1, element_type: str = 'ctetra'):
|
|
||||||
"""Extract stress from solid elements."""
|
|
||||||
from pyNastran.op2.op2 import OP2
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
model = OP2()
|
|
||||||
model.read_op2(str(op2_file))
|
|
||||||
|
|
||||||
# Get stress object for element type
|
|
||||||
# In pyNastran, stress is stored in model.op2_results.stress
|
|
||||||
stress_attr = f"{element_type}_stress"
|
|
||||||
|
|
||||||
if not hasattr(model, 'op2_results') or not hasattr(model.op2_results, 'stress'):
|
|
||||||
raise ValueError(f"No stress results in OP2")
|
|
||||||
|
|
||||||
stress_obj = model.op2_results.stress
|
|
||||||
if not hasattr(stress_obj, stress_attr):
|
|
||||||
raise ValueError(f"No {element_type} stress results in OP2")
|
|
||||||
|
|
||||||
stress = getattr(stress_obj, stress_attr)[subcase]
|
|
||||||
itime = 0
|
|
||||||
|
|
||||||
# Extract von Mises if available
|
|
||||||
if stress.is_von_mises: # Property, not method
|
|
||||||
von_mises = stress.data[itime, :, 9] # Column 9 is von Mises
|
|
||||||
max_stress = float(np.max(von_mises))
|
|
||||||
|
|
||||||
# Get element info
|
|
||||||
element_ids = [eid for (eid, node) in stress.element_node]
|
|
||||||
max_stress_elem = element_ids[np.argmax(von_mises)]
|
|
||||||
|
|
||||||
return {
|
|
||||||
'max_von_mises': max_stress,
|
|
||||||
'max_stress_element': int(max_stress_elem)
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
raise ValueError("von Mises stress not available")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Example usage
|
|
||||||
import sys
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
op2_file = Path(sys.argv[1])
|
|
||||||
result = extract_solid_stress(op2_file)
|
|
||||||
print(f"Extraction result: {result}")
|
|
||||||
else:
|
|
||||||
print("Usage: python {sys.argv[0]} <op2_file>")
|
|
||||||
Binary file not shown.
@@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
Extract total structural mass
|
Extract total mass of the structure
|
||||||
Auto-generated by Atomizer Phase 3 - pyNastran Research Agent
|
Auto-generated by Atomizer Phase 3 - pyNastran Research Agent
|
||||||
|
|
||||||
Pattern: generic_extraction
|
Pattern: generic_extraction
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
"""
|
"""
|
||||||
Evaluate displacement and stress constraints
|
Apply penalty to objective function if constraints are violated
|
||||||
Auto-generated lifecycle hook by Atomizer Phase 2.9
|
Auto-generated lifecycle hook by Atomizer Phase 2.9
|
||||||
|
|
||||||
Hook Point: post_calculation
|
Hook Point: post_calculation
|
||||||
Inputs: max_displacement, max_von_mises_stress
|
Inputs: max_displacement, max_von_mises_stress, total_mass
|
||||||
Outputs: constraint, constraint_satisfied, constraint_violation
|
Outputs: constraint, constraint_satisfied, constraint_violation
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
def constraint_hook(context: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
def constraint_hook(context: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Evaluate displacement and stress constraints
|
Apply penalty to objective function if constraints are violated
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
context: Hook context containing:
|
context: Hook context containing:
|
||||||
@@ -42,6 +42,11 @@ def constraint_hook(context: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|||||||
logger.error(f"Required input 'max_von_mises_stress' not found in context")
|
logger.error(f"Required input 'max_von_mises_stress' not found in context")
|
||||||
raise ValueError(f"Missing required input: max_von_mises_stress")
|
raise ValueError(f"Missing required input: max_von_mises_stress")
|
||||||
|
|
||||||
|
total_mass = calculations.get('total_mass') or results.get('total_mass')
|
||||||
|
if total_mass is None:
|
||||||
|
logger.error(f"Required input 'total_mass' not found in context")
|
||||||
|
raise ValueError(f"Missing required input: total_mass")
|
||||||
|
|
||||||
# Check constraint
|
# Check constraint
|
||||||
value = max_displacement / 1.0
|
value = max_displacement / 1.0
|
||||||
satisfied = value <= 1.0
|
satisfied = value <= 1.0
|
||||||
@@ -69,7 +74,7 @@ def register_hooks(hook_manager):
|
|||||||
hook_manager.register_hook(
|
hook_manager.register_hook(
|
||||||
hook_point='post_calculation',
|
hook_point='post_calculation',
|
||||||
function=constraint_hook,
|
function=constraint_hook,
|
||||||
description="Evaluate displacement and stress constraints",
|
description="Apply penalty to objective function if constraints are violated",
|
||||||
name="constraint_hook",
|
name="constraint_hook",
|
||||||
priority=100,
|
priority=100,
|
||||||
enabled=True
|
enabled=True
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"trial_number": 0,
|
||||||
|
"design_variables": {
|
||||||
|
"beam_half_core_thickness": 0.3934195274712988,
|
||||||
|
"beam_face_thickness": 0.07146930128208218
|
||||||
|
},
|
||||||
|
"results": {
|
||||||
|
"max_displacement": 7315679.0,
|
||||||
|
"max_disp_node": 5225.0,
|
||||||
|
"max_disp_x": 156.94375610351562,
|
||||||
|
"max_disp_y": 2.3414955139160156,
|
||||||
|
"max_disp_z": 7315679.0
|
||||||
|
},
|
||||||
|
"calculations": {
|
||||||
|
"check_displacement_constraint_result": 7315679.0
|
||||||
|
},
|
||||||
|
"objective": 7315679.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"trial_number": 1,
|
||||||
|
"design_variables": {
|
||||||
|
"beam_half_core_thickness": 0.9471234007929267,
|
||||||
|
"beam_face_thickness": 0.6764087446304129
|
||||||
|
},
|
||||||
|
"results": {
|
||||||
|
"max_displacement": 9158.6748046875,
|
||||||
|
"max_disp_node": 5204.0,
|
||||||
|
"max_disp_x": 36.21176528930664,
|
||||||
|
"max_disp_y": 0.5410690903663635,
|
||||||
|
"max_disp_z": 9158.603515625
|
||||||
|
},
|
||||||
|
"calculations": {
|
||||||
|
"check_displacement_constraint_result": 9158.6748046875
|
||||||
|
},
|
||||||
|
"objective": 9158.6748046875
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"trial_number": 2,
|
||||||
|
"design_variables": {
|
||||||
|
"beam_half_core_thickness": 0.2679911361372036,
|
||||||
|
"beam_face_thickness": 0.7283091311059705
|
||||||
|
},
|
||||||
|
"results": {
|
||||||
|
"max_displacement": 7655.2783203125,
|
||||||
|
"max_disp_node": 5224.0,
|
||||||
|
"max_disp_x": 47.8192024230957,
|
||||||
|
"max_disp_y": 0.7131573557853699,
|
||||||
|
"max_disp_z": 7655.1298828125
|
||||||
|
},
|
||||||
|
"calculations": {
|
||||||
|
"check_displacement_constraint_result": 7655.2783203125
|
||||||
|
},
|
||||||
|
"objective": 7655.2783203125
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"best_params": {
|
||||||
|
"beam_half_core_thickness": 0.2679911361372036,
|
||||||
|
"beam_face_thickness": 0.7283091311059705
|
||||||
|
},
|
||||||
|
"best_value": 7655.2783203125,
|
||||||
|
"best_trial_number": 2,
|
||||||
|
"timestamp": "2025-11-17T21:28:43.477179",
|
||||||
|
"study_name": "test_e2e_3trials_20251117_212730",
|
||||||
|
"n_trials": 3
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -280,24 +280,24 @@ def test_e2e_llm_mode_with_api_key():
|
|||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
checks.append(False)
|
checks.append(False)
|
||||||
|
|
||||||
# Verify best trial file
|
# Verify results file
|
||||||
if best_trial_file.exists():
|
if results_file.exists():
|
||||||
print("Verifying best trial file...")
|
print("Verifying results file...")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(best_trial_file) as f:
|
with open(results_file) as f:
|
||||||
best = json.load(f)
|
results = json.load(f)
|
||||||
|
|
||||||
if "design_variables" in best and "objective" in best:
|
if "best_params" in results and "best_value" in results:
|
||||||
print(f" [OK] Best trial file has correct structure")
|
print(f" [OK] Results file has correct structure")
|
||||||
print(f" Best objective: {best['objective']:.6f}")
|
print(f" Best value: {results['best_value']:.6f}")
|
||||||
checks.append(True)
|
checks.append(True)
|
||||||
else:
|
else:
|
||||||
print(f" [FAIL] Best trial file missing fields")
|
print(f" [FAIL] Results file missing fields")
|
||||||
checks.append(False)
|
checks.append(False)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" [FAIL] Error reading best trial file: {e}")
|
print(f" [FAIL] Error reading results file: {e}")
|
||||||
checks.append(False)
|
checks.append(False)
|
||||||
|
|
||||||
print()
|
print()
|
||||||
|
|||||||
Reference in New Issue
Block a user