100enigma's picture
SimQuantum — AMD Developer Hackathon
da98415
Raw
History Blame Contribute Delete
8.71 kB
"""
qdot/core/state.py
==================
ExperimentState — single source of truth for the entire agent run.
Voltage bounds default changed to ±3.0 V (was ±1.0 V).
Rationale: the benchmark draws CIM params with E_c ≈ 2–3 meV and
lever_arm ≈ 0.65–0.85, placing the charge transition at -1.5 to -2.5 V.
The old ±1.0 V default made transitions physically unreachable regardless
of navigation strategy — the agent hit the safety wall before it could
reach the first charge boundary. ±3.0 V covers the full GaAs-class
device parameter space used in the benchmark.
The Safety Critic architecture is unchanged; only the enforced bound
values are updated. Real hardware deployments should override these
defaults with device-class-appropriate values via ExperimentState.new().
"""
from __future__ import annotations
import time
import uuid
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Any
from uuid import UUID
import numpy as np
from qdot.core.types import (
ActionProposal, BacktrackEvent, BOPoint, ChargeLabel,
Classification, Decision, DQCResult, HITLEvent,
Measurement, MeasurementModality, OODResult,
SafetyVerdict, TuningStage, VoltagePoint,
)
@dataclass
class BeliefState:
"""POMDP belief state: P(charge configuration | observations, CIM prior)."""
charge_probs: Dict[tuple, float] = field(default_factory=dict)
uncertainty_map: Optional[Any] = None
device_params: Dict[str, float] = field(default_factory=lambda: {
"E_c1": 0.50,
"E_c2": 0.55,
"t_c": 0.05,
"T": 0.015,
"lever_arm": 1.0,
"noise_level": 0.01,
})
disorder_estimate: Optional[Dict[str, Any]] = None
def entropy(self) -> float:
if not self.charge_probs:
return float("inf")
probs = np.array(list(self.charge_probs.values()), dtype=float)
probs = probs[probs > 0]
return float(-np.sum(probs * np.log(probs)))
def most_likely_state(self) -> Optional[tuple]:
if not self.charge_probs:
return None
return max(self.charge_probs, key=lambda k: self.charge_probs[k])
def initialise_uniform(self, charge_states: Optional[List[tuple]] = None) -> None:
if charge_states is None:
charge_states = [(n1, n2) for n1 in range(3) for n2 in range(3)]
p = 1.0 / len(charge_states)
self.charge_probs = {s: p for s in charge_states}
@dataclass
class ExperimentState:
"""Centralised state object. All modules read from and write to this."""
run_id: str = field(default_factory=lambda: str(uuid.uuid4()))
device_id: str = ""
started_at: float = field(default_factory=time.time)
target_label: ChargeLabel = ChargeLabel.DOUBLE_DOT
current_voltage: VoltagePoint = field(default_factory=lambda: VoltagePoint(0.0, 0.0))
trajectory: List[VoltagePoint] = field(default_factory=list)
# DEFAULT BOUNDS WIDENED TO ±3.0 V (was ±1.0 V).
# Rationale: GaAs-class devices with E_c ≈ 2–3 meV and lever_arm ≈ 0.65–0.85
# place the first charge transition at -1.5 to -2.5 V. The old ±1.0 V
# constraint was an arbitrary hackathon placeholder that made the benchmark
# unsolvable by construction. Override per device class via ExperimentState.new().
voltage_bounds: Dict[str, Dict[str, float]] = field(default_factory=lambda: {
"vg1": {"min": -8.0, "max": 8.0},
"vg2": {"min": -8.0, "max": 8.0},
})
step_caps: Dict[str, float] = field(default_factory=lambda: {"l1_max": 0.10})
belief: BeliefState = field(default_factory=BeliefState)
measurements: Dict[UUID, Measurement] = field(default_factory=dict)
dqc_results: Dict[UUID, DQCResult] = field(default_factory=dict)
classifications: Dict[UUID, Classification] = field(default_factory=dict)
ood_history: List[OODResult] = field(default_factory=list)
last_classification: Optional[Classification] = None
last_ood: Optional[OODResult] = None
last_dqc: Optional[DQCResult] = None
bo_history: List[BOPoint] = field(default_factory=list)
decisions: List[Decision] = field(default_factory=list)
hitl_events: List[HITLEvent] = field(default_factory=list)
backtrack_log: List[BacktrackEvent] = field(default_factory=list)
stage: TuningStage = TuningStage.BOOTSTRAPPING
consecutive_backtracks: int = 0
total_backtracks: int = 0
total_measurements: int = 0
safety_violations: int = 0
llm_tokens_total: int = 0
config: Dict[str, Any] = field(default_factory=dict)
@classmethod
def new(
cls,
device_id: str,
target_label: ChargeLabel = ChargeLabel.DOUBLE_DOT,
voltage_bounds: Optional[Dict] = None,
config: Optional[Dict] = None,
) -> "ExperimentState":
state = cls(device_id=device_id, target_label=target_label)
if voltage_bounds:
state.voltage_bounds = voltage_bounds
if config:
state.config = config
state.belief.initialise_uniform()
state.trajectory.append(state.current_voltage)
return state
def add_measurement(self, m: Measurement) -> None:
self.measurements[m.id] = m
self._update_measurement_count(m)
def add_dqc_result(self, result: DQCResult) -> None:
self.dqc_results[result.measurement_id] = result
self.last_dqc = result
def add_classification(self, cls: Classification) -> None:
self.classifications[cls.measurement_id] = cls
self.last_classification = cls
score = cls.confidence if cls.label == self.target_label else 0.0
self.bo_history.append(BOPoint(
voltage=self.current_voltage,
score=score,
label=cls.label,
confidence=cls.confidence,
step=len(self.decisions),
))
def add_ood_result(self, result: OODResult) -> None:
self.ood_history.append(result)
self.last_ood = result
def add_decision(self, d: Decision) -> None:
self.decisions.append(d)
self.llm_tokens_total += d.llm_tokens_used
def add_hitl_event(self, event: HITLEvent) -> None:
self.hitl_events.append(event)
def apply_move(self, safe_delta: VoltagePoint) -> None:
self.current_voltage = VoltagePoint(
vg1=self.current_voltage.vg1 + safe_delta.vg1,
vg2=self.current_voltage.vg2 + safe_delta.vg2,
)
self.trajectory.append(self.current_voltage)
def record_backtrack(self, event: BacktrackEvent) -> None:
self.backtrack_log.append(event)
self.consecutive_backtracks += 1
self.total_backtracks += 1
def advance_stage(self, new_stage: TuningStage) -> None:
self.stage = new_stage
self.consecutive_backtracks = 0
def record_safety_violation(self) -> None:
self.safety_violations += 1
@property
def step(self) -> int:
return len(self.decisions)
@property
def last_confidence(self) -> float:
if self.last_classification is None:
return 0.0
return self.last_classification.confidence
@property
def last_label(self) -> ChargeLabel:
if self.last_classification is None:
return ChargeLabel.UNKNOWN
return self.last_classification.label
@property
def is_ood(self) -> bool:
if self.last_ood is None:
return False
return self.last_ood.flag
@property
def target_achieved(self) -> bool:
return (
self.last_label == self.target_label
and self.last_confidence >= self.config.get("Ct_high", 0.85)
)
@property
def elapsed_s(self) -> float:
return time.time() - self.started_at
def current_belief_summary(self) -> str:
most_likely = self.belief.most_likely_state()
entropy = self.belief.entropy()
last_cls = self.last_classification
return (
f"Step {self.step} | Stage: {self.stage.name} | "
f"Voltage: ({self.current_voltage.vg1:.3f}, {self.current_voltage.vg2:.3f}) | "
f"Most likely charge state: {most_likely} | "
f"Belief entropy: {entropy:.2f} | "
f"Last label: {last_cls.label.value if last_cls else 'none'} @ "
f"{self.last_confidence:.1%} confidence | "
f"OOD: {self.is_ood}"
)
def _update_measurement_count(self, m: Measurement) -> None:
if m.modality == MeasurementModality.LINE_SCAN:
self.total_measurements += m.steps or 128
elif m.is_2d:
res = m.resolution or 32
self.total_measurements += res * res