#[allow(unused_imports)]
use crate::fusion::shim::{serde_json};
use super::mwpm_solver::PrimalDualSolver;
use super::pointers::*;
use super::rand_xoshiro;
use crate::fusion::rand_xoshiro::rand_core::RngCore;
use std::collections::BTreeSet;
use std::fs::File;
use std::io::prelude::*;
use std::time::Instant;
pub type Weight = isize;
pub type EdgeIndex = usize;
pub type VertexIndex = usize;
pub type NodeIndex = VertexIndex;
pub type DefectIndex = VertexIndex;
pub type VertexNodeIndex = VertexIndex; pub type VertexNum = VertexIndex;
pub type NodeNum = VertexIndex;
#[derive(Debug, Clone)]
pub struct SolverInitializer {
pub vertex_num: VertexNum,
pub weighted_edges: Vec<(VertexIndex, VertexIndex, Weight)>,
pub virtual_vertices: Vec<VertexIndex>,
}
#[derive(Debug, Clone)]
pub struct SyndromePattern {
pub defect_vertices: Vec<VertexIndex>,
pub erasures: Vec<EdgeIndex>,
pub dynamic_weights: Vec<(EdgeIndex, Weight)>,
}
pub fn default_dynamic_weights() -> Vec<(EdgeIndex, Weight)> {
vec![]
}
pub fn default_erasures() -> Vec<EdgeIndex> {
vec![]
}
impl SyndromePattern {
pub fn new(defect_vertices: Vec<VertexIndex>, erasures: Vec<EdgeIndex>) -> Self {
Self {
defect_vertices,
erasures,
dynamic_weights: vec![],
}
}
pub fn new_dynamic_weights(
defect_vertices: Vec<VertexIndex>,
erasures: Vec<EdgeIndex>,
dynamic_weights: Vec<(EdgeIndex, Weight)>,
) -> Self {
Self {
defect_vertices,
erasures,
dynamic_weights,
}
}
}
impl SyndromePattern {
pub fn py_new(
mut defect_vertices: Vec<VertexIndex>,
erasures: Vec<EdgeIndex>,
dynamic_weights: Vec<(EdgeIndex, Weight)>,
syndrome_vertices: Option<Vec<VertexIndex>>,
) -> Self {
if let Some(syndrome_vertices) = syndrome_vertices {
assert!(
defect_vertices.is_empty(),
"do not pass both `syndrome_vertices` and `defect_vertices` since they're aliasing"
);
defect_vertices = syndrome_vertices;
}
assert!(
erasures.is_empty() || dynamic_weights.is_empty(),
"erasures and dynamic_weights cannot be provided at the same time"
);
Self::new_dynamic_weights(defect_vertices, erasures, dynamic_weights)
}
pub fn new_vertices(defect_vertices: Vec<VertexIndex>) -> Self {
Self::new(defect_vertices, vec![])
}
pub fn new_empty() -> Self {
Self::new(vec![], vec![])
}
}
#[derive(Debug, Clone)]
pub struct PartitionedSyndromePattern<'a> {
pub syndrome_pattern: &'a SyndromePattern,
pub whole_defect_range: DefectRange,
}
impl<'a> PartitionedSyndromePattern<'a> {
pub fn new(syndrome_pattern: &'a SyndromePattern) -> Self {
assert!(
syndrome_pattern.erasures.is_empty(),
"erasure partition not supported yet;
even if the edges in the erasure is well ordered, they may not be able to be represented as
a single range simply because the partition is vertex-based. need more consideration"
);
Self {
syndrome_pattern,
whole_defect_range: DefectRange::new(0, syndrome_pattern.defect_vertices.len() as DefectIndex),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IndexRange {
pub range: [VertexNodeIndex; 2],
}
pub type VertexRange = IndexRange;
pub type NodeRange = IndexRange;
pub type DefectRange = IndexRange;
impl IndexRange {
pub fn new(start: VertexNodeIndex, end: VertexNodeIndex) -> Self {
debug_assert!(end >= start, "invalid range [{}, {})", start, end);
Self { range: [start, end] }
}
pub fn new_length(start: VertexNodeIndex, length: VertexNodeIndex) -> Self {
Self::new(start, start + length)
}
pub fn is_empty(&self) -> bool {
self.range[1] == self.range[0]
}
#[allow(clippy::unnecessary_cast)]
pub fn len(&self) -> usize {
(self.range[1] - self.range[0]) as usize
}
pub fn start(&self) -> VertexNodeIndex {
self.range[0]
}
pub fn end(&self) -> VertexNodeIndex {
self.range[1]
}
pub fn append_by(&mut self, append_count: VertexNodeIndex) {
self.range[1] += append_count;
}
pub fn bias_by(&mut self, bias: VertexNodeIndex) {
self.range[0] += bias;
self.range[1] += bias;
}
pub fn sanity_check(&self) {
assert!(self.start() <= self.end(), "invalid vertex range {:?}", self);
}
pub fn contains(&self, vertex_index: VertexNodeIndex) -> bool {
vertex_index >= self.start() && vertex_index < self.end()
}
pub fn fuse(&self, other: &Self) -> (Self, Self) {
self.sanity_check();
other.sanity_check();
assert!(self.range[1] <= other.range[0], "only lower range can fuse higher range");
(
Self::new(self.range[0], other.range[1]),
Self::new(self.range[1], other.range[0]),
)
}
}
impl IndexRange {
pub fn iter(&self) -> std::ops::Range<VertexNodeIndex> {
self.range[0]..self.range[1]
}
pub fn contains_any(&self, vertex_indices: &[VertexNodeIndex]) -> bool {
for vertex_index in vertex_indices.iter() {
if self.contains(*vertex_index) {
return true;
}
}
false
}
}
#[derive(Debug, Clone)]
pub struct PartitionUnit {
pub unit_index: usize,
pub enabled: bool,
}
pub type PartitionUnitPtr = ArcManualSafeLock<PartitionUnit>;
pub type PartitionUnitWeak = WeakManualSafeLock<PartitionUnit>;
impl std::fmt::Debug for PartitionUnitPtr {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let partition_unit = self.read_recursive();
write!(
f,
"{}{}",
if partition_unit.enabled { "E" } else { "D" },
partition_unit.unit_index
)
}
}
impl std::fmt::Debug for PartitionUnitWeak {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
self.upgrade_force().fmt(f)
}
}
#[derive(Debug, Clone)]
pub struct PartitionConfig {
pub vertex_num: VertexNum,
pub partitions: Vec<VertexRange>,
pub fusions: Vec<(usize, usize)>,
}
impl PartitionConfig {
pub fn new(vertex_num: VertexNum) -> Self {
Self {
vertex_num,
partitions: vec![VertexRange::new(0, vertex_num as VertexIndex)],
fusions: vec![],
}
}
#[allow(clippy::unnecessary_cast)]
pub fn info(&self) -> PartitionInfo {
assert!(!self.partitions.is_empty(), "at least one partition must exist");
let mut whole_ranges = vec![];
let mut owning_ranges = vec![];
for &partition in self.partitions.iter() {
partition.sanity_check();
assert!(
partition.end() <= self.vertex_num as VertexIndex,
"invalid vertex index {} in partitions",
partition.end()
);
whole_ranges.push(partition);
owning_ranges.push(partition);
}
let unit_count = self.partitions.len() + self.fusions.len();
let mut parents: Vec<Option<usize>> = (0..unit_count).map(|_| None).collect();
for (fusion_index, (left_index, right_index)) in self.fusions.iter().enumerate() {
let unit_index = fusion_index + self.partitions.len();
assert!(
*left_index < unit_index,
"dependency wrong, {} depending on {}",
unit_index,
left_index
);
assert!(
*right_index < unit_index,
"dependency wrong, {} depending on {}",
unit_index,
right_index
);
assert!(parents[*left_index].is_none(), "cannot fuse {} twice", left_index);
assert!(parents[*right_index].is_none(), "cannot fuse {} twice", right_index);
parents[*left_index] = Some(unit_index);
parents[*right_index] = Some(unit_index);
let (whole_range, interface_range) = whole_ranges[*left_index].fuse(&whole_ranges[*right_index]);
whole_ranges.push(whole_range);
owning_ranges.push(interface_range);
}
for (unit_index, parent) in parents.iter().enumerate().take(unit_count - 1) {
assert!(parent.is_some(), "found unit {} without being fused", unit_index);
}
let last_unit_index = self.partitions.len() + self.fusions.len() - 1;
assert!(
whole_ranges[last_unit_index].start() == 0,
"final range not covering all vertices {:?}",
whole_ranges[last_unit_index]
);
assert!(
whole_ranges[last_unit_index].end() == self.vertex_num as VertexIndex,
"final range not covering all vertices {:?}",
whole_ranges[last_unit_index]
);
let mut partition_unit_info: Vec<_> = (0..self.partitions.len() + self.fusions.len())
.map(|i| PartitionUnitInfo {
whole_range: whole_ranges[i],
owning_range: owning_ranges[i],
children: if i >= self.partitions.len() {
Some(self.fusions[i - self.partitions.len()])
} else {
None
},
parent: parents[i],
leaves: if i < self.partitions.len() { vec![i] } else { vec![] },
descendants: BTreeSet::new(),
})
.collect();
for (fusion_index, (left_index, right_index)) in self.fusions.iter().enumerate() {
let unit_index = fusion_index + self.partitions.len();
let mut leaves = vec![];
leaves.extend(partition_unit_info[*left_index].leaves.iter());
leaves.extend(partition_unit_info[*right_index].leaves.iter());
partition_unit_info[unit_index].leaves.extend(leaves.iter());
let mut descendants = vec![];
descendants.push(*left_index);
descendants.push(*right_index);
descendants.extend(partition_unit_info[*left_index].descendants.iter());
descendants.extend(partition_unit_info[*right_index].descendants.iter());
partition_unit_info[unit_index].descendants.extend(descendants.iter());
}
let mut vertex_to_owning_unit: Vec<_> = (0..self.vertex_num).map(|_| usize::MAX).collect();
for (unit_index, unit_range) in partition_unit_info.iter().map(|x| x.owning_range).enumerate() {
for vertex_index in unit_range.iter() {
vertex_to_owning_unit[vertex_index as usize] = unit_index;
}
}
PartitionInfo {
config: self.clone(),
units: partition_unit_info,
vertex_to_owning_unit,
}
}
}
#[derive(Debug, Clone)]
pub struct PartitionInfo {
pub config: PartitionConfig,
pub units: Vec<PartitionUnitInfo>,
pub vertex_to_owning_unit: Vec<usize>,
}
impl PartitionInfo {
#[allow(clippy::unnecessary_cast)]
pub fn partition_syndrome_unordered(&self, syndrome_pattern: &SyndromePattern) -> Vec<SyndromePattern> {
let mut partitioned_syndrome: Vec<_> = (0..self.units.len()).map(|_| SyndromePattern::new_empty()).collect();
for defect_vertex in syndrome_pattern.defect_vertices.iter() {
let unit_index = self.vertex_to_owning_unit[*defect_vertex as usize];
partitioned_syndrome[unit_index].defect_vertices.push(*defect_vertex);
}
partitioned_syndrome
}
}
impl<'a> PartitionedSyndromePattern<'a> {
#[allow(clippy::unnecessary_cast)]
pub fn partition(&self, partition_unit_info: &PartitionUnitInfo) -> (Self, (Self, Self)) {
let owning_start_index = {
let mut left_index = self.whole_defect_range.start();
let mut right_index = self.whole_defect_range.end();
while left_index != right_index {
let mid_index = (left_index + right_index) / 2;
let mid_defect_vertex = self.syndrome_pattern.defect_vertices[mid_index as usize];
if mid_defect_vertex < partition_unit_info.owning_range.start() {
left_index = mid_index + 1;
} else {
right_index = mid_index;
}
}
left_index
};
let owning_end_index = {
let mut left_index = self.whole_defect_range.start();
let mut right_index = self.whole_defect_range.end();
while left_index != right_index {
let mid_index = (left_index + right_index) / 2;
let mid_defect_vertex = self.syndrome_pattern.defect_vertices[mid_index as usize];
if mid_defect_vertex < partition_unit_info.owning_range.end() {
left_index = mid_index + 1;
} else {
right_index = mid_index;
}
}
left_index
};
(
Self {
syndrome_pattern: self.syndrome_pattern,
whole_defect_range: DefectRange::new(owning_start_index, owning_end_index),
},
(
Self {
syndrome_pattern: self.syndrome_pattern,
whole_defect_range: DefectRange::new(self.whole_defect_range.start(), owning_start_index),
},
Self {
syndrome_pattern: self.syndrome_pattern,
whole_defect_range: DefectRange::new(owning_end_index, self.whole_defect_range.end()),
},
),
)
}
#[allow(clippy::unnecessary_cast)]
pub fn expand(&self) -> SyndromePattern {
let mut defect_vertices = Vec::with_capacity(self.whole_defect_range.len());
for defect_index in self.whole_defect_range.iter() {
defect_vertices.push(self.syndrome_pattern.defect_vertices[defect_index as usize]);
}
SyndromePattern::new(defect_vertices, vec![])
}
}
#[derive(Debug, Clone)]
pub struct PartitionUnitInfo {
pub whole_range: VertexRange,
pub owning_range: VertexRange,
pub children: Option<(usize, usize)>,
pub parent: Option<usize>,
pub leaves: Vec<usize>,
pub descendants: BTreeSet<usize>,
}
#[derive(Debug, Clone)]
pub struct PartitionedSolverInitializer {
pub unit_index: usize,
pub vertex_num: VertexNum,
pub edge_num: usize,
pub owning_range: VertexRange,
pub owning_interface: Option<PartitionUnitWeak>,
pub interfaces: Vec<(PartitionUnitWeak, Vec<(VertexIndex, bool)>)>,
pub weighted_edges: Vec<(VertexIndex, VertexIndex, Weight, EdgeIndex)>,
pub virtual_vertices: Vec<VertexIndex>,
}
#[allow(clippy::unnecessary_cast)]
pub fn build_old_to_new(reordered_vertices: &[VertexIndex]) -> Vec<Option<VertexIndex>> {
let mut old_to_new: Vec<Option<VertexIndex>> = (0..reordered_vertices.len()).map(|_| None).collect();
for (new_index, old_index) in reordered_vertices.iter().enumerate() {
assert_eq!(old_to_new[*old_index as usize], None, "duplicate vertex found {}", old_index);
old_to_new[*old_index as usize] = Some(new_index as VertexIndex);
}
old_to_new
}
#[allow(clippy::unnecessary_cast)]
pub fn translated_defect_to_reordered(
reordered_vertices: &[VertexIndex],
old_defect_vertices: &[VertexIndex],
) -> Vec<VertexIndex> {
let old_to_new = build_old_to_new(reordered_vertices);
old_defect_vertices
.iter()
.map(|old_index| old_to_new[*old_index as usize].unwrap())
.collect()
}
impl SolverInitializer {
pub fn new(
vertex_num: VertexNum,
weighted_edges: Vec<(VertexIndex, VertexIndex, Weight)>,
virtual_vertices: Vec<VertexIndex>,
) -> SolverInitializer {
SolverInitializer {
vertex_num,
weighted_edges,
virtual_vertices,
}
}
}
impl SolverInitializer {
#[allow(clippy::unnecessary_cast)]
pub fn syndrome_of(&self, subgraph: &[EdgeIndex]) -> BTreeSet<VertexIndex> {
let mut defects = BTreeSet::new();
for edge_index in subgraph {
let (left, right, _weight) = self.weighted_edges[*edge_index as usize];
for vertex_index in [left, right] {
if defects.contains(&vertex_index) {
defects.remove(&vertex_index);
} else {
defects.insert(vertex_index);
}
}
}
for vertex_index in self.virtual_vertices.iter() {
defects.remove(vertex_index);
}
defects
}
}
pub type FastClearTimestamp = usize;
#[allow(dead_code)]
pub type DeterministicRng = rand_xoshiro::Xoshiro256StarStar;
pub trait F64Rng {
fn next_f64(&mut self) -> f64;
}
impl F64Rng for DeterministicRng {
fn next_f64(&mut self) -> f64 {
f64::from_bits(0x3FF << 52 | self.next_u64() >> 12) - 1.
}
}
pub struct BenchmarkProfiler {
pub records: Vec<BenchmarkProfilerEntry>,
pub sum_round_time: f64,
pub sum_syndrome: usize,
pub noisy_measurements: VertexNum,
pub benchmark_profiler_output: Option<File>,
}
impl BenchmarkProfiler {
pub fn new(noisy_measurements: VertexNum, detail_log_file: Option<(String, &PartitionInfo)>) -> Self {
let benchmark_profiler_output = detail_log_file.map(|(filename, partition_info)| {
let mut file = File::create(filename).unwrap();
file.write_all(serde_json::to_string(&partition_info.config).unwrap().as_bytes())
.unwrap();
file.write_all(b"\n").unwrap();
file.write_all(
serde_json::to_string(&json!({
"noisy_measurements": noisy_measurements,
}))
.unwrap()
.as_bytes(),
)
.unwrap();
file.write_all(b"\n").unwrap();
file
});
Self {
records: vec![],
sum_round_time: 0.,
sum_syndrome: 0,
noisy_measurements,
benchmark_profiler_output,
}
}
pub fn begin(&mut self, syndrome_pattern: &SyndromePattern) {
if let Some(last_entry) = self.records.last() {
assert!(
last_entry.is_complete(),
"the last benchmark profiler entry is not complete, make sure to call `begin` and `end` in pairs"
);
}
let entry = BenchmarkProfilerEntry::new(syndrome_pattern);
self.records.push(entry);
self.records.last_mut().unwrap().record_begin();
}
pub fn event(&mut self, event_name: String) {
let last_entry = self
.records
.last_mut()
.expect("last entry not exists, call `begin` before `end`");
last_entry.record_event(event_name);
}
pub fn end(&mut self, solver: Option<&dyn PrimalDualSolver>) {
let last_entry = self
.records
.last_mut()
.expect("last entry not exists, call `begin` before `end`");
last_entry.record_end();
self.sum_round_time += last_entry.round_time.unwrap();
self.sum_syndrome += last_entry.syndrome_pattern.defect_vertices.len();
if let Some(file) = self.benchmark_profiler_output.as_mut() {
let mut events = serde_json::Map::new();
for (event_name, time) in last_entry.events.iter() {
events.insert(event_name.clone(), json!(time));
}
let mut value = json!({
"round_time": last_entry.round_time.unwrap(),
"defect_num": last_entry.syndrome_pattern.defect_vertices.len(),
"events": events,
});
if let Some(solver) = solver {
let solver_profile = solver.generate_profiler_report();
value
.as_object_mut()
.unwrap()
.insert("solver_profile".to_string(), solver_profile);
}
file.write_all(serde_json::to_string(&value).unwrap().as_bytes()).unwrap();
file.write_all(b"\n").unwrap();
}
}
pub fn brief(&self) -> String {
let total = self.sum_round_time / (self.records.len() as f64);
let per_round = total / (1. + self.noisy_measurements as f64);
let per_defect = self.sum_round_time / (self.sum_syndrome as f64);
format!("total: {total:.3e}, round: {per_round:.3e}, defect: {per_defect:.3e},")
}
}
pub struct BenchmarkProfilerEntry {
pub syndrome_pattern: SyndromePattern,
begin_time: Option<Instant>,
pub events: Vec<(String, f64)>,
pub round_time: Option<f64>,
}
impl BenchmarkProfilerEntry {
pub fn new(syndrome_pattern: &SyndromePattern) -> Self {
Self {
syndrome_pattern: syndrome_pattern.clone(),
begin_time: None,
events: vec![],
round_time: None,
}
}
pub fn record_begin(&mut self) {
assert_eq!(self.begin_time, None, "do not call `record_begin` twice on the same entry");
self.begin_time = Some(Instant::now());
}
pub fn record_end(&mut self) {
let begin_time = self
.begin_time
.as_ref()
.expect("make sure to call `record_begin` before calling `record_end`");
self.round_time = Some(begin_time.elapsed().as_secs_f64());
}
pub fn record_event(&mut self, event_name: String) {
let begin_time = self
.begin_time
.as_ref()
.expect("make sure to call `record_begin` before calling `record_end`");
self.events.push((event_name, begin_time.elapsed().as_secs_f64()));
}
pub fn is_complete(&self) -> bool {
self.round_time.is_some()
}
}
#[cfg(test)]
pub mod tests {
use super::*;
#[test]
fn util_partitioned_syndrome_pattern_1() {
let mut partition_config = PartitionConfig::new(132);
partition_config.partitions = vec![
VertexRange::new(0, 72), VertexRange::new(84, 132), ];
partition_config.fusions = vec![
(0, 1), ];
let partition_info = partition_config.info();
let tests = vec![
(vec![10, 11, 12, 71, 72, 73, 84, 85, 111], DefectRange::new(4, 6)),
(vec![10, 11, 12, 13, 71, 72, 73, 84, 85, 111], DefectRange::new(5, 7)),
(vec![10, 11, 12, 71, 72, 73, 83, 84, 85, 111], DefectRange::new(4, 7)),
(
vec![10, 11, 12, 71, 72, 73, 84, 85, 100, 101, 102, 103, 111],
DefectRange::new(4, 6),
),
];
for (defect_vertices, expected_defect_range) in tests.into_iter() {
let syndrome_pattern = SyndromePattern::new(defect_vertices, vec![]);
let partitioned_syndrome_pattern = PartitionedSyndromePattern::new(&syndrome_pattern);
let (owned_partitioned, (_left_partitioned, _right_partitioned)) =
partitioned_syndrome_pattern.partition(&partition_info.units[2]);
println!("defect_range: {:?}", owned_partitioned.whole_defect_range);
assert_eq!(owned_partitioned.whole_defect_range, expected_defect_range);
}
}
}