From c910009665b5686493272d9f06a66039840dc52d Mon Sep 17 00:00:00 2001 From: Ryan Byrne Date: Tue, 11 Jun 2024 13:15:11 -0400 Subject: [PATCH 1/9] Add field for defined charges in Model --- src/Model.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Model.py b/src/Model.py index 28ff58f..d9c622a 100644 --- a/src/Model.py +++ b/src/Model.py @@ -34,8 +34,9 @@ def __init__(self, model_type: AnyonModel, num_fusion_channels=5) -> None: self.model_type = model_type if model_type == AnyonModel.Ising: + self._charges = {"vacuum", "sigma", "psi"} + self._r_mtx = cmath.exp(-1j*np.pi/8)*np.array([[1,0],[0,1j]]) - self._f_mtx = np.zeros((3,3,3,3,2,2)) for i in range(3): @@ -52,6 +53,8 @@ def __init__(self, model_type: AnyonModel, num_fusion_channels=5) -> None: self._rules = [] elif model_type == AnyonModel.Fibonacci: + self._charges = {"vacuum", "psi"} + self._r_mtx = np.array([[cmath.exp(4*np.pi*1j/5), 0],[0, -1*cmath.exp(2*np.pi*1j/5)]]) self._f_mtx = [] self._rules = [] @@ -63,6 +66,12 @@ def __init__(self, model_type: AnyonModel, num_fusion_channels=5) -> None: raise ValueError("Model type not recognized") self._num_fusion_channels = num_fusion_channels + + def get_charges(self) -> set: + """ + Provide the charges that are defined in this model. + """ + return self._charges def getFMatrix(self, a: str, b: str, c: str, d: str) -> np.ndarray: From 733820f9383da92a8c4959890cc08cddea81c10c Mon Sep 17 00:00:00 2001 From: Ryan Byrne Date: Tue, 11 Jun 2024 13:16:44 -0400 Subject: [PATCH 2/9] Check in Braid init if charges valid given model --- src/Braiding.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Braiding.py b/src/Braiding.py index 983cfed..029948c 100644 --- a/src/Braiding.py +++ b/src/Braiding.py @@ -1,6 +1,7 @@ import numpy as np from Anyon import Anyon +from Model import Model def apply_unitary(state, unitary): @@ -47,7 +48,7 @@ def track_state_history(model, initial_state, operations): class Braid: - def __init__(self, anyons: list[Anyon]): + def __init__(self, anyons: list[Anyon], model: Model): """ Parameters: anyons (list): List of Anyon objects @@ -61,6 +62,14 @@ def __init__(self, anyons: list[Anyon]): names = [anyon.name for anyon in anyons] if len(names) != len(set(names)): raise ValueError("Duplicate anyon names detected") + + # Check if all charges of given anyons are included in given model + charges = model.get_charges() + invalid_anyons = [anyon for anyon in anyons if anyon.topo_charge not in charges] + if invalid_anyons: + names_and_charges = [f"Name: {anyon.name}, Charge: {anyon.topo_charge}" for anyon in invalid_anyons] + message = "The following anyons have charges that aren't defined in the given model:\n" + "\n".join(names_and_charges) + raise ValueError(message) self.anyons = anyons self.operations = [] From 4119c1bbf26443bd454dbd3a2ffd5a8bc4d92d99 Mon Sep 17 00:00:00 2001 From: Ryan Byrne Date: Tue, 11 Jun 2024 14:02:39 -0400 Subject: [PATCH 3/9] Updated the allowed anyon charge & position arguments - Charge is now accepted as a string in main - Added dim_of_anyon_pos field to Simulator - Position arg in main can now be omitted (defaulting to 1D) if this was the case for all previously given anyons --- src/Simulator.py | 13 +++++++++++ src/main.py | 60 +++++++++++++++++++++++++++++++++--------------- 2 files changed, 54 insertions(+), 19 deletions(-) diff --git a/src/Simulator.py b/src/Simulator.py index 78daa6d..58f2f11 100644 --- a/src/Simulator.py +++ b/src/Simulator.py @@ -6,6 +6,7 @@ def __init__(self): """ self._anyons = [] self._model = None + self._dim_of_anyon_pos = 2 # Default is 2D anyon positions def update_anyons(self, is_increasing: bool, anyons) -> None: """ @@ -31,6 +32,18 @@ def list_anyons(self) -> list: List the anyons currently in the simulator. """ return self._anyons + + def get_dim_of_anyon_pos(self) -> int: + """ + Provides the dimension of anyon positions. + """ + return self._dim_of_anyon_pos + + def switch_to_1D(self) -> None: + """ + Sets the dimension of anyon positions to be 1. + """ + self._dim_of_anyon_pos = 1 def contains_anyon(self, anyon_name: str) -> bool: """ diff --git a/src/main.py b/src/main.py index 4284295..7ff4945 100644 --- a/src/main.py +++ b/src/main.py @@ -15,32 +15,54 @@ def anyon(*args): """ Handle the anyon command. This command adds an anyon to the simulation. """ - if len(args) < 3: - print('Error: Not enough arguments') + if len(args) != 2 and len(args) != 3: + print('Error: There should be either 2 or 3 arguments') return name = args[0] - - try: - topological_charge = float(args[1]) - except ValueError: - print('Error: topological charge must be a number') - return - - try: - position = tuple(map(float, args[2].replace('{', '').replace('}', '').split(','))) - position[1] - except ValueError: - print('Error: position must be formatted as {x, y} where x and y are numbers') - return - except IndexError: - print('Error: position must be formatted as {x, y} where x and y are numbers') - return + topological_charge = args[1] + + # Why is topo charge a number here? Easier if string?: + # try: + # topological_charge = float(args[1]) + # except ValueError: + # print('Error: topological charge must be a number') + # return + + if len(args) == 2: + anyons = sim.list_anyons() + # Make sure any previous anyons were specified in 1D space (i.e. without a position argument) + if anyons and sim.get_dim_of_anyon_pos() == 2: + print('Error: you have already provided an anyon in 2D space, so the rest must also have a specified 2D position') + return + elif not anyons: + sim.switch_to_1D() + + position_1D = len(anyons) # Index/position of new anyon in 1D + position = (position_1D, 0) + print(f'Created anyon {name} with TC {topological_charge} at position {position_1D} in 1D') + else: + # Make sure any previous anyons were specified in 2D space + if sim.get_dim_of_anyon_pos() == 1: + print('Error: you have already provided an anyon in 1D space, so the positions of the rest cannot be specified in 2D') + return + + try: + position = tuple(map(float, args[2].replace('{', '').replace('}', '').split(','))) + position[1] + except ValueError: + print('Error: position must be formatted as {x,y} where x and y are numbers') + return + except IndexError: + print('Error: position must be formatted as {x,y} where x and y are numbers') + return + + print(f'Created anyon {name} with TC {topological_charge} at position {position} in 2D') new_anyon = Anyon(topological_charge, name, position) sim.update_anyons(True, [new_anyon]) - print(f'Created anyon {name} with TC {topological_charge} at position {position}') + def model(*args): From ef6dffbc2fe9753076c089986194a1f7b47289e8 Mon Sep 17 00:00:00 2001 From: Ryan Byrne Date: Fri, 14 Jun 2024 23:13:17 -0400 Subject: [PATCH 4/9] Make user input a model at the beginning of simulator --- src/main.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/main.py b/src/main.py index 7ff4945..79d764e 100644 --- a/src/main.py +++ b/src/main.py @@ -115,7 +115,7 @@ def braid(*args): print('Error: Not enough arguments') return - braid = Braid(sim.list_anyons()) + braid = Braid(sim.list_anyons(), sim._model) cmd = args[0] if cmd.lower() == 'swap': @@ -141,6 +141,30 @@ def __init__(self): 'list': 'list', } + # Prompt the user to input the anyon model + while True: + model_input = input('Enter the anyon model ("ising" or "fibonacci"): ') + if model_input.lower() == 'ising' or model_input.lower() == 'fibonacci': + break + else: + print('Error: Invalid model. Please enter either "ising" or "fibonacci"') + self.model(model_input) + + def model(self, *args): + """ + Handle the model command. This command sets the model for the simulation. + """ + if len(args) < 1: + print('Error: Not enough arguments') + return + model_type = str(args[0]) + if model_type.lower() != 'ising' and model_type.lower() != 'fibonacci': + print('Error: Model must be Ising or Fibonacci') + return + model_convert = {'ising': AnyonModel.Ising, 'fibonacci': AnyonModel.Fibonacci} + model = Model(model_convert[model_type.lower()]) + sim.set_model(model) + def do_shell(self, arg): "Run a shell command" print('running shell command:', arg) From 473fea26a442f4f5be1ba1ad92c62ccef05cf0aa Mon Sep 17 00:00:00 2001 From: Ryan Byrne Date: Sat, 15 Jun 2024 00:07:29 -0400 Subject: [PATCH 5/9] Allow for exit during init loop & prevent model change after init loop --- src/main.py | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/src/main.py b/src/main.py index 79d764e..d8a4cc8 100644 --- a/src/main.py +++ b/src/main.py @@ -1,6 +1,7 @@ # Standard Library import cmd import subprocess +import sys from Anyon import Anyon from Braiding import Braid @@ -141,29 +142,21 @@ def __init__(self): 'list': 'list', } + # Flag to indicate whether initialization (model & anyon choice) is completed + self.init_completed = False + # Prompt the user to input the anyon model while True: - model_input = input('Enter the anyon model ("ising" or "fibonacci"): ') - if model_input.lower() == 'ising' or model_input.lower() == 'fibonacci': + user_input = input('Enter the anyon model ("ising" or "fibonacci"): ') + if user_input.lower() == 'ising' or user_input.lower() == 'fibonacci': break + elif user_input.lower() == 'exit': + sys.exit(0) else: - print('Error: Invalid model. Please enter either "ising" or "fibonacci"') - self.model(model_input) - - def model(self, *args): - """ - Handle the model command. This command sets the model for the simulation. - """ - if len(args) < 1: - print('Error: Not enough arguments') - return - model_type = str(args[0]) - if model_type.lower() != 'ising' and model_type.lower() != 'fibonacci': - print('Error: Model must be Ising or Fibonacci') - return - model_convert = {'ising': AnyonModel.Ising, 'fibonacci': AnyonModel.Fibonacci} - model = Model(model_convert[model_type.lower()]) - sim.set_model(model) + print('\nError: Invalid model.') + model(user_input) + + self.init_complete = True def do_shell(self, arg): "Run a shell command" @@ -183,6 +176,10 @@ def do_anyon(self, arg): def do_model(self, arg): "Set the model for the simulation" + if self.init_complete: + print('Error: Cannot change model after initialization') + return + args = arg.split(' ') model(*args) if args[0] == 'help' or args[0] == '-h': @@ -221,7 +218,7 @@ def do_exit(self, arg): def do_help(self, arg): "Print help" - print('Commands: anyon, model, fusion, braid, exit, help') + print('Commands: anyon, fusion, braid, exit, help') if __name__ == '__main__': From fb56d4beedff3b2bd2d6222e2246eda46283d2fb Mon Sep 17 00:00:00 2001 From: Ryan Byrne Date: Sat, 15 Jun 2024 00:36:57 -0400 Subject: [PATCH 6/9] Add anyon initialization loop --- src/main.py | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/main.py b/src/main.py index d8a4cc8..6f496c5 100644 --- a/src/main.py +++ b/src/main.py @@ -156,6 +156,34 @@ def __init__(self): print('\nError: Invalid model.') model(user_input) + # Prompt the user to input the anyon details + no_anyons = True + while True: + if no_anyons: + user_input = input( + '\nEnter the anyon name, topological charge, and optionally, the 2D position.' + '\nUse the format <{x,y}>.\n' + '> ' + ) + else: + user_input = input( + '\nContinue adding anyons, or type "done" when finished initializing.\n' + '> ' + ) + + if user_input.lower() == 'exit': + sys.exit(0) + elif user_input.lower() == 'done': + break + + args = user_input.split(' ') + if len(args) < 2 or len(args) > 3: + print('Error: There should be either 2 or 3 arguments') + continue + + anyon(*args) + no_anyons = False + self.init_complete = True def do_shell(self, arg): @@ -168,6 +196,10 @@ def do_shell(self, arg): def do_anyon(self, arg): "Add an anyon to the simulation" + if self.init_complete: + print('Error: Cannot add anyons after initialization') + return + args = arg.split(' ') if args[0] == 'help' or args[0] == '-h': print(self.command_options['anyon']) @@ -218,7 +250,7 @@ def do_exit(self, arg): def do_help(self, arg): "Print help" - print('Commands: anyon, fusion, braid, exit, help') + print('Commands: fusion, braid, exit, help') if __name__ == '__main__': From b629e6e0241199752b730f85cf4d6b6d7ac0cbe4 Mon Sep 17 00:00:00 2001 From: Haadi Khan Date: Sat, 15 Jun 2024 23:47:29 -0400 Subject: [PATCH 7/9] Updated CI/CD for project --- .github/workflows/workflows/Maturin.yml | 60 +++++++++++++++++++ .../{python-app.yml => workflows/Python.yml} | 0 .ruff.toml | 16 ----- pyproject.toml | 39 ++++++++++++ 4 files changed, 99 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/workflows/Maturin.yml rename .github/workflows/{python-app.yml => workflows/Python.yml} (100%) delete mode 100644 .ruff.toml create mode 100644 pyproject.toml diff --git a/.github/workflows/workflows/Maturin.yml b/.github/workflows/workflows/Maturin.yml new file mode 100644 index 0000000..0386fce --- /dev/null +++ b/.github/workflows/workflows/Maturin.yml @@ -0,0 +1,60 @@ +# This file is autogenerated by maturin v1.6.0 +# To update, run +# +# maturin generate-ci github +# +name: CI + +on: + push: + branches: + - main + - master + tags: + - '*' + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + linux: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: ubuntu-latest + target: x86_64 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: 'true' + manylinux: auto + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-linux-${{ matrix.platform.target }} + path: dist + + release: + name: Release + runs-on: ubuntu-latest + if: "startsWith(github.ref, 'refs/tags/')" + needs: [linux] + steps: + - uses: actions/download-artifact@v4 + - name: Publish to PyPI + uses: PyO3/maturin-action@v1 + env: + MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }} + with: + command: upload + args: --non-interactive --skip-existing wheels-*/* diff --git a/.github/workflows/python-app.yml b/.github/workflows/workflows/Python.yml similarity index 100% rename from .github/workflows/python-app.yml rename to .github/workflows/workflows/Python.yml diff --git a/.ruff.toml b/.ruff.toml deleted file mode 100644 index 82dc402..0000000 --- a/.ruff.toml +++ /dev/null @@ -1,16 +0,0 @@ -lint.select = ["E", "F"] -lint.ignore = ["F405", "F403"] -exclude = [ - "build", - "dist", - "venv", -] -line-length = 120 - -[lint.per-file-ignores] -"tools/ignore.py" = ["F401"] - -[format] -quote-style = "single" -indent-style = "space" -docstring-code-format = true diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b3d1551 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,39 @@ +[build-system] +requires = ["maturin>=1.6,<2.0"] +build-backend = "maturin" + +[project] +name = "anyon-braiding-simulator" +requires-python = ">=3.8" +classifiers = [ + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] +dynamic = ["version"] + +[tool.maturin] +features = ["pyo3/extension-module"] + +[tool.ruff] +lint.select = ["E", "F"] +lint.ignore = ["F405", "F403"] +exclude = [ + "build", + "dist", + "venv", + ".venv" +] +line-length = 120 + +[tool.ruff.lint.per-file-ignores] +"tools/ignore.py" = ["F401"] +"python/anyon_braiding_simulator/__init__.py" = ["ALL"] + +[tool.ruff.format] +quote-style = "single" +indent-style = "space" +docstring-code-format = true + +[tool.pyright] +include = ["python", "target/wheels", "venv/lib/python3.12/site-packages/"] From 285d4f99d179b8d854a3b170f5a97e5b0c244c3f Mon Sep 17 00:00:00 2001 From: Haadi Khan Date: Sat, 15 Jun 2024 23:48:04 -0400 Subject: [PATCH 8/9] Merged main with init-braiding. Bug fixes needed Will also write some testing for main perhaps --- .gitignore | 79 ++++- Cargo.lock | 295 ++++++++++++++++++ Cargo.toml | 12 + .../anyon_braiding_simulator}/Braiding.py | 31 +- .../anyon_braiding_simulator}/Model.py | 103 +++--- .../anyon_braiding_simulator}/Simulator.py | 11 +- python/anyon_braiding_simulator/__init__.py | 5 + .../anyon_braiding_simulator}/main.py | 61 ++-- python/tests/test_anyon.py | 8 + python/tests/test_basis.py | 32 ++ python/tests/test_fusion.py | 59 ++++ python/tests/test_model.py | 6 + python/tests/test_state.py | 37 +++ requirements.txt | 3 + src/Anyon.py | 11 - src/Fusion.py | 56 ---- src/fusion.rs | 2 + src/fusion/fusion.rs | 215 +++++++++++++ src/fusion/state.rs | 84 +++++ src/lib.rs | 22 ++ src/model.rs | 2 + src/model/anyon.rs | 67 ++++ src/model/model.rs | 39 +++ src/util.rs | 1 + src/util/basis.rs | 64 ++++ 25 files changed, 1130 insertions(+), 175 deletions(-) create mode 100644 Cargo.lock create mode 100644 Cargo.toml rename {src => python/anyon_braiding_simulator}/Braiding.py (92%) rename {src => python/anyon_braiding_simulator}/Model.py (64%) rename {src => python/anyon_braiding_simulator}/Simulator.py (90%) create mode 100644 python/anyon_braiding_simulator/__init__.py rename {src => python/anyon_braiding_simulator}/main.py (84%) create mode 100644 python/tests/test_anyon.py create mode 100644 python/tests/test_basis.py create mode 100644 python/tests/test_fusion.py create mode 100644 python/tests/test_model.py create mode 100644 python/tests/test_state.py delete mode 100644 src/Anyon.py delete mode 100644 src/Fusion.py create mode 100644 src/fusion.rs create mode 100644 src/fusion/fusion.rs create mode 100644 src/fusion/state.rs create mode 100644 src/lib.rs create mode 100644 src/model.rs create mode 100644 src/model/anyon.rs create mode 100644 src/model/model.rs create mode 100644 src/util.rs create mode 100644 src/util/basis.rs diff --git a/.gitignore b/.gitignore index 9caec28..2012274 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,76 @@ -.DS_Store -.idea -*.log -tmp/ +/target +# Byte-compiled / optimized / DLL files +__pycache__/ +.pytest_cache/ *.py[cod] -*.egg -build -htmlcov +# C extensions +*.so + +# Distribution / packaging +.Python +.venv/ +env/ +bin/ +build/ +develop-eggs/ +dist/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +include/ +man/ venv/ +*.egg-info/ +.installed.cfg +*.egg + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt +pip-selfcheck.json + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# Rope +.ropeproject + +# Django stuff: +*.log +*.pot + +.DS_Store + +# Sphinx documentation +docs/_build/ + +# PyCharm +.idea/ + +# VSCode +.vscode/ + +# Pyenv +.python-version + + +# pyright config file (unnecessary for ppl on vscode lmao) +pyrightconfig.json diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..785c964 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,295 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyon-braiding-simulator" +version = "0.1.0" +dependencies = [ + "pyo3", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + +[[package]] +name = "proc-macro2" +version = "1.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pyo3" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e00b96a521718e08e03b1a622f01c8a8deb50719335de3f60b3b3950f069d8" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "memoffset", + "parking_lot", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7883df5835fafdad87c0d888b266c8ec0f4c9ca48a5bed6bbb592e8dedee1b50" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01be5843dc60b916ab4dad1dca6d20b9b4e6ddc8e15f50c47fe6d85f1fb97403" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77b34069fc0682e11b31dbd10321cbf94808394c56fd996796ce45217dfac53c" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08260721f32db5e1a5beae69a55553f56b99bd0e1c3e6e0a5e8851a9d0f5a85c" +dependencies = [ + "heck", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +dependencies = [ + "bitflags", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "syn" +version = "2.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-lexicon" +version = "0.12.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unindent" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..5dedcf3 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "anyon-braiding-simulator" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +name = "anyon_braiding_simulator" +crate-type = ["cdylib"] + +[dependencies] +pyo3 = "0.21.1" diff --git a/src/Braiding.py b/python/anyon_braiding_simulator/Braiding.py similarity index 92% rename from src/Braiding.py rename to python/anyon_braiding_simulator/Braiding.py index 029948c..7475be9 100644 --- a/src/Braiding.py +++ b/python/anyon_braiding_simulator/Braiding.py @@ -1,6 +1,5 @@ import numpy as np - -from Anyon import Anyon +from anyon_braiding_simulator import Anyon from Model import Model @@ -56,19 +55,21 @@ def __init__(self, anyons: list[Anyon], model: Model): """ # Check if there are fewer than 3 anyons if len(anyons) < 3: - raise ValueError("There must be at least 3 anyons") + raise ValueError('There must be at least 3 anyons') # Check for duplicate anyon names names = [anyon.name for anyon in anyons] if len(names) != len(set(names)): - raise ValueError("Duplicate anyon names detected") - + raise ValueError('Duplicate anyon names detected') + # Check if all charges of given anyons are included in given model charges = model.get_charges() invalid_anyons = [anyon for anyon in anyons if anyon.topo_charge not in charges] if invalid_anyons: - names_and_charges = [f"Name: {anyon.name}, Charge: {anyon.topo_charge}" for anyon in invalid_anyons] - message = "The following anyons have charges that aren't defined in the given model:\n" + "\n".join(names_and_charges) + names_and_charges = [f'Name: {anyon.name}, Charge: {anyon.topo_charge}' for anyon in invalid_anyons] + message = "The following anyons have charges that aren't defined in the given model:\n" + '\n'.join( + names_and_charges + ) raise ValueError(message) self.anyons = anyons @@ -118,13 +119,13 @@ def __str__(self) -> str: if not self.operations: print('No operations to print') return '' - + # Initialize the output for each anyon num_anyons = len(self.anyons) max_rows = len(self.operations) * 5 # Each swap occupies 5 rows output = [[' ' for _ in range(num_anyons * 5)] for _ in range(max_rows)] - spacing = 4 # 3 spaces between cols + spacing = 4 # 3 spaces between cols # Add '|' for non-swap columns for col in range(num_anyons): @@ -140,20 +141,21 @@ def __str__(self) -> str: for i in range(3): output[base + i][index_A * spacing + 4 + i * 1] = '\\' output[base + i][index_B * spacing + 4 - i * 1] = '/' - for i in range(3, 5): + for i in range(3, 5): output[base + i][index_A * spacing + 4 + (5 - i - 1) * 1] = '/' output[base + i][index_B * spacing + 4 - (5 - i - 1) * 1] = '\\' - + else: for i in range(3): output[base + i][index_B * spacing + 4 + i * 1] = '\\' output[base + i][index_A * spacing + 4 - i * 1] = '/' - for i in range(3, 5): + for i in range(3, 5): output[base + i][index_B * spacing + 4 + (5 - i - 1) * 1] = '/' output[base + i][index_A * spacing + 4 - (5 - i - 1) * 1] = '\\' return '\n'.join([''.join(row) for row in output if any(c != ' ' for c in row)]) + # Function to test __str__ after each timestep def print_anyons_state(braid, swap_number): """ @@ -170,7 +172,7 @@ def print_anyons_state(braid, swap_number): initial_anyons = [anyon.name for anyon in braid.anyons] print(f"Before swap {swap_number}: [{', '.join(initial_anyons)}]") - + # Perform the swap operation if swap_number <= len(braid.operations): index_A, index_B = braid.operations[swap_number - 1] @@ -179,5 +181,4 @@ def print_anyons_state(braid, swap_number): print(braid) print(f"After swap {swap_number}: [{', '.join(anyon_names)}]") else: - print("Invalid swap number") - + print('Invalid swap number') diff --git a/src/Model.py b/python/anyon_braiding_simulator/Model.py similarity index 64% rename from src/Model.py rename to python/anyon_braiding_simulator/Model.py index d9c622a..0cd3e6d 100644 --- a/src/Model.py +++ b/python/anyon_braiding_simulator/Model.py @@ -1,8 +1,11 @@ # Standard Library -from enum import Enum import cmath +from enum import Enum +from itertools import product + import numpy as np + class AnyonModel(Enum): Ising = 1 Fibonacci = 2 @@ -15,56 +18,49 @@ def __init__(self, model_type: AnyonModel, num_fusion_channels=5) -> None: Requires: 'model_type' representing the type of model being used The Ising, Fibonacci, and Custom models correspond to 1, 2, an 3 respectively - - The F matrix is implemented as a 6 dimensional array, with the + + The F matrix is implemented as a 6 dimensional array, with the first 3 indexes corresponding to the 3 lower indices of the F matrix - (which physically correspond to the anyons being fused) and the + (which physically correspond to the anyons being fused) and the 4th index corresponding to the upper index of the F matrix (which physically corresponds to the anyon resulting from fusion) - + Indices correspond to anyon types as follows: 0 = vacuum state/ trivial anyon 1 = sigma 2 = psi - + For details on notation, c.f.r. On classification of modular tensor categories by Rowell, Stong, and Wang https://www.arxiv.org/abs/0712.1377 """ self.model_type = model_type - + if model_type == AnyonModel.Ising: - self._charges = {"vacuum", "sigma", "psi"} + self._r_mtx = cmath.exp(-1j * np.pi / 8) * np.array([[1, 0], [0, 1j]]) + + self._f_mtx = np.zeros((3, 3, 3, 3, 2, 2)) + + for w, x, y, z in product(range(3), repeat=4): + self._f_mtx[w][x][y][z] = np.identity(2) - self._r_mtx = cmath.exp(-1j*np.pi/8)*np.array([[1,0],[0,1j]]) - self._f_mtx = np.zeros((3,3,3,3,2,2)) - - for i in range(3): - for j in range(3): - for k in range(3): - for l in range(3): - self._f_mtx[i][j][k][l] = np.identity(2) - for i in range(3): - self._f_mtx[1][1][1][i] = 1/np.sqrt(2)*np.array([[1,1],[1,-1]]) - self._f_mtx[2][1][1][i] = self._f_mtx[1][2][1][i] = self._f_mtx[1][1][2][i] = -1*np.identity(2) - self._f_mtx[1][2][2][i] = self._f_mtx[2][1][2][i] = self._f_mtx[2][2][1][i] = -1*np.identity(2) - + self._f_mtx[1][1][1][i] = 1 / np.sqrt(2) * np.array([[1, 1], [1, -1]]) + self._f_mtx[2][1][1][i] = self._f_mtx[1][2][1][i] = self._f_mtx[1][1][2][i] = -1 * np.identity(2) + self._f_mtx[1][2][2][i] = self._f_mtx[2][1][2][i] = self._f_mtx[2][2][1][i] = -1 * np.identity(2) + self._rules = [] - - elif model_type == AnyonModel.Fibonacci: - self._charges = {"vacuum", "psi"} - self._r_mtx = np.array([[cmath.exp(4*np.pi*1j/5), 0],[0, -1*cmath.exp(2*np.pi*1j/5)]]) + elif model_type == AnyonModel.Fibonacci: + self._r_mtx = np.array([[cmath.exp(4 * np.pi * 1j / 5), 0], [0, -1 * cmath.exp(2 * np.pi * 1j / 5)]]) self._f_mtx = [] self._rules = [] elif model_type == AnyonModel.Custom: - - raise NotImplementedError("Custom Models not yet implemented") - + raise NotImplementedError('Custom Models not yet implemented') + else: - raise ValueError("Model type not recognized") - + raise ValueError('Model type not recognized') + self._num_fusion_channels = num_fusion_channels def get_charges(self) -> set: @@ -72,10 +68,9 @@ def get_charges(self) -> set: Provide the charges that are defined in this model. """ return self._charges - + def getFMatrix(self, a: str, b: str, c: str, d: str) -> np.ndarray: - - ''' + """ Parameters ---------- a : str @@ -89,40 +84,38 @@ def getFMatrix(self, a: str, b: str, c: str, d: str) -> np.ndarray: Only the following strings are accepted as parameters: vacuum, sigma, psi - - Requires that all anyons used as parameters are contained with the + + Requires that all anyons used as parameters are contained with the given model - + For details on notation, c.f.r. On classification of modular tensor categories by Rowell, Stong, and Wang https://www.arxiv.org/abs/0712.1377 - + Returns ------- the F-matrix corresponding to the set of indices in the model - ''' - + """ + if self.model_type == AnyonModel.Ising: - anyondict = { - "vacuum": 0, - "sigma": 1, - "psi": 2} - + anyondict = {'vacuum': 0, 'sigma': 1, 'psi': 2} + elif self.model_type == AnyonModel.Fibonacci: anyondict = { - "vacuum": 0, - "sigma": 1,} - + 'vacuum': 0, + 'sigma': 1, + } + elif self.model_type == AnyonModel.Custom: - raise NotImplementedError("Custom Models not yet implemented") - + raise NotImplementedError('Custom Models not yet implemented') + else: - raise ValueError("Model type not recognized") - - inputs = {a,b,c,d} + raise ValueError('Model type not recognized') + + inputs = {a, b, c, d} for i in inputs: if i not in anyondict: - raise ValueError("invalid anyon name") - - return self._f_mtx[anyondict[a], anyondict[b], anyondict[c], anyondict[d]] \ No newline at end of file + raise ValueError('invalid anyon name') + + return self._f_mtx[anyondict[a], anyondict[b], anyondict[c], anyondict[d]] diff --git a/src/Simulator.py b/python/anyon_braiding_simulator/Simulator.py similarity index 90% rename from src/Simulator.py rename to python/anyon_braiding_simulator/Simulator.py index 58f2f11..29f847d 100644 --- a/src/Simulator.py +++ b/python/anyon_braiding_simulator/Simulator.py @@ -1,3 +1,6 @@ +from anyon_braiding_simulator import Model + + class Simulator: def __init__(self): """ @@ -8,7 +11,7 @@ def __init__(self): self._model = None self._dim_of_anyon_pos = 2 # Default is 2D anyon positions - def update_anyons(self, is_increasing: bool, anyons) -> None: + def update_anyons(self, is_increasing: bool, anyons: list) -> None: """ Update the anyons stored in memory. @@ -21,7 +24,7 @@ def update_anyons(self, is_increasing: bool, anyons) -> None: else: self._anyons = [anyon for anyon in self._anyons if anyon not in anyons] - def set_model(self, model) -> None: + def set_model(self, model: Model) -> None: """ Set the model for the simulator. """ @@ -32,13 +35,13 @@ def list_anyons(self) -> list: List the anyons currently in the simulator. """ return self._anyons - + def get_dim_of_anyon_pos(self) -> int: """ Provides the dimension of anyon positions. """ return self._dim_of_anyon_pos - + def switch_to_1D(self) -> None: """ Sets the dimension of anyon positions to be 1. diff --git a/python/anyon_braiding_simulator/__init__.py b/python/anyon_braiding_simulator/__init__.py new file mode 100644 index 0000000..cb7d49a --- /dev/null +++ b/python/anyon_braiding_simulator/__init__.py @@ -0,0 +1,5 @@ +from .anyon_braiding_simulator import * + +__doc__ = anyon_braiding_simulator.__doc__ +if hasattr(anyon_braiding_simulator, '__all__'): + __all__ = anyon_braiding_simulator.__all__ diff --git a/src/main.py b/python/anyon_braiding_simulator/main.py similarity index 84% rename from src/main.py rename to python/anyon_braiding_simulator/main.py index 6f496c5..bdd9699 100644 --- a/src/main.py +++ b/python/anyon_braiding_simulator/main.py @@ -3,10 +3,9 @@ import subprocess import sys -from Anyon import Anyon +from anyon_braiding_simulator import Anyon, AnyonModel, IsingTopoCharge from Braiding import Braid -from Fusion import Fusion -from Model import AnyonModel, Model +from Model import Model from Simulator import Simulator sim = Simulator() @@ -23,31 +22,41 @@ def anyon(*args): name = args[0] topological_charge = args[1] - # Why is topo charge a number here? Easier if string?: - # try: - # topological_charge = float(args[1]) - # except ValueError: - # print('Error: topological charge must be a number') - # return + try: + topo_charge = { + 'psi': IsingTopoCharge.Psi, + 'sigma': IsingTopoCharge.Sigma, + 'vac': IsingTopoCharge.Vacuum, + } + topological_charge = topo_charge[args[1].lower()] + except ValueError: + print('Error: topological charge must be a number') + return if len(args) == 2: anyons = sim.list_anyons() # Make sure any previous anyons were specified in 1D space (i.e. without a position argument) if anyons and sim.get_dim_of_anyon_pos() == 2: - print('Error: you have already provided an anyon in 2D space, so the rest must also have a specified 2D position') + print( + 'Error: you have already provided an anyon in 2D space, so the rest must also have a \ + specified 2D position' + ) return elif not anyons: sim.switch_to_1D() - + position_1D = len(anyons) # Index/position of new anyon in 1D position = (position_1D, 0) print(f'Created anyon {name} with TC {topological_charge} at position {position_1D} in 1D') else: # Make sure any previous anyons were specified in 2D space if sim.get_dim_of_anyon_pos() == 1: - print('Error: you have already provided an anyon in 1D space, so the positions of the rest cannot be specified in 2D') + print( + 'Error: you have already provided an anyon in 1D space, so the positions of the rest \ + cannot be specified in 2D' + ) return - + try: position = tuple(map(float, args[2].replace('{', '').replace('}', '').split(','))) position[1] @@ -57,14 +66,12 @@ def anyon(*args): except IndexError: print('Error: position must be formatted as {x,y} where x and y are numbers') return - + print(f'Created anyon {name} with TC {topological_charge} at position {position} in 2D') - new_anyon = Anyon(topological_charge, name, position) + new_anyon = Anyon(name, topological_charge, position) sim.update_anyons(True, [new_anyon]) - - def model(*args): """ @@ -82,6 +89,7 @@ def model(*args): model_convert = {'ising': AnyonModel.Ising, 'fibonacci': AnyonModel.Fibonacci} + # TODO: figure out why this throws an error model = Model(model_convert[model_type.lower()]) sim.set_model(model) @@ -94,15 +102,17 @@ def fusion(*args): print('Error: Not enough arguments') return - fusion = Fusion(sim.list_anyons(), []) + # fusion = Fusion() cmd = args[0] if cmd.lower() == 'fuse': - anyon_indices = [sim.list_anyons().index(anyon) for anyon in args[1:]] - fusion.fuse(*anyon_indices) + # anyon_indices = [sim.list_anyons().index(anyon) for anyon in args[1:]] + # fusion.fuse(*anyon_indices) + pass elif cmd.lower() == 'print': - print(fusion) + # print(fusion) + pass else: print('Error: Unknown fusion command') @@ -164,12 +174,9 @@ def __init__(self): '\nEnter the anyon name, topological charge, and optionally, the 2D position.' '\nUse the format <{x,y}>.\n' '> ' - ) + ) else: - user_input = input( - '\nContinue adding anyons, or type "done" when finished initializing.\n' - '> ' - ) + user_input = input('\nContinue adding anyons, or type "done" when finished initializing.\n' '> ') if user_input.lower() == 'exit': sys.exit(0) @@ -211,7 +218,7 @@ def do_model(self, arg): if self.init_complete: print('Error: Cannot change model after initialization') return - + args = arg.split(' ') model(*args) if args[0] == 'help' or args[0] == '-h': diff --git a/python/tests/test_anyon.py b/python/tests/test_anyon.py new file mode 100644 index 0000000..78ff9bf --- /dev/null +++ b/python/tests/test_anyon.py @@ -0,0 +1,8 @@ +from anyon_braiding_simulator import Anyon, IsingTopoCharge + + +def test_anyon(): + anyon = Anyon('thisisatest', IsingTopoCharge.Psi, (1, 1)) + assert anyon.name == 'thisisatest' + assert anyon.charge == IsingTopoCharge.Psi + assert anyon.position == (1, 1) diff --git a/python/tests/test_basis.py b/python/tests/test_basis.py new file mode 100644 index 0000000..d4d9204 --- /dev/null +++ b/python/tests/test_basis.py @@ -0,0 +1,32 @@ +from anyon_braiding_simulator import Basis, FusionPair + + +def setup(): + pass + + +# Test basic input sanitizations +def test_basis(): + anyons = 10 + operations = [] + for t in range(1, 10): + operations.append((t, FusionPair(0, t))) + + assert Basis(operations).verify_basis(anyons) + + operations = [] + + assert not Basis(operations).verify_basis(anyons) + + operations = [] + for t in range(1, 10): + operations.append((t, FusionPair(t, 0))) + + assert not Basis(operations).verify_basis(anyons) + + operations = [] + + for t in range(1, 10): + operations.append((1, FusionPair(0, t))) + + assert not Basis(operations).verify_basis(anyons) diff --git a/python/tests/test_fusion.py b/python/tests/test_fusion.py new file mode 100644 index 0000000..894e420 --- /dev/null +++ b/python/tests/test_fusion.py @@ -0,0 +1,59 @@ +from anyon_braiding_simulator import Anyon, Fusion, FusionPair, IsingTopoCharge, State + + +def setup() -> State: + state = State() + + return state + + +def test_str_1(): + state = setup() + for i in range(6): + state.add_anyon(Anyon(f'{i}', IsingTopoCharge.Sigma, (0, 0))) + + state.add_operation(1, FusionPair(0, 1)) + state.add_operation(1, FusionPair(2, 3)) + state.add_operation(1, FusionPair(4, 5)) + state.add_operation(2, FusionPair(2, 4)) + state.add_operation(3, FusionPair(0, 2)) + + fusion = Fusion(state) + expected = '0 1 2 3 4 5 \n| | | | | | \n|─| |─| |─| \n| |───| \n|───| \n| ' + + assert str(fusion) == expected + + +def test_apply_ising_fusion(): + state = setup() + fusion = Fusion(state) + + psi = [1, 0, 0] + 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)] + + 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 + + +def test_qubit_enc(): + state = setup() + + for i in range(6): + state.add_anyon(Anyon(f'{i}', IsingTopoCharge.Sigma, (0, 0))) + + state.add_operation(1, FusionPair(0, 1)) + state.add_operation(1, FusionPair(2, 3)) + state.add_operation(1, FusionPair(4, 5)) + state.add_operation(2, FusionPair(2, 4)) + state.add_operation(3, FusionPair(0, 2)) + + fusion = Fusion(state) + correct = [FusionPair(0, 1), FusionPair(2, 4), FusionPair(2, 3)] + + assert set(map(str, fusion.qubit_enc())) == set(map(str, correct)) diff --git a/python/tests/test_model.py b/python/tests/test_model.py new file mode 100644 index 0000000..c554ffb --- /dev/null +++ b/python/tests/test_model.py @@ -0,0 +1,6 @@ +from anyon_braiding_simulator import Model + + +def setup(): + model = Model() + return model diff --git a/python/tests/test_state.py b/python/tests/test_state.py new file mode 100644 index 0000000..445f701 --- /dev/null +++ b/python/tests/test_state.py @@ -0,0 +1,37 @@ +from anyon_braiding_simulator import Anyon, FusionPair, IsingTopoCharge, State + + +def setup() -> State: + state = State() + return state + + +def test_add_anyon(): + state = setup() + for i in range(100): + anyon = Anyon(f'{i}', 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.position == (0, 0) + + assert len(state.anyons) == 100 + + +def test_add_operation(): + state = setup() + for i in range(101): + anyon = Anyon(f'{i}', IsingTopoCharge.Sigma, (0, 0)) + state.add_anyon(anyon) + + assert state.add_operation(1, FusionPair(0, 1)) + assert state.add_operation(1, FusionPair(2, 3)) + + assert not state.add_operation(1, FusionPair(1, 2)) + assert not state.add_operation(1, FusionPair(2, 4)) + + assert state.add_operation(1, FusionPair(4, 5)) + assert state.add_operation(2, FusionPair(2, 4)) + assert state.add_operation(3, FusionPair(0, 2)) diff --git a/requirements.txt b/requirements.txt index 980914c..654cf02 100644 --- a/requirements.txt +++ b/requirements.txt @@ -34,6 +34,7 @@ llvmlite==0.42.0 MarkupSafe==2.1.5 matplotlib==3.9.0 matplotlib-inline==0.1.7 +maturin==1.6.0 mistune==3.0.2 nbclient==0.10.0 nbconvert==7.16.4 @@ -64,11 +65,13 @@ referencing==0.35.1 requests==2.32.3 rpds-py==0.18.1 ruff==0.4.8 +rustimport==1.5.0 scipy==1.13.1 six==1.16.0 soupsieve==2.5 stack-data==0.6.3 tinycss2==1.3.0 +toml==0.10.2 tornado==6.4 traitlets==5.14.3 urllib3==2.2.1 diff --git a/src/Anyon.py b/src/Anyon.py deleted file mode 100644 index 486515c..0000000 --- a/src/Anyon.py +++ /dev/null @@ -1,11 +0,0 @@ -class Anyon: - def __init__(self, topo_charge, name, position): - """ - Parameters: - topo_charge (String) name of topological charge of anyon - name (String) name of anyon - position (Double) cartesian coordinate representation of anyon's position - """ - self.topo_charge = topo_charge - self.name = name - self.position = position diff --git a/src/Fusion.py b/src/Fusion.py deleted file mode 100644 index 68746b9..0000000 --- a/src/Fusion.py +++ /dev/null @@ -1,56 +0,0 @@ -from Anyon import Anyon - - -class FusionPair: - def __init__(self, anyon1: int, anyon2: int): - self.first = anyon1 - self.second = anyon2 - - -class Fusion: - def __init__(self, anyons: list[Anyon], operations: list[list[FusionPair]]): - """ - Requires: 'anyons' representing a list of 'Anyon' objects - Requires: 'operations' represeting a list of list of 'FusionPairs' with - operations[t] indicating the fusions that occur at time step t. - Constructs: A binary indexed tree 'anyons' represented in a 2D array - """ - - self.anyons = None - pass - - def _verify_operations(self, anyons: list[Anyon], operations: list[list[FusionPair]]): - """ - Verifies that the list 'operations' is in fact a valid tree of fusions - (checks fusions are between adjacent anyons and the anyons exist) - """ - pass - - def fuse(self, anyon1: int, anyon2: int): - """ - Fuses anyon1, anyon2 - Returns: All possible outcomes after fusing them, chooses outcome to use - probabilistically - """ - pass - - def __str__(self) -> str: - """ - Returns: Human-readable representation of the fusion diagram - """ - return '' - # pass - - def qubit_enc(self): - """ - Returns: Qubit encoding implied by the current anyon fusions - """ - pass - - def soft_fuse(self, anyon1: int, anyon2: int): - """ - Returns: All possible outcomes that could occur after fusing anyon1 and - anyon2. - Does not affect current state of anyons - """ - pass diff --git a/src/fusion.rs b/src/fusion.rs new file mode 100644 index 0000000..fba1cc9 --- /dev/null +++ b/src/fusion.rs @@ -0,0 +1,2 @@ +pub mod fusion; +pub mod state; diff --git a/src/fusion/fusion.rs b/src/fusion/fusion.rs new file mode 100644 index 0000000..b9cf814 --- /dev/null +++ b/src/fusion/fusion.rs @@ -0,0 +1,215 @@ +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::util::basis::Basis; +use pyo3::prelude::*; + +#[pyclass] +#[derive(Clone, Debug, PartialEq, Hash, Eq, Ord, PartialOrd)] +pub struct FusionPair { + #[pyo3(get)] + anyon_1: usize, + #[pyo3(get)] + 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 { + self.anyon_1 + } + fn anyon_2(&self) -> usize { + self.anyon_2 + } +} + +#[pymethods] +impl FusionPair { + #[new] + fn new(anyon_1: usize, anyon_2: usize) -> Self { + FusionPair { anyon_1, anyon_2 } + } + + fn __str__(&self) -> PyResult { + Ok(format!("({} {})", self.anyon_1, self.anyon_2)) + } +} + +#[pyclass] +pub struct Fusion { + state: State, + ops: Vec>, +} + +#[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 + /// TODO: THIS IS NON DETERMINISTIC WTF + 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], + } + }; + 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.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 Ok(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(); + Ok(encoding_fusions) + } + + /// Builds the fusion tree's graphical representation + fn __str__(&self) -> PyResult { + // call state's get_anyons + let anyons = self.state.anyons(); + + let mut active_anyons: Vec = anyons.iter().map(|_| true).collect(); + + // Anyon names + let top_level: String = anyons.iter().map(|a| format!("{} ", (*a).name())).collect(); + + // Anyon levels + let level_2: String = anyons.iter().map(|_| format!("| ")).collect(); + + let mut body: String = String::new(); + + for level in self.ops.iter() { + // even indices are for anyons, odd indices are for operations (i.e. joining or no action) + let mut level_vec = vec![" "; 2 * anyons.len()]; + // set active anyons with a pipe + level_vec.iter_mut().enumerate().for_each(|(i, v)| { + if i % 2 == 0 && active_anyons[i / 2] { + *v = "|"; + } + }); + + // + // here rishi :3 + // + for fusion_pair in level.iter() { + let start = 2 * (fusion_pair.anyon_1()) + 1; + let end = 2 * (fusion_pair.anyon_2()); + for i in start..end { + level_vec[i] = "─"; + } + active_anyons[fusion_pair.anyon_2()] = false; + } + + body.push_str(&format!("{}\n", level_vec.join(""))); + } + + let last_time = format!( + "{}", + active_anyons + .iter() + .map(|is_active| if *is_active { "| " } else { " " }) + .collect::() + .to_string() + ); + + 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]; + } + } + + // 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])); + } + } + + return Ok(output); + } +} diff --git a/src/fusion/state.rs b/src/fusion/state.rs new file mode 100644 index 0000000..5502dfc --- /dev/null +++ b/src/fusion/state.rs @@ -0,0 +1,84 @@ +use crate::{fusion::fusion::AccessFusionPair, fusion::fusion::FusionPair, model::anyon::Anyon}; +use pyo3::prelude::*; + +/// The state of the system +#[pyclass] +#[derive(Clone, Debug, PartialEq)] +pub struct State { + #[pyo3(get)] + anyons: Vec, + #[pyo3(get)] + 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 { + self.anyons.clone() + } + + 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 { + assert!(operation.anyon_1() < operation.anyon_2()); + assert!(operation.anyon_2() < self.anyons.len()); + let mut fusible_anyons = vec![true; self.anyons.len()]; + + for (t, op) in &self.operations { + fusible_anyons[op.anyon_2()] = false; + if *t == time { + fusible_anyons[op.anyon_1()] = false; + } + } + + if !fusible_anyons[operation.anyon_1()] || !fusible_anyons[operation.anyon_2()] { + return false; + } + + for i in operation.anyon_1() + 1..operation.anyon_2() - 1 { + if fusible_anyons[i] { + return false; + } + } + true + } + + /// Add 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 { + return Ok(false); + } + self.operations.push((time, operation)); + + Ok(true) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..4ae8239 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,22 @@ +use pyo3::prelude::*; +mod fusion; +mod model; +mod util; + +/// A Python module implemented in Rust. +#[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::()?; + + m.add_class::()?; + + m.add_class::()?; + Ok(()) +} diff --git a/src/model.rs b/src/model.rs new file mode 100644 index 0000000..cfb50d5 --- /dev/null +++ b/src/model.rs @@ -0,0 +1,2 @@ +pub mod anyon; +pub mod model; diff --git a/src/model/anyon.rs b/src/model/anyon.rs new file mode 100644 index 0000000..cf22c1b --- /dev/null +++ b/src/model/anyon.rs @@ -0,0 +1,67 @@ +use std::slice::SliceIndex; + +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 { + Psi, + Vacuum, + Sigma, +} + +impl IsingTopoCharge { + pub fn value(&self) -> usize { + match self { + IsingTopoCharge::Psi => 0, + IsingTopoCharge::Vacuum => 1, + IsingTopoCharge::Sigma => 2, + } + } +} + +#[pyclass] +#[derive(Clone, Debug, PartialEq)] +pub struct Anyon { + #[pyo3(get)] + name: String, + #[pyo3(get)] + charge: IsingTopoCharge, + #[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 { + &self.name + } + + fn charge(&self) -> IsingTopoCharge { + self.charge.clone() + } + + fn position(&self) -> (f64, f64) { + self.position + } +} + + +#[pymethods] +impl Anyon { + #[new] + pub fn new(name: String, charge: IsingTopoCharge, position: (f64, f64)) -> Self { + Anyon { + name, + charge, + position, + } + } +} diff --git a/src/model/model.rs b/src/model/model.rs new file mode 100644 index 0000000..98d6f54 --- /dev/null +++ b/src/model/model.rs @@ -0,0 +1,39 @@ +use pyo3::prelude::*; + +use super::anyon::{AccessAnyon, Anyon, IsingTopoCharge}; + +/// Different Anyon models that can be used to simulate the system +#[pyclass] +#[derive(PartialEq, Eq)] +pub enum AnyonModel { + Ising, + Fibonacci, + Custom, +} + +#[pymethods] +impl AnyonModel { + #[new] + fn new() -> Self { + 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.rs b/src/util.rs new file mode 100644 index 0000000..aea0ba4 --- /dev/null +++ b/src/util.rs @@ -0,0 +1 @@ +pub mod basis; diff --git a/src/util/basis.rs b/src/util/basis.rs new file mode 100644 index 0000000..31fab60 --- /dev/null +++ b/src/util/basis.rs @@ -0,0 +1,64 @@ +use pyo3::prelude::*; + +use crate::fusion::fusion::AccessFusionPair; +use crate::fusion::fusion::FusionPair; + +#[pyclass] +#[derive(Clone, Debug, PartialEq)] +pub struct Basis { + ops: Vec<(u32, FusionPair)>, +} + +#[pymethods] +impl Basis { + #[new] + fn new(ops: Vec<(u32, FusionPair)>) -> Self { + Basis { ops } + } + + // Assumes sorted by time + pub fn verify_basis(&self, anyons: usize) -> bool { + if self.ops.len() != anyons-1{ + return false; + } + + let mut fusible_anyons = vec![true; anyons]; + let mut unused_anyons = vec![true; anyons]; + + let mut current_time: u32 = 0; + + for (t, op) in &self.ops { + if *t != current_time { + unused_anyons = vec![true; anyons]; + current_time = *t; + } + + //Anyons in fuision pair is not in range [0,anyons) + if !(op.anyon_1() < op.anyon_2() && op.anyon_2() < anyons) { + return false; + } + + //Anyons have been fusioned away at a previous time + if !fusible_anyons[op.anyon_1()] || !fusible_anyons[op.anyon_2()] { + return false; + } + + //Anyons have already been fusioned at the current time + if !unused_anyons[op.anyon_1()] || !unused_anyons[op.anyon_2()] { + return false; + } + + //Checks for adjacency of anyon_1 and anyon_2 + for anyon in op.anyon_1() + 1..op.anyon_2() - 1 { + if fusible_anyons[anyon] && unused_anyons[anyon] { + return false; + } + } + + fusible_anyons[op.anyon_2()] = false; + unused_anyons[op.anyon_1()] = false; + } + + true + } +} From 17c720df8b9ebb22d99e06a37d00f836ecbd5ca6 Mon Sep 17 00:00:00 2001 From: Haadi Khan Date: Sun, 16 Jun 2024 00:46:38 -0400 Subject: [PATCH 9/9] Added proper testing Need @william590y and @dbizdfvy to write tests for Model Some more work to do (including further testing for main) --- pyproject.toml | 11 +++++++++++ python/anyon_braiding_simulator/main.py | 3 ++- python/tests/test_anyon.py | 2 ++ python/tests/test_basis.py | 19 ++++++++++++++---- python/tests/test_fusion.py | 26 +++++++++++-------------- python/tests/test_main.py | 25 ++++++++++++++++++++++++ python/tests/test_model.py | 11 ++++++++--- python/tests/test_state.py | 14 ++++++------- 8 files changed, 81 insertions(+), 30 deletions(-) create mode 100644 python/tests/test_main.py diff --git a/pyproject.toml b/pyproject.toml index b3d1551..ede22b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,17 @@ classifiers = [ ] dynamic = ["version"] +[tool.pytest.ini_options] +markers = [ + "main", + "fusion", + "braiding", + "state", + "model", + "anyon", + "basis" +] + [tool.maturin] features = ["pyo3/extension-module"] diff --git a/python/anyon_braiding_simulator/main.py b/python/anyon_braiding_simulator/main.py index bdd9699..d37f829 100644 --- a/python/anyon_braiding_simulator/main.py +++ b/python/anyon_braiding_simulator/main.py @@ -164,7 +164,8 @@ def __init__(self): sys.exit(0) else: print('\nError: Invalid model.') - model(user_input) + input_to_model_type = {'ising': AnyonModel.Ising, 'fibonacci': AnyonModel.Fibonacci} + model(input_to_model_type[user_input.lower()]) # Prompt the user to input the anyon details no_anyons = True diff --git a/python/tests/test_anyon.py b/python/tests/test_anyon.py index 78ff9bf..bcf99ef 100644 --- a/python/tests/test_anyon.py +++ b/python/tests/test_anyon.py @@ -1,6 +1,8 @@ +import pytest from anyon_braiding_simulator import Anyon, IsingTopoCharge +@pytest.mark.anyon def test_anyon(): anyon = Anyon('thisisatest', IsingTopoCharge.Psi, (1, 1)) assert anyon.name == 'thisisatest' diff --git a/python/tests/test_basis.py b/python/tests/test_basis.py index d4d9204..59b4cbb 100644 --- a/python/tests/test_basis.py +++ b/python/tests/test_basis.py @@ -1,12 +1,14 @@ +import pytest from anyon_braiding_simulator import Basis, FusionPair -def setup(): - pass +@pytest.fixture +def anyons(): + return 10 -# Test basic input sanitizations -def test_basis(): +@pytest.mark.basis +def test_basis(anyons): anyons = 10 operations = [] for t in range(1, 10): @@ -14,16 +16,25 @@ def test_basis(): assert Basis(operations).verify_basis(anyons) + +@pytest.mark.basis +def test_empty_basis(anyons): operations = [] assert not Basis(operations).verify_basis(anyons) + +@pytest.mark.basis +def test_swapped_basis(anyons): operations = [] for t in range(1, 10): operations.append((t, FusionPair(t, 0))) assert not Basis(operations).verify_basis(anyons) + +@pytest.mark.basis +def test_invalid_time_basis(anyons): operations = [] for t in range(1, 10): diff --git a/python/tests/test_fusion.py b/python/tests/test_fusion.py index 894e420..84db54c 100644 --- a/python/tests/test_fusion.py +++ b/python/tests/test_fusion.py @@ -1,17 +1,17 @@ +import pytest from anyon_braiding_simulator import Anyon, Fusion, FusionPair, IsingTopoCharge, State -def setup() -> State: +@pytest.fixture +def state() -> State: state = State() - - return state - - -def test_str_1(): - state = setup() for i in range(6): state.add_anyon(Anyon(f'{i}', IsingTopoCharge.Sigma, (0, 0))) + return state + +@pytest.mark.fusion +def test_str_1(state): state.add_operation(1, FusionPair(0, 1)) state.add_operation(1, FusionPair(2, 3)) state.add_operation(1, FusionPair(4, 5)) @@ -24,8 +24,8 @@ def test_str_1(): assert str(fusion) == expected -def test_apply_ising_fusion(): - state = setup() +@pytest.mark.fusion +def test_apply_ising_fusion(state): fusion = Fusion(state) psi = [1, 0, 0] @@ -41,12 +41,8 @@ def test_apply_ising_fusion(): assert not fusion.apply_fusion(psi_sigma, psi_sigma) == [2, 1, 2] # get owned rishi -def test_qubit_enc(): - state = setup() - - for i in range(6): - state.add_anyon(Anyon(f'{i}', IsingTopoCharge.Sigma, (0, 0))) - +@pytest.mark.fusion +def test_qubit_enc(state): state.add_operation(1, FusionPair(0, 1)) state.add_operation(1, FusionPair(2, 3)) state.add_operation(1, FusionPair(4, 5)) diff --git a/python/tests/test_main.py b/python/tests/test_main.py new file mode 100644 index 0000000..22e3d4b --- /dev/null +++ b/python/tests/test_main.py @@ -0,0 +1,25 @@ +# Standard Library +import os + +import pytest +import sh + + +def exec(cmds: list[str]): + os.chdir('python/anyon_braiding_simulator') + + cmd_string = '\n'.join(cmds) + + try: + sh.python(['main.py'], _in=cmd_string) + except sh.ErrorReturnCode as e: + print(e) + assert False + finally: + os.chdir('../..') + + +@pytest.mark.main +def test_join(): + cmds = ['ising', 'exit'] + exec(cmds) diff --git a/python/tests/test_model.py b/python/tests/test_model.py index c554ffb..98216d5 100644 --- a/python/tests/test_model.py +++ b/python/tests/test_model.py @@ -1,6 +1,11 @@ -from anyon_braiding_simulator import Model +import pytest +from anyon_braiding_simulator import AnyonModel, Model +""" +TODO: Write tests for the Model class +""" + +@pytest.fixture def setup(): - model = Model() - return model + return Model(AnyonModel.Ising) diff --git a/python/tests/test_state.py b/python/tests/test_state.py index 445f701..112c2b5 100644 --- a/python/tests/test_state.py +++ b/python/tests/test_state.py @@ -1,13 +1,14 @@ +import pytest from anyon_braiding_simulator import Anyon, FusionPair, IsingTopoCharge, State -def setup() -> State: - state = State() - return state +@pytest.fixture +def state() -> State: + return State() -def test_add_anyon(): - state = setup() +@pytest.mark.state +def test_add_anyon(state): for i in range(100): anyon = Anyon(f'{i}', IsingTopoCharge.Sigma, (0, 0)) state.add_anyon(anyon) @@ -20,8 +21,7 @@ def test_add_anyon(): assert len(state.anyons) == 100 -def test_add_operation(): - state = setup() +def test_add_operation(state): for i in range(101): anyon = Anyon(f'{i}', IsingTopoCharge.Sigma, (0, 0)) state.add_anyon(anyon)