use deq_runtime::bin::{self, instruction};
use deq_runtime::coordinator::coordinator_server::Coordinator;
use deq_runtime::coordinator::monolithic_coordinator::MonolithicCoordinator;
use deq_runtime::decoder::{DynDecoder, MockDecoder};
use deq_runtime::util::{BitMatrix, BitVector};
use std::sync::Arc;
use tonic::Request;
fn make_mock_decoder() -> Arc<MockDecoder> {
Arc::new(MockDecoder::new())
}
fn make_coordinator(mock: Arc<MockDecoder>) -> MonolithicCoordinator {
let config = serde_json::json!({
"persistent_decoder": false,
"merge_hyperedges": false
});
MonolithicCoordinator::new(config, DynDecoder::Mock(mock))
}
#[test]
#[should_panic(expected = "forced_gap does not support loss_strategy \"handoff\"")]
fn forced_gap_rejects_structured_loss_handoff() {
MonolithicCoordinator::new(
serde_json::json!({
"forced_gap": true,
"loss_strategy": "handoff"
}),
DynDecoder::Mock(make_mock_decoder()),
);
}
fn make_gadget(gid: u64, gtype: u64, connectors: Vec<(u64, u64)>) -> bin::Gadget {
bin::Gadget {
gid,
gtype,
connectors: connectors
.into_iter()
.map(|(gid, port)| bin::gadget::Connector { gid, port })
.collect(),
..Default::default()
}
}
fn make_check_model(cid: u64, ctype: u64, gid: u64) -> bin::CheckModel {
bin::CheckModel {
cid,
ctype,
gid,
..Default::default()
}
}
fn make_error_model(eid: u64, etype: u64, cid: u64) -> bin::ErrorModel {
bin::ErrorModel {
eid,
etype,
cid,
..Default::default()
}
}
fn make_canonical_library() -> bin::Library {
let port_type = bin::PortType {
ptype: 1,
observables: vec![], ..Default::default()
};
let gadget_type = bin::GadgetType {
gtype: 1,
inputs: vec![],
outputs: vec![bin::gadget_type::Port {
ptype: 1,
..Default::default()
}],
measurements: vec![bin::gadget_type::Measurement::default()],
readouts: vec![bin::gadget_type::Readout {
measurement_indices: vec![0],
..Default::default()
}],
correction_propagation: Some(BitMatrix {
rows: 0,
cols: 1,
..Default::default()
}),
readout_propagation: Some(BitMatrix {
rows: 1,
cols: 1,
..Default::default()
}),
logical_correction: Some(BitMatrix {
rows: 0,
cols: 1,
..Default::default()
}),
physical_correction: Some(BitMatrix {
rows: 0,
cols: 1,
..Default::default()
}),
..Default::default()
};
let check_model_type = bin::CheckModelType {
ctype: 1,
gtype: 1,
checks: vec![bin::check_model_type::Check {
measurements: vec![bin::check_model_type::RemoteMeasurement {
measurement_index: 0,
remote_gadget: None,
}],
naturally_flipped: false,
..Default::default()
}],
remote_gadgets: vec![],
..Default::default()
};
let error_model_type = bin::ErrorModelType {
etype: 1,
ctype: 1,
errors: vec![bin::error_model_type::Error {
probability: 0.1,
checks: vec![bin::error_model_type::RemoteCheck {
check_index: 0,
remote_check_model: None,
}],
residual: vec![],
readout_flips: vec![],
..Default::default()
}],
remote_check_models: vec![],
..Default::default()
};
bin::Library {
port_types: vec![port_type],
gadget_types: vec![gadget_type],
check_model_types: vec![check_model_type],
error_model_types: vec![error_model_type],
..Default::default()
}
}
fn forced_gap_library() -> bin::Library {
let mut library = make_canonical_library();
library.gadget_types[0].outputs.clear();
let mut alternative = library.error_model_types[0].errors[0].clone();
alternative.probability = 0.02;
library.error_model_types[0].errors[0].readout_flips = vec![0];
library.error_model_types[0].errors.push(alternative);
library
}
async fn run_forced_gap_shot(
coordinator: &MonolithicCoordinator,
probability: Option<f64>,
) -> Result<deq_runtime::coordinator::Readouts, tonic::Status> {
for create in [
instruction::Create::Gadget(make_gadget(1, 1, vec![])),
instruction::Create::CheckModel(make_check_model(1, 1, 1)),
instruction::Create::ErrorModel(make_error_model(1, 1, 1)),
] {
Coordinator::execute(coordinator, Request::new(bin::Instruction { create: Some(create) })).await?;
}
Coordinator::decode(
coordinator,
Request::new(deq_runtime::coordinator::Outcomes {
gid: 1,
outcomes: Some(BitVector { size: 1, data: vec![0] }),
modifiers: probability
.map(|probability| bin::ProbabilityModifier {
probabilities: vec![probability, 0.02],
..Default::default()
})
.into_iter()
.collect(),
..Default::default()
}),
)
.await
.map(tonic::Response::into_inner)
}
#[tokio::test]
async fn correction_statistics_are_reported_and_reset() {
for persistent_decoder in [false, true] {
for probability in [None, Some(0.4)] {
let mock = make_mock_decoder();
let coordinator = MonolithicCoordinator::new(
serde_json::json!({
"persistent_decoder": persistent_decoder,
"merge_hyperedges": false,
"assert_parity_factor": true,
}),
DynDecoder::Mock(Arc::clone(&mock)),
);
Coordinator::load_library(&coordinator, Request::new(forced_gap_library()))
.await
.unwrap();
for selection in [vec![0, 1], vec![], vec![0, 1]] {
let count = selection.len();
mock.set_response(vec![0], selection).await;
let result = run_forced_gap_shot(&coordinator, probability).await.unwrap();
assert!(result.probabilities.is_empty());
assert_eq!(result.syndrome_count, 0);
assert_eq!(result.correction_count, count as u64);
let expected_weight = if count == 0 {
0.0
} else {
deq_runtime::misc::util::weight_of(probability.unwrap_or(0.1)) + deq_runtime::misc::util::weight_of(0.02)
};
assert!((result.correction_weight - expected_weight).abs() < 1e-12);
reset_keeping_library_and_decoder(&coordinator).await;
}
let state = mock.state.read().await;
assert_eq!(state.decode_calls.len() + state.decode_loaded_calls.len(), 3);
}
}
}
#[tokio::test]
async fn correction_statistics_count_each_gadget_separately() {
for persistent_decoder in [false, true] {
let mut library = make_canonical_library();
let mut terminal = library.gadget_types[0].clone();
terminal.gtype = 2;
terminal.inputs = terminal.outputs.clone();
terminal.outputs.clear();
let mut terminal_check = library.check_model_types[0].clone();
terminal_check.ctype = 2;
terminal_check.gtype = 2;
let mut terminal_errors = library.error_model_types[0].clone();
terminal_errors.etype = 2;
terminal_errors.ctype = 2;
let second_error = library.error_model_types[0].errors[0].clone();
library.error_model_types[0].errors.push(second_error);
library.gadget_types.push(terminal);
library.check_model_types.push(terminal_check);
library.error_model_types.push(terminal_errors);
let mock = make_mock_decoder();
let coordinator = MonolithicCoordinator::new(
serde_json::json!({
"persistent_decoder": persistent_decoder,
"merge_hyperedges": false,
"assert_parity_factor": true,
}),
DynDecoder::Mock(Arc::clone(&mock)),
);
Coordinator::load_library(&coordinator, Request::new(library)).await.unwrap();
for split_between_gadgets in [true, false] {
let syndrome = if split_between_gadgets { 0xc0 } else { 0 };
let selection = if split_between_gadgets { vec![0, 2] } else { vec![0, 1] };
mock.set_response(vec![syndrome], selection).await;
for create in [
instruction::Create::Gadget(make_gadget(1, 1, vec![])),
instruction::Create::CheckModel(make_check_model(1, 1, 1)),
instruction::Create::ErrorModel(make_error_model(1, 1, 1)),
instruction::Create::Gadget(make_gadget(2, 2, vec![(1, 0)])),
instruction::Create::CheckModel(make_check_model(2, 2, 2)),
instruction::Create::ErrorModel(make_error_model(2, 2, 2)),
] {
Coordinator::execute(&coordinator, Request::new(bin::Instruction { create: Some(create) }))
.await
.unwrap();
}
let outcomes = |gid| {
Request::new(deq_runtime::coordinator::Outcomes {
gid,
outcomes: Some(BitVector {
size: 1,
data: vec![if split_between_gadgets { 0x80 } else { 0 }],
}),
..Default::default()
})
};
let (source, terminal) = tokio::join!(
Coordinator::decode(&coordinator, outcomes(1)),
Coordinator::decode(&coordinator, outcomes(2)),
);
let expected_counts = if split_between_gadgets { [1, 1] } else { [2, 0] };
for (result, count) in [source, terminal].into_iter().zip(expected_counts) {
let result = result.unwrap().into_inner();
assert_eq!(result.syndrome_count, u64::from(split_between_gadgets));
assert_eq!(result.correction_count, count);
assert!((result.correction_weight - count as f64 * deq_runtime::misc::util::weight_of(0.1)).abs() < 1e-12);
}
reset_keeping_library_and_decoder(&coordinator).await;
}
}
}
#[tokio::test]
async fn forced_gap_scores_match_across_cache_merge_and_reweight_modes() {
for persistent_decoder in [false, true] {
for merge_hyperedges in [false, true] {
for decoder_reweighting in ["auto", "disabled"] {
let mock = make_mock_decoder();
mock.set_response(vec![0b0100_0000], vec![0, 1]).await;
let coordinator = MonolithicCoordinator::new(
serde_json::json!({
"forced_gap": true,
"persistent_decoder": persistent_decoder,
"merge_hyperedges": merge_hyperedges,
"decoder_reweighting": decoder_reweighting
}),
DynDecoder::Mock(Arc::clone(&mock)),
);
Coordinator::load_library(&coordinator, Request::new(forced_gap_library()))
.await
.unwrap();
for probability in [None, Some(0.4), None] {
let readouts = run_forced_gap_shot(&coordinator, probability).await.unwrap();
assert_eq!(readouts.readouts, Some(BitVector { size: 1, data: vec![0] }));
assert_eq!(readouts.probabilities.len(), 1);
let prior = probability.unwrap_or(0.1);
let relative_weight = prior * 0.02 / ((1.0 - prior) * 0.98);
let expected = relative_weight / (1.0 + relative_weight);
assert!((readouts.probabilities[0] - expected).abs() < 1e-12);
reset_keeping_library_and_decoder(&coordinator).await;
}
let state = mock.state.read().await;
assert_eq!(state.loaded_hypergraphs.len(), if persistent_decoder { 2 } else { 0 });
}
}
}
}
#[tokio::test]
async fn forced_gap_search_failure_reaches_monolithic_caller() {
for persistent_decoder in [false, true] {
for separate_gap_decoder in [false, true] {
let hard = make_mock_decoder();
if separate_gap_decoder {
hard.set_response(vec![0b0100_0000], vec![0, 1]).await;
}
let coordinator = MonolithicCoordinator::with_gap_decoder(
serde_json::json!({ "forced_gap": true, "persistent_decoder": persistent_decoder }),
DynDecoder::Mock(hard),
separate_gap_decoder.then(|| DynDecoder::Mock(make_mock_decoder())),
);
Coordinator::load_library(&coordinator, Request::new(forced_gap_library()))
.await
.unwrap();
let error = tokio::time::timeout(std::time::Duration::from_secs(2), run_forced_gap_shot(&coordinator, None))
.await
.expect("failed scoring must release every waiting caller")
.unwrap_err();
assert_eq!(error.code(), tonic::Code::Internal);
assert!(error.message().contains("reachable forced-gap constraint"));
}
}
}
#[tokio::test]
async fn separate_gap_decoder_preserves_hard_corrections() {
use deq_runtime::decoder::DecoderFeatures;
for persistent_decoder in [false, true] {
for (hard_features, gap_features) in [
(DecoderFeatures::REWEIGHTS, DecoderFeatures::REWEIGHTS),
(DecoderFeatures::REWEIGHTS, DecoderFeatures::empty()),
(DecoderFeatures::empty(), DecoderFeatures::REWEIGHTS),
] {
let hard = Arc::new(MockDecoder::with_features(hard_features));
let gap = Arc::new(MockDecoder::with_features(gap_features));
gap.set_response(vec![0b0100_0000], vec![0, 1]).await;
let coordinator = MonolithicCoordinator::with_gap_decoder(
serde_json::json!({
"forced_gap": true,
"persistent_decoder": persistent_decoder,
"merge_hyperedges": false,
}),
DynDecoder::Mock(Arc::clone(&hard)),
Some(DynDecoder::Mock(Arc::clone(&gap))),
);
Coordinator::load_library(&coordinator, Request::new(forced_gap_library()))
.await
.unwrap();
for probability in [None, Some(0.4), None] {
let readouts = run_forced_gap_shot(&coordinator, probability).await.unwrap();
assert_eq!(readouts.readouts, Some(BitVector { size: 1, data: vec![0] }));
assert_eq!(readouts.correction_count, 0);
assert_eq!(readouts.correction_weight, 0.0);
let prior = probability.unwrap_or(0.1);
let odds = prior * 0.02 / ((1.0 - prior) * 0.98);
assert!((readouts.probabilities[0] - odds / (1.0 + odds)).abs() < 1e-12);
reset_keeping_library_and_decoder(&coordinator).await;
}
for decoder in [&hard, &gap] {
let state = decoder.state.read().await;
assert_eq!(state.decode_calls.len() + state.decode_loaded_calls.len(), 3);
assert_eq!(state.loaded_hypergraphs.len(), usize::from(persistent_decoder));
assert_eq!(state.reset_count, 3);
assert_eq!(
state.decode_loaded_calls.iter().any(|call| !call.reweights.is_empty()),
persistent_decoder && decoder.supported_features().contains(DecoderFeatures::REWEIGHTS),
);
}
}
}
}
#[tokio::test]
async fn test_monolithic_coordinator_load_library() {
let mock = make_mock_decoder();
let coordinator = make_coordinator(mock.clone());
Coordinator::load_library(&coordinator, Request::new(make_canonical_library()))
.await
.unwrap();
let gadget_types = coordinator.gadget_types.read().await;
assert!(gadget_types.contains_key(&1));
let check_model_types = coordinator.check_model_types.read().await;
assert!(check_model_types.contains_key(&1));
let error_model_types = coordinator.error_model_types.read().await;
assert!(error_model_types.contains_key(&1));
}
#[tokio::test]
async fn test_decode_rejects_malformed_outcomes() {
let coordinator = make_coordinator(make_mock_decoder());
Coordinator::load_library(&coordinator, Request::new(make_canonical_library()))
.await
.unwrap();
let gid = Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::Gadget(make_gadget(0, 1, vec![]))),
}),
)
.await
.unwrap()
.into_inner()
.id;
let wrong_outcome_size = Coordinator::decode(
&coordinator,
Request::new(deq_runtime::coordinator::Outcomes {
gid,
outcomes: Some(BitVector { size: 2, data: vec![0] }),
..Default::default()
}),
)
.await
.unwrap_err();
assert_eq!(wrong_outcome_size.code(), tonic::Code::InvalidArgument);
assert!(
wrong_outcome_size
.message()
.contains("outcomes size 2 does not match gadget measurement count 1")
);
let wrong_loss_mask_size = Coordinator::decode(
&coordinator,
Request::new(deq_runtime::coordinator::Outcomes {
gid,
outcomes: Some(BitVector { size: 1, data: vec![0] }),
loss_mask: Some(BitVector { size: 2, data: vec![0] }),
..Default::default()
}),
)
.await
.unwrap_err();
assert_eq!(wrong_loss_mask_size.code(), tonic::Code::InvalidArgument);
assert!(
wrong_loss_mask_size
.message()
.contains("loss_mask size 2 does not match outcomes size 1")
);
}
#[tokio::test]
async fn test_monolithic_coordinator_reset() {
let mock = make_mock_decoder();
let coordinator = make_coordinator(mock.clone());
Coordinator::load_library(&coordinator, Request::new(make_canonical_library()))
.await
.unwrap();
Coordinator::reset(
&coordinator,
Request::new(deq_runtime::coordinator::ResetRequest {
reset_library: true,
reset_decoder_service: true,
..Default::default()
}),
)
.await
.unwrap();
let gadget_types = coordinator.gadget_types.read().await;
assert!(gadget_types.is_empty());
let next_gid = *coordinator.next_gid.lock().await;
assert_eq!(next_gid, 1);
let state = mock.state.read().await;
assert_eq!(state.reset_count, 1);
}
#[tokio::test]
async fn test_monolithic_coordinator_auto_assigned_gid() {
let mock = make_mock_decoder();
let coordinator = make_coordinator(mock.clone());
Coordinator::load_library(&coordinator, Request::new(make_canonical_library()))
.await
.unwrap();
let gadget = make_gadget(0, 1, vec![]);
let response = Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::Gadget(gadget)),
}),
)
.await
.unwrap();
let gid = response.into_inner().id;
assert_eq!(gid, 1);
let gadgets = coordinator.gadgets.read().await;
assert!(gadgets.contains_key(&1));
}
#[tokio::test]
async fn test_monolithic_coordinator_user_provided_gid() {
let mock = make_mock_decoder();
let coordinator = make_coordinator(mock.clone());
Coordinator::load_library(&coordinator, Request::new(make_canonical_library()))
.await
.unwrap();
let gadget = make_gadget(100, 1, vec![]);
let response = Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::Gadget(gadget)),
}),
)
.await
.unwrap();
assert_eq!(response.into_inner().id, 100);
let gadgets = coordinator.gadgets.read().await;
assert!(gadgets.contains_key(&100));
}
#[tokio::test]
async fn test_monolithic_coordinator_mixed_gid_assignment() {
let mock = make_mock_decoder();
let coordinator = make_coordinator(mock.clone());
Coordinator::load_library(&coordinator, Request::new(make_canonical_library()))
.await
.unwrap();
let gadget1 = make_gadget(1, 1, vec![]);
let response = Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::Gadget(gadget1)),
}),
)
.await
.unwrap();
assert_eq!(response.into_inner().id, 1);
let gadget2 = make_gadget(0, 1, vec![]);
let response = Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::Gadget(gadget2)),
}),
)
.await
.unwrap();
assert_eq!(response.into_inner().id, 2);
let gadgets = coordinator.gadgets.read().await;
assert!(gadgets.contains_key(&1));
assert!(gadgets.contains_key(&2));
}
#[tokio::test]
async fn test_monolithic_coordinator_cid_assignment() {
let mock = make_mock_decoder();
let coordinator = make_coordinator(mock.clone());
Coordinator::load_library(&coordinator, Request::new(make_canonical_library()))
.await
.unwrap();
let gadget = make_gadget(0, 1, vec![]);
let gid_response = Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::Gadget(gadget)),
}),
)
.await
.unwrap();
let gid = gid_response.into_inner().id;
let check_model = make_check_model(50, 1, gid);
let response = Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::CheckModel(check_model)),
}),
)
.await
.unwrap();
assert_eq!(response.into_inner().id, 50);
let check_models = coordinator.check_models.read().await;
assert!(check_models.contains_key(&50));
}
#[tokio::test]
async fn test_monolithic_coordinator_eid_assignment() {
let mock = make_mock_decoder();
let coordinator = make_coordinator(mock.clone());
Coordinator::load_library(&coordinator, Request::new(make_canonical_library()))
.await
.unwrap();
let gadget = make_gadget(0, 1, vec![]);
let gid = Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::Gadget(gadget)),
}),
)
.await
.unwrap()
.into_inner()
.id;
let check_model = make_check_model(0, 1, gid);
let cid = Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::CheckModel(check_model)),
}),
)
.await
.unwrap()
.into_inner()
.id;
let error_model = make_error_model(99, 1, cid);
let response = Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::ErrorModel(error_model)),
}),
)
.await
.unwrap();
assert_eq!(response.into_inner().id, 99);
let error_models = coordinator.error_models.read().await;
assert!(error_models.contains_key(&99));
}
#[tokio::test]
async fn test_invalid_error_model_probability_modifier_is_atomic() {
let coordinator = make_coordinator(make_mock_decoder());
Coordinator::load_library(&coordinator, Request::new(make_canonical_library()))
.await
.unwrap();
let gid = Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::Gadget(make_gadget(0, 1, vec![]))),
}),
)
.await
.unwrap()
.into_inner()
.id;
let cid = Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::CheckModel(make_check_model(0, 1, gid))),
}),
)
.await
.unwrap()
.into_inner()
.id;
let mut error_model = make_error_model(0, 1, cid);
error_model.modifier = Some(bin::error_model::ErrorModelModifier {
probability_modifier: Some(bin::ProbabilityModifier {
probabilities: vec![0.1, 0.2],
..Default::default()
}),
..Default::default()
});
let error = Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::ErrorModel(error_model)),
}),
)
.await
.unwrap_err();
assert_eq!(error.code(), tonic::Code::InvalidArgument);
assert!(coordinator.error_models.read().await.is_empty());
assert!(coordinator.check_models.read().await[&cid].attaching_eid_vec.is_empty());
assert_eq!(*coordinator.next_eid.lock().await, 1);
}
#[tokio::test]
async fn test_oversized_remote_reroute_is_atomic() {
let coordinator = make_coordinator(make_mock_decoder());
Coordinator::load_library(&coordinator, Request::new(make_canonical_library()))
.await
.unwrap();
let gid = Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::Gadget(make_gadget(0, 1, vec![]))),
}),
)
.await
.unwrap()
.into_inner()
.id;
let mut check_model = make_check_model(0, 1, gid);
check_model.modifier = Some(bin::check_model::CheckModelModifier {
reroute_remote_gadgets: vec![bin::check_model::check_model_modifier::RerouteRemoteGadget {
remote_gadget_index: 65_536,
value: None,
}],
});
let error = Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::CheckModel(check_model)),
}),
)
.await
.unwrap_err();
assert_eq!(error.code(), tonic::Code::InvalidArgument);
assert!(coordinator.check_models.read().await.is_empty());
assert!(coordinator.gadgets.read().await[&gid].binding_cid.borrow().is_none());
assert_eq!(*coordinator.next_cid.lock().await, 1);
}
fn make_default_library() -> bin::Library {
let port_type_rep = bin::PortType {
ptype: 1,
name: "rep-code".to_string(),
observables: vec![bin::port_type::Observable {
tag: "logical_z".to_string(),
..Default::default()
}],
..Default::default()
};
let port_type_surface = bin::PortType {
ptype: 2,
name: "surface-code".to_string(),
observables: vec![
bin::port_type::Observable {
tag: "logical_x".to_string(),
..Default::default()
},
bin::port_type::Observable {
tag: "logical_z".to_string(),
..Default::default()
},
],
..Default::default()
};
let gadget_type_initialize = bin::GadgetType {
gtype: 1,
name: "initialize".to_string(),
inputs: vec![],
outputs: vec![bin::gadget_type::Port {
ptype: 1,
..Default::default()
}],
measurements: vec![
bin::gadget_type::Measurement {
tag: "m1".to_string(),
..Default::default()
},
bin::gadget_type::Measurement {
tag: "m2".to_string(),
..Default::default()
},
],
correction_propagation: Some(BitMatrix {
rows: 1,
cols: 1,
..Default::default()
}),
logical_correction: Some(BitMatrix {
rows: 1,
cols: 0,
..Default::default()
}),
readout_propagation: Some(BitMatrix {
rows: 0,
cols: 1,
..Default::default()
}),
physical_correction: Some(BitMatrix {
rows: 1,
cols: 2,
..Default::default()
}),
..Default::default()
};
let gadget_type_cnot = bin::GadgetType {
gtype: 2,
name: "cnot".to_string(),
inputs: vec![bin::gadget_type::Port {
ptype: 1,
..Default::default()
}],
outputs: vec![bin::gadget_type::Port {
ptype: 2,
..Default::default()
}],
measurements: vec![
bin::gadget_type::Measurement {
tag: "m3".to_string(),
..Default::default()
},
bin::gadget_type::Measurement {
tag: "m4".to_string(),
..Default::default()
},
],
correction_propagation: Some(BitMatrix {
rows: 2,
cols: 2,
i: vec![0, 0],
j: vec![0, 1],
}),
logical_correction: Some(BitMatrix {
rows: 2,
cols: 0,
..Default::default()
}),
readout_propagation: Some(BitMatrix {
rows: 0,
cols: 2,
..Default::default()
}),
physical_correction: Some(BitMatrix {
rows: 2,
cols: 2,
..Default::default()
}),
..Default::default()
};
let gadget_type_measure = bin::GadgetType {
gtype: 3,
name: "measure".to_string(),
inputs: vec![bin::gadget_type::Port {
ptype: 2,
..Default::default()
}],
outputs: vec![],
measurements: vec![
bin::gadget_type::Measurement {
tag: "m5".to_string(),
..Default::default()
},
bin::gadget_type::Measurement {
tag: "m6".to_string(),
..Default::default()
},
bin::gadget_type::Measurement {
tag: "m7".to_string(),
..Default::default()
},
],
readouts: vec![bin::gadget_type::Readout {
tag: "r1".to_string(),
measurement_indices: vec![0, 2],
..Default::default()
}],
readout_propagation: Some(BitMatrix {
rows: 1,
cols: 3,
i: vec![0, 0],
j: vec![0, 2],
}),
correction_propagation: Some(BitMatrix {
rows: 0,
cols: 3,
..Default::default()
}),
logical_correction: Some(BitMatrix {
rows: 0,
cols: 1,
..Default::default()
}),
physical_correction: Some(BitMatrix {
rows: 0,
cols: 3,
..Default::default()
}),
..Default::default()
};
let check_model_type_1 = bin::CheckModelType {
ctype: 1,
gtype: 2,
remote_gadgets: vec![
bin::check_model_type::RemoteGadget {
port: Some(bin::check_model_type::remote_gadget::Port::Input(0)),
expecting_gtype: 1,
measurement_bias: 1,
..Default::default()
},
bin::check_model_type::RemoteGadget {
port: Some(bin::check_model_type::remote_gadget::Port::Output(0)),
previous_remote_gadget: Some(0),
expecting_gtype: 2,
..Default::default()
},
bin::check_model_type::RemoteGadget {
port: Some(bin::check_model_type::remote_gadget::Port::Output(0)),
previous_remote_gadget: None,
expecting_gtype: 3,
..Default::default()
},
],
checks: vec![
bin::check_model_type::Check {
tag: "c1".to_string(),
measurements: vec![
bin::check_model_type::RemoteMeasurement {
measurement_index: 1,
remote_gadget: None,
},
bin::check_model_type::RemoteMeasurement {
measurement_index: 0,
remote_gadget: Some(0),
},
bin::check_model_type::RemoteMeasurement {
measurement_index: 0,
remote_gadget: Some(1),
},
bin::check_model_type::RemoteMeasurement {
measurement_index: 1,
remote_gadget: Some(2),
},
],
..Default::default()
},
bin::check_model_type::Check {
tag: "c2".to_string(),
..Default::default()
},
bin::check_model_type::Check {
tag: "c3".to_string(),
..Default::default()
},
],
..Default::default()
};
let check_model_type_2 = bin::CheckModelType {
ctype: 2,
gtype: 3,
remote_gadgets: vec![bin::check_model_type::RemoteGadget {
port: Some(bin::check_model_type::remote_gadget::Port::Input(0)),
expecting_gtype: 2,
..Default::default()
}],
checks: vec![
bin::check_model_type::Check {
tag: "c4".to_string(),
measurements: vec![
bin::check_model_type::RemoteMeasurement {
measurement_index: 0,
remote_gadget: None,
},
bin::check_model_type::RemoteMeasurement {
measurement_index: 1,
remote_gadget: None,
},
bin::check_model_type::RemoteMeasurement {
measurement_index: 2,
remote_gadget: None,
},
bin::check_model_type::RemoteMeasurement {
measurement_index: 0,
remote_gadget: Some(0),
},
],
..Default::default()
},
bin::check_model_type::Check {
tag: "c5".to_string(),
..Default::default()
},
bin::check_model_type::Check {
tag: "c6".to_string(),
..Default::default()
},
],
..Default::default()
};
let error_model_type_1 = bin::ErrorModelType {
etype: 1,
ctype: 1,
remote_check_models: vec![
bin::error_model_type::RemoteCheckModel {
port: Some(bin::error_model_type::remote_check_model::Port::Output(0)),
expecting_ctype: 2,
..Default::default()
},
bin::error_model_type::RemoteCheckModel {
port: Some(bin::error_model_type::remote_check_model::Port::Input(0)),
previous_remote_check_model: Some(0),
expecting_ctype: 1,
..Default::default()
},
bin::error_model_type::RemoteCheckModel {
port: Some(bin::error_model_type::remote_check_model::Port::Output(0)),
expecting_ctype: 2,
..Default::default()
},
],
errors: vec![bin::error_model_type::Error {
probability: 0.1,
residual: vec![0],
checks: vec![
bin::error_model_type::RemoteCheck {
check_index: 0,
remote_check_model: None,
},
bin::error_model_type::RemoteCheck {
check_index: 1,
remote_check_model: Some(1),
},
bin::error_model_type::RemoteCheck {
check_index: 1,
remote_check_model: Some(2),
},
],
..Default::default()
}],
..Default::default()
};
let error_model_type_2 = bin::ErrorModelType {
etype: 2,
ctype: 2,
remote_check_models: vec![bin::error_model_type::RemoteCheckModel {
port: Some(bin::error_model_type::remote_check_model::Port::Input(0)),
expecting_ctype: 1,
..Default::default()
}],
errors: vec![bin::error_model_type::Error {
probability: 0.1,
readout_flips: vec![0],
..Default::default()
}],
..Default::default()
};
bin::Library {
port_types: vec![port_type_rep, port_type_surface],
gadget_types: vec![gadget_type_initialize, gadget_type_cnot, gadget_type_measure],
check_model_types: vec![check_model_type_1, check_model_type_2],
error_model_types: vec![error_model_type_1, error_model_type_2],
..Default::default()
}
}
#[tokio::test]
async fn test_hypergraph_construction_with_default_library() {
let mock = make_mock_decoder();
let coordinator = make_coordinator(mock.clone());
Coordinator::load_library(&coordinator, Request::new(make_default_library()))
.await
.unwrap();
let gid1 = Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::Gadget(bin::Gadget {
gid: 0,
gtype: 1,
tag: "initialize".to_string(),
..Default::default()
})),
}),
)
.await
.unwrap()
.into_inner()
.id;
assert_eq!(gid1, 1);
let gid2 = Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::Gadget(bin::Gadget {
gid: 0,
gtype: 2,
tag: "idle".to_string(),
connectors: vec![bin::gadget::Connector { gid: 1, port: 0 }],
..Default::default()
})),
}),
)
.await
.unwrap()
.into_inner()
.id;
assert_eq!(gid2, 2);
let cid1 = Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::CheckModel(bin::CheckModel {
cid: 0,
ctype: 1,
gid: 2,
..Default::default()
})),
}),
)
.await
.unwrap()
.into_inner()
.id;
assert_eq!(cid1, 1);
let eid1 = Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::ErrorModel(bin::ErrorModel {
eid: 0,
etype: 1,
cid: 1,
..Default::default()
})),
}),
)
.await
.unwrap()
.into_inner()
.id;
assert_eq!(eid1, 1);
let gid3 = Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::Gadget(bin::Gadget {
gid: 0,
gtype: 3,
tag: "measure".to_string(),
connectors: vec![bin::gadget::Connector { gid: 2, port: 0 }],
..Default::default()
})),
}),
)
.await
.unwrap()
.into_inner()
.id;
assert_eq!(gid3, 3);
let cid2 = Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::CheckModel(bin::CheckModel {
cid: 0,
ctype: 2,
gid: 3,
..Default::default()
})),
}),
)
.await
.unwrap()
.into_inner()
.id;
assert_eq!(cid2, 2);
let eid2 = Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::ErrorModel(bin::ErrorModel {
eid: 0,
etype: 2,
cid: 2,
..Default::default()
})),
}),
)
.await
.unwrap()
.into_inner()
.id;
assert_eq!(eid2, 2);
let gadgets = coordinator.gadgets.read().await;
assert_eq!(gadgets.len(), 3, "Expected 3 gadgets");
assert!(gadgets.contains_key(&1), "gid=1 should exist");
assert!(gadgets.contains_key(&2), "gid=2 should exist");
assert!(gadgets.contains_key(&3), "gid=3 should exist");
assert_eq!(gadgets.get(&1).unwrap().instance.gtype, 1);
assert_eq!(gadgets.get(&2).unwrap().instance.gtype, 2);
assert_eq!(gadgets.get(&3).unwrap().instance.gtype, 3);
assert!(gadgets.get(&1).unwrap().instance.connectors.is_empty());
assert_eq!(gadgets.get(&2).unwrap().instance.connectors.len(), 1);
assert_eq!(gadgets.get(&2).unwrap().instance.connectors[0].gid, 1);
assert_eq!(gadgets.get(&3).unwrap().instance.connectors.len(), 1);
assert_eq!(gadgets.get(&3).unwrap().instance.connectors[0].gid, 2);
drop(gadgets);
let check_models = coordinator.check_models.read().await;
assert_eq!(check_models.len(), 2, "Expected 2 check models");
assert!(check_models.contains_key(&1), "cid=1 should exist");
assert!(check_models.contains_key(&2), "cid=2 should exist");
assert_eq!(check_models.get(&1).unwrap().instance.gid, 2);
assert_eq!(check_models.get(&1).unwrap().instance.ctype, 1);
assert_eq!(check_models.get(&2).unwrap().instance.gid, 3);
assert_eq!(check_models.get(&2).unwrap().instance.ctype, 2);
drop(check_models);
let error_models = coordinator.error_models.read().await;
assert_eq!(error_models.len(), 2, "Expected 2 error models");
assert!(error_models.contains_key(&1), "eid=1 should exist");
assert!(error_models.contains_key(&2), "eid=2 should exist");
assert_eq!(error_models.get(&1).unwrap().instance.cid, 1);
assert_eq!(error_models.get(&1).unwrap().instance.etype, 1);
assert_eq!(error_models.get(&2).unwrap().instance.cid, 2);
assert_eq!(error_models.get(&2).unwrap().instance.etype, 2);
drop(error_models);
let check_model_types = coordinator.check_model_types.read().await;
let cmt1 = check_model_types.get(&1).unwrap();
let cmt2 = check_model_types.get(&2).unwrap();
assert_eq!(cmt1.checks.len(), 3, "ctype=1 should have 3 checks");
assert_eq!(cmt2.checks.len(), 3, "ctype=2 should have 3 checks");
drop(check_model_types);
let error_model_types = coordinator.error_model_types.read().await;
let emt1 = error_model_types.get(&1).unwrap();
let emt2 = error_model_types.get(&2).unwrap();
assert_eq!(emt1.errors.len(), 1, "etype=1 should have 1 error");
assert_eq!(emt2.errors.len(), 1, "etype=2 should have 1 error");
assert!(
(emt1.errors[0].probability - 0.1).abs() < 1e-9,
"etype=1 error probability should be 0.1"
);
assert!(
(emt2.errors[0].probability - 0.1).abs() < 1e-9,
"etype=2 error probability should be 0.1"
);
assert_eq!(emt1.errors[0].checks.len(), 3, "etype=1 error should reference 3 checks");
assert_eq!(
emt2.errors[0].readout_flips.len(),
1,
"etype=2 error should have 1 readout flip"
);
}
#[tokio::test]
async fn test_decode_triggers_hypergraph_construction() {
let mock = make_mock_decoder();
let coordinator = make_coordinator(mock.clone());
Coordinator::load_library(&coordinator, Request::new(make_default_library()))
.await
.unwrap();
Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::Gadget(bin::Gadget {
gid: 0,
gtype: 1,
..Default::default()
})),
}),
)
.await
.unwrap();
Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::Gadget(bin::Gadget {
gid: 0,
gtype: 2,
connectors: vec![bin::gadget::Connector { gid: 1, port: 0 }],
..Default::default()
})),
}),
)
.await
.unwrap();
Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::CheckModel(bin::CheckModel {
cid: 0,
ctype: 1,
gid: 2,
..Default::default()
})),
}),
)
.await
.unwrap();
Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::ErrorModel(bin::ErrorModel {
eid: 0,
etype: 1,
cid: 1,
..Default::default()
})),
}),
)
.await
.unwrap();
Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::Gadget(bin::Gadget {
gid: 0,
gtype: 3,
connectors: vec![bin::gadget::Connector { gid: 2, port: 0 }],
..Default::default()
})),
}),
)
.await
.unwrap();
Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::CheckModel(bin::CheckModel {
cid: 0,
ctype: 2,
gid: 3,
..Default::default()
})),
}),
)
.await
.unwrap();
Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::ErrorModel(bin::ErrorModel {
eid: 0,
etype: 2,
cid: 2,
..Default::default()
})),
}),
)
.await
.unwrap();
let coordinator_clone1 = &coordinator;
let coordinator_clone2 = &coordinator;
let coordinator_clone3 = &coordinator;
let (result1, result2, result3) = tokio::join!(
async {
Coordinator::decode(
coordinator_clone1,
Request::new(deq_runtime::coordinator::Outcomes {
gid: 1,
outcomes: Some(BitVector { data: vec![0], size: 2 }),
..Default::default()
}),
)
.await
},
async {
Coordinator::decode(
coordinator_clone2,
Request::new(deq_runtime::coordinator::Outcomes {
gid: 2,
outcomes: Some(BitVector { data: vec![0], size: 2 }),
..Default::default()
}),
)
.await
},
async {
Coordinator::decode(
coordinator_clone3,
Request::new(deq_runtime::coordinator::Outcomes {
gid: 3,
outcomes: Some(BitVector { data: vec![0], size: 3 }),
..Default::default()
}),
)
.await
}
);
result1.unwrap();
result2.unwrap();
result3.unwrap();
let state = mock.state.read().await;
assert!(
!state.decode_calls.is_empty() || !state.loaded_hypergraphs.is_empty(),
"Expected either decode_calls or loaded_hypergraphs"
);
if !state.decode_calls.is_empty() {
let hypergraph = &state.decode_calls[0].hypergraph;
assert_eq!(hypergraph.vertex_num, 6, "Expected 6 vertices (checks) in the hypergraph");
assert_eq!(
hypergraph.hyperedges.len(),
1,
"Expected 1 hyperedge (error with checks) in the hypergraph"
);
let first_error = &hypergraph.hyperedges[0];
assert!(
(first_error.probability - 0.1).abs() < 1e-9,
"Expected probability 0.1, got {}",
first_error.probability
);
assert_eq!(first_error.vertices.len(), 3, "First error should touch 3 checks");
assert!(first_error.vertices.contains(&0), "Error should touch check 0");
assert!(first_error.vertices.contains(&1), "Error should touch check 1");
assert!(first_error.vertices.contains(&4), "Error should touch check 4");
}
}
#[tokio::test]
async fn test_remote_conditional_correction_affects_residual() {
let mock = make_mock_decoder();
let coordinator = make_coordinator(mock.clone());
let port_type = bin::PortType {
ptype: 1,
observables: vec![bin::port_type::Observable {
tag: "Z".to_string(),
..Default::default()
}],
..Default::default()
};
let gadget_type_1 = bin::GadgetType {
gtype: 1,
inputs: vec![],
outputs: vec![bin::gadget_type::Port {
ptype: 1,
..Default::default()
}],
measurements: vec![bin::gadget_type::Measurement::default()],
readouts: vec![bin::gadget_type::Readout {
measurement_indices: vec![0],
..Default::default()
}],
correction_propagation: Some(BitMatrix {
rows: 1,
cols: 1,
..Default::default()
}),
readout_propagation: Some(BitMatrix {
rows: 1,
cols: 1,
..Default::default()
}),
logical_correction: Some(BitMatrix {
rows: 1,
cols: 1,
..Default::default()
}),
physical_correction: Some(BitMatrix {
rows: 1,
cols: 1,
..Default::default()
}),
..Default::default()
};
let gadget_type_2 = bin::GadgetType {
gtype: 2,
inputs: vec![bin::gadget_type::Port {
ptype: 1,
..Default::default()
}],
outputs: vec![bin::gadget_type::Port {
ptype: 1,
..Default::default()
}],
measurements: vec![bin::gadget_type::Measurement::default()],
readouts: vec![bin::gadget_type::Readout {
measurement_indices: vec![0],
..Default::default()
}],
correction_propagation: Some(BitMatrix {
rows: 1,
cols: 2,
i: vec![0],
j: vec![0],
}),
readout_propagation: Some(BitMatrix {
rows: 1,
cols: 2,
..Default::default()
}),
logical_correction: Some(BitMatrix {
rows: 1,
cols: 1,
..Default::default()
}),
physical_correction: Some(BitMatrix {
rows: 1,
cols: 1,
..Default::default()
}),
..Default::default()
};
let gadget_type_3 = bin::GadgetType {
gtype: 3,
inputs: vec![bin::gadget_type::Port {
ptype: 1,
..Default::default()
}],
outputs: vec![],
measurements: vec![bin::gadget_type::Measurement::default()],
readouts: vec![bin::gadget_type::Readout {
measurement_indices: vec![0],
..Default::default()
}],
correction_propagation: Some(BitMatrix {
rows: 0,
cols: 2,
..Default::default()
}),
readout_propagation: Some(BitMatrix {
rows: 1,
cols: 2,
i: vec![0],
j: vec![0],
}),
logical_correction: Some(BitMatrix {
rows: 0,
cols: 1,
..Default::default()
}),
physical_correction: Some(BitMatrix {
rows: 0,
cols: 1,
..Default::default()
}),
..Default::default()
};
let check_model_type = bin::CheckModelType {
ctype: 1,
gtype: 0, checks: vec![],
..Default::default()
};
let error_model_type = bin::ErrorModelType {
etype: 1,
ctype: 0, errors: vec![],
..Default::default()
};
let library = bin::Library {
port_types: vec![port_type],
gadget_types: vec![gadget_type_1, gadget_type_2, gadget_type_3],
check_model_types: vec![check_model_type],
error_model_types: vec![error_model_type],
..Default::default()
};
Coordinator::load_library(&coordinator, Request::new(library)).await.unwrap();
Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::Gadget(bin::Gadget {
gid: 1,
gtype: 1,
..Default::default()
})),
}),
)
.await
.unwrap();
Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::CheckModel(bin::CheckModel {
cid: 1,
ctype: 1,
gid: 1,
..Default::default()
})),
}),
)
.await
.unwrap();
Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::ErrorModel(bin::ErrorModel {
eid: 1,
etype: 1,
cid: 1,
..Default::default()
})),
}),
)
.await
.unwrap();
Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::Gadget(bin::Gadget {
gid: 2,
gtype: 2,
connectors: vec![bin::gadget::Connector { gid: 1, port: 0 }],
modifier: Some(bin::GadgetModifier {
remote_conditional_correction: Some(bin::RemoteConditionalCorrection {
remote_readouts: vec![bin::remote_conditional_correction::RemoteReadout {
gid: 1,
readout_index: 0,
}],
correction: Some(BitMatrix {
rows: 1,
cols: 1,
i: vec![0],
j: vec![0],
}),
}),
..Default::default()
}),
..Default::default()
})),
}),
)
.await
.unwrap();
Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::CheckModel(bin::CheckModel {
cid: 2,
ctype: 1,
gid: 2,
..Default::default()
})),
}),
)
.await
.unwrap();
Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::ErrorModel(bin::ErrorModel {
eid: 2,
etype: 1,
cid: 2,
..Default::default()
})),
}),
)
.await
.unwrap();
Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::Gadget(bin::Gadget {
gid: 3,
gtype: 3,
connectors: vec![bin::gadget::Connector { gid: 2, port: 0 }],
..Default::default()
})),
}),
)
.await
.unwrap();
Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::CheckModel(bin::CheckModel {
cid: 3,
ctype: 1,
gid: 3,
..Default::default()
})),
}),
)
.await
.unwrap();
Coordinator::execute(
&coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::ErrorModel(bin::ErrorModel {
eid: 3,
etype: 1,
cid: 3,
..Default::default()
})),
}),
)
.await
.unwrap();
let (result1, result2, result3) = tokio::join!(
async {
Coordinator::decode(
&coordinator,
Request::new(deq_runtime::coordinator::Outcomes {
gid: 1,
outcomes: Some(BitVector {
data: vec![0x80], size: 1,
}),
..Default::default()
}),
)
.await
},
async {
Coordinator::decode(
&coordinator,
Request::new(deq_runtime::coordinator::Outcomes {
gid: 2,
outcomes: Some(BitVector {
data: vec![0x00], size: 1,
}),
..Default::default()
}),
)
.await
},
async {
Coordinator::decode(
&coordinator,
Request::new(deq_runtime::coordinator::Outcomes {
gid: 3,
outcomes: Some(BitVector {
data: vec![0x00], size: 1,
}),
..Default::default()
}),
)
.await
}
);
let readouts1 = result1.unwrap().into_inner().readouts.unwrap();
let readouts2 = result2.unwrap().into_inner().readouts.unwrap();
let readouts3 = result3.unwrap().into_inner().readouts.unwrap();
assert_eq!(readouts1.size, 1, "Gadget 1 should have 1 readout");
assert_eq!(readouts2.size, 1, "Gadget 2 should have 1 readout");
assert_eq!(readouts3.size, 1, "Gadget 3 should have 1 readout");
assert_eq!(
readouts1.data[0] & 0x80,
0x80,
"Gadget 1 readout should be 1 (from measurement)"
);
assert_eq!(
readouts2.data[0] & 0x80,
0,
"Gadget 2 readout should be 0 (measurement=0, no readout_propagation)"
);
assert_eq!(
readouts3.data[0] & 0x80,
0x80,
"Gadget 3 readout should be 1 (0 XOR input_observable from gadget 2's residual)"
);
}
fn make_persistent_coordinator(mock: Arc<MockDecoder>) -> MonolithicCoordinator {
let config = serde_json::json!({
"persistent_decoder": true,
"merge_hyperedges": false
});
MonolithicCoordinator::new(config, DynDecoder::Mock(mock))
}
#[tokio::test]
#[should_panic(expected = "the provided parity factor does not match the syndrome")]
async fn test_first_persistent_decode_checks_the_parity_factor() {
let mock = make_mock_decoder();
mock.set_response(vec![0], vec![0]).await;
let coordinator = MonolithicCoordinator::new(
serde_json::json!({
"persistent_decoder": true,
"merge_hyperedges": false,
"assert_parity_factor": true,
}),
DynDecoder::Mock(mock),
);
Coordinator::load_library(&coordinator, Request::new(make_default_library()))
.await
.unwrap();
run_canonical_shot(&coordinator, None, None, None).await;
}
async fn run_canonical_shot(
coordinator: &MonolithicCoordinator,
modifier_for_etype_1: Option<bin::ProbabilityModifier>,
modifier_for_etype_2: Option<bin::ProbabilityModifier>,
runtime_modifier_for_etype_1: Option<bin::ProbabilityModifier>,
) {
for result in run_canonical_shot_results(
coordinator,
modifier_for_etype_1,
modifier_for_etype_2,
runtime_modifier_for_etype_1,
)
.await
{
result.unwrap();
}
}
async fn run_canonical_shot_results(
coordinator: &MonolithicCoordinator,
modifier_for_etype_1: Option<bin::ProbabilityModifier>,
modifier_for_etype_2: Option<bin::ProbabilityModifier>,
runtime_modifier_for_etype_1: Option<bin::ProbabilityModifier>,
) -> [Result<deq_runtime::coordinator::Readouts, tonic::Status>; 3] {
let wrap_modifier = |pm: Option<bin::ProbabilityModifier>| {
pm.map(|p| bin::error_model::ErrorModelModifier {
probability_modifier: Some(p),
reroute_remote_check_models: vec![],
})
};
Coordinator::execute(
coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::Gadget(bin::Gadget {
gid: 0,
gtype: 1,
..Default::default()
})),
}),
)
.await
.unwrap();
Coordinator::execute(
coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::Gadget(bin::Gadget {
gid: 0,
gtype: 2,
connectors: vec![bin::gadget::Connector { gid: 1, port: 0 }],
..Default::default()
})),
}),
)
.await
.unwrap();
Coordinator::execute(
coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::CheckModel(bin::CheckModel {
cid: 0,
ctype: 1,
gid: 2,
..Default::default()
})),
}),
)
.await
.unwrap();
Coordinator::execute(
coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::ErrorModel(bin::ErrorModel {
eid: 0,
etype: 1,
cid: 1,
modifier: wrap_modifier(modifier_for_etype_1),
..Default::default()
})),
}),
)
.await
.unwrap();
Coordinator::execute(
coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::Gadget(bin::Gadget {
gid: 0,
gtype: 3,
connectors: vec![bin::gadget::Connector { gid: 2, port: 0 }],
..Default::default()
})),
}),
)
.await
.unwrap();
Coordinator::execute(
coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::CheckModel(bin::CheckModel {
cid: 0,
ctype: 2,
gid: 3,
..Default::default()
})),
}),
)
.await
.unwrap();
Coordinator::execute(
coordinator,
Request::new(bin::Instruction {
create: Some(instruction::Create::ErrorModel(bin::ErrorModel {
eid: 0,
etype: 2,
cid: 2,
modifier: wrap_modifier(modifier_for_etype_2),
..Default::default()
})),
}),
)
.await
.unwrap();
let (r1, r2, r3) = tokio::join!(
async {
Coordinator::decode(
coordinator,
Request::new(deq_runtime::coordinator::Outcomes {
gid: 1,
outcomes: Some(BitVector { data: vec![0], size: 2 }),
..Default::default()
}),
)
.await
},
async {
Coordinator::decode(
coordinator,
Request::new(deq_runtime::coordinator::Outcomes {
gid: 2,
outcomes: Some(BitVector { data: vec![0], size: 2 }),
modifiers: runtime_modifier_for_etype_1.into_iter().collect(),
..Default::default()
}),
)
.await
},
async {
Coordinator::decode(
coordinator,
Request::new(deq_runtime::coordinator::Outcomes {
gid: 3,
outcomes: Some(BitVector { data: vec![0], size: 3 }),
..Default::default()
}),
)
.await
}
);
[r1, r2, r3].map(|result| result.map(|response| response.into_inner()))
}
#[tokio::test]
async fn decoder_failures_reach_all_monolithic_requests() {
for (persistent, fail_load, warm_cache) in [
(false, false, false),
(true, true, false),
(true, false, false),
(true, false, true),
] {
let mock = make_mock_decoder();
let coordinator = MonolithicCoordinator::new(
serde_json::json!({"persistent_decoder": persistent, "merge_hyperedges": false}),
DynDecoder::Mock(mock.clone()),
);
Coordinator::load_library(&coordinator, Request::new(make_default_library()))
.await
.unwrap();
if warm_cache {
run_canonical_shot(&coordinator, None, None, None).await;
reset_keeping_library_and_decoder(&coordinator).await;
}
let expected =
tonic::Status::with_details(tonic::Code::Unavailable, "injected decoder failure", vec![1, 2, 3].into());
{
let mut state = mock.state.write().await;
if fail_load {
state.load_error = Some(expected.clone());
} else {
state.decode_error = Some(expected.clone());
}
}
let results = tokio::time::timeout(
std::time::Duration::from_secs(30),
run_canonical_shot_results(&coordinator, None, None, None),
)
.await
.expect("decoder failure must not strand subgraph requests");
for result in results {
let error = result.unwrap_err();
assert_eq!(error.code(), expected.code());
assert_eq!(error.message(), expected.message());
assert_eq!(error.details(), expected.details());
}
{
let mut state = mock.state.write().await;
assert_eq!(state.loaded_hypergraphs.len(), usize::from(persistent && !fail_load));
state.load_error = None;
state.decode_error = None;
}
reset_keeping_library_and_decoder(&coordinator).await;
tokio::time::timeout(
std::time::Duration::from_secs(30),
run_canonical_shot(&coordinator, None, None, None),
)
.await
.expect("decoding must recover after reset");
}
}
async fn reset_keeping_library_and_decoder(coordinator: &MonolithicCoordinator) {
Coordinator::reset(
coordinator,
Request::new(deq_runtime::coordinator::ResetRequest {
reset_library: false,
reset_decoder_service: false,
..Default::default()
}),
)
.await
.unwrap();
}
#[tokio::test]
async fn test_persistent_decoder_distinguishes_probability_modifier_across_shots() {
let mock = make_mock_decoder();
let coordinator = make_persistent_coordinator(mock.clone());
Coordinator::load_library(&coordinator, Request::new(make_default_library()))
.await
.unwrap();
run_canonical_shot(
&coordinator,
Some(bin::ProbabilityModifier {
probabilities: vec![0.1],
..Default::default()
}),
None,
None,
)
.await;
reset_keeping_library_and_decoder(&coordinator).await;
run_canonical_shot(
&coordinator,
Some(bin::ProbabilityModifier {
probabilities: vec![0.2],
..Default::default()
}),
None,
None,
)
.await;
let mock_state = mock.state.read().await;
assert_eq!(
mock_state.loaded_hypergraphs.len(),
2,
"Expected 2 distinct loaded hypergraphs (one per modifier), got {}; old cache key \
would reuse the first one and report 1",
mock_state.loaded_hypergraphs.len(),
);
let loaded_decoders = coordinator.loaded_decoders.read().await;
assert_eq!(
loaded_decoders.len(),
2,
"Expected 2 distinct DecoderCacheKey entries (one per modifier), got {}",
loaded_decoders.len(),
);
}
#[tokio::test]
async fn test_persistent_decoder_reuses_cache_when_modifier_unchanged() {
let mock = make_mock_decoder();
let coordinator = make_persistent_coordinator(mock.clone());
Coordinator::load_library(&coordinator, Request::new(make_default_library()))
.await
.unwrap();
let modifier = bin::ProbabilityModifier {
probabilities: vec![0.1],
..Default::default()
};
run_canonical_shot(&coordinator, Some(modifier.clone()), None, None).await;
reset_keeping_library_and_decoder(&coordinator).await;
run_canonical_shot(&coordinator, Some(modifier), None, None).await;
let mock_state = mock.state.read().await;
assert_eq!(
mock_state.loaded_hypergraphs.len(),
1,
"Expected 1 cached hypergraph reused across both shots, got {}",
mock_state.loaded_hypergraphs.len(),
);
assert!(
!mock_state.decode_loaded_calls.is_empty(),
"Expected the second shot to hit the cache and call decode_loaded",
);
let loaded_decoders = coordinator.loaded_decoders.read().await;
assert_eq!(loaded_decoders.len(), 1, "Expected a single cache entry");
}
#[tokio::test]
async fn test_library_reset_invalidates_coordinator_decoder_cache() {
let mock = make_mock_decoder();
let coordinator = make_persistent_coordinator(mock.clone());
Coordinator::load_library(&coordinator, Request::new(make_default_library()))
.await
.unwrap();
run_canonical_shot(&coordinator, None, None, None).await;
assert_eq!(coordinator.loaded_decoders.read().await.len(), 1);
Coordinator::reset(
&coordinator,
Request::new(deq_runtime::coordinator::ResetRequest {
reset_library: true,
reset_decoder_service: false,
..Default::default()
}),
)
.await
.unwrap();
assert!(coordinator.loaded_decoders.read().await.is_empty());
assert_eq!(
mock.state.read().await.loaded_hypergraphs.len(),
1,
"reset_decoder_service=false must leave the remote decoder service intact"
);
}
#[tokio::test]
async fn test_outcomes_modifier_is_sent_as_loaded_reweight() {
let mock = make_mock_decoder();
let coordinator = make_persistent_coordinator(mock.clone());
Coordinator::load_library(&coordinator, Request::new(make_default_library()))
.await
.unwrap();
run_canonical_shot(
&coordinator,
None,
None,
Some(bin::ProbabilityModifier {
probabilities: vec![0.27],
..Default::default()
}),
)
.await;
let state = mock.state.read().await;
assert_eq!(state.loaded_hypergraphs.len(), 1);
assert_eq!(state.decode_loaded_calls.len(), 1);
assert_eq!(state.decode_loaded_calls[0].reweights.len(), 1);
assert!((state.decode_loaded_calls[0].reweights[0].probability - 0.27).abs() < 1e-12);
let loaded_probability = state.loaded_hypergraphs[&1].hyperedges[0].probability;
assert!(
(loaded_probability - 0.27).abs() > 1e-12,
"runtime update must not mutate the loaded graph"
);
}