| """ |
| tests/test_integration_phase012.py |
| =================================== |
| Integration smoke test for Phase 0/1/2. |
| |
| Verifies that all components work together in a single end-to-end flow: |
| Phase 0: ExperimentState, SafetyCritic, HITLManager, GovernanceLogger |
| Phase 1: DQCGatekeeper, InspectionAgent (with or without trained models) |
| Phase 2: BeliefUpdater, ActiveSensingPolicy, MultiResBO, StateMachine, ExecutiveAgent |
| |
| This test does NOT require trained model checkpoints — it runs with |
| InspectionAgent in stub mode (untrained ensemble, no OOD detector). |
| |
| Usage: |
| pytest tests/test_integration_phase012.py -v |
| """ |
|
|
| import pytest |
| import tempfile |
| import numpy as np |
|
|
| |
| from qdot.core.types import ChargeLabel, DQCQuality, MeasurementModality, TuningStage, VoltagePoint, HITLOutcome |
| from qdot.core.state import ExperimentState |
| from qdot.core.governance import GovernanceLogger |
| from qdot.core.hitl import HITLManager |
| from qdot.hardware.safety import SafetyCritic |
| from qdot.simulator.cim import CIMSimulatorAdapter |
|
|
| |
| from qdot.perception.dqc import DQCGatekeeper |
| from qdot.perception.inspector import InspectionAgent |
|
|
| |
| from qdot.planning.belief import BeliefUpdater, CIMObservationModel |
| from qdot.planning.sensing import ActiveSensingPolicy |
| from qdot.planning.bayesian_opt import MultiResBO |
| from qdot.planning.state_machine import StateMachine, bootstrap_result |
| from qdot.agent.executive import ExecutiveAgent |
| from qdot.agent.translator import TranslationAgent |
|
|
|
|
| class TestPhase012Integration: |
| """Integration tests across all three phases.""" |
|
|
| def test_full_pipeline_single_measurement(self): |
| """ |
| End-to-end: Take one measurement and pass it through the full pipeline. |
| |
| Flow: |
| DeviceAdapter → Measurement |
| → DQCGatekeeper → DQCResult |
| → InspectionAgent → (Classification, OODResult) |
| → BeliefUpdater → updates ExperimentState.belief |
| → SafetyCritic → clips voltage move |
| → GovernanceLogger → logs Decision |
| """ |
| |
| state = ExperimentState.new(device_id="integration_test") |
| adapter = CIMSimulatorAdapter(device_id="integration_test", seed=42) |
| dqc = DQCGatekeeper() |
| inspector = InspectionAgent(ensemble=None, ood_detector=None) |
| belief_updater = BeliefUpdater(belief=state.belief) |
| safety = SafetyCritic(voltage_bounds=state.voltage_bounds) |
|
|
| with tempfile.TemporaryDirectory() as tmpdir: |
| governance = GovernanceLogger(run_id=state.run_id, log_dir=tmpdir) |
|
|
| |
| measurement = adapter.sample_patch( |
| v1_range=(-0.5, 0.5), |
| v2_range=(-0.5, 0.5), |
| res=16, |
| ) |
| state.add_measurement(measurement) |
| assert measurement.is_2d |
| assert measurement.array.shape == (16, 16) |
|
|
| |
| dqc_result = dqc.assess(measurement) |
| state.add_dqc_result(dqc_result) |
| assert dqc_result.quality in (DQCQuality.HIGH, DQCQuality.MODERATE, DQCQuality.LOW) |
|
|
| |
| if dqc_result.quality != DQCQuality.LOW: |
| classification, ood_result = inspector.inspect(measurement, dqc_result) |
| state.add_classification(classification) |
| state.add_ood_result(ood_result) |
|
|
| assert classification.label in (ChargeLabel.SINGLE_DOT, ChargeLabel.DOUBLE_DOT, ChargeLabel.MISC) |
| assert 0.0 <= classification.confidence <= 1.0 |
|
|
| |
| belief_updater.update_from_2d(measurement, classification) |
| assert len(state.belief.charge_probs) > 0 |
| assert abs(sum(state.belief.charge_probs.values()) - 1.0) < 1e-6 |
|
|
| |
| from qdot.core.types import ActionProposal |
| proposal = ActionProposal(delta_v=VoltagePoint(vg1=0.05, vg2=0.03)) |
| clipped = safety.clip(proposal, state.current_voltage) |
| verdict = safety.verify(state.current_voltage, clipped) |
|
|
| assert verdict.all_passed or not verdict.all_passed |
| if verdict.all_passed: |
| state.apply_move(clipped.safe_delta_v) |
|
|
| |
| from qdot.core.types import Decision |
| decision = Decision( |
| run_id=state.run_id, |
| step=state.step, |
| intent="integration_test", |
| stage=state.stage, |
| observation_summary={"measurement_id": str(measurement.id)}, |
| action_summary={"voltage_move": "applied" if verdict.all_passed else "rejected"}, |
| rationale="Integration test", |
| ) |
| state.add_decision(decision) |
| governance.log(decision) |
|
|
| |
| assert state.total_measurements > 0 |
| assert len(state.decisions) >= 1 |
| assert len(state.trajectory) >= 1 |
|
|
| def test_executive_agent_bootstrap_stage(self): |
| """ |
| ExecutiveAgent runs through the BOOTSTRAPPING stage. |
| |
| Verifies: |
| - Agent constructs without errors |
| - Bootstrap stage takes at least one measurement |
| - State machine advances or retries appropriately |
| - All Phase 0/1/2 components are called correctly |
| """ |
| state = ExperimentState.new(device_id="bootstrap_test") |
| adapter = CIMSimulatorAdapter(device_id="bootstrap_test", seed=42) |
| inspector = InspectionAgent(ensemble=None, ood_detector=None) |
|
|
| hitl = HITLManager(enabled=True) |
| hitl.set_test_mode(auto_outcome=HITLOutcome.APPROVED) |
|
|
| with tempfile.TemporaryDirectory() as tmpdir: |
| governance = GovernanceLogger(run_id=state.run_id, log_dir=tmpdir) |
|
|
| agent = ExecutiveAgent( |
| state=state, |
| adapter=adapter, |
| inspection_agent=inspector, |
| hitl_manager=hitl, |
| governance_logger=governance, |
| max_steps=3, |
| measurement_budget=256, |
| ) |
|
|
| |
| result = agent._run_bootstrap() |
|
|
| |
| assert result.success in (True, False) |
| assert state.total_measurements > 0 |
| assert state.stage == TuningStage.BOOTSTRAPPING |
|
|
| def test_state_machine_with_real_state(self): |
| """State machine correctly updates ExperimentState during transitions.""" |
| state = ExperimentState.new(device_id="state_machine_test") |
| sm = StateMachine(state=state) |
|
|
| |
| result = bootstrap_result(device_responds=True, signal_detected=True) |
| new_stage, rationale, hitl = sm.process_result(result) |
|
|
| assert new_stage == TuningStage.COARSE_SURVEY |
| assert state.stage == TuningStage.COARSE_SURVEY |
| assert state.consecutive_backtracks == 0 |
| assert not hitl |
|
|
| def test_belief_updater_with_real_measurements(self): |
| """BeliefUpdater correctly processes real CIM measurements.""" |
| state = ExperimentState.new(device_id="belief_test") |
| adapter = CIMSimulatorAdapter(device_id="belief_test", seed=42) |
| belief_updater = BeliefUpdater(belief=state.belief) |
|
|
| |
| measurement = adapter.sample_patch((-0.3, 0.3), (-0.3, 0.3), res=8) |
|
|
| |
| entropy_before = state.belief.entropy() |
| belief_updater.update_from_2d(measurement) |
| entropy_after = state.belief.entropy() |
|
|
| |
| assert len(state.belief.charge_probs) > 0 |
| assert abs(sum(state.belief.charge_probs.values()) - 1.0) < 1e-5 |
| assert entropy_after != float("inf") |
|
|
| def test_bayesian_opt_with_real_bo_history(self): |
| """MultiResBO correctly proposes moves using real BOPoint history.""" |
| state = ExperimentState.new(device_id="bo_test") |
|
|
| |
| from qdot.core.types import BOPoint |
| state.bo_history.append(BOPoint( |
| voltage=VoltagePoint(vg1=0.0, vg2=0.0), |
| score=0.3, |
| label=ChargeLabel.MISC, |
| confidence=0.5, |
| step=1, |
| )) |
| state.bo_history.append(BOPoint( |
| voltage=VoltagePoint(vg1=0.1, vg2=0.1), |
| score=0.7, |
| label=ChargeLabel.DOUBLE_DOT, |
| confidence=0.8, |
| step=2, |
| )) |
|
|
| bo = MultiResBO(belief=state.belief, voltage_bounds=state.voltage_bounds) |
| bo.update(state.bo_history) |
|
|
| |
| proposal = bo.propose(current=state.current_voltage, l1_max=0.10) |
|
|
| assert proposal.delta_v.l1_norm <= 0.10 + 1e-6 |
| assert proposal.expected_new_voltage is not None |
|
|
| def test_active_sensing_with_real_belief(self): |
| """ActiveSensingPolicy selects measurements using real belief state.""" |
| state = ExperimentState.new(device_id="sensing_test") |
| state.belief.initialise_uniform() |
|
|
| policy = ActiveSensingPolicy(n_mc_samples=4) |
| plan = policy.select(state.belief, v1_range=(-0.5, 0.5), v2_range=(-0.5, 0.5)) |
|
|
| assert plan.modality in MeasurementModality |
| if plan.modality != MeasurementModality.NONE: |
| assert plan.info_gain_per_cost >= 0.0 |
|
|
| def test_translator_with_real_adapter(self): |
| """TranslationAgent correctly executes plans on real CIM adapter.""" |
| adapter = CIMSimulatorAdapter(device_id="translator_test", seed=42) |
| translator = TranslationAgent(adapter=adapter) |
|
|
| from qdot.core.types import MeasurementPlan |
| plan = MeasurementPlan( |
| modality=MeasurementModality.COARSE_2D, |
| v1_range=(-0.3, 0.3), |
| v2_range=(-0.3, 0.3), |
| resolution=8, |
| ) |
|
|
| result = translator.execute(plan) |
|
|
| assert result.success |
| assert result.measurement is not None |
| assert result.measurement.array.shape == (8, 8) |
|
|
| def test_full_agent_run_completes(self): |
| """ |
| ExecutiveAgent.run() completes without errors (even if target not reached). |
| |
| This is the ultimate integration test — every Phase 0/1/2 component |
| must work together correctly for this to succeed. |
| """ |
| state = ExperimentState.new(device_id="full_run_test") |
| adapter = CIMSimulatorAdapter(device_id="full_run_test", seed=42) |
| inspector = InspectionAgent(ensemble=None, ood_detector=None) |
|
|
| hitl = HITLManager(enabled=True) |
| hitl.set_test_mode(auto_outcome=HITLOutcome.APPROVED) |
|
|
| with tempfile.TemporaryDirectory() as tmpdir: |
| governance = GovernanceLogger(run_id=state.run_id, log_dir=tmpdir) |
|
|
| agent = ExecutiveAgent( |
| state=state, |
| adapter=adapter, |
| inspection_agent=inspector, |
| hitl_manager=hitl, |
| governance_logger=governance, |
| max_steps=5, |
| measurement_budget=512, |
| ) |
|
|
| summary = agent.run() |
|
|
| |
| assert summary is not None |
| assert "success" in summary |
| assert "total_steps" in summary |
| assert "total_measurements" in summary |
| assert summary["total_steps"] <= 5 |
| assert summary["total_measurements"] <= 512 |
|
|
| |
| decisions = GovernanceLogger.load(run_id=state.run_id, log_dir=tmpdir) |
| assert len(decisions) > 0 |
|
|