diff --git a/.github/workflows/actions.yaml b/.github/workflows/actions.yaml index e3d8bcf7..87790ba1 100644 --- a/.github/workflows/actions.yaml +++ b/.github/workflows/actions.yaml @@ -1,6 +1,6 @@ name: General check -on: push +on: [push, pull_request] jobs: lint: diff --git a/.github/workflows/simulaqron.yaml b/.github/workflows/simulaqron.yaml index 816949c1..8dc81e8d 100644 --- a/.github/workflows/simulaqron.yaml +++ b/.github/workflows/simulaqron.yaml @@ -1,7 +1,6 @@ - name: SimulaQron backend tests -on: push +on: [push, pull_request] jobs: examples: @@ -17,7 +16,7 @@ jobs: - name: Install netqasm run: make install-dev - name: Install projectq - run: pip install -Iv projectq==0.5.1 + run: pip install -Iv projectq - name: Run examples env: NETQASM_SIMULATOR: simulaqron diff --git a/.github/workflows/squidasm.yaml b/.github/workflows/squidasm.yaml index d77f383a..7a1eb5f4 100644 --- a/.github/workflows/squidasm.yaml +++ b/.github/workflows/squidasm.yaml @@ -1,6 +1,6 @@ name: SquidASM backend tests -on: push +on: [push, pull_request] jobs: tests: diff --git a/.gitignore b/.gitignore index df766853..68a0b4c0 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ *.log **/log/* *.pkl -/docs/build/* +/docs/_build/* /build/* /dist/* /*.egg-info/* diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 4436554f..bd0798f9 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,4 +1,20 @@ +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.10" + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/conf.py + python: - version: 3.8 - setup_py_install: true - pip_install: true \ No newline at end of file + install: + - requirements: docs/requirements.txt + - method: pip + path: . + +formats: + - pdf + - epub diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a5f045e..1032d976 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,38 @@ CHANGELOG ========= +2024-09-13 (1.0.0) +------------------- +- Fix bell state corrections when compiling for hardware + +2024-03-20 (0.16.0) +------------------- +- Added the REIDS flavour. + +2024-01-15 (0.15.0) +------------------- +- Change level of subroutine compilation logging from `INFO` to `DEBUG` to avoid cluttering. + +2023-10-10 (0.14.0) +------------------- +- Fix connection getting flushed during exception handling + +2023-07-24 (0.13.3) +------------------- +- Fixed an issue with LAST instruction logs. + +2023-07-07 (0.13.2) +------------------- +- Relaxed version requirements on dependencies + +2023-01-26 (0.13.1) +------------------- +- Add instructions on updating CHANGELOG.md + +2022-12-13 (0.13.0) +------------------- +- Use nodes' app names for instruction logging. + 2022-11-15 (0.12.2) ------------------- - Update SquidASM to 0.10.0. diff --git a/README.md b/README.md index de91a564..994219e1 100644 --- a/README.md +++ b/README.md @@ -107,13 +107,41 @@ explore a commercial market, please contact us for a license agreement. -## Development +# Development For code formatting, [`black`](https://github.com/psf/black) and [`isort`](https://github.com/PyCQA/isort) are used. Type hints should be added as much as possible. Types are checked using [`mypy`](https://github.com/python/mypy). Before code is pushed, make sure that the `make lint` command succeeds, which runs `black`, `isort`, `flake8` and `mypy`. +## Branches +A form of "git flow" is used for branch and release management. The main active branch is `develop`. +New features are developed in new separate branches, preferrably with a name representing the new feature. +To get the new features in the main branch, open a pull request for merging the feature branch into the `develop` branch. +These pull requests are then reviewed by maintainers of the repository. +A `master` or `main` branch is not used. + + +## Releases (for maintainers only) +When a release is made, a new branch `release-X.Y` (e.g. `release-0.12`) is created from the `develop` branch. +Only small fixes (patches) may be pushed onto this release branch. Bigger new features need to go into separate branches, merged into `develop`, and will hence end up in a later release. +**Tags** are *only* applied on commits in the release branch. The first tag on a new release branch needs to be `vX.Y.0`, e.g. `v0.12.0`. +Patches (i.e. commits on the release branch) may then be tagged with `vX.Y.1`, `vX.Y.2` etc. +Pushing a 'tag' automatically triggers the Github Action for publishing the corresponding version to PyPI. +Whenever a new tag is pushed for a patch, the corresponding commit (on the release branch) should be merged into `develop`. + +Example list of steps for releasing a new version: +- (0) `develop` contains all the features that are needed for the release +- (1) update `CHANGELOG.md` with the new version number and the changes (this will require a final PR to `develop`) +- (2) create a `release-X.Y` branch from `develop` and switch to this branch +- (3) create a tag `vX.Y.0` on this branch +- (4) push the release branch as well as the tag (pushing the tag will automatically publish to PyPI) +- (5) develop a small fix (patch) as a commit on the `release-X.Y` branch +- (6) update `CHANGELOG.md` with the new (minor) version number and the changes +- (7) push the new commit and see if all the workflows (in Github Actions) succeed for `release-X.Y` +- (8) add a tag `vX.Y.1` to this latest commit +- (9) push the new tag; the patch will automatically be published on PyPI +- (10) create a pull request for merging `release-X.Y` into `develop` # Contributors In alphabetical order: diff --git a/docs/Makefile b/docs/Makefile index 01c04856..1b85a1b0 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -5,8 +5,8 @@ SPHINXOPTS = SPHINXBUILD = sphinx-build SOURCEDIR = . -BUILDDIR = build -PYTHON3 = python3 +BUILDDIR = _build +PYTHON3 = python # TODO handle like this? export NETQASM_SIMULATOR = debug @@ -19,7 +19,6 @@ help: %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - python-deps: @${PYTHON3} -m pip install -r requirements.txt diff --git a/docs/api_root.rst b/docs/api_root.rst index 84bda24b..0f51256b 100644 --- a/docs/api_root.rst +++ b/docs/api_root.rst @@ -9,4 +9,4 @@ netqasm package netqasm.logging netqasm.runtime netqasm.sdk - netqasm.util \ No newline at end of file + netqasm.util diff --git a/docs/api_sdk/netqasm.sdk.compiling.rst b/docs/api_sdk/netqasm.sdk.compiling.rst deleted file mode 100644 index f847758f..00000000 --- a/docs/api_sdk/netqasm.sdk.compiling.rst +++ /dev/null @@ -1,8 +0,0 @@ -netqasm\.sdk\.compiling ---------------------------- - -.. automodule:: netqasm.sdk.compiling - :members: - :undoc-members: - :show-inheritance: - :inherited-members: diff --git a/docs/conf.py b/docs/conf.py index 445ac0a2..88675c15 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -10,22 +10,25 @@ # 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. # +from importlib.metadata import version as get_release_version # import os +import re # import sys + # sys.path.insert(0, os.path.abspath('.')) # -- Project information ----------------------------------------------------- project = "netqasm" -copyright = "2021, QuTech" +copyright = "2023, QuTech" author = "QuTech" # The full version, including alpha/beta/rc tags -from importlib.metadata import version - -release = version("netqasm") +release = get_release_version("netqasm") +# The short X.Y version. +version = re.match(r'^(\d+\.\d+)', release).expand(r'\1') # -- General configuration --------------------------------------------------- @@ -84,4 +87,4 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ["_static"] +# html_static_path = ["_static"] diff --git a/docs/netqasm.examples.rst b/docs/netqasm.examples.rst index aed5013a..23d7ac49 100644 --- a/docs/netqasm.examples.rst +++ b/docs/netqasm.examples.rst @@ -8,4 +8,6 @@ netqasm\.examples api_examples/netqasm.examples.apps api_examples/netqasm.examples.lib api_examples/netqasm.examples.netqasm_files - api_examples/netqasm.examples.sdk_examples \ No newline at end of file + api_examples/netqasm.examples.qne_apps + api_examples/netqasm.examples.sdk_compilation + api_examples/netqasm.examples.sdk_scripts diff --git a/docs/netqasm.sdk.rst b/docs/netqasm.sdk.rst index b692486a..5fa0d382 100644 --- a/docs/netqasm.sdk.rst +++ b/docs/netqasm.sdk.rst @@ -1,5 +1,5 @@ netqasm\.sdk -================ +============ .. toctree:: :caption: Modules @@ -7,7 +7,6 @@ netqasm\.sdk api_sdk/netqasm.sdk.builder api_sdk/netqasm.sdk.classical_communication - api_sdk/netqasm.sdk.compiling api_sdk/netqasm.sdk.config api_sdk/netqasm.sdk.connection api_sdk/netqasm.sdk.epr_socket @@ -17,4 +16,4 @@ netqasm\.sdk api_sdk/netqasm.sdk.progress_bar api_sdk/netqasm.sdk.qubit api_sdk/netqasm.sdk.shared_memory - api_sdk/netqasm.sdk.toolbox \ No newline at end of file + api_sdk/netqasm.sdk.toolbox diff --git a/docs/quickstart/modules.rst b/docs/quickstart/modules.rst index 698f7f29..327c917a 100644 --- a/docs/quickstart/modules.rst +++ b/docs/quickstart/modules.rst @@ -50,7 +50,7 @@ NetQASM connection :noindex: :special-members: __enter__, __exit__ :members: - :undoc-members: app_name, node_name, app_id, flush, block + :undoc-members: app_name, node_name, app_id, flush, block Qubit ----- @@ -61,7 +61,7 @@ Qubit .. autoclass:: Qubit :noindex: :members: - :undoc-members: qubit_id, entanglement_info, remote_entangled_node, measure, + :undoc-members: qubit_id, entanglement_info, remote_entangled_node, measure, X, Y, Z, H, S, K, T, rot_X, rot_Y, rot_Z, cnot, cphase, reset, free @@ -74,8 +74,8 @@ EPR socket .. autoclass:: EPRSocket :members: - :undoc-members: conn, remote_app_name, remote_node_id, epr_socket_id, remote_epr_socket_id, min_fidelity, - create, recv, + :undoc-members: conn, remote_app_name, remote_node_id, epr_socket_id, remote_epr_socket_id, min_fidelity, + create, recv, :noindex: .. automethod:: create_context(number=1, sequential=False) @@ -92,20 +92,19 @@ Futures .. autoclass:: Future :members: - :undoc-members: add, value + :undoc-members: add, value :noindex: .. autoclass:: Array :members: - :undoc-members: get_future_index, get_future_slice, foreach, enumerate + :undoc-members: get_future_index, get_future_slice, foreach, enumerate :noindex: .. autoclass:: RegFuture :members: - :undoc-members: add, value + :undoc-members: add, value :noindex: - Classical communication ----------------------- diff --git a/docs/requirements.txt b/docs/requirements.txt index 73611b25..c9ad6015 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,3 +1,5 @@ -sphinx>=3.5.3,<4.0.0 -sphinx-rtd-theme>=0.5.2,<1.0.0 +sphinx>=7.2.6 +sphinx-rtd-theme>=2.0.0 sphinx-autodoc-typehints>=1.11.1,<2.0.0 +jinja2>=3.0 +MarkupSafe==2.0.1 diff --git a/netqasm/lang/instr/base.py b/netqasm/lang/instr/base.py index 52652b14..84f71564 100644 --- a/netqasm/lang/instr/base.py +++ b/netqasm/lang/instr/base.py @@ -868,7 +868,7 @@ class DebugInstruction(NetQASMInstruction): @property def operands(self) -> List[Operand]: - pass + return [] @classmethod def deserialize_from(cls, raw: bytes): diff --git a/netqasm/lang/instr/flavour.py b/netqasm/lang/instr/flavour.py index a097678f..26ba3a6e 100644 --- a/netqasm/lang/instr/flavour.py +++ b/netqasm/lang/instr/flavour.py @@ -117,6 +117,15 @@ def __init__(self): super().__init__(self.instrs) +class REIDSFlavour(Flavour): + @property + def instrs(self): + return [] + + def __init__(self): + super().__init__(self.instrs) + + class TrappedIonFlavour(Flavour): @property def instrs(self): diff --git a/netqasm/lang/parsing/text.py b/netqasm/lang/parsing/text.py index 828aae7b..8c7767bb 100644 --- a/netqasm/lang/parsing/text.py +++ b/netqasm/lang/parsing/text.py @@ -46,7 +46,7 @@ def parse_text_subroutine( assign_branch_labels=True, make_args_operands=True, replace_constants=True, - flavour: Flavour = None, + flavour: Optional[Flavour] = None, ) -> Subroutine: """ Convert a text representation of a subroutine into a Subroutine object. @@ -70,7 +70,7 @@ def assemble_subroutine( assign_branch_labels=True, make_args_operands=True, replace_constants=True, - flavour: Flavour = None, + flavour: Optional[Flavour] = None, ) -> Subroutine: """ Convert a `ProtoSubroutine` into a `Subroutine`, given a Flavour (default: vanilla). diff --git a/netqasm/runtime/application.py b/netqasm/runtime/application.py index 94a419be..e296c9fe 100644 --- a/netqasm/runtime/application.py +++ b/netqasm/runtime/application.py @@ -107,7 +107,7 @@ def load_yaml_file(path: str) -> Any: return load_yaml(path) -def app_instance_from_path(app_dir: str = None) -> ApplicationInstance: +def app_instance_from_path(app_dir: Optional[str] = None) -> ApplicationInstance: """ Create an Application Instance based on files in a directory. Uses the current working directory if `app_dir` is None. @@ -168,7 +168,7 @@ def default_app_instance(programs: List[Tuple[str, Callable]]) -> ApplicationIns def network_cfg_from_path( - app_dir: str = None, network_config_file: str = None + app_dir: Optional[str] = None, network_config_file: Optional[str] = None ) -> Optional[NetworkConfig]: if network_config_file is None: network_config_file = "network.yaml" @@ -184,7 +184,7 @@ def network_cfg_from_path( def post_function_from_path( - app_dir: str = None, post_function_file: str = None + app_dir: Optional[str] = None, post_function_file: Optional[str] = None ) -> Optional[Callable]: if post_function_file is None: post_function_file = "post_function.yaml" diff --git a/netqasm/runtime/cli.py b/netqasm/runtime/cli.py index cd1319f6..214b3dd2 100644 --- a/netqasm/runtime/cli.py +++ b/netqasm/runtime/cli.py @@ -1,3 +1,6 @@ +# TODO: Fix typing in this file or fix mypy configuration. +# type: ignore + """Command-line interface of the `netqasm` executable. This module defines the commands that may be used when using `netqasm` as a program @@ -19,6 +22,7 @@ post_function_from_path, ) from netqasm.runtime.env import get_example_apps, init_folder, new_folder +from netqasm.runtime.process_logs import create_app_instr_logs, make_last_log from netqasm.runtime.settings import ( Formalism, Simulator, @@ -352,6 +356,10 @@ def simulate( enable_logging=log_to_files, hardware=hardware, ) + if log_to_files: + create_app_instr_logs(log_cfg.log_subroutines_dir) + make_last_log(log_cfg.log_subroutines_dir) + if timer: print(f"finished simulation in {round(time.perf_counter() - start, 2)} seconds") diff --git a/netqasm/runtime/interface/config.py b/netqasm/runtime/interface/config.py index f499286a..65076e58 100644 --- a/netqasm/runtime/interface/config.py +++ b/netqasm/runtime/interface/config.py @@ -1,6 +1,6 @@ from dataclasses import dataclass from enum import Enum -from typing import Any, Dict, List +from typing import Any, Dict, List, Optional from netqasm.util.yaml import load_yaml @@ -125,13 +125,12 @@ def parse_network_config(cfg) -> NetworkConfig: # Keys are role names, values are node names. RolesConfig = Dict[str, str] - # App input per role. # Keys are names of variables that are passed to the role's `main` function. AppInput = Dict[str, Any] -def network_cfg_from_file(network_config_file: str = None) -> NetworkConfig: +def network_cfg_from_file(network_config_file: Optional[str] = None) -> NetworkConfig: yaml_dict = load_yaml(network_config_file) network_cfg = parse_network_config(yaml_dict) return network_cfg diff --git a/netqasm/runtime/process_logs.py b/netqasm/runtime/process_logs.py index d078ba2a..6c03400a 100644 --- a/netqasm/runtime/process_logs.py +++ b/netqasm/runtime/process_logs.py @@ -11,7 +11,7 @@ def process_log(log_dir): # Add host line numbers to logs _add_hln_to_logs(log_dir) - _create_app_instr_logs(log_dir) + create_app_instr_logs(log_dir) make_last_log(log_dir) @@ -65,7 +65,7 @@ def _add_hln_to_log_entry(subroutines, entry): entry["HFL"] = hostline.filename -def _create_app_instr_logs(log_dir): +def create_app_instr_logs(log_dir): file_end = "_instrs.yaml" app_names = BaseNetQASMConnection.get_app_names() diff --git a/netqasm/sdk/build_epr.py b/netqasm/sdk/build_epr.py index 681d0850..ebc45a2b 100644 --- a/netqasm/sdk/build_epr.py +++ b/netqasm/sdk/build_epr.py @@ -28,6 +28,7 @@ class EntRequestParams: time_unit: TimeUnit = TimeUnit.MICRO_SECONDS max_time: int = 0 expect_phi_plus: bool = True + expect_psi_plus: bool = False min_fidelity_all_at_end: Optional[int] = None max_tries: Optional[int] = None random_basis_local: Optional[RandomBasis] = None diff --git a/netqasm/sdk/builder.py b/netqasm/sdk/builder.py index 0e0b124c..a72c7546 100644 --- a/netqasm/sdk/builder.py +++ b/netqasm/sdk/builder.py @@ -38,6 +38,7 @@ from netqasm.lang.subroutine import Subroutine from netqasm.lang.version import NETQASM_VERSION from netqasm.qlink_compat import BellState, EPRRole, EPRType, LinkLayerOKTypeK +from netqasm.runtime.settings import get_is_using_hardware from netqasm.sdk.build_epr import ( SER_RESPONSE_KEEP_IDX_BELL_STATE, SER_RESPONSE_KEEP_LEN, @@ -201,7 +202,7 @@ def __init__( connection: BaseNetQASMConnection, app_id: int, hardware_config: Optional[HardwareConfig] = None, - log_config: LogConfig = None, + log_config: Optional[LogConfig] = None, compiler: Optional[Type[SubroutineTranspiler]] = None, return_arrays: bool = True, ): @@ -377,7 +378,9 @@ def post_loop(conn: BaseNetQASMConnection, loop_reg: RegFuture): pair=pair, ) - if params.expect_phi_plus and role == EPRRole.RECV: + if ( + params.expect_phi_plus or params.expect_psi_plus + ) and role == EPRRole.RECV: # Perform Bell corrections bell_state = self._get_raw_bell_state( ent_results_array, loop_reg, bell_state_reg @@ -386,7 +389,9 @@ def post_loop(conn: BaseNetQASMConnection, loop_reg: RegFuture): instruction=GenericInstr.SET, operands=[qubit_reg, 0] ) self.subrt_add_pending_command(set_qubit_reg_cmd) # type: ignore - self._build_cmds_epr_keep_corrections_single_pair(bell_state, qubit_reg) + self._build_cmds_epr_keep_corrections_single_pair( + bell_state, qubit_reg, params + ) # If it's the last pair, don't move it to a mem qubit with loop_reg.if_ne(params.number - 1): @@ -448,7 +453,9 @@ def post_loop(conn: BaseNetQASMConnection, loop_reg: RegFuture): ) assert tp == EPRType.K or tp == EPRType.R - if params.expect_phi_plus and role == EPRRole.RECV: + if ( + params.expect_phi_plus or params.expect_psi_plus + ) and role == EPRRole.RECV: bell_state = self._get_raw_bell_state( ent_results_array, loop_reg, bell_state_reg ) @@ -456,7 +463,9 @@ def post_loop(conn: BaseNetQASMConnection, loop_reg: RegFuture): instruction=GenericInstr.SET, operands=[qubit_reg, 0] ) self.subrt_add_pending_command(set_qubit_reg_cmd) # type: ignore - self._build_cmds_epr_keep_corrections_single_pair(bell_state, qubit_reg) + self._build_cmds_epr_keep_corrections_single_pair( + bell_state, qubit_reg, params + ) q_id = qubit_ids.get_future_index(loop_register) q = FutureQubit(conn=conn, future_id=q_id) @@ -1357,26 +1366,43 @@ def undef_result_element(conn: BaseNetQASMConnection, _: RegFuture): ) def _build_cmds_epr_keep_corrections_single_pair( - self, bell_state: RegFuture, qubit_reg: operand.Register + self, + bell_state: RegFuture, + qubit_reg: operand.Register, + params: EntRequestParams, ) -> None: - with bell_state.if_eq(BellState.PHI_MINUS.value): # Phi- -> apply Z-gate - correction_cmds = [ - ICmd(instruction=GenericInstr.ROT_Z, operands=[qubit_reg, 16, 4]) - ] - self.subrt_add_pending_commands(correction_cmds) # type: ignore - with bell_state.if_eq(BellState.PSI_PLUS.value): # Psi+ -> apply X-gate - correction_cmds = [ - ICmd(instruction=GenericInstr.ROT_X, operands=[qubit_reg, 16, 4]) - ] - self.subrt_add_pending_commands(correction_cmds) # type: ignore - with bell_state.if_eq( - BellState.PSI_MINUS.value - ): # Psi- -> apply X-gate and Z-gate - correction_cmds = [ + x180 = [ICmd(instruction=GenericInstr.ROT_X, operands=[qubit_reg, 16, 4])] + + if get_is_using_hardware(): + # For hardware, don't use Z-gates. + # Decompose Z180 into Y90, X180, -Y90 + z180 = [ + ICmd(instruction=GenericInstr.ROT_Y, operands=[qubit_reg, 8, 4]), ICmd(instruction=GenericInstr.ROT_X, operands=[qubit_reg, 16, 4]), - ICmd(instruction=GenericInstr.ROT_Z, operands=[qubit_reg, 16, 4]), + ICmd(instruction=GenericInstr.ROT_Y, operands=[qubit_reg, 24, 4]), ] - self.subrt_add_pending_commands(correction_cmds) # type: ignore + else: + z180 = [ICmd(instruction=GenericInstr.ROT_Z, operands=[qubit_reg, 16, 4])] + + if params.expect_phi_plus: + with bell_state.if_eq(BellState.PHI_MINUS.value): # Phi- -> apply Z-gate + self.subrt_add_pending_commands(z180) # type: ignore + with bell_state.if_eq(BellState.PSI_PLUS.value): # Psi+ -> apply X-gate + self.subrt_add_pending_commands(x180) # type: ignore + with bell_state.if_eq( + BellState.PSI_MINUS.value + ): # Psi- -> apply X-gate and Z-gate + self.subrt_add_pending_commands(x180 + z180) # type: ignore + else: + assert params.expect_psi_plus + with bell_state.if_eq(BellState.PHI_PLUS.value): # Phi+ -> apply X-gate + self.subrt_add_pending_commands(x180) # type: ignore + with bell_state.if_eq( + BellState.PHI_MINUS.value + ): # Phi- -> apply X-gate and Z-gate + self.subrt_add_pending_commands(x180 + z180) # type: ignore + with bell_state.if_eq(BellState.PSI_MINUS.value): # Psi- -> apply Z-gate + self.subrt_add_pending_commands(z180) # type: ignore def _build_cmds_epr_keep_corrections( self, qubit_ids_array: Array, ent_results_array: Array, params: EntRequestParams @@ -1398,7 +1424,9 @@ def loop(conn: BaseNetQASMConnection, index: RegFuture): instruction=GenericInstr.SET, operands=[qubit_reg, 0] ) self.subrt_add_pending_command(set_qubit_reg_cmd) # type: ignore - self._build_cmds_epr_keep_corrections_single_pair(bell_state, qubit_reg) + self._build_cmds_epr_keep_corrections_single_pair( + bell_state, qubit_reg, params + ) self._build_cmds_loop_body( loop, stop=params.number, loop_register=loop_register @@ -1505,7 +1533,7 @@ def _build_cmds_epr_recv_keep( self.subrt_add_pending_commands(wait_cmds) # type: ignore - if wait_all and params.expect_phi_plus: + if wait_all and (params.expect_phi_plus or params.expect_psi_plus): self._build_cmds_epr_keep_corrections( qubit_ids_array, ent_results_array, params ) @@ -1645,7 +1673,7 @@ def _build_cmds_epr_recv_rsp( self.subrt_add_pending_commands(wait_cmds) # type: ignore - if wait_all and params.expect_phi_plus: + if wait_all and (params.expect_phi_plus or params.expect_psi_plus): self._build_cmds_epr_keep_corrections( qubit_ids_array, ent_results_array, params ) @@ -1875,6 +1903,8 @@ def sdk_epr_keep( else: wait_all = True + self._connection._logger.info(f"wait_all = {wait_all}") + if reset_results_array: self._build_cmds_undefine_array(ent_results_array) diff --git a/netqasm/sdk/classical_communication/socket.py b/netqasm/sdk/classical_communication/socket.py index 20f5eebb..86035b34 100644 --- a/netqasm/sdk/classical_communication/socket.py +++ b/netqasm/sdk/classical_communication/socket.py @@ -103,7 +103,10 @@ def send_silent(self, msg: str) -> None: raise NotImplementedError def recv_silent( - self, block: bool = True, timeout: Optional[float] = None, maxsize: int = None + self, + block: bool = True, + timeout: Optional[float] = None, + maxsize: Optional[int] = None, ) -> str: """Receive a message without logging""" raise NotImplementedError diff --git a/netqasm/sdk/classical_communication/thread_socket/socket.py b/netqasm/sdk/classical_communication/thread_socket/socket.py index 50f1a364..0e1bbf27 100644 --- a/netqasm/sdk/classical_communication/thread_socket/socket.py +++ b/netqasm/sdk/classical_communication/thread_socket/socket.py @@ -401,7 +401,10 @@ def send_silent(self, msg: str) -> None: self._SOCKET_HUB.send(self, msg) def recv_silent( - self, block: bool = True, timeout: Optional[float] = None, maxsize: int = None + self, + block: bool = True, + timeout: Optional[float] = None, + maxsize: Optional[int] = None, ) -> str: """Receive a message without logging""" msg = self._SOCKET_HUB.recv(self, block=block, timeout=timeout) diff --git a/netqasm/sdk/connection.py b/netqasm/sdk/connection.py index acb381fb..3325bac4 100644 --- a/netqasm/sdk/connection.py +++ b/netqasm/sdk/connection.py @@ -85,7 +85,7 @@ class BaseNetQASMConnection(abc.ABC): # Global dict to track app names per node. # Currently only used for logging purposes, specifically only in - # `netqasm.runtime.process_logs._create_app_instr_logs`. + # `netqasm.runtime.process_logs.create_app_instr_logs`. # Dict[node_name, Dict[app_id, app_name]] _app_names: Dict[str, Dict[int, str]] = {} @@ -96,7 +96,7 @@ def __init__( app_id: Optional[int] = None, max_qubits: int = 5, hardware_config: Optional[HardwareConfig] = None, - log_config: LogConfig = None, + log_config: Optional[LogConfig] = None, epr_sockets: Optional[List[esck.EPRSocket]] = None, compiler: Optional[Type[SubroutineTranspiler]] = None, return_arrays: bool = True, @@ -308,6 +308,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): self.close( clear_app=self._clear_app_on_exit, stop_backend=self._stop_backend_on_exit, + exception=exc_type is not None, ) def _get_new_app_id(self, app_id: Optional[int]) -> int: @@ -342,13 +343,19 @@ def _pop_app_id(self) -> None: def clear(self) -> None: self._pop_app_id() - def close(self, clear_app: bool = True, stop_backend: bool = False) -> None: + def close( + self, + clear_app: bool = True, + stop_backend: bool = False, + exception: bool = False, + ) -> None: """Close a connection. By default, this method is automatically called when a connection context ends. """ - # Flush all pending commands - self.flush() + if not exception: + # Flush all pending commands + self.flush() self._pop_app_id() @@ -510,7 +517,7 @@ def compile(self) -> Optional[Subroutine]: in concrete values for templates. """ protosubroutine = self._builder.subrt_pop_pending_subroutine() - self._logger.info(f"Compiling protosubroutine:\n{protosubroutine}") + self._logger.debug(f"Compiling protosubroutine:\n{protosubroutine}") if protosubroutine is None: return None @@ -535,7 +542,7 @@ def commit_protosubroutine( # Parse, assembly and possibly compile the subroutine subroutine = self._builder.subrt_compile_subroutine(protosubroutine) - self._logger.info(f"Flushing compiled subroutine:\n{subroutine}") + self._logger.debug(f"Flushing compiled subroutine:\n{subroutine}") subroutine.instantiate(self.app_id) @@ -550,7 +557,7 @@ def commit_subroutine( block: bool = True, callback: Optional[Callable] = None, ) -> None: - self._logger.info(f"Committing compiled subroutine:\n{subroutine}") + self._logger.debug(f"Committing compiled subroutine:\n{subroutine}") self._commit_message( msg=SubroutineMessage(subroutine=subroutine), diff --git a/netqasm/sdk/epr_socket.py b/netqasm/sdk/epr_socket.py index f7063d72..7c137eec 100644 --- a/netqasm/sdk/epr_socket.py +++ b/netqasm/sdk/epr_socket.py @@ -273,8 +273,8 @@ def create_measure( number: int = 1, time_unit: TimeUnit = TimeUnit.MICRO_SECONDS, max_time: int = 0, - basis_local: EprMeasBasis = None, - basis_remote: EprMeasBasis = None, + basis_local: Optional[EprMeasBasis] = None, + basis_remote: Optional[EprMeasBasis] = None, rotations_local: Tuple[int, int, int] = (0, 0, 0), rotations_remote: Tuple[int, int, int] = (0, 0, 0), random_basis_local: Optional[RandomBasis] = None, @@ -363,7 +363,7 @@ def create_rsp( number: int = 1, time_unit: TimeUnit = TimeUnit.MICRO_SECONDS, max_time: int = 0, - basis_local: EprMeasBasis = None, + basis_local: Optional[EprMeasBasis] = None, rotations_local: Tuple[int, int, int] = (0, 0, 0), random_basis_local: Optional[RandomBasis] = None, min_fidelity_all_at_end: Optional[int] = None, @@ -451,8 +451,8 @@ def create( tp: EPRType = EPRType.K, time_unit: TimeUnit = TimeUnit.MICRO_SECONDS, max_time: int = 0, - basis_local: EprMeasBasis = None, - basis_remote: EprMeasBasis = None, + basis_local: Optional[EprMeasBasis] = None, + basis_remote: Optional[EprMeasBasis] = None, rotations_local: Tuple[int, int, int] = (0, 0, 0), rotations_remote: Tuple[int, int, int] = (0, 0, 0), random_basis_local: Optional[RandomBasis] = None, @@ -644,6 +644,7 @@ def recv_keep( post_routine: Optional[Callable] = None, sequential: bool = False, expect_phi_plus: bool = True, + expect_psi_plus: bool = False, min_fidelity_all_at_end: Optional[int] = None, max_tries: Optional[int] = None, ) -> List[Qubit]: @@ -681,6 +682,10 @@ def recv_keep( if self.conn is None: raise RuntimeError("EPRSocket does not have an open connection") + assert not ( + expect_phi_plus and expect_psi_plus + ), "cannot ask for both phi+ and psi+" + qubits, _ = self.conn._builder.sdk_recv_epr_keep( params=EntRequestParams( remote_node_id=self.remote_node_id, @@ -689,6 +694,7 @@ def recv_keep( post_routine=post_routine, sequential=sequential, expect_phi_plus=expect_phi_plus, + expect_psi_plus=expect_psi_plus, min_fidelity_all_at_end=min_fidelity_all_at_end, max_tries=max_tries, ), diff --git a/netqasm/sdk/transpile.py b/netqasm/sdk/transpile.py index d389990e..74b0ee2c 100644 --- a/netqasm/sdk/transpile.py +++ b/netqasm/sdk/transpile.py @@ -9,6 +9,7 @@ from typing import Dict, List, Optional, Set, Tuple, Union from netqasm.lang.instr import DebugInstruction, NetQASMInstruction, core, nv, vanilla +from netqasm.lang.instr.flavour import REIDSFlavour from netqasm.lang.operand import Immediate, Register, RegisterName from netqasm.lang.subroutine import Subroutine from netqasm.runtime.settings import get_is_using_hardware @@ -17,7 +18,8 @@ class SubroutineTranspiler(abc.ABC): def __init__(self, subroutine: Subroutine, debug: bool = False): - pass + self._subroutine: Subroutine = subroutine + self._debug: bool = debug @abc.abstractmethod def transpile(self) -> Subroutine: @@ -31,10 +33,9 @@ class NVSubroutineTranspiler(SubroutineTranspiler): """ def __init__(self, subroutine: Subroutine, debug=False): - self._subroutine: Subroutine = subroutine + super().__init__(subroutine, debug) self._used_registers: Set[Register] = set() self._register_values: Dict[Register, Immediate] = dict() - self._debug: bool = debug def get_reg_value(self, reg: Register) -> Immediate: """Get the value of a register at this moment""" @@ -579,7 +580,7 @@ def _map_single_gate( ), ] elif isinstance(instr, vanilla.RotZInstruction): - if get_is_using_hardware(): + if get_is_using_hardware() and instr.angle_denom.value != 4: imm0, imm1 = get_hardware_num_denom(instr) else: imm0, imm1 = instr.angle_num, instr.angle_denom @@ -589,7 +590,7 @@ def _map_single_gate( ), ] elif isinstance(instr, vanilla.RotXInstruction): - if get_is_using_hardware(): + if get_is_using_hardware() and instr.angle_denom.value != 4: imm0, imm1 = get_hardware_num_denom(instr) else: imm0, imm1 = instr.angle_num, instr.angle_denom @@ -599,7 +600,7 @@ def _map_single_gate( ), ] elif isinstance(instr, vanilla.RotYInstruction): - if get_is_using_hardware(): + if get_is_using_hardware() and instr.angle_denom.value != 4: imm0, imm1 = get_hardware_num_denom(instr) else: imm0, imm1 = instr.angle_num, instr.angle_denom @@ -614,6 +615,49 @@ def _map_single_gate( ) +class REIDSSubroutineTranspiler(SubroutineTranspiler): + """ + A transpiler that converts a subroutine with the vanilla flavour + to a subroutine with the REIDS flavour. + """ + + def __init__(self, subroutine: Subroutine, debug: bool = False): + super().__init__(subroutine, debug) + self._flavour = REIDSFlavour() + + def transpile(self) -> Subroutine: + add_no_op_at_end: bool = False + + for instr in self._subroutine.instructions: + try: + self._flavour.id_map[instr.id] + except KeyError as e: + raise ValueError( + f"Instruction {instr} not supported: Unsupported instruction for REIDS flavour." + ) from e + + if ( + isinstance(instr, core.BranchUnaryInstruction) + or isinstance(instr, core.BranchBinaryInstruction) + or isinstance(instr, core.JmpInstruction) + ): + original_line = instr.line.value + if original_line == len(self._subroutine.instructions): + # There was a label in the original subroutine at the very end. + # Since this label is now removed, we should put a "no-op" + # instruction there so there is something to jump to. + add_no_op_at_end = True + + if add_no_op_at_end: + self._subroutine.instructions += [ + core.SetInstruction( + lineno=None, reg=Register(RegisterName.C, 15), imm=Immediate(1337) + ) + ] + + return self._subroutine + + def get_hardware_num_denom( instr: core.RotationInstruction, ) -> Tuple[Immediate, Immediate]: diff --git a/setup.cfg b/setup.cfg index 7b3fa329..a23715ce 100644 --- a/setup.cfg +++ b/setup.cfg @@ -14,8 +14,8 @@ python_requires = >=3.7 install_requires = click >=8.0, <9.0 qlink-interface >=1.0, <2.0 - numpy >=1.22, <1.23 - scipy >=1.8, <1.9 + numpy >=1.22 + scipy >=1.8 pyyaml >=6.0, <7.0 [options.extras_require] @@ -25,7 +25,7 @@ dev = flake8 >=4.0, <5.0 isort >=5.10, <5.11 black >=22.3, <22.4 - mypy >=0.950, <0.951 + mypy >=0.950 squidasm = squidasm >=0.10 # requires extra pip arguments to install, see README diff --git a/tests/test_cli.py b/tests/test_cli.py index 216364a3..7be2fd65 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -3,10 +3,11 @@ from click.testing import CliRunner import netqasm -from netqasm.runtime.cli import cli from netqasm.runtime.env import EXAMPLE_APPS_DIR from netqasm.util.yaml import load_yaml +from netqasm.runtime.cli import cli # type: ignore # TODO: Fix mypy not seeing cli in cli.py # isort:skip + TEMPLATE_EXAMPLE_NAME = "teleport" TEMPLATE_EXAMPLE_DIR = os.path.join(EXAMPLE_APPS_DIR, TEMPLATE_EXAMPLE_NAME) diff --git a/tests/test_sdk/test_connection.py b/tests/test_sdk/test_connection.py index cf823827..577f5b8f 100644 --- a/tests/test_sdk/test_connection.py +++ b/tests/test_sdk/test_connection.py @@ -12,6 +12,7 @@ from netqasm.lang.version import NETQASM_VERSION from netqasm.logging.glob import set_log_level from netqasm.qlink_compat import EPRType, TimeUnit +from netqasm.runtime.settings import set_is_using_hardware from netqasm.sdk.connection import DebugConnection from netqasm.sdk.epr_socket import EPRSocket from netqasm.sdk.qubit import Qubit @@ -297,6 +298,91 @@ def test_epr_k_recv(): print(expected) +def test_epr_k_recv_hardware(): + set_is_using_hardware(True) + + set_log_level(logging.DEBUG) + + epr_socket = EPRSocket(remote_app_name="Bob") + with DebugConnection("Alice", epr_sockets=[epr_socket]) as alice: + q1 = epr_socket.recv_keep()[0] + q1.H() + + # 5 messages: init, open_epr_socket, subroutine, stop app and stop backend + assert len(alice.storage) == 5 + raw_subroutine = deserialize_message(raw=alice.storage[2]).subroutine + subroutine = deserialize_subroutine(raw_subroutine) + print(subroutine) + + expected_text = """ +# NETQASM 0.0 +# APPID 0 +set R5 10 +array R5 @0 +set R5 1 +array R5 @1 +set R5 0 +set R6 0 +store R5 @1[R6] +set R5 1 +set R6 0 +set R7 1 +set R8 0 +recv_epr R5 R6 R7 R8 +set R5 0 +set R6 10 +wait_all @0[R5:R6] +set R2 0 +set R5 1 +beq R2 R5 46 +load R0 @1[R2] +set R3 9 +set R4 0 +beq R4 R2 27 +set R5 10 +add R3 R3 R5 +set R5 1 +add R4 R4 R5 +jmp 21 +load R1 @0[R3] +set R0 0 +set R5 3 +bne R1 R5 34 +rot_y R0 8 4 +rot_x R0 16 4 +rot_y R0 24 4 +set R5 1 +bne R1 R5 37 +rot_x R0 16 4 +set R5 2 +bne R1 R5 43 +rot_x R0 16 4 +rot_y R0 8 4 +rot_x R0 16 4 +rot_y R0 24 4 +set R5 1 +add R2 R2 R5 +jmp 16 +set Q0 0 +h Q0 +ret_arr @0 +ret_arr @1 +""" + + expected = parse_text_subroutine(expected_text) + + for i, instr in enumerate(subroutine.instructions): + print(repr(instr)) + expected_instr = expected.instructions[i] + print(repr(expected_instr)) + print() + assert instr == expected_instr + print(subroutine) + print(expected) + + set_is_using_hardware(False) + + def test_two_epr_k_create(): set_log_level(logging.DEBUG)