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/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..5f6937a 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,18 @@ 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)) + + +@pytest.mark.fusion +def test_verify_fusion_result(state): + 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 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) + assert fusion.verify_fusion_result(TopoCharge.from_ising(IsingTopoCharge.Sigma), AnyonModel.Ising) 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/fusion/fusion.rs b/src/fusion/fusion.rs index 148174f..a8520ed 100644 --- a/src/fusion/fusion.rs +++ b/src/fusion/fusion.rs @@ -1,14 +1,21 @@ 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::anyon::TopoCharge; +use crate::model::model::AnyonModel; 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, @@ -16,16 +23,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 } } @@ -43,51 +45,36 @@ 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>, } -#[pymethods] +/// Internal Methods 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; - } + /// 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], } - - 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) -> PyResult> { - 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], - } - }; + /// 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 mut tcs: Vec<[u64; 3]> = self .state .anyons() .iter() - .map(|a| enum_to_canonical(a.charge())) + .map(|a| self.ising_canonical_topo_charge(a.charge().get_ising())) .collect(); let mut fusion_pair_tc: HashMap = HashMap::new(); @@ -96,7 +83,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; @@ -112,7 +99,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 +109,96 @@ impl Fusion { .collect(); encoding_fusions.sort(); encoding_fusions.pop().unwrap(); - Ok(encoding_fusions) + encoding_fusions + } + + /// Applies the fusion rules to two anyons and returns the resulting anyon(s). + 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: [[[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]], + ]; + + // 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 + } + + /// 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 { + let overall_fusion_result: [u64; 3] = self + .state + .anyons() + .iter() + .map(|a| self.ising_canonical_topo_charge(a.charge().get_ising())) + .reduce(|acc, tc| self.ising_apply_fusion(acc, tc)) + .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) + } +} + +/// 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())) + } + + 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")), + } } /// Builds the fusion tree's graphical representation @@ -150,9 +226,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()); @@ -177,38 +250,22 @@ impl Fusion { Ok(format!("{}\n{}\n{}{}", top_level, level_2, body, last_time).to_string()) } - /// 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]; - } + 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")), } + } - // 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])); - } + 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, } - - return Ok(output); } } diff --git a/src/fusion/state.rs b/src/fusion/state.rs index 5502dfc..124ca3f 100644 --- a/src/fusion/state.rs +++ b/src/fusion/state.rs @@ -1,9 +1,12 @@ -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 #[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 4ae8239..ee3f8ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,11 +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..28c02c2 100644 --- a/src/model/anyon.rs +++ b/src/model/anyon.rs @@ -1,22 +1,101 @@ 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)] +/// Options for the topological charge for an Ising Model anyon pub enum IsingTopoCharge { Psi, Vacuum, Sigma, } +#[pyclass] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +/// Options for the topological charge for an Fibonacci Model anyon +pub enum FibonacciTopoCharge { + Tau, + Vacuum, +} + +#[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, +} + +// 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 } pub fn to_string(&self) -> &str { @@ -30,31 +109,29 @@ impl IsingTopoCharge { #[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), } -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) -> TopoCharge { self.charge.clone() } - fn position(&self) -> (f64, f64) { + pub fn position(&self) -> (f64, f64) { self.position } } @@ -62,7 +139,7 @@ impl AccessAnyon for 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 98d6f54..0d5f3ef 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,7 +19,11 @@ 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 +/// More docs later when we impl stuff from python #[pyclass] pub struct Model { model_type: AnyonModel, 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]