# Atomizer Architecture Review **Date**: January 2026 **Version**: 2.0 (AtomizerSpec unified configuration) **Author**: Architecture Review --- ## Executive Summary Atomizer is a structural optimization platform that enables engineers to optimize FEA (Finite Element Analysis) designs through a visual canvas interface and AI-powered assistance. The architecture follows a **single source of truth** pattern where all configuration flows through `atomizer_spec.json`. ### Key Strengths - **Unified Configuration**: One JSON file defines the entire optimization study - **Type Safety**: Pydantic validation at every modification point - **Real-time Collaboration**: WebSocket-based sync between all clients - **Responsive UI**: Optimistic updates with background synchronization - **AI Integration**: Claude can modify configurations in Power Mode ### Architecture Quality Score: **8.5/10** | Aspect | Score | Notes | | --------------- | ----- | ----------------------------------------------------- | | Data Integrity | 9/10 | Single source of truth, hash-based conflict detection | | Type Safety | 9/10 | Pydantic models throughout backend | | Extensibility | 8/10 | Custom extractors, algorithms supported | | Performance | 8/10 | Optimistic updates, WebSocket streaming | | Maintainability | 8/10 | Clear separation of concerns | | Documentation | 7/10 | Good inline docs, needs more high-level guides | --- ## 1. Configuration Layer ### 1.1 AtomizerSpec v2.0 - The Single Source of Truth **Location**: `studies/{study_name}/atomizer_spec.json` The AtomizerSpec is the heart of Atomizer's configuration. Every component reads from and writes to this single file. ``` atomizer_spec.json ├── meta # Study metadata │ ├── version: "2.0" │ ├── study_name │ ├── created_by # canvas | claude | api | migration │ └── modified_at ├── model # NX model files │ ├── sim: { path, solver } │ ├── nx_part: { path } │ └── fem: { path } ├── design_variables[] # Parameters to optimize │ ├── id: "dv_001" │ ├── name, expression_name │ ├── type: continuous | discrete │ ├── bounds: { min, max } │ └── canvas_position ├── extractors[] # Physics result extractors │ ├── id: "ext_001" │ ├── type: mass | displacement | stress | zernike | custom │ ├── config: {} │ └── outputs: [{ name, metric }] ├── objectives[] # Optimization goals │ ├── id: "obj_001" │ ├── direction: minimize | maximize │ ├── weight │ └── source: { extractor_id, output_key } ├── constraints[] # Hard/soft constraints │ ├── id: "con_001" │ ├── operator: <= | >= | == │ ├── threshold │ └── source: { extractor_id, output_key } ├── optimization # Algorithm settings │ ├── algorithm: { type, config } │ ├── budget: { max_trials } │ └── surrogate: { enabled, type } └── canvas # UI layout state └── edges[] ``` ### 1.2 Node ID Convention All configurable elements use unique IDs with prefixes: | Prefix | Element Type | Example | |--------|--------------|---------| | `dv_` | Design Variable | `dv_001`, `dv_002` | | `ext_` | Extractor | `ext_001`, `ext_002` | | `obj_` | Objective | `obj_001`, `obj_002` | | `con_` | Constraint | `con_001`, `con_002` | IDs are auto-generated: `{prefix}{max_existing + 1:03d}` ### 1.3 Legacy Configuration **File**: `optimization_config.json` (deprecated) Legacy studies may have this file. The `SpecMigrator` automatically converts them to AtomizerSpec v2.0 on load. ```python from optimization_engine.config.migrator import SpecMigrator migrator = SpecMigrator(study_dir) spec = migrator.migrate_file(legacy_path, spec_path) ``` --- ## 2. Frontend Architecture ### 2.1 Technology Stack | Layer | Technology | Purpose | |-------|------------|---------| | Build | Vite + TypeScript | Fast bundling, type safety | | UI | React 18 + TailwindCSS | Component framework | | State | Zustand | Lightweight global state | | Canvas | ReactFlow | Graph visualization | | Communication | Fetch + WebSocket | API + real-time sync | ### 2.2 Directory Structure ``` atomizer-dashboard/frontend/src/ ├── components/ │ ├── canvas/ # Canvas components │ │ ├── AtomizerCanvas # Main wrapper │ │ ├── SpecRenderer # Spec → ReactFlow │ │ ├── nodes/ # Node type components │ │ └── panels/ # Side panels │ └── chat/ # Claude chat UI ├── hooks/ │ ├── useSpecStore.ts # Central state (Zustand) │ ├── useChat.ts # Claude integration │ ├── useCanvasStore.ts # Local canvas state │ └── useSpecWebSocket.ts # Real-time sync ├── lib/ │ └── spec/converter.ts # Spec ↔ ReactFlow ├── types/ │ └── atomizer-spec.ts # TypeScript definitions └── pages/ ├── CanvasView.tsx # Main canvas page ├── Home.tsx # Study selection └── Setup.tsx # Study wizard ``` ### 2.3 State Management Architecture ``` ┌─────────────────────────────────────────────────────────────┐ │ useSpecStore (Zustand) │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌───────────────┐ │ │ │ spec │ │ hash │ │ isDirty │ │ selectedNode │ │ │ └────┬────┘ └────┬────┘ └────┬────┘ └───────┬───────┘ │ └───────┼────────────┼────────────┼───────────────┼──────────┘ │ │ │ │ ▼ ▼ ▼ ▼ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────────┐ │Canvas │ │Conflict │ │ Save │ │ NodeConfig │ │ Render │ │Detection│ │ Button │ │ Panel │ └─────────┘ └─────────┘ └─────────┘ └─────────────┘ ``` **Key Actions**: - `loadSpec(studyId)` - Fetch spec from backend - `patchSpec(path, value)` - Update with conflict check - `patchSpecOptimistic(path, value)` - Fire-and-forget update - `addNode(type, data)` - Add design var/extractor/etc. - `removeNode(nodeId)` - Delete with edge cleanup ### 2.4 Canvas Rendering Pipeline ``` AtomizerSpec JSON │ ▼ specToNodes() [converter.ts] │ ├──► Model Node (synthetic) ├──► Solver Node (synthetic) ├──► DesignVar Nodes × N ├──► Extractor Nodes × N ├──► Objective Nodes × N ├──► Constraint Nodes × N ├──► Algorithm Node (synthetic) └──► Surrogate Node (optional) │ ▼ ReactFlow Component │ ▼ Interactive Canvas ``` **Layout Constants**: ``` Design Variables: x = 50 Model: x = 280 Solver: x = 510 Extractors: x = 740 Objectives: x = 1020 Constraints: x = 1020 (offset y) Algorithm: x = 1300 Surrogate: x = 1530 Row Height: 100px ``` --- ## 3. Backend Architecture ### 3.1 Technology Stack | Layer | Technology | Purpose | |-------|------------|---------| | Framework | FastAPI | Async REST + WebSocket | | Validation | Pydantic | Schema enforcement | | Database | SQLite | Trial storage (Optuna schema) | | LLM | Anthropic Claude | AI assistance | ### 3.2 Directory Structure ``` atomizer-dashboard/backend/api/ ├── main.py # FastAPI app ├── routes/ │ ├── spec.py # Spec CRUD + WebSocket │ ├── optimization.py # Run management │ ├── claude.py # Chat sessions │ └── files.py # File operations └── services/ ├── spec_manager.py # Central spec management ├── claude_agent.py # Claude with tools ├── context_builder.py # System prompts └── session_manager.py # WebSocket sessions ``` ### 3.3 SpecManager Service **The SpecManager is the gatekeeper for all spec modifications.** ```python class SpecManager: def __init__(self, study_path: Path): self.study_path = study_path self.spec_file = study_path / "atomizer_spec.json" self.subscribers: List[WebSocket] = [] # Loading def load_spec(self) -> AtomizerSpec def load_raw(self) -> dict # Validation def validate(self, spec) -> ValidationReport def validate_semantic(self, spec) -> ValidationReport # Modifications def patch_spec(self, path, value) -> dict def add_node(self, type, data) -> str def update_node(self, id, updates) -> None def remove_node(self, id) -> None # Persistence def save_spec(self, spec) -> dict # Atomic write + hash def compute_hash(self, spec) -> str # Real-time sync def subscribe(self, ws: WebSocket) def broadcast(self, message: dict) ``` **Modification Flow**: 1. Load current spec 2. Apply modification 3. Validate with Pydantic 4. Atomic write to disk 5. Compute new hash 6. Broadcast to all WebSocket subscribers 7. Return hash + timestamp ### 3.4 REST API Endpoints | Method | Endpoint | Purpose | |--------|----------|---------| | GET | `/api/studies/{id}/spec` | Load full spec | | GET | `/api/studies/{id}/spec/hash` | Get current hash | | PUT | `/api/studies/{id}/spec` | Replace entire spec | | PATCH | `/api/studies/{id}/spec` | JSONPath patch | | POST | `/api/studies/{id}/spec/nodes` | Add node | | PATCH | `/api/studies/{id}/spec/nodes/{nid}` | Update node | | DELETE | `/api/studies/{id}/spec/nodes/{nid}` | Remove node | | POST | `/api/studies/{id}/spec/validate` | Validate spec | | WS | `/api/studies/{id}/spec/sync` | Real-time sync | ### 3.5 Conflict Detection Uses SHA256 hash of spec content: ``` Client A loads spec (hash: abc123) Client B loads spec (hash: abc123) Client A modifies → sends with hash abc123 Server: hash matches → apply → new hash: def456 Server: broadcast to all clients Client B modifies → sends with hash abc123 Server: hash mismatch (expected def456) Server: return 409 Conflict Client B: reload latest spec ``` --- ## 4. Optimization Engine ### 4.1 Directory Structure ``` optimization_engine/ ├── config/ # Configuration management │ ├── spec_models.py # Pydantic models │ ├── spec_validator.py # Semantic validation │ └── migrator.py # Legacy migration ├── extractors/ # Physics extractors │ ├── extract_displacement.py │ ├── extract_stress.py │ ├── extract_mass_*.py │ ├── extract_zernike*.py │ └── custom_extractor_loader.py ├── core/ # Optimization algorithms │ ├── runner.py # Main loop │ ├── method_selector.py # Algorithm selection │ └── intelligent_optimizer.py # IMSO ├── nx/ # NX integration │ ├── solver.py # Nastran execution │ └── updater.py # Parameter updates ├── study/ # Study management │ ├── creator.py │ └── state.py └── utils/ ├── dashboard_db.py # Optuna schema └── trial_manager.py # Trial CRUD ``` ### 4.2 Extractor Library | ID | Type | Function | Inputs | |----|------|----------|--------| | E1 | Displacement | Max/RMS displacement | OP2, subcase | | E2 | Frequency | Eigenvalue | OP2, mode | | E3 | Stress | Von Mises, principal | OP2, element set | | E4 | Mass (BDF) | Total mass | BDF file | | E5 | Mass (Expr) | NX expression | NX session | | E8-10 | Zernike | OPD polynomial fit | OP2, grid config | **Custom Extractor Pattern**: ```python def extract_volume(op2_path: str) -> Dict[str, float]: from pyNastran.op2.op2 import OP2 op2 = OP2() op2.read_op2(op2_path) # ... calculation return {"volume_mm3": calculated_volume} ``` ### 4.3 Trial Storage **Folder Structure**: ``` studies/{study}/2_iterations/ ├── trial_0001/ │ ├── params.json # Input parameters │ ├── results.json # Objectives/constraints │ ├── _meta.json # Metadata │ └── *.op2, *.fem # FEA outputs ├── trial_0002/ └── ... ``` **Database Schema** (Optuna-compatible SQLite): ```sql trials (trial_id, study_id, number, state, created_at) trial_params (trial_id, param_name, param_value) trial_values (trial_id, objective_id, value) trial_user_attributes (trial_id, key, value_json) ``` --- ## 5. Claude Integration ### 5.1 Two Operation Modes | Mode | Endpoint | Capabilities | Use Case | |------|----------|--------------|----------| | **User** | `/ws` | Read-only, MCP tools | Safe exploration | | **Power** | `/ws/power` | Full write access | Canvas modification | ### 5.2 Power Mode Tools ```python # claude_agent.py - Direct API tools add_design_variable(name, min, max, baseline, units) add_extractor(name, type, config, custom_code) add_objective(name, direction, weight, extractor_id) add_constraint(name, operator, threshold, extractor_id) update_spec_field(path, value) # JSONPath update remove_node(node_id) ``` ### 5.3 Context Building The `ContextBuilder` assembles rich system prompts: ``` # Atomizer Assistant ## Current Mode: POWER (full write access) ## Current Study: bracket_optimization - Design Variables: 3 (thickness, angle, radius) - Extractors: 2 (Displacement, Mass) - Objectives: 2 (Min mass, Max stiffness) - Constraints: 1 (mass <= 0.2 kg) - Status: 47/100 trials complete ## Canvas State 8 nodes, 11 edges [Node list with IDs and types...] ## Available Tools - add_design_variable: Add a new design variable - add_extractor: Add physics extractor - add_objective: Add optimization objective - add_constraint: Add constraint - update_spec_field: Update any field by JSONPath - remove_node: Remove element by ID **ACT IMMEDIATELY** when asked to modify things. ``` --- ## 6. Data Flow Diagrams ### 6.1 Canvas Edit Flow ``` User edits node property │ ▼ ┌─────────────────────────────┐ │ useSpecStore.patchSpec() │ │ ┌─────────────────────────┐ │ │ │ 1. Optimistic UI update │ │ │ └───────────┬─────────────┘ │ │ │ │ │ ┌───────────▼─────────────┐ │ │ │ 2. Async PATCH request │ │ │ └───────────┬─────────────┘ │ └─────────────┼───────────────┘ │ ▼ ┌─────────────────────────────┐ │ Backend: SpecManager │ │ ┌─────────────────────────┐ │ │ │ 3. JSONPath parse │ │ │ │ 4. Apply modification │ │ │ │ 5. Pydantic validate │ │ │ │ 6. Atomic file write │ │ │ │ 7. Compute new hash │ │ │ │ 8. Broadcast to clients │ │ │ └───────────┬─────────────┘ │ └─────────────┼───────────────┘ │ ▼ ┌─────────────────────────────┐ │ All WebSocket Clients │ │ ┌─────────────────────────┐ │ │ │ 9. Receive spec_updated │ │ │ │ 10. Update local hash │ │ │ │ 11. Re-render if needed │ │ │ └─────────────────────────┘ │ └─────────────────────────────┘ ``` ### 6.2 Optimization Run Flow ``` User clicks "Run" │ ▼ ┌─────────────────────────────┐ │ POST /api/optimization/start│ │ { study_id, trials, method }│ └─────────────┬───────────────┘ │ ▼ ┌─────────────────────────────┐ │ Backend: Spawn runner │ │ ┌─────────────────────────┐ │ │ │ 1. Load spec │ │ │ │ 2. Initialize Optuna │ │ │ │ 3. Create trial folders │ │ │ └───────────┬─────────────┘ │ └─────────────┼───────────────┘ │ ┌─────────┼─────────┐ │ │ │ ▼ ▼ ▼ ┌───────────────────────────────────────────────────────┐ │ For each trial (1 to N): │ │ ┌───────────────────────────────────────────────────┐ │ │ │ 4. Optuna suggests parameters │ │ │ │ 5. Update NX expressions │ │ │ │ 6. Run Nastran simulation │ │ │ │ 7. Extract physics results │ │ │ │ 8. Compute objectives/constraints │ │ │ │ 9. Save to trial folder + database │ │ │ │ 10. Send WebSocket update │ │ │ └─────────────────────────────────────────────────┘ │ └───────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────┐ │ Frontend: Real-time updates │ │ ┌─────────────────────────┐ │ │ │ Update convergence plot │ │ │ │ Update trial table │ │ │ │ Show best design │ │ │ └─────────────────────────┘ │ └─────────────────────────────┘ ``` ### 6.3 Claude Canvas Modification Flow ``` User: "Add volume extractor with constraint <= 1000" │ ▼ ┌─────────────────────────────┐ │ WebSocket: /ws/power │ │ { type: 'message', │ │ content: '...', │ │ canvas_state: {...} } │ └─────────────┬───────────────┘ │ ▼ ┌─────────────────────────────┐ │ AtomizerClaudeAgent │ │ ┌─────────────────────────┐ │ │ │ 1. Build context prompt │ │ │ │ 2. Send to Claude API │ │ │ │ 3. Claude decides tools │ │ │ └───────────┬─────────────┘ │ └─────────────┼───────────────┘ │ ▼ ┌─────────────────────────────┐ │ Claude Tool Calls: │ │ ┌─────────────────────────┐ │ │ │ add_extractor( │ │ │ │ name="Volume", │ │ │ │ type="custom", │ │ │ │ code="..." ) │ │ │ │ │ │ │ │ add_constraint( │ │ │ │ name="Max Volume", │ │ │ │ operator="<=", │ │ │ │ threshold=1000 ) │ │ │ └───────────┬─────────────┘ │ └─────────────┼───────────────┘ │ ▼ ┌─────────────────────────────┐ │ Each tool modifies spec: │ │ ┌─────────────────────────┐ │ │ │ Load → Modify → Save │ │ │ │ Send spec_modified │ │ │ └───────────┬─────────────┘ │ └─────────────┼───────────────┘ │ ▼ ┌─────────────────────────────┐ │ Frontend receives events: │ │ ┌─────────────────────────┐ │ │ │ spec_modified → reload │ │ │ │ Canvas shows new nodes │ │ │ └─────────────────────────┘ │ └─────────────────────────────┘ ``` --- ## 7. Component Relationships ### 7.1 Frontend Component Hierarchy ``` ├── │ ├── │ │ ├── │ │ │ ├── × N │ │ │ ├── × N │ │ │ ├── × N │ │ │ ├── × N │ │ │ ├── │ │ │ ├── │ │ │ └── │ │ ├── │ │ ├── │ │ ├── │ │ └── │ └── │ ├── × N │ └── × M ├── │ └── └── └── ``` ### 7.2 Backend Service Dependencies ``` FastAPI App │ ├── spec.py ─────────► SpecManager │ ├── Pydantic Models │ └── File I/O + Hash │ ├── claude.py ───────► AtomizerClaudeAgent │ ├── ContextBuilder │ ├── Anthropic Client │ └── Write Tools │ ├── optimization.py ─► Runner Process │ ├── TrialManager │ ├── NX Solver │ └── Extractors │ └── WebSocket Hub ◄─── All routes broadcast ``` --- ## 8. Critical Patterns ### 8.1 Modification Pattern **Always use SpecManager for modifications:** ```python # ❌ WRONG: Direct file write with open("atomizer_spec.json", "w") as f: json.dump(spec, f) # ✅ CORRECT: Use SpecManager manager = SpecManager(study_path) spec = manager.load_spec() spec.objectives[0].weight = 2.0 manager.save_spec(spec) ``` ### 8.2 Optimistic Update Pattern ```typescript // 1. Update UI immediately setSpec(modifiedSpec); // 2. Async sync to backend patchSpec(path, value) .then(({ hash }) => setHash(hash)) .catch(() => setSpec(originalSpec)); // Rollback on failure ``` ### 8.3 WebSocket Sync Pattern ``` Client A ─────► Server ─────► Client B │ │ │ │ PATCH │ │ ├─────────────►│ │ │ │ broadcast │ │ ├─────────────►│ │ │ │ │◄─────────────┤◄─────────────┤ │ ack + hash │ │ ``` --- ## 9. Potential Improvements ### 9.1 Current Limitations 1. **No Undo/Redo**: Canvas modifications are immediate 2. **Single File Lock**: No distributed locking for multi-user 3. **Memory-only Sessions**: Session state lost on restart 4. **Limited Offline**: Requires backend connection ### 9.2 Recommended Enhancements | Priority | Enhancement | Benefit | |----------|-------------|---------| | High | Operation history with undo | Better UX | | High | Persistent sessions (Redis) | Scalability | | Medium | Spec versioning/branching | Experimentation | | Medium | Batch operations API | Performance | | Low | Offline canvas editing | Flexibility | --- ## 10. Conclusion Atomizer's architecture is **well-designed for its purpose**: enabling engineers to configure and run FEA optimizations through a visual interface with AI assistance. **Strongest Points**: - Single source of truth eliminates sync issues - Pydantic ensures data integrity - WebSocket enables real-time collaboration - Optimistic updates provide responsive UX **Areas for Attention**: - Add undo/redo for canvas operations - Consider persistent session storage for production - Expand test coverage for spec migrations The architecture is **production-ready** for single-user/small-team scenarios and can be enhanced for enterprise deployment.