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))
}
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()
}
}
#[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"
);
}