fix: mass extraction NaN in Hydrotech Beam DOE — two bugs

Bug 1 — Journal (solve_simulation.py simple workflow):
  Expression lookup for p173 fails silently for derived/measurement
  expressions, so _temp_mass.txt was never written. Added MeasureManager
  fallback via extract_part_mass() (already used in assembly workflow).

Bug 2 — Extractor (extract_mass_from_expression.py):
  Journal writes 'p173=<value>' format but extractor tried float() on
  the whole content including 'p173='. Added key=value parsing.

Defense in depth — nx_interface.py:
  Added stdout parsing fallback: if _temp_mass.txt still missing, parse
  mass from journal output captured via solver.py stdout passthrough.

Files changed:
  - optimization_engine/nx/solve_simulation.py — MeasureManager fallback
  - optimization_engine/extractors/extract_mass_from_expression.py — key=value parse
  - optimization_engine/nx/solver.py — include stdout in result dict
  - projects/hydrotech-beam/studies/01_doe_landscape/nx_interface.py — stdout fallback

Tags: hydrotech-beam, mass-extraction
This commit is contained in:
2026-02-11 19:02:43 +00:00
parent 04f06766a0
commit 6f3325d86f
4 changed files with 70 additions and 16 deletions

View File

@@ -42,13 +42,19 @@ def extract_mass_from_expression(prt_file: Path, expression_name: str = "p173")
# Read mass from file
try:
with open(mass_file, 'r') as f:
mass_kg = float(f.read().strip())
content = f.read().strip()
# Handle key=value format (e.g., "p173=1185.767")
if '=' in content:
content = content.split('=', 1)[1]
mass_kg = float(content)
print(f"[OK] Mass from {expression_name}: {mass_kg:.6f} kg ({mass_kg * 1000:.2f} g)")
return mass_kg
except ValueError as e:
raise ValueError(f"Could not parse mass from {mass_file}: {e}")
raise ValueError(f"Could not parse mass from {mass_file} (content: {content!r}): {e}")
except Exception as e:
raise RuntimeError(f"Failed to read mass file: {e}")

View File

@@ -1170,28 +1170,49 @@ def solve_simple_workflow(
f"[JOURNAL] Solve completed: {numsolved} solved, {numfailed} failed, {numskipped} skipped"
)
# Extract mass from geometry part expression (p173) and write to temp file
# Extract mass and write to _temp_mass.txt
# Strategy: try expression lookup first, fall back to MeasureManager
try:
mass_value = None
# Find geometry part (Beam.prt)
for part in theSession.Parts:
part_type = type(part).__name__
if "fem" not in part_type.lower() and "sim" not in part_type.lower():
# This is the geometry part — look for mass expression
for expr in part.Expressions:
if expr.Name == "p173":
mass_value = expr.Value
print(f"[JOURNAL] Mass expression p173 = {mass_value}")
break
break
# Attempt 1: Read mass from expression p173 on geometry part
try:
for part in theSession.Parts:
part_type = type(part).__name__
if "fem" not in part_type.lower() and "sim" not in part_type.lower():
for expr in part.Expressions:
if expr.Name == "p173":
mass_value = expr.Value
print(f"[JOURNAL] Mass expression p173 = {mass_value}")
break
break
except Exception as expr_err:
print(f"[JOURNAL] Expression lookup failed: {expr_err}")
# Attempt 2: Use MeasureManager (more reliable for derived/measurement expressions)
if mass_value is None:
print(f"[JOURNAL] Expression p173 not found, using MeasureManager fallback...")
geom_part = None
for part in theSession.Parts:
part_type = type(part).__name__
if "fem" not in part_type.lower() and "sim" not in part_type.lower():
geom_part = part
break
if geom_part is not None:
try:
mass_value = extract_part_mass(theSession, geom_part, working_dir)
print(f"[JOURNAL] MeasureManager mass = {mass_value}")
except Exception as mm_err:
print(f"[JOURNAL] MeasureManager failed: {mm_err}")
# Write mass file (extract_part_mass may have already written it, but ensure consistency)
if mass_value is not None:
mass_file = os.path.join(working_dir, "_temp_mass.txt")
with open(mass_file, "w") as f:
f.write(f"p173={mass_value}\n")
print(f"[JOURNAL] Wrote mass to {mass_file}")
else:
print(f"[JOURNAL] WARNING: Could not find mass expression p173")
print(f"[JOURNAL] WARNING: Could not extract mass via expression or MeasureManager")
except Exception as e:
print(f"[JOURNAL] WARNING: Mass extraction failed: {e}")

View File

@@ -605,7 +605,8 @@ sys.argv = ['', {argv_str}] # Set argv for the main function
'elapsed_time': elapsed_time,
'errors': errors,
'return_code': result.returncode,
'solution_name': solution_name
'solution_name': solution_name,
'stdout': result.stdout if self.use_journal else '',
}
except subprocess.TimeoutExpired: