Files
Atomizer/docs/plans/MODEL_INTROSPECTION_MASTER_PLAN.md

875 lines
32 KiB
Markdown
Raw Normal View History

2026-02-15 08:00:21 +00:00
# Model Introspection Master Plan — v1.0
**Authors:** Technical Lead 🔧 (plan owner), NX Expert 🖥️ (initial research)
**Date:** 2026-02-15
**Status:** Approved for R&D implementation
**Location:** `docs/plans/MODEL_INTROSPECTION_MASTER_PLAN.md`
---
## 1. Executive Summary
Atomizer currently executes optimization studies that users manually configure. It has **basic introspection** — expression extraction, mass properties, partial solver config — but lacks the deep model knowledge needed to *understand* what it's optimizing.
This plan defines a **four-layer introspection framework** that captures the complete data picture of any NX CAD/FEA model before optimization. The output is a single structured JSON file (`model_introspection.json`) containing everything an engineer or agent needs to design a sound optimization study:
- **What can change** — design variables, expressions, parametric geometry
- **What the FEA model looks like** — mesh quality, element types, materials, properties
- **What physics governs the problem** — boundary conditions, loads, solver config, subcases
- **Where we're starting from** — baseline displacement, stress, frequency, mass
A fifth layer (dependency mapping / expression graphs) is **deferred to v2** due to NXOpen API limitations that make reliable extraction impractical today.
**Timeline:** 1217 working days for production-quality v1.
**Primary tools:** NXOpen Python API (Layer 1), pyNastran BDF (Layers 23), pyNastran OP2 (Layer 4).
---
## 2. Current State
### 2.1 What Exists
| Script | Location | Extracts | Output |
|--------|----------|----------|--------|
| `introspect_part.py` | `nx_journals/` | Expressions (user/internal), mass, materials, bodies, features, datums, units | `_temp_introspection.json` |
| `introspect_sim.py` | `nx_journals/` | Solutions (partial), BCs (partial), subcases (exploratory) | `_introspection_sim.json` |
| `discover_model.py` | `nx_journals/` | Quick scan of expressions + solutions | JSON to stdout |
| `extract_displacement.py` | `optimization_engine/extractors/` | Max displacement from OP2 | Per-trial result |
| `extract_von_mises_stress.py` | `optimization_engine/extractors/` | Max von Mises from OP2 | Per-trial result |
| `extract_part_mass_material.py` | `nx_journals/` | Mass + material from part | JSON |
### 2.2 What's Missing
-**Mesh quality metrics** — no aspect ratio, jacobian, warpage, skew
-**BC/load details** — no magnitudes, DOF specifications, target node/element sets
-**Solver configuration** — no output requests, convergence settings, solution sequence details
-**Property cards** — no PSHELL thickness, PSOLID assignments, property-element mapping
-**Baseline results in one place** — extractors exist but aren't aggregated pre-optimization
-**Unified output** — no single JSON capturing the full model state
-**Validation** — no cross-checking between data sources
---
## 3. Framework Architecture
### 3.1 Four-Layer Model (v1)
```
┌─────────────────────────────────────────────────────────────────┐
│ Layer 1: GEOMETRIC PARAMETERS [NXOpen API] │
│ Expressions, features, mass, materials, units │
│ → What can be optimized? │
├─────────────────────────────────────────────────────────────────┤
│ Layer 2: FEA MODEL STRUCTURE [pyNastran BDF] │
│ Mesh quality, element types, materials, properties │
│ → What's the baseline mesh health? │
├─────────────────────────────────────────────────────────────────┤
│ Layer 3: SOLVER CONFIGURATION [pyNastran BDF] │
│ Solutions, subcases, BCs, loads, output requests │
│ → What physics governs the problem? │
├─────────────────────────────────────────────────────────────────┤
│ Layer 4: BASELINE RESULTS [pyNastran OP2] │
│ Pre-optimization stress, displacement, frequency, mass │
│ → Where are we starting from? │
└─────────────────────────────────────────────────────────────────┘
DEFERRED TO v2:
┌───────────────────────────────────────────────────────────────┐
│ Layer 5: DEPENDENCIES & RELATIONSHIPS │
│ Expression graph, feature tree, parametric sensitivities │
└───────────────────────────────────────────────────────────────┘
```
### 3.2 Design Principles
1. **One source per data type.** Don't mix NXOpen and pyNastran for the same data. NXOpen owns geometry/expressions; pyNastran owns FEA/solver data.
2. **BDF export is a prerequisite.** Layer 23 extraction requires a current BDF file. The orchestrator must trigger a BDF export (or verify freshness) before parsing.
3. **Report, don't recommend.** v1 reports what exists in the model. It does NOT auto-suggest bounds, objectives, or study types. That's the engineer's job.
4. **Fail gracefully.** If one layer fails, the others still produce output. Partial introspection is better than no introspection.
5. **Validate across sources.** Where data overlaps (element count, mass, materials), cross-check and flag discrepancies.
### 3.3 Data Flow
```
┌──────────┐
│ .prt │
│ file │
└────┬─────┘
NXOpen API
┌──────────────┐
│ Layer 1 │
│ Geometric │
│ Parameters │
└──────┬───────┘
┌────────────────────┼────────────────────┐
│ │ │
▼ ▼ ▼
┌────────┐ ┌──────────┐ ┌──────────┐
│ .bdf │ │ .sim │ │ .op2 │
│ file │ │ file │ │ file │
└───┬────┘ └──────────┘ └────┬─────┘
│ (metadata only │
│ via NXOpen) │
pyNastran BDF pyNastran OP2
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Layer 2+3 │ │ Layer 4 │
│ FEA Model │ │ Baseline │
│ + Solver │ │ Results │
└──────┬───────┘ └──────┬───────┘
│ │
└──────────┬───────────────────────┘
┌────────────────┐
│ Orchestrator │
│ Merge + JSON │
│ + Validate │
└────────┬───────┘
┌──────────────────────────┐
│ model_introspection.json │
│ introspection_summary.md │
└──────────────────────────┘
```
---
## 4. Master JSON Schema
### 4.1 Top-Level Structure
```json
{
"introspection_version": "1.0.0",
"timestamp": "ISO-8601",
"model_id": "string — derived from part filename",
"files": {
"part": "path/to/model.prt",
"sim": "path/to/model_sim1.sim",
"fem": "path/to/model_fem1.fem",
"bdf": "path/to/exported.bdf",
"op2": "path/to/results.op2"
},
"geometric_parameters": { "..." },
"fea_model": { "..." },
"solver_configuration": { "..." },
"baseline_results": { "..." },
"candidate_design_variables": [ "..." ],
"validation": { "..." },
"metadata": {
"extraction_time_seconds": 0.0,
"layers_completed": ["geometric", "fea_model", "solver", "baseline"],
"layers_failed": [],
"warnings": []
}
}
```
### 4.2 Layer 1 — `geometric_parameters`
```json
{
"expressions": {
"user_defined": [
{
"name": "thickness",
"value": 3.0,
"units": "mm",
"formula": "3.0",
"is_constant": false,
"part": "bracket.prt"
}
],
"internal": [
{
"name": "p47",
"value": 6.0,
"units": "mm",
"formula": "thickness * 2"
}
],
"total_user": 3,
"total_internal": 52
},
"mass_properties": {
"mass_kg": 0.234,
"volume_mm3": 85000.0,
"surface_area_mm2": 15000.0,
"center_of_gravity_mm": [12.3, 45.6, 78.9],
"num_solid_bodies": 1
},
"materials": [
{
"name": "Aluminum 6061-T6",
"assigned_to_bodies": ["Body(1)"],
"properties": {
"density_kg_m3": 2700.0,
"youngs_modulus_MPa": 68900.0,
"poisson_ratio": 0.33,
"yield_strength_MPa": 276.0,
"ultimate_strength_MPa": 310.0
},
"source": "NXOpen part material"
}
],
"features": {
"total_count": 12,
"by_type": {
"Extrude": 3,
"Shell": 1,
"Sketch": 2,
"Datum Plane": 2,
"Fillet": 4
},
"suppressed_count": 0
},
"units": {
"length": "Millimeter",
"mass": "Kilogram",
"force": "Newton",
"temperature": "Celsius",
"system": "Metric (mm, kg, N, °C)"
}
}
```
### 4.3 Layer 2 — `fea_model`
```json
{
"mesh": {
"total_nodes": 12450,
"total_elements": 8234,
"element_types": {
"CTETRA": { "count": 7800, "order": "linear" },
"CQUAD4": { "count": 434, "order": "linear" }
},
"quality_metrics": {
"aspect_ratio": {
"min": 1.02,
"max": 8.34,
"mean": 2.45,
"std": 1.23,
"p95": 5.12,
"threshold": 10.0,
"elements_exceeding": 0
},
"jacobian": {
"min": 0.62,
"max": 1.0,
"mean": 0.91,
"threshold": 0.5,
"elements_below": 0
},
"warpage_deg": {
"max": 5.2,
"threshold": 10.0,
"elements_exceeding": 0
},
"skew_deg": {
"max": 45.2,
"threshold": 60.0,
"elements_exceeding": 0
}
},
"quality_verdict": "PASS"
},
"materials": [
{
"mat_id": 1,
"card_type": "MAT1",
"name": "Aluminum 6061-T6",
"E_MPa": 68900.0,
"G_MPa": 25900.0,
"nu": 0.33,
"rho": 2.7e-6,
"alpha": 2.36e-5
}
],
"properties": [
{
"prop_id": 1,
"card_type": "PSHELL",
"thickness_mm": 3.0,
"mat_id": 1,
"element_count": 434
},
{
"prop_id": 2,
"card_type": "PSOLID",
"mat_id": 1,
"element_count": 7800
}
]
}
```
**Mesh quality computation:** All quality metrics are computed from element node coordinates using pyNastran's geometry data. For each element, aspect ratio = longest edge / shortest edge. Jacobian is computed at element integration points. This is more reliable than NXOpen's `QualityAuditBuilder` which has limited documentation.
### 4.4 Layer 3 — `solver_configuration`
```json
{
"solutions": [
{
"name": "Solution 1",
"sol_sequence": 101,
"sol_type": "Static Linear",
"solver": "NX Nastran"
}
],
"subcases": [
{
"id": 1,
"label": "Subcase - Static 1",
"load_set_id": 1,
"spc_set_id": 1,
"output_requests": {
"displacement": { "format": "OP2", "scope": "ALL" },
"stress": { "format": "OP2", "scope": "ALL" },
"strain": null,
"force": null
}
}
],
"constraints": [
{
"spc_id": 1,
"type": "SPC1",
"dofs_constrained": [1, 2, 3, 4, 5, 6],
"dof_labels": ["Tx", "Ty", "Tz", "Rx", "Ry", "Rz"],
"node_count": 145,
"node_ids_sample": [1, 2, 3, 4, 5],
"description": "Fixed support — all 6 DOF"
}
],
"loads": [
{
"load_id": 1,
"type": "FORCE",
"node_id": 456,
"magnitude_N": 1000.0,
"direction": [0.0, -1.0, 0.0],
"components_N": { "Fx": 0.0, "Fy": -1000.0, "Fz": 0.0 }
},
{
"load_id": 2,
"type": "PLOAD4",
"element_count": 25,
"pressure_MPa": 5.0,
"direction": "element normal"
}
],
"bulk_data_stats": {
"total_cards": 15234,
"card_types": {
"GRID": 12450,
"CTETRA": 7800,
"CQUAD4": 434,
"MAT1": 1,
"PSHELL": 1,
"PSOLID": 1,
"SPC1": 1,
"FORCE": 1,
"PLOAD4": 1
}
}
}
```
**BDF export requirement:** The orchestrator must ensure a current BDF file exists before Layer 23 extraction. Options:
1. Export BDF via NXOpen journal (`sim.ExportNastranDeck()`) as the first orchestrator step
2. Accept a user-provided BDF path
3. Find the most recent BDF in the sim output directory and verify its timestamp
Option 1 is preferred — it guarantees freshness.
### 4.5 Layer 4 — `baseline_results`
```json
{
"source_op2": "bracket_sim1-solution_1.op2",
"solution": "Solution 1",
"subcase_id": 1,
"converged": true,
"displacement": {
"max_magnitude_mm": 2.34,
"max_node_id": 4567,
"max_component": "Tz",
"max_component_value_mm": -2.31,
"mean_magnitude_mm": 0.45
},
"stress": {
"von_mises": {
"max_MPa": 145.6,
"max_element_id": 2345,
"mean_MPa": 45.2,
"p95_MPa": 112.0
},
"margin_of_safety": {
"yield": 0.89,
"ultimate": 1.13,
"yield_strength_MPa": 276.0,
"ultimate_strength_MPa": 310.0,
"note": "MoS = (allowable / actual) - 1"
}
},
"modal": null,
"mass_from_solver_kg": 0.234
}
```
**Note:** `modal` is populated only if a SOL 103 result exists. Fields would include `modes: [{number, frequency_hz, effective_mass_fraction}]`.
### 4.6 `candidate_design_variables`
This section **reports** expressions that are likely design variable candidates. It does NOT suggest bounds or objectives — that's the engineer's job.
```json
[
{
"name": "thickness",
"current_value": 3.0,
"units": "mm",
"reason": "User-defined expression, non-constant, drives geometry"
},
{
"name": "width",
"current_value": 50.0,
"units": "mm",
"reason": "User-defined expression, non-constant, drives geometry"
}
]
```
**Selection criteria:** An expression is a candidate DV if:
1. It is user-defined (not internal `p###`)
2. It is not constant (formula is not just a literal used in a single non-geometric context)
3. It has dimensional units (mm, deg, etc.) or is clearly a count
4. Its name is not a known system expression (e.g., `PI`, `TRUE`, unit names)
### 4.7 `validation`
```json
{
"cross_checks": [
{
"metric": "element_count",
"nxopen_value": 8234,
"pynastran_value": 8234,
"match": true
},
{
"metric": "mass_kg",
"nxopen_value": 0.234,
"pynastran_value": 0.2338,
"match": false,
"delta_percent": 0.09,
"tolerance_percent": 1.0,
"within_tolerance": true,
"note": "Small delta due to mesh discretization vs. CAD geometry"
},
{
"metric": "material_E_MPa",
"nxopen_value": 68900.0,
"pynastran_value": 68900.0,
"match": true
}
],
"overall_status": "PASS",
"discrepancies": []
}
```
---
## 5. Extraction Methods
### 5.1 Layer 1 — NXOpen Python API
**Base script:** `nx_journals/introspect_part.py` (enhance, don't rewrite)
| Data | API | Notes |
|------|-----|-------|
| Expressions | `part.Expressions` iterator | Filter user vs internal via name pattern (`p###` = internal) |
| Expression values | `expr.Value`, `expr.RightHandSide`, `expr.Units.Name` | |
| Mass properties | `part.MeasureManager.NewMassProperties()` | Requires solid body list |
| Materials | `body.GetPhysicalMaterial()` | Per-body; extract property values via `GetPropertyValue()` |
| Features | `part.Features` iterator | Type via `type(feature).__name__`; count suppressed |
| Units | `part.UnitCollection`, `part.PartUnits` | System-level unit identification |
**Enhancement needed:**
- Add candidate DV identification logic
- Structure output to match schema §4.2
- Add error handling for each extraction block (fail gracefully)
### 5.2 Layers 23 — pyNastran BDF
**New script:** `nx_journals/introspect_bdf.py` (or `optimization_engine/extractors/introspect_bdf.py`)
| Data | pyNastran API | Notes |
|------|---------------|-------|
| Elements | `bdf.elements` dict | Key = EID, value has `.type` attribute |
| Nodes | `bdf.nodes` dict | Key = NID |
| Materials | `bdf.materials` dict | MAT1: `.E`, `.G`, `.nu`, `.rho` |
| Properties | `bdf.properties` dict | PSHELL: `.t`, `.mid1`; PSOLID: `.mid` |
| SPCs | `bdf.spcs` dict | SPC1: `.components` (DOF string), `.node_ids` |
| Forces | `bdf.loads` dict | FORCE: `.mag`, `.xyz` (direction vector) |
| Pressures | `bdf.loads` dict | PLOAD4: `.pressures`, element IDs |
| Subcases | `bdf.subcases` dict | `.params` for LOAD, SPC, output requests |
| Bulk stats | `bdf.card_count` | Card type → count |
**Mesh quality computation** (pyNastran element geometry):
```python
import numpy as np
from pyNastran.bdf.bdf import BDF
def compute_mesh_quality(bdf_model):
"""Compute mesh quality metrics from element geometry."""
aspect_ratios = []
for eid, elem in bdf_model.elements.items():
if elem.type in ('CQUAD4', 'CTRIA3'):
# Get node positions
nodes = [bdf_model.nodes[nid].get_position() for nid in elem.node_ids]
# Compute edge lengths
edges = []
n = len(nodes)
for i in range(n):
edge = np.linalg.norm(nodes[(i+1) % n] - nodes[i])
edges.append(edge)
ar = max(edges) / max(min(edges), 1e-12)
aspect_ratios.append(ar)
elif elem.type == 'CTETRA':
nodes = [bdf_model.nodes[nid].get_position() for nid in elem.node_ids[:4]]
edges = []
for i in range(4):
for j in range(i+1, 4):
edges.append(np.linalg.norm(nodes[j] - nodes[i]))
ar = max(edges) / max(min(edges), 1e-12)
aspect_ratios.append(ar)
if not aspect_ratios:
return None
ar = np.array(aspect_ratios)
return {
"min": float(ar.min()),
"max": float(ar.max()),
"mean": float(ar.mean()),
"std": float(ar.std()),
"p95": float(np.percentile(ar, 95))
}
```
### 5.3 Layer 4 — pyNastran OP2
**Leverage existing extractors:**
- `optimization_engine/extractors/extract_displacement.py`
- `optimization_engine/extractors/extract_von_mises_stress.py`
**New aggregation script:** `nx_journals/introspect_baseline.py`
| Data | pyNastran API | Notes |
|------|---------------|-------|
| Displacement | `op2.displacements[subcase_id].data` | Magnitude = sqrt(Tx² + Ty² + Tz²) |
| Stress | `op2.ctetra_stress[sc]` or `op2.cquad4_stress[sc]` | Von Mises column varies by element type |
| Eigenvalues | `op2.eigenvalues` | SOL 103 only |
| Grid point weight | `op2.grid_point_weight` | Solver-computed mass |
### 5.4 BDF Export (Prerequisite Step)
```python
# NXOpen journal to export BDF from .sim
def export_bdf(sim_path, output_bdf_path):
"""Export Nastran input deck from simulation."""
theSession = NXOpen.Session.GetSession()
# Open sim, find solution, export deck
# Details depend on NX version — see introspect_sim.py patterns
pass
```
**Alternative:** If a solve has already been run, the BDF exists in the solver output directory. The orchestrator should check for it before triggering a fresh export.
---
## 6. Implementation Phases
### Phase 1: Enhanced Part Introspection (34 days)
**Goal:** Complete Layer 1 extraction with structured JSON output.
**Tasks:**
1. Refactor `introspect_part.py` output to match schema §4.2
2. Add candidate DV identification logic (§4.6 criteria)
3. Add feature type counting and suppression detection
4. Add material property extraction via `GetPropertyValue()`
5. Structured error handling — each block in try/except, log failures
6. Unit tests with known bracket/beam models
**Output:** `layer1_geometric.json`
**Owner:** NX Expert
### Phase 2: BDF-Based FEA Model Introspection (34 days)
**Goal:** Complete Layer 2 extraction — mesh, materials, properties, quality.
**Tasks:**
1. Create `introspect_bdf.py` with pyNastran BDF parsing
2. Implement mesh quality computation (aspect ratio, jacobian, warpage, skew)
3. Extract material cards (MAT1, MAT2 logged but not parsed in v1)
4. Extract property cards (PSHELL, PSOLID) with element assignments
5. Compute quality verdict (PASS/WARN/FAIL based on thresholds)
6. Test on Hydrotech beam BDF and M1 mirror BDF
**Output:** `layer2_fea_model.json`
**Owner:** NX Expert
### Phase 3: BDF-Based Solver Configuration (34 days)
**Goal:** Complete Layer 3 extraction — subcases, BCs, loads, output requests.
**Tasks:**
1. Extend `introspect_bdf.py` with subcase parsing
2. Extract SPC constraints with DOF details and node counts
3. Extract loads (FORCE, PLOAD4, MOMENT, GRAV) with magnitudes and directions
4. Extract output requests from case control
5. Add bulk data card statistics
6. Integrate BDF export step (NXOpen journal or path detection)
**Output:** `layer3_solver.json`
**Owner:** NX Expert
### Phase 4: Baseline Results Aggregation (12 days)
**Goal:** Complete Layer 4 — aggregate existing extractors into baseline JSON.
**Tasks:**
1. Create `introspect_baseline.py` using existing OP2 extractors
2. Compute displacement max/mean with node identification
3. Compute stress max with element identification and MoS
4. Optionally extract modal frequencies if SOL 103 results exist
5. Extract solver-computed mass from grid point weight generator
**Output:** `layer4_baseline.json`
**Owner:** NX Expert or Technical Lead
### Phase 5: Orchestrator + Validation (23 days)
**Goal:** Single-command full introspection with cross-validation and summary.
**Tasks:**
1. Create `run_introspection.py` orchestrator
2. Sequence: BDF export → Layer 1 → Layer 2 → Layer 3 → Layer 4
3. Merge all layer JSONs into master `model_introspection.json`
4. Implement cross-validation checks (§4.7)
5. Generate `introspection_summary.md` — human-readable report
6. Add CLI interface: `python run_introspection.py model.prt model_sim1.sim [--op2 path]`
**Output:** `model_introspection.json` + `introspection_summary.md`
**Owner:** NX Expert + Technical Lead (review)
### Timeline Summary
| Phase | Days | Cumulative | Depends On |
|-------|------|-----------|------------|
| Phase 1 — Part introspection | 34 | 34 | — |
| Phase 2 — FEA model (BDF) | 34 | 68 | — (parallel with Phase 1) |
| Phase 3 — Solver config (BDF) | 34 | 912 | Phase 2 (shared script) |
| Phase 4 — Baseline (OP2) | 12 | 1014 | — (parallel with Phase 3) |
| Phase 5 — Orchestrator | 23 | 1217 | All prior phases |
**Phases 1 and 2 can run in parallel** (different APIs, different scripts). Phase 4 can run in parallel with Phase 3. Critical path: Phase 2 → Phase 3 → Phase 5.
---
## 7. Validation Strategy
### 7.1 Cross-Source Checks
Where data is available from multiple sources, cross-validate:
| Metric | Source A | Source B | Tolerance |
|--------|----------|----------|-----------|
| Element count | pyNastran `len(bdf.elements)` | NXOpen `FeelementLabelMap.Size` | Exact match |
| Node count | pyNastran `len(bdf.nodes)` | NXOpen `FenodeLabelMap.Size` | Exact match |
| Mass | NXOpen `MeasureManager` | OP2 grid point weight | 1% (mesh vs. CAD geometry) |
| Material E | NXOpen `GetPropertyValue('YoungModulus')` | pyNastran `bdf.materials[id].E` | Exact match |
| Material ρ | NXOpen `GetPropertyValue('Density')` | pyNastran `bdf.materials[id].rho` | Unit conversion tolerance |
### 7.2 Self-Consistency Checks
- Total element count = sum of per-type counts
- Every property ID referenced by elements exists in properties list
- Every material ID referenced by properties exists in materials list
- SPC/LOAD set IDs in subcases exist in constraints/loads lists
- OP2 subcase IDs match BDF subcase IDs
### 7.3 Sanity Checks
- Mass > 0
- Max displacement > 0 (model is loaded and responding)
- Max stress > 0
- No element type with 0 elements in the count
- At least 1 constraint and 1 load in every subcase
### 7.4 Validation Verdict
```
PASS — All checks pass
WARN — Non-critical discrepancies (mass within tolerance but not exact)
FAIL — Critical mismatch (element count differs, missing materials)
```
---
## 8. Integration with Atomizer HQ
### 8.1 How Agents Consume Introspection Data
| Agent | Uses | For |
|-------|------|-----|
| **Study Builder** | `candidate_design_variables`, `expressions`, `units` | Study configuration, DV setup |
| **Technical Lead** | `mesh.quality_metrics`, `baseline_results`, `validation` | Technical review, go/no-go |
| **Optimizer** | `fea_model.mesh.total_elements`, `baseline_results` | Runtime estimation, convergence criteria |
| **Manager** | `metadata.layers_completed`, `validation.overall_status` | Status tracking, resource planning |
### 8.2 Usage Workflow
```
1. Antoine opens new study
2. Run introspection: python run_introspection.py model.prt model_sim1.sim
3. Review introspection_summary.md
4. Study Builder reads model_introspection.json → proposes study config
5. Technical Lead reviews → approves or flags issues
6. Optimization proceeds with full model context
```
### 8.3 Knowledge Base Integration
Every introspection JSON is stored in the study directory:
```
studies/<project>/<study>/
1_setup/
model/
model_introspection.json ← NEW
introspection_summary.md ← NEW
model.prt
model_sim1.sim
```
This becomes the canonical reference for all agents working on the study.
---
## 9. Scope Exclusions (v1)
The following are **explicitly NOT handled** in v1. Attempting to introspect models with these features will produce partial results with appropriate warnings.
| Feature | Why Excluded | v2 Priority |
|---------|-------------|-------------|
| **Assemblies** | Multi-part expression scoping, component transforms, assembly-level materials — significant complexity | High |
| **Composite materials** (MAT2, MAT8) | Ply stack introspection, layup sequences, orientation angles — specialized domain | Medium |
| **Nonlinear analysis** (SOL 106) | Contact definitions, convergence settings, load stepping, large deformation flags | Medium |
| **Topology optimization** regions | Design vs. non-design space, manufacturing constraints, density filters | Low |
| **Expression dependency graphs** | NXOpen `RightHandSide` parsing is fragile: breaks on built-in functions (`if()`, `min()`), unit qualifiers, substring expression names. Feature-to-expression links require rebuilding builder objects — massive effort | Medium |
| **Parametric sensitivity estimation** | Labeling `thickness → mass` as "linear" is textbook, not model-specific. Real sensitivity requires perturbation studies (separate effort) | Low |
| **Multi-FEM models** | Multiple FEM files linked to one part — need to handle collector mapping across files | Medium |
| **Thermal-structural coupling** | Multi-physics BC detection, thermal load application | Low |
| **Contact pairs** | Contact detection, friction coefficients, contact algorithms | Medium |
| **Dynamic loads** | PSD, time-history, random vibration, frequency-dependent loads | Low |
---
## 10. v2 Roadmap
### 10.1 Expression Dependency Graph (Layer 5)
**Challenge:** NXOpen's `RightHandSide` returns the formula as a string, but parsing it reliably requires handling:
- NX built-in math functions
- Unit-qualified references
- Expression names that are substrings of other names
- Conditional expressions (`if(expr > val, a, b)`)
**Approach for v2:** Build a proper tokenizer/parser for NX expression syntax. Consider exporting expressions to a file and parsing offline rather than relying on API string manipulation.
### 10.2 Assembly Support
**Challenge:** Expressions live at component scope. Design variables may be in sub-components. Assembly-level mass is different from component mass.
**Approach:** Recursive introspection — introspect each component, then merge with assembly context (transforms, overrides).
### 10.3 Composite Introspection
**Challenge:** MAT2/MAT8 cards, PCOMP/PCOMPG properties, ply orientations, stacking sequences.
**Approach:** Extend pyNastran parsing for composite cards. Map plies to design variables (thickness, angle).
### 10.4 AI-Powered Analysis (Future)
- Sensitivity prediction from geometry/BC features without running FEA
- Design variable clustering (correlated parameters)
- Failure mode prediction from stress distributions
- Automated study type recommendation (with engineer approval)
---
## 11. Appendix: Key Reference Paths
### Existing Scripts
| Script | Path | Status |
|--------|------|--------|
| `introspect_part.py` | `nx_journals/introspect_part.py` | Enhance for v1 |
| `introspect_sim.py` | `nx_journals/introspect_sim.py` | Reference only (BDF replaces most functionality) |
| `discover_model.py` | `nx_journals/discover_model.py` | Reference only |
| `extract_displacement.py` | `optimization_engine/extractors/extract_displacement.py` | Use in Layer 4 |
| `extract_von_mises_stress.py` | `optimization_engine/extractors/extract_von_mises_stress.py` | Use in Layer 4 |
### New Scripts (to create)
| Script | Purpose |
|--------|---------|
| `introspect_bdf.py` | Layers 23: BDF parsing for mesh, materials, properties, solver config |
| `introspect_baseline.py` | Layer 4: OP2 result aggregation |
| `run_introspection.py` | Master orchestrator — runs all layers, merges, validates |
| `export_bdf.py` | NXOpen journal to export Nastran input deck |
### NXOpen API Reference
- Expressions: `NXOpen.Part.Expressions`
- Mass: `NXOpen.Part.MeasureManager.NewMassProperties()`
- Materials: `body.GetPhysicalMaterial()`, `mat.GetPropertyValue()`
- Features: `NXOpen.Part.Features`
- FEM: `NXOpen.CAE.FemPart.BaseFEModel`
- SIM: `NXOpen.CAE.SimPart.Simulation`
### pyNastran API Reference
- BDF: `pyNastran.bdf.bdf.BDF``.elements`, `.nodes`, `.materials`, `.properties`, `.spcs`, `.loads`, `.subcases`
- OP2: `pyNastran.op2.op2.OP2``.displacements`, `.ctetra_stress`, `.cquad4_stress`, `.eigenvalues`
---
## 12. Sign-Off
| Role | Status | Notes |
|------|--------|-------|
| Technical Lead 🔧 | ✅ Approved | Plan owner, technical review complete |
| NX Expert 🖥️ | 🔲 Pending | Initial research author, implementation owner |
| Manager 🎯 | 🔲 Pending | Resource allocation, timeline approval |
| CEO (Antoine) | 🔲 Pending | Strategic direction approval |
---
*The physics is the boss. Introspection just makes sure we understand what the physics is doing before we start changing things.*
— Technical Lead 🔧 | Atomizer Engineering Co.