From c9880108249398007bd4c8cc9c6830765db31eac Mon Sep 17 00:00:00 2001 From: Haadi Khan Date: Wed, 19 Jun 2024 02:57:11 -0400 Subject: [PATCH 1/7] Generalizing Topo Charges --- src/fusion/fusion.rs | 251 +++++++++++++++++++++++++++++++++++-------- src/fusion/state.rs | 2 +- src/lib.rs | 6 +- src/model/anyon.rs | 120 ++++++++++++++++++--- src/model/model.rs | 37 ++++--- src/util/basis.rs | 1 - 6 files changed, 337 insertions(+), 80 deletions(-) diff --git a/src/fusion/fusion.rs b/src/fusion/fusion.rs index 148174f..56b73c3 100644 --- a/src/fusion/fusion.rs +++ b/src/fusion/fusion.rs @@ -2,8 +2,9 @@ use std::collections::HashMap; use crate::fusion::state::AccessState; use crate::fusion::state::State; -use crate::model::anyon::AccessAnyon; use crate::model::anyon::IsingTopoCharge; +use crate::model::model::AnyonModel; +use pyo3::exceptions::PyValueError; use crate::util::basis::Basis; use pyo3::prelude::*; @@ -16,16 +17,11 @@ pub struct FusionPair { anyon_2: usize, } -pub trait AccessFusionPair { - fn anyon_1(&self) -> usize; - fn anyon_2(&self) -> usize; -} - -impl AccessFusionPair for FusionPair { - fn anyon_1(&self) -> usize { +impl FusionPair { + pub fn anyon_1(&self) -> usize { self.anyon_1 } - fn anyon_2(&self) -> usize { + pub fn anyon_2(&self) -> usize { self.anyon_2 } } @@ -48,6 +44,99 @@ pub struct Fusion { ops: Vec>, } +/// Internal Methods +<<<<<<< ours +<<<<<<< Updated upstream +<<<<<<< ours +<<<<<<< ours +======= +>>>>>>> Stashed changes +impl Fusion { + pub fn ising_qubit_enc(&self) -> Vec { + let enum_to_canonical = |charge: IsingTopoCharge| -> [u64; 3] { + match charge { + IsingTopoCharge::Psi => [1, 0, 0], + IsingTopoCharge::Vacuum => [0, 1, 0], + IsingTopoCharge::Sigma => [0, 0, 1], + } + }; + let mut tcs: Vec<[u64; 3]> = self + .state + .anyons() + .iter() + .map(|a| enum_to_canonical(a.charge())) + .collect(); + let mut fusion_pair_tc: HashMap = HashMap::new(); + + let mut final_tc: [u64; 3] = [0, 0, 0]; + + for (i, op) in self.ops.iter().enumerate() { + for (j, fusion_pair) in op.iter().enumerate() { + let tc = + self.ising_apply_fusion(tcs[fusion_pair.anyon_1()], tcs[fusion_pair.anyon_2()]); + if i == self.ops.len() - 1 && j == op.len() - 1 { + final_tc = tc; + break; + } + fusion_pair_tc.insert(fusion_pair.clone(), tc); + tcs[fusion_pair.anyon_1()] = tc; + } + } + + if final_tc[IsingTopoCharge::Sigma.value()] == 0 + && ((final_tc[IsingTopoCharge::Psi.value()] == 1 + && final_tc[IsingTopoCharge::Vacuum.value()] == 0) + || (final_tc[IsingTopoCharge::Psi.value()] == 1 + && final_tc[IsingTopoCharge::Vacuum.value()] == 0)) + { + return Vec::new(); + } + + let mut encoding_fusions: Vec = fusion_pair_tc + .into_iter() + .filter(|(_, tc)| tc[IsingTopoCharge::Sigma.value()] == 0) + .map(|(fusion_pair, _)| fusion_pair) + .collect(); + encoding_fusions.sort(); + encoding_fusions.pop().unwrap(); + encoding_fusions + } + + pub fn ising_apply_fusion(&self, anyon_1: [u64; 3], anyon_2: [u64; 3]) -> [u64; 3] { + let add = |a: [u64; 3], b: [u64; 3]| -> [u64; 3] { std::array::from_fn(|i| a[i] + b[i]) }; + let arr_scale = |a: [u64; 3], b: u64| -> [u64; 3] { std::array::from_fn(|i| a[i] * b) }; + + let mut output = [0 as u64; 3]; + + // ising fusion rules + // symmetric matrix which is built from fusion rules of (psi, 1, sigma) ^ (psi, 1, sigma) + let fusion_rules_mtx = [ + [[0 as u64, 1, 0], [1, 0, 0], [0, 0, 1]], + [[1, 0, 0], [0, 1, 0], [0, 0, 1]], + [[0, 0, 1], [0, 0, 1], [1, 1, 0]], + ]; + + // build the outer product of the two tc vectors + let mut tc_mtx = [[0; 3]; 3]; + for i in 0..tc_mtx.len() { + for j in 0..tc_mtx[i].len() { + tc_mtx[i][j] = anyon_1[i] * anyon_2[j]; + } + } + + // mtx multiply fusion rules with tc_mtx + for i in 0..3 { + for j in 0..3 { + output = add(output, arr_scale(fusion_rules_mtx[i][j], tc_mtx[i][j])); + } + } + + output + + } +} + +/// Python Facing Methods #[pymethods] impl Fusion { #[new] @@ -75,7 +164,21 @@ impl Fusion { } /// Assumes model is Ising +<<<<<<< Updated upstream +<<<<<<< Updated upstream fn qubit_enc(&self) -> PyResult> { +======= +impl Fusion { + pub fn ising_qubit_enc(&self) -> Vec { +>>>>>>> theirs +======= +impl Fusion { + pub fn ising_qubit_enc(&self) -> Vec { +>>>>>>> theirs +======= +impl Fusion { + pub fn ising_qubit_enc(&self) -> Vec { +>>>>>>> theirs let enum_to_canonical = |charge: IsingTopoCharge| -> [u64; 3] { match charge { IsingTopoCharge::Psi => [1, 0, 0], @@ -96,7 +199,7 @@ impl Fusion { for (i, op) in self.ops.iter().enumerate() { for (j, fusion_pair) in op.iter().enumerate() { let tc = - self.apply_fusion(tcs[fusion_pair.anyon_1()], tcs[fusion_pair.anyon_2()])?; + self.ising_apply_fusion(tcs[fusion_pair.anyon_1()], tcs[fusion_pair.anyon_2()]); if i == self.ops.len() - 1 && j == op.len() - 1 { final_tc = tc; break; @@ -104,7 +207,18 @@ impl Fusion { fusion_pair_tc.insert(fusion_pair.clone(), tc); tcs[fusion_pair.anyon_1()] = tc; } +======= +======= +>>>>>>> Stashed changes + fn qubit_enc(&self, anyon_model: &AnyonModel) -> PyResult> { + match anyon_model { + AnyonModel::Ising => Ok(self.ising_qubit_enc()), + _ => Err(PyValueError::new_err("This model is not supported yet")), +<<<<<<< Updated upstream +>>>>>>> Stashed changes } +<<<<<<< ours +======= if final_tc[IsingTopoCharge::Sigma.value()] == 0 && ((final_tc[IsingTopoCharge::Psi.value()] == 1 @@ -112,7 +226,7 @@ impl Fusion { || (final_tc[IsingTopoCharge::Psi.value()] == 1 && final_tc[IsingTopoCharge::Vacuum.value()] == 0)) { - return Ok(Vec::new()); + return Vec::new(); } let mut encoding_fusions: Vec = fusion_pair_tc @@ -122,7 +236,86 @@ impl Fusion { .collect(); encoding_fusions.sort(); encoding_fusions.pop().unwrap(); - Ok(encoding_fusions) + encoding_fusions + } + + pub fn ising_apply_fusion(&self, anyon_1: [u64; 3], anyon_2: [u64; 3]) -> [u64; 3] { + let add = |a: [u64; 3], b: [u64; 3]| -> [u64; 3] { std::array::from_fn(|i| a[i] + b[i]) }; + let arr_scale = |a: [u64; 3], b: u64| -> [u64; 3] { std::array::from_fn(|i| a[i] * b) }; + + let mut output = [0 as u64; 3]; + + // ising fusion rules + // symmetric matrix which is built from fusion rules of (psi, 1, sigma) ^ (psi, 1, sigma) + let fusion_rules_mtx = [ + [[0 as u64, 1, 0], [1, 0, 0], [0, 0, 1]], + [[1, 0, 0], [0, 1, 0], [0, 0, 1]], + [[0, 0, 1], [0, 0, 1], [1, 1, 0]], + ]; + + // build the outer product of the two tc vectors + let mut tc_mtx = [[0; 3]; 3]; + for i in 0..tc_mtx.len() { + for j in 0..tc_mtx[i].len() { + tc_mtx[i][j] = anyon_1[i] * anyon_2[j]; + } + } + + // mtx multiply fusion rules with tc_mtx + for i in 0..3 { + for j in 0..3 { + output = add(output, arr_scale(fusion_rules_mtx[i][j], tc_mtx[i][j])); + } + } + + output + + } +} + +/// Python Facing Methods +#[pymethods] +impl Fusion { + #[new] + fn new(state: State) -> Self { + let operations = state.operations(); + + let mut ops: Vec> = Vec::new(); + + let mut prev_time = 0; + for (time, op) in operations { + if prev_time == time { + ops[time as usize - 1].push(op); + } else { + ops.push(vec![op]); + prev_time = time; + } + } + + Fusion { state, ops } + } + + /// Verifies the basis + fn verify_basis(&self, basis: &Basis) -> PyResult { + Ok(basis.verify_basis(self.state.anyons().len())) + } + + /// Assumes model is Ising + fn qubit_enc(&self, anyon_model: &AnyonModel) -> PyResult> { + match anyon_model { + AnyonModel::Ising => Ok(self.ising_qubit_enc()), + _ => Err(PyValueError::new_err("This model is not supported yet")), + } +<<<<<<< ours +<<<<<<< ours +>>>>>>> theirs +======= +>>>>>>> theirs +======= + } +>>>>>>> Stashed changes +======= +>>>>>>> theirs } /// Builds the fusion tree's graphical representation @@ -178,37 +371,11 @@ impl Fusion { } /// Fuses anyons according to their fusion rules - /// TODO: Generalize this to all models (has to be known size) /// Format is [psi, vacuum, sigma] (so we can use the index as the encode) - pub fn apply_fusion(&self, anyon_1: [u64; 3], anyon_2: [u64; 3]) -> PyResult<[u64; 3]> { - let add = |a: [u64; 3], b: [u64; 3]| -> [u64; 3] { std::array::from_fn(|i| a[i] + b[i]) }; - let arr_scale = |a: [u64; 3], b: u64| -> [u64; 3] { std::array::from_fn(|i| a[i] * b) }; - - let mut output = [0 as u64; 3]; - - // ising fusion rules - // symmetric matrix which is built from fusion rules of (psi, 1, sigma) ^ (psi, 1, sigma) - let fusion_rules_mtx = [ - [[0 as u64, 1, 0], [1, 0, 0], [0, 0, 1]], - [[1, 0, 0], [0, 1, 0], [0, 0, 1]], - [[0, 0, 1], [0, 0, 1], [1, 1, 0]], - ]; - - // build the outer product of the two tc vectors - let mut tc_mtx = [[0; 3]; 3]; - for i in 0..tc_mtx.len() { - for j in 0..tc_mtx[i].len() { - tc_mtx[i][j] = anyon_1[i] * anyon_2[j]; - } - } - - // mtx multiply fusion rules with tc_mtx - for i in 0..3 { - for j in 0..3 { - output = add(output, arr_scale(fusion_rules_mtx[i][j], tc_mtx[i][j])); - } + pub fn apply_fusion(&self, anyon_1: [u64; 3], anyon_2: [u64; 3], anyon_model: &AnyonModel ) -> PyResult<[u64; 3]> { + match anyon_model { + AnyonModel::Ising => Ok(self.ising_apply_fusion(anyon_1, anyon_2)), + _ => Err(PyValueError::new_err("This model is not supported yet")), } - - return Ok(output); } } diff --git a/src/fusion/state.rs b/src/fusion/state.rs index 5502dfc..4e1e336 100644 --- a/src/fusion/state.rs +++ b/src/fusion/state.rs @@ -1,4 +1,4 @@ -use crate::{fusion::fusion::AccessFusionPair, fusion::fusion::FusionPair, model::anyon::Anyon}; +use crate::{fusion::fusion::FusionPair, model::anyon::Anyon}; use pyo3::prelude::*; /// The state of the system diff --git a/src/lib.rs b/src/lib.rs index 4ae8239..89bf2ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,13 +3,15 @@ mod fusion; mod model; mod util; -/// A Python module implemented in Rust. +/// This builds the bindings for maturin and enables the python module to be +/// imported. For any new class which should be accessible by python, add it +/// here following the same format #[pymodule] fn anyon_braiding_simulator(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; - m.add_class::()?; + // m.add_class::()?; m.add_class::()?; m.add_class::()?; diff --git a/src/model/anyon.rs b/src/model/anyon.rs index e0796c3..42e3d71 100644 --- a/src/model/anyon.rs +++ b/src/model/anyon.rs @@ -1,7 +1,5 @@ use pyo3::prelude::*; -/// Lazy solution for now, will properly implement a more general Topo Charge w/ specified -/// version for each different model #[pyclass] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum IsingTopoCharge { @@ -10,13 +8,99 @@ pub enum IsingTopoCharge { Sigma, } +#[pyclass] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum FibonacciTopoCharge { + Tau, + Vacuum, +} + +#[pyclass] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct TopoCharge { + ising: Option, + fibonacci: Option, +} + +// Implement TryFrom trait to convert from TopoCharge to specific enums +impl TryFrom<&TopoCharge> for IsingTopoCharge { + type Error = &'static str; + + fn try_from(value: &TopoCharge) -> Result { + value.ising.ok_or("Not an IsingTopoCharge") + } +} + +impl TryFrom<&TopoCharge> for FibonacciTopoCharge { + type Error = &'static str; + + fn try_from(value: &TopoCharge) -> Result { + value.fibonacci.ok_or("Not a FibonacciTopoCharge") + } +} + +#[pymethods] +impl TopoCharge { + #[new] + pub fn new(ising: Option, fibonacci: Option) -> Self { + TopoCharge { ising, fibonacci } + } + + #[staticmethod] + pub fn from_ising(ising: IsingTopoCharge) -> Self { + TopoCharge { + ising: Some(ising), + fibonacci: None, + } + } + + #[staticmethod] + pub fn from_fibonacci(fibonacci: FibonacciTopoCharge) -> Self { + TopoCharge { + ising: None, + fibonacci: Some(fibonacci), + } + } + + pub fn is_ising(&self) -> bool { + self.ising.is_some() + } + + pub fn is_fibonacci(&self) -> bool { + self.fibonacci.is_some() + } + + pub fn get_ising(&self) -> IsingTopoCharge { + self.ising.unwrap() + } + + pub fn get_fibonacci(&self) -> FibonacciTopoCharge { + self.fibonacci.unwrap() + } + + pub fn to_string(&self) -> String { + if let Some(ising) = self.ising { + format!("{:?}", ising) + } else if let Some(fibonacci) = self.fibonacci { + format!("{:?}", fibonacci) + } else { + "None".to_string() + } + } +} + impl IsingTopoCharge { pub fn value(&self) -> usize { - match self { - IsingTopoCharge::Psi => 0, - IsingTopoCharge::Vacuum => 1, - IsingTopoCharge::Sigma => 2, - } + *self as usize + // match self { + // IsingTopoCharge::Psi => 0, + // IsingTopoCharge::Vacuum => 1, + // IsingTopoCharge::Sigma => 2, + // } +<<<<<<< ours +<<<<<<< Updated upstream +<<<<<<< ours +<<<<<<< ours } pub fn to_string(&self) -> &str { @@ -25,6 +109,14 @@ impl IsingTopoCharge { IsingTopoCharge::Vacuum => "Vacuum", IsingTopoCharge::Sigma => "Sigma", } +======= +>>>>>>> theirs +======= +>>>>>>> theirs +======= +>>>>>>> Stashed changes +======= +>>>>>>> theirs } } @@ -39,22 +131,16 @@ pub struct Anyon { position: (f64, f64), } -pub trait AccessAnyon { - fn name(&self) -> &str; - fn charge(&self) -> IsingTopoCharge; - fn position(&self) -> (f64, f64); -} - -impl AccessAnyon for Anyon { - fn name(&self) -> &str { +impl Anyon { + pub fn name(&self) -> &str { &self.name } - fn charge(&self) -> IsingTopoCharge { + pub fn charge(&self) -> IsingTopoCharge { self.charge.clone() } - fn position(&self) -> (f64, f64) { + pub fn position(&self) -> (f64, f64) { self.position } } diff --git a/src/model/model.rs b/src/model/model.rs index 98d6f54..c1abd39 100644 --- a/src/model/model.rs +++ b/src/model/model.rs @@ -1,6 +1,6 @@ use pyo3::prelude::*; -use super::anyon::{AccessAnyon, Anyon, IsingTopoCharge}; +// use super::anyon::{Anyon, IsingTopoCharge}; /// Different Anyon models that can be used to simulate the system #[pyclass] @@ -19,21 +19,24 @@ impl AnyonModel { } } -/// The parameters accompanying a model -#[pyclass] -pub struct Model { - model_type: AnyonModel, - // more fields which we'll impl later -} +// Commenting out Model for now because it has no use atm We might port the +// python stuff to rust later, but for now we have no use -#[pymethods] -impl Model { - #[new] - fn new() -> Self { - // Model { model_type } - Model { - model_type: AnyonModel::Ising, - } - } +// /// The parameters accompanying a model +// #[pyclass] +// pub struct Model { +// model_type: AnyonModel, +// // more fields which we'll impl later +// } -} +// #[pymethods] +// impl Model { +// #[new] +// fn new() -> Self { +// // Model { model_type } +// Model { +// model_type: AnyonModel::Ising, +// } +// } + +// } diff --git a/src/util/basis.rs b/src/util/basis.rs index 31fab60..cb570bf 100644 --- a/src/util/basis.rs +++ b/src/util/basis.rs @@ -1,6 +1,5 @@ use pyo3::prelude::*; -use crate::fusion::fusion::AccessFusionPair; use crate::fusion::fusion::FusionPair; #[pyclass] From 8fa20525ce9ed56f3d9b6def7e2417415e5b0b3f Mon Sep 17 00:00:00 2001 From: Haadi Khan Date: Wed, 19 Jun 2024 04:14:54 -0400 Subject: [PATCH 2/7] Updated Documentation in the Rust code, need tests --- src/fusion/fusion.rs | 191 ++++++------------------------------------- src/fusion/state.rs | 57 ++++++------- src/lib.rs | 2 +- src/model/anyon.rs | 31 +++---- src/model/model.rs | 33 ++++---- 5 files changed, 81 insertions(+), 233 deletions(-) diff --git a/src/fusion/fusion.rs b/src/fusion/fusion.rs index 56b73c3..4c32484 100644 --- a/src/fusion/fusion.rs +++ b/src/fusion/fusion.rs @@ -1,15 +1,20 @@ use std::collections::HashMap; -use crate::fusion::state::AccessState; use crate::fusion::state::State; use crate::model::anyon::IsingTopoCharge; use crate::model::model::AnyonModel; -use pyo3::exceptions::PyValueError; use crate::util::basis::Basis; +use pyo3::exceptions::PyValueError; use pyo3::prelude::*; #[pyclass] #[derive(Clone, Debug, PartialEq, Hash, Eq, Ord, PartialOrd)] +/// Represents a pair of anyons indices that are fused. The indices are derived +/// from the relative ordering in the list of anyons stored in State. +/// FusionPair is used to represent the fusion events along the fusion tree. +/// +/// When two anyons are fused, the resulting anyon carries the lower index. i.e. +/// the anyon resulting from the fusion (1,2) is later referenced as 1 pub struct FusionPair { #[pyo3(get)] anyon_1: usize, @@ -39,19 +44,20 @@ impl FusionPair { } #[pyclass] +/// Stores the state of the system and all fusion operations that occur in the +/// fusion tree. The vector is 2D, where the outer vector represents the time +/// step and the inner vector represents the fusion operations that occur at +/// that time step. pub struct Fusion { state: State, ops: Vec>, } /// Internal Methods -<<<<<<< ours -<<<<<<< Updated upstream -<<<<<<< ours -<<<<<<< ours -======= ->>>>>>> Stashed changes impl Fusion { + /// Creates a qubit encoding from the fusion tree. The encoding is a list of + /// FusionPairs that represent the anyons that are fused to create the qubit + /// encoding. pub fn ising_qubit_enc(&self) -> Vec { let enum_to_canonical = |charge: IsingTopoCharge| -> [u64; 3] { match charge { @@ -64,7 +70,7 @@ impl Fusion { .state .anyons() .iter() - .map(|a| enum_to_canonical(a.charge())) + .map(|a| enum_to_canonical(a.charge().get_ising())) .collect(); let mut fusion_pair_tc: HashMap = HashMap::new(); @@ -102,6 +108,8 @@ impl Fusion { encoding_fusions } + /// Applies the fusion rules to two anyons and returns the resulting anyon(s). + /// Format is [psi, vacuum, sigma] (so we can use the index as the encode) pub fn ising_apply_fusion(&self, anyon_1: [u64; 3], anyon_2: [u64; 3]) -> [u64; 3] { let add = |a: [u64; 3], b: [u64; 3]| -> [u64; 3] { std::array::from_fn(|i| a[i] + b[i]) }; let arr_scale = |a: [u64; 3], b: u64| -> [u64; 3] { std::array::from_fn(|i| a[i] * b) }; @@ -110,145 +118,8 @@ impl Fusion { // ising fusion rules // symmetric matrix which is built from fusion rules of (psi, 1, sigma) ^ (psi, 1, sigma) - let fusion_rules_mtx = [ - [[0 as u64, 1, 0], [1, 0, 0], [0, 0, 1]], - [[1, 0, 0], [0, 1, 0], [0, 0, 1]], - [[0, 0, 1], [0, 0, 1], [1, 1, 0]], - ]; - - // build the outer product of the two tc vectors - let mut tc_mtx = [[0; 3]; 3]; - for i in 0..tc_mtx.len() { - for j in 0..tc_mtx[i].len() { - tc_mtx[i][j] = anyon_1[i] * anyon_2[j]; - } - } - - // mtx multiply fusion rules with tc_mtx - for i in 0..3 { - for j in 0..3 { - output = add(output, arr_scale(fusion_rules_mtx[i][j], tc_mtx[i][j])); - } - } - - output - - } -} - -/// Python Facing Methods -#[pymethods] -impl Fusion { - #[new] - fn new(state: State) -> Self { - let operations = state.operations(); - - let mut ops: Vec> = Vec::new(); - - let mut prev_time = 0; - for (time, op) in operations { - if prev_time == time { - ops[time as usize - 1].push(op); - } else { - ops.push(vec![op]); - prev_time = time; - } - } - - Fusion { state, ops } - } - - /// Verifies the basis - fn verify_basis(&self, basis: &Basis) -> PyResult { - Ok(basis.verify_basis(self.state.anyons().len())) - } - - /// Assumes model is Ising -<<<<<<< Updated upstream -<<<<<<< Updated upstream - fn qubit_enc(&self) -> PyResult> { -======= -impl Fusion { - pub fn ising_qubit_enc(&self) -> Vec { ->>>>>>> theirs -======= -impl Fusion { - pub fn ising_qubit_enc(&self) -> Vec { ->>>>>>> theirs -======= -impl Fusion { - pub fn ising_qubit_enc(&self) -> Vec { ->>>>>>> theirs - let enum_to_canonical = |charge: IsingTopoCharge| -> [u64; 3] { - match charge { - IsingTopoCharge::Psi => [1, 0, 0], - IsingTopoCharge::Vacuum => [0, 1, 0], - IsingTopoCharge::Sigma => [0, 0, 1], - } - }; - let mut tcs: Vec<[u64; 3]> = self - .state - .anyons() - .iter() - .map(|a| enum_to_canonical(a.charge())) - .collect(); - let mut fusion_pair_tc: HashMap = HashMap::new(); - - let mut final_tc: [u64; 3] = [0, 0, 0]; - - for (i, op) in self.ops.iter().enumerate() { - for (j, fusion_pair) in op.iter().enumerate() { - let tc = - self.ising_apply_fusion(tcs[fusion_pair.anyon_1()], tcs[fusion_pair.anyon_2()]); - if i == self.ops.len() - 1 && j == op.len() - 1 { - final_tc = tc; - break; - } - fusion_pair_tc.insert(fusion_pair.clone(), tc); - tcs[fusion_pair.anyon_1()] = tc; - } -======= -======= ->>>>>>> Stashed changes - fn qubit_enc(&self, anyon_model: &AnyonModel) -> PyResult> { - match anyon_model { - AnyonModel::Ising => Ok(self.ising_qubit_enc()), - _ => Err(PyValueError::new_err("This model is not supported yet")), -<<<<<<< Updated upstream ->>>>>>> Stashed changes - } -<<<<<<< ours -======= - - if final_tc[IsingTopoCharge::Sigma.value()] == 0 - && ((final_tc[IsingTopoCharge::Psi.value()] == 1 - && final_tc[IsingTopoCharge::Vacuum.value()] == 0) - || (final_tc[IsingTopoCharge::Psi.value()] == 1 - && final_tc[IsingTopoCharge::Vacuum.value()] == 0)) - { - return Vec::new(); - } - - let mut encoding_fusions: Vec = fusion_pair_tc - .into_iter() - .filter(|(_, tc)| tc[IsingTopoCharge::Sigma.value()] == 0) - .map(|(fusion_pair, _)| fusion_pair) - .collect(); - encoding_fusions.sort(); - encoding_fusions.pop().unwrap(); - encoding_fusions - } - - pub fn ising_apply_fusion(&self, anyon_1: [u64; 3], anyon_2: [u64; 3]) -> [u64; 3] { - let add = |a: [u64; 3], b: [u64; 3]| -> [u64; 3] { std::array::from_fn(|i| a[i] + b[i]) }; - let arr_scale = |a: [u64; 3], b: u64| -> [u64; 3] { std::array::from_fn(|i| a[i] * b) }; - - let mut output = [0 as u64; 3]; - - // ising fusion rules - // symmetric matrix which is built from fusion rules of (psi, 1, sigma) ^ (psi, 1, sigma) - let fusion_rules_mtx = [ - [[0 as u64, 1, 0], [1, 0, 0], [0, 0, 1]], + let fusion_rules_mtx: [[[u64; 3]; 3]; 3] = [ + [[0, 1, 0], [1, 0, 0], [0, 0, 1]], [[1, 0, 0], [0, 1, 0], [0, 0, 1]], [[0, 0, 1], [0, 0, 1], [1, 1, 0]], ]; @@ -269,7 +140,6 @@ impl Fusion { } output - } } @@ -300,22 +170,11 @@ impl Fusion { Ok(basis.verify_basis(self.state.anyons().len())) } - /// Assumes model is Ising fn qubit_enc(&self, anyon_model: &AnyonModel) -> PyResult> { match anyon_model { AnyonModel::Ising => Ok(self.ising_qubit_enc()), _ => Err(PyValueError::new_err("This model is not supported yet")), } -<<<<<<< ours -<<<<<<< ours ->>>>>>> theirs -======= ->>>>>>> theirs -======= - } ->>>>>>> Stashed changes -======= ->>>>>>> theirs } /// Builds the fusion tree's graphical representation @@ -343,9 +202,6 @@ impl Fusion { } }); - // - // here rishi :3 - // for fusion_pair in level.iter() { let start = 2 * (fusion_pair.anyon_1()) + 1; let end = 2 * (fusion_pair.anyon_2()); @@ -370,9 +226,12 @@ impl Fusion { Ok(format!("{}\n{}\n{}{}", top_level, level_2, body, last_time).to_string()) } - /// Fuses anyons according to their fusion rules - /// Format is [psi, vacuum, sigma] (so we can use the index as the encode) - pub fn apply_fusion(&self, anyon_1: [u64; 3], anyon_2: [u64; 3], anyon_model: &AnyonModel ) -> PyResult<[u64; 3]> { + pub fn apply_fusion( + &self, + anyon_1: [u64; 3], + anyon_2: [u64; 3], + anyon_model: &AnyonModel, + ) -> PyResult<[u64; 3]> { match anyon_model { AnyonModel::Ising => Ok(self.ising_apply_fusion(anyon_1, anyon_2)), _ => Err(PyValueError::new_err("This model is not supported yet")), diff --git a/src/fusion/state.rs b/src/fusion/state.rs index 4e1e336..124ca3f 100644 --- a/src/fusion/state.rs +++ b/src/fusion/state.rs @@ -4,6 +4,9 @@ use pyo3::prelude::*; /// The state of the system #[pyclass] #[derive(Clone, Debug, PartialEq)] +/// Stores the overall state of the system. Use this struct to keep track of any +/// common information throughout the simulation (e.g. anyons, operations, +/// statevector). pub struct State { #[pyo3(get)] anyons: Vec, @@ -11,43 +14,19 @@ pub struct State { operations: Vec<(u32, FusionPair)>, } -pub trait AccessState { - /// Get the anyons in the state - fn anyons(&self) -> Vec; - - /// Get the operations in the state - fn operations(&self) -> Vec<(u32, FusionPair)>; -} - -impl AccessState for State { - fn anyons(&self) -> Vec { +/// Internal Methods +impl State { + pub fn anyons(&self) -> Vec { self.anyons.clone() } - fn operations(&self) -> Vec<(u32, FusionPair)> { + pub fn operations(&self) -> Vec<(u32, FusionPair)> { self.operations.clone() } -} - -#[pymethods] -impl State { - #[new] - fn new() -> Self { - State { - anyons: Vec::new(), - operations: Vec::new(), - } - } - - /// Add an anyon to the state - fn add_anyon(&mut self, anyon: Anyon) -> PyResult { - self.anyons.push(anyon); - Ok(true) - } /// Verify the operation /// TODO: Provide better error for panic when no anyons loaded - fn verify_operation(&self, time: u32, operation: &FusionPair) -> bool { + pub fn verify_operation(&self, time: u32, operation: &FusionPair) -> bool { assert!(operation.anyon_1() < operation.anyon_2()); assert!(operation.anyon_2() < self.anyons.len()); let mut fusible_anyons = vec![true; self.anyons.len()]; @@ -70,8 +49,26 @@ impl State { } true } +} + +/// Python Methods +#[pymethods] +impl State { + #[new] + fn new() -> Self { + State { + anyons: Vec::new(), + operations: Vec::new(), + } + } + + /// Add an anyon to the state + fn add_anyon(&mut self, anyon: Anyon) -> PyResult { + self.anyons.push(anyon); + Ok(true) + } - /// Add an operation to the state + /// Verifies and then adds an operation to the state fn add_operation(&mut self, time: u32, operation: FusionPair) -> PyResult { let result = Self::verify_operation(self, time, &operation); if !result { diff --git a/src/lib.rs b/src/lib.rs index 89bf2ae..6089266 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,7 +11,7 @@ fn anyon_braiding_simulator(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; - // m.add_class::()?; + m.add_class::()?; m.add_class::()?; m.add_class::()?; diff --git a/src/model/anyon.rs b/src/model/anyon.rs index 42e3d71..28c02c2 100644 --- a/src/model/anyon.rs +++ b/src/model/anyon.rs @@ -2,6 +2,7 @@ use pyo3::prelude::*; #[pyclass] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +/// Options for the topological charge for an Ising Model anyon pub enum IsingTopoCharge { Psi, Vacuum, @@ -10,6 +11,7 @@ pub enum IsingTopoCharge { #[pyclass] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +/// Options for the topological charge for an Fibonacci Model anyon pub enum FibonacciTopoCharge { Tau, Vacuum, @@ -17,6 +19,8 @@ pub enum FibonacciTopoCharge { #[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] +/// Options for the topological charge for an anyon +/// Currently only supports Ising and Fibonacci models pub struct TopoCharge { ising: Option, fibonacci: Option, @@ -92,15 +96,6 @@ impl TopoCharge { impl IsingTopoCharge { pub fn value(&self) -> usize { *self as usize - // match self { - // IsingTopoCharge::Psi => 0, - // IsingTopoCharge::Vacuum => 1, - // IsingTopoCharge::Sigma => 2, - // } -<<<<<<< ours -<<<<<<< Updated upstream -<<<<<<< ours -<<<<<<< ours } pub fn to_string(&self) -> &str { @@ -109,24 +104,20 @@ impl IsingTopoCharge { IsingTopoCharge::Vacuum => "Vacuum", IsingTopoCharge::Sigma => "Sigma", } -======= ->>>>>>> theirs -======= ->>>>>>> theirs -======= ->>>>>>> Stashed changes -======= ->>>>>>> theirs } } #[pyclass] #[derive(Clone, Debug, PartialEq)] +/// In Topological Quantum Computing, anyons are the fundamental quasiparticles +/// which enable the computation. Anyons have an associated topological charge +/// given by the model used. This struct represents an anyon with a name, +/// charge, and position. pub struct Anyon { #[pyo3(get)] name: String, #[pyo3(get)] - charge: IsingTopoCharge, + charge: TopoCharge, #[pyo3(get)] position: (f64, f64), } @@ -136,7 +127,7 @@ impl Anyon { &self.name } - pub fn charge(&self) -> IsingTopoCharge { + pub fn charge(&self) -> TopoCharge { self.charge.clone() } @@ -148,7 +139,7 @@ impl Anyon { #[pymethods] impl Anyon { #[new] - pub fn new(name: String, charge: IsingTopoCharge, position: (f64, f64)) -> Self { + pub fn new(name: String, charge: TopoCharge, position: (f64, f64)) -> Self { Anyon { name, charge, diff --git a/src/model/model.rs b/src/model/model.rs index c1abd39..0d5f3ef 100644 --- a/src/model/model.rs +++ b/src/model/model.rs @@ -22,21 +22,22 @@ impl AnyonModel { // Commenting out Model for now because it has no use atm We might port the // python stuff to rust later, but for now we have no use -// /// The parameters accompanying a model -// #[pyclass] -// pub struct Model { -// model_type: AnyonModel, -// // more fields which we'll impl later -// } +/// The parameters accompanying a model +/// More docs later when we impl stuff from python +#[pyclass] +pub struct Model { + model_type: AnyonModel, + // more fields which we'll impl later +} -// #[pymethods] -// impl Model { -// #[new] -// fn new() -> Self { -// // Model { model_type } -// Model { -// model_type: AnyonModel::Ising, -// } -// } +#[pymethods] +impl Model { + #[new] + fn new() -> Self { + // Model { model_type } + Model { + model_type: AnyonModel::Ising, + } + } -// } +} From c451ffda3d0c3dae52207c1c40e26881830f78bf Mon Sep 17 00:00:00 2001 From: Haadi Khan Date: Wed, 19 Jun 2024 04:23:13 -0400 Subject: [PATCH 3/7] Updated tests w/ new topo charge structure --- python/tests/test_anyon.py | 6 +++--- python/tests/test_braiding.py | 12 ++++++------ python/tests/test_fusion.py | 16 ++++++++-------- python/tests/test_state.py | 8 ++++---- src/lib.rs | 1 + 5 files changed, 22 insertions(+), 21 deletions(-) diff --git a/python/tests/test_anyon.py b/python/tests/test_anyon.py index bcf99ef..e710603 100644 --- a/python/tests/test_anyon.py +++ b/python/tests/test_anyon.py @@ -1,10 +1,10 @@ import pytest -from anyon_braiding_simulator import Anyon, IsingTopoCharge +from anyon_braiding_simulator import Anyon, IsingTopoCharge, TopoCharge @pytest.mark.anyon def test_anyon(): - anyon = Anyon('thisisatest', IsingTopoCharge.Psi, (1, 1)) + anyon = Anyon('thisisatest', TopoCharge.from_ising(IsingTopoCharge.Psi), (1, 1)) assert anyon.name == 'thisisatest' - assert anyon.charge == IsingTopoCharge.Psi + assert anyon.charge.get_ising() == IsingTopoCharge.Psi assert anyon.position == (1, 1) diff --git a/python/tests/test_braiding.py b/python/tests/test_braiding.py index 58ed8c3..2b45f4c 100644 --- a/python/tests/test_braiding.py +++ b/python/tests/test_braiding.py @@ -10,16 +10,16 @@ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'model'))) -from anyon_braiding_simulator import Anyon, IsingTopoCharge +from anyon_braiding_simulator import Anyon, IsingTopoCharge, TopoCharge @pytest.fixture def setup_anyons(): return [ - Anyon('A', IsingTopoCharge.Psi, (1, 1)), - Anyon('B', IsingTopoCharge.Psi, (2, 2)), - Anyon('C', IsingTopoCharge.Psi, (3, 3)), - Anyon('D', IsingTopoCharge.Psi, (4, 4)), + Anyon('A', TopoCharge.from_ising(IsingTopoCharge.Psi), (1, 1)), + Anyon('B', TopoCharge.from_ising(IsingTopoCharge.Psi), (2, 2)), + Anyon('C', TopoCharge.from_ising(IsingTopoCharge.Psi), (3, 3)), + Anyon('D', TopoCharge.from_ising(IsingTopoCharge.Psi), (4, 4)), ] @@ -36,7 +36,7 @@ def test_braid_initialization(setup_anyons): # Test with duplicate anyon names duplicate_anyons = setup_anyons[:] - duplicate_anyons[3] = Anyon('A', IsingTopoCharge.Psi, (4, 4)) + duplicate_anyons[3] = Anyon('A', TopoCharge.from_ising(IsingTopoCharge.Psi), (4, 4)) with pytest.raises(ValueError, match='Duplicate anyon names detected'): Braid(duplicate_anyons) diff --git a/python/tests/test_fusion.py b/python/tests/test_fusion.py index 84db54c..0a7b8d7 100644 --- a/python/tests/test_fusion.py +++ b/python/tests/test_fusion.py @@ -1,12 +1,12 @@ import pytest -from anyon_braiding_simulator import Anyon, Fusion, FusionPair, IsingTopoCharge, State +from anyon_braiding_simulator import Anyon, AnyonModel, Fusion, FusionPair, IsingTopoCharge, State, TopoCharge @pytest.fixture def state() -> State: state = State() for i in range(6): - state.add_anyon(Anyon(f'{i}', IsingTopoCharge.Sigma, (0, 0))) + state.add_anyon(Anyon(f'{i}', TopoCharge.from_ising(IsingTopoCharge.Sigma), (0, 0))) return state @@ -32,13 +32,13 @@ def test_apply_ising_fusion(state): vacuum = [0, 1, 0] sigma = [0, 0, 1] - assert fusion.apply_fusion(psi, psi) == vacuum - assert fusion.apply_fusion(vacuum, vacuum) == vacuum - assert fusion.apply_fusion(sigma, sigma) == [psi[i] + vacuum[i] for i in range(3)] + assert fusion.apply_fusion(psi, psi, AnyonModel.Ising) == vacuum + assert fusion.apply_fusion(vacuum, vacuum, AnyonModel.Ising) == vacuum + assert fusion.apply_fusion(sigma, sigma, AnyonModel.Ising) == [psi[i] + vacuum[i] for i in range(3)] psi_sigma = [1, 0, 1] - assert fusion.apply_fusion(psi_sigma, psi_sigma) == [1, 2, 2] - assert not fusion.apply_fusion(psi_sigma, psi_sigma) == [2, 1, 2] # get owned rishi + assert fusion.apply_fusion(psi_sigma, psi_sigma, AnyonModel.Ising) == [1, 2, 2] + assert not fusion.apply_fusion(psi_sigma, psi_sigma, AnyonModel.Ising) == [2, 1, 2] # get owned rishi @pytest.mark.fusion @@ -52,4 +52,4 @@ def test_qubit_enc(state): fusion = Fusion(state) correct = [FusionPair(0, 1), FusionPair(2, 4), FusionPair(2, 3)] - assert set(map(str, fusion.qubit_enc())) == set(map(str, correct)) + assert set(map(str, fusion.qubit_enc(AnyonModel.Ising))) == set(map(str, correct)) diff --git a/python/tests/test_state.py b/python/tests/test_state.py index 112c2b5..2c5a956 100644 --- a/python/tests/test_state.py +++ b/python/tests/test_state.py @@ -1,5 +1,5 @@ import pytest -from anyon_braiding_simulator import Anyon, FusionPair, IsingTopoCharge, State +from anyon_braiding_simulator import Anyon, FusionPair, IsingTopoCharge, State, TopoCharge @pytest.fixture @@ -10,12 +10,12 @@ def state() -> State: @pytest.mark.state def test_add_anyon(state): for i in range(100): - anyon = Anyon(f'{i}', IsingTopoCharge.Sigma, (0, 0)) + anyon = Anyon(f'{i}', TopoCharge.from_ising(IsingTopoCharge.Sigma), (0, 0)) state.add_anyon(anyon) for anyon in state.anyons: assert anyon.name in [f'{i}' for i in range(100)] - assert anyon.charge == IsingTopoCharge.Sigma + assert anyon.charge.get_ising() == IsingTopoCharge.Sigma assert anyon.position == (0, 0) assert len(state.anyons) == 100 @@ -23,7 +23,7 @@ def test_add_anyon(state): def test_add_operation(state): for i in range(101): - anyon = Anyon(f'{i}', IsingTopoCharge.Sigma, (0, 0)) + anyon = Anyon(f'{i}', TopoCharge.from_ising(IsingTopoCharge.Sigma), (0, 0)) state.add_anyon(anyon) assert state.add_operation(1, FusionPair(0, 1)) diff --git a/src/lib.rs b/src/lib.rs index 6089266..9adb3c1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ mod util; fn anyon_braiding_simulator(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; + m.add_class::()?; m.add_class::()?; m.add_class::()?; From b8e02973a8d9307a1b1465be1e5168c1a906950e Mon Sep 17 00:00:00 2001 From: Haadi Khan Date: Fri, 21 Jun 2024 00:29:22 -0400 Subject: [PATCH 4/7] Added Fusion Verification Closes #45. --- python/tests/test_fusion.py | 17 ++++++++++++++ src/fusion/fusion.rs | 46 +++++++++++++++++++++++++++++-------- 2 files changed, 53 insertions(+), 10 deletions(-) diff --git a/python/tests/test_fusion.py b/python/tests/test_fusion.py index 0a7b8d7..64dab3d 100644 --- a/python/tests/test_fusion.py +++ b/python/tests/test_fusion.py @@ -53,3 +53,20 @@ def test_qubit_enc(state): correct = [FusionPair(0, 1), FusionPair(2, 4), FusionPair(2, 3)] assert set(map(str, fusion.qubit_enc(AnyonModel.Ising))) == set(map(str, correct)) + + +@pytest.mark.fusion +def test_verify_fusion_result(state): + for i in range(3): + state.add_anyon(Anyon(f'{i}', TopoCharge.from_ising(IsingTopoCharge.Sigma), (0, 0))) + + fusion = Fusion(state) + assert fusion.verify_fusion_result(TopoCharge.from_ising(IsingTopoCharge.Sigma), AnyonModel.Ising) + assert not fusion.verify_fusion_result(TopoCharge.from_ising(IsingTopoCharge.Vacuum), AnyonModel.Ising) + assert not fusion.verify_fusion_result(TopoCharge.from_ising(IsingTopoCharge.Psi), AnyonModel.Ising) + + state.add_anyon(Anyon('3', TopoCharge.from_ising(IsingTopoCharge.Sigma), (0, 0))) + fusion = Fusion(state) + assert fusion.verify_fusion_result(TopoCharge.from_ising(IsingTopoCharge.Vacuum), AnyonModel.Ising) + assert fusion.verify_fusion_result(TopoCharge.from_ising(IsingTopoCharge.Psi), AnyonModel.Ising) + assert not fusion.verify_fusion_result(TopoCharge.from_ising(IsingTopoCharge.Sigma), AnyonModel.Ising) diff --git a/src/fusion/fusion.rs b/src/fusion/fusion.rs index 4c32484..3d53bbe 100644 --- a/src/fusion/fusion.rs +++ b/src/fusion/fusion.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use crate::fusion::state::State; use crate::model::anyon::IsingTopoCharge; +use crate::model::anyon::TopoCharge; use crate::model::model::AnyonModel; use crate::util::basis::Basis; use pyo3::exceptions::PyValueError; @@ -55,22 +56,25 @@ pub struct Fusion { /// Internal Methods impl Fusion { + /// Converts from IsingTopoCharge to internal format + /// Format is [psi, vacuum, sigma] (so we can use the index as the encode) + pub fn ising_canonical_topo_charge(&self, charge: IsingTopoCharge) -> [u64; 3] { + match charge { + IsingTopoCharge::Psi => [1, 0, 0], + IsingTopoCharge::Vacuum => [0, 1, 0], + IsingTopoCharge::Sigma => [0, 0, 1], + } + } + /// Creates a qubit encoding from the fusion tree. The encoding is a list of /// FusionPairs that represent the anyons that are fused to create the qubit /// encoding. pub fn ising_qubit_enc(&self) -> Vec { - let enum_to_canonical = |charge: IsingTopoCharge| -> [u64; 3] { - match charge { - IsingTopoCharge::Psi => [1, 0, 0], - IsingTopoCharge::Vacuum => [0, 1, 0], - IsingTopoCharge::Sigma => [0, 0, 1], - } - }; let mut tcs: Vec<[u64; 3]> = self .state .anyons() .iter() - .map(|a| enum_to_canonical(a.charge().get_ising())) + .map(|a| self.ising_canonical_topo_charge(a.charge().get_ising())) .collect(); let mut fusion_pair_tc: HashMap = HashMap::new(); @@ -109,7 +113,6 @@ impl Fusion { } /// Applies the fusion rules to two anyons and returns the resulting anyon(s). - /// Format is [psi, vacuum, sigma] (so we can use the index as the encode) pub fn ising_apply_fusion(&self, anyon_1: [u64; 3], anyon_2: [u64; 3]) -> [u64; 3] { let add = |a: [u64; 3], b: [u64; 3]| -> [u64; 3] { std::array::from_fn(|i| a[i] + b[i]) }; let arr_scale = |a: [u64; 3], b: u64| -> [u64; 3] { std::array::from_fn(|i| a[i] * b) }; @@ -141,6 +144,22 @@ impl Fusion { output } + + /// Checks if an overall fusion result is possible given the state's + /// configuration and an initial topo charge + pub fn ising_verify_fusion_result(&self, init_charge: IsingTopoCharge) -> bool { + self.state + .anyons() + .iter_mut() + .map(|a| self.ising_canonical_topo_charge(a.charge().get_ising())) + .reduce(|acc, tc| self.ising_apply_fusion(acc, tc)) + .unwrap() + .iter() + .zip(self.ising_canonical_topo_charge(init_charge).iter()) + .all(|(a, b)| if *b > 0 { *a > 0 } else { true }) + // if an element > 0 that means it was our initial charge, so we need to + // check if our final fusion result also has that element > 0 + } } /// Python Facing Methods @@ -226,7 +245,7 @@ impl Fusion { Ok(format!("{}\n{}\n{}{}", top_level, level_2, body, last_time).to_string()) } - pub fn apply_fusion( + fn apply_fusion( &self, anyon_1: [u64; 3], anyon_2: [u64; 3], @@ -237,4 +256,11 @@ impl Fusion { _ => Err(PyValueError::new_err("This model is not supported yet")), } } + + fn verify_fusion_result(&self, init_charge: TopoCharge, anyon_model: &AnyonModel) -> bool { + match anyon_model { + AnyonModel::Ising => self.ising_verify_fusion_result(init_charge.get_ising()), + _ => false, + } + } } From e7e03976afd87e3398bc0cb209a2bb3ae863ea84 Mon Sep 17 00:00:00 2001 From: Haadi Khan Date: Fri, 21 Jun 2024 01:14:58 -0400 Subject: [PATCH 5/7] Small cleaning, fixed test setup, precondition --- python/tests/test_fusion.py | 15 ++++++--------- src/fusion/fusion.rs | 17 +++++++++++------ 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/python/tests/test_fusion.py b/python/tests/test_fusion.py index 64dab3d..5f6937a 100644 --- a/python/tests/test_fusion.py +++ b/python/tests/test_fusion.py @@ -57,16 +57,13 @@ def test_qubit_enc(state): @pytest.mark.fusion def test_verify_fusion_result(state): - for i in range(3): - state.add_anyon(Anyon(f'{i}', TopoCharge.from_ising(IsingTopoCharge.Sigma), (0, 0))) + fusion = Fusion(state) + assert not fusion.verify_fusion_result(TopoCharge.from_ising(IsingTopoCharge.Sigma), AnyonModel.Ising) + assert fusion.verify_fusion_result(TopoCharge.from_ising(IsingTopoCharge.Vacuum), AnyonModel.Ising) + assert fusion.verify_fusion_result(TopoCharge.from_ising(IsingTopoCharge.Psi), AnyonModel.Ising) + state.add_anyon(Anyon('7', TopoCharge.from_ising(IsingTopoCharge.Sigma), (0, 0))) fusion = Fusion(state) - assert fusion.verify_fusion_result(TopoCharge.from_ising(IsingTopoCharge.Sigma), AnyonModel.Ising) assert not fusion.verify_fusion_result(TopoCharge.from_ising(IsingTopoCharge.Vacuum), AnyonModel.Ising) assert not fusion.verify_fusion_result(TopoCharge.from_ising(IsingTopoCharge.Psi), AnyonModel.Ising) - - state.add_anyon(Anyon('3', TopoCharge.from_ising(IsingTopoCharge.Sigma), (0, 0))) - fusion = Fusion(state) - assert fusion.verify_fusion_result(TopoCharge.from_ising(IsingTopoCharge.Vacuum), AnyonModel.Ising) - assert fusion.verify_fusion_result(TopoCharge.from_ising(IsingTopoCharge.Psi), AnyonModel.Ising) - assert not fusion.verify_fusion_result(TopoCharge.from_ising(IsingTopoCharge.Sigma), AnyonModel.Ising) + assert fusion.verify_fusion_result(TopoCharge.from_ising(IsingTopoCharge.Sigma), AnyonModel.Ising) diff --git a/src/fusion/fusion.rs b/src/fusion/fusion.rs index 3d53bbe..a8520ed 100644 --- a/src/fusion/fusion.rs +++ b/src/fusion/fusion.rs @@ -147,18 +147,23 @@ impl Fusion { /// Checks if an overall fusion result is possible given the state's /// configuration and an initial topo charge + /// + /// Precondition: Non empty list of anyons pub fn ising_verify_fusion_result(&self, init_charge: IsingTopoCharge) -> bool { - self.state + let overall_fusion_result: [u64; 3] = self + .state .anyons() - .iter_mut() + .iter() .map(|a| self.ising_canonical_topo_charge(a.charge().get_ising())) .reduce(|acc, tc| self.ising_apply_fusion(acc, tc)) - .unwrap() - .iter() - .zip(self.ising_canonical_topo_charge(init_charge).iter()) - .all(|(a, b)| if *b > 0 { *a > 0 } else { true }) + .unwrap(); + // if an element > 0 that means it was our initial charge, so we need to // check if our final fusion result also has that element > 0 + overall_fusion_result + .iter() + .zip(self.ising_canonical_topo_charge(init_charge).iter()) + .all(|(a, b)| *b <= 0 || *a > 0) } } From 17bd248e6a83206edae1e202b51b71177f8de2e2 Mon Sep 17 00:00:00 2001 From: Haadi Khan Date: Fri, 21 Jun 2024 18:51:24 -0400 Subject: [PATCH 6/7] Added get_model --- python/anyon_braiding_simulator/Simulator.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/python/anyon_braiding_simulator/Simulator.py b/python/anyon_braiding_simulator/Simulator.py index 1e8615a..250f9d6 100644 --- a/python/anyon_braiding_simulator/Simulator.py +++ b/python/anyon_braiding_simulator/Simulator.py @@ -36,6 +36,14 @@ def set_model(self, model: Model) -> None: """ self._model = model + def get_model(self) -> Model: + """ + Get the model for the simulator. + """ + if self._model is None: + raise ValueError('Model has not been set') + return self._model + def list_anyons(self) -> list: """ List the anyons currently in the simulator. From 2305250d0a1663667dc49db9fa941155f3d44af5 Mon Sep 17 00:00:00 2001 From: Haadi Khan Date: Fri, 21 Jun 2024 21:04:44 -0400 Subject: [PATCH 7/7] Main checks for anyon model --- python/anyon_braiding_simulator/Model.py | 6 ++++++ python/anyon_braiding_simulator/main.py | 25 +++++++++++++++++------- src/lib.rs | 1 + 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/python/anyon_braiding_simulator/Model.py b/python/anyon_braiding_simulator/Model.py index b47630c..63fbcdd 100644 --- a/python/anyon_braiding_simulator/Model.py +++ b/python/anyon_braiding_simulator/Model.py @@ -59,6 +59,12 @@ def __init__(self, model_type: AnyonModel, num_fusion_channels=5) -> None: self._num_fusion_channels = num_fusion_channels + def get_model_type(self) -> AnyonModel: + """ + Provides the model type + """ + return self.model_type + def get_charges(self) -> set: """ Provide the charges that are defined in this model. diff --git a/python/anyon_braiding_simulator/main.py b/python/anyon_braiding_simulator/main.py index 5e73706..3d31e18 100644 --- a/python/anyon_braiding_simulator/main.py +++ b/python/anyon_braiding_simulator/main.py @@ -3,7 +3,7 @@ import subprocess import sys -from anyon_braiding_simulator import Anyon, AnyonModel, IsingTopoCharge +from anyon_braiding_simulator import Anyon, AnyonModel, FibonacciTopoCharge, IsingTopoCharge, TopoCharge from Braiding import Braid from Model import Model from Simulator import Simulator @@ -23,11 +23,22 @@ def anyon(*args): topological_charge = args[1] position = () - topo_charge = { - 'psi': IsingTopoCharge.Psi, - 'sigma': IsingTopoCharge.Sigma, - 'vac': IsingTopoCharge.Vacuum, - } + topo_charge = {} + if sim.get_model().get_model_type() == AnyonModel.Ising: + topo_charge = { + 'psi': IsingTopoCharge.Psi, + 'sigma': IsingTopoCharge.Sigma, + 'vac': IsingTopoCharge.Vacuum, + } + elif sim.get_model().get_model_type() == AnyonModel.Fibonacci: + topo_charge = { + 'tau': FibonacciTopoCharge.Tau, + 'Vacuum': FibonacciTopoCharge.Vacuum, + } + else: + print('Error: Model not set') + return + try: topological_charge = topo_charge[args[1].lower()] except KeyError: @@ -67,7 +78,7 @@ def anyon(*args): print('Error: position must be formatted as {x,y} where x and y are numbers') return - new_anyon = Anyon(name, topological_charge, position) + new_anyon = Anyon(name, TopoCharge(topological_charge), position) try: sim.update_anyons(True, [new_anyon]) if len(args) == 2: diff --git a/src/lib.rs b/src/lib.rs index 9adb3c1..ee3f8ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ mod util; fn anyon_braiding_simulator(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; + m.add_class::()?; m.add_class::()?; m.add_class::()?;