feat(hydrotech-beam): Phase 1 LHS DoE study code
Implements the optimization study code for Phase 1 (LHS DoE) of the
Hydrotech Beam structural optimization.
Files added:
- run_doe.py: Main entry point — Optuna study with SQLite persistence,
Deb's feasibility rules, CSV/JSON export, Phase 1→2 gate check
- sampling.py: 50-point LHS via scipy.stats.qmc with stratified integer
sampling ensuring all 11 hole_count levels (5-15) are covered
- geometric_checks.py: Pre-flight feasibility filter — hole overlap
(corrected formula: span/(n-1) - d ≥ 30mm) and web clearance checks
- nx_interface.py: NX automation module with stub solver for development
and NXOpen template for Windows/dalidou integration
- requirements.txt: optuna, scipy, numpy, pandas
Key design decisions:
- Baseline enqueued as Trial 0 (LAC lesson)
- All 4 DV expression names from binary introspection (exact spelling)
- Pre-flight geometric filter saves compute and prevents NX crashes
- No surrogates (LAC lesson: direct FEA via TPE beats surrogate+L-BFGS)
- SQLite persistence enables resume after interruption
Tested end-to-end with stub solver: 51 trials, 12 geometric rejects,
39 solved, correct CSV/JSON output.
Ref: OPTIMIZATION_STRATEGY.md, auditor review 2026-02-10
2026-02-10 22:15:06 +00:00
|
|
|
|
"""NX automation interface for Hydrotech Beam optimization.
|
|
|
|
|
|
|
2026-02-11 13:33:09 +00:00
|
|
|
|
Integrates with the existing Atomizer optimization_engine:
|
|
|
|
|
|
- NXSolver for journal-based solve (run_journal.exe → solve_simulation.py)
|
|
|
|
|
|
- pyNastran OP2 extractors for displacement + stress
|
|
|
|
|
|
- Expression-based mass extraction via journal temp file
|
|
|
|
|
|
|
|
|
|
|
|
The proven SAT3 pipeline: write .exp → NX journal updates + solves → pyNastran reads OP2.
|
feat(hydrotech-beam): Phase 1 LHS DoE study code
Implements the optimization study code for Phase 1 (LHS DoE) of the
Hydrotech Beam structural optimization.
Files added:
- run_doe.py: Main entry point — Optuna study with SQLite persistence,
Deb's feasibility rules, CSV/JSON export, Phase 1→2 gate check
- sampling.py: 50-point LHS via scipy.stats.qmc with stratified integer
sampling ensuring all 11 hole_count levels (5-15) are covered
- geometric_checks.py: Pre-flight feasibility filter — hole overlap
(corrected formula: span/(n-1) - d ≥ 30mm) and web clearance checks
- nx_interface.py: NX automation module with stub solver for development
and NXOpen template for Windows/dalidou integration
- requirements.txt: optuna, scipy, numpy, pandas
Key design decisions:
- Baseline enqueued as Trial 0 (LAC lesson)
- All 4 DV expression names from binary introspection (exact spelling)
- Pre-flight geometric filter saves compute and prevents NX crashes
- No surrogates (LAC lesson: direct FEA via TPE beats surrogate+L-BFGS)
- SQLite persistence enables resume after interruption
Tested end-to-end with stub solver: 51 trials, 12 geometric rejects,
39 solved, correct CSV/JSON output.
Ref: OPTIMIZATION_STRATEGY.md, auditor review 2026-02-10
2026-02-10 22:15:06 +00:00
|
|
|
|
|
|
|
|
|
|
NX Expression Names (confirmed via binary introspection — CONTEXT.md):
|
|
|
|
|
|
Design Variables:
|
|
|
|
|
|
- beam_half_core_thickness (mm, continuous)
|
|
|
|
|
|
- beam_face_thickness (mm, continuous)
|
|
|
|
|
|
- holes_diameter (mm, continuous)
|
|
|
|
|
|
- hole_count (integer, links to Pattern_p7)
|
|
|
|
|
|
Outputs:
|
|
|
|
|
|
- p173 (mass in kg, body_property147.mass)
|
|
|
|
|
|
Fixed:
|
|
|
|
|
|
- beam_lenght (⚠️ TYPO in NX — no 'h', 5000 mm)
|
|
|
|
|
|
- beam_half_height (250 mm)
|
|
|
|
|
|
- beam_half_width (150 mm)
|
|
|
|
|
|
|
|
|
|
|
|
References:
|
|
|
|
|
|
CONTEXT.md — Full expression map
|
2026-02-11 13:33:09 +00:00
|
|
|
|
optimization_engine/nx/solver.py — NXSolver class
|
|
|
|
|
|
optimization_engine/extractors/ — pyNastran-based result extractors
|
|
|
|
|
|
studies/M1_Mirror/SAT3_Trajectory_V7/run_optimization.py — proven pattern
|
feat(hydrotech-beam): Phase 1 LHS DoE study code
Implements the optimization study code for Phase 1 (LHS DoE) of the
Hydrotech Beam structural optimization.
Files added:
- run_doe.py: Main entry point — Optuna study with SQLite persistence,
Deb's feasibility rules, CSV/JSON export, Phase 1→2 gate check
- sampling.py: 50-point LHS via scipy.stats.qmc with stratified integer
sampling ensuring all 11 hole_count levels (5-15) are covered
- geometric_checks.py: Pre-flight feasibility filter — hole overlap
(corrected formula: span/(n-1) - d ≥ 30mm) and web clearance checks
- nx_interface.py: NX automation module with stub solver for development
and NXOpen template for Windows/dalidou integration
- requirements.txt: optuna, scipy, numpy, pandas
Key design decisions:
- Baseline enqueued as Trial 0 (LAC lesson)
- All 4 DV expression names from binary introspection (exact spelling)
- Pre-flight geometric filter saves compute and prevents NX crashes
- No surrogates (LAC lesson: direct FEA via TPE beats surrogate+L-BFGS)
- SQLite persistence enables resume after interruption
Tested end-to-end with stub solver: 51 trials, 12 geometric rejects,
39 solved, correct CSV/JSON output.
Ref: OPTIMIZATION_STRATEGY.md, auditor review 2026-02-10
2026-02-10 22:15:06 +00:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
import logging
|
2026-02-11 13:33:09 +00:00
|
|
|
|
import os
|
2026-02-10 23:26:51 +00:00
|
|
|
|
import sys
|
2026-02-11 13:33:09 +00:00
|
|
|
|
import time
|
feat(hydrotech-beam): Phase 1 LHS DoE study code
Implements the optimization study code for Phase 1 (LHS DoE) of the
Hydrotech Beam structural optimization.
Files added:
- run_doe.py: Main entry point — Optuna study with SQLite persistence,
Deb's feasibility rules, CSV/JSON export, Phase 1→2 gate check
- sampling.py: 50-point LHS via scipy.stats.qmc with stratified integer
sampling ensuring all 11 hole_count levels (5-15) are covered
- geometric_checks.py: Pre-flight feasibility filter — hole overlap
(corrected formula: span/(n-1) - d ≥ 30mm) and web clearance checks
- nx_interface.py: NX automation module with stub solver for development
and NXOpen template for Windows/dalidou integration
- requirements.txt: optuna, scipy, numpy, pandas
Key design decisions:
- Baseline enqueued as Trial 0 (LAC lesson)
- All 4 DV expression names from binary introspection (exact spelling)
- Pre-flight geometric filter saves compute and prevents NX crashes
- No surrogates (LAC lesson: direct FEA via TPE beats surrogate+L-BFGS)
- SQLite persistence enables resume after interruption
Tested end-to-end with stub solver: 51 trials, 12 geometric rejects,
39 solved, correct CSV/JSON output.
Ref: OPTIMIZATION_STRATEGY.md, auditor review 2026-02-10
2026-02-10 22:15:06 +00:00
|
|
|
|
from dataclasses import dataclass
|
2026-02-10 23:26:51 +00:00
|
|
|
|
from pathlib import Path
|
2026-02-11 13:33:09 +00:00
|
|
|
|
from typing import Any, Dict, Optional, Protocol
|
feat(hydrotech-beam): Phase 1 LHS DoE study code
Implements the optimization study code for Phase 1 (LHS DoE) of the
Hydrotech Beam structural optimization.
Files added:
- run_doe.py: Main entry point — Optuna study with SQLite persistence,
Deb's feasibility rules, CSV/JSON export, Phase 1→2 gate check
- sampling.py: 50-point LHS via scipy.stats.qmc with stratified integer
sampling ensuring all 11 hole_count levels (5-15) are covered
- geometric_checks.py: Pre-flight feasibility filter — hole overlap
(corrected formula: span/(n-1) - d ≥ 30mm) and web clearance checks
- nx_interface.py: NX automation module with stub solver for development
and NXOpen template for Windows/dalidou integration
- requirements.txt: optuna, scipy, numpy, pandas
Key design decisions:
- Baseline enqueued as Trial 0 (LAC lesson)
- All 4 DV expression names from binary introspection (exact spelling)
- Pre-flight geometric filter saves compute and prevents NX crashes
- No surrogates (LAC lesson: direct FEA via TPE beats surrogate+L-BFGS)
- SQLite persistence enables resume after interruption
Tested end-to-end with stub solver: 51 trials, 12 geometric rejects,
39 solved, correct CSV/JSON output.
Ref: OPTIMIZATION_STRATEGY.md, auditor review 2026-02-10
2026-02-10 22:15:06 +00:00
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
# Data types
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
|
|
class TrialInput:
|
|
|
|
|
|
"""Design variable values for a single trial."""
|
|
|
|
|
|
|
|
|
|
|
|
beam_half_core_thickness: float # mm — DV1
|
|
|
|
|
|
beam_face_thickness: float # mm — DV2
|
|
|
|
|
|
holes_diameter: float # mm — DV3
|
|
|
|
|
|
hole_count: int # — DV4
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
|
class TrialResult:
|
|
|
|
|
|
"""Results extracted from NX after a trial solve.
|
|
|
|
|
|
|
|
|
|
|
|
All values populated after a successful SOL 101 solve.
|
|
|
|
|
|
On failure, success=False and error_message explains the failure.
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
success: bool
|
|
|
|
|
|
mass: float = float("nan") # kg — from expression `p173`
|
|
|
|
|
|
tip_displacement: float = float("nan") # mm — from SOL 101 results
|
|
|
|
|
|
max_von_mises: float = float("nan") # MPa — from SOL 101 results
|
2026-02-11 13:33:09 +00:00
|
|
|
|
solve_time: float = 0.0 # seconds
|
feat(hydrotech-beam): Phase 1 LHS DoE study code
Implements the optimization study code for Phase 1 (LHS DoE) of the
Hydrotech Beam structural optimization.
Files added:
- run_doe.py: Main entry point — Optuna study with SQLite persistence,
Deb's feasibility rules, CSV/JSON export, Phase 1→2 gate check
- sampling.py: 50-point LHS via scipy.stats.qmc with stratified integer
sampling ensuring all 11 hole_count levels (5-15) are covered
- geometric_checks.py: Pre-flight feasibility filter — hole overlap
(corrected formula: span/(n-1) - d ≥ 30mm) and web clearance checks
- nx_interface.py: NX automation module with stub solver for development
and NXOpen template for Windows/dalidou integration
- requirements.txt: optuna, scipy, numpy, pandas
Key design decisions:
- Baseline enqueued as Trial 0 (LAC lesson)
- All 4 DV expression names from binary introspection (exact spelling)
- Pre-flight geometric filter saves compute and prevents NX crashes
- No surrogates (LAC lesson: direct FEA via TPE beats surrogate+L-BFGS)
- SQLite persistence enables resume after interruption
Tested end-to-end with stub solver: 51 trials, 12 geometric rejects,
39 solved, correct CSV/JSON output.
Ref: OPTIMIZATION_STRATEGY.md, auditor review 2026-02-10
2026-02-10 22:15:06 +00:00
|
|
|
|
error_message: str = ""
|
2026-02-11 13:33:09 +00:00
|
|
|
|
iteration_dir: Optional[str] = None # path to iteration folder
|
feat(hydrotech-beam): Phase 1 LHS DoE study code
Implements the optimization study code for Phase 1 (LHS DoE) of the
Hydrotech Beam structural optimization.
Files added:
- run_doe.py: Main entry point — Optuna study with SQLite persistence,
Deb's feasibility rules, CSV/JSON export, Phase 1→2 gate check
- sampling.py: 50-point LHS via scipy.stats.qmc with stratified integer
sampling ensuring all 11 hole_count levels (5-15) are covered
- geometric_checks.py: Pre-flight feasibility filter — hole overlap
(corrected formula: span/(n-1) - d ≥ 30mm) and web clearance checks
- nx_interface.py: NX automation module with stub solver for development
and NXOpen template for Windows/dalidou integration
- requirements.txt: optuna, scipy, numpy, pandas
Key design decisions:
- Baseline enqueued as Trial 0 (LAC lesson)
- All 4 DV expression names from binary introspection (exact spelling)
- Pre-flight geometric filter saves compute and prevents NX crashes
- No surrogates (LAC lesson: direct FEA via TPE beats surrogate+L-BFGS)
- SQLite persistence enables resume after interruption
Tested end-to-end with stub solver: 51 trials, 12 geometric rejects,
39 solved, correct CSV/JSON output.
Ref: OPTIMIZATION_STRATEGY.md, auditor review 2026-02-10
2026-02-10 22:15:06 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
# NX expression name constants
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
# ⚠️ These are EXACT NX expression names from binary introspection.
|
|
|
|
|
|
# Do NOT change spelling — `beam_lenght` has a typo (no 'h') in NX.
|
|
|
|
|
|
EXPR_HALF_CORE_THICKNESS = "beam_half_core_thickness"
|
|
|
|
|
|
EXPR_FACE_THICKNESS = "beam_face_thickness"
|
|
|
|
|
|
EXPR_HOLES_DIAMETER = "holes_diameter"
|
|
|
|
|
|
EXPR_HOLE_COUNT = "hole_count"
|
|
|
|
|
|
EXPR_MASS = "p173" # body_property147.mass, kg
|
|
|
|
|
|
EXPR_BEAM_LENGTH = "beam_lenght" # ⚠️ TYPO IN NX — intentional
|
|
|
|
|
|
|
2026-02-11 13:33:09 +00:00
|
|
|
|
# Unit mapping for .exp file generation (NXSolver._write_expression_file)
|
|
|
|
|
|
UNIT_MAPPING = {
|
|
|
|
|
|
EXPR_HALF_CORE_THICKNESS: "mm",
|
|
|
|
|
|
EXPR_FACE_THICKNESS: "mm",
|
|
|
|
|
|
EXPR_HOLES_DIAMETER: "mm",
|
|
|
|
|
|
EXPR_HOLE_COUNT: "Constant", # integer — no unit
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat(hydrotech-beam): Phase 1 LHS DoE study code
Implements the optimization study code for Phase 1 (LHS DoE) of the
Hydrotech Beam structural optimization.
Files added:
- run_doe.py: Main entry point — Optuna study with SQLite persistence,
Deb's feasibility rules, CSV/JSON export, Phase 1→2 gate check
- sampling.py: 50-point LHS via scipy.stats.qmc with stratified integer
sampling ensuring all 11 hole_count levels (5-15) are covered
- geometric_checks.py: Pre-flight feasibility filter — hole overlap
(corrected formula: span/(n-1) - d ≥ 30mm) and web clearance checks
- nx_interface.py: NX automation module with stub solver for development
and NXOpen template for Windows/dalidou integration
- requirements.txt: optuna, scipy, numpy, pandas
Key design decisions:
- Baseline enqueued as Trial 0 (LAC lesson)
- All 4 DV expression names from binary introspection (exact spelling)
- Pre-flight geometric filter saves compute and prevents NX crashes
- No surrogates (LAC lesson: direct FEA via TPE beats surrogate+L-BFGS)
- SQLite persistence enables resume after interruption
Tested end-to-end with stub solver: 51 trials, 12 geometric rejects,
39 solved, correct CSV/JSON output.
Ref: OPTIMIZATION_STRATEGY.md, auditor review 2026-02-10
2026-02-10 22:15:06 +00:00
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
# Interface protocol
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class NXSolverInterface(Protocol):
|
2026-02-11 13:33:09 +00:00
|
|
|
|
"""Protocol for NX solver backends (enables stub/real swap)."""
|
feat(hydrotech-beam): Phase 1 LHS DoE study code
Implements the optimization study code for Phase 1 (LHS DoE) of the
Hydrotech Beam structural optimization.
Files added:
- run_doe.py: Main entry point — Optuna study with SQLite persistence,
Deb's feasibility rules, CSV/JSON export, Phase 1→2 gate check
- sampling.py: 50-point LHS via scipy.stats.qmc with stratified integer
sampling ensuring all 11 hole_count levels (5-15) are covered
- geometric_checks.py: Pre-flight feasibility filter — hole overlap
(corrected formula: span/(n-1) - d ≥ 30mm) and web clearance checks
- nx_interface.py: NX automation module with stub solver for development
and NXOpen template for Windows/dalidou integration
- requirements.txt: optuna, scipy, numpy, pandas
Key design decisions:
- Baseline enqueued as Trial 0 (LAC lesson)
- All 4 DV expression names from binary introspection (exact spelling)
- Pre-flight geometric filter saves compute and prevents NX crashes
- No surrogates (LAC lesson: direct FEA via TPE beats surrogate+L-BFGS)
- SQLite persistence enables resume after interruption
Tested end-to-end with stub solver: 51 trials, 12 geometric rejects,
39 solved, correct CSV/JSON output.
Ref: OPTIMIZATION_STRATEGY.md, auditor review 2026-02-10
2026-02-10 22:15:06 +00:00
|
|
|
|
|
2026-02-11 13:33:09 +00:00
|
|
|
|
def solve(self, trial: TrialInput) -> TrialResult: ...
|
|
|
|
|
|
def close(self) -> None: ...
|
feat(hydrotech-beam): Phase 1 LHS DoE study code
Implements the optimization study code for Phase 1 (LHS DoE) of the
Hydrotech Beam structural optimization.
Files added:
- run_doe.py: Main entry point — Optuna study with SQLite persistence,
Deb's feasibility rules, CSV/JSON export, Phase 1→2 gate check
- sampling.py: 50-point LHS via scipy.stats.qmc with stratified integer
sampling ensuring all 11 hole_count levels (5-15) are covered
- geometric_checks.py: Pre-flight feasibility filter — hole overlap
(corrected formula: span/(n-1) - d ≥ 30mm) and web clearance checks
- nx_interface.py: NX automation module with stub solver for development
and NXOpen template for Windows/dalidou integration
- requirements.txt: optuna, scipy, numpy, pandas
Key design decisions:
- Baseline enqueued as Trial 0 (LAC lesson)
- All 4 DV expression names from binary introspection (exact spelling)
- Pre-flight geometric filter saves compute and prevents NX crashes
- No surrogates (LAC lesson: direct FEA via TPE beats surrogate+L-BFGS)
- SQLite persistence enables resume after interruption
Tested end-to-end with stub solver: 51 trials, 12 geometric rejects,
39 solved, correct CSV/JSON output.
Ref: OPTIMIZATION_STRATEGY.md, auditor review 2026-02-10
2026-02-10 22:15:06 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
2026-02-11 13:33:09 +00:00
|
|
|
|
# Factory
|
feat(hydrotech-beam): Phase 1 LHS DoE study code
Implements the optimization study code for Phase 1 (LHS DoE) of the
Hydrotech Beam structural optimization.
Files added:
- run_doe.py: Main entry point — Optuna study with SQLite persistence,
Deb's feasibility rules, CSV/JSON export, Phase 1→2 gate check
- sampling.py: 50-point LHS via scipy.stats.qmc with stratified integer
sampling ensuring all 11 hole_count levels (5-15) are covered
- geometric_checks.py: Pre-flight feasibility filter — hole overlap
(corrected formula: span/(n-1) - d ≥ 30mm) and web clearance checks
- nx_interface.py: NX automation module with stub solver for development
and NXOpen template for Windows/dalidou integration
- requirements.txt: optuna, scipy, numpy, pandas
Key design decisions:
- Baseline enqueued as Trial 0 (LAC lesson)
- All 4 DV expression names from binary introspection (exact spelling)
- Pre-flight geometric filter saves compute and prevents NX crashes
- No surrogates (LAC lesson: direct FEA via TPE beats surrogate+L-BFGS)
- SQLite persistence enables resume after interruption
Tested end-to-end with stub solver: 51 trials, 12 geometric rejects,
39 solved, correct CSV/JSON output.
Ref: OPTIMIZATION_STRATEGY.md, auditor review 2026-02-10
2026-02-10 22:15:06 +00:00
|
|
|
|
# ---------------------------------------------------------------------------
|
2026-02-11 13:33:09 +00:00
|
|
|
|
def create_solver(backend: str = "stub", **kwargs: Any) -> NXSolverInterface:
|
|
|
|
|
|
"""Create the appropriate solver backend.
|
feat(hydrotech-beam): Phase 1 LHS DoE study code
Implements the optimization study code for Phase 1 (LHS DoE) of the
Hydrotech Beam structural optimization.
Files added:
- run_doe.py: Main entry point — Optuna study with SQLite persistence,
Deb's feasibility rules, CSV/JSON export, Phase 1→2 gate check
- sampling.py: 50-point LHS via scipy.stats.qmc with stratified integer
sampling ensuring all 11 hole_count levels (5-15) are covered
- geometric_checks.py: Pre-flight feasibility filter — hole overlap
(corrected formula: span/(n-1) - d ≥ 30mm) and web clearance checks
- nx_interface.py: NX automation module with stub solver for development
and NXOpen template for Windows/dalidou integration
- requirements.txt: optuna, scipy, numpy, pandas
Key design decisions:
- Baseline enqueued as Trial 0 (LAC lesson)
- All 4 DV expression names from binary introspection (exact spelling)
- Pre-flight geometric filter saves compute and prevents NX crashes
- No surrogates (LAC lesson: direct FEA via TPE beats surrogate+L-BFGS)
- SQLite persistence enables resume after interruption
Tested end-to-end with stub solver: 51 trials, 12 geometric rejects,
39 solved, correct CSV/JSON output.
Ref: OPTIMIZATION_STRATEGY.md, auditor review 2026-02-10
2026-02-10 22:15:06 +00:00
|
|
|
|
|
2026-02-11 13:33:09 +00:00
|
|
|
|
Args:
|
|
|
|
|
|
backend: "stub" for testing, "nxopen" for real NX runs.
|
|
|
|
|
|
**kwargs: Passed to solver constructor.
|
|
|
|
|
|
For "nxopen":
|
|
|
|
|
|
model_dir: Path to NX model files (required)
|
|
|
|
|
|
nx_version: NX version string (default: "2412")
|
|
|
|
|
|
timeout: Max solve time in seconds (default: 600)
|
|
|
|
|
|
use_iteration_folders: HEEDS-style per-trial folders (default: True)
|
feat(hydrotech-beam): Phase 1 LHS DoE study code
Implements the optimization study code for Phase 1 (LHS DoE) of the
Hydrotech Beam structural optimization.
Files added:
- run_doe.py: Main entry point — Optuna study with SQLite persistence,
Deb's feasibility rules, CSV/JSON export, Phase 1→2 gate check
- sampling.py: 50-point LHS via scipy.stats.qmc with stratified integer
sampling ensuring all 11 hole_count levels (5-15) are covered
- geometric_checks.py: Pre-flight feasibility filter — hole overlap
(corrected formula: span/(n-1) - d ≥ 30mm) and web clearance checks
- nx_interface.py: NX automation module with stub solver for development
and NXOpen template for Windows/dalidou integration
- requirements.txt: optuna, scipy, numpy, pandas
Key design decisions:
- Baseline enqueued as Trial 0 (LAC lesson)
- All 4 DV expression names from binary introspection (exact spelling)
- Pre-flight geometric filter saves compute and prevents NX crashes
- No surrogates (LAC lesson: direct FEA via TPE beats surrogate+L-BFGS)
- SQLite persistence enables resume after interruption
Tested end-to-end with stub solver: 51 trials, 12 geometric rejects,
39 solved, correct CSV/JSON output.
Ref: OPTIMIZATION_STRATEGY.md, auditor review 2026-02-10
2026-02-10 22:15:06 +00:00
|
|
|
|
|
2026-02-11 13:33:09 +00:00
|
|
|
|
Returns:
|
|
|
|
|
|
Solver instance implementing NXSolverInterface.
|
feat(hydrotech-beam): Phase 1 LHS DoE study code
Implements the optimization study code for Phase 1 (LHS DoE) of the
Hydrotech Beam structural optimization.
Files added:
- run_doe.py: Main entry point — Optuna study with SQLite persistence,
Deb's feasibility rules, CSV/JSON export, Phase 1→2 gate check
- sampling.py: 50-point LHS via scipy.stats.qmc with stratified integer
sampling ensuring all 11 hole_count levels (5-15) are covered
- geometric_checks.py: Pre-flight feasibility filter — hole overlap
(corrected formula: span/(n-1) - d ≥ 30mm) and web clearance checks
- nx_interface.py: NX automation module with stub solver for development
and NXOpen template for Windows/dalidou integration
- requirements.txt: optuna, scipy, numpy, pandas
Key design decisions:
- Baseline enqueued as Trial 0 (LAC lesson)
- All 4 DV expression names from binary introspection (exact spelling)
- Pre-flight geometric filter saves compute and prevents NX crashes
- No surrogates (LAC lesson: direct FEA via TPE beats surrogate+L-BFGS)
- SQLite persistence enables resume after interruption
Tested end-to-end with stub solver: 51 trials, 12 geometric rejects,
39 solved, correct CSV/JSON output.
Ref: OPTIMIZATION_STRATEGY.md, auditor review 2026-02-10
2026-02-10 22:15:06 +00:00
|
|
|
|
"""
|
2026-02-11 13:33:09 +00:00
|
|
|
|
if backend == "stub":
|
|
|
|
|
|
return StubSolver(**kwargs)
|
|
|
|
|
|
elif backend == "nxopen":
|
|
|
|
|
|
return AtomizerNXSolver(**kwargs)
|
|
|
|
|
|
else:
|
|
|
|
|
|
raise ValueError(f"Unknown backend: {backend!r}. Use 'stub' or 'nxopen'.")
|
feat(hydrotech-beam): Phase 1 LHS DoE study code
Implements the optimization study code for Phase 1 (LHS DoE) of the
Hydrotech Beam structural optimization.
Files added:
- run_doe.py: Main entry point — Optuna study with SQLite persistence,
Deb's feasibility rules, CSV/JSON export, Phase 1→2 gate check
- sampling.py: 50-point LHS via scipy.stats.qmc with stratified integer
sampling ensuring all 11 hole_count levels (5-15) are covered
- geometric_checks.py: Pre-flight feasibility filter — hole overlap
(corrected formula: span/(n-1) - d ≥ 30mm) and web clearance checks
- nx_interface.py: NX automation module with stub solver for development
and NXOpen template for Windows/dalidou integration
- requirements.txt: optuna, scipy, numpy, pandas
Key design decisions:
- Baseline enqueued as Trial 0 (LAC lesson)
- All 4 DV expression names from binary introspection (exact spelling)
- Pre-flight geometric filter saves compute and prevents NX crashes
- No surrogates (LAC lesson: direct FEA via TPE beats surrogate+L-BFGS)
- SQLite persistence enables resume after interruption
Tested end-to-end with stub solver: 51 trials, 12 geometric rejects,
39 solved, correct CSV/JSON output.
Ref: OPTIMIZATION_STRATEGY.md, auditor review 2026-02-10
2026-02-10 22:15:06 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
2026-02-11 13:33:09 +00:00
|
|
|
|
# Real solver — wraps optimization_engine
|
feat(hydrotech-beam): Phase 1 LHS DoE study code
Implements the optimization study code for Phase 1 (LHS DoE) of the
Hydrotech Beam structural optimization.
Files added:
- run_doe.py: Main entry point — Optuna study with SQLite persistence,
Deb's feasibility rules, CSV/JSON export, Phase 1→2 gate check
- sampling.py: 50-point LHS via scipy.stats.qmc with stratified integer
sampling ensuring all 11 hole_count levels (5-15) are covered
- geometric_checks.py: Pre-flight feasibility filter — hole overlap
(corrected formula: span/(n-1) - d ≥ 30mm) and web clearance checks
- nx_interface.py: NX automation module with stub solver for development
and NXOpen template for Windows/dalidou integration
- requirements.txt: optuna, scipy, numpy, pandas
Key design decisions:
- Baseline enqueued as Trial 0 (LAC lesson)
- All 4 DV expression names from binary introspection (exact spelling)
- Pre-flight geometric filter saves compute and prevents NX crashes
- No surrogates (LAC lesson: direct FEA via TPE beats surrogate+L-BFGS)
- SQLite persistence enables resume after interruption
Tested end-to-end with stub solver: 51 trials, 12 geometric rejects,
39 solved, correct CSV/JSON output.
Ref: OPTIMIZATION_STRATEGY.md, auditor review 2026-02-10
2026-02-10 22:15:06 +00:00
|
|
|
|
# ---------------------------------------------------------------------------
|
2026-02-11 13:33:09 +00:00
|
|
|
|
class AtomizerNXSolver:
|
|
|
|
|
|
"""Production solver using Atomizer's optimization_engine.
|
|
|
|
|
|
|
|
|
|
|
|
Pipeline (proven in SAT3):
|
|
|
|
|
|
1. Create iteration folder with fresh model copies
|
|
|
|
|
|
2. Write .exp file with updated expression values
|
|
|
|
|
|
3. NX journal: open .sim → import .exp → update geometry → solve SOL 101
|
|
|
|
|
|
4. Journal writes mass to _temp_mass.txt
|
|
|
|
|
|
5. pyNastran reads .op2 → extract displacement + stress
|
|
|
|
|
|
6. Return results
|
feat(hydrotech-beam): Phase 1 LHS DoE study code
Implements the optimization study code for Phase 1 (LHS DoE) of the
Hydrotech Beam structural optimization.
Files added:
- run_doe.py: Main entry point — Optuna study with SQLite persistence,
Deb's feasibility rules, CSV/JSON export, Phase 1→2 gate check
- sampling.py: 50-point LHS via scipy.stats.qmc with stratified integer
sampling ensuring all 11 hole_count levels (5-15) are covered
- geometric_checks.py: Pre-flight feasibility filter — hole overlap
(corrected formula: span/(n-1) - d ≥ 30mm) and web clearance checks
- nx_interface.py: NX automation module with stub solver for development
and NXOpen template for Windows/dalidou integration
- requirements.txt: optuna, scipy, numpy, pandas
Key design decisions:
- Baseline enqueued as Trial 0 (LAC lesson)
- All 4 DV expression names from binary introspection (exact spelling)
- Pre-flight geometric filter saves compute and prevents NX crashes
- No surrogates (LAC lesson: direct FEA via TPE beats surrogate+L-BFGS)
- SQLite persistence enables resume after interruption
Tested end-to-end with stub solver: 51 trials, 12 geometric rejects,
39 solved, correct CSV/JSON output.
Ref: OPTIMIZATION_STRATEGY.md, auditor review 2026-02-10
2026-02-10 22:15:06 +00:00
|
|
|
|
"""
|
|
|
|
|
|
|
2026-02-10 23:26:51 +00:00
|
|
|
|
def __init__(
|
|
|
|
|
|
self,
|
2026-02-11 13:33:09 +00:00
|
|
|
|
model_dir: str | Path = ".",
|
2026-02-11 15:54:32 +00:00
|
|
|
|
nx_version: str = "2512",
|
2026-02-10 23:26:51 +00:00
|
|
|
|
timeout: int = 600,
|
2026-02-11 14:35:56 +00:00
|
|
|
|
use_iteration_folders: bool = False, # Disabled: copied NX files break internal references
|
2026-02-11 13:33:09 +00:00
|
|
|
|
):
|
|
|
|
|
|
model_dir = Path(model_dir)
|
|
|
|
|
|
if not model_dir.exists():
|
|
|
|
|
|
raise FileNotFoundError(f"Model directory not found: {model_dir}")
|
|
|
|
|
|
|
2026-02-11 14:39:10 +00:00
|
|
|
|
self.model_dir = model_dir.resolve()
|
2026-02-11 13:33:09 +00:00
|
|
|
|
self.nx_version = nx_version
|
2026-02-10 23:26:51 +00:00
|
|
|
|
self.timeout = timeout
|
2026-02-11 13:33:09 +00:00
|
|
|
|
self.use_iteration_folders = use_iteration_folders
|
|
|
|
|
|
self._iteration = 0
|
|
|
|
|
|
|
2026-02-11 14:39:10 +00:00
|
|
|
|
self.study_dir = Path(__file__).parent.resolve()
|
2026-02-11 15:05:18 +00:00
|
|
|
|
|
|
|
|
|
|
# Iteration output folders (solver outputs + params, NOT model copies)
|
|
|
|
|
|
self.iterations_dir = self.study_dir / "iterations"
|
|
|
|
|
|
self.iterations_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
|
|
|
|
|
|
# One-time backup of master model (restored before each trial for isolation)
|
|
|
|
|
|
# NX .sim files store absolute internal references to .fem/.prt — copying
|
|
|
|
|
|
# them to iteration folders breaks these references. Instead we solve on
|
|
|
|
|
|
# the master model in-place and archive outputs to iteration folders.
|
|
|
|
|
|
import shutil
|
|
|
|
|
|
self._backup_dir = self.study_dir / "_model_backup"
|
|
|
|
|
|
if not self._backup_dir.exists():
|
|
|
|
|
|
logger.info("Creating master model backup at %s", self._backup_dir)
|
|
|
|
|
|
self._backup_dir.mkdir(parents=True)
|
|
|
|
|
|
for f in model_dir.iterdir():
|
|
|
|
|
|
if f.is_file():
|
|
|
|
|
|
shutil.copy2(f, self._backup_dir / f.name)
|
|
|
|
|
|
n_backed = len(list(self._backup_dir.iterdir()))
|
|
|
|
|
|
logger.info("Backed up %d model files", n_backed)
|
|
|
|
|
|
else:
|
|
|
|
|
|
logger.info("Using existing model backup at %s", self._backup_dir)
|
2026-02-11 14:42:07 +00:00
|
|
|
|
|
2026-02-11 13:33:09 +00:00
|
|
|
|
# Find the .sim file
|
2026-02-11 15:24:20 +00:00
|
|
|
|
# Use resolved model_dir for all path operations (NX needs clean absolute paths)
|
|
|
|
|
|
sim_files = list(self.model_dir.glob("*.sim"))
|
2026-02-11 13:33:09 +00:00
|
|
|
|
if not sim_files:
|
2026-02-11 15:24:20 +00:00
|
|
|
|
raise FileNotFoundError(f"No .sim file found in {self.model_dir}")
|
|
|
|
|
|
self.sim_file = sim_files[0] # Already absolute (from resolved parent)
|
2026-02-11 13:33:09 +00:00
|
|
|
|
logger.info("SIM file: %s", self.sim_file.name)
|
2026-02-11 15:24:20 +00:00
|
|
|
|
logger.info("SIM path: %s", self.sim_file)
|
2026-02-11 13:33:09 +00:00
|
|
|
|
|
|
|
|
|
|
# Find the .prt file (for mass extraction)
|
2026-02-11 15:24:20 +00:00
|
|
|
|
prt_files = [f for f in self.model_dir.glob("*.prt") if "_i." not in f.name]
|
2026-02-11 13:33:09 +00:00
|
|
|
|
if not prt_files:
|
2026-02-11 15:24:20 +00:00
|
|
|
|
raise FileNotFoundError(f"No .prt file found in {self.model_dir}")
|
2026-02-11 13:33:09 +00:00
|
|
|
|
self.prt_file = prt_files[0]
|
|
|
|
|
|
logger.info("PRT file: %s", self.prt_file.name)
|
|
|
|
|
|
|
|
|
|
|
|
# Add Atomizer root to path for imports
|
|
|
|
|
|
atomizer_root = self._find_atomizer_root()
|
|
|
|
|
|
if atomizer_root and str(atomizer_root) not in sys.path:
|
|
|
|
|
|
sys.path.insert(0, str(atomizer_root))
|
|
|
|
|
|
logger.info("Added Atomizer root to path: %s", atomizer_root)
|
|
|
|
|
|
|
|
|
|
|
|
# Import optimization_engine components
|
2026-02-10 23:26:51 +00:00
|
|
|
|
try:
|
|
|
|
|
|
from optimization_engine.nx.solver import NXSolver
|
2026-02-11 13:33:09 +00:00
|
|
|
|
self._nx_solver = NXSolver(
|
|
|
|
|
|
nastran_version=nx_version,
|
|
|
|
|
|
timeout=timeout,
|
|
|
|
|
|
use_journal=True,
|
|
|
|
|
|
study_name="hydrotech_beam_doe",
|
2026-02-11 14:35:56 +00:00
|
|
|
|
use_iteration_folders=False, # Direct model: avoid broken NX file references
|
2026-02-11 13:33:09 +00:00
|
|
|
|
master_model_dir=model_dir,
|
|
|
|
|
|
)
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
"NXSolver initialized (NX %s, timeout=%ds, journal mode)",
|
|
|
|
|
|
nx_version, timeout,
|
|
|
|
|
|
)
|
|
|
|
|
|
except ImportError as e:
|
|
|
|
|
|
raise ImportError(
|
|
|
|
|
|
f"Could not import optimization_engine. "
|
|
|
|
|
|
f"Ensure Atomizer repo root is on PYTHONPATH.\n"
|
|
|
|
|
|
f"Error: {e}"
|
|
|
|
|
|
) from e
|
|
|
|
|
|
|
|
|
|
|
|
# Import extractors
|
|
|
|
|
|
try:
|
feat(hydrotech-beam): complete NXOpenSolver.evaluate() implementation
Complete the NXOpenSolver class in nx_interface.py with production-ready
evaluate() and close() methods, following proven patterns from
M1_Mirror/SAT3_Trajectory_V7.
Pipeline per trial:
1. NXSolver.create_iteration_folder() — HEEDS-style isolation with fresh
model copies + params.exp generation
2. NXSolver.run_simulation() — journal-based solve via run_journal.exe
(handles expression import, geometry rebuild, FEM update, SOL 101)
3. extract_displacement() — max displacement from OP2
4. extract_solid_stress() — max von Mises with auto-detect element type
(tries all solid types first, falls back to CQUAD4 shell)
5. extract_mass_from_expression() — reads _temp_mass.txt from journal,
with _temp_part_properties.json fallback
Key decisions:
- Auto-detect element type for stress (element_type=None) instead of
hardcoding CQUAD4 — the beam model may use solid or shell elements
- Lazy solver init on first evaluate() call for clean error handling
- OP2 fallback path: tries solver result first, then expected naming
convention (beam_sim1-solution_1.op2)
- Mass fallback: _temp_mass.txt -> _temp_part_properties.json
- LAC-compliant close(): only uses session_manager.cleanup_stale_locks(),
never kills NX processes directly
Expression mapping (confirmed from binary introspection):
- beam_half_core_thickness, beam_face_thickness, holes_diameter, hole_count
- Mass output: p173 (body_property147.mass, kg)
Refs: OP_09, OPTIMIZATION_STRATEGY.md §8.2
2026-02-11 01:11:09 +00:00
|
|
|
|
from optimization_engine.extractors.extract_displacement import (
|
|
|
|
|
|
extract_displacement,
|
|
|
|
|
|
)
|
|
|
|
|
|
from optimization_engine.extractors.extract_von_mises_stress import (
|
|
|
|
|
|
extract_solid_stress,
|
|
|
|
|
|
)
|
|
|
|
|
|
from optimization_engine.extractors.extract_mass_from_expression import (
|
|
|
|
|
|
extract_mass_from_expression,
|
|
|
|
|
|
)
|
2026-02-11 13:33:09 +00:00
|
|
|
|
self._extract_displacement = extract_displacement
|
|
|
|
|
|
self._extract_stress = extract_solid_stress
|
|
|
|
|
|
self._extract_mass = extract_mass_from_expression
|
|
|
|
|
|
logger.info("Extractors loaded: displacement, von_mises, mass")
|
2026-02-10 23:26:51 +00:00
|
|
|
|
except ImportError as e:
|
|
|
|
|
|
raise ImportError(
|
2026-02-11 13:33:09 +00:00
|
|
|
|
f"Could not import extractors from optimization_engine.\n"
|
|
|
|
|
|
f"Error: {e}"
|
feat(hydrotech-beam): complete NXOpenSolver.evaluate() implementation
Complete the NXOpenSolver class in nx_interface.py with production-ready
evaluate() and close() methods, following proven patterns from
M1_Mirror/SAT3_Trajectory_V7.
Pipeline per trial:
1. NXSolver.create_iteration_folder() — HEEDS-style isolation with fresh
model copies + params.exp generation
2. NXSolver.run_simulation() — journal-based solve via run_journal.exe
(handles expression import, geometry rebuild, FEM update, SOL 101)
3. extract_displacement() — max displacement from OP2
4. extract_solid_stress() — max von Mises with auto-detect element type
(tries all solid types first, falls back to CQUAD4 shell)
5. extract_mass_from_expression() — reads _temp_mass.txt from journal,
with _temp_part_properties.json fallback
Key decisions:
- Auto-detect element type for stress (element_type=None) instead of
hardcoding CQUAD4 — the beam model may use solid or shell elements
- Lazy solver init on first evaluate() call for clean error handling
- OP2 fallback path: tries solver result first, then expected naming
convention (beam_sim1-solution_1.op2)
- Mass fallback: _temp_mass.txt -> _temp_part_properties.json
- LAC-compliant close(): only uses session_manager.cleanup_stale_locks(),
never kills NX processes directly
Expression mapping (confirmed from binary introspection):
- beam_half_core_thickness, beam_face_thickness, holes_diameter, hole_count
- Mass output: p173 (body_property147.mass, kg)
Refs: OP_09, OPTIMIZATION_STRATEGY.md §8.2
2026-02-11 01:11:09 +00:00
|
|
|
|
) from e
|
feat(hydrotech-beam): Phase 1 LHS DoE study code
Implements the optimization study code for Phase 1 (LHS DoE) of the
Hydrotech Beam structural optimization.
Files added:
- run_doe.py: Main entry point — Optuna study with SQLite persistence,
Deb's feasibility rules, CSV/JSON export, Phase 1→2 gate check
- sampling.py: 50-point LHS via scipy.stats.qmc with stratified integer
sampling ensuring all 11 hole_count levels (5-15) are covered
- geometric_checks.py: Pre-flight feasibility filter — hole overlap
(corrected formula: span/(n-1) - d ≥ 30mm) and web clearance checks
- nx_interface.py: NX automation module with stub solver for development
and NXOpen template for Windows/dalidou integration
- requirements.txt: optuna, scipy, numpy, pandas
Key design decisions:
- Baseline enqueued as Trial 0 (LAC lesson)
- All 4 DV expression names from binary introspection (exact spelling)
- Pre-flight geometric filter saves compute and prevents NX crashes
- No surrogates (LAC lesson: direct FEA via TPE beats surrogate+L-BFGS)
- SQLite persistence enables resume after interruption
Tested end-to-end with stub solver: 51 trials, 12 geometric rejects,
39 solved, correct CSV/JSON output.
Ref: OPTIMIZATION_STRATEGY.md, auditor review 2026-02-10
2026-02-10 22:15:06 +00:00
|
|
|
|
|
2026-02-11 13:33:09 +00:00
|
|
|
|
def _find_atomizer_root(self) -> Optional[Path]:
|
|
|
|
|
|
"""Walk up from model_dir to find the Atomizer repo root."""
|
|
|
|
|
|
# Look for optimization_engine directory
|
|
|
|
|
|
candidate = self.model_dir
|
|
|
|
|
|
for _ in range(10):
|
|
|
|
|
|
candidate = candidate.parent
|
|
|
|
|
|
if (candidate / "optimization_engine").is_dir():
|
|
|
|
|
|
return candidate
|
|
|
|
|
|
if candidate == candidate.parent:
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
# Fallback: common paths
|
|
|
|
|
|
for path in [
|
|
|
|
|
|
Path("C:/Users/antoi/Atomizer"),
|
|
|
|
|
|
Path("/home/papa/repos/Atomizer"),
|
|
|
|
|
|
]:
|
|
|
|
|
|
if (path / "optimization_engine").is_dir():
|
|
|
|
|
|
return path
|
|
|
|
|
|
|
|
|
|
|
|
logger.warning("Could not find Atomizer root with optimization_engine/")
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def solve(self, trial: TrialInput) -> TrialResult:
|
|
|
|
|
|
"""Run a single trial through the NX pipeline.
|
feat(hydrotech-beam): Phase 1 LHS DoE study code
Implements the optimization study code for Phase 1 (LHS DoE) of the
Hydrotech Beam structural optimization.
Files added:
- run_doe.py: Main entry point — Optuna study with SQLite persistence,
Deb's feasibility rules, CSV/JSON export, Phase 1→2 gate check
- sampling.py: 50-point LHS via scipy.stats.qmc with stratified integer
sampling ensuring all 11 hole_count levels (5-15) are covered
- geometric_checks.py: Pre-flight feasibility filter — hole overlap
(corrected formula: span/(n-1) - d ≥ 30mm) and web clearance checks
- nx_interface.py: NX automation module with stub solver for development
and NXOpen template for Windows/dalidou integration
- requirements.txt: optuna, scipy, numpy, pandas
Key design decisions:
- Baseline enqueued as Trial 0 (LAC lesson)
- All 4 DV expression names from binary introspection (exact spelling)
- Pre-flight geometric filter saves compute and prevents NX crashes
- No surrogates (LAC lesson: direct FEA via TPE beats surrogate+L-BFGS)
- SQLite persistence enables resume after interruption
Tested end-to-end with stub solver: 51 trials, 12 geometric rejects,
39 solved, correct CSV/JSON output.
Ref: OPTIMIZATION_STRATEGY.md, auditor review 2026-02-10
2026-02-10 22:15:06 +00:00
|
|
|
|
|
2026-02-10 23:26:51 +00:00
|
|
|
|
Args:
|
2026-02-11 13:33:09 +00:00
|
|
|
|
trial: Design variable values.
|
feat(hydrotech-beam): Phase 1 LHS DoE study code
Implements the optimization study code for Phase 1 (LHS DoE) of the
Hydrotech Beam structural optimization.
Files added:
- run_doe.py: Main entry point — Optuna study with SQLite persistence,
Deb's feasibility rules, CSV/JSON export, Phase 1→2 gate check
- sampling.py: 50-point LHS via scipy.stats.qmc with stratified integer
sampling ensuring all 11 hole_count levels (5-15) are covered
- geometric_checks.py: Pre-flight feasibility filter — hole overlap
(corrected formula: span/(n-1) - d ≥ 30mm) and web clearance checks
- nx_interface.py: NX automation module with stub solver for development
and NXOpen template for Windows/dalidou integration
- requirements.txt: optuna, scipy, numpy, pandas
Key design decisions:
- Baseline enqueued as Trial 0 (LAC lesson)
- All 4 DV expression names from binary introspection (exact spelling)
- Pre-flight geometric filter saves compute and prevents NX crashes
- No surrogates (LAC lesson: direct FEA via TPE beats surrogate+L-BFGS)
- SQLite persistence enables resume after interruption
Tested end-to-end with stub solver: 51 trials, 12 geometric rejects,
39 solved, correct CSV/JSON output.
Ref: OPTIMIZATION_STRATEGY.md, auditor review 2026-02-10
2026-02-10 22:15:06 +00:00
|
|
|
|
|
2026-02-10 23:26:51 +00:00
|
|
|
|
Returns:
|
2026-02-11 13:33:09 +00:00
|
|
|
|
TrialResult with mass, displacement, stress (or failure info).
|
feat(hydrotech-beam): Phase 1 LHS DoE study code
Implements the optimization study code for Phase 1 (LHS DoE) of the
Hydrotech Beam structural optimization.
Files added:
- run_doe.py: Main entry point — Optuna study with SQLite persistence,
Deb's feasibility rules, CSV/JSON export, Phase 1→2 gate check
- sampling.py: 50-point LHS via scipy.stats.qmc with stratified integer
sampling ensuring all 11 hole_count levels (5-15) are covered
- geometric_checks.py: Pre-flight feasibility filter — hole overlap
(corrected formula: span/(n-1) - d ≥ 30mm) and web clearance checks
- nx_interface.py: NX automation module with stub solver for development
and NXOpen template for Windows/dalidou integration
- requirements.txt: optuna, scipy, numpy, pandas
Key design decisions:
- Baseline enqueued as Trial 0 (LAC lesson)
- All 4 DV expression names from binary introspection (exact spelling)
- Pre-flight geometric filter saves compute and prevents NX crashes
- No surrogates (LAC lesson: direct FEA via TPE beats surrogate+L-BFGS)
- SQLite persistence enables resume after interruption
Tested end-to-end with stub solver: 51 trials, 12 geometric rejects,
39 solved, correct CSV/JSON output.
Ref: OPTIMIZATION_STRATEGY.md, auditor review 2026-02-10
2026-02-10 22:15:06 +00:00
|
|
|
|
"""
|
2026-02-11 13:33:09 +00:00
|
|
|
|
self._iteration += 1
|
|
|
|
|
|
start_time = time.time()
|
|
|
|
|
|
|
|
|
|
|
|
# Build expression update dict
|
|
|
|
|
|
expressions: Dict[str, float] = {
|
|
|
|
|
|
EXPR_HALF_CORE_THICKNESS: trial.beam_half_core_thickness,
|
|
|
|
|
|
EXPR_FACE_THICKNESS: trial.beam_face_thickness,
|
|
|
|
|
|
EXPR_HOLES_DIAMETER: trial.holes_diameter,
|
|
|
|
|
|
EXPR_HOLE_COUNT: float(trial.hole_count), # NX expects float in .exp
|
|
|
|
|
|
}
|
feat(hydrotech-beam): Phase 1 LHS DoE study code
Implements the optimization study code for Phase 1 (LHS DoE) of the
Hydrotech Beam structural optimization.
Files added:
- run_doe.py: Main entry point — Optuna study with SQLite persistence,
Deb's feasibility rules, CSV/JSON export, Phase 1→2 gate check
- sampling.py: 50-point LHS via scipy.stats.qmc with stratified integer
sampling ensuring all 11 hole_count levels (5-15) are covered
- geometric_checks.py: Pre-flight feasibility filter — hole overlap
(corrected formula: span/(n-1) - d ≥ 30mm) and web clearance checks
- nx_interface.py: NX automation module with stub solver for development
and NXOpen template for Windows/dalidou integration
- requirements.txt: optuna, scipy, numpy, pandas
Key design decisions:
- Baseline enqueued as Trial 0 (LAC lesson)
- All 4 DV expression names from binary introspection (exact spelling)
- Pre-flight geometric filter saves compute and prevents NX crashes
- No surrogates (LAC lesson: direct FEA via TPE beats surrogate+L-BFGS)
- SQLite persistence enables resume after interruption
Tested end-to-end with stub solver: 51 trials, 12 geometric rejects,
39 solved, correct CSV/JSON output.
Ref: OPTIMIZATION_STRATEGY.md, auditor review 2026-02-10
2026-02-10 22:15:06 +00:00
|
|
|
|
|
feat(hydrotech-beam): complete NXOpenSolver.evaluate() implementation
Complete the NXOpenSolver class in nx_interface.py with production-ready
evaluate() and close() methods, following proven patterns from
M1_Mirror/SAT3_Trajectory_V7.
Pipeline per trial:
1. NXSolver.create_iteration_folder() — HEEDS-style isolation with fresh
model copies + params.exp generation
2. NXSolver.run_simulation() — journal-based solve via run_journal.exe
(handles expression import, geometry rebuild, FEM update, SOL 101)
3. extract_displacement() — max displacement from OP2
4. extract_solid_stress() — max von Mises with auto-detect element type
(tries all solid types first, falls back to CQUAD4 shell)
5. extract_mass_from_expression() — reads _temp_mass.txt from journal,
with _temp_part_properties.json fallback
Key decisions:
- Auto-detect element type for stress (element_type=None) instead of
hardcoding CQUAD4 — the beam model may use solid or shell elements
- Lazy solver init on first evaluate() call for clean error handling
- OP2 fallback path: tries solver result first, then expected naming
convention (beam_sim1-solution_1.op2)
- Mass fallback: _temp_mass.txt -> _temp_part_properties.json
- LAC-compliant close(): only uses session_manager.cleanup_stale_locks(),
never kills NX processes directly
Expression mapping (confirmed from binary introspection):
- beam_half_core_thickness, beam_face_thickness, holes_diameter, hole_count
- Mass output: p173 (body_property147.mass, kg)
Refs: OP_09, OPTIMIZATION_STRATEGY.md §8.2
2026-02-11 01:11:09 +00:00
|
|
|
|
logger.info(
|
2026-02-11 13:33:09 +00:00
|
|
|
|
"Trial %d: core=%.2f face=%.2f dia=%.1f count=%d",
|
|
|
|
|
|
self._iteration,
|
|
|
|
|
|
trial.beam_half_core_thickness,
|
|
|
|
|
|
trial.beam_face_thickness,
|
|
|
|
|
|
trial.holes_diameter,
|
|
|
|
|
|
trial.hole_count,
|
feat(hydrotech-beam): complete NXOpenSolver.evaluate() implementation
Complete the NXOpenSolver class in nx_interface.py with production-ready
evaluate() and close() methods, following proven patterns from
M1_Mirror/SAT3_Trajectory_V7.
Pipeline per trial:
1. NXSolver.create_iteration_folder() — HEEDS-style isolation with fresh
model copies + params.exp generation
2. NXSolver.run_simulation() — journal-based solve via run_journal.exe
(handles expression import, geometry rebuild, FEM update, SOL 101)
3. extract_displacement() — max displacement from OP2
4. extract_solid_stress() — max von Mises with auto-detect element type
(tries all solid types first, falls back to CQUAD4 shell)
5. extract_mass_from_expression() — reads _temp_mass.txt from journal,
with _temp_part_properties.json fallback
Key decisions:
- Auto-detect element type for stress (element_type=None) instead of
hardcoding CQUAD4 — the beam model may use solid or shell elements
- Lazy solver init on first evaluate() call for clean error handling
- OP2 fallback path: tries solver result first, then expected naming
convention (beam_sim1-solution_1.op2)
- Mass fallback: _temp_mass.txt -> _temp_part_properties.json
- LAC-compliant close(): only uses session_manager.cleanup_stale_locks(),
never kills NX processes directly
Expression mapping (confirmed from binary introspection):
- beam_half_core_thickness, beam_face_thickness, holes_diameter, hole_count
- Mass output: p173 (body_property147.mass, kg)
Refs: OP_09, OPTIMIZATION_STRATEGY.md §8.2
2026-02-11 01:11:09 +00:00
|
|
|
|
)
|
feat(hydrotech-beam): Phase 1 LHS DoE study code
Implements the optimization study code for Phase 1 (LHS DoE) of the
Hydrotech Beam structural optimization.
Files added:
- run_doe.py: Main entry point — Optuna study with SQLite persistence,
Deb's feasibility rules, CSV/JSON export, Phase 1→2 gate check
- sampling.py: 50-point LHS via scipy.stats.qmc with stratified integer
sampling ensuring all 11 hole_count levels (5-15) are covered
- geometric_checks.py: Pre-flight feasibility filter — hole overlap
(corrected formula: span/(n-1) - d ≥ 30mm) and web clearance checks
- nx_interface.py: NX automation module with stub solver for development
and NXOpen template for Windows/dalidou integration
- requirements.txt: optuna, scipy, numpy, pandas
Key design decisions:
- Baseline enqueued as Trial 0 (LAC lesson)
- All 4 DV expression names from binary introspection (exact spelling)
- Pre-flight geometric filter saves compute and prevents NX crashes
- No surrogates (LAC lesson: direct FEA via TPE beats surrogate+L-BFGS)
- SQLite persistence enables resume after interruption
Tested end-to-end with stub solver: 51 trials, 12 geometric rejects,
39 solved, correct CSV/JSON output.
Ref: OPTIMIZATION_STRATEGY.md, auditor review 2026-02-10
2026-02-10 22:15:06 +00:00
|
|
|
|
|
2026-02-11 15:05:18 +00:00
|
|
|
|
# Create iteration output folder
|
|
|
|
|
|
iter_dir = self.iterations_dir / f"iter{self._iteration:03d}"
|
|
|
|
|
|
iter_dir.mkdir(parents=True, exist_ok=True)
|
2026-02-11 14:48:05 +00:00
|
|
|
|
|
2026-02-11 15:05:18 +00:00
|
|
|
|
try:
|
|
|
|
|
|
# Step 0: Restore master model from backup (clean state)
|
|
|
|
|
|
import shutil
|
|
|
|
|
|
import json
|
|
|
|
|
|
restored = 0
|
|
|
|
|
|
for bf in self._backup_dir.iterdir():
|
|
|
|
|
|
if bf.is_file():
|
|
|
|
|
|
shutil.copy2(bf, self.model_dir / bf.name)
|
|
|
|
|
|
restored += 1
|
|
|
|
|
|
logger.info("Restored %d model files from backup", restored)
|
2026-02-11 14:39:10 +00:00
|
|
|
|
|
|
|
|
|
|
# Save trial params to iteration folder
|
|
|
|
|
|
params_file = iter_dir / "params.json"
|
|
|
|
|
|
params_file.write_text(json.dumps({
|
|
|
|
|
|
"iteration": self._iteration,
|
|
|
|
|
|
"expressions": expressions,
|
|
|
|
|
|
"trial_input": {
|
|
|
|
|
|
"beam_half_core_thickness": trial.beam_half_core_thickness,
|
|
|
|
|
|
"beam_face_thickness": trial.beam_face_thickness,
|
|
|
|
|
|
"holes_diameter": trial.holes_diameter,
|
|
|
|
|
|
"hole_count": trial.hole_count,
|
|
|
|
|
|
},
|
|
|
|
|
|
}, indent=2))
|
|
|
|
|
|
|
2026-02-11 15:05:18 +00:00
|
|
|
|
# Also write .exp file to iteration folder (import into NX to recreate)
|
|
|
|
|
|
exp_file = iter_dir / "params.exp"
|
|
|
|
|
|
with open(exp_file, "w") as f:
|
|
|
|
|
|
for name, val in expressions.items():
|
|
|
|
|
|
unit = "Constant" if name in ("hole_count",) else "MilliMeter"
|
|
|
|
|
|
f.write(f'{name}={val} [{unit}]\n')
|
|
|
|
|
|
|
|
|
|
|
|
# Step 1: Solve on MASTER model (NX internal references intact)
|
|
|
|
|
|
sim_file = self.sim_file
|
|
|
|
|
|
prt_file = self.prt_file
|
2026-02-11 13:33:09 +00:00
|
|
|
|
solve_result = self._nx_solver.run_simulation(
|
|
|
|
|
|
sim_file=sim_file,
|
2026-02-11 15:05:18 +00:00
|
|
|
|
working_dir=self.model_dir,
|
feat(hydrotech-beam): complete NXOpenSolver.evaluate() implementation
Complete the NXOpenSolver class in nx_interface.py with production-ready
evaluate() and close() methods, following proven patterns from
M1_Mirror/SAT3_Trajectory_V7.
Pipeline per trial:
1. NXSolver.create_iteration_folder() — HEEDS-style isolation with fresh
model copies + params.exp generation
2. NXSolver.run_simulation() — journal-based solve via run_journal.exe
(handles expression import, geometry rebuild, FEM update, SOL 101)
3. extract_displacement() — max displacement from OP2
4. extract_solid_stress() — max von Mises with auto-detect element type
(tries all solid types first, falls back to CQUAD4 shell)
5. extract_mass_from_expression() — reads _temp_mass.txt from journal,
with _temp_part_properties.json fallback
Key decisions:
- Auto-detect element type for stress (element_type=None) instead of
hardcoding CQUAD4 — the beam model may use solid or shell elements
- Lazy solver init on first evaluate() call for clean error handling
- OP2 fallback path: tries solver result first, then expected naming
convention (beam_sim1-solution_1.op2)
- Mass fallback: _temp_mass.txt -> _temp_part_properties.json
- LAC-compliant close(): only uses session_manager.cleanup_stale_locks(),
never kills NX processes directly
Expression mapping (confirmed from binary introspection):
- beam_half_core_thickness, beam_face_thickness, holes_diameter, hole_count
- Mass output: p173 (body_property147.mass, kg)
Refs: OP_09, OPTIMIZATION_STRATEGY.md §8.2
2026-02-11 01:11:09 +00:00
|
|
|
|
expression_updates=expressions,
|
2026-02-10 23:26:51 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
2026-02-11 13:33:09 +00:00
|
|
|
|
if not solve_result.get("success", False):
|
|
|
|
|
|
errors = solve_result.get("errors", ["Unknown solver error"])
|
2026-02-10 23:26:51 +00:00
|
|
|
|
return TrialResult(
|
|
|
|
|
|
success=False,
|
2026-02-11 13:33:09 +00:00
|
|
|
|
solve_time=time.time() - start_time,
|
|
|
|
|
|
error_message=f"NX solve failed: {'; '.join(errors)}",
|
|
|
|
|
|
iteration_dir=str(iter_dir),
|
2026-02-10 23:26:51 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
feat(hydrotech-beam): complete NXOpenSolver.evaluate() implementation
Complete the NXOpenSolver class in nx_interface.py with production-ready
evaluate() and close() methods, following proven patterns from
M1_Mirror/SAT3_Trajectory_V7.
Pipeline per trial:
1. NXSolver.create_iteration_folder() — HEEDS-style isolation with fresh
model copies + params.exp generation
2. NXSolver.run_simulation() — journal-based solve via run_journal.exe
(handles expression import, geometry rebuild, FEM update, SOL 101)
3. extract_displacement() — max displacement from OP2
4. extract_solid_stress() — max von Mises with auto-detect element type
(tries all solid types first, falls back to CQUAD4 shell)
5. extract_mass_from_expression() — reads _temp_mass.txt from journal,
with _temp_part_properties.json fallback
Key decisions:
- Auto-detect element type for stress (element_type=None) instead of
hardcoding CQUAD4 — the beam model may use solid or shell elements
- Lazy solver init on first evaluate() call for clean error handling
- OP2 fallback path: tries solver result first, then expected naming
convention (beam_sim1-solution_1.op2)
- Mass fallback: _temp_mass.txt -> _temp_part_properties.json
- LAC-compliant close(): only uses session_manager.cleanup_stale_locks(),
never kills NX processes directly
Expression mapping (confirmed from binary introspection):
- beam_half_core_thickness, beam_face_thickness, holes_diameter, hole_count
- Mass output: p173 (body_property147.mass, kg)
Refs: OP_09, OPTIMIZATION_STRATEGY.md §8.2
2026-02-11 01:11:09 +00:00
|
|
|
|
op2_file = solve_result.get("op2_file")
|
2026-02-11 13:33:09 +00:00
|
|
|
|
if not op2_file or not Path(op2_file).exists():
|
2026-02-10 23:26:51 +00:00
|
|
|
|
return TrialResult(
|
|
|
|
|
|
success=False,
|
2026-02-11 13:33:09 +00:00
|
|
|
|
solve_time=time.time() - start_time,
|
|
|
|
|
|
error_message="OP2 file not generated after solve",
|
|
|
|
|
|
iteration_dir=str(iter_dir),
|
2026-02-10 23:26:51 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
2026-02-11 13:33:09 +00:00
|
|
|
|
op2_path = Path(op2_file)
|
|
|
|
|
|
|
|
|
|
|
|
# Step 3: Extract mass from journal temp file
|
2026-02-11 19:02:43 +00:00
|
|
|
|
# The journal writes _temp_mass.txt to working_dir (= model_dir).
|
|
|
|
|
|
# The extractor looks in prt_file.parent (= model_dir). These MUST match.
|
2026-02-10 23:26:51 +00:00
|
|
|
|
try:
|
2026-02-11 13:33:09 +00:00
|
|
|
|
mass_kg = self._extract_mass(prt_file, expression_name=EXPR_MASS)
|
2026-02-11 19:02:43 +00:00
|
|
|
|
except FileNotFoundError:
|
|
|
|
|
|
# Fallback: parse mass from journal stdout captured in solve_result
|
|
|
|
|
|
# The journal prints "[JOURNAL] Mass expression p173 = <value>" or
|
|
|
|
|
|
# "[JOURNAL] MeasureManager mass = <value>"
|
|
|
|
|
|
mass_kg = float("nan")
|
|
|
|
|
|
stdout = solve_result.get("stdout", "")
|
|
|
|
|
|
if stdout:
|
|
|
|
|
|
import re
|
|
|
|
|
|
# Match either extraction method's output
|
|
|
|
|
|
m = re.search(
|
|
|
|
|
|
r'\[JOURNAL\]\s+(?:Mass expression p173|MeasureManager mass)\s*=\s*([0-9.eE+-]+)',
|
|
|
|
|
|
stdout,
|
|
|
|
|
|
)
|
|
|
|
|
|
if m:
|
|
|
|
|
|
try:
|
|
|
|
|
|
mass_kg = float(m.group(1))
|
|
|
|
|
|
logger.info("Mass recovered from journal stdout: %.6f kg", mass_kg)
|
|
|
|
|
|
except ValueError:
|
|
|
|
|
|
pass
|
|
|
|
|
|
if mass_kg != mass_kg: # NaN check
|
|
|
|
|
|
logger.warning(
|
|
|
|
|
|
"Mass temp file not found in %s and no mass in journal stdout",
|
|
|
|
|
|
self.model_dir,
|
|
|
|
|
|
)
|
2026-02-10 23:26:51 +00:00
|
|
|
|
except Exception as e:
|
2026-02-11 13:33:09 +00:00
|
|
|
|
logger.warning("Mass extraction failed: %s", e)
|
|
|
|
|
|
mass_kg = float("nan")
|
|
|
|
|
|
|
|
|
|
|
|
# Step 4: Extract displacement from OP2
|
2026-02-10 23:26:51 +00:00
|
|
|
|
try:
|
2026-02-11 13:33:09 +00:00
|
|
|
|
disp_result = self._extract_displacement(op2_path)
|
|
|
|
|
|
# For cantilever beam, max displacement IS tip displacement
|
|
|
|
|
|
tip_displacement = disp_result["max_displacement"]
|
2026-02-10 23:26:51 +00:00
|
|
|
|
except Exception as e:
|
2026-02-11 13:33:09 +00:00
|
|
|
|
logger.warning("Displacement extraction failed: %s", e)
|
|
|
|
|
|
tip_displacement = float("nan")
|
|
|
|
|
|
|
|
|
|
|
|
# Step 5: Extract max von Mises stress from OP2
|
|
|
|
|
|
# Use shell element extraction (CQUAD4 mesh)
|
|
|
|
|
|
try:
|
|
|
|
|
|
stress_result = self._extract_stress(
|
|
|
|
|
|
op2_path,
|
|
|
|
|
|
element_type="cquad4",
|
|
|
|
|
|
convert_to_mpa=True, # ⚠️ LAC lesson: NX outputs kPa, must convert
|
2026-02-10 23:26:51 +00:00
|
|
|
|
)
|
2026-02-11 13:33:09 +00:00
|
|
|
|
max_vm_stress = stress_result["max_von_mises"]
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.warning("Stress extraction failed: %s", e)
|
|
|
|
|
|
max_vm_stress = float("nan")
|
2026-02-10 23:26:51 +00:00
|
|
|
|
|
2026-02-11 15:05:18 +00:00
|
|
|
|
# Step 6: Archive solver outputs to iteration folder
|
|
|
|
|
|
# Copy OP2, F06, and other solver outputs from models/ dir
|
|
|
|
|
|
for suffix in (".op2", ".f06", ".log", ".dat"):
|
|
|
|
|
|
for src in self.model_dir.glob(f"*{suffix}"):
|
|
|
|
|
|
try:
|
|
|
|
|
|
shutil.copy2(src, iter_dir / src.name)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.warning("Could not archive %s: %s", src.name, e)
|
|
|
|
|
|
|
|
|
|
|
|
# Copy temp files (mass extraction, etc.)
|
|
|
|
|
|
for pattern in ("_temp_*",):
|
|
|
|
|
|
for src in self.model_dir.glob(pattern):
|
|
|
|
|
|
try:
|
|
|
|
|
|
shutil.copy2(src, iter_dir / src.name)
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
2026-02-11 14:48:05 +00:00
|
|
|
|
|
|
|
|
|
|
# Write results summary JSON
|
|
|
|
|
|
results_file = iter_dir / "results.json"
|
|
|
|
|
|
results_file.write_text(json.dumps({
|
|
|
|
|
|
"iteration": self._iteration,
|
|
|
|
|
|
"mass_kg": mass_kg,
|
|
|
|
|
|
"tip_displacement_mm": tip_displacement,
|
|
|
|
|
|
"max_von_mises_mpa": max_vm_stress,
|
|
|
|
|
|
"feasible": tip_displacement <= 10.0 and max_vm_stress <= 130.0,
|
|
|
|
|
|
"op2_file": op2_path.name if op2_path else None,
|
|
|
|
|
|
}, indent=2))
|
2026-02-11 14:39:10 +00:00
|
|
|
|
|
2026-02-11 15:05:18 +00:00
|
|
|
|
logger.info("Archived iter%03d: results + solver outputs", self._iteration)
|
|
|
|
|
|
|
2026-02-11 13:33:09 +00:00
|
|
|
|
elapsed = time.time() - start_time
|
feat(hydrotech-beam): complete NXOpenSolver.evaluate() implementation
Complete the NXOpenSolver class in nx_interface.py with production-ready
evaluate() and close() methods, following proven patterns from
M1_Mirror/SAT3_Trajectory_V7.
Pipeline per trial:
1. NXSolver.create_iteration_folder() — HEEDS-style isolation with fresh
model copies + params.exp generation
2. NXSolver.run_simulation() — journal-based solve via run_journal.exe
(handles expression import, geometry rebuild, FEM update, SOL 101)
3. extract_displacement() — max displacement from OP2
4. extract_solid_stress() — max von Mises with auto-detect element type
(tries all solid types first, falls back to CQUAD4 shell)
5. extract_mass_from_expression() — reads _temp_mass.txt from journal,
with _temp_part_properties.json fallback
Key decisions:
- Auto-detect element type for stress (element_type=None) instead of
hardcoding CQUAD4 — the beam model may use solid or shell elements
- Lazy solver init on first evaluate() call for clean error handling
- OP2 fallback path: tries solver result first, then expected naming
convention (beam_sim1-solution_1.op2)
- Mass fallback: _temp_mass.txt -> _temp_part_properties.json
- LAC-compliant close(): only uses session_manager.cleanup_stale_locks(),
never kills NX processes directly
Expression mapping (confirmed from binary introspection):
- beam_half_core_thickness, beam_face_thickness, holes_diameter, hole_count
- Mass output: p173 (body_property147.mass, kg)
Refs: OP_09, OPTIMIZATION_STRATEGY.md §8.2
2026-02-11 01:11:09 +00:00
|
|
|
|
logger.info(
|
2026-02-11 13:33:09 +00:00
|
|
|
|
"Trial %d complete: mass=%.2f kg, disp=%.3f mm, stress=%.1f MPa (%.1fs)",
|
|
|
|
|
|
self._iteration, mass_kg, tip_displacement, max_vm_stress, elapsed,
|
feat(hydrotech-beam): complete NXOpenSolver.evaluate() implementation
Complete the NXOpenSolver class in nx_interface.py with production-ready
evaluate() and close() methods, following proven patterns from
M1_Mirror/SAT3_Trajectory_V7.
Pipeline per trial:
1. NXSolver.create_iteration_folder() — HEEDS-style isolation with fresh
model copies + params.exp generation
2. NXSolver.run_simulation() — journal-based solve via run_journal.exe
(handles expression import, geometry rebuild, FEM update, SOL 101)
3. extract_displacement() — max displacement from OP2
4. extract_solid_stress() — max von Mises with auto-detect element type
(tries all solid types first, falls back to CQUAD4 shell)
5. extract_mass_from_expression() — reads _temp_mass.txt from journal,
with _temp_part_properties.json fallback
Key decisions:
- Auto-detect element type for stress (element_type=None) instead of
hardcoding CQUAD4 — the beam model may use solid or shell elements
- Lazy solver init on first evaluate() call for clean error handling
- OP2 fallback path: tries solver result first, then expected naming
convention (beam_sim1-solution_1.op2)
- Mass fallback: _temp_mass.txt -> _temp_part_properties.json
- LAC-compliant close(): only uses session_manager.cleanup_stale_locks(),
never kills NX processes directly
Expression mapping (confirmed from binary introspection):
- beam_half_core_thickness, beam_face_thickness, holes_diameter, hole_count
- Mass output: p173 (body_property147.mass, kg)
Refs: OP_09, OPTIMIZATION_STRATEGY.md §8.2
2026-02-11 01:11:09 +00:00
|
|
|
|
)
|
feat(hydrotech-beam): Phase 1 LHS DoE study code
Implements the optimization study code for Phase 1 (LHS DoE) of the
Hydrotech Beam structural optimization.
Files added:
- run_doe.py: Main entry point — Optuna study with SQLite persistence,
Deb's feasibility rules, CSV/JSON export, Phase 1→2 gate check
- sampling.py: 50-point LHS via scipy.stats.qmc with stratified integer
sampling ensuring all 11 hole_count levels (5-15) are covered
- geometric_checks.py: Pre-flight feasibility filter — hole overlap
(corrected formula: span/(n-1) - d ≥ 30mm) and web clearance checks
- nx_interface.py: NX automation module with stub solver for development
and NXOpen template for Windows/dalidou integration
- requirements.txt: optuna, scipy, numpy, pandas
Key design decisions:
- Baseline enqueued as Trial 0 (LAC lesson)
- All 4 DV expression names from binary introspection (exact spelling)
- Pre-flight geometric filter saves compute and prevents NX crashes
- No surrogates (LAC lesson: direct FEA via TPE beats surrogate+L-BFGS)
- SQLite persistence enables resume after interruption
Tested end-to-end with stub solver: 51 trials, 12 geometric rejects,
39 solved, correct CSV/JSON output.
Ref: OPTIMIZATION_STRATEGY.md, auditor review 2026-02-10
2026-02-10 22:15:06 +00:00
|
|
|
|
|
2026-02-10 23:26:51 +00:00
|
|
|
|
return TrialResult(
|
|
|
|
|
|
success=True,
|
2026-02-11 13:33:09 +00:00
|
|
|
|
mass=mass_kg,
|
2026-02-10 23:26:51 +00:00
|
|
|
|
tip_displacement=tip_displacement,
|
2026-02-11 13:33:09 +00:00
|
|
|
|
max_von_mises=max_vm_stress,
|
|
|
|
|
|
solve_time=elapsed,
|
|
|
|
|
|
iteration_dir=str(iter_dir),
|
2026-02-10 23:26:51 +00:00
|
|
|
|
)
|
feat(hydrotech-beam): Phase 1 LHS DoE study code
Implements the optimization study code for Phase 1 (LHS DoE) of the
Hydrotech Beam structural optimization.
Files added:
- run_doe.py: Main entry point — Optuna study with SQLite persistence,
Deb's feasibility rules, CSV/JSON export, Phase 1→2 gate check
- sampling.py: 50-point LHS via scipy.stats.qmc with stratified integer
sampling ensuring all 11 hole_count levels (5-15) are covered
- geometric_checks.py: Pre-flight feasibility filter — hole overlap
(corrected formula: span/(n-1) - d ≥ 30mm) and web clearance checks
- nx_interface.py: NX automation module with stub solver for development
and NXOpen template for Windows/dalidou integration
- requirements.txt: optuna, scipy, numpy, pandas
Key design decisions:
- Baseline enqueued as Trial 0 (LAC lesson)
- All 4 DV expression names from binary introspection (exact spelling)
- Pre-flight geometric filter saves compute and prevents NX crashes
- No surrogates (LAC lesson: direct FEA via TPE beats surrogate+L-BFGS)
- SQLite persistence enables resume after interruption
Tested end-to-end with stub solver: 51 trials, 12 geometric rejects,
39 solved, correct CSV/JSON output.
Ref: OPTIMIZATION_STRATEGY.md, auditor review 2026-02-10
2026-02-10 22:15:06 +00:00
|
|
|
|
|
2026-02-10 23:26:51 +00:00
|
|
|
|
except Exception as e:
|
2026-02-11 13:33:09 +00:00
|
|
|
|
elapsed = time.time() - start_time
|
|
|
|
|
|
logger.error("Trial %d failed: %s", self._iteration, e, exc_info=True)
|
2026-02-10 23:26:51 +00:00
|
|
|
|
return TrialResult(
|
|
|
|
|
|
success=False,
|
2026-02-11 13:33:09 +00:00
|
|
|
|
solve_time=elapsed,
|
|
|
|
|
|
error_message=str(e),
|
|
|
|
|
|
iteration_dir=str(iter_dir) if 'iter_dir' in locals() else None,
|
2026-02-10 23:26:51 +00:00
|
|
|
|
)
|
feat(hydrotech-beam): Phase 1 LHS DoE study code
Implements the optimization study code for Phase 1 (LHS DoE) of the
Hydrotech Beam structural optimization.
Files added:
- run_doe.py: Main entry point — Optuna study with SQLite persistence,
Deb's feasibility rules, CSV/JSON export, Phase 1→2 gate check
- sampling.py: 50-point LHS via scipy.stats.qmc with stratified integer
sampling ensuring all 11 hole_count levels (5-15) are covered
- geometric_checks.py: Pre-flight feasibility filter — hole overlap
(corrected formula: span/(n-1) - d ≥ 30mm) and web clearance checks
- nx_interface.py: NX automation module with stub solver for development
and NXOpen template for Windows/dalidou integration
- requirements.txt: optuna, scipy, numpy, pandas
Key design decisions:
- Baseline enqueued as Trial 0 (LAC lesson)
- All 4 DV expression names from binary introspection (exact spelling)
- Pre-flight geometric filter saves compute and prevents NX crashes
- No surrogates (LAC lesson: direct FEA via TPE beats surrogate+L-BFGS)
- SQLite persistence enables resume after interruption
Tested end-to-end with stub solver: 51 trials, 12 geometric rejects,
39 solved, correct CSV/JSON output.
Ref: OPTIMIZATION_STRATEGY.md, auditor review 2026-02-10
2026-02-10 22:15:06 +00:00
|
|
|
|
|
feat(hydrotech-beam): complete NXOpenSolver.evaluate() implementation
Complete the NXOpenSolver class in nx_interface.py with production-ready
evaluate() and close() methods, following proven patterns from
M1_Mirror/SAT3_Trajectory_V7.
Pipeline per trial:
1. NXSolver.create_iteration_folder() — HEEDS-style isolation with fresh
model copies + params.exp generation
2. NXSolver.run_simulation() — journal-based solve via run_journal.exe
(handles expression import, geometry rebuild, FEM update, SOL 101)
3. extract_displacement() — max displacement from OP2
4. extract_solid_stress() — max von Mises with auto-detect element type
(tries all solid types first, falls back to CQUAD4 shell)
5. extract_mass_from_expression() — reads _temp_mass.txt from journal,
with _temp_part_properties.json fallback
Key decisions:
- Auto-detect element type for stress (element_type=None) instead of
hardcoding CQUAD4 — the beam model may use solid or shell elements
- Lazy solver init on first evaluate() call for clean error handling
- OP2 fallback path: tries solver result first, then expected naming
convention (beam_sim1-solution_1.op2)
- Mass fallback: _temp_mass.txt -> _temp_part_properties.json
- LAC-compliant close(): only uses session_manager.cleanup_stale_locks(),
never kills NX processes directly
Expression mapping (confirmed from binary introspection):
- beam_half_core_thickness, beam_face_thickness, holes_diameter, hole_count
- Mass output: p173 (body_property147.mass, kg)
Refs: OP_09, OPTIMIZATION_STRATEGY.md §8.2
2026-02-11 01:11:09 +00:00
|
|
|
|
def close(self) -> None:
|
2026-02-11 15:05:18 +00:00
|
|
|
|
"""Clean up NX solver resources."""
|
2026-02-11 13:33:09 +00:00
|
|
|
|
logger.info("AtomizerNXSolver closed. %d iterations completed.", self._iteration)
|
feat(hydrotech-beam): Phase 1 LHS DoE study code
Implements the optimization study code for Phase 1 (LHS DoE) of the
Hydrotech Beam structural optimization.
Files added:
- run_doe.py: Main entry point — Optuna study with SQLite persistence,
Deb's feasibility rules, CSV/JSON export, Phase 1→2 gate check
- sampling.py: 50-point LHS via scipy.stats.qmc with stratified integer
sampling ensuring all 11 hole_count levels (5-15) are covered
- geometric_checks.py: Pre-flight feasibility filter — hole overlap
(corrected formula: span/(n-1) - d ≥ 30mm) and web clearance checks
- nx_interface.py: NX automation module with stub solver for development
and NXOpen template for Windows/dalidou integration
- requirements.txt: optuna, scipy, numpy, pandas
Key design decisions:
- Baseline enqueued as Trial 0 (LAC lesson)
- All 4 DV expression names from binary introspection (exact spelling)
- Pre-flight geometric filter saves compute and prevents NX crashes
- No surrogates (LAC lesson: direct FEA via TPE beats surrogate+L-BFGS)
- SQLite persistence enables resume after interruption
Tested end-to-end with stub solver: 51 trials, 12 geometric rejects,
39 solved, correct CSV/JSON output.
Ref: OPTIMIZATION_STRATEGY.md, auditor review 2026-02-10
2026-02-10 22:15:06 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
2026-02-11 13:33:09 +00:00
|
|
|
|
# Stub solver — for development/testing without NX
|
feat(hydrotech-beam): Phase 1 LHS DoE study code
Implements the optimization study code for Phase 1 (LHS DoE) of the
Hydrotech Beam structural optimization.
Files added:
- run_doe.py: Main entry point — Optuna study with SQLite persistence,
Deb's feasibility rules, CSV/JSON export, Phase 1→2 gate check
- sampling.py: 50-point LHS via scipy.stats.qmc with stratified integer
sampling ensuring all 11 hole_count levels (5-15) are covered
- geometric_checks.py: Pre-flight feasibility filter — hole overlap
(corrected formula: span/(n-1) - d ≥ 30mm) and web clearance checks
- nx_interface.py: NX automation module with stub solver for development
and NXOpen template for Windows/dalidou integration
- requirements.txt: optuna, scipy, numpy, pandas
Key design decisions:
- Baseline enqueued as Trial 0 (LAC lesson)
- All 4 DV expression names from binary introspection (exact spelling)
- Pre-flight geometric filter saves compute and prevents NX crashes
- No surrogates (LAC lesson: direct FEA via TPE beats surrogate+L-BFGS)
- SQLite persistence enables resume after interruption
Tested end-to-end with stub solver: 51 trials, 12 geometric rejects,
39 solved, correct CSV/JSON output.
Ref: OPTIMIZATION_STRATEGY.md, auditor review 2026-02-10
2026-02-10 22:15:06 +00:00
|
|
|
|
# ---------------------------------------------------------------------------
|
2026-02-11 13:33:09 +00:00
|
|
|
|
class StubSolver:
|
|
|
|
|
|
"""Synthetic solver for testing without NX.
|
feat(hydrotech-beam): Phase 1 LHS DoE study code
Implements the optimization study code for Phase 1 (LHS DoE) of the
Hydrotech Beam structural optimization.
Files added:
- run_doe.py: Main entry point — Optuna study with SQLite persistence,
Deb's feasibility rules, CSV/JSON export, Phase 1→2 gate check
- sampling.py: 50-point LHS via scipy.stats.qmc with stratified integer
sampling ensuring all 11 hole_count levels (5-15) are covered
- geometric_checks.py: Pre-flight feasibility filter — hole overlap
(corrected formula: span/(n-1) - d ≥ 30mm) and web clearance checks
- nx_interface.py: NX automation module with stub solver for development
and NXOpen template for Windows/dalidou integration
- requirements.txt: optuna, scipy, numpy, pandas
Key design decisions:
- Baseline enqueued as Trial 0 (LAC lesson)
- All 4 DV expression names from binary introspection (exact spelling)
- Pre-flight geometric filter saves compute and prevents NX crashes
- No surrogates (LAC lesson: direct FEA via TPE beats surrogate+L-BFGS)
- SQLite persistence enables resume after interruption
Tested end-to-end with stub solver: 51 trials, 12 geometric rejects,
39 solved, correct CSV/JSON output.
Ref: OPTIMIZATION_STRATEGY.md, auditor review 2026-02-10
2026-02-10 22:15:06 +00:00
|
|
|
|
|
2026-02-11 13:33:09 +00:00
|
|
|
|
Generates physically-plausible approximate results based on
|
|
|
|
|
|
beam theory. NOT accurate — only for pipeline validation.
|
|
|
|
|
|
"""
|
feat(hydrotech-beam): Phase 1 LHS DoE study code
Implements the optimization study code for Phase 1 (LHS DoE) of the
Hydrotech Beam structural optimization.
Files added:
- run_doe.py: Main entry point — Optuna study with SQLite persistence,
Deb's feasibility rules, CSV/JSON export, Phase 1→2 gate check
- sampling.py: 50-point LHS via scipy.stats.qmc with stratified integer
sampling ensuring all 11 hole_count levels (5-15) are covered
- geometric_checks.py: Pre-flight feasibility filter — hole overlap
(corrected formula: span/(n-1) - d ≥ 30mm) and web clearance checks
- nx_interface.py: NX automation module with stub solver for development
and NXOpen template for Windows/dalidou integration
- requirements.txt: optuna, scipy, numpy, pandas
Key design decisions:
- Baseline enqueued as Trial 0 (LAC lesson)
- All 4 DV expression names from binary introspection (exact spelling)
- Pre-flight geometric filter saves compute and prevents NX crashes
- No surrogates (LAC lesson: direct FEA via TPE beats surrogate+L-BFGS)
- SQLite persistence enables resume after interruption
Tested end-to-end with stub solver: 51 trials, 12 geometric rejects,
39 solved, correct CSV/JSON output.
Ref: OPTIMIZATION_STRATEGY.md, auditor review 2026-02-10
2026-02-10 22:15:06 +00:00
|
|
|
|
|
2026-02-11 13:33:09 +00:00
|
|
|
|
def __init__(self, **kwargs: Any):
|
|
|
|
|
|
self._call_count = 0
|
|
|
|
|
|
logger.warning(
|
|
|
|
|
|
"Using NX STUB solver — results are synthetic approximations. "
|
|
|
|
|
|
"Replace with AtomizerNXSolver (--backend nxopen) for real evaluations."
|
|
|
|
|
|
)
|
feat(hydrotech-beam): Phase 1 LHS DoE study code
Implements the optimization study code for Phase 1 (LHS DoE) of the
Hydrotech Beam structural optimization.
Files added:
- run_doe.py: Main entry point — Optuna study with SQLite persistence,
Deb's feasibility rules, CSV/JSON export, Phase 1→2 gate check
- sampling.py: 50-point LHS via scipy.stats.qmc with stratified integer
sampling ensuring all 11 hole_count levels (5-15) are covered
- geometric_checks.py: Pre-flight feasibility filter — hole overlap
(corrected formula: span/(n-1) - d ≥ 30mm) and web clearance checks
- nx_interface.py: NX automation module with stub solver for development
and NXOpen template for Windows/dalidou integration
- requirements.txt: optuna, scipy, numpy, pandas
Key design decisions:
- Baseline enqueued as Trial 0 (LAC lesson)
- All 4 DV expression names from binary introspection (exact spelling)
- Pre-flight geometric filter saves compute and prevents NX crashes
- No surrogates (LAC lesson: direct FEA via TPE beats surrogate+L-BFGS)
- SQLite persistence enables resume after interruption
Tested end-to-end with stub solver: 51 trials, 12 geometric rejects,
39 solved, correct CSV/JSON output.
Ref: OPTIMIZATION_STRATEGY.md, auditor review 2026-02-10
2026-02-10 22:15:06 +00:00
|
|
|
|
|
2026-02-11 13:33:09 +00:00
|
|
|
|
def solve(self, trial: TrialInput) -> TrialResult:
|
|
|
|
|
|
"""Generate approximate results from beam theory.
|
|
|
|
|
|
|
|
|
|
|
|
Uses simplified cantilever beam formulas:
|
|
|
|
|
|
- Mass ∝ cross-section area × length - hole_volume
|
|
|
|
|
|
- Displacement ~ PL³/3EI (Euler-Bernoulli)
|
|
|
|
|
|
- Stress ~ Mc/I (nominal) with hole SCF
|
|
|
|
|
|
"""
|
|
|
|
|
|
self._call_count += 1
|
|
|
|
|
|
import numpy as np
|
|
|
|
|
|
|
|
|
|
|
|
# Geometry (mm)
|
|
|
|
|
|
L = 5000.0 # beam length
|
|
|
|
|
|
h_half = 250.0 # beam half-height (fixed)
|
|
|
|
|
|
w_half = 150.0 # beam half-width (fixed)
|
|
|
|
|
|
h_core = trial.beam_half_core_thickness
|
|
|
|
|
|
t_face = trial.beam_face_thickness
|
|
|
|
|
|
d_hole = trial.holes_diameter
|
|
|
|
|
|
n_hole = trial.hole_count
|
|
|
|
|
|
|
|
|
|
|
|
# Material: AISI 1005
|
|
|
|
|
|
E = 205000.0 # MPa (Young's modulus)
|
|
|
|
|
|
rho = 7.3e-6 # kg/mm³ (7.3 g/cm³)
|
|
|
|
|
|
|
|
|
|
|
|
# I-beam cross-section second moment of area (approximate)
|
|
|
|
|
|
# Full section: 2*w_half × 2*h_half rectangle
|
|
|
|
|
|
# Minus core cutouts (simplified)
|
|
|
|
|
|
H = 2 * h_half # 500 mm total height
|
|
|
|
|
|
W = 2 * w_half # 300 mm total width
|
|
|
|
|
|
I_full = W * H**3 / 12
|
|
|
|
|
|
# Subtract inner rectangle (core region without faces)
|
|
|
|
|
|
h_web = H - 2 * t_face
|
|
|
|
|
|
w_web = W - 2 * h_core # approximate
|
|
|
|
|
|
I_inner = max(0, w_web) * max(0, h_web)**3 / 12
|
|
|
|
|
|
I_eff = max(I_full - I_inner, I_full * 0.01) # don't go to zero
|
|
|
|
|
|
|
|
|
|
|
|
# Cross-section area (approximate)
|
|
|
|
|
|
A_section = W * H - max(0, w_web) * max(0, h_web)
|
|
|
|
|
|
# Hole volume removal
|
|
|
|
|
|
web_height = H - 2 * t_face
|
|
|
|
|
|
hole_area = n_hole * np.pi * (d_hole / 2)**2
|
|
|
|
|
|
# Only remove from web if holes fit
|
|
|
|
|
|
if d_hole < web_height:
|
|
|
|
|
|
effective_hole_area = min(hole_area, 0.8 * web_height * 4000)
|
|
|
|
|
|
else:
|
|
|
|
|
|
effective_hole_area = 0
|
|
|
|
|
|
# Mass
|
|
|
|
|
|
vol = A_section * L - effective_hole_area * min(h_core * 2, 50)
|
|
|
|
|
|
mass = max(rho * vol, 1.0)
|
|
|
|
|
|
|
|
|
|
|
|
# Tip displacement: δ = PL³ / 3EI
|
|
|
|
|
|
P = 10000 * 9.80665 # 10,000 kgf → N
|
|
|
|
|
|
delta = P * L**3 / (3 * E * I_eff)
|
|
|
|
|
|
|
|
|
|
|
|
# Stress: σ = M*c/I with SCF from holes
|
|
|
|
|
|
M = P * L # max moment at fixed end
|
|
|
|
|
|
c = h_half # distance to extreme fiber
|
|
|
|
|
|
sigma_nominal = M * c / I_eff / 1000 # kPa → MPa
|
|
|
|
|
|
# Stress concentration from holes (simplified)
|
|
|
|
|
|
scf = 1.0 + 0.5 * (d_hole / (web_height + 1))
|
|
|
|
|
|
sigma_max = sigma_nominal * scf
|
|
|
|
|
|
|
|
|
|
|
|
# Add noise (±5%) to simulate model variability
|
|
|
|
|
|
rng = np.random.default_rng(self._call_count)
|
|
|
|
|
|
noise = rng.uniform(0.95, 1.05, 3)
|
|
|
|
|
|
|
|
|
|
|
|
return TrialResult(
|
|
|
|
|
|
success=True,
|
|
|
|
|
|
mass=float(mass * noise[0]),
|
|
|
|
|
|
tip_displacement=float(delta * noise[1]),
|
|
|
|
|
|
max_von_mises=float(sigma_max * noise[2]),
|
|
|
|
|
|
solve_time=0.1,
|
2026-02-10 23:26:51 +00:00
|
|
|
|
)
|
2026-02-11 13:33:09 +00:00
|
|
|
|
|
|
|
|
|
|
def close(self) -> None:
|
|
|
|
|
|
"""Clean up stub solver."""
|
|
|
|
|
|
logger.info("Stub solver closed.")
|