Files
Atomizer/docs/reviews/ARCHITECTURE_REVIEW.md
Anto01 5c419e2358 fix(canvas): Multiple fixes for drag-drop, undo/redo, and code generation
Drag-drop fixes:
- Fix Objective default data: use nested 'source' object with extractor_id/output_name
- Fix Constraint default data: use 'type' field (not constraint_type), 'threshold' (not limit)

Undo/Redo fixes:
- Remove dependency on isDirty flag (which is always false due to auto-save)
- Record snapshots based on actual spec changes via deep comparison

Code generation improvements:
- Update system prompt to support multiple extractor types:
  * OP2-based extractors for FEA results (stress, displacement, frequency)
  * Expression-based extractors for NX model values (dimensions, volumes)
  * Computed extractors for derived values (no FEA needed)
- Claude will now choose appropriate signature based on user's description
2026-01-20 15:08:49 -05:00

26 KiB
Raw Blame History

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.

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.

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:

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):

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

# 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:

# ❌ 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

// 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
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.