diff --git a/.bumpversion.cfg b/.bumpversion.cfg deleted file mode 100644 index f8834d0d..00000000 --- a/.bumpversion.cfg +++ /dev/null @@ -1,8 +0,0 @@ -[bumpversion] -current_version = 4.0.0 -commit = True -tag = False - -[bumpversion:file:simulaqron/__init__.py] - -[bumpversion:file:README.md] diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..77eec0da --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +* text=auto eol=lf +*.png binary +*.jpg binary +*.gif binary diff --git a/.github/workflows/actions.yaml b/.github/workflows/actions.yaml index 78e913c7..d1d2ac45 100644 --- a/.github/workflows/actions.yaml +++ b/.github/workflows/actions.yaml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@master - uses: actions/setup-python@master with: - python-version: 3.8 + python-version: "3.12" - run: | make install make lint @@ -22,9 +22,10 @@ jobs: - uses: actions/checkout@master - uses: actions/setup-python@master with: - python-version: 3.8 + python-version: "3.12" - run: | make install + make test-deps make tests examples: @@ -34,7 +35,7 @@ jobs: - uses: actions/checkout@master - uses: actions/setup-python@master with: - python-version: 3.8 + python-version: "3.12" - run: | make install make examples diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml new file mode 100644 index 00000000..8d904fe5 --- /dev/null +++ b/.github/workflows/docs.yaml @@ -0,0 +1,46 @@ +name: Build docs + +on: push + +jobs: + build: + name: Build Docs + runs-on: ubuntu-latest + # We only execute this step if the commit is tagged, i.e., this is a release + if: startsWith(github.ref, 'refs/tags') + steps: + - name: Checkout code + uses: actions/checkout@master + - name: Build docs + uses: actions/setup-python@master + with: + python-version: "3.12" + # We *need* to install projectq to generate documentation + # This is needed because sphinx analyzes SimulaQron's code to generate the HTML; + # this implies that the modules are imported (but not executed), which therefore + # imports projectq classes. + - name: Install simulaqron and build docs + run: | + make install-optional + cd docs + make install-deps + make build + - name: Upload static files as artifact + id: deployment + uses: actions/upload-pages-artifact@v4 + with: + path: docs/build/html/ + + deploy: + name: Deploy docs + needs: build + runs-on: ubuntu-latest + # We only execute this step if the commit is tagged, i.e., this is a release + if: startsWith(github.ref, 'refs/tags') + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/netqasm.yaml b/.github/workflows/netqasm.yaml deleted file mode 100644 index 07c2b0fc..00000000 --- a/.github/workflows/netqasm.yaml +++ /dev/null @@ -1,27 +0,0 @@ -name: NetQASM backend tests - -on: push - -jobs: - examples: - name: Run examples - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - uses: actions/setup-python@master - with: - python-version: 3.8 - - name: Clone and install netqasm - run: | - git clone https://github.com/QuTech-Delft/netqasm.git - cd netqasm - make install - cd .. - - name: Install simulaqron - run: make install - - name: Install projectq - run: pip install -Iv projectq==0.5.1 - - name: Run examples - env: - NETQASM_SIMULATOR: simulaqron - run: make -C netqasm external-examples diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 00000000..389c9864 --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,46 @@ +name: Publish Python distributions to PyPI + +on: push +jobs: + build-n-publish: + name: Build and publish Python distributions to PyPI + runs-on: ubuntu-latest + # We only execute this step if the commit is tagged, i.e., this is a release + if: startsWith(github.ref, 'refs/tags') + # Use the following for PyPI deployments + environment: + name: pypi + url: https://pypi.org/p/simulaqron + # Use the following for TestPyPI deployments +# environment: +# name: testpypi +# url: https://test.pypi.org/p/simulaqron + permissions: + id-token: write + steps: + - name: Checkout code + uses: actions/checkout@master + - name: Set up Python 3.12 + uses: actions/setup-python@master + with: + python-version: "3.12" + - name: Install pypa/build + run: >- + python3.12 -m + pip install + build + --user + - name: Build a binary wheel and a source tarball + run: >- + python3.12 -m + build + --sdist + --wheel + --outdir dist/ + . +# - name: Publish distribution to Test PyPI +# uses: pypa/gh-action-pypi-publish@release/v1.14 +# with: +# repository_url: https://test.pypi.org/legacy/ + - name: Publish distribution to PyPI + uses: pypa/gh-action-pypi-publish@release/v1.14 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 22e570f8..a234c35c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.pyc .*.sw? *.DS_Store +*.whl /docs/build/* /build/* /dist/* @@ -9,6 +10,9 @@ .idea/* .vscode/* +.claude/* cqc/backend/logFile* examples/**/log/* + +*.out.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 539c9bb0..cc863464 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ For more details refer to the [documentation](https://softwarequtech.github.io/S Upcoming -------- +2026-03-13 (v4.0.1) +------------------- +- Migrated project specification to use modern TOML approach. +- Tested working with Python versions 3.10, 3.11 and 3.12. +- Added new SDK for easily creating SimulaQron applications. This new SDK allows to also create + client-server applications using event-based programming paradigm. +- Updated the SimulaQron network configuration file format. This is done to align the network + configuration file with the data required by SimulaQron "native" mode and the "NetQASM" mode. +- Updated documentation of most of the undocumented functions and to match the new developments. 2021-11-18 (v4.0.0) ------------------- diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..b8eb0c59 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,30 @@ +# SimulaQron Development Conventions + +## Hard Requirements + +### Examples +- Examples MUST always use separate files per node (e.g. `nodeTest-client.py`, `nodeTest-server.py`), never single-file programs. Students must be able to run each node on a separate machine. +- Follow the `new-sdk/template-client-server` structure: two node scripts + `run.sh` + `terminate.sh` + config JSONs. +- All nodes should print their own output (not rely on return values visible only to the test harness). + +### Connection Pattern +- Prefer creating a `NetQASMConnection` once and reusing it with `flush()` for multi-round quantum programs, rather than opening/closing with `with` each round. +- `conn.flush()` is the sync point that makes measurement results readable via `int(m)`. + +### Backends +- Use `stabilizer` backend by default in examples and tests unless non-Clifford gates are needed. +- `projectq` is deprecated / hard to install. Prefer `qutip` when full state simulation is needed. + +## File Conventions +- **Examples:** `examples/new-sdk/` — all new examples use the new SDK +- **Tests:** `tests/slow/sdk/` — SDK-level integration tests +- **Core code:** `simulaqron/` — simulator source +- **Docs:** `docs/` — Sphinx documentation (currently outdated, being rewritten) + +## Git / Commit Policy +- **Always run `make ci` before committing.** All linting, tests, and examples must pass before any commit is created. +- Never commit or push without explicit user approval. + +## Virtual Environment +- The project venv is at `.venv/` — activate with `source .venv/bin/activate` +- Install with `pip install -e .` (skip `[test]` if projectq build fails) diff --git a/Dockerfile b/Dockerfile index 03e4c6e7..ed5406e4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,57 +1,88 @@ -## Dockerfile -# This container provides a complete run-time environment for SimulaQron. -# -## Build: -# From inside the top-level SimulaQron directory run -# -# docker build -t . -# -# where you should replace with the name you want to give to the -# docker image. -# -## Run: -# To start the container and enter the shell prompt inside, run -# -# docker run -it -# -# This will start a docker container with SimulaQron inside the -# /workspace/SimulaQron directory (this default can be changed, by changing the -# WORKSPACE variable in this file). Note that this is only a COPY of the -# SimulaQron directory that was made during the build step so if you make any -# changes on the host system, they will not be reflected in the container until -# you rebuild the image. -# -# However, if you wish, you can mount the host's SimulaQron directory into the -# container rather than using a copy made during the build step, you need to -# explicitly mount it when starting the container, e.g. -# -# docker run -it -v /path/to/SimulaQron:/workspace/SimulaQron -# -# This will mount /path/to/SimulaQron inside the container in -# /workspace/SimulaQron. Note that on Linux systems with SELinux present, the -# mount option has to be `-v /path/to/SimulaQron:/workspace/SimulaQron:z`. -# -## Multiple shells: -# To attach to a running container with a new shell run -# -# docker exec -it bash -# -# where is the name of the running container (this is in -# general not the same as the image name). To find what name your container -# has, run `docker ps`. - -FROM ubuntu:18.04 -LABEL author="Wojciech Kozlowski " - -# Update docker image -ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update && apt-get upgrade -y - -# Install Python 3 -RUN apt-get install -y python3 python3-pip python3-tk - -# Set a UTF-8 locale - this is needed for some python packages to play nice -RUN apt-get -y install language-pack-en -ENV LANG="en_US.UTF-8" - -RUN pip3 install simulaqron +# If you need a slim version, use "alpine" base instead of trixie (Debian) +#FROM python:3.12-alpine AS base-image +FROM ubuntu:24.04 AS base-image + +# Install packages + +#RUN apk add --no-cache bash vim nano libstdc++ libgomp +RUN apt-get update +RUN apt-get dist-upgrade -y +RUN apt-get install -y bash vim nano libgomp1 adduser git openssl make software-properties-common + +RUN add-apt-repository ppa:deadsnakes/ppa +RUN apt-get update + +# Install Python 3.12 +RUN apt-get install -y python3.12-full python3.12-dev + +# Switch to Bash +SHELL ["/bin/bash", "-c"] + +# Delete the build-in user - not needed in alpine images +RUN userdel -r ubuntu + +# Create student user +ARG GNAME=student +ARG GID=1000 +ARG UNAME=student +ARG UID=1000 +ARG UHOME=/home/student +ENV HOME=${UHOME} +RUN set -o errexit -o nounset +RUN addgroup --gid ${GID} "${GNAME}" +RUN adduser --home "${UHOME}" --disabled-password --uid ${UID} --ingroup "${GNAME}" "${UNAME}" +WORKDIR ${UHOME} + +# Remove unnecessary software +RUN apt-get purge -y adduser software-properties-common +RUN apt-get autoremove -y + +FROM base-image AS venv-image + +# Install build essentials, to correctly build python deps +# We install it only for this stage, so we create a lighter docker image for executing +#RUN apk add --no-cache alpine-sdk +RUN apt-get update +RUN apt-get install -y build-essential cmake linux-headers-generic + +# Create python virtual environment +WORKDIR ${UHOME} +RUN python3.12 -m venv simulaqron-venv +# We have to "manually" activate the virtual environment +ENV PATH="${UHOME}/simulaqron-venv/bin:$PATH" + +# Install SimulaQron +# Option 1: Install from the wheel file +# Copy the simulaqron wheel into the container +COPY dist/*.whl ${UHOME} +RUN pip install *.whl +RUN rm *.whl + +# Install extra packages that need build-essential +RUN pip install "qutip<5.0.0" +RUN pip install "setuptools<81" pybind11 +RUN pip install "git+https://github.com/ProjectQ-Framework/ProjectQ.git@v0.8.0" --no-build-isolation + +# Option 2: (Recommended for release) Install from PyPI +#RUN pip install "simulaqron[opt]>=4.0.1" + +FROM base-image AS run-image + +# Copy installed python packages from previous image +COPY --from=venv-image --chown=${UNAME} ${UHOME}/simulaqron-venv "${UHOME}/.local" +# Since these files come from a python virtual environment +# we need to fix some absolute paths +WORKDIR ${UHOME}/.local/bin +RUN sed -i "s|${UHOME}/simulaqron-venv/|${UHOME}/.local/|g" * +# Activate the "virtual environment" we just copied +ENV PATH="${UHOME}/.local/bin:${UHOME}/.local:$PATH" + +# Configure libgomp to use a single thread to avoid deadlocks in simulaqron +ENV OMP_NUM_THREADS=1 + +# Switch to user student +USER student +WORKDIR ${UHOME} +RUN echo 'export PS1="\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ "' >> ~/.bashrc + +CMD bash diff --git a/Makefile b/Makefile index 88d9f2d1..3c1f6f03 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,9 @@ PIP = pip3 EXAMPLES_DIR = examples SIMULAQRON_DIR = simulaqron TEST_DIR = tests -RESET_FILE = ${SIMULAQRON_DIR}/toolbox/reset.py + +# IMPORTANT: For running in makefile, we need to use only 1 thread in OMP library +export OMP_NUM_THREADS=1 clean: _delete_pyc _delete_pid _clear_build _reset @@ -17,26 +19,42 @@ lint: @${PYTHON} -m flake8 ${SIMULAQRON_DIR} ${EXAMPLES_DIR} ${TEST_DIR} test-deps: - @${PYTHON} -m pip install -r test_requirements.txt + @${PYTHON} -m pip install .\[test\] requirements python-deps: - @cat requirements.txt | xargs -n 1 -L 1 $(PIP) install + @${PYTHON} -m pip install . install-optional: install - @cat optional-requirements.txt | xargs -n 1 -L 1 $(PIP) install - -_reset: - @${PYTHON} ${RESET_FILE} - -_tests: - @${PYTHON} -m pytest ${TEST_DIR}/quick - -tests: _tests _reset - -_tests_all: - @${PYTHON} -m pytest ${TEST_DIR} - -tests_all: _tests_all _reset + # Python setuptools 81 removed "dry_run" option when compiling C++ code + # this breaks the build of projectq + # As a hack, we install the bare minimum tools to build projectq, then + # we build and install it (ignoring any build requirement in the projectq + # package spec), and finally we install the rest of the optional requirements + @${PYTHON} -m pip install "setuptools<81" pybind11 + @${PYTHON} -m pip install "git+https://github.com/ProjectQ-Framework/ProjectQ.git@v0.8.0" --no-build-isolation + @${PYTHON} -m pip install .\[opt\] + +tests: + @${PYTHON} -m pytest -v ${TEST_DIR}/quick + +tests_slow: + @${PYTHON} -m pytest -v ${TEST_DIR}/slow + +tests_all: + @${PYTHON} -m pytest -v --capture=tee-sys ${TEST_DIR} + +examples: + @echo "--- new-sdk examples ---" + @cd examples/new-sdk/corrRNG && timeout 90 bash run.sh + @cd examples/new-sdk/corrRNG && bash terminate.sh && sleep 3 + @cd examples/new-sdk/extendGHZ && timeout 90 bash run.sh + @cd examples/new-sdk/extendGHZ && bash terminate.sh && sleep 3 + @cd examples/new-sdk/teleport && timeout 90 bash run.sh + @cd examples/new-sdk/teleport && bash terminate.sh && sleep 3 + @cd examples/new-sdk/midCircuitLogic && timeout 90 bash run.sh + @cd examples/new-sdk/midCircuitLogic && bash terminate.sh && sleep 3 + @cd examples/native-mode/teleport && bash terminate.sh && sleep 3 + @echo "Chosen examples passed." install: test-deps @$(PYTHON) -m pip install -e . ${PIP_FLAGS} @@ -44,6 +62,9 @@ install: test-deps _verified: @echo "SimulaQron is verified!" +ci: lint tests tests_slow examples + @echo "All CI checks passed." + verify: clean python-deps lint tests _verified _remove_build: @@ -62,4 +83,4 @@ _build: build: _clear_build _build -.PHONY: clean lint python-deps tests full_tests verify build +.PHONY: clean lint python-deps tests tests_slow tests_all examples ci full_tests verify build diff --git a/README.md b/README.md index a93e09f9..5f4e24a8 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -SimulaQron - simple quantum network simulator (4.0.0) -===================================================== +# SimulaQron - simple quantum network simulator (4.0.0) The purpose of this simulator of quantum network nodes is to allow you to develop new applications for a future quantum internet, while we do not yet have real quantum network nodes available for testing. @@ -7,12 +6,90 @@ a future quantum internet, while we do not yet have real quantum network nodes a Since version 4.0, SimulaQron is compatible with [NetQASM](https://github.com/QuTech-Delft/netqasm). See its documentation for how to use SimulaQron as a backend for running NetQASM applications. -Installation: +## Installation + +### Linux + +Before proceeding, make sure you install Python 3.12. Please note that Python 3.13 or newer *is not supported*. +To install Python 3.12 in Debian-based distributions, you can first add the "deadsnakes" repository: + +```shell +sudo add-apt-repository -y "ppa:deadsnakes/ppa" +``` + +Then you can install Python 3.12 and the Python development package: + +```shell +sudo apt-get install python3.12-full python3.12-dev +``` + +Additionally, you will need the `build-essential` package, to install tools used when building some SimulaQron dependencies:: + +```shell +sudo apt-get install build-essential cmake vim linux-headers-generic +``` + +After this, you can install this repository by using the Makefile: + +```shell +make install ``` -pip3 install simulaqron + +Additionally, you can install SimulaQron with extra dependencies: +```shell +make install-optional ``` -Documentation and examples are explained in the html documentation +### Windows + +In Windows, SimulaQron can be installed in two similar ways: + +* Using WSL: *Windows for Linux Subsystems* (WSL) is a way to execute the linux kernel (and linux apps) + in a Windows environment. To install WSL, you can follow the [official microsoft documentation](https://learn.microsoft.com/en-us/windows/wsl/install). + After this you can install SimulaQron in WSL using the Linux instructions from above. +* Using a Linux Virtual Machine: It is also possible to create a Linux environment using a Virtual Machine + Hypervisor such as [Oracle VirtualBox](https://www.virtualbox.org/wiki/Downloads). After installing this, + create a new Virtual Machine and install a compatible linux version (such as [Ubuntu 24.04](https://ubuntu.com/download/desktop/thank-you?version=24.04.4&architecture=amd64<s=true)). + After the installation is finished, follow the instructions to install SimulaQron on a Linux machine as + presented above. + + +### macOS + +In macOS, the only supported way to install SimulaQron is by using a Virtual Machine. Considering this +please install a Virtual Machine Hypervisor such as [Oracle VirtualBox](https://www.virtualbox.org/wiki/Downloads), +and install a compatible operating system: + +* Intel-based Macs: This is the case for Mac computers with Intel processor.s You can directly install the ["amd64" + version of Ubuntu 24.04](https://ubuntu.com/download/desktop/thank-you?version=24.04.4&architecture=amd64<s=true). +* ARM-based Macs: This is the case for "Apple Silicon" processors (M1 or newer, including the A18 Macbook Neo). For + this type of Macs, you can install the ["arm64" version of Ubuntu 24.04](https://cdimage.ubuntu.com/releases/24.04/release/ubuntu-24.04.4-desktop-arm64.iso) + +After installing the Operating System on the virtual machine, please continue the installation of SimulaQron in +the virtual machine using the Linux instructions as mentioned above. + + +## Tests + +There are 2 sets of tests: quick and slow ones. To ease the execution, the `Makefile` provides two targets: +* `tests`: This target only run the quick tests. +* `tests_all`: This target runs quick and slow tests. + +To run a test target, simply invoke it with make: +```shell +make tests +``` + +or: +```shell +make tests_all +``` + + +## Documentation + + +Documentation and examples are explained in the HTML documentation https://softwarequtech.github.io/SimulaQron/html/index.html For upcoming and previous changes see the file [CHANGELOG.md](CHANGELOG.md) diff --git a/docs/CQC.rst b/docs/CQC.rst deleted file mode 100644 index 98b4cfd3..00000000 --- a/docs/CQC.rst +++ /dev/null @@ -1,63 +0,0 @@ -The CQC interface -================= - -SimulaQron can be access from any programming language supporting network connections. Instructions to the quantum hardware simulation can be sent via the CQC interface described `here `_. - -A `C `_, `Python `_, and `Rust `_ Library for programming SimulaQron using the CQC Interface are provided. If you are new to SimulaQron, programming via the Python CQC is the easiest way to get started. - -^^^^^^^^^^^^ -Installation -^^^^^^^^^^^^ - -If you have installed SimulaQron using pip, the cqc interface for python should already be installed. -If needed you can also only install the CQC interface in Python using pip by typing:: - - pip3 install cqc - -^^^^^ -Usage -^^^^^ - -The python library provides a way to program a protocol on a network where the nodes listen to instructions through the classical-quantum combiner (CQC) interface. In the following examples the network is simulated by SimulaQron. But the same examples could be executed on a network with real quantum hardware which allows for instructions through the CQC interface, which is the aim for the 2020 quantum internet demonstrator. - -To use the Python library you first need instantiate an object from the class :code:`cqc.pythonLib.CQCConnection`. This should be done in a `context `_, that is using a :code:`with`-statement as follows:: - - with CQCConnection("Alice") as alice: - # your program - -This is to make sure the CQCConnection is correctly closed by the end of the program and that the qubits in the backend are released, even if an error occurs in your program. - -.. note:: It is still possible to initialize a :code:`CQCConnection` in the old way, i.e. without :code:`with`, however you will receive a warning message everytime you create a qubit. - -Let's look at an extremely trivial example were we have the node `Alice` allocate a qubit, perform a Hadamard gate and measure the qubit:: - - with CQCConnection("Alice") as alice: - q = qubit(Alice) - q.H() - m = q.measure() - print(m) - -.. note:: If you do not specify the argument ``socket_address`` specifying the hostname and port of the cqc server receiving incoming CQC messages, you need to have simulaqron installed. The python library then tries to use the socket address of this nodes specified in simulaqron. - -A object from the :class:`qubit`-class is created with the :class:`CQCConnection` as argument, such that whenever an operation is applied to the qubit a CQC message will be sent to the simulation backend to actually perform this operation on the simulated qubit. -For more examples using the Python library see :doc:`GettingStarted` and https://softwarequtech.github.io/CQC-Python/examples.html - -.. _remoteNetwork: - ----------------------------------------- -Connecting to a remote simulated network ----------------------------------------- - -If a simulated network (consisting of virtual nodes and CQC servers) are setup on a remote computer (or on your own computer), CQC messages can be sent to the correct address and port numbers to control the nodes of the network. In this section we describe how to do this. - -Given the ip and port number of the CQC server of a node, you can send CQC messages over TCP using in any way you prefer. To know how these messages should look like to perform certain instructions, refer to https://softwarequtech.github.io/CQC-Python/interface.html - -An easier way to send CQC messages to a CQC server of a node is to use the provided Python library. -Assuming that you know the hostname and port number of the CQC server, you can then easily instantiate an object of the class :class:`~cqc.pythonLib.CQCConnection` which will communicate with the CQC server for you, using the CQC interface. -You can directly specify the ip and port number as follows:: - - cqc = CQCConnection("Alice", socket_address=("1.1.1.1", 8801)) - -More information on how to then actually allocating qubits, manipulating these and creating simulated entanglement see https://softwarequtech.github.io/CQC-Python/useful_commands.html - -We give some more detailed information below on how the classical communication between nodes in the application layer can be realized and also provide some useful commands to program a protocol using the Python library. diff --git a/docs/ConfNodes.rst b/docs/ConfNodes.rst index 5f628b2b..675e200c 100644 --- a/docs/ConfNodes.rst +++ b/docs/ConfNodes.rst @@ -1,219 +1,263 @@ -Configuring the simulated network -================================= +SimulaQron Configuration +======================== -------------------------------- -Starting the SimulaQron backend -------------------------------- +SimulaQron uses two configuration files: -The backend of a SimulaQron network is a set of running virtual nodes and their corresponding CQC servers. To start the backend of a SimulaQron network run the command ``simulaqron start``. +* ``simulaqron_network.json`` — defines nodes, their socket ports, and network topology (described on this page) +* ``simulaqron_settings.json`` — configures the simulation backend, timeouts, and other settings + (see the Settings section in :ref:`Configuring Settings `) -With no arguments, a network is by default started with the five nodes Alice, Bob, Charlie, David and Eve. How to adjust the nodes and the topology of the network is described below. +------------------------------------- +Running all nodes on a single machine +------------------------------------- -.. warning:: ``simulaqron start`` can fail if any of the ports specified in the config files are already in use by a running SimulaQron network or another program. +When developing and testing, you typically run all simulated nodes on one computer. +In this case, all sockets use ``localhost`` and you just need distinct port numbers for each node. -To configure networks see section :ref:`networkConfig`. -Finally for instructions on how to connect to an already runnning simulated network using CQC, see section :ref:`remoteNetwork`. +^^^^^^^^^^^^^^^^^^^^^^^^ +Using the SimulaQron CLI +^^^^^^^^^^^^^^^^^^^^^^^^ -If you want to start a network with for example the three nodes Alex, Bart, Curt, simply type:: +The ``simulaqron`` command manages the backend for you. To start a network with nodes Alice and Bob:: - simulaqron start --nodes Alex,Bart,Curt + simulaqron start --nodes Alice,Bob -If you simply want a network with 10 nodes, type:: +This reads ``simulaqron_network.json`` and ``simulaqron_settings.json`` from the current directory (or uses +defaults), starts the virtual node servers, the QNodeOS servers, and the classical communication servers for +each node listed. - simulaqron start --nrnodes 10 +To stop the backend:: -This will start up a network where the nodes are called Node0, Node1, ..., Node9. + simulaqron stop -The --nodes and --nrnodes can be combined. Let's say you want a network with 10 nodes and that three of the nodes are called Alice, Bob and Charlie, type:: +If something went wrong (e.g. the process was killed) and SimulaQron thinks the network is still running:: - simulaqron start --nodes Alice,Bob,Charlie --nrnodes 10 + simulaqron reset -Which will start up a network with the nodes Alice, Bob, Charlie, Node0, Node1, ..., Node6. If --nrnodes is less than the entries in --nodes, then --nrnodes is ignored. The two keywords can also be specified shorter as -nd and -nn respectively. So the above can also be done as:: +The ``simulaqron start`` command accepts these arguments: - simulaqron start -n Alice,Bob,Charlie -N 10 +* ``--nodes `` (optional): Comma-separated list of node names to start. These must exist in + the network configuration file. If not given, SimulaQron will start all the defined nodes in + ``simulaqron_network.json``. +* ``--simulaqron-config-file=PATH`` (optional): Path to a SimulaQron settings file. Defaults to + ``simulaqron_settings.json`` in the current folder. +* ``--network-config-file=PATH`` (optional): Path to a network configuration file. Defaults to + ``simulaqron_network.json`` in the current folder. +* ``--network-name=`` (optional): Name of the network to start (must match a name in the config file). + Defaults to ``default``. -You can also specify a topology of the network. For example if you want 10 nodes in a ring topology, type:: +.. warning:: ``simulaqron start`` will fail if any of the ports specified in the config files are already in + use by a running SimulaQron network or another program. - simulaqron start --nrnodes 10 --topology ring +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Using per-example run scripts +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In this network Node :math:`i` can create EPR pairs and send qubits to Node :math:`i-1 \pmod{10}` and Node :math:`i+1 \pmod{10}`. However, if a CQC message is sent to for example Node2 to produce entanglement with Node5, a error message (CQC_ERR_UNSUPP) will be returned. The options for the automatically generated topologies are currently: +Each example in ``examples/new-sdk/`` and ``examples/native-mode/`` includes a ``run.sh`` script that starts +the SimulaQron backend and launches the node programs. This is the easiest way to try an example:: -* `complete`: A fully connected. This is also used if the argument --topology is not used. -* `ring`: A ring network, i.e. a connected topology where every node has exactly two neighbors. -* `path`: A path network, i.e. a connected topology where every node has exactly two neighbors but there are no cycles. -* `random_tree`: Generates a random tree, i.e. a topology without cycles. -* `random_connected_{int}`: Generates a random connected graph with a specified number of edges. For example a random connected network on 10 nodes, can be specified as `random_connected_20`. Note that the number of edges for a network with :math:`n` nodes must be greater or equal to :math:`n-1` and less or equal to :math:`n(n-1)/1`. + cd examples/new-sdk/corrRNG + bash run.sh -Along with setting up the network with the specified topology a .png figure is also generated and stored as config/topology.png. This is useful if a random network is used, to easily visualize the network used. +The ``run.sh`` script reads the ``simulaqron_network.json`` and ``simulaqron_settings.json`` in the example +directory, so each example is self-contained. -As a final example let's combine all the arguments specified above and create a network using 15 nodes, where two of then are called Alice and Bob and the topology of the network is randomly generated as a connected graph with 20 edges:: +---------------------------------- +Running nodes on separate machines +---------------------------------- - simulaqron start -n Alice,Bob -N 15 -t random_connected_20 +To simulate a real distributed quantum network, you can run each node on a different physical computer. +In this case, you need to: -The network that is then started might look like this: - -.. image:: figs/topology.png - :width: 400px - :align: center - :alt: Programming SimulaQrons Interfaces +1. **Use real hostnames/IPs** instead of ``localhost`` in the ``simulaqron_network.json`` file. + Each node's sockets must be reachable from the other machines. -To create a custom topology, see below. +2. **Copy the same** ``simulaqron_network.json`` **to every machine**. All nodes must agree on the + network configuration. ---------------------- -Using the --keep flag ---------------------- -By default simulaqron will try to overwrite the current network config of a network your trying to start. -For example if you have a network called "my_network" with the nodes Alice, Bob and Charlie and you type:: - - simulaqron start --name=my_network --nodes=Alice,Bob +3. **Start only the local node** on each machine. On the machine running Alice:: -simulaqron will ask you if you want to edit the config file to make "my_network" be a network with the nodes Alice and Bob. -However if you add the flag ``--keep``, simulaqron will simply start up Alice and Bob in the network "my_network" without editing the config file. -This is useful if your planning to simulated a network between multiple physical computers. -Since in this case, the node Charlie might be simulated at a differnent computer so you still want the addresses of Charlie in your config file but you don't want to start that node on your computer. + simulaqron start --nodes Alice -.. note:: If you want to suppress the check from simulaqron whether you want to edit the network config file you can always add the flag ``--force`` (``-f``). + On the machine running Bob:: ------------------ -Multiple networks ------------------ + simulaqron start --nodes Bob -To run multiple networks at the same time you need to given them different names by using the --name flag:: +4. **Run your node program** on each machine after the backend is started. - simulaqron start --name NETWORK +An example ``simulaqron_network.json`` for a distributed setup:: -To stop a network with a specific name type:: - - simulaqron stop --name NETWORK - -.. note:: By default the network name is "default". To have multiple networks running at the same time the nodes cannot use the same port numbers. + { + "default": { + "nodes": { + "Alice": { + "app_socket": ["192.168.1.10", 8000], + "qnodeos_socket": ["192.168.1.10", 8001], + "vnode_socket": ["192.168.1.10", 8004] + }, + "Bob": { + "app_socket": ["192.168.1.20", 8000], + "qnodeos_socket": ["192.168.1.20", 8001], + "vnode_socket": ["192.168.1.20", 8004] + } + }, + "topology": null + } + } -How multiple networks can be setup is described below. +.. note:: When running on separate machines, the port numbers can be the same on each machine since they + bind to different IP addresses. .. _networkConfig: ----------------------- Configuring the network ----------------------- -Using the CLI you can add nodes to a network using for example:: + +The network configuration file (``simulaqron_network.json``) defines nodes and their socket assignments. +For each node, you specify IP and port for three sockets: + +* ``app_socket`` — classical communication between application-level nodes +* ``qnodeos_socket`` — connection to the QNodeOS server that interprets NetQASM subroutines +* ``vnode_socket`` — connection to the SimulaQron VirtualNode that runs the quantum simulation + +You can easily copy the default network configuration by using the simulaqron CLI command:: + + simulaqron nodes default + +This will create a ``simulaqron_network.json`` file in the current folder with 5 nodes: `Alice`, +`Bob`, `Charlie`, `David` and `Eve`. + +^^^^^^^^^^^^^^^^^^^^^^^^^^ +Using the CLI to add nodes +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can build up a network incrementally using the CLI:: simulaqron nodes add Maria -which adds the node Maria to the default network "default". If you want add a node to another network you can do:: +This adds Maria to the default network with random ports on ``localhost``. To add to a different network:: simulaqron nodes add Maria --network-name="OtherNetwork" -which adds Maria to the network "OtherNetwork". -You can also specify hostname and port numbers to be used for this node including what it's neighbors are using the arguments: +You can also specify explicit hostnames and ports: - * ``--hostname`` - * ``--app_port`` - * ``--cqc_port`` - * ``--vnode_port`` - * ``--neighbors`` +* ``--hostname`` +* ``--app-port`` +* ``--qnodeos-port`` +* ``--vnode-port`` +* ``--neighbors`` -SimulaQron will ask you before it makes any changes to the network config file. If you wan to suppress this you can add the flag ``--force`` (``-f``). +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Writing the JSON config manually +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -If you want to build up a (or many) more complex networks it can become tedious to do this through the CLI. -You can instead write your own network config file. -This network config file should be a .json file and could for example look as follows. -An example of such a file can be seen below which contains two networks ("default" and "small_network") which the nodes "Alice", "Bob" and "Test" respectively:: +For more complex setups, write the ``simulaqron_network.json`` file directly. +Here is an example with two networks ("default" and "small_network"):: { "default": { "nodes": { "Alice": { - "app_socket": [ - "localhost", - 8000 - ], - "cqc_socket": [ - "localhost", - 8001 - ], - "vnode_socket": [ - "localhost", - 8004 - ] + "app_socket": ["localhost", 8000], + "qnodeos_socket": ["localhost", 8001], + "vnode_socket": ["localhost", 8004] }, "Bob": { - "app_socket": [ - "localhost", - 8007 - ], - "cqc_socket": [ - "localhost", - 8008 - ], - "vnode_socket": [ - "localhost", - 8010 - ] + "app_socket": ["localhost", 8007], + "qnodeos_socket": ["localhost", 8008], + "vnode_socket": ["localhost", 8010] } }, "topology": null - } + }, "small_network": { "nodes": { "Test": { - "app_socket": [ - "localhost", - 8031 - ], - "cqc_socket": [ - "localhost", - 8043 - ], - "vnode_socket": [ - "localhost", - 8089 - ] + "app_socket": ["localhost", 8031], + "qnodeos_socket": ["localhost", 8043], + "vnode_socket": ["localhost", 8089] } }, "topology": null } } -If you want simulaqron to use your custom network.json file simply set this in the settings by ``simulaqron set network-config-file your/path/my_network.json`` or add the following line to a file ``~/.simulaqron.json``: ``network_config_file: your/path/my_network.json``, where ``your/path/my_network.json`` is the path to your custom network config file. +Place this file in the same directory as your code and name it ``simulaqron_network.json``. +Alternatively, load a custom path in your Python code:: + + from simulaqron.settings import network_config + + network_config.read_from_file("/path/to/your/simulaqron_network.json") + +.. _network-topologies: -The entries ``"topology"`` can be used to define the topology of the network. -This could for example be:: +------------------ +Network topologies +------------------ + +Each network configuration contains a ``"topology"`` entry that defines which nodes can communicate +quantum information with each other. Setting it to ``null`` means fully connected (every node can reach +every other node). + +A custom topology is specified as a dictionary of adjacency lists:: { "Alice": ["Bob"], - "Bob": ["Alice", "Charlie"] + "Bob": ["Alice", "Charlie"], "Charlie": ["Bob"] } -descibing network where Alice is adjacent to Bob, Bob is adjacent to Alice and Charlie and Charlie is adjacent to Bob. +This describes a network where Alice is adjacent to Bob, Bob is adjacent to Alice and Charlie, and Charlie +is adjacent to Bob. + +.. note:: Directed topologies are also supported. For example, Alice can send a qubit to Bob but Bob + cannot send a qubit to Alice. + +--------------------------- +Generate network topologies +--------------------------- + +SimulaQron can automatically generate certain well-known network topologies: -.. note:: Undirected topologies are also supported. That is, networks where for example Alice can send a qubit to Bob but Bob cannot send a qubit to Alice. +* ``complete``: Fully connected (default if no topology is specified) +* ``ring``: Every node has exactly two neighbors, forming a cycle +* ``path``: Every node has at most two neighbors, no cycles +* ``random_tree``: A random tree (connected, no cycles) +* ``random_connected_{int}``: A random connected graph with a specified number of edges (e.g. + ``random_connected_20`` for 20 edges). The number of edges must be between :math:`n-1` and + :math:`n(n-1)/2` for :math:`n` nodes. +.. note:: Topology generation via the CLI is planned but not yet implemented. For now, specify topologies + directly in the ``simulaqron_network.json`` file (see `Network topologies`_ above). ------------------------------- -Starting a network from Python ------------------------------- +Along with setting up the network with the specified topology a .png figure is also generated and stored as +config/topology.png. This is useful if a random network is used, to easily visualize the network used. -You can also start a network within a Python script (this is in fact what simulaqron does), by using the class :code:`simulaqron.network.Network`. To setup a network by name "test" with the nodes Alice, Bob and Charlie, where Bob is connected with Alice and Charlie but Alice and Charlie are not connected use the following code code:: +The network that is then started might look like this: - from simulaqron.network import Network +.. image:: figs/topology.png + :width: 400px + :align: center + :alt: Example network topology +To create a custom topology, see section `Network topologies`_ above. - def main(): - # Setup the network - nodes = ["Alice", "Bob", "Charlie"] - topology = {"Alice": ["Bob"], "Bob": ["Alice", "Charlie"], "Charlie": ["Bob"]} - network = Network(name="test", nodes=nodes, topology=topology) +-------------------------- +Starting multiple networks +-------------------------- - # Start the network - network.start() +To run multiple networks at the same time, give them different names in the network configuration file +and use the ``--name`` flag:: - input("To stop the network, press enter...") + simulaqron start --name NETWORK --nodes Alice,Bob +To stop a specific network:: - if __name__ == '__main__': - main() + simulaqron stop --name NETWORK -By default the method :code:`simulaqron.network.Network.start`, only returns when the network is running, i.e. all the connections are established. To avoid this use the argument :code:`wait_until_running=False`. +.. note:: By default the network name is "default". To have multiple networks running at the same time the + nodes cannot use the same port numbers. -.. note:: The network will stop when the network-object goes out of scope and is handled by the Python garbade collector. The network can be manually stopped with the method :code:`simulaqron.network.Network.stop`. +The JSON configuration file can hold more than one network configuration. See `Writing the JSON config manually`_ +above for an example with multiple networks. diff --git a/docs/Examples.rst b/docs/Examples.rst index 286df10b..f86272a6 100644 --- a/docs/Examples.rst +++ b/docs/Examples.rst @@ -1,26 +1,89 @@ -Programming via SimulaQron's native Python Twisted Interface (specific to SimulaQron) -===================================================================================== +SimulaQron Programming Examples +=============================== -One way to program SimulaQron is directly via its 'native interface' using Twisted. -This means writing a client program connecting directly to the local virtual quantum node, and issuing instructions -to such simulated quantum hardware. Programming SimulaQron in its native interface is evidently Python specific, and -meant primarily as an internal interface allowing one to explore higher level abstractions built on top of it. -One such abstraction is the classical-quantum combiner (CQC) interface, which we aim to make available on the 2020 quantum -internet demonstrator. For programming in a universal, i.e., not Python specific interface see :doc:`CQC`. +SimulaQron offers three ways to write quantum network programs, from highest-level to lowest-level: -The examples below assume that you have already made your way through :doc:`GettingStarted`: you have the virtual node servers -up and running, and ran the simple example of generating correlated randomness. Further examples can also be found in examples/nativeMode. +1. **New SDK** (``examples/new-sdk/``) — The recommended approach *for simple quantum apps using + the NetQASM SDK*. Programs use ``NetQASMConnection`` and ``EPRSocket`` for quantum operations, + and ``SimulaQronClassicalClient``/``SimulaQronClassicalServer`` for classical messaging. + Start here if you are new to SimulaQron. -.. note:: The 'native' mode is not the recommended way to program applications for SimulaQron, instead use either the `Python `_ (recommended if you are gettings started), `C `_, or the `Rust `_ Library. +2. **Event-based** (``examples/event-based/``) — Builds on the new SDK by adding a state-machine + pattern for classical messaging. Each node defines states, message handlers, and a dispatch + table. This is the recommended pattern *for protocols that interleave classical negotiation + with quantum operations*. -.. toctree:: - :maxdepth: 2 - :caption: Native mode examples: +3. **Native mode** (``examples/native-mode/``) — The low-level Twisted interface that talks + directly to SimulaQron's virtual quantum nodes. This is Python-specific and more verbose, + but gives full control over the simulation backend. + +.. _get-examples: + +----------------------- +How to get the examples +----------------------- + +The code of the examples can be found in `SimulaQron GitHub repository `_. +Clone this repository using ``git``:: + + git clone https://github.com/SoftwareQuTech/SimulaQron.git + +All the examples can be found in the ``examples`` folder. + +When running one of the examples mentioned below, we assume that you have already made your way through +:doc:`Getting Started ` and you have the virtual node servers up and running. + +.. _new-sdk-examples: - NativeModeCorrRng - NativeModeTemplate - NativeModeTeleport - NativeModeGraphState +----------------- +New SDK examples +----------------- +* :doc:`Overview ` — Key concepts: ``NetQASMConnection``, ``EPRSocket``, ``flush()``, file structure +* :doc:`Template ` — Getting started: single-node and client-server templates +* :doc:`CorrRNG ` — EPR pairs between two nodes, correlated measurement +* :doc:`Teleport ` — Quantum teleportation with classical correction messages +* :doc:`ExtendGHZ ` — Three-party entanglement, multiple EPR sockets +* :doc:`MidCircuitLogic ` — Multiple ``flush()`` calls for mid-circuit classical decisions +.. _event-based-examples: + +--------------------- +Event-based examples +--------------------- + +* :doc:`Overview ` — Event-based programming model and state machines +* :doc:`PingPong ` — Classical ping-pong between two nodes +* :doc:`PolitePingPong ` — State-machine message dispatch pattern +* :doc:`QuantumCorrRNG ` — Quantum correlated RNG with state machine +* :doc:`QuantumCorrRNGVerified ` — Correlated RNG with verification protocol + +.. _native-mode-examples: + +--------------------- +Native mode examples +--------------------- + +* :doc:`Template ` — Template for programming in native (Twisted) mode +* :doc:`CorrRNG ` — Correlated randomness using native mode +* :doc:`Teleport ` — Teleportation using native mode +* :doc:`GraphState ` — Distributing a graph state across four nodes + +.. toctree:: + :hidden: + new-sdk/Overview + new-sdk/Template + new-sdk/CorrRNG + new-sdk/Teleport + new-sdk/ExtendGHZ + new-sdk/MidCircuitLogic + event-based/Overview + event-based/PingPong + event-based/PolitePingPong + event-based/QuantumCorrRNG + event-based/QuantumCorrRNGVerified + native-mode/CorrRNG + native-mode/Template + native-mode/Teleport + native-mode/GraphState diff --git a/docs/GettingStarted.rst b/docs/GettingStarted.rst index acd2f1a4..8d690efa 100644 --- a/docs/GettingStarted.rst +++ b/docs/GettingStarted.rst @@ -5,186 +5,194 @@ Getting started Setup ----- -SimulaQron requires `Python 3 `_ along with the packages *cqc*, *twisted*, *numpy*, *scipy*, *networkx*, *flake8*, *click* and *daemons*. +SimulaQron requires `Python 3.12 `_ along with the packages *netqasm*, *twisted*, *numpy*, *scipy*, +*networkx*, *click* and *daemons*. By following the installation instructions in the following sections, you will install +SimulaQron with all the required packages. ^^^^^^^^^^^^^^^^^^^^^^ Installation using pip ^^^^^^^^^^^^^^^^^^^^^^ -The easiest way to install SimulaQron is using pip (requires MacOS or Linux). Simply type :: +The easiest way to install SimulaQron is using pip. SimulaQron has been tested working in Linux, and WSL (under windows). +For installation on macOS, please use a Linux virtual machine to install SimulaQron. - pip3 install simulaqron - -You can then make use of SimulaQron using the command :code:`simulaqron` in the terminal. For more information on how to use this command see below or type:: - - simulaqron -h - -To make sure you have the version compatible with this documentation type:: - - simulaqron version - -If you want to make sure that everything has been installed properly you can start run the unittests. Open a interactive python console by typing `python3` and the:: - - import simulaqron - simulaqron.tests() +Before proceeding with the installation, you need to install Python 3.12. For Debian-based distributions (like Ubuntu) +you can install the *deadsnakes* repository to gain access to some specific python versions:: -^^^^^^^^^^^^^^^^^^^^^^^^ -Installation from source -^^^^^^^^^^^^^^^^^^^^^^^^ + sudo add-apt-repository -y "ppa:deadsnakes/ppa" -If you want to get the source code, you can clone the git repository. Do:: +After adding the repository, you can install the *full* version of python, including the development package:: - git clone https://github.com/SoftwareQuTech/SimulaQron.git + sudo apt-get install python3.12-full python3.12-dev -You will then -need to set the following environment variable in order to execute the code. Assuming that -you use bash (e.g., standard on OSX or the GIT Bash install on Windows 10), otherwise set the same variables using your favorite shell.:: +Additionally, you will need the `build-essential` package, to install tools used when building some SimulaQron dependencies:: - export PYTHONPATH=yourPath/SimulaQron:$PYTHONPATH + sudo apt-get install build-essential cmake vim linux-headers-generic -where yourPath is the directory containing SimulaQron. You can add this to your ~/.bashrc or ~/.bash_profile file. +To install SimulaQron, start by creating and activating a python virtual environment:: -.. note:: - If you want to use SimulaQron in the same way as when installed using pip you can use an alias by for example + python3.12 -m venv simulaqron-venv + source simulaqron-venv/bin/activate - alias simulaqron=yourPath/SimulaQron/simulaqron/SimulaQron.py +Now, we can install SimulaQron by simply typing:: -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Verifying the installation (if installed from source) -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -To run SimulaQron you need to following Python packages: - -* cqc -* numpy -* scipy -* twisted -* networkx -* flake8 -* click -* daemons -* qutip (optional) -* projectq (optional) + pip3 install simulaqron -To verify that that SimulaQron is working on your computer, type:: +It is also recommended that you also install optional dependencies to enable full support of qubit engines:: - make verify + pip3 install simulaqron\[opt\] -in a terminal at the root of the repository. This command will clear any .pyc files in this directory, check that the needed python packages are installed (and if not install these using pip) and run the automated tests. By default a shorter version of the tests are run. If you wish to run the full tests type :code:`make full_tests`. Not however that the full tests can take quite some time since they perform quantum tomography to tests operations on the qubits. -By default the *stabilizer* engine will be used. +You can then make use of SimulaQron using the command ``simulaqron`` in the terminal. For more information on how +to use this command see below or type:: -.. note:: During the tests you might see quite some error messages. This is to be expected since some tests test that errors are handled correctly when something goes wrong. If the tests pass it will say OK in the end. + simulaqron -h -.. If you wish to run the tests with the *qutip* backend instead, type :code:`make tests_qutip` or :code:`make full_tests_qutip`. If you want to run all tests with all three backends, type :code:`make full_tests_allBackends`. Note that running the full tests with all backends takes a lot of time. +To make sure you have the version compatible with this documentation type:: -.. If :code:`make` does not work for you, you can also run the test by typing :code:`sh tests/runTests.sh --quick` (not including tomography tests) or :code:`sh tests/runTests.sh --full` (full tests). + simulaqron version ------------------------ Testing a simple example ------------------------ -Before delving into how to write any program yourself, let's first simply run one of the existing examples when programming SimulaQron through the Python library (see https://softwarequtech.github.io/CQC-Python/examples.html). -Remember from the Overview that SimulaQron has two parts: the first are the virtual node servers that act simulate the hardware at each node as well as the quantum communication between them in a transparent manner. -The second are the applications themselves which can be written in two ways, the direct way is to use the native mode using the Python Twisted framework connecting to the virtual node servers, see :doc:`Examples`. -The recommended way however is the use the provided Python library that calls the virtual nodes by making use of the classical/quantum combiner interface. -We will here illustrate how to use SimulaQron with the Python library. +Before delving into how to write any program yourself, let's first simply run one of the existing examples. +Remember from the :doc:`Overview` that SimulaQron has two parts: the first are the virtual node servers +that simulate the hardware at each node as well as the quantum communication between them in a transparent manner. +The second are the applications themselves which can be written in two ways: the direct way is to use the native +mode using the Python Twisted framework connecting to the virtual node servers (see :ref:`Native mode examples `), +and the recommended way is to use the NetQASM library that calls the virtual nodes via the NetQASM interface. +We will here illustrate how to use SimulaQron with the NetQASM library. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Starting the SimulaQron backend ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -By default SimulaQron uses the five nodes Alice, Bob, Charlie, David and Eve on your local computers. In this example there will be two processes for each node listening to incoming messages on a certain port number. These make up the simulation backend and the CQC server. To start the processes and thus the backend of SimulaQron simply type:: + +By default SimulaQron uses the five nodes Alice, Bob, Charlie, David and Eve on your local computers. In this example +there will be three processes for each node listening to incoming messages on a certain port number. These make up +the simulation backend, the NetQASM server and the classical communication server. To start the processes and thus +the backend of SimulaQron simply type:: simulaqron start -.. warning:: Running :code:`simulaqron start` will be default start up servers on localhost (i.e., your own computer), using port numbers between 8000 and 9000, to form the simulated quantum internet hardware. SimulaQron does not provide any access control to its simulated hardware, so you are responsible to securing access should this be relevant for you. You can also run the different simulated nodes on different computers. We do not take any responsibility for problems caused by SimulaQron. +.. warning:: Running ``simulaqron start`` will by default start up servers on localhost (i.e., your own computer), + using port numbers between 8000 and 9000, to form the simulated quantum internet hardware. SimulaQron does not + provide any access control to its simulated hardware, so you are responsible to securing access should this be + relevant for you. You can also run the different simulated nodes on different computers. We do not take any + responsibility for problems caused by SimulaQron. -For more information on what :code:`./cli/SimulaQron start` does, how to change the nodes and the ports of the network, the topology etc, see :doc:`ConfNodes`. +For more information on what ``simulaqron start`` does, how to change the nodes and the ports of the network, +the topology etc, see :doc:`Configuring the Network `. To stop the backend, simply type:: simulaqron stop -If something went wrong (for example the process was killed before you stopped it) there might be leftover files which makes SimulaQron think that the network is still running. To reset this you can type:: +If something went wrong (for example the process was killed before you stopped it) there might be leftover files which +makes SimulaQron think that the network is still running. To reset this you can type:: simulaqron reset -Note that this also kills any currently running network and resets any settings or configurations. +Note that this also kills any currently running network and resets any local settings or configurations. ^^^^^^^^^^^^^^^^^^^ Running a protocol ^^^^^^^^^^^^^^^^^^^ -Having started the virtual quantum nodes as above, let us now run a simple test application, which already illustrates some of the aspects in realizing protocols. -Our objective will be to realize the following protocol which will generate 1 shared random bit between Alice and Bob. Evidently, there would be classical means to achieve this trivial task chosen for illustration. +Having started the virtual quantum nodes as above, let us now run a simple test application, which already illustrates +some of the aspects in realizing protocols. Before proceeding, please download the SimulaQron examples as shown in the +:ref:`How to get the examples` section. +Our objective will be to realize the following protocol which will generate 1 shared random bit between Alice and Bob. +Evidently, there would be classical means to achieve this trivial task chosen for illustration. -* Alice and Bob generates one EPR pair, that is, two maximally entangled qubits :math:`A` and :math:`B` of the form :math:`|\Psi\rangle_{AB} = \frac{1}{\sqrt{2}} \left(|0\rangle_A |0\rangle_B + |1\rangle_A |1\rangle_B\right)` +* Alice and Bob generates one EPR pair, that is, two maximally entangled qubits :math:`A` and :math:`B` of the form + :math:`|\Psi\rangle_{AB} = \frac{1}{\sqrt{2}} \left(|0\rangle_A |0\rangle_B + |1\rangle_A |1\rangle_B\right)` * Both Alice and Bob measure their respective qubits to obtain a classical random number :math:`x \in \{0,1\}`. -The examples can be found in the repo `pythonLib `_. -Before seeing how this example works, let us simply run the code:: +We will follow the example located in ``examples/new-sdk/corrRNG`` (see :ref:`New SDK Examples ` for +the full list). Before seeing how this works, let us simply run the code (assuming you're already in the ``SimulaQron`` +folder cloned from GitHub):: - cd examples/pythonLib/corrRNG - sh run.sh + cd examples/new-sdk/corrRNG + bash run.sh You should be seeing the following two lines:: - App Alice: Measurement outcome is: 0/1 - App Bob: Measurement outcome is: 0/1 - -Note that the order of these two lines may differ, as it does not matter who measures first. So what is actually going on here? Let us first look at how we will realize the example by making an additional step (3) explicit: + Alice: My Random Number is '0/1' + Bob: My Random Number is '0/1' -* Alice and Bob generate one EPR pair, that is, two maximally entangled qubits :math:`A` and :math:`B` of the form :math:`|\Psi\rangle_{AB} = \frac{1}{\sqrt{2}} \left(|0\rangle_A |0\rangle_B + |1\rangle_A |1\rangle_B\right)` +Note that the order of these two lines may differ, as it does not matter who measures first. So what is actually +going on here? Let us first look at how we will realize the example by making an additional step (3) explicit: -* Alice and Bob are informed of the identifiers of the qubits and are informed that entanglement was generated. +* Alice and Bob generate one EPR pair, that is, two maximally entangled qubits :math:`A` and :math:`B` of the form + :math:`|\Psi\rangle_{AB} = \frac{1}{\sqrt{2}} \left(|0\rangle_A |0\rangle_B + |1\rangle_A |1\rangle_B\right)` * Both Alice and Bob measure their respective qubits to obtain a classical random number :math:`x \in \{0,1\}`. -While the task we want to realize here is completely trivial, the addition of step 3 does however already highlight a range of choices on how to realize step 3 and the need to find good abstractions to allow easy application development. -One way to realize step 3 would be to hardwire Alices and Bobs measurements: if the hardware can identify the correct qubits from the entanglement generation, then we could instruct it to measure it immediately without asking for a notification from the entanglement generation process. It is clear that in a network that is a bit larger than our tiny three node setup, identifying the right setup requires a link between the underlying qubits and classical control information: this is the objective of the classical/quantum combiner. +While the task we want to realize here is completely trivial, the addition of step 2 does however already highlight a +range of choices on how to realize step 2 and the need to find good abstractions to allow easy application development. +One way to realize step 2 would be to hardwire Alice's and Bob's measurements: if the hardware can identify the +correct qubits from the entanglement generation, then we could instruct it to measure it immediately without asking +for a notification from the entanglement generation process. It is clear that in a network that is a bit larger than +our tiny two node setup, identifying the right setup requires a link between the underlying qubits and classical +control information: this is the objective of the classical/quantum combiner. -The script run.sh executes the following two python scripts:: +The script ``run.sh`` executes the following two python scripts:: - #!/bin/sh + #!/usr/bin/env bash - python3 aliceTest.py - python3 bobTest.py & + # Some code to start SimulaQron backend + + python3 aliceTest.py & + python3 bobTest.py Let us now look at the programs for Alice and Bob. -We first initialize an object of the class ``CQCConnection`` which will do all the communication to the virtual through the CQC interface. -Qubits can then be created by initializing a qubit-object, which takes a ``CQCConnection`` as an input. -On these qubits operations can be applied and they can also be sent to other nodes in the network by use of the ``CQCConnection``. -The full code in aliceTest.py is:: - # Initialize the connection - with CQCConnection("Alice") as Alice: +We first create a ``NetQASMConnection`` which handles all communication with the local quantum backend. +An ``EPRSocket`` is used to create or receive entangled qubit pairs with a remote node. +The key pattern is: queue operations, call ``flush()`` to execute them, then read results with ``int(m)``. + +The core of ``aliceTest.py`` is:: - # Create an EPR pair - q = Alice.createEPR("Bob") + epr_socket = EPRSocket("Bob") - # Measure qubit - m=q.measure() - to_print="App {}: Measurement outcome is: {}".format(Alice.name,m) - print("|"+"-"*(len(to_print)+2)+"|") - print("| "+to_print+" |") - print("|"+"-"*(len(to_print)+2)+"|") + # sim_conn is our connection to the quantum backend (SimulaQron), not to Bob. + sim_conn = NetQASMConnection("Alice", epr_sockets=[epr_socket]) -Similarly the code in bobTest.py read:: + # Create an entangled qubit + epr = epr_socket.create_keep()[0] - # Initialize the connection - with CQCConnection("Bob") as Bob: + # Measure it + m1 = epr.measure() - # Receive qubit - q=Bob.recvEPR() + # flush() executes all queued quantum operations and makes measurement + # results available. Before flush(), m1 is just a future/promise. + sim_conn.flush() - # Measure qubit - m=q.measure() - to_print="App {}: Measurement outcome is: {}".format(Bob.name,m) - print("|"+"-"*(len(to_print)+2)+"|") - print("| "+to_print+" |") - print("|"+"-"*(len(to_print)+2)+"|") + # int(m) extracts the measurement outcome — only valid after flush(). + m1_val = int(m1) + sim_conn.close() -For further examples, see the examples/ folder and for the docs of the Python library see https://softwarequtech.github.io/CQC-Python/index.html. +Similarly the core of ``bobTest.py`` is:: + + epr_socket = EPRSocket("Alice") + + # sim_conn is our connection to the quantum backend (SimulaQron), not to Alice. + sim_conn = NetQASMConnection("Bob", epr_sockets=[epr_socket]) + + # Receive an entangled qubit + epr = epr_socket.recv_keep()[0] + + # Measure it + m1 = epr.measure() + + sim_conn.flush() + m1_val = int(m1) + sim_conn.close() + +For further examples, see :doc:`Examples ` and :doc:`The NetQASM Interface ` for the full SDK reference. + +.. _settings: -------- Settings @@ -198,14 +206,95 @@ To set a setting, for example to use the projectQ backend, type:: simulaqron set backend projectq -Alternatively, you can add a file ``.simulaqron.json`` in your home folder (i.e. ``~``). -For example this file could look like:: +This will create a file named ``simulaqron_settings.json`` in the current folder. This new file contains a full set of +simulaqron configuration, including the setting that was just configured (using the `projectq` backend, in the example). + +It is also possible to manually create this ``simulaqron_settings.json`` file with any text editor:: { - "backend": "projectq", + "sim_backend": "projectq", "log_level": 10 } -which would set the backend to be use ProjectQ and the log-level to be debug (10). Any setting in this file will override the settings set in the CLI. +which would set the backend to use ProjectQ and the log-level to be debug (10). + +It is also possible to create a configuration file that contains all the default configurations:: + + simulaqron set default + +This command will create a file with the following configuration:: + + { + "max_qubits": 20, + "max_registers": 1000, + "conn_retry_time": 0.5, + "conn_max_retries": 10, + "recv_timeout": 100, + "recv_retry_time": 0.1, + "recv_max_retries": 10, + "log_level": 30, + "sim_backend": "qutip", + "noisy_qubits": false, + "max_app_waiting_time": -1.0, + "t1": 1.0 + } + +The section `Settings Fields`_ below provides a description about each one of the configuration options in the file. + +Alternatively, you can place the ``simulaqron_settings.json`` file in the folder ``~/.simulaqron`` (i.e. a folder +named ``.simulaqron`` in your home folder). Doing so will make your settings persist across different projects you +implement using simulaqron. + +.. note:: Settings need to be set before starting the SimulaQron backend. If the backend is already running, stop + it, set the settings and start it again. + +It is also possible to create the default SimulaQron network configuration in the current folder. Check the +:doc:`Configuring the Network ` document to check how to achieve this. + +^^^^^^^^^^^^^^^^^^^ +Settings precedence +^^^^^^^^^^^^^^^^^^^ -.. note:: Settings needs to be set before starting the SimulaQron backend. If the backend is already running, stop it, set the settings and start it again. +Since the simulaqron configuration file can be placed in several places, SimulaQron will follow a priority for +reading the settings: + +* Settings file placed in the current working folder. +* Settings file placed in the ``~/.simulaqron`` folder. +* If none of the above is found, SimulaQron will create a settings file in the ``~/.simulaqron`` folder, then it + will try to load it in that place. + +.. _settings_fields: + +^^^^^^^^^^^^^^^ +Settings Fields +^^^^^^^^^^^^^^^ + +The SimulaQron settings file contains a set of fields to control the configurations of the SimulaQron simulation: + +* ``max_qubits``: Maximum number of qubits to simulate on the Virtual Node. +* ``max_registers``: Maximum number of registers to use in the Virtual Node. +* ``conn_retry_time``: Number of seconds to wait between connection retries. +* ``conn_max_retries``: Maximum number of times to retry a connection before failing the whole execution. +* ``recv_timeout``: Maximum number of milliseconds to wait for the messages when trying to create EPR pairs. +* ``recv_retry_time``: Maximum number of milliseconds to wait between attempts to create EPR pairs. +* ``recv_max_retries``: Maximum number of tries to attempt when creating EPR pairs. +* ``log_level``: The log level to use for SimulaQron. The integer value in this field must match the values exposed + by the python ``logging`` package. For more information about the specific values for each logging level, please + check the `official python documentation for the logging package `_. +* ``sim_backend``: The backend qubit simulation that SimulaQron will use to emulate qubits. Currently, three backends + are supported: "projectq", "qutip" and "stabilizer". +* ``noisy_qubits``: Whether to enable noisy qubits simulation or not. Setting this to ``true`` will randomly apply a + Pauli gate after every operation, emulating noise on the qubit backend. +* ``max_app_waiting_time``: Maximum time (in seconds) to wait before considering the running application as stalled. + A value of ``-1.0`` will disable the stalling waiting time, allowing SimulaQron to wait indefinitely. +* ``t1``: T1 parameter to use when applying noise on the emulated qubits. This value is only used when the + ``noisy_qubits`` option is set to ``true``. + +The default value of all these fields can be seen in the Settings_ section above. + +.. note:: An application can become "stalled" in certain configurations, leaving the application to look "hung". This + leads to a deadlock of the application. SimulaQron will wait for the configured time before considering the + application as "stalled" and kill all the processes. + +.. warning:: Please correctly configure the ``max_app_waiting_time`` to allow your application to wait for any + potential "slow" peers. A low value on this field might lead SimulaQron to killing your application prematurely. diff --git a/docs/Makefile b/docs/Makefile index 72d7e259..8eecf676 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -14,7 +14,6 @@ ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -.PHONY: help help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @@ -46,17 +45,14 @@ help: @echo " coverage to run coverage check of the documentation (if enabled)" @echo " dummy to check syntax errors of document sources" -.PHONY: clean clean: rm -rf $(BUILDDIR)/* -.PHONY: html html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." -.PHONY: docs docs: @make clean @rm -r html @@ -64,38 +60,32 @@ docs: @mkdir html @cp -r _build/html/* html -.PHONY: dirhtml dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." -.PHONY: singlehtml singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." -.PHONY: pickle pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." -.PHONY: json json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." -.PHONY: htmlhelp htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." -.PHONY: qthelp qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @@ -105,7 +95,6 @@ qthelp: @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/SimulaQron.qhc" -.PHONY: applehelp applehelp: $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp @echo @@ -114,7 +103,6 @@ applehelp: "~/Library/Documentation/Help or install it in your application" \ "bundle." -.PHONY: devhelp devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @@ -124,19 +112,16 @@ devhelp: @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/SimulaQron" @echo "# devhelp" -.PHONY: epub epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." -.PHONY: epub3 epub3: $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 @echo @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." -.PHONY: latex latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @@ -144,47 +129,40 @@ latex: @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." -.PHONY: latexpdf latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." -.PHONY: latexpdfja latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." -.PHONY: lualatexpdf lualatexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through lualatex..." $(MAKE) PDFLATEX=lualatex -C $(BUILDDIR)/latex all-pdf @echo "lualatex finished; the PDF files are in $(BUILDDIR)/latex." -.PHONY: xelatexpdf xelatexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through xelatex..." $(MAKE) PDFLATEX=xelatex -C $(BUILDDIR)/latex all-pdf @echo "xelatex finished; the PDF files are in $(BUILDDIR)/latex." -.PHONY: text text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." -.PHONY: man man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." -.PHONY: texinfo texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @@ -192,74 +170,92 @@ texinfo: @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." -.PHONY: info info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." -.PHONY: gettext gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." -.PHONY: changes changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." -.PHONY: linkcheck linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." -.PHONY: doctest doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." -.PHONY: coverage coverage: $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage @echo "Testing of coverage in the sources finished, look at the " \ "results in $(BUILDDIR)/coverage/python.txt." -.PHONY: xml xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." -.PHONY: pseudoxml pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." -.PHONY: dummy dummy: $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy @echo @echo "Build finished. Dummy builder generates no files." +projectq-dep: + # WARNING - To build the docs, we *need* projectq, since sphinx imports all modules fof simulaqron + # This, as a side effect, imports projectq modules, so the doc build *fails* if projectq is not installed. + # Python setuptools 81 removed "dry_run" option when compiling C++ code + # this breaks the build of projectq + # As a hack, we install the bare minimum tools to build projectq, then + # we build and install it (ignoring any build requirement in the projectq + # package spec), and finally we install the rest of the optional requirements + @pip install "setuptools<81" pybind11 + @pip install "git+https://github.com/ProjectQ-Framework/ProjectQ.git@v0.8.0" --no-build-isolation + python-deps: @cat requirements.txt | xargs -n 1 -L 1 pip3 install _add_jekyll: touch build/.nojekyll -build: python-deps html _add_jekyll +build: html _add_jekyll + +install-deps: projectq-dep python-deps + +check-xdg: + @{ \ + set -e ;\ + MIME_APP="$$(xdg-mime query default $$(xdg-mime query filetype ${BUILDDIR}/html/index.html) )"; \ + if [ "" = "$$MIME_APP" ]; then \ + echo "No web browser detected, or this is a headless environment."; \ + echo "If you already have a web browser installed, please run"; \ + echo "xdg-mime default .desktop text/html"; \ + echo 'Where could be "google-chrome", "firefox", etc.'; \ + exit 1; \ + fi; \ + } -open: - @echo "test" +open: check-xdg open ${BUILDDIR}/html/index.html see: html open -.PHONY: python-deps build open see +.PHONY: help clean html docs dirhtml singlehtml pickle json htmlhelp qthelp applehelp devhelp epub epub3 latex +.PHONY: latexpdf latexpdfja lualatexpdf xelatexpdf text man texinfo info gettext changes linkcheck doctest coverage +.PHONY: xml dummy pseudoxml python-deps build open see check-xdg diff --git a/docs/NativeModeCorrRng.rst b/docs/NativeModeCorrRng.rst deleted file mode 100644 index b431d09f..00000000 --- a/docs/NativeModeCorrRng.rst +++ /dev/null @@ -1,139 +0,0 @@ -Generate correlated randomness -============================== - -Having started the virtual quantum nodes, let us now run a simple test application, which already illustrates some of the aspects in realizing protocols. -Our objective will be to realize the following protocol which will generate 1 shared random bit between Alice and Bob. Evidently, there would be classical means to achieve this trivial task chosen for illustration. - -* Alice generates 1 EPR pair, that is, two maximally entangled qubits :math:`A` and :math:`B` of the form :math:`|\Psi\rangle_{AB} = \frac{1}{\sqrt{2}} \left(|0\rangle_A |0\rangle_B + |1\rangle_A |1\rangle_B\right)` - -* She sends qubit :math:`B` to Bob. - -* Both Alice and Bob measure their respective qubits to obtain a classical random number :math:`x \in \{0,1\}`. - -Before seeing how this example works, let us again simply run the code:: - - cd examples/nativeMode/corrRNG - sh doNew.sh - -Next to a considerable about of debugging information, you should be seeing the following two lines:: - - ALICE: My Random Number is 0/1 - BOB: My Random Number is 0/1 - -Note that the order of these two lines may differ, as it does not matter who measures first. So what is actually going on here ? Let us first look at how we will realize the example by making an additional step (3) explicit: - -* Alice generates 1 EPR pair, that is, two maximally entangled qubits :math:`A` and :math:`B` of the form :math:`|\Psi\rangle_{AB} = \frac{1}{\sqrt{2}} \left(|0\rangle_A |0\rangle_B + |1\rangle_A |1\rangle_B\right)` - -* She sends qubit :math:`B` to Bob. - -* Bob is informed of the identifier of the qubit and is informed it has arrived. - -* Both Alice and Bob measure their respective qubits to obtain a classical random number :math:`x \in \{0,1\}`. - -While the task we want to realize here is completely trivial, the addition of step 3 does however already highlight a range of choices on how to realize step 3 and the need to find good abstractions to allow easy application development. -One way to realize step 3 would be to hardwire Bobs measurement: if the hardware can identify the correct qubit from Alice, then we could instruct it to measure it immediately without asking for a notification from Alice. It is clear that in a network that is a bit larger than our tiny three node setup, identifying the right setup requires a link between the underlying qubits and classical control information: this is the objective of the classical/quantum combiner, for which we will provide code in version 0.2 of SimulaQron. - - -This version simply allows a completely barebones access to the virtual nodes without implementing such convenient abstractions in order to allow you to explore such possibilities. To this end, we will here actually implement the following protocol for mere illustration purposes. We emphasize that this would be inefficient on a real quantum network since it requires Bob to store his qubit until Alice's control message arrives, which can be a significant delay causing the qubit to decohere in the meantime. - -* Alice generates 1 EPR pair, that is, two maximally entangled qubits :math:`A` and :math:`B` of the form :math:`|\Psi\rangle_{AB} = \frac{1}{\sqrt{2}} \left(|0\rangle_A |0\rangle_B + |1\rangle_A |1\rangle_B\right)` - -* She sends qubit :math:`B` to Bob. - -* Alice sends Bob the correct identifier of the qubit, and tells him to measure it. - -* Both Alice and Bob measure their respective qubits to obtain a classical random number :math:`x \in \{0,1\}`. - -To realize this, we thus need not only the connection to the virtual quantum node servers, but Alice and Bob themselves need to run a client/server to exchange classical control information. Before looking at the code, we node that the setup of these servers is again determined by a configuration file, namely config/classicalNet.cfg. This file defines which nodes act as servers in the classical communication network listening for control information to execute the protocol. You want to copy this to whatever example you are running. It takes the same format as above, where in our example only Bob will act run a server:: - - # Configuration file for servers on the classical communication network - # - # For each host its informal name, as well as its location in the network must - # be listed. - # - # [name], [hostname], [port number] - # - - Bob, localhost, 8812 - -The first thing that happens if we execute the script doNew.sh is that after some setting up it will call run.sh, executing:: - - #!/bin/sh - - python3 bobTest.py & - python3 aliceTest.py - -Let us now look at the programs for Alice and Bob. Alice will merely run a client on the classical communication network that connects to Bob to be found in aliceTest.py. Using the template (see general Examples section) which establishes the connections to the local virtual nodes, we thus need to provide client code for Alice to implement the protocol above. The function runClientNode will automatically be executed once Alice connected to her local virtual quantum node simulating the underlying hardware, and to Bob's server:: - - ##################################################################################################### - # - # runClientNode - # - # This will be run on the local node if all communication links are set up (to the virtual node - # quantum backend, as well as the nodes in the classical communication network), and the local classical - # communication server is running (if applicable). - # - @inlineCallbacks - def runClientNode(qReg, virtRoot, myName, classicalNet): - """ - Code to execute for the local client node. Called if all connections are established. - - Arguments - qReg quantum register (twisted object supporting remote method calls) - virtRoot virtual quantum ndoe (twisted object supporting remote method calls) - myName name of this node (string) - classicalNet servers in the classical communication network (dictionary of hosts) - """ - - logging.debug("LOCAL %s: Runing client side program.",myName) - - # Create 2 qubits - qA = yield virtRoot.callRemote("new_qubit_inreg",qReg) - qB = yield virtRoot.callRemote("new_qubit_inreg",qReg) - - # Put qubits A and B in a maximally entangled state - yield qA.callRemote("apply_H") - yield qA.callRemote("cnot_onto",qB) - - # Send qubit B to Bob - # Instruct the virtual node to transfer the qubit - remoteNum = yield virtRoot.callRemote("send_qubit",qB, "Bob") - - # Tell Bob the ID of the qubit, and ask him to measure - bob = classicalNet.hostDict["Bob"] - yield bob.root.callRemote("process_qubit", remoteNum) - - # Measure qubit A to obtain a random number - x = yield qA.callRemote("measure") - print("ALICE: My Random Number is ",x,"\n") - - reactor.stop() - - -Let us now look at Bob's server program to be found in bobTest.py. Observe that Alice will call process_qubit above. Not included in the code below are several standard methods that require no change to be used in examples.:: - - ##################################################################################################### - # - # localNode - # - # This will be run if the local node acts as a server on the classical communication network, - # accepting remote method calls from the other nodes. - - class localNode(pb.Root): - - # This can be called by Alice to tell Bob to process the qubit - @inlineCallbacks - def remote_process_qubit(self, virtualNum): - """ - Recover the qubit and measure it to get a random number. - - Arguments - virtualNum number of the virtual qubit corresponding to the EPR pair received - """ - - qB = yield self.virtRoot.callRemote("get_virtual_ref",virtualNum) - - # Measure - x = yield qB.callRemote("measure") - - print("BOB: My Random Number is ", x, "\n") diff --git a/docs/NativeModeGraphState.rst b/docs/NativeModeGraphState.rst deleted file mode 100644 index cb3aa001..00000000 --- a/docs/NativeModeGraphState.rst +++ /dev/null @@ -1,449 +0,0 @@ -Distributing a graph state -========================== - -Here we consider a more complicated example, where we have four parties; Alice, Bob, Charlie and David. -They will distribute a graph state and transform this with local operations and classical communication to make a GHZ-like state. -Finally they measure their qubits in the correct bases to achieved correlated outcomes. -(Note that this is not a efficient way to distribute a GHZ-state but an example illustrating how to use SimulaQron) - ------------- -An overview ------------- - -We will first give the main idea of the protocol by describing the evolution of the graph describing the graph state shared by the parties. -The actual order of the operations performed will be different in the actual implementation and is described below. -For more information on definition of graph states and their transformation under local operations see https://arxiv.org/abs/quant-ph/0602096. - -A graph state described by a star graph is a GHZ-state up to Hadamard operations on the qubits which are the leaves of the graph. -The parties will therefore transform the shared state to make a star graph. -Alice, Bob, Charlie and David generate a graph state :math:`|P_4\rangle`, i.e. a graph state described by a path graph on four vertices, where Alice and David are the ends of the path. -The edge-set of this graph is - -.. math:: \{(A,B),(B,C),(C,D)\} - -where :math:`A`, :math:`B`, :math:`C` and :math:`D` are the vertices corresponding to the parties Alice, Bob, Charlie and David, respectively. -Local Clifford operations are performed at :math:`B`, :math:`C` and :math:`D` which induces a graph operation called a local complementation at :math:`C` and therefore adds the edge :math:`(B,D)` to the graph. -The graph is now a star graph with an additional edge :math:`(C,D)`. -This edge will be removed by the use of an additional qubit :math:`E` generated by David. -David will entangle his qubit :math:`D` with this new qubit and therefore adding the edge :math:`(D,E)` to the graph. -Qubit :math:`E` is then sent to Charlie which also entangles this with his qubit. -The edge-set is then - -.. math:: \{(A,B),(B,C),(C,D),(B,D),(D,E),(C,E)(\} - -Local Cliffords are performed at :math:`C`, :math:`D` and :math:`E` which induces a local complementation at :math:`E` and therefore removes the edge :math:`(C,D)`. -Finally qubit :math:`E` is measured in the standard basis to disconnect is from the rest of the graph state. -Depending on the measurement outcome, corrections are performed at :math:`C` and :math:`D`. - ------------- -The protocol ------------- - -We now describe the operations performed in the protocol, which effectively induces the graph operations described above. -Although the order described here is slightly different the end result is still the same, since local operations commute. - -* Alice performs the following operations. - - #. Alice prepares two qubits in the state :math:`|+\rangle_A |+\rangle_B`. - - #. Alice performs a CPHASE operation between :math:`A` and :math:`B`. - - #. Alice sends :math:`B` to Bob. - - #. Alice measures qubit :math:`A` in the :math:`X`-basis. - -* Bob performs the following operations. - - #. Bob receives qubit :math:`B` from Alice. - - #. Bob prepares a qubit in the state :math:`|+\rangle_C`. - - #. Bob performs a CPHASE operation between :math:`B` and :math:`C`. - - #. Bob performs the operation :math:`\exp(\frac{\mathrm{i}\pi}{4}Z)` on :math:`B` (one of the operations to induced a local complementation at :math:`C`). - - #. Bob sends :math:`C` to Charlie. - - #. Bob measures qubit :math:`B` in the :math:`Z`-basis. - -* Charlie performs the following operations - - #. Charlie receives qubit :math:`C` from Bob - - #. Charlie prepares a qubit in the state :math:`|+\rangle_D` - - #. Charlie performs a CPHASE operation between :math:`C` and :math:`D`. - - #. Charlie performs the operations :math:`\exp(-\frac{\mathrm{i}\pi}{4}X)` and :math:`\exp(\frac{\mathrm{i}\pi}{4}Z)` on :math:`C` and :math:`D`, respectively (two of the operations to induced a local complementation at :math:`C`). - - #. Charlie sends :math:`D` to David. - - #. Charlie receives qubit :math:`E` from David - - #. Charlie performs a CPHASE operation between :math:`E` and :math:`C`. - - #. Charlie performs the operations :math:`\exp(-\frac{\mathrm{i}\pi}{4}X)` and :math:`\exp(\frac{\mathrm{i}\pi}{4}Z)` on :math:`E` and :math:`C`, respectively (two of the operations to induced a local complementation at :math:`E`). - - #. Charlie measures qubit :math:`E` in the :math:`Z`-basis. - - #. Charlie performs a :math:`Z`-operation on :math:`C` if the measurement outcome is :math:`1` and does nothing if it is :math:`0`. - - #. Charlie sends the measurement outcome to David. - - #. Charlie measures qubit :math:`C` in the :math:`X`-basis. - -* David performs the following operations - - #. David receives qubit :math:`D` from Charlie - - #. David prepares a qubit in the state :math:`|+\rangle_E` - - #. David performs a CPHASE operation between :math:`D` and :math:`E` and sends :math:`E` to Bob. - - #. David performs the operation :math:`\exp(\frac{\mathrm{i}\pi}{4}Z)` on :math:`D` (one of the operations to induced a local complementation at :math:`E`). - - #. David receives the measurement outcome from Charlie and performs a :math:`Z`-operation on :math:`D` if this is :math:`1` and nothing if it is :math:`0`. - - #. David measures qubit :math:`D` in the :math:`X`-basis. - ------------ -Setting up ------------ - -We will run everything locally (localhost) using the standard virtualNodes.cfg file found in config that define the virtual quantum nodes run in the background to simulate the quantum hardware:: - - # Network configuration file - # - # For each host its informal name, as well as its location in the network must - # be listed. - # - # [name], [hostname], [port number] - - Alice, localhost, 8801 - Bob, localhost, 8802 - Charlie, localhost, 8803 - David, localhost, 8804 - -As we can see from the proocol above, Alice is the one that initializes the protocol and the others listen. We will therefore run a client at Alice and servers at Bob, Charlie and David. Since we run everything locally, we may thus use for the configuration file classicalNet.cfg:: - - # Network configuration file - # - # For each host its informal name, as well as its location in the network must - # be listed. - # - # [name], [hostname], [port number] - # - - Bob, localhost, 8812 - Charlie, localhost, 8813 - David, localhost, 8814 - -Let us now provide the actual program code for all the parties. - ------------------ -Programming Alice ------------------ - -Since Alice acts as a client, we will only need to fill in runClientNode. This gives:: - - ##################################################################################################### - # - # runClientNode - # - # This will be run on the local node if all communication links are set up (to the virtual node - # quantum backend, as well as the nodes in the classical communication network), and the local classical - # communication server is running (if applicable). - # - @inlineCallbacks - def runClientNode(qReg, virtRoot, myName, classicalNet): - """ - Code to execute for the local client node. Called if all connections are established. - - Arguments - qReg quantum register (twisted object supporting remote method calls) - virtRoot virtual quantum ndoe (twisted object supporting remote method calls) - myName name of this node (string) - classicalNet servers in the classical communication network (dictionary of hosts) - """ - - logging.debug("LOCAL %s: Runing client side program.",myName) - - #Create 2 qubits - qA = yield virtRoot.callRemote("new_qubit_inreg",qReg) - qB = yield virtRoot.callRemote("new_qubit_inreg",qReg) - - #Make 2-qubit graph state - yield qA.callRemote("apply_H") - yield qB.callRemote("apply_H") - yield qA.callRemote("cphase_onto",qB) - - #send qubit B to Bob - #instruct virtual node to transfer qubit - remoteNum = yield virtRoot.callRemote("send_qubit",qB,"Bob") - logging.debug("LOCAL %s: Remote qubit is %d.",myName,remoteNum) - - #Tell number of virtual qubit to Bob and receive measurement outcome parity - bob=classicalNet.hostDict["Bob"] - yield bob.root.callRemote("receive_qubit",remoteNum) - - #Measure qubit (X-basis) - yield qA.callRemote("apply_H") - outcome=yield qA.callRemote("measure") - print("Alice outcome was:", outcome) - - reactor.stop() - ---------------- -Programming Bob ---------------- - -Let us now program the code for Bob. Since he only acts as a server on the classical network, it is enough to edit the localNode portion of the template. Alice calls receive_qubit to convey the identifier of the virtual qubit.:: - - - ##################################################################################################### - # - # localNode - # - # This will be run if the local node acts as a server on the classical communication network, - # accepting remote method calls from the other nodes. - - class localNode(pb.Root): - - def __init__(self, node, classicalNet): - - self.node = node - self.classicalNet = classicalNet - - self.virtRoot = None - self.qReg = None - - def set_virtual_node(self, virtRoot): - self.virtRoot = virtRoot - - def set_virtual_reg(self, qReg): - self.qReg = qReg - - def remote_test(self): - return "Tested!" - - # This can be called by Alice (or other clients on the classical network) to inform Bob - # of an event. - @inlineCallbacks - def remote_receive_qubit(self, virtualNum): - - logging.debug("LOCAL %s: Getting reference to qubit number %d.",self.node.name, virtualNum) - - # Get ref of qubit - qB=yield self.virtRoot.callRemote("get_virtual_ref",virtualNum) - - #Create new qubit - qC=yield self.virtRoot.callRemote("new_qubit_inreg",self.qReg) - - #Expand graph state - yield qC.callRemote("apply_H") - yield qB.callRemote("cphase_onto",qC) - - #Perform part of tau at C - yield qB.callRemote("apply_rotation",[0,0,1],-np.pi/2) - - - #send qubit C to Charlie - #instruct virtual node to transfer qubit - remoteNum = yield self.virtRoot.callRemote("send_qubit",qC,"Charlie") - logging.debug("LOCAL %s: Remote qubit is %d.","Bob",remoteNum) - - #Tell number of virtual qubit to Charlie and receive measurement outcome parity - charlie=self.classicalNet.hostDict["Charlie"] - yield charlie.root.callRemote("receive_qubit",remoteNum,"Bob") - - #Measure qubit (Z-basis) - outcome=yield qB.callRemote("measure") - print("Bob outcome was:", outcome) - -------------------- -Programming Charlie -------------------- - -Let us now program the code for Charlie. Since he only acts as a server on the classical network, it is enough to edit the localNode portion of the template. Both Bob and David calls receive_qubit to convey the identifier of the virtual qubit and depending on the sender Charlie does different things.:: - - - ##################################################################################################### - # - # localNode - # - # This will be run if the local node acts as a server on the classical communication network, - # accepting remote method calls from the other nodes. - - class localNode(pb.Root): - - def __init__(self, node, classicalNet): - - self.node = node - self.classicalNet = classicalNet - - self.virtRoot = None - self.qReg = None - self.qC = None #Maybe not the indented way - - def set_virtual_node(self, virtRoot): - self.virtRoot = virtRoot - - def set_virtual_reg(self, qReg): - self.qReg = qReg - - def remote_test(self): - return "Tested!" - - # This can be called by Alice (or other clients on the classical network) to inform Bob - # of an event. - @inlineCallbacks - def remote_receive_qubit(self, virtualNum,sender): - - if sender=="Bob": - - logging.debug("LOCAL %s: Getting reference to qubit number %d.",self.node.name, virtualNum) - - # Get ref of qubit - self.qC=yield self.virtRoot.callRemote("get_virtual_ref",virtualNum) - qC=self.qC - - #Create new qubit - qD=yield self.virtRoot.callRemote("new_qubit_inreg",self.qReg) - - #Expand graph state - yield qD.callRemote("apply_H") - yield qC.callRemote("cphase_onto",qD) - - #Perform part of tau at C - yield qC.callRemote("apply_rotation",[1,0,0],np.pi/2) - yield qD.callRemote("apply_rotation",[0,0,1],-np.pi/2) - - # tmp=yield self.virtRoot.callRemote("get_register",qC) - # np.save("data_R",tmp[0]) - # np.save("data_I",tmp[1]) - - #send qubit D to David - #instruct virtual node to transfer qubit - remoteNum = yield self.virtRoot.callRemote("send_qubit",qD,"David") - logging.debug("LOCAL %s: Remote qubit is %d.","Charlie",remoteNum) - - #Tell number of virtual qubit to Charlie and receive measurement outcome parity - david=self.classicalNet.hostDict["David"] - yield david.root.callRemote("receive_qubit",remoteNum) - - #Measure qubit (X-basis) - yield qC.callRemote("apply_H") - outcome=yield qC.callRemote("measure") - print("Charlie outcome was:", outcome) - - elif sender=="David": - - logging.debug("LOCAL %s: Getting reference to qubit number %d.",self.node.name, virtualNum) - - # Get ref of qubit - qE=yield self.virtRoot.callRemote("get_virtual_ref",virtualNum) - qC=self.qC - - # Expand graph state - yield qE.callRemote("cphase_onto",qC) - - #Do local part of tau - yield qE.callRemote("apply_rotation",[1,0,0],np.pi/2) - yield qC.callRemote("apply_rotation",[0,0,1],-np.pi/2) - - #Measure extra qubit (Z-basis) - m=yield qE.callRemote("measure") - if m==1: - yield qC.callRemote("apply_Z") - return m - ------------------ -Programming David ------------------ - -Let us now program the code for David. Since he only acts as a server on the classical network, it is enough to edit the localNode portion of the template. Charlie calls receive_qubit to convey the identifier of the virtual qubit.:: - - - ##################################################################################################### - # - # localNode - # - # This will be run if the local node acts as a server on the classical communication network, - # accepting remote method calls from the other nodes. - - class localNode(pb.Root): - - def __init__(self, node, classicalNet): - - self.node = node - self.classicalNet = classicalNet - - self.virtRoot = None - self.qReg = None - - def set_virtual_node(self, virtRoot): - self.virtRoot = virtRoot - - def set_virtual_reg(self, qReg): - self.qReg = qReg - - def remote_test(self): - return "Tested!" - - # This can be called by Alice (or other clients on the classical network) to inform Bob - # of an event. - @inlineCallbacks - def remote_receive_qubit(self, virtualNum): - - logging.debug("LOCAL %s: Getting reference to qubit number %d.",self.node.name, virtualNum) - - # Get ref of qubit - qD=yield self.virtRoot.callRemote("get_virtual_ref",virtualNum) - - #Create new qubit - qE=yield self.virtRoot.callRemote("new_qubit_inreg",self.qReg) - - #Expand graph state - yield qE.callRemote("apply_H") - yield qD.callRemote("cphase_onto",qE) - - #send qubit E to Charlie - #instruct virtual node to transfer qubit - remoteNum = yield self.virtRoot.callRemote("send_qubit",qE,"Charlie") - logging.debug("LOCAL %s: Remote qubit is %d.","David",remoteNum) - - #Tell number of virtual qubit to Charlie and receive meas outcome - charlie=self.classicalNet.hostDict["Charlie"] - m=yield charlie.root.callRemote("receive_qubit",remoteNum,"David") - - logging.debug("LOCAL %s: Got outcome %d.","David",m) - yield qD.callRemote("apply_rotation",[0,0,1],-np.pi/2) - if m==1: - yield qD.callRemote("apply_Z") - - #Measure qubit (X-basis) - # tmp=yield self.virtRoot.callRemote("get_register",qD) - # np.save("data_R",tmp[0]) - # np.save("data_I",tmp[1]) - yield qD.callRemote("apply_H") - outcome=yield qD.callRemote("measure") - print("Davids outcome was:", outcome) - --------- -Starting --------- - -We first start the virtual quantum node backend, by executing:: - - python3 simulaqron/run/startNode.py Alice & - python3 simulaqron/run/startNode.py Bob & - python3 simulaqron/run/startNode.py David & - python3 simulaqron/run/startNode.py Charlie & - -We then start up the programs for the parties themselves. These will connect to the virtual quantum nodes, and execute the quantum commands and classical communication outlined above, in the same directory as we placed classicalNet.cfg:: - - python3 bobTest.py & - python3 charlieTest.py & - python3 davidTest.py & - python3 aliceTest.py - diff --git a/docs/NativeModeTeleport.rst b/docs/NativeModeTeleport.rst deleted file mode 100644 index d9310b9d..00000000 --- a/docs/NativeModeTeleport.rst +++ /dev/null @@ -1,213 +0,0 @@ -Teleporting a Qubit -=================== - -Let's now consider a very simple protocol, in which Alice first generates an EPR pair with Bob, and then teleports a qubit to Bob. To program it in SimulaQron's native mode, we will use the template described in :doc:`NativeModeTemplate`. - ------------- -The protocol ------------- - -For completness, let's briefly recap the protocol: - -* For generating an EPR pair with Bob, Alice proceeds as follows: - - #. Alice prepares two qubits in the state :math:`|0\rangle_A |0\rangle_B` - - #. Alice applies a Hadamard transform to qubit A, giving :math:`|+\rangle_A |0\rangle_B` - - #. Alice applies a CNOT on qubits A and B, where qubit A is the control and B is the target, giving the state :math:`\frac{1}{\sqrt{2}}(|0\rangle_A |0\rangle_B + |1\rangle_A |1\rangle_B)` - - #. Alice sends qubit B to Bob - -* Alice then teleports qubit q1 to Bob using the EPR pair: - - #. Alice applies a CNOT between q1 and qubit A, where q1 is the control and A is the target. - - #. Alice applies a Hadamard to qubit q1. - - #. Alice measures qubits q1 and A to obtain outcomes a and b. - - #. She sends a and b to Bob, who applies the correction unitary :math:`Z^a X^b` - ------------ -Setting up ------------ - -We will run everything locally (localhost) using the standard virtualNodes.cfg file found in conig that define the virtual quantum nodes run in the background to simulate the quantum hardware:: - - # Network configuration file - # - # For each host its informal name, as well as its location in the network must - # be listed. - # - # [name], [hostname], [port number] - - Alice, localhost, 8801 - Bob, localhost, 8802 - -As we can see from the proocol above, only Alice communicates to Bob for which we will run a client at Alice' and a server at Bob's. Since we run everything locally, we may thus use for the configuration file classicalNet.cfg:: - - # Network configuration file - # - # For each host its informal name, as well as its location in the network must - # be listed. - # - # [name], [hostname], [port number] - # - - Bob, localhost, 8812 - -Let us now provide the actual program code for both Alice and Bob. - ------------------ -Programming Alice ------------------ - -Since Alice acts as a client, we will only need to fill in runClientNode. This gives:: - - ##################################################################################################### - # - # runClientNode - # - # This will be run on the local node if all communication links are set up (to the virtual node - # quantum backend, as well as the nodes in the classical communication network), and the local classical - # communication server is running (if applicable). - # - @inlineCallbacks - def runClientNode(qReg, virtRoot, myName, classicalNet): - """ - Code to execute for the local client node. Called if all connections are established. - - Arguments - qReg quantum register (twisted object supporting remote method calls) - virtRoot virtual quantum ndoe (twisted object supporting remote method calls) - myName name of this node (string) - classicalNet servers in the classical communication network (dictionary of hosts) - """ - - logging.debug("LOCAL %s: Runing client side program.",myName) - - # Create 3 qubits - q1 = yield virtRoot.callRemote("new_qubit_inreg",qReg) - qA = yield virtRoot.callRemote("new_qubit_inreg",qReg) - qB = yield virtRoot.callRemote("new_qubit_inreg",qReg) - - # Prepare the first one in the |-> state - yield q1.callRemote("apply_H") - - # For information purposes, let's print the state of that qubit - (R,I) = yield q1.callRemote("get_qubit") - print("Qubit to be teleported is: ", assemble_qubit(R,I)) - - # Put qubits A and B in an EPR state - yield qA.callRemote("apply_H") - yield qA.callRemote("cnot_onto",qB) - - # Send qubit B to Bob - # Instruct the virtual node to transfer the qubit - remoteNum = yield virtRoot.callRemote("send_qubit",qB, "Bob") - logging.debug("LOCAL %s: Remote qubit is %d.",myName, remoteNum) - - # Apply the local teleportation operations - yield q1.callRemote("cnot_onto",qA) - yield q1.callRemote("apply_H") - - a = yield q1.callRemote("measure") - b = yield qA.callRemote("measure") - logging.debug("LOCAL %s: Correction info is a=%d, b=%d.",myName, a, b) - - # Tell Bob the number of the virtual qubit so the can use it locally - bob = classicalNet.hostDict["Bob"] - yield bob.root.callRemote("recover_teleport", a, b, remoteNum) - - reactor.stop() - ---------------- -Programming Bob ---------------- - -Let us now program the code for Bob. Since he only acts as a server on the classical network, it is enough to edit the localNode portion of the template. Alice calls recover_teleport to convey the classical measurement outcomes, as well as the identifier of the virtual qubit. Bob first asks his local virtual quantum node for a reference -for the qubit with that identfier and then processes the relevant quantum instructions to recover the qubit. We print the density matrix of the qubit at the end for illustration.:: - - - ##################################################################################################### - # - # localNode - # - # This will be run if the local node acts as a server on the classical communication network, - # accepting remote method calls from the other nodes. - - class localNode(pb.Root): - - def __init__(self, node, classicalNet): - - self.node = node - self.classicalNet = classicalNet - - self.virtRoot = None - self.qReg = None - - def set_virtual_node(self, virtRoot): - self.virtRoot = virtRoot - - def set_virtual_reg(self, qReg): - self.qReg = qReg - - def remote_test(self): - return "Tested!" - - # This can be called by Alice to tell Bob where to get the qubit and what corrections to apply - @inlineCallbacks - def remote_recover_teleport(self, a, b, virtualNum): - """ - Recover the qubit from teleportation. - - Arguments - a,b received measurement outcomes from Alice - virtualNum number of the virtual qubit corresponding to the EPR pair received - """ - - logging.debug("LOCAL %s: Getting reference to qubit number %d.",self.node.name, virtualNum) - - # Get the reference to Alice's qubit from the local virtual node - eprB = yield self.virtRoot.callRemote("get_virtual_ref",virtualNum) - - # Apply the desired correction info - logging.debug("LOCAL %s: Correction info is a=%d, b=%d.", self.node.name, a, b) - if b == 1: - yield eprB.callRemote("apply_X") - if a == 1: - yield eprB.callRemote("apply_Z") - - # Just print the qubit we received - (realRho, imagRho) = yield eprB.callRemote("get_qubit") - rho = self.assemble_qubit(realRho, imagRho) - - print("Qubit is:", rho) - - def assemble_qubit(self, realM, imagM): - """ - Reconstitute the qubit as a qutip object from its real and imaginary components given as a list. - We need this since Twisted PB does not support sending complex valued object natively. - """ - M = realM - for s in range(len(M)): - for t in range(len(M)): - M[s][t] = realM[s][t] + 1j * imagM[s][t] - - return Qobj(M) - --------- -Starting --------- - -We first start the virtual quantum node backend, by executing:: - - python3 simulaqron/run/startNode.py Alice & - python3 simulaqron/run/startNode.py Bob & - -We then start up the programs for Alice and Bob themselves. These will connect to the virtual quantum nodes, and execute the quantum commands and classical communication outlined above, in the same directory as we placed classicalNet.cfg:: - - python3 bobTest.py & - python3 aliceTest.py - diff --git a/docs/NativeModeTemplate.rst b/docs/NativeModeTemplate.rst deleted file mode 100644 index 57e1c5df..00000000 --- a/docs/NativeModeTemplate.rst +++ /dev/null @@ -1,166 +0,0 @@ -Template for programming in native mode -======================================= - -In examples/nativeMode/template you will find a template that allows you to program relatively easily by filling in the relevant parts of the template. Let us now discuss this template in detail. - -#. The first step in programming your application protocol is to determine how many nodes are involved. For simplicity, let us here assume you only have two, called Alice and Bob. This will typically be obvious from the high level description of the quantum protocol that you are given. - -#. The next, and possibly less obvious step, is to determine how classical information is exchanged in the quantum protocol. That is, who sends classical messages to whom, at what time, and what actions are taken when those messages are received. Based on this, you need to decide which nodes run a server on the classical network, and which nodes may simply be a client program that connects to the servers to deliver messages. Let us here simply assume, Alice only sends information to Bob, who then acts upon the message received. In this case, we would make Alice a client and Bob a server. Note that one node can obviously fullfill both roles. - -#. The template will look for a file called classicalNet.cfg in the local directory to determine who acts as a server and what that nodes address details are. An example, if only Bob acts as a server would be:: - - # Network configuration file - # - # For each host its informal name, as well as its location in the network must - # be listed. - # - # [name], [hostname], [port number] - # - - Bob, localhost, 8812 - -#. The next step is to check that on each network computer that you will run on, the global configuration file starting the virtual quantum nodes is set up correctly. See :doc:`GettingStarted` on how to perform such a configuration and start the local quantum virtual node backends. - -#. Now copy nodeTest.py to a separate file for each node. In our example above where we just have Alice (client only) and Bob (server only), you would copy nodeTest to aliceTest.py and bobTest.py. - ---------------------- -Programming a client ---------------------- - -Let us now see how we would program a protocol in which a node acts as a client. In our example above this would be Alice, requiring you to edit aliceTest.py. The template code already includes everything necessary to connect to the local virtual quantum node backend, so all you have to do is to write that classical client program communicating with Bob (and possibly directing the local quantum hardware simulated by the virtual node to perform certain tasks). Specifically, you would add your code at the indicated place below:: - - - ##################################################################################################### - # - # runClientNode - # - # This will be run on the local node if all communication links are set up (to the virtual node - # quantum backend, as well as the nodes in the classical communication network), and the local classical - # communication server is running (if applicable). - # - #@inlineCallbacks - def runClientNode(qReg, virtRoot, myName, classicalNet): - """ - Code to execute for the local client node. Called if all connections are established. - - Arguments - qReg quantum register (twisted object supporting remote method calls) - virtRoot virtual quantum ndoe (twisted object supporting remote method calls) - myName name of this node (string) - classicalNet servers in the classical communication network (dictionary of hosts) - """ - - logging.debug("LOCAL %s: Runing client side program.",myName) - - # Here the code to execute for Alice acting as a client - # Uncomment @inlineCallbacks above if you use yield statements - - # Stop the server and client - you want to delete this if the nodes acts as a server - reactor.stop() - -That's all. As a simple example, this code would correspond to the protocol where Alice creates a qubit in the :math:`|+\rangle` state and send it to Bob.:: - - - ##################################################################################################### - # - # runClientNode - # - # This will be run on the local node if all communication links are set up (to the virtual node - # quantum backend, as well as the nodes in the classical communication network), and the local classical - # communication server is running (if applicable). - # - @inlineCallbacks - def runClientNode(qReg, virtRoot, myName, classicalNet): - """ - Code to execute for the local client node. Called if all connections are established. - - Arguments - qReg quantum register (twisted object supporting remote method calls) - virtRoot virtual quantum ndoe (twisted object supporting remote method calls) - myName name of this node (string) - classicalNet servers in the classical communication network (dictionary of hosts) - """ - - logging.debug("LOCAL %s: Runing client side program.",myName) - - # Prepare a new qubit - qA = yield virtRoot.callRemote("new_qubit_inreg",qReg) - - # Apply the Hadamard transform - yield qA.callRemote("apply_H") - - # Instruct the virtual node to transfer the qubit to Bob - remoteNum = yield virtRoot.callRemote("send_qubit",qB, "Bob") - - # Tell Bob to process the qubit - bob = classicalNet.hostDict["Bob"] - yield bob.root.callRemote("tell_bob", remoteNum) - - # Stop the server and client - you want to delete this if the nodes acts as a server - reactor.stop() - - - --------------------- -Programming a server --------------------- - -Let us now have a look on how to program a node that acts as a server on the classical network. In our example above this would be Bob, requiring you to edit bobTest.py. The template code already includes everything necessary to connect to the local virtual quantum node backend, so all you have to do is to write that classical server program communicating with Alice (and possibly directing the local quantum hardware simulated by the virtual node to perform certain tasks). Specifically, you would add your code at the indicated place below:: - - - ##################################################################################################### - # - # localNode - # - # This will be run if the local node acts as a server on the classical communication network, - # accepting remote method calls from the other nodes. - - class localNode(pb.Root): - - def __init__(self, node, classicalNet): - - self.node = node - self.classicalNet = classicalNet - - self.virtRoot = None - self.qReg = None - - def set_virtual_node(self, virtRoot): - self.virtRoot = virtRoot - - def set_virtual_reg(self, qReg): - self.qReg = qReg - - def remote_test(self): - return "Tested!" - - # This can be called by Alice (or other clients on the classical network) to inform Bob - # of an event. Your code goes here. - # @inlineCallbacks - def remote_tell_bob(self, someInfo): - - # Uncomment inlineCallbacks if you use yield here - # Also remove the pass statement when executing actual code - pass - -Evidently, it depends on the program what actions Bob would perform precisely. Here, let us just assume Bob receives the qubit and applies Pauli X:: - - # This can be called by Alice to tell Bob where to get the qubit and what corrections to apply - @inlineCallbacks - def remote_tell_bob (self, virtualNum): - """ - Apply X - - Arguments - virtualNum number of the virtual qubit corresponding to the qubit received - """ - - logging.debug("LOCAL %s: Getting reference to qubit number %d.",self.node.name, virtualNum) - - # Get a reference to the qubit from the local virtual quantum node. - qB = yield self.virtRoot.callRemote("get_virtual_ref",virtualNum) - - # Apply Pauli X - yield qB.callRemote("apply_X") - - diff --git a/docs/NetQASM.rst b/docs/NetQASM.rst new file mode 100644 index 00000000..a1baa7ab --- /dev/null +++ b/docs/NetQASM.rst @@ -0,0 +1,208 @@ +The NetQASM interface +===================== + +SimulaQron applications are written using the **NetQASM SDK**. This page describes the core concepts and +programming model. For complete working examples, see :doc:`Examples `. + +------------ +Installation +------------ + +The NetQASM library is included as a dependency of SimulaQron. For the instructions how to install SimulaQron +please check the :doc:`Getting Started` section. + +-------------- +Core concepts +-------------- + +^^^^^^^^^^^^^^^^^ +NetQASMConnection +^^^^^^^^^^^^^^^^^ + +This objects represent your connection to the **local quantum backend** (SimulaQron's virtual quantum node). +This is *not* a connection to another party — it is how your node talks to its local simulated quantum hardware. +All qubit operations are queued through this connection. + +Create it once, use it throughout your program, and close it at the end:: + + from netqasm.sdk.external import NetQASMConnection + + conn = NetQASMConnection("Alice") + # ... queue quantum operations ... + conn.flush() + conn.close() + +If your program uses EPR pairs, pass the EPR sockets at creation time:: + + from netqasm.sdk import EPRSocket + + epr_socket = EPRSocket("Bob") + conn = NetQASMConnection("Alice", epr_sockets=[epr_socket]) + +^^^^^ +Qubit +^^^^^ + +A qubit allocated on the local quantum backend. The qubit gets initialized to the :math:`|0\rangle` state. +Pass the connection so the backend knows where to allocate it:: + + from netqasm.sdk import Qubit + + q = Qubit(conn) + q.H() # Hadamard + q.X() # Pauli X + q.cnot(other) # CNOT with another qubit + m = q.measure() + +Gates are **queued** — nothing executes until you call ``flush()``. + +^^^^^^^^^ +EPRSocket +^^^^^^^^^ + +Used to create or receive entangled qubit pairs with a remote node:: + + from netqasm.sdk import EPRSocket + + # On Alice's side: + epr_socket = EPRSocket("Bob") + epr = epr_socket.create_keep()[0] + + # On Bob's side: + epr_socket = EPRSocket("Alice") + epr = epr_socket.recv_keep()[0] + +^^^^^^^ +flush() +^^^^^^^ + +The **sync point** that executes all queued quantum operations and makes measurement results available. +Before ``flush()``, measurement results are just futures/promises. After ``flush()``, you can read them +with ``int(m)``:: + + m = q.measure() + conn.flush() # execute everything queued so far + result = int(m) # NOW this works + +You can call ``flush()`` multiple times on the same connection. This enables **mid-circuit classical logic** +— measure, read the result, and decide what to do next:: + + m1 = q.measure() + conn.flush() + if int(m1) == 1: + other_qubit.X() # conditional correction + conn.flush() + +See the mid-circuit logic example in :doc:`Examples ` for a full demonstration. + +--------------- +Minimal example +--------------- + +A single-node program that creates a qubit, applies a Hadamard gate, and measures:: + + from netqasm.runtime.settings import set_simulator + set_simulator("simulaqron") + from netqasm.sdk.external import NetQASMConnection + from netqasm.sdk import Qubit + + conn = NetQASMConnection("Alice") + q = Qubit(conn) + q.H() + m = q.measure() + conn.flush() + print("Measurement outcome:", int(m)) + conn.close() + +If you want to run this minimal example, remember to start the SimulaQron backend before executing the code: +``simulaqron start``. + +-------------------- +Two-node EPR example +-------------------- + +Alice and Bob generate an EPR pair and each measure their qubit to get correlated random numbers. + +**Alice** (creates the EPR pair):: + + epr_socket = EPRSocket("Bob") + conn = NetQASMConnection("Alice", epr_sockets=[epr_socket]) + epr = epr_socket.create_keep()[0] + m = epr.measure() + conn.flush() + print("Alice:", int(m)) + conn.close() + +**Bob** (receives the EPR pair):: + + epr_socket = EPRSocket("Alice") + conn = NetQASMConnection("Bob", epr_sockets=[epr_socket]) + epr = epr_socket.recv_keep()[0] + m = epr.measure() + conn.flush() + print("Bob:", int(m)) + conn.close() + +Both sides will print the same random number (0 or 1), demonstrating quantum correlation. A full working example +of this can be found in the ``new-sdk/corrRNG`` example. See the :doc:`Correlated RNG` section for +more details. + +----------------------- +Classical communication +----------------------- + +For exchanging classical messages between nodes (e.g. measurement outcomes for teleportation corrections), +SimulaQron provides ``SimulaQronClassicalClient`` and ``SimulaQronClassicalServer``. + +Your quantum program function receives ``(reader, writer)`` — standard asyncio streams — for sending and +receiving classical messages:: + + from asyncio import StreamReader, StreamWriter + + async def run_alice(reader: StreamReader, writer: StreamWriter): + # Quantum operations + conn = NetQASMConnection("Alice", epr_sockets=[epr_socket]) + m = epr_socket.create_keep()[0].measure() + conn.flush() + + # Send classical message to Bob + writer.write(str(int(m)).encode("utf-8")) + conn.close() + + async def run_bob(reader: StreamReader, writer: StreamWriter): + # Receive classical message from Alice + data = await reader.read(255) + correction = int(data.decode("utf-8")) + + # Use correction in quantum operations + conn = NetQASMConnection("Bob", epr_sockets=[epr_socket]) + epr = epr_socket.recv_keep()[0] + if correction == 1: + epr.X() + conn.flush() + conn.close() + +See the :doc:`Template ` page for how to set up the client and server, and the +:doc:`teleportation example ` for a complete two-node program with classical messaging. + +----------------------- +Configuration +----------------------- + +Each program needs two configuration files in its directory: + +* ``simulaqron_network.json`` — defines the nodes and their socket ports. See :doc:`Configuring the Network ` + for details. +* ``simulaqron_settings.json`` — configures the simulation backend and other settings. See the + Settings section in :doc:`Getting Started `. + +The ``qutip`` backend is used by default and is recommended unless you need Clifford gates (use +``stabilizer`` in that case). + +----------------------- +Further reading +----------------------- + +* :doc:`Examples ` — complete working examples from simple to complex +* :doc:`New SDK Overview ` — detailed SDK concepts and file structure +* `NetQASM library documentation `_ diff --git a/docs/Overview.rst b/docs/Overview.rst index 02889253..741a3a98 100644 --- a/docs/Overview.rst +++ b/docs/Overview.rst @@ -5,25 +5,40 @@ Overview Programming SimulaQron ---------------------- -There are two ways to program SimulaQron to run applications. The first is to use SimulaQron's native interface via `Twisted `_ Perspective Broker. This interface is specific to Python, and not (easily) accessible from other languages. We will call this interface SimulaQron's native interface throughout. While you may play with simple applications this way, the main purpose of this interface is to allow the development of higher protocol layers and abstractions which will ultimately be used to program quantum networks. -In the light of the alternate interface below it may appear inefficient to export an intermediary interface. However, the purpose of SimulaQron is precisely to explore and play with higher layer abstractions on top of any hardware, or its simulated version, SimulaQron. As such it is best to think of SimulaQron as a piece of simulated hardware with its own native interface, which we may first abstract into a higher level command language for programming. Examples of how to program SimulaQron in native mode can be found in :doc:`Examples`. - -The second way to run applications is via a higher level interface bundled with SimulaQron, called the classical-quantum combiner (CQC) interface. This interface is universally accessible from any language. It comes with a C and Python library, where the Python CQC is definitely the best place to get started if you have never programmed SimulaQron before. An evolved version of a C library and interface is targeted to be available on the planned 2020 quantum internet demonstrator connecting several Dutch cities. If you want your applications to later use real quantum hardware more easily instead of SimulaQron, then this is the interface to use. Internally, the CQC included in this package, uses SimulaQron's native interface from above in place of real quantum hardware. Examples of how to program using the CQC can be found in :doc:`CQC`. - -.. note:: The CQC packages and libraries have been moved to seperate repos at `pythonLib `_ and `cLib `_. The python library can be installed using pip by the command :code:`pip3 install cqc`. - -.. image:: figs/CQC_schematic_v3.png - :width: 400px +There are two ways to program SimulaQron to run applications. The first is to use SimulaQron's native interface via +`Twisted `_ Perspective Broker. This interface is specific to Python, and not (easily) +accessible from other languages. We will call this interface SimulaQron's native interface throughout. While you may +play with simple applications this way, the main purpose of this interface is to allow the development of higher +protocol layers and abstractions which will ultimately be used to program quantum networks. + +In the light of the alternate interface below it may appear inefficient to export an intermediary interface. However, +the purpose of SimulaQron is precisely to explore and play with higher layer abstractions on top of any hardware, or +its simulated version, SimulaQron. As such it is best to think of SimulaQron as a piece of simulated hardware with its +own native interface, which we may first abstract into a higher level command language for programming. Examples of +how to program SimulaQron in native mode can be found in :doc:`Examples `. + +The second way to run applications is via a higher level interface, the NetQASM interface. If you want your +applications to later use real quantum hardware more easily instead of SimulaQron, then this is the interface to use. +Examples of how to program using the NetQASM can be found in :doc:`The NetQASM Interface `. + +.. image:: figs/netqasm_architecture.png + :width: 500px :align: center - :alt: Programming SimulaQrons Interfaces + :alt: SimulaQron Programming Interfaces -Practically, SimulaQron's Backend is a server process running on each local classical computer (or on a single classical computer), emulating quantum hardware. The backend can be programmed directly using Twisted PB aka native mode. For clarity, not efficiency, the CQC Backend is a separate server process, which connects to the SimulaQron backend using Twisted PB. It accepts incoming connections, and using a general packet format can be programmed using any programming language. Libraries for C and Python are provided. If you are starting out, programming in the Python CQC library is by far the easiest way to get going! Further information about the Python library can be found in https://softwarequtech.github.io/CQC-Python/index.html and explicit examples using this library in https://softwarequtech.github.io/CQC-Python/examples.html. +Practically, SimulaQron's Backend is a server process running on each local classical computer (or on a single +classical computer), emulating quantum hardware. The backend can be programmed directly using Twisted PB (native mode). +For clarity, not efficiency, the NetQASM Backend is a separate server process, which connects to the SimulaQron backend +using Twisted PB. If you are starting out, programming in the Python NetQASM library is by far the easiest way to get +going! Further information about the Python NetQASM library can be found in https://netqasm.readthedocs.io/en/latest/. ------------------------------- How SimulaQron works internally ------------------------------- -Let us here briefly sketch how the SimulaQron backend works internally. Further details can be found in our `paper `_. +Let us here briefly sketch how the SimulaQron backend works internally. Further details can be found in our +`paper `_. + The simulator consists of two parts and has a relatively modular design: @@ -31,50 +46,66 @@ The simulator consists of two parts and has a relatively modular design: Virtual quantum nodes ^^^^^^^^^^^^^^^^^^^^^ -A virtual quantum node is a server program running on a particular computer that pretends to be quantum hardware, simulating qubits and quantum communication. -That is, you may think of these nodes as fake hardware programmable directly via SimulaQron's native interface. Each node presents -a number of virtual qubits for you to use. These virtual qubits would correspond to the physical qubits -available at this node, would this be a real physical implementation of the quantum network node. By connecting to the virtual quantum node server, a -client program that depends on using quantum hardware, may manipulate these qubits as if they were local physical qubit and also -instruct to send them to remote nodes. -The virtual quantum node servers are identified -by their common names (eg Alice, Bob, Charlie), and amongst themselves connect classically to form a virtual simulation -network as a backend. - -An important internal element in SimulaQron is the distinction between virtual qubits and simulated qubits. Virtual qubits -are the qubits as they would be present in real quantum hardware. A virtual qubit is local to each virtual quantum node server -and may be manipulated there. Each virtual qubit is simulated somewhere, by a simulated qubit. Importantly, this simulated qubit -may be located at a different simulating node than the node holding the corresponding virtual qubit. -To see why this is necessary, note that -entangled qubits cannot be locally represented by any form of classical information (hence -the quantum advantage of entanglement in the first place!). As such, if two (or more) virtual nodes share -qubits which are somehow entangled with each other, then these qubits will actually need to be simulated +A virtual quantum node is a server program running on a particular computer that pretends to be quantum hardware, +simulating qubits and quantum communication. That is, you may think of these nodes as fake hardware programmable +directly via SimulaQron's native interface. Each node presents a number of virtual qubits for you to use. These +virtual qubits would correspond to the physical qubits available at this node, would this be a real physical +implementation of the quantum network node. By connecting to the virtual quantum node server, a client program that +depends on using quantum hardware, may manipulate these qubits as if they were local physical qubit and also instruct +to send them to remote nodes. + +The virtual quantum node servers are identified by their common names (eg Alice, Bob, Charlie), and amongst themselves +connect classically to form a virtual simulation network as a backend. + +An important internal element in SimulaQron is the distinction between virtual qubits and simulated qubits. Virtual +qubits are the qubits as they would be present in real quantum hardware. A virtual qubit is local to each virtual +quantum node server and may be manipulated there. Each virtual qubit is simulated somewhere, by a simulated qubit. +Importantly, this simulated qubit may be located at a different simulating node than the node holding the corresponding +virtual qubit. + +To see why this is necessary, note that entangled qubits cannot be locally represented by any form of classical +information (hence the quantum advantage of entanglement in the first place!). As such, if two (or more) virtual +nodes share qubits which are somehow entangled with each other, then these qubits will actually need to be simulated at just one of these nodes. That is, they appear to be virtually local (as if they were real physical qubits), yet they are actually simulated at just one of the network nodes. As you might imagine, this also means that if we want to perform an entangling gate between two qubits which are virtually local, but actually simulated in two different backend quantum registers, then a merge of these registers is required before the entangling gate can be executed. This is all handled transparently by the backend provided here. For programming network applications using SimulaQron you will not need to -deal with this backend simulation. +deal with this backend simulation. Nevertheless, as a guide to the backend, it consists of three essential components: -* quantumEngine - There are currenlty three different quantumEngines implemented: Using `QuTip `_ and mixed state, using `Project Q `_ and pure states and finally using stabilizer formalism. - This corresponds to one quantum register full of qubits across which gates can be performed. Should you wish to use a different backend, you may wish to add a different engine. +.. _sim_backends: + +* quantumEngine - There are currently three different quantumEngines (simulation backends) implemented: Using + `QuTip `_ and mixed state, using `Project Q `_ and pure states and + finally using stabilizer formalism. + This corresponds to one quantum register full of qubits across which gates can be performed. Should you wish to + use a different backend, you may wish to add a different engine. The three current backends give different runtimes due to how quantum states are stored and manipulated. - In the stabilizer formalism, only Clifford operations can be performed and the simulation is in fact efficient in the number of qubits. - See the figure below for a comparison of runtimes to create a GHZ-state on a number of qubits using the three different backends. + In the stabilizer formalism, only Clifford operations can be performed and the simulation is in fact efficient + in the number of qubits. + See the figure below for a comparison of runtimes to create a GHZ-state on a number of qubits using the three + different backends. .. image:: figs/runtime_qutip_vs_projectq_vs_stabilizer.png :width: 700px :align: center :alt: Runtime of creating a GHZ-state using the three different backends currently implemented in SimulaQron. -* simulatedQubit - for each qubit simulated in that register, there is a simQubit object. This is local to each node. It exports remote method calls. These methods are only called by the virtual node network itself: when a virtual node discovers the qubit is actually simulated remotely, it passes on this call by calling the relevant method on the remote qubit object. +* simulatedQubit - for each qubit simulated in that register, there is a simQubit object. This is local to each node. + It exports remote method calls. These methods are only called by the virtual node network itself: when a virtual + node discovers the qubit is actually simulated remotely, it passes on this call by calling the relevant method on + the remote qubit object. -* virtualQubit - this is the object representing a virtually local qubit. This carries information about the remote simulating qubit. These virtualQubit objects can in turn be accessed by the clients who can access these virtual qubits as if they were real local physical qubits without having to know where they are actually simulated. That is, the client obtains a pointer to the relevant virtual qubit object on which it can perform operations directly. +* virtualQubit - this is the object representing a virtually local qubit. This carries information about the remote + simulating qubit. These virtualQubit objects can in turn be accessed by the clients who can access these virtual + qubits as if they were real local physical qubits without having to know where they are actually simulated. That + is, the client obtains a pointer to the relevant virtual qubit object on which it can perform operations directly. -* virtualNode - this is the local virtual node which accepts requests to get a virtual qubit object, send qubits to other nodes, read out the state of qubits, perform gates etc. +* virtualNode - this is the local virtual node which accepts requests to get a virtual qubit object, send qubits to + other nodes, read out the state of qubits, perform gates etc. * backEnd - starts up the virtual node backend @@ -87,9 +118,10 @@ Nevertheless, as a guide to the backend, it consists of three essential componen The local client engine ^^^^^^^^^^^^^^^^^^^^^^^ -The second part is a framework for writing applications that use the virtually simulated quantum -network. Such an application needs to connect locally to the virtual quantum node server simulating the underlying hardware (for programming -in native mode), or to the CQC interface. It is up to these applications to exchange any classical communication required to execute the protocol. +The second part is a framework for writing applications that use the virtually simulated quantum network. Such an +application needs to connect locally to the virtual quantum node server simulating the underlying hardware (for +programming in native mode), or to the NetQASM interface. It is up to these applications to exchange any classical +communication required to execute the protocol. -------------------------- @@ -100,7 +132,8 @@ Report bugs and contribute Bugs and feature requests ^^^^^^^^^^^^^^^^^^^^^^^^^^ -For bugs, feature requests, suggestions or other general questions please use GitHubs issue tracker in the repository (located under ‘Issues’ on the main page of the repository). +For bugs, feature requests, suggestions or other general questions please use GitHubs issue tracker in the +repository (located under ‘Issues’ on the main page of the repository). Please start your message with specifying one of the four labels below for easier handling of issues, for example:: Type: bug @@ -109,13 +142,17 @@ Please start your message with specifying one of the four labels below for easie Always provide enough information to assess the issue and separate different issues into different messages. -* *bug*: This is for bugs that are encountered. Please provide a way to reproduce the bug, preferably with a minimal example, and explain what goes wrong. +* *bug*: This is for bugs that are encountered. Please provide a way to reproduce the bug, preferably with a minimal + example, and explain what goes wrong. -* *feature request*: Is there a feature that you think should be provided? Explain the details of the feature and why you think this should be implemented. +* *feature request*: Is there a feature that you think should be provided? Explain the details of the feature and why + you think this should be implemented. -* *help wanted*: If there is something you having trouble with but which is not necessarily a bug. Also use this for other general questions. +* *help wanted*: If there is something you having trouble with but which is not necessarily a bug. Also use this for + other general questions. -* *suggestions*: If you have suggestions on how to improve the features already existing. Be clear on what exactly you think should be improved and explain why. +* *suggestions*: If you have suggestions on how to improve the features already existing. Be clear on what exactly + you think should be improved and explain why. ^^^^^^^^^^ Contribute @@ -123,9 +160,9 @@ Contribute If you would like to contribute with your own code to fix a bug or add an additional feature, this is most welcomed. Please then make a pull request on GitHub, which will be reviewed before approval. -For contributing use the *Develop*-branch. -Please make sure you run the automated tests below before submitting any code. -The easiest way to proceed is to: + +For contributing use the *Develop*-branch. Please make sure you run the automated tests below before submitting +any code. The easiest way to proceed is to: #. Fork the repository at the *Develop*-branch. #. Make the changes and commit these to your forked branch. @@ -135,8 +172,9 @@ The easiest way to proceed is to: Automated tests ^^^^^^^^^^^^^^^ -There are number of automated tests that test many (but not all) of the features of SimulaQron and the CQC interface. -See :doc:`GettingStarted` for how to run these. +There are number of automated tests that test many (but not all) of the features of SimulaQron and the NetQASM interface. +To run these tests, you need to clone the SimulaQron `GitHub repository `_, +and follow the ``README.md`` file to install and run the tests. Some of the automated tests use quantum tomography and are thus inherently probabilistic. Therefore if you see that one of these fails, you can try to run the test again and see if it is consistent. If the tests are to slow on your computer you can also run the short version, which skips the quantum tomography tests. diff --git a/docs/README.md b/docs/README.md index cc69b89c..6d1107d1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,15 +1,19 @@ How to build the docs ===================== -First build the docs by: +First, install some dependencies needed to build the docs: ```bash -make build +make install-deps ``` -This will first install any required dependencies and build the html files. (the next time you can simply do `make html`). +Then, you can build the docs by: + +```bash +make build +``` -To open the built docs, do: +In system with a desktop GUI (i.e. non-headless systems), you can open the built docs with the command: ```bash make open diff --git a/docs/conf.py b/docs/conf.py index 1d6779fc..eb5d62b5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,8 +17,7 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # -import os -# import sys +import importlib.metadata as metadata # -- General configuration ------------------------------------------------ @@ -54,24 +53,13 @@ # General information about the project. project = "SimulaQron" -copyright = "2017, Stephanie Wehner and Axel Dahlberg" -author = "Stephanie Wehner and Axel Dahlberg" - -# Get the version from simulaqron __init__ -path_to_here = os.path.dirname(os.path.abspath(__file__)) -simulaqron_init = os.path.join(path_to_here, "../simulaqron", "__init__.py") - -with open(simulaqron_init, 'r') as f: - for line in f: - line = line.strip() - if line.startswith("__version__"): - _version = line.split("__version__ = ")[1] - _version = _version.split(' ')[0] - _version = eval(_version) - _short_version = '.'.join(_version.split('.')[:-1]) - break - else: - raise RuntimeError("Could not find the version!") +copyright = "2026, Stephanie Wehner, Axel Dahlberg and Diego Rivera" +author = "Stephanie Wehner, Axel Dahlberg and Diego Rivera" + +# Get the version from simulaqron +_base_version_line = metadata.version('simulaqron') +_version = _base_version_line +_short_version = '.'.join(_base_version_line.split('.')[:-1]) # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -193,3 +181,6 @@ # A list of files that should not be packed into the epub file. epub_exclude_files = ["search.html"] + +# Also generate sphinx docs for constructors +autoclass_content = "both" diff --git a/docs/event-based/Overview.rst b/docs/event-based/Overview.rst new file mode 100644 index 00000000..776b04f0 --- /dev/null +++ b/docs/event-based/Overview.rst @@ -0,0 +1,78 @@ +Event-Based Programming Overview +================================ + +The event-based examples show how to structure quantum network protocols using +a **state machine** pattern. This is the recommended approach for protocols that +interleave classical negotiation with quantum operations. + +Prerequisites: you should understand the :doc:`New SDK Overview <../new-sdk/Overview>` first. + +The state machine pattern +------------------------- + +Each node's behaviour is defined by: + +1. **States** — string constants naming each phase of the protocol +2. **Handlers** — async functions that execute when a specific message arrives in + a specific state. Each handler performs an action and returns the next state. +3. **Dispatch table** — a dictionary mapping ``(current_state, message)`` to handler. + This *is* the state machine. +4. **Event loop** — reads messages, looks up the transition, calls the handler. + +Example structure:: + + # States + STATE_WAITING_ACCEPT = "WAITING_ACCEPT" + STATE_DONE = "DONE" + + # Handler + async def handle_yes(writer: StreamWriter) -> str: + # ... do something ... + return STATE_DONE + + # Dispatch table + DISPATCH = { + (STATE_WAITING_ACCEPT, "yes"): handle_yes, + } + + # Event loop + async def run_alice(reader, writer): + state = STATE_WAITING_ACCEPT + while state != STATE_DONE: + data = await reader.read(255) + msg = data.decode("utf-8") + handler = DISPATCH.get((state, msg)) + if handler is None: + continue # ignore unknown messages + state = await handler(writer) + +Why this pattern? +----------------- + +- **Clear protocol structure**: the dispatch table maps directly to a state + transition diagram, making the protocol easy to read and verify. +- **Extensible**: adding a new state or message is just adding a handler and + a dispatch entry — no changes to the event loop. +- **Quantum integration**: handlers can create ``NetQASMConnection``, perform + quantum operations, and ``flush()`` — keeping quantum code isolated in + focused handler functions. + +Examples +-------- + +The examples progress from purely classical to quantum: + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - Example + - What it teaches + * - :doc:`PingPong ` + - Basic event loop with simple if/else message handling + * - :doc:`PolitePingPong ` + - Full state machine pattern with dispatch table + * - :doc:`QuantumCorrRNG ` + - Adding quantum operations (EPR + measure) to event handlers + * - :doc:`QuantumCorrRNGVerified ` + - Multi-state protocol: negotiate, quantum, then classical verification diff --git a/docs/event-based/PingPong.rst b/docs/event-based/PingPong.rst new file mode 100644 index 00000000..ea395b5d --- /dev/null +++ b/docs/event-based/PingPong.rst @@ -0,0 +1,76 @@ +Ping-Pong: Basic Event Loop +=========================== + +The simplest event-based example. Alice sends a sequence of messages to Bob, +who replies to each one. No state machine — just a simple if/else to choose +the reply. Found in ``examples/event-based/pingPong/``. + +This is purely classical (no quantum operations). It demonstrates the basic +async event loop pattern before adding state machines and quantum in later examples. + +Alice (client) +-------------- + +Alice sends a list of messages and prints Bob's replies:: + + async def run_alice(reader: StreamReader, writer: StreamWriter): + messages = ["ping", "ping", "hello", "ping"] + + for msg in messages: + print(f"Alice: sending '{msg}'") + writer.write(msg.encode("utf-8")) + await writer.drain() + + reply_data = await reader.read(255) + reply = reply_data.decode("utf-8") + print(f"Alice: received '{reply}'") + +Bob (server) +------------ + +Bob replies "pong" to "ping" and "no way!" to anything else:: + + async def run_bob(reader: StreamReader, writer: StreamWriter): + while True: + data = await reader.read(255) + if not data: + break # Alice disconnected + + message = data.decode("utf-8") + if message == "ping": + reply = "pong" + else: + reply = "no way!" + + writer.write(reply.encode("utf-8")) + await writer.drain() + +Key concepts +------------ + +- **reader/writer**: The async streams for classical TCP communication. + ``writer.write()`` sends bytes, ``await reader.read(N)`` receives up to N bytes. +- **await writer.drain()**: Ensures the write buffer is flushed to the network. +- **Connection lifecycle**: Bob's loop exits when ``reader.read()`` returns empty + bytes, meaning Alice has disconnected. + +Running +------- + +:: + + cd examples/event-based/pingPong + bash run.sh + +Expected output:: + + Alice: sending 'ping' + Bob: received 'ping' + Bob: sending 'pong' + Alice: received 'pong' + Alice: sending 'ping' + ... + Alice: sending 'hello' + Bob: received 'hello' + Bob: sending 'no way!' + Alice: received 'no way!' diff --git a/docs/event-based/PolitePingPong.rst b/docs/event-based/PolitePingPong.rst new file mode 100644 index 00000000..e573244e --- /dev/null +++ b/docs/event-based/PolitePingPong.rst @@ -0,0 +1,94 @@ +Polite Ping-Pong: The State Machine Pattern +============================================ + +Extends the basic ping-pong into a proper state machine with dispatch tables. +Alice and Bob have a polite exchange: ping, pong, thank you, you're welcome. +Found in ``examples/event-based/politePingPong/``. + +This is still purely classical, but introduces the **state machine pattern** +that all subsequent quantum examples will use. + +Alice's state diagram +--------------------- + +:: + + (connect) + │ send "ping" + ▼ + WAITING_PONG + │ recv "pong" → send "thank you" + ▼ + WAITING_YOURE_WELCOME + │ recv "you're welcome" + ▼ + DONE + +Alice's dispatch table +---------------------- + +:: + + ALICE_DISPATCH = { + (STATE_WAITING_PONG, "pong"): handle_pong, + (STATE_WAITING_YOURE_WELCOME, "you're welcome"): handle_youre_welcome, + } + +Each handler is a focused function that performs one action and returns the next state:: + + async def handle_pong(writer: StreamWriter) -> str: + reply = "thank you" + writer.write(reply.encode("utf-8")) + await writer.drain() + return STATE_WAITING_YOURE_WELCOME + +The event loop +-------------- + +The event loop is generic — it works for any protocol by just changing the dispatch +table and initial state:: + + async def run_alice(reader: StreamReader, writer: StreamWriter): + # Initial action (before the loop) + writer.write(b"ping") + await writer.drain() + + state = STATE_WAITING_PONG + + while state != STATE_DONE: + data = await reader.read(255) + if not data: + break + msg = data.decode("utf-8") + + handler = ALICE_DISPATCH.get((state, msg)) + if handler is None: + print(f"no transition for '{msg}' — ignoring.") + continue + + state = await handler(writer) + +Key concepts +------------ + +- **Dispatch table = state machine**: the ``(state, message) → handler`` mapping + *is* the protocol definition. Every valid transition is listed; anything not + listed is automatically rejected. +- **Handlers are independent**: each handler only knows about its own transition, + making the code modular and easy to extend. +- **The event loop is reusable**: the same loop structure works for every example + that follows. + +Bob's side +---------- + +Bob uses the same pattern with his own states (``WAITING_PING``, ``WAITING_THANKS``, +``DONE``) and dispatch table. See ``politeBob.py`` for the full code. + +Running +------- + +:: + + cd examples/event-based/politePingPong + bash run.sh diff --git a/docs/event-based/QuantumCorrRNG.rst b/docs/event-based/QuantumCorrRNG.rst new file mode 100644 index 00000000..87bff9db --- /dev/null +++ b/docs/event-based/QuantumCorrRNG.rst @@ -0,0 +1,104 @@ +Quantum Correlated RNG: Adding Quantum to Event Handlers +========================================================= + +This example combines the state machine pattern with quantum operations. +Alice proposes generating shared randomness; if Bob agrees, both create an EPR +pair and measure their half. Found in ``examples/event-based/quantumCorrRNG/``. + +This is the first example that runs quantum operations inside an event handler. + +The protocol +------------ + +1. Alice sends "generate randomness?" to Bob. +2. Bob replies "yes". +3. Both create an EPR pair and measure their half. +4. Both get the same correlated random bit. + +Alice's state diagram +--------------------- + +:: + + (connect) + │ send "generate randomness?" + ▼ + WAITING_ACCEPT + │ recv "yes" → EPR create + measure + ▼ + DONE + +Quantum inside a handler +------------------------- + +The key insight: quantum operations happen inside a handler function, using the +same ``sim_conn`` + ``flush()`` pattern from the new-sdk examples:: + + async def handle_yes(writer: StreamWriter) -> str: + epr_socket = EPRSocket("Bob") + + # Open a connection to the quantum backend (not to Bob — that's the + # classical reader/writer). + sim_conn = NetQASMConnection("Alice", epr_sockets=[epr_socket]) + + # Create an EPR pair with Bob and keep our half + epr = epr_socket.create_keep()[0] + m = epr.measure() + + # flush() sends all queued quantum operations to the backend and waits + # for them to complete. After this, int(m) gives the real value. + sim_conn.flush() + + result = int(m) + sim_conn.close() + + print(f"Alice: my random bit is {result}") + return STATE_DONE + +Bob's handler is symmetric, using ``recv_keep()`` instead of ``create_keep()``. + +Bob's handler +------------- + +:: + + async def handle_generate(writer: StreamWriter) -> str: + # Classical: agree + writer.write("yes".encode("utf-8")) + await writer.drain() + + # Quantum: receive our half of the EPR pair + epr_socket = EPRSocket("Alice") + sim_conn = NetQASMConnection("Bob", epr_sockets=[epr_socket]) + + epr = epr_socket.recv_keep()[0] + m = epr.measure() + sim_conn.flush() + + result = int(m) + sim_conn.close() + + print(f"Bob: my random bit is {result}") + return STATE_DONE + +Key concepts +------------ + +- **Quantum in handlers**: The handler creates a ``NetQASMConnection``, performs + quantum operations, calls ``flush()``, reads results, and closes — all within + one state transition. +- **Classical before quantum**: Bob first sends "yes" (classical), *then* does the + quantum operation. The state machine cleanly separates negotiation from execution. +- **sim_conn vs reader/writer**: ``sim_conn`` talks to the local quantum backend. + ``reader``/``writer`` talk to the other node classically. These are independent. + +Running +------- + +:: + + cd examples/event-based/quantumCorrRNG + bash run.sh + +.. note:: Unlike the purely classical examples, this one requires the SimulaQron + backend to be running. The ``run.sh`` script starts it automatically. diff --git a/docs/event-based/QuantumCorrRNGVerified.rst b/docs/event-based/QuantumCorrRNGVerified.rst new file mode 100644 index 00000000..a1df611c --- /dev/null +++ b/docs/event-based/QuantumCorrRNGVerified.rst @@ -0,0 +1,129 @@ +Quantum Correlated RNG with Verification +========================================= + +Extends the previous example by adding a classical verification step after the +quantum measurement. After both nodes measure their EPR halves, Alice sends her +result to Bob so he can confirm the correlation. +Found in ``examples/event-based/quantumCorrRNGVerified/``. + +This demonstrates the full cycle: +**classical negotiation → quantum operation → classical verification** + +The protocol +------------ + +1. Alice sends "generate randomness?" to Bob. +2. Bob replies "yes", then both create an EPR pair and measure. +3. Alice sends her measurement result to Bob. +4. Bob compares both results and sends back a verification message. + +Alice's state diagram +--------------------- + +:: + + (connect) + │ send "generate randomness?" + ▼ + WAITING_ACCEPT + │ recv "yes" → EPR create + measure, send result to Bob + ▼ + WAITING_VERIFICATION + │ recv "verified: ..." + ▼ + DONE + +Bob's state diagram +------------------- + +:: + + WAITING_PROPOSAL + │ recv "generate randomness?" → send "yes", EPR recv + measure + ▼ + WAITING_ALICE_RESULT + │ recv Alice's bit → compare, send verdict + ▼ + DONE + +Multi-state quantum protocol +----------------------------- + +The key difference from the simple version: Bob now has **two states** after the +quantum operation. His handler stores the measurement result, and a second handler +processes Alice's result:: + + bob_result = None # module-level storage + + async def handle_generate(writer: StreamWriter) -> str: + global bob_result + + # Classical: agree + writer.write("yes".encode("utf-8")) + await writer.drain() + + # Quantum: receive EPR pair and measure + epr_socket = EPRSocket("Alice") + sim_conn = NetQASMConnection("Bob", epr_sockets=[epr_socket]) + epr = epr_socket.recv_keep()[0] + m = epr.measure() + sim_conn.flush() + + bob_result = int(m) + sim_conn.close() + return STATE_WAITING_ALICE_RESULT + + async def handle_alice_result(writer: StreamWriter, alice_result: int) -> str: + match = alice_result == bob_result + verdict = f"verified: Alice={alice_result}, Bob={bob_result}, match={match}" + writer.write(verdict.encode("utf-8")) + await writer.drain() + return STATE_DONE + +Handling variable messages +-------------------------- + +In ``WAITING_ALICE_RESULT``, the message content is Alice's measurement bit — it +could be "0" or "1". Since the exact message is not known ahead of time, Bob's +event loop handles this state specially rather than through the dispatch table:: + + while state != STATE_DONE: + data = await reader.read(255) + msg = data.decode("utf-8") + + # Special handling: in WAITING_ALICE_RESULT, the message IS the data + if state == STATE_WAITING_ALICE_RESULT: + alice_bit = int(msg) + state = await handle_alice_result(writer, alice_bit) + continue + + # Normal dispatch for other states + handler = BOB_DISPATCH.get((state, msg)) + ... + +Key concepts +------------ + +- **Multi-phase protocols**: The state machine naturally handles protocols with + multiple phases (negotiate → quantum → verify). +- **Data-carrying messages**: Not all messages are fixed strings. When a state + expects variable data (like a measurement result), handle it directly in the + event loop rather than through the dispatch table. +- **Cross-handler state**: Use module-level variables (like ``bob_result``) to + pass data between handlers that execute in different states. + +Running +------- + +:: + + cd examples/event-based/quantumCorrRNGVerified + bash run.sh + +Expected output:: + + Alice: sending 'generate randomness?' + Bob: sending 'yes' + Alice: my random bit is 0 + Bob: my random bit is 0 + Bob: verified: Alice=0, Bob=0, match=True diff --git a/docs/figs/netqasm_architecture.png b/docs/figs/netqasm_architecture.png new file mode 100644 index 00000000..a4df3935 Binary files /dev/null and b/docs/figs/netqasm_architecture.png differ diff --git a/docs/index.rst b/docs/index.rst index 1f2851a4..61ca5889 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,47 +1,111 @@ -.. SimulaQron documentation master file, created by - sphinx-quickstart on Fri May 26 13:25:00 2017. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - SimulaQron Documentation ======================== Welcome to the Quantum Internet simulator SimulaQron! -SimulaQron is a distributed simulation of the end nodes in a future quantum internet with the specific goal to explore application development. -The end nodes in a quantum internet are few qubit processors, which may exchange qubits using -a quantum internet. -Specifically, SimulaQron -allows the installation of a local simulation program on each computer in the network that provides the illusion of having a local quantum processor to potential applications. -The local simulation programs on each classical computer connect to each other classically, forming a simulated quantum internet allowing the exchange of simulated qubits between the different -network nodes, as well as the creation of simulated entanglement. +SimulaQron is a distributed simulation of the end nodes in a future quantum internet with the specific goal to explore +application development. Each node in the simulated network provides the illusion of having a local quantum processor +to potential applications, while the nodes connect classically to allow the exchange of simulated qubits and the +creation of simulated entanglement. + +Key features +------------ + +* **Distributed quantum internet simulation** — install a local simulation program on each computer, or run all nodes + on a single machine +* **Three simulation backends** — stabilizer formalism (efficient), `QuTip `_ (default, mixed + state), and `ProjectQ `_ (pure state) +* **Two programming interfaces** — the NetQASM SDK (recommended) and a native Twisted mode for low-level access +* **Configurable network topologies** — complete, ring, path, random tree, or custom topologies +* **Classical communication** — built-in client/server framework for exchanging classical messages between nodes + +Quick start +----------- + +#. **Install dependencies**:: + + sudo add-apt-repository -y "ppa:deadsnakes/ppa" + sudo apt-get install python3.12-full python3.12-dev + sudo apt-get install build-essential cmake linux-headers-generic + +#. **Create a python virtual environment**:: + + python3.12 -m venv simulaqron-venv + +#. **Activate the virtual environment**:: + + source simulaqron-venv/bin/activate + +#. **Install SimulaQron**:: + + pip install simulaqron + +#. **Configure SimulaQron** — use the ``simulaqron`` command line tool to create a default ``simulaqron_settings.json`` file:: + + simulaqron set default + +#. **Configure your network** — use the ``simulaqron`` command line tool to create a default ``simulaqron_network.json`` file, which contains 5 nodes:: -SimulaQron is written in `Python `_ and uses the `Twisted `_ Perspective Broker. -To perform the local qubit simulation, three different backends have so far been implemented: Using `QuTip `_ and mixed state, using `Project Q `_ and pure states and finally using stabilizer formalism. -However, any other quantum simulator with a python interface can easily be used as a local backend. -The main challenge of SimulaQron is to allow the simulation of virtual qubits at different network nodes: since these may be entangled they cannot be simulated on one network node, which is solved by a transparent distributed simulation on top of in principle any local simulation engine. + simulaqron nodes default -We also have a `paper `_ that describe the design of SimulaQron, which is also freely available on `arxiv `_. +#. **Start the simulaqron backend**:: -The documentation below assumes familiarity with classical network programming concepts, Python, Twisted, as well as an elementary understanding of quantum information. More information on a competition at `Our website `_ + simulaqron start -SimulaQron can be installed from pip by the command :code:`pip3 install simulaqron` on MacOS and Linux. +#. **Write your first program** using the NetQASM SDK. Save this code as ``program.py``:: + + from netqasm.runtime.settings import set_simulator + set_simulator("simulaqron") + from netqasm.sdk.external import NetQASMConnection + from netqasm.sdk import Qubit + + conn = NetQASMConnection("Alice") + q = Qubit(conn) + q.H() + m = q.measure() + conn.flush() # execute queued operations + print(f"Qubit measurement: {int(m)}") # read measurement result + conn.close() + +#. **Execute your program**:: + + python program.py + +#. **Check the output**. Output should be ``Qubit measurement: 0/1``. Measurement should randomly be ``0`` or ``1``. + +#. **Stop simulaqron backend**. Before running another application, stop the current running backend:: + + pip install simulaqron + +#. **Run other more complex examples** — see :doc:`Examples ` for complete working programs. + +Where to go next +---------------- + +* **New to SimulaQron?** Start with :doc:`Getting Started ` for installation and your first example +* **Want to write programs?** See :doc:`The NetQASM Interface ` for the NetQASM SDK reference +* **Looking for examples?** See :doc:`Examples ` — new SDK, event-based, and native-mode examples +* **Configuring networks and settings?** See :ref:`Configuring the Network ` and :ref:`Settings `. +* **Architecture and internals?** See :doc:`Overview ` + +We also have a `paper `_ describing the design of +SimulaQron, freely available on `arxiv `_. .. toctree:: :maxdepth: 2 :caption: Contents: - Overview GettingStarted - ConfNodes - CQC + NetQASM Examples + ConfNodes + Overview simulaqron Indices and tables ================== -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` +* Index +* Module Index +* Search diff --git a/docs/native-mode/CorrRNG.rst b/docs/native-mode/CorrRNG.rst new file mode 100644 index 00000000..1936196c --- /dev/null +++ b/docs/native-mode/CorrRNG.rst @@ -0,0 +1,190 @@ +Generate correlated randomness +============================== + +.. note:: Native mode is the low-level Twisted interface. For new projects, the NetQASM SDK is recommended. + See :doc:`New SDK Overview <../new-sdk/Overview>` and the :doc:`new SDK version of this example <../new-sdk/CorrRNG>`. + +Having started the virtual quantum nodes, let us now run a simple test application, which already illustrates some of +the aspects in realizing protocols. Our objective will be to realize the following protocol which will generate 1 +shared random bit between Alice and Bob. Evidently, there would be classical means to achieve this trivial task chosen +for illustration. + +* Alice generates 1 EPR pair, that is, two maximally entangled qubits :math:`A` and :math:`B` of the form + :math:`|\Psi\rangle_{AB} = \frac{1}{\sqrt{2}} \left(|0\rangle_A |0\rangle_B + |1\rangle_A |1\rangle_B\right)` +* She sends qubit :math:`B` to Bob. +* Both Alice and Bob measure their respective qubits to obtain a classical random number :math:`x \in \{0,1\}`. + +Before seeing how this example works, let us again simply run the code:: + + cd examples/native-mode/corrRNG + bash doNew.sh + +Next to a considerable about of debugging information, you should be seeing the following two lines:: + + ALICE: My Random Number is 0/1 + BOB: My Random Number is 0/1 + +Note that the order of these two lines may differ, as it does not matter who measures first. So what is actually +going on here ? Let us first look at how we will realize the example by making an additional step (3) explicit: + +* Alice generates 1 EPR pair, that is, two maximally entangled qubits :math:`A` and :math:`B` of the form + :math:`|\Psi\rangle_{AB} = \frac{1}{\sqrt{2}} \left(|0\rangle_A |0\rangle_B + |1\rangle_A |1\rangle_B\right)` +* She sends qubit :math:`B` to Bob. +* Bob is informed of the identifier if the qubit and is informed it has arrived. +* Both Alice and Bob measure their respective qubits to obtain a classical random number :math:`x \in \{0,1\}`. + +While the task we want to realize here is completely trivial, the addition of step 3 does however already highlight a +range of choices on how to realize step 3 and the need to find good abstractions to allow easy application development. +One way to realize step 3 would be to hardwire Bobs measurement: if the hardware can identify the correct qubit from +Alice, then we could instruct it to measure it immediately without asking for a notification from Alice. + +This version simply allows a completely barebones access to the virtual nodes without implementing such convenient +abstractions in order to allow you to explore such possibilities. To this end, we will here actually implement the +following protocol for mere illustration purposes. We emphasize that this would be inefficient on a real quantum +network since it requires Bob to store his qubit until Alice's control message arrives, which can be a significant +delay causing the qubit to decohere in the meantime. + +* Alice generates 1 EPR pair, that is, two maximally entangled qubits :math:`A` and :math:`B` of the form + :math:`|\Psi\rangle_{AB} = \frac{1}{\sqrt{2}} \left(|0\rangle_A |0\rangle_B + |1\rangle_A |1\rangle_B\right)` +* She sends qubit :math:`B` to Bob. +* Alice sends Bob the correct identifier of the qubit, and tells him to measure it. +* Both Alice and Bob measure their respective qubits to obtain a classical random number :math:`x \in \{0,1\}`. + +To realize this, we thus need not only the connection to the virtual quantum node servers, but Alice and Bob +themselves need to run a client/server to exchange classical control information. Before looking at the code, we +know that the setup of these servers is again determined by a configuration file, namely ``classicalNet.json``. +You want to copy this to whatever example you are running. It takes the JSON format, where in our example we +have Alice and Bob:: + + { + "default": { + "nodes": { + "Alice": { + "app_socket": ["localhost", 8821], + "qnodeos_socket": ["localhost", 8822], + "vnode_socket": ["localhost", 8823] + }, + "Bob": { + "app_socket": ["localhost", 8831], + "qnodeos_socket": ["localhost", 8832], + "vnode_socket": ["localhost", 8833] + } + }, + "topology": null + } + } + +The first thing that happens if we execute the script ``doNew.sh`` is that after some setting up it will call ``run.sh``, +executing:: + + #!/usr/bin/env bash + + python3 bobTest.py & + python3 aliceTest.py + +Let us now look at the programs for Alice and Bob. Alice will merely run a client on the classical communication +network that connects to Bob to be found in aliceTest.py. Using the template (see general Examples section) which +establishes the connections to the local virtual nodes, we thus need to provide client code for Alice to implement +the protocol above. The function runClientNode will automatically be executed once Alice connected to her local +virtual quantum node simulating the underlying hardware, and to Bob's server:: + + ##################################################################################################### + # + # runClientNode + # + # This will be run on the local node if all communication links are set up (to the virtual node + # quantum backend, as well as the nodes in the classical communication network), and the local classical + # communication server is running (if applicable). + # + @inlineCallbacks + def runClientNode(qReg, virtRoot, myName, classicalNet): + """ + Code to execute for the local client node. Called if all connections are established. + + Arguments + qReg quantum register (twisted object supporting remote method calls) + virtRoot virtual quantum ndoe (twisted object supporting remote method calls) + myName name of this node (string) + classicalNet servers in the classical communication network (dictionary of hosts) + """ + + logging.debug("LOCAL %s: Runing client side program.",myName) + + # Create 2 qubits + qA = yield virtRoot.callRemote("new_qubit_inreg",qReg) + qB = yield virtRoot.callRemote("new_qubit_inreg",qReg) + + # Put qubits A and B in a maximally entangled state + yield qA.callRemote("apply_H") + yield qA.callRemote("cnot_onto",qB) + + # Send qubit B to Bob + # Instruct the virtual node to transfer the qubit + remoteNum = yield virtRoot.callRemote("send_qubit",qB, "Bob") + + # Tell Bob the ID of the qubit, and ask him to measure + bob = classicalNet.hostDict["Bob"] + yield bob.root.callRemote("process_qubit", remoteNum) + + # Measure qubit A to obtain a random number + x = yield qA.callRemote("measure") + print("ALICE: My Random Number is ",x,"\n") + + reactor.stop() + + +Let us now look at Bob's server program to be found in bobTest.py. Observe that Alice will call process_qubit above. +Not included in the code below are several standard methods that require no change to be used in examples.:: + + ##################################################################################################### + # + # localNode + # + # This will be run if the local node acts as a server on the classical communication network, + # accepting remote method calls from the other nodes. + + class localNode(pb.Root): + + # This can be called by Alice to tell Bob to process the qubit + @inlineCallbacks + def remote_process_qubit(self, virtualNum): + """ + Recover the qubit and measure it to get a random number. + + Arguments + virtualNum number of the virtual qubit corresponding to the EPR pair received + """ + + qB = yield self.virtRoot.callRemote("get_virtual_ref",virtualNum) + + # Measure + x = yield qB.callRemote("measure") + + print("BOB: My Random Number is ", x, "\n") + +-------- +Starting +-------- + +We first start the virtual quantum node backend, by executing:: + + simulaqron start --nodes=Alice,Bob --network-config-file classicalNet.json + +We then start up the programs for Alice and Bob themselves. These will connect to the virtual quantum nodes, and +execute the quantum commands and classical communication outlined above, in the same directory as we placed +``classicalNet.json``:: + + python3 bobTest.py & + python3 aliceTest.py + +You can easily start everything by using the a single helper script:: + + bash doNew.sh + +------- +Stoping +------- + +You can stop all the running processes by using the helper script:: + + bash terminate.sh diff --git a/docs/native-mode/GraphState.rst b/docs/native-mode/GraphState.rst new file mode 100644 index 00000000..2148ad08 --- /dev/null +++ b/docs/native-mode/GraphState.rst @@ -0,0 +1,457 @@ +Distributing a graph state +========================== + +.. note:: Native mode is the low-level Twisted interface. For new projects, the NetQASM SDK is recommended. + See :doc:`New SDK Overview <../new-sdk/Overview>`. + +Here we consider a more complicated example, where we have four parties; Alice, Bob, Charlie and David. +They will distribute a graph state and transform this with local operations and classical communication to make +a GHZ-like state. +Finally they measure their qubits in the correct bases to achieved correlated outcomes. +(Note that this is not a efficient way to distribute a GHZ-state but an example illustrating how to use SimulaQron) + +------------ +An overview +------------ + +We will first give the main idea of the protocol by describing the evolution of the graph describing the graph +state shared by the parties. +The actual order of the operations performed will be different in the actual implementation and is described below. +For more information on definition of graph states and their transformation under local operations see +https://arxiv.org/abs/quant-ph/0602096. + +A graph state described by a star graph is a GHZ-state up to Hadamard operations on the qubits which are the leaves +of the graph. The parties will therefore transform the shared state to make a star graph. +Alice, Bob, Charlie and David generate a graph state :math:`|P_4\rangle`, i.e. a graph state described by a path +graph on four vertices, where Alice and David are the ends of the path. +The edge-set of this graph is + +.. math:: \{(A,B),(B,C),(C,D)\} + +where :math:`A`, :math:`B`, :math:`C` and :math:`D` are the vertices corresponding to the parties Alice, Bob, Charlie +and David, respectively. Local Clifford operations are performed at :math:`B`, :math:`C` and :math:`D` which induces +a graph operation called a local complementation at :math:`C` and therefore adds the edge :math:`(B,D)` to the graph. +The graph is now a star graph with an additional edge :math:`(C,D)`. + +This edge will be removed by the use of an additional qubit :math:`E` generated by David. +David will entangle his qubit :math:`D` with this new qubit and therefore adding the edge :math:`(D,E)` to the graph. +Qubit :math:`E` is then sent to Charlie which also entangles this with his qubit. +The edge-set is then + +.. math:: \{(A,B),(B,C),(C,D),(B,D),(D,E),(C,E)(\} + +Local Cliffords are performed at :math:`C`, :math:`D` and :math:`E` which induces a local complementation at :math:`E` +and therefore removes the edge :math:`(C,D)`. Finally qubit :math:`E` is measured in the standard basis to disconnect +is from the rest of the graph state. Depending on the measurement outcome, corrections are performed at :math:`C` and +:math:`D`. + +------------ +The protocol +------------ + +We now describe the operations performed in the protocol, which effectively induces the graph operations described above. +Although the order described here is slightly different the end result is still the same, since local operations commute. + +* Alice performs the following operations. + #. Alice prepares two qubits in the state :math:`|+\rangle_A |+\rangle_B`. + #. Alice performs a CPHASE operation between :math:`A` and :math:`B`. + #. Alice sends :math:`B` to Bob. + #. Alice measures qubit :math:`A` in the :math:`X`-basis. +* Bob performs the following operations. + #. Bob receives qubit :math:`B` from Alice. + #. Bob prepares a qubit in the state :math:`|+\rangle_C`. + #. Bob performs a CPHASE operation between :math:`B` and :math:`C`. + #. Bob performs the operation :math:`\exp(\frac{\mathrm{i}\pi}{4}Z)` on :math:`B` (one of the operations + to induced a local complementation at :math:`C`). + #. Bob sends :math:`C` to Charlie. + #. Bob measures qubit :math:`B` in the :math:`Z`-basis. +* Charlie performs the following operations + #. Charlie receives qubit :math:`C` from Bob + #. Charlie prepares a qubit in the state :math:`|+\rangle_D` + #. Charlie performs a CPHASE operation between :math:`C` and :math:`D`. + #. Charlie performs the operations :math:`\exp(-\frac{\mathrm{i}\pi}{4}X)` and + :math:`\exp(\frac{\mathrm{i}\pi}{4}Z)` on :math:`C` and :math:`D`, respectively (two of the operations to + induced a local complementation at :math:`C`). + #. Charlie sends :math:`D` to David. + #. Charlie receives qubit :math:`E` from David + #. Charlie performs a CPHASE operation between :math:`E` and :math:`C`. + #. Charlie performs the operations :math:`\exp(-\frac{\mathrm{i}\pi}{4}X)` and + :math:`\exp(\frac{\mathrm{i}\pi}{4}Z)` on :math:`E` and :math:`C`, respectively (two of the operations to + induced a local complementation at :math:`E`). + #. Charlie measures qubit :math:`E` in the :math:`Z`-basis. + #. Charlie performs a :math:`Z`-operation on :math:`C` if the measurement outcome is :math:`1` and does nothing + if it is :math:`0`. + #. Charlie sends the measurement outcome to David. + #. Charlie measures qubit :math:`C` in the :math:`X`-basis. +* David performs the following operations + #. David receives qubit :math:`D` from Charlie + #. David prepares a qubit in the state :math:`|+\rangle_E` + #. David performs a CPHASE operation between :math:`D` and :math:`E` and sends :math:`E` to Bob. + #. David performs the operation :math:`\exp(\frac{\mathrm{i}\pi}{4}Z)` on :math:`D` (one of the operations to + induced a local complementation at :math:`E`). + #. David receives the measurement outcome from Charlie and performs a :math:`Z`-operation on :math:`D` if this + is :math:`1` and nothing if it is :math:`0`. + #. David measures qubit :math:`D` in the :math:`X`-basis. + +----------- +Setting up +----------- + +We will run everything locally (localhost) using a single ``simulaqron_network.json`` configuration file that defines all +nodes and their socket assignments:: + + { + "default": { + "nodes": { + "Alice": { + "app_socket": ["localhost", 8821], + "qnodeos_socket": ["localhost", 8822], + "vnode_socket": ["localhost", 8823] + }, + "Bob": { + "app_socket": ["localhost", 8831], + "qnodeos_socket": ["localhost", 8832], + "vnode_socket": ["localhost", 8833] + }, + "Charlie": { + "app_socket": ["localhost", 8841], + "qnodeos_socket": ["localhost", 8842], + "vnode_socket": ["localhost", 8843] + }, + "David": { + "app_socket": ["localhost", 8871], + "qnodeos_socket": ["localhost", 8872], + "vnode_socket": ["localhost", 8873] + } + }, + "topology": null + } + } + +As we can see from the protocol above, Alice is the one that initializes the protocol and the others listen. We will +therefore run a client at Alice and servers at Bob, Charlie and David. + +Let us now provide the actual program code for all the parties. + +----------------- +Programming Alice +----------------- + +Since Alice acts as a client, we will only need to fill in runClientNode. This gives:: + + ##################################################################################################### + # + # runClientNode + # + # This will be run on the local node if all communication links are set up (to the virtual node + # quantum backend, as well as the nodes in the classical communication network), and the local classical + # communication server is running (if applicable). + # + @inlineCallbacks + def runClientNode(qReg, virtRoot, myName, classicalNet): + """ + Code to execute for the local client node. Called if all connections are established. + + Arguments + qReg quantum register (twisted object supporting remote method calls) + virtRoot virtual quantum node (twisted object supporting remote method calls) + myName name of this node (string) + classicalNet servers in the classical communication network (dictionary of hosts) + """ + + logging.debug("LOCAL %s: Runing client side program.",myName) + + #Create 2 qubits + qA = yield virtRoot.callRemote("new_qubit_inreg",qReg) + qB = yield virtRoot.callRemote("new_qubit_inreg",qReg) + + #Make 2-qubit graph state + yield qA.callRemote("apply_H") + yield qB.callRemote("apply_H") + yield qA.callRemote("cphase_onto",qB) + + #send qubit B to Bob + #instruct virtual node to transfer qubit + remoteNum = yield virtRoot.callRemote("send_qubit",qB,"Bob") + logging.debug("LOCAL %s: Remote qubit is %d.",myName,remoteNum) + + #Tell number of virtual qubit to Bob and receive measurement outcome parity + bob=classicalNet.hostDict["Bob"] + yield bob.root.callRemote("receive_qubit",remoteNum) + + #Measure qubit (X-basis) + yield qA.callRemote("apply_H") + outcome=yield qA.callRemote("measure") + print("Alice outcome was:", outcome) + + reactor.stop() + +--------------- +Programming Bob +--------------- + +Let us now program the code for Bob. Since he only acts as a server on the classical network, it is enough to edit +the localNode portion of the template. Alice calls receive_qubit to convey the identifier of the virtual qubit.:: + + + ##################################################################################################### + # + # localNode + # + # This will be run if the local node acts as a server on the classical communication network, + # accepting remote method calls from the other nodes. + + class localNode(pb.Root): + + def __init__(self, node, classicalNet): + + self.node = node + self.classicalNet = classicalNet + + self.virtRoot = None + self.qReg = None + + def set_virtual_node(self, virtRoot): + self.virtRoot = virtRoot + + def set_virtual_reg(self, qReg): + self.qReg = qReg + + def remote_test(self): + return "Tested!" + + # This can be called by Alice (or other clients on the classical network) to inform Bob + # of an event. + @inlineCallbacks + def remote_receive_qubit(self, virtualNum): + + logging.debug("LOCAL %s: Getting reference to qubit number %d.",self.node.name, virtualNum) + + # Get ref of qubit + qB=yield self.virtRoot.callRemote("get_virtual_ref",virtualNum) + + #Create new qubit + qC=yield self.virtRoot.callRemote("new_qubit_inreg",self.qReg) + + #Expand graph state + yield qC.callRemote("apply_H") + yield qB.callRemote("cphase_onto",qC) + + #Perform part of tau at C + yield qB.callRemote("apply_rotation",[0,0,1],-np.pi/2) + + + #send qubit C to Charlie + #instruct virtual node to transfer qubit + remoteNum = yield self.virtRoot.callRemote("send_qubit",qC,"Charlie") + logging.debug("LOCAL %s: Remote qubit is %d.","Bob",remoteNum) + + #Tell number of virtual qubit to Charlie and receive measurement outcome parity + charlie=self.classicalNet.hostDict["Charlie"] + yield charlie.root.callRemote("receive_qubit",remoteNum,"Bob") + + #Measure qubit (Z-basis) + outcome=yield qB.callRemote("measure") + print("Bob outcome was:", outcome) + +------------------- +Programming Charlie +------------------- + +Let us now program the code for Charlie. Since he only acts as a server on the classical network, it is enough to +edit the localNode portion of the template. Both Bob and David calls receive_qubit to convey the identifier of the +virtual qubit and depending on the sender Charlie does different things.:: + + + ##################################################################################################### + # + # localNode + # + # This will be run if the local node acts as a server on the classical communication network, + # accepting remote method calls from the other nodes. + + class localNode(pb.Root): + + def __init__(self, node, classicalNet): + + self.node = node + self.classicalNet = classicalNet + + self.virtRoot = None + self.qReg = None + self.qC = None #Maybe not the indented way + + def set_virtual_node(self, virtRoot): + self.virtRoot = virtRoot + + def set_virtual_reg(self, qReg): + self.qReg = qReg + + def remote_test(self): + return "Tested!" + + # This can be called by Alice (or other clients on the classical network) to inform Bob + # of an event. + @inlineCallbacks + def remote_receive_qubit(self, virtualNum,sender): + + if sender=="Bob": + + logging.debug("LOCAL %s: Getting reference to qubit number %d.",self.node.name, virtualNum) + + # Get ref of qubit + self.qC=yield self.virtRoot.callRemote("get_virtual_ref",virtualNum) + qC=self.qC + + #Create new qubit + qD=yield self.virtRoot.callRemote("new_qubit_inreg",self.qReg) + + #Expand graph state + yield qD.callRemote("apply_H") + yield qC.callRemote("cphase_onto",qD) + + #Perform part of tau at C + yield qC.callRemote("apply_rotation",[1,0,0],np.pi/2) + yield qD.callRemote("apply_rotation",[0,0,1],-np.pi/2) + + # tmp=yield self.virtRoot.callRemote("get_register",qC) + # np.save("data_R",tmp[0]) + # np.save("data_I",tmp[1]) + + #send qubit D to David + #instruct virtual node to transfer qubit + remoteNum = yield self.virtRoot.callRemote("send_qubit",qD,"David") + logging.debug("LOCAL %s: Remote qubit is %d.","Charlie",remoteNum) + + #Tell number of virtual qubit to Charlie and receive measurement outcome parity + david=self.classicalNet.hostDict["David"] + yield david.root.callRemote("receive_qubit",remoteNum) + + #Measure qubit (X-basis) + yield qC.callRemote("apply_H") + outcome=yield qC.callRemote("measure") + print("Charlie outcome was:", outcome) + + elif sender=="David": + + logging.debug("LOCAL %s: Getting reference to qubit number %d.",self.node.name, virtualNum) + + # Get ref of qubit + qE=yield self.virtRoot.callRemote("get_virtual_ref",virtualNum) + qC=self.qC + + # Expand graph state + yield qE.callRemote("cphase_onto",qC) + + #Do local part of tau + yield qE.callRemote("apply_rotation",[1,0,0],np.pi/2) + yield qC.callRemote("apply_rotation",[0,0,1],-np.pi/2) + + #Measure extra qubit (Z-basis) + m=yield qE.callRemote("measure") + if m==1: + yield qC.callRemote("apply_Z") + return m + +----------------- +Programming David +----------------- + +Let us now program the code for David. Since he only acts as a server on the classical network, it is enough to edit +the localNode portion of the template. Charlie calls receive_qubit to convey the identifier of the virtual qubit.:: + + + ##################################################################################################### + # + # localNode + # + # This will be run if the local node acts as a server on the classical communication network, + # accepting remote method calls from the other nodes. + + class localNode(pb.Root): + + def __init__(self, node, classicalNet): + + self.node = node + self.classicalNet = classicalNet + + self.virtRoot = None + self.qReg = None + + def set_virtual_node(self, virtRoot): + self.virtRoot = virtRoot + + def set_virtual_reg(self, qReg): + self.qReg = qReg + + def remote_test(self): + return "Tested!" + + # This can be called by Alice (or other clients on the classical network) to inform Bob + # of an event. + @inlineCallbacks + def remote_receive_qubit(self, virtualNum): + + logging.debug("LOCAL %s: Getting reference to qubit number %d.",self.node.name, virtualNum) + + # Get ref of qubit + qD=yield self.virtRoot.callRemote("get_virtual_ref",virtualNum) + + #Create new qubit + qE=yield self.virtRoot.callRemote("new_qubit_inreg",self.qReg) + + #Expand graph state + yield qE.callRemote("apply_H") + yield qD.callRemote("cphase_onto",qE) + + #send qubit E to Charlie + #instruct virtual node to transfer qubit + remoteNum = yield self.virtRoot.callRemote("send_qubit",qE,"Charlie") + logging.debug("LOCAL %s: Remote qubit is %d.","David",remoteNum) + + #Tell number of virtual qubit to Charlie and receive meas outcome + charlie=self.classicalNet.hostDict["Charlie"] + m=yield charlie.root.callRemote("receive_qubit",remoteNum,"David") + + logging.debug("LOCAL %s: Got outcome %d.","David",m) + yield qD.callRemote("apply_rotation",[0,0,1],-np.pi/2) + if m==1: + yield qD.callRemote("apply_Z") + + #Measure qubit (X-basis) + # tmp=yield self.virtRoot.callRemote("get_register",qD) + # np.save("data_R",tmp[0]) + # np.save("data_I",tmp[1]) + yield qD.callRemote("apply_H") + outcome=yield qD.callRemote("measure") + print("Davids outcome was:", outcome) + +-------- +Starting +-------- + +We first start the virtual quantum node backend, by executing:: + + simulaqron start --nodes=Alice,Bob,Charlie,David --network-config-file classicalNet.json --simulaqron-config-file simulaqron_settings.json + +We then start up the programs for the parties themselves. These will connect to the virtual quantum nodes, and +execute the quantum commands and classical communication outlined above, in the same directory as we placed +simulaqron_network.json:: + + python3 bobTest.py & + python3 charlieTest.py & + python3 davidTest.py & + python3 aliceTest.py + +You can easily start everything by using the a single helper script:: + + sh doNew.sh + +------- +Stoping +------- + +You can stop all the running processes by using the helper script:: + + bash terminate.sh + diff --git a/docs/native-mode/Teleport.rst b/docs/native-mode/Teleport.rst new file mode 100644 index 00000000..65f92aab --- /dev/null +++ b/docs/native-mode/Teleport.rst @@ -0,0 +1,233 @@ +Teleporting a Qubit +=================== + +.. note:: Native mode is the low-level Twisted interface. For new projects, the NetQASM SDK is recommended. + See :doc:`New SDK Overview <../new-sdk/Overview>` and the :doc:`new SDK version of this example <../new-sdk/Teleport>`. + +Let's now consider a very simple protocol, in which Alice first generates an EPR pair with Bob, and then teleports +a qubit to Bob. To program it in SimulaQron's native mode, we will use the template described in +:doc:`Template