2026-01-20 10:03:45 -05:00
# 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**
2026-01-20 15:08:49 -05:00
| 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 |
2026-01-20 10:03:45 -05:00
---
## 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
```
<App>
├── <CanvasView>
│ ├── <AtomizerCanvas>
│ │ ├── <ReactFlow>
│ │ │ ├── <DesignVarNode> × N
│ │ │ ├── <ExtractorNode> × N
│ │ │ ├── <ObjectiveNode> × N
│ │ │ ├── <ConstraintNode> × N
│ │ │ ├── <ModelNode>
│ │ │ ├── <SolverNode>
│ │ │ └── <AlgorithmNode>
│ │ ├── <NodePalette>
│ │ ├── <NodeConfigPanel>
│ │ ├── <ValidationPanel>
│ │ └── <ExecuteDialog>
│ └── <ChatPanel>
│ ├── <ChatMessage> × N
│ └── <ToolCallCard> × M
├── <Home>
│ └── <StudyList>
└── <Setup>
└── <StudyWizard>
```
### 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.