diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index f1b5a1ffbd..6c959cbe70 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -76,7 +76,7 @@ jobs: pip install -r dev_tools/requirements/envs/pytest.env.txt pip install --no-deps -e . - run: | - python dev_tools/execute-notebooks.py --n-workers=8 + check/pytest-notebook env: NUMBA_NUM_THREADS: 4 diff --git a/check/pytest-notebook b/check/pytest-notebook new file mode 100755 index 0000000000..2698674896 --- /dev/null +++ b/check/pytest-notebook @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +################################################################################ +# Runs notebook tests via jupytext-based execution. +################################################################################ + +# Get the working directory to the repo root. +thisdir="$(dirname "${BASH_SOURCE[0]}")" || exit $? +topdir="$(git -C "${thisdir}" rev-parse --show-toplevel)" || exit $? +cd "${topdir}" || exit $? + +set -e + +python dev_tools/check-notebook-tests.py + +# Use non-interactive backend so plt.show() doesn't open GUI windows. +export MPLBACKEND=agg + +pytest -v qualtran/ tutorials/ \ + -m notebook diff --git a/dev_tools/check-notebook-tests.py b/dev_tools/check-notebook-tests.py new file mode 100644 index 0000000000..c987384415 --- /dev/null +++ b/dev_tools/check-notebook-tests.py @@ -0,0 +1,149 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Verify that every committed .ipynb has a corresponding pytest notebook test. + +For each committed notebook, checks that: +1. A *_test.py file exists containing an execute_notebook('name') call +2. That test function is decorated with @pytest.mark.notebook + +Usage: + python dev_tools/check-notebook-tests.py +""" + +import ast +import subprocess +import sys +from pathlib import Path +from typing import cast, Dict, Tuple + +from qualtran_dev_tools.git_tools import get_git_root + +_EXCLUDED_DIRS = {'dev_tools'} + + +def get_committed_notebooks(reporoot: Path) -> Dict[str, Path]: + """Return {stem: relative_path} for all committed .ipynb files under reporoot. + + Excludes notebooks in dev_tools/ since those are developer utilities, + not user-facing documentation. + """ + result = subprocess.run( + ['git', 'ls-files', '--', '**/*.ipynb'], + capture_output=True, + text=True, + check=True, + cwd=reporoot, + ) + return { + Path(f).stem: Path(f) + for f in result.stdout.strip().split('\n') + if f and not any(Path(f).parts[0] == d for d in _EXCLUDED_DIRS) + } + + +def _is_notebook_marker(decorator: ast.expr) -> bool: + """Check if a decorator is @pytest.mark.notebook.""" + # Handle pytest.mark.notebook (attr chain) + if isinstance(decorator, ast.Attribute) and decorator.attr == 'notebook': + return True + return False + + +def find_notebook_tests(reporoot: Path) -> Dict[str, Tuple[Path, bool]]: + """Find all execute_notebook() calls in test files. + + Searches all *_test.py files under the repo root (including qualtran/ + and tutorials/). + + Returns {notebook_name: (test_file_path_relative, has_notebook_marker)}. + """ + results: Dict[str, Tuple[Path, bool]] = {} + for test_file in reporoot.rglob('*_test.py'): + try: + tree = ast.parse(test_file.read_text()) + except SyntaxError: + continue + + for node in ast.walk(tree): + if not isinstance(node, ast.FunctionDef): + continue + # Check if function body contains execute_notebook('xxx') + for child in ast.walk(node): + if ( + isinstance(child, ast.Call) + and _is_execute_notebook_call(child) + and child.args + and isinstance(child.args[0], ast.Constant) + ): + nb_name: str = cast(str, child.args[0].value) + # Check for @pytest.mark.notebook decorator + has_marker = any(_is_notebook_marker(dec) for dec in node.decorator_list) + results[nb_name] = (test_file.relative_to(reporoot), has_marker) + return results + + +def _is_execute_notebook_call(node: ast.Call) -> bool: + """Check if a Call node is a call to execute_notebook (with or without module prefix).""" + if isinstance(node.func, ast.Attribute) and node.func.attr == 'execute_notebook': + return True + if isinstance(node.func, ast.Name) and node.func.id == 'execute_notebook': + return True + return False + + +def main(): + reporoot = get_git_root() + + committed = get_committed_notebooks(reporoot) + tested = find_notebook_tests(reporoot) + + errors = [] + + for stem, nb_rel_path in sorted(committed.items()): + if stem not in tested: + # Suggest the likely test file location + nb_dir = nb_rel_path.parent + test_file = nb_dir / f'{stem}_test.py' + errors.append( + f" MISSING TEST: {nb_rel_path}\n" + f" Add to {test_file}:\n" + f"\n" + f" @pytest.mark.notebook\n" + f" def test_{stem}_notebook():\n" + f" qlt_testing.execute_notebook('{stem}')\n" + ) + else: + test_file, has_marker = tested[stem] + if not has_marker: + errors.append( + f" MISSING MARKER: {nb_rel_path}\n" + f" The test in {test_file} calls execute_notebook('{stem}')\n" + f" but is not decorated with @pytest.mark.notebook.\n" + f" Add the decorator so this test runs in the notebooks CI job:\n" + f"\n" + f" @pytest.mark.notebook\n" + f" def test_...():\n" + ) + + if errors: + print(f"ERROR: {len(errors)} notebook(s) have issues:\n") + print("\n".join(errors)) + sys.exit(1) + + print(f"OK: All {len(committed)} notebooks have properly marked tests.") + + +if __name__ == '__main__': + main() diff --git a/dev_tools/conf/mypy.ini b/dev_tools/conf/mypy.ini index 73963ae15f..e62338d3b4 100644 --- a/dev_tools/conf/mypy.ini +++ b/dev_tools/conf/mypy.ini @@ -21,7 +21,7 @@ follow_imports = silent ignore_missing_imports = true # Non-Google -[mypy-sympy.*,matplotlib.*,proto.*,pandas.*,scipy.*,freezegun.*,mpl_toolkits.*,networkx.*,ply.*,astroid.*,pytest.*,_pytest.*,pylint.*,setuptools.*,qiskit.*,quimb.*,pylatex.*,filelock.*,sortedcontainers.*,tqdm.*,plotly.*,dash.*,tensorflow_docs.*,fxpmath.*,ipywidgets.*,cachetools.*,pydot.*,nbformat.*,nbconvert.*,openfermion.*,pennylane.*,mpmath.*] +[mypy-sympy.*,matplotlib.*,proto.*,pandas.*,scipy.*,freezegun.*,mpl_toolkits.*,networkx.*,ply.*,astroid.*,pytest.*,_pytest.*,pylint.*,setuptools.*,qiskit.*,quimb.*,pylatex.*,filelock.*,sortedcontainers.*,tqdm.*,plotly.*,dash.*,tensorflow_docs.*,fxpmath.*,ipywidgets.*,cachetools.*,pydot.*,nbformat.*,nbconvert.*,openfermion.*,pennylane.*,mpmath.*,jupytext.*] follow_imports = silent ignore_missing_imports = true diff --git a/dev_tools/requirements/envs/dev.env.txt b/dev_tools/requirements/envs/dev.env.txt index bb50765f14..fde6f0d476 100644 --- a/dev_tools/requirements/envs/dev.env.txt +++ b/dev_tools/requirements/envs/dev.env.txt @@ -294,6 +294,7 @@ jupyterlab-server==2.28.0 # notebook jupyterlab-widgets==3.0.16 # via ipywidgets +jupytext==1.19.1 kiwisolver==1.5.0 # via matplotlib lark==1.3.1 @@ -306,6 +307,7 @@ llvmlite==0.47.0 # via numba markdown-it-py==4.0.0 # via + # jupytext # mdit-py-plugins # myst-parser markupsafe==3.0.3 @@ -326,7 +328,9 @@ matplotlib-inline==0.2.1 mccabe==0.7.0 # via pylint mdit-py-plugins==0.5.0 - # via myst-parser + # via + # jupytext + # myst-parser mdurl==0.1.2 # via markdown-it-py mistune==3.2.0 @@ -363,6 +367,7 @@ nbformat==5.10.4 # via # jupyter-cache # jupyter-server + # jupytext # myst-nb # nbclient # nbconvert @@ -425,6 +430,7 @@ packaging==26.0 # jupyter-server # jupyterlab # jupyterlab-server + # jupytext # matplotlib # nbconvert # pennylane @@ -544,6 +550,7 @@ pyyaml==6.0.3 # via # jupyter-cache # jupyter-events + # jupytext # myst-nb # myst-parser # tensorflow-docs diff --git a/dev_tools/requirements/envs/pylint.env.txt b/dev_tools/requirements/envs/pylint.env.txt index 859c79a894..72ea297c59 100644 --- a/dev_tools/requirements/envs/pylint.env.txt +++ b/dev_tools/requirements/envs/pylint.env.txt @@ -256,6 +256,7 @@ jupyterlab-server==2.28.0 # notebook jupyterlab-widgets==3.0.16 # via ipywidgets +jupytext==1.19.1 kiwisolver==1.5.0 # via matplotlib lark==1.3.1 @@ -265,7 +266,9 @@ lark==1.3.1 llvmlite==0.47.0 # via numba markdown-it-py==4.0.0 - # via mdit-py-plugins + # via + # jupytext + # mdit-py-plugins markupsafe==3.0.3 # via # flask @@ -284,6 +287,7 @@ matplotlib-inline==0.2.1 mccabe==0.7.0 # via pylint mdit-py-plugins==0.5.0 + # via jupytext mdurl==0.1.2 # via markdown-it-py mistune==3.2.0 @@ -307,6 +311,7 @@ nbconvert==7.17.0 nbformat==5.10.4 # via # jupyter-server + # jupytext # nbclient # nbconvert # qualtran @@ -367,6 +372,7 @@ packaging==26.0 # jupyter-server # jupyterlab # jupyterlab-server + # jupytext # matplotlib # nbconvert # pennylane @@ -467,6 +473,7 @@ pywinpty==3.0.3 ; os_name == 'nt' pyyaml==6.0.3 # via # jupyter-events + # jupytext # tensorflow-docs pyzmq==27.1.0 # via diff --git a/dev_tools/requirements/envs/pytest.env.txt b/dev_tools/requirements/envs/pytest.env.txt index 19658ad258..2b83deee01 100644 --- a/dev_tools/requirements/envs/pytest.env.txt +++ b/dev_tools/requirements/envs/pytest.env.txt @@ -236,6 +236,7 @@ jupyterlab-server==2.28.0 # notebook jupyterlab-widgets==3.0.16 # via ipywidgets +jupytext==1.19.1 kiwisolver==1.5.0 # via matplotlib lark==1.3.1 @@ -244,6 +245,10 @@ lark==1.3.1 # rfc3987-syntax llvmlite==0.47.0 # via numba +markdown-it-py==4.0.0 + # via + # jupytext + # mdit-py-plugins markupsafe==3.0.3 # via # flask @@ -259,6 +264,10 @@ matplotlib-inline==0.2.1 # via # ipykernel # ipython +mdit-py-plugins==0.5.0 + # via jupytext +mdurl==0.1.2 + # via markdown-it-py mistune==3.2.0 # via nbconvert ml-dtypes==0.5.4 @@ -280,6 +289,7 @@ nbconvert==7.17.0 nbformat==5.10.4 # via # jupyter-server + # jupytext # nbclient # nbconvert # qualtran @@ -339,6 +349,7 @@ packaging==26.0 # jupyter-server # jupyterlab # jupyterlab-server + # jupytext # matplotlib # nbconvert # pennylane @@ -439,7 +450,9 @@ pywinpty==3.0.3 ; os_name == 'nt' # jupyter-server-terminals # terminado pyyaml==6.0.3 - # via jupyter-events + # via + # jupyter-events + # jupytext pyzmq==27.1.0 # via # ipykernel diff --git a/pyproject.toml b/pyproject.toml index 6e24e28592..df778dbcba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,6 +79,7 @@ test = [ "pytest-xdist", # test executing notebooks + "jupytext", "ipykernel", "filelock", @@ -99,6 +100,7 @@ lint = [ # for checking _test.py files "pytest", "openfermion[resources]", + "jupytext", # dev tools "tensorflow-docs", diff --git a/qualtran/Adjoint_test.py b/qualtran/Adjoint_test.py new file mode 100644 index 0000000000..f51df0427a --- /dev/null +++ b/qualtran/Adjoint_test.py @@ -0,0 +1,22 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from qualtran import testing as qlt_testing + + +@pytest.mark.notebook +def test_Adjoint_notebook(): + qlt_testing.execute_notebook('Adjoint') diff --git a/qualtran/Autodoc_test.py b/qualtran/Autodoc_test.py new file mode 100644 index 0000000000..7e6d79a307 --- /dev/null +++ b/qualtran/Autodoc_test.py @@ -0,0 +1,22 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from qualtran import testing as qlt_testing + + +@pytest.mark.notebook +def test_Autodoc_notebook(): + qlt_testing.execute_notebook('Autodoc') diff --git a/qualtran/Controlled_test.py b/qualtran/Controlled_test.py new file mode 100644 index 0000000000..2196f1fe88 --- /dev/null +++ b/qualtran/Controlled_test.py @@ -0,0 +1,22 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from qualtran import testing as qlt_testing + + +@pytest.mark.notebook +def test_Controlled_notebook(): + qlt_testing.execute_notebook('Controlled') diff --git a/qualtran/DataTypes_test.py b/qualtran/DataTypes_test.py new file mode 100644 index 0000000000..8afafa1098 --- /dev/null +++ b/qualtran/DataTypes_test.py @@ -0,0 +1,22 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from qualtran import testing as qlt_testing + + +@pytest.mark.notebook +def test_DataTypes_notebook(): + qlt_testing.execute_notebook('DataTypes') diff --git a/qualtran/Protocols_test.py b/qualtran/Protocols_test.py new file mode 100644 index 0000000000..d354cfd021 --- /dev/null +++ b/qualtran/Protocols_test.py @@ -0,0 +1,22 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from qualtran import testing as qlt_testing + + +@pytest.mark.notebook +def test_Protocols_notebook(): + qlt_testing.execute_notebook('Protocols') diff --git a/qualtran/bloqs/arithmetic/bitwise_test.py b/qualtran/bloqs/arithmetic/bitwise_test.py index 4de3ab11de..6a0c1ad7b0 100644 --- a/qualtran/bloqs/arithmetic/bitwise_test.py +++ b/qualtran/bloqs/arithmetic/bitwise_test.py @@ -189,3 +189,8 @@ def test_bitwisenot_classical_action(dtype, bitsize): def test_bitwise_not_str(): bloq = BitwiseNot(QUInt(5)) assert str(bloq) == "BitwiseNot(5)" + + +@pytest.mark.notebook +def test_bitwise_notebook(): + qlt_testing.execute_notebook('bitwise') diff --git a/qualtran/bloqs/arithmetic/controlled_add_or_subtract_test.py b/qualtran/bloqs/arithmetic/controlled_add_or_subtract_test.py index b0bc8c8a0a..6f2e606edd 100644 --- a/qualtran/bloqs/arithmetic/controlled_add_or_subtract_test.py +++ b/qualtran/bloqs/arithmetic/controlled_add_or_subtract_test.py @@ -29,6 +29,7 @@ Soquet, SoquetT, ) +from qualtran import testing as qlt_testing from qualtran.bloqs.arithmetic import Add, Negate, Subtract from qualtran.bloqs.arithmetic.controlled_add_or_subtract import ( _ctrl_add_or_sub_signed, @@ -130,3 +131,8 @@ def test_controlled_t_complexity(): _ = bloq.controlled().adjoint().t_complexity() _ = bloq.adjoint().controlled().t_complexity() + + +@pytest.mark.notebook +def test_controlled_add_or_subtract_notebook(): + qlt_testing.execute_notebook('controlled_add_or_subtract') diff --git a/qualtran/bloqs/arithmetic/conversions/contiguous_index_test.py b/qualtran/bloqs/arithmetic/conversions/contiguous_index_test.py index 01d5618ecd..388ba8efa5 100644 --- a/qualtran/bloqs/arithmetic/conversions/contiguous_index_test.py +++ b/qualtran/bloqs/arithmetic/conversions/contiguous_index_test.py @@ -11,7 +11,10 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +import pytest + from qualtran import BloqBuilder +from qualtran import testing as qlt_testing from qualtran.bloqs.arithmetic.conversions.contiguous_index import ( _to_contg_index, ToContiguousIndex, @@ -31,3 +34,8 @@ def test_to_contiguous_index_t_complexity(): q0, q1, out = bb.add(ToContiguousIndex(bitsize, 2 * bitsize), mu=q0, nu=q1, s=out) cbloq = bb.finalize(mu=q0, nu=q1, s=out) assert cbloq.t_complexity().t == 4 * 29 + + +@pytest.mark.notebook +def test_contiguous_index_notebook(): + qlt_testing.execute_notebook('contiguous_index') diff --git a/qualtran/bloqs/arithmetic/conversions/conversions_test.py b/qualtran/bloqs/arithmetic/conversions/conversions_test.py new file mode 100644 index 0000000000..0f3d5115d5 --- /dev/null +++ b/qualtran/bloqs/arithmetic/conversions/conversions_test.py @@ -0,0 +1,22 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from qualtran import testing as qlt_testing + + +@pytest.mark.notebook +def test_conversions_notebook(): + qlt_testing.execute_notebook('conversions') diff --git a/qualtran/bloqs/arithmetic/error_analysis_for_fxp_arithmetic_test.py b/qualtran/bloqs/arithmetic/error_analysis_for_fxp_arithmetic_test.py new file mode 100644 index 0000000000..e3630cd11e --- /dev/null +++ b/qualtran/bloqs/arithmetic/error_analysis_for_fxp_arithmetic_test.py @@ -0,0 +1,22 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from qualtran import testing as qlt_testing + + +@pytest.mark.notebook +def test_error_analysis_for_fxp_arithmetic_notebook(): + qlt_testing.execute_notebook('error_analysis_for_fxp_arithmetic') diff --git a/qualtran/bloqs/arithmetic/negate_test.py b/qualtran/bloqs/arithmetic/negate_test.py index b6ee06978a..ac492133de 100644 --- a/qualtran/bloqs/arithmetic/negate_test.py +++ b/qualtran/bloqs/arithmetic/negate_test.py @@ -14,6 +14,7 @@ import pytest from qualtran import QInt, QUInt +from qualtran import testing as qlt_testing from qualtran.bloqs.arithmetic.negate import _negate, _negate_symb, Negate @@ -42,3 +43,8 @@ def _uint_to_int(val: int) -> int: else: # all other values assert neg_x == -x + + +@pytest.mark.notebook +def test_negate_notebook(): + qlt_testing.execute_notebook('negate') diff --git a/qualtran/bloqs/arithmetic/permutation_test.py b/qualtran/bloqs/arithmetic/permutation_test.py index b5d84a16ec..b25c57de25 100644 --- a/qualtran/bloqs/arithmetic/permutation_test.py +++ b/qualtran/bloqs/arithmetic/permutation_test.py @@ -164,3 +164,8 @@ def test_permutation_symbolic_call_graph(): And(): (N + 1) * (logN - 1), CNOT(): N * logN + N + 1, } + + +@pytest.mark.notebook +def test_permutation_notebook(): + qlt_testing.execute_notebook('permutation') diff --git a/qualtran/bloqs/arithmetic/subtraction_test.py b/qualtran/bloqs/arithmetic/subtraction_test.py index fe9a5cb48e..cae7cc32ca 100644 --- a/qualtran/bloqs/arithmetic/subtraction_test.py +++ b/qualtran/bloqs/arithmetic/subtraction_test.py @@ -169,3 +169,8 @@ def test_subtractfrom_classical_action(bitsize): qlt_testing.assert_consistent_classical_action( blq, a=tuple(dtype.get_classical_domain()), b=tuple(dtype.get_classical_domain()) ) + + +@pytest.mark.notebook +def test_subtraction_notebook(): + qlt_testing.execute_notebook('subtraction') diff --git a/qualtran/bloqs/arithmetic/trigonometric/trigonometric_test.py b/qualtran/bloqs/arithmetic/trigonometric/trigonometric_test.py new file mode 100644 index 0000000000..2e6d2e4a37 --- /dev/null +++ b/qualtran/bloqs/arithmetic/trigonometric/trigonometric_test.py @@ -0,0 +1,22 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from qualtran import testing as qlt_testing + + +@pytest.mark.notebook +def test_trigonometric_notebook(): + qlt_testing.execute_notebook('trigonometric') diff --git a/qualtran/bloqs/basic_gates/cnot_test.py b/qualtran/bloqs/basic_gates/cnot_test.py index cb4f3767b8..00354c2d35 100644 --- a/qualtran/bloqs/basic_gates/cnot_test.py +++ b/qualtran/bloqs/basic_gates/cnot_test.py @@ -18,6 +18,7 @@ import pytest from qualtran import BloqBuilder, Signature +from qualtran import testing as qlt_testing from qualtran.bloqs.basic_gates import CNOT, PlusState, ZeroState from qualtran.bloqs.basic_gates.cnot import _cnot from qualtran.drawing import get_musical_score_data @@ -130,3 +131,8 @@ def test_cnot_ctrl_system(): ctrl[1] -> RightDangle.ctrl target -> RightDangle.target""" ) + + +@pytest.mark.notebook +def test_cnot_notebook(): + qlt_testing.execute_notebook('cnot') diff --git a/qualtran/bloqs/basic_gates/diag_gates_test.py b/qualtran/bloqs/basic_gates/diag_gates_test.py new file mode 100644 index 0000000000..69e19f8afe --- /dev/null +++ b/qualtran/bloqs/basic_gates/diag_gates_test.py @@ -0,0 +1,22 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from qualtran import testing as qlt_testing + + +@pytest.mark.notebook +def test_diag_gates_notebook(): + qlt_testing.execute_notebook('diag_gates') diff --git a/qualtran/bloqs/basic_gates/global_phase_test.py b/qualtran/bloqs/basic_gates/global_phase_test.py index d7e65eb58d..10e60e37d9 100644 --- a/qualtran/bloqs/basic_gates/global_phase_test.py +++ b/qualtran/bloqs/basic_gates/global_phase_test.py @@ -18,6 +18,7 @@ import pytest from qualtran import CtrlSpec +from qualtran import testing as qlt_testing from qualtran.bloqs.basic_gates.global_phase import _global_phase, GlobalPhase from qualtran.cirq_interop import cirq_gate_to_bloq from qualtran.cirq_interop.t_complexity_protocol import TComplexity @@ -82,3 +83,8 @@ def test_global_phase(bloq_autotester): def test_global_phase_str(): bloq = GlobalPhase(exponent=0.5) assert str(bloq) == "GPhase(0.5)" + + +@pytest.mark.notebook +def test_global_phase_notebook(): + qlt_testing.execute_notebook('global_phase') diff --git a/qualtran/bloqs/basic_gates/hadamard_test.py b/qualtran/bloqs/basic_gates/hadamard_test.py index 9af68dcaca..42e2f1f5b1 100644 --- a/qualtran/bloqs/basic_gates/hadamard_test.py +++ b/qualtran/bloqs/basic_gates/hadamard_test.py @@ -16,6 +16,7 @@ import pytest from qualtran import BloqBuilder +from qualtran import testing as qlt_testing from qualtran.bloqs.basic_gates import Hadamard, OneEffect, OneState from qualtran.bloqs.basic_gates.hadamard import _hadamard, CHadamard from qualtran.cirq_interop import cirq_gate_to_bloq @@ -103,3 +104,8 @@ def test_chadamard_adjoint(): cbloq = bb.finalize(ctrl=ctrl, q=q) np.testing.assert_allclose(np.eye(4), cbloq.tensor_contract(), atol=1e-12) + + +@pytest.mark.notebook +def test_hadamard_notebook(): + qlt_testing.execute_notebook('hadamard') diff --git a/qualtran/bloqs/basic_gates/rotation_test.py b/qualtran/bloqs/basic_gates/rotation_test.py index 38b4cef50c..edc2f708f3 100644 --- a/qualtran/bloqs/basic_gates/rotation_test.py +++ b/qualtran/bloqs/basic_gates/rotation_test.py @@ -18,6 +18,7 @@ from cirq.ops import SimpleQubitManager from qualtran import BloqBuilder, Controlled, CtrlSpec +from qualtran import testing as qlt_testing from qualtran._infra.gate_with_registers import get_named_qubits from qualtran.bloqs.basic_gates import ( CZ, @@ -305,3 +306,8 @@ def test_cry(): # testing the specialized ctrl ctrl, bloq_with_ctrl = ry.get_ctrl_system(CtrlSpec()) assert ctrl == CRy(angle=angle) + + +@pytest.mark.notebook +def test_rotation_notebook(): + qlt_testing.execute_notebook('rotation') diff --git a/qualtran/bloqs/basic_gates/su2_rotation_test.py b/qualtran/bloqs/basic_gates/su2_rotation_test.py index 61b4d09dbc..77ee71a16e 100644 --- a/qualtran/bloqs/basic_gates/su2_rotation_test.py +++ b/qualtran/bloqs/basic_gates/su2_rotation_test.py @@ -13,9 +13,11 @@ # limitations under the License. import cirq import numpy as np +import pytest import sympy from qualtran import Bloq +from qualtran import testing as qlt_testing from qualtran.bloqs.basic_gates import GlobalPhase, Hadamard, Rx, Rz, TGate, XGate, YGate, ZGate from .su2_rotation import _hadamard, _su2_rotation_gate, _t_gate, SU2RotationGate @@ -86,3 +88,8 @@ def test_call_graph(): Rz(-lambd + pi / 2, eps=eps / 3): 1, Rx(2 * theta, eps / 3): 1, } + + +@pytest.mark.notebook +def test_su2_rotation_notebook(): + qlt_testing.execute_notebook('su2_rotation') diff --git a/qualtran/bloqs/basic_gates/swap_test.py b/qualtran/bloqs/basic_gates/swap_test.py index 9f5d563a9e..1552290d15 100644 --- a/qualtran/bloqs/basic_gates/swap_test.py +++ b/qualtran/bloqs/basic_gates/swap_test.py @@ -269,3 +269,8 @@ def test_cswap_symb(bloq_autotester): if bloq_autotester.check_name == 'serialize': pytest.skip("Sympy equality with assumptions.") bloq_autotester(_cswap) + + +@pytest.mark.notebook +def test_swap_notebook(): + qlt_testing.execute_notebook('swap') diff --git a/qualtran/bloqs/basic_gates/t_gate_test.py b/qualtran/bloqs/basic_gates/t_gate_test.py index 3bfc107c31..6f5fab69c2 100644 --- a/qualtran/bloqs/basic_gates/t_gate_test.py +++ b/qualtran/bloqs/basic_gates/t_gate_test.py @@ -16,8 +16,10 @@ import cirq import numpy as np +import pytest from qualtran import Bloq, BloqBuilder, Signature, SoquetT +from qualtran import testing as qlt_testing from qualtran.bloqs.basic_gates import Hadamard, PlusState, TGate from qualtran.bloqs.basic_gates.t_gate import _t_gate from qualtran.cirq_interop.t_complexity_protocol import TComplexity @@ -93,3 +95,8 @@ def test_test_t_state_tensor_adjoint(): unitary = TestTStateMaker().tensor_contract() adj_unitary = TestTStateMaker().adjoint().tensor_contract() np.testing.assert_allclose(unitary.conj().T, adj_unitary) + + +@pytest.mark.notebook +def test_t_gate_notebook(): + qlt_testing.execute_notebook('t_gate') diff --git a/qualtran/bloqs/basic_gates/toffoli_test.py b/qualtran/bloqs/basic_gates/toffoli_test.py index ff83e79311..3208068734 100644 --- a/qualtran/bloqs/basic_gates/toffoli_test.py +++ b/qualtran/bloqs/basic_gates/toffoli_test.py @@ -15,6 +15,7 @@ import cirq import numpy as np +import pytest import qualtran.testing as qlt_testing from qualtran import BloqBuilder, CtrlSpec, QBit, Register, Signature @@ -138,3 +139,8 @@ def test_toffoli_controlled_2(): assert c0t.signature == Signature( [Register('ctrl1', QBit()), Register('ctrl2', QBit(), shape=(2,)), Register('q', QBit())] ) + + +@pytest.mark.notebook +def test_toffoli_notebook(): + qlt_testing.execute_notebook('toffoli') diff --git a/qualtran/bloqs/basic_gates/y_gate_test.py b/qualtran/bloqs/basic_gates/y_gate_test.py index 9c7dab6b15..79e9340fec 100644 --- a/qualtran/bloqs/basic_gates/y_gate_test.py +++ b/qualtran/bloqs/basic_gates/y_gate_test.py @@ -13,8 +13,10 @@ # limitations under the License. import cirq import numpy as np +import pytest from qualtran import BloqBuilder +from qualtran import testing as qlt_testing from qualtran.bloqs.basic_gates import MinusState, OneEffect, OneState, PlusState, YGate from qualtran.bloqs.basic_gates.y_gate import _cy_gate, _y_gate, CYGate from qualtran.cirq_interop import cirq_gate_to_bloq @@ -108,3 +110,8 @@ def test_cy_adjoint(): def test_cy_t_complexity(): assert t_complexity(CYGate()) == TComplexity(clifford=1) + + +@pytest.mark.notebook +def test_y_gate_notebook(): + qlt_testing.execute_notebook('y_gate') diff --git a/qualtran/bloqs/block_encoding/sparse_matrix_hermitian_test.py b/qualtran/bloqs/block_encoding/sparse_matrix_hermitian_test.py index 367de6a3ae..044e9f9757 100644 --- a/qualtran/bloqs/block_encoding/sparse_matrix_hermitian_test.py +++ b/qualtran/bloqs/block_encoding/sparse_matrix_hermitian_test.py @@ -14,6 +14,7 @@ from typing import cast import numpy as np +import pytest import sympy import qualtran.testing as qlt_testing @@ -116,3 +117,8 @@ def test_counts(): qlt_testing.assert_equivalent_bloq_counts( _sparse_matrix_hermitian_block_encoding().controlled(), generalizer=ignore_split_join ) + + +@pytest.mark.notebook +def test_sparse_matrix_hermitian_notebook(): + qlt_testing.execute_notebook('sparse_matrix_hermitian') diff --git a/qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard_test.py b/qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard_test.py index c3307b5f56..a179caa212 100644 --- a/qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard_test.py +++ b/qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard_test.py @@ -142,3 +142,8 @@ def test_hubbard_spin_up_z_classical(): # The only place a phase should be applied is at our chosen coordinate. assert negative_phases == [(the_x, the_y)] + + +@pytest.mark.notebook +def test_select_hubbard_notebook(): + qlt_testing.execute_notebook('select_hubbard') diff --git a/qualtran/bloqs/chemistry/trotter/hubbard/hubbard_test.py b/qualtran/bloqs/chemistry/trotter/hubbard/hubbard_test.py new file mode 100644 index 0000000000..a3131377dc --- /dev/null +++ b/qualtran/bloqs/chemistry/trotter/hubbard/hubbard_test.py @@ -0,0 +1,22 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from qualtran import testing as qlt_testing + + +@pytest.mark.notebook +def test_hubbard_notebook(): + qlt_testing.execute_notebook('hubbard') diff --git a/qualtran/bloqs/chemistry/trotter/ising/ising_test.py b/qualtran/bloqs/chemistry/trotter/ising/ising_test.py new file mode 100644 index 0000000000..5cd8e16d78 --- /dev/null +++ b/qualtran/bloqs/chemistry/trotter/ising/ising_test.py @@ -0,0 +1,22 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from qualtran import testing as qlt_testing + + +@pytest.mark.notebook +def test_ising_notebook(): + qlt_testing.execute_notebook('ising') diff --git a/qualtran/bloqs/chemistry/trotter/trotterized_unitary_test.py b/qualtran/bloqs/chemistry/trotter/trotterized_unitary_test.py index d0531b7690..13f1ba8434 100644 --- a/qualtran/bloqs/chemistry/trotter/trotterized_unitary_test.py +++ b/qualtran/bloqs/chemistry/trotter/trotterized_unitary_test.py @@ -16,6 +16,7 @@ import pytest from qualtran import Bloq, Signature +from qualtran import testing as qlt_testing from qualtran.bloqs.chemistry.trotter.ising import IsingXUnitary, IsingZZUnitary from qualtran.bloqs.chemistry.trotter.trotterized_unitary import _trott_unitary, TrotterizedUnitary @@ -129,3 +130,8 @@ def test_trotterized_unitary_tensor_contract_suzuki_4(nsites): suzuki = TrotterizedUnitary(bloqs=bloqs, indices=indices, coeffs=coeffs, timestep=dt) bloq_step = suzuki.tensor_contract() np.testing.assert_allclose(bloq_step, ref_step) + + +@pytest.mark.notebook +def test_trotterized_unitary_notebook(): + qlt_testing.execute_notebook('trotterized_unitary') diff --git a/qualtran/bloqs/data_loading/qroam_clean_test.py b/qualtran/bloqs/data_loading/qroam_clean_test.py index c4e5ce6d7e..efd6064105 100644 --- a/qualtran/bloqs/data_loading/qroam_clean_test.py +++ b/qualtran/bloqs/data_loading/qroam_clean_test.py @@ -15,6 +15,7 @@ import pytest import sympy +from qualtran import testing as qlt_testing from qualtran._infra.data_types import QAny from qualtran._infra.registers import Register from qualtran.bloqs.data_loading.qroam_clean import ( @@ -227,3 +228,8 @@ def test_qroam_clean_small_bloq_counts(): qroam_clean=qroam_clean, log_block_sizes=(0,) ) assert qroam_clean_adj_wrapper.t_complexity().t == 0 + + +@pytest.mark.notebook +def test_qroam_clean_notebook(): + qlt_testing.execute_notebook('qroam_clean') diff --git a/qualtran/bloqs/data_loading/select_swap_qrom_test.py b/qualtran/bloqs/data_loading/select_swap_qrom_test.py index 028fa9ec0c..4fe674e96f 100644 --- a/qualtran/bloqs/data_loading/select_swap_qrom_test.py +++ b/qualtran/bloqs/data_loading/select_swap_qrom_test.py @@ -17,6 +17,7 @@ import pytest import sympy +import qualtran.testing as qlt_testing from qualtran._infra.data_types import QUInt from qualtran._infra.gate_with_registers import get_named_qubits, split_qubits from qualtran.bloqs.data_loading import QROM @@ -30,7 +31,6 @@ from qualtran.cirq_interop.testing import assert_circuit_inp_out_cirqsim from qualtran.resource_counting import GateCounts, get_cost_value, QECGatesCost, QubitCount from qualtran.symbolics import ceil, log2 -from qualtran.testing import assert_valid_bloq_decomposition @pytest.mark.parametrize( @@ -49,7 +49,7 @@ ) def test_select_swap_qrom(data, block_size): qrom = SelectSwapQROM.build_from_data(*data, log_block_sizes=block_size) - assert_valid_bloq_decomposition(qrom) + qlt_testing.assert_valid_bloq_decomposition(qrom) qubit_regs = get_named_qubits(qrom.signature) selection = qubit_regs.get("selection", ()) @@ -259,3 +259,8 @@ def test_select_swap_block_sizes(): qroam = SelectSwapQROM.build_from_data(data, use_dirty_ancilla=False) assert qroam.block_sizes == (8,) + + +@pytest.mark.notebook +def test_select_swap_qrom_notebook(): + qlt_testing.execute_notebook('select_swap_qrom') diff --git a/qualtran/bloqs/gf_arithmetic/gf2_add_k_test.py b/qualtran/bloqs/gf_arithmetic/gf2_add_k_test.py index 0add69ae4e..b82414e0d0 100644 --- a/qualtran/bloqs/gf_arithmetic/gf2_add_k_test.py +++ b/qualtran/bloqs/gf_arithmetic/gf2_add_k_test.py @@ -15,8 +15,8 @@ import pytest from galois import GF +import qualtran.testing as qlt_testing from qualtran.bloqs.gf_arithmetic.gf2_add_k import _gf2_add_k_symbolic, _gf16_add_k, GF2AddK -from qualtran.testing import assert_consistent_classical_action def test_gf16_add_k(bloq_autotester): @@ -32,7 +32,7 @@ def test_gf2_add_k_classical_sim_quick(): GFM = GF(2**m) for k in GFM.elements: bloq = GF2AddK(m, int(k)) - assert_consistent_classical_action(bloq, x=GFM.elements) + qlt_testing.assert_consistent_classical_action(bloq, x=GFM.elements) @pytest.mark.slow @@ -41,4 +41,9 @@ def test_gf2_add_k_classical_sim(m): GFM = GF(2**m) for k in GFM.elements: bloq = GF2AddK(m, int(k)) - assert_consistent_classical_action(bloq, x=GFM.elements) + qlt_testing.assert_consistent_classical_action(bloq, x=GFM.elements) + + +@pytest.mark.notebook +def test_gf2_add_k_notebook(): + qlt_testing.execute_notebook('gf2_add_k') diff --git a/qualtran/bloqs/gf_arithmetic/gf2_addition_test.py b/qualtran/bloqs/gf_arithmetic/gf2_addition_test.py index ed1ed34345..cedbdb5a77 100644 --- a/qualtran/bloqs/gf_arithmetic/gf2_addition_test.py +++ b/qualtran/bloqs/gf_arithmetic/gf2_addition_test.py @@ -15,6 +15,7 @@ import pytest from galois import GF +import qualtran.testing as qlt_testing from qualtran.bloqs.gf_arithmetic.gf2_addition import ( _gf2_addition_symbolic, _gf16_addition, @@ -51,3 +52,8 @@ def test_gf2_addition_classical_sim(m): bloq = GF2Addition(m) GFM = GF(2**m) assert_consistent_classical_action(bloq, x=GFM.elements, y=GFM.elements) + + +@pytest.mark.notebook +def test_gf2_addition_notebook(): + qlt_testing.execute_notebook('gf2_addition') diff --git a/qualtran/bloqs/gf_arithmetic/gf2_inverse_test.py b/qualtran/bloqs/gf_arithmetic/gf2_inverse_test.py index b0fb83bcb1..fe9faafb7c 100644 --- a/qualtran/bloqs/gf_arithmetic/gf2_inverse_test.py +++ b/qualtran/bloqs/gf_arithmetic/gf2_inverse_test.py @@ -16,6 +16,7 @@ import sympy from galois import GF +import qualtran.testing as qlt_testing from qualtran.bloqs.gf_arithmetic.gf2_inverse import ( _gf2_inverse_symbolic, _gf16_inverse, @@ -64,3 +65,8 @@ def test_gf2_inverse_classical_sim(m): def test_gf2_equivalent_bloq_counts(m): bloq = GF2Inverse(m) assert_equivalent_bloq_counts(bloq, generalizer=[ignore_split_join, ignore_alloc_free]) + + +@pytest.mark.notebook +def test_gf2_inverse_notebook(): + qlt_testing.execute_notebook('gf2_inverse') diff --git a/qualtran/bloqs/gf_arithmetic/gf2_square_test.py b/qualtran/bloqs/gf_arithmetic/gf2_square_test.py index f192f8c161..294caece7f 100644 --- a/qualtran/bloqs/gf_arithmetic/gf2_square_test.py +++ b/qualtran/bloqs/gf_arithmetic/gf2_square_test.py @@ -16,6 +16,7 @@ import sympy from galois import GF +import qualtran.testing as qlt_testing from qualtran.bloqs.gf_arithmetic.gf2_square import _gf2_square_symbolic, _gf16_square, GF2Square from qualtran.resource_counting import get_cost_value, QECGatesCost from qualtran.testing import assert_consistent_classical_action @@ -51,3 +52,8 @@ def test_gf2_square_classical_sim(m): bloq = GF2Square(m) GFM = GF(2**m) assert_consistent_classical_action(bloq, x=GFM.elements) + + +@pytest.mark.notebook +def test_gf2_square_notebook(): + qlt_testing.execute_notebook('gf2_square') diff --git a/qualtran/bloqs/gf_poly_arithmetic/gf2_poly_add_k_test.py b/qualtran/bloqs/gf_poly_arithmetic/gf2_poly_add_k_test.py index 8b6ab0b6eb..c528c7cfec 100644 --- a/qualtran/bloqs/gf_poly_arithmetic/gf2_poly_add_k_test.py +++ b/qualtran/bloqs/gf_poly_arithmetic/gf2_poly_add_k_test.py @@ -12,8 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. import numpy as np +import pytest from galois import Poly +import qualtran.testing as qlt_testing from qualtran.bloqs.gf_poly_arithmetic.gf2_poly_add_k import ( _gf2_poly_4_8_add_k, _gf2_poly_add_k_symbolic, @@ -56,3 +58,8 @@ def test_gf2_poly_add_k_classical_sim(): ] ) assert_consistent_classical_action(bloq, f_x=f_x_range) + + +@pytest.mark.notebook +def test_gf2_poly_add_k_notebook(): + qlt_testing.execute_notebook('gf2_poly_add_k') diff --git a/qualtran/bloqs/gf_poly_arithmetic/gf2_poly_add_test.py b/qualtran/bloqs/gf_poly_arithmetic/gf2_poly_add_test.py index 6d698c536f..d46e26ac39 100644 --- a/qualtran/bloqs/gf_poly_arithmetic/gf2_poly_add_test.py +++ b/qualtran/bloqs/gf_poly_arithmetic/gf2_poly_add_test.py @@ -12,8 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. import numpy as np +import pytest from galois import Poly +import qualtran.testing as qlt_testing from qualtran.bloqs.gf_poly_arithmetic.gf2_poly_add import _gf2_poly_4_8_add, _gf2_poly_add_symbolic from qualtran.resource_counting import get_cost_value, QECGatesCost from qualtran.testing import assert_consistent_classical_action @@ -52,3 +54,8 @@ def test_gf2_poly_add_resource(): bloq = _gf2_poly_add_symbolic.make() assert get_cost_value(bloq, QECGatesCost()).total_t_count() == 0 assert get_cost_value(bloq, QECGatesCost()).clifford == bloq.qgf_poly.bitsize + + +@pytest.mark.notebook +def test_gf2_poly_add_notebook(): + qlt_testing.execute_notebook('gf2_poly_add') diff --git a/qualtran/bloqs/gf_poly_arithmetic/gf_poly_split_and_join_test.py b/qualtran/bloqs/gf_poly_arithmetic/gf_poly_split_and_join_test.py index 998c06c353..cf0ac290df 100644 --- a/qualtran/bloqs/gf_poly_arithmetic/gf_poly_split_and_join_test.py +++ b/qualtran/bloqs/gf_poly_arithmetic/gf_poly_split_and_join_test.py @@ -18,6 +18,7 @@ from galois import Poly from qualtran import QGF, QGFPoly +from qualtran import testing as qlt_testing from qualtran.bloqs.gf_poly_arithmetic.gf_poly_split_and_join import ( _gf_poly_join, _gf_poly_split, @@ -55,3 +56,8 @@ def test_tensor_sim(): bloq = GFPolySplit(QGFPoly(2, QGF(2, 2))) assert np.all(bloq.tensor_contract() == np.eye(2 ** (3 * 2))) assert np.all(bloq.adjoint().tensor_contract() == np.eye(2 ** (3 * 2))) + + +@pytest.mark.notebook +def test_gf_poly_split_and_join_notebook(): + qlt_testing.execute_notebook('gf_poly_split_and_join') diff --git a/qualtran/bloqs/hamiltonian_simulation/hamiltonian_simulation_by_gqsp_test.py b/qualtran/bloqs/hamiltonian_simulation/hamiltonian_simulation_by_gqsp_test.py index 37ecf02900..1f1ea5182f 100644 --- a/qualtran/bloqs/hamiltonian_simulation/hamiltonian_simulation_by_gqsp_test.py +++ b/qualtran/bloqs/hamiltonian_simulation/hamiltonian_simulation_by_gqsp_test.py @@ -19,6 +19,7 @@ import sympy from numpy.typing import NDArray +from qualtran import testing as qlt_testing from qualtran.bloqs.for_testing.matrix_gate import MatrixGate from qualtran.bloqs.for_testing.random_select_and_prepare import random_qubitization_walk_operator from qualtran.bloqs.hamiltonian_simulation.hamiltonian_simulation_by_gqsp import ( @@ -130,3 +131,8 @@ def test_symbolic_t_cost(): def test_ctrl_ham_sim_cost(): bloq = _hubbard_time_evolution_by_gqsp().controlled() _ = get_cost_value(bloq, QECGatesCost()) + + +@pytest.mark.notebook +def test_hamiltonian_simulation_by_gqsp_notebook(): + qlt_testing.execute_notebook('hamiltonian_simulation_by_gqsp') diff --git a/qualtran/bloqs/mcmt/controlled_via_and_test.py b/qualtran/bloqs/mcmt/controlled_via_and_test.py index 9e60e3eccb..4bbab153e2 100644 --- a/qualtran/bloqs/mcmt/controlled_via_and_test.py +++ b/qualtran/bloqs/mcmt/controlled_via_and_test.py @@ -15,6 +15,7 @@ import pytest from qualtran import Controlled, CtrlSpec, QInt, QUInt +from qualtran import testing as qlt_testing from qualtran.bloqs.basic_gates import XGate from qualtran.bloqs.for_testing.matrix_gate import MatrixGate from qualtran.bloqs.mcmt.controlled_via_and import ( @@ -78,3 +79,8 @@ def test_nested_controlled_x(): ctrl_bloq.tensor_contract(), XGate().controlled(CtrlSpec(cvs=[1, 1, 1, 1])).tensor_contract(), ) + + +@pytest.mark.notebook +def test_controlled_via_and_notebook(): + qlt_testing.execute_notebook('controlled_via_and') diff --git a/qualtran/bloqs/multiplexers/apply_lth_bloq_test.py b/qualtran/bloqs/multiplexers/apply_lth_bloq_test.py index b98e5bcdff..0aec6c8140 100644 --- a/qualtran/bloqs/multiplexers/apply_lth_bloq_test.py +++ b/qualtran/bloqs/multiplexers/apply_lth_bloq_test.py @@ -17,6 +17,7 @@ import numpy as np import pytest +import qualtran.testing as qlt_testing from qualtran import BloqBuilder, BQUInt, QBit, Register, Signature, Soquet from qualtran.bloqs.basic_gates import ( CHadamard, @@ -166,3 +167,8 @@ def test_ndim(i, j, ctrl): )().tensor_contract() from_tensors = bloq.tensor_contract() np.testing.assert_allclose(from_gate, from_tensors) + + +@pytest.mark.notebook +def test_apply_lth_bloq_notebook(): + qlt_testing.execute_notebook('apply_lth_bloq') diff --git a/qualtran/bloqs/phase_estimation/kaiser_window_state_test.py b/qualtran/bloqs/phase_estimation/kaiser_window_state_test.py index 6a4917cf85..7508e4eef3 100644 --- a/qualtran/bloqs/phase_estimation/kaiser_window_state_test.py +++ b/qualtran/bloqs/phase_estimation/kaiser_window_state_test.py @@ -12,6 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +import pytest + +from qualtran import testing as qlt_testing from qualtran.bloqs.phase_estimation.kaiser_window_state import ( _kaiser_window_state_small, _kaiser_window_state_symbolic, @@ -21,3 +24,8 @@ def test_kaiser_window_state_auto(bloq_autotester): bloq_autotester(_kaiser_window_state_small) bloq_autotester(_kaiser_window_state_symbolic) + + +@pytest.mark.notebook +def test_kaiser_window_state_notebook(): + qlt_testing.execute_notebook('kaiser_window_state') diff --git a/qualtran/bloqs/phase_estimation/lp_resource_state_test.py b/qualtran/bloqs/phase_estimation/lp_resource_state_test.py index 41b84e5cb8..bc2a36a38b 100644 --- a/qualtran/bloqs/phase_estimation/lp_resource_state_test.py +++ b/qualtran/bloqs/phase_estimation/lp_resource_state_test.py @@ -109,3 +109,8 @@ def test_interim_lp2s_interim_prep_t_complexity(bitsize: int): assert t_complexity(LPRSInterimPrep(bitsize)) == TComplexity( rotations=crz_rots + 1, clifford=2 + crz_cliff + h_cliff ) + + +@pytest.mark.notebook +def test_lp_resource_state_notebook(): + qlt_testing.execute_notebook('lp_resource_state') diff --git a/qualtran/bloqs/phase_estimation/phase_estimation_of_quantum_walk.ipynb b/qualtran/bloqs/phase_estimation/phase_estimation_of_quantum_walk.ipynb index 6937510fa2..fa8d5c9e2d 100644 --- a/qualtran/bloqs/phase_estimation/phase_estimation_of_quantum_walk.ipynb +++ b/qualtran/bloqs/phase_estimation/phase_estimation_of_quantum_walk.ipynb @@ -177,7 +177,8 @@ "walk, _ = get_walk_operator_for_1d_ising_model(num_sites, eps)\n", "\n", "circuit = cirq.Circuit(phase_estimation(walk, m=m_bits))\n", - "%time result = get_qec_gates_cost_for_circuit(circuit)\n", + "%time get_qec_gates_cost_for_circuit(circuit)\n", + "result = get_qec_gates_cost_for_circuit(circuit)\n", "print(result)" ] }, @@ -204,7 +205,9 @@ "m_bits = int(np.ceil(np.log2(qlambda * np.pi * np.sqrt(2) / delta_E)))\n", "walk = get_walk_operator_for_hubbard_model(x_dim, y_dim, t, mu)\n", "circuit = cirq.Circuit(phase_estimation(walk, m=m_bits))\n", - "%time result = get_qec_gates_cost_for_circuit(circuit)\n", + "%time get_qec_gates_cost_for_circuit(circuit)\n", + "result = get_qec_gates_cost_for_circuit(circuit)\n", + "\n", "print(result)" ] }, @@ -218,7 +221,8 @@ "\n", "from qualtran.bloqs.phase_estimation.qubitization_qpe import _qubitization_qpe_hubbard_model_large\n", "qpe = _qubitization_qpe_hubbard_model_large.make()\n", - "%time result = get_cost_value(qpe, QECGatesCost())\n", + "%time get_cost_value(qpe, QECGatesCost())\n", + "result = get_cost_value(qpe, QECGatesCost())\n", "print(result)" ] }, @@ -260,7 +264,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.8" + "version": "3.12.12" } }, "nbformat": 4, diff --git a/qualtran/bloqs/phase_estimation/text_book_qpe_test.py b/qualtran/bloqs/phase_estimation/text_book_qpe_test.py index 39a0d095da..8ee38b6c11 100644 --- a/qualtran/bloqs/phase_estimation/text_book_qpe_test.py +++ b/qualtran/bloqs/phase_estimation/text_book_qpe_test.py @@ -124,3 +124,8 @@ def test_qpe_of_gqsp(): hubbard_time_evolution_by_gqsp = HamiltonianSimulationByGQSP(walk_op, t=5, precision=1e-7) textbook_qpe_w_gqsp = TextbookQPE(hubbard_time_evolution_by_gqsp, RectangularWindowState(3)) qlt_testing.assert_valid_bloq_decomposition(textbook_qpe_w_gqsp) + + +@pytest.mark.notebook +def test_text_book_qpe_notebook(): + qlt_testing.execute_notebook('text_book_qpe') diff --git a/qualtran/bloqs/qft/approximate_qft_test.py b/qualtran/bloqs/qft/approximate_qft_test.py index 7dd0b10f8d..0f7eee14fd 100644 --- a/qualtran/bloqs/qft/approximate_qft_test.py +++ b/qualtran/bloqs/qft/approximate_qft_test.py @@ -122,3 +122,8 @@ def f(n, b): b = math.ceil(math.log2(n)) assert qft_t_complexity.t == f(n, b) <= 8 * n * (math.log2(n)) assert qft_t_complexity.rotations == 0 + + +@pytest.mark.notebook +def test_approximate_qft_notebook(): + qlt_testing.execute_notebook('approximate_qft') diff --git a/qualtran/bloqs/qft/qft_phase_gradient_test.py b/qualtran/bloqs/qft/qft_phase_gradient_test.py index d6fee8c159..5eaef7ebd6 100644 --- a/qualtran/bloqs/qft/qft_phase_gradient_test.py +++ b/qualtran/bloqs/qft/qft_phase_gradient_test.py @@ -19,6 +19,7 @@ import pytest import sympy +import qualtran.testing as qlt_testing from qualtran import GateWithRegisters, Signature from qualtran.bloqs.qft.qft_phase_gradient import _qft_phase_gradient_small, QFTPhaseGradient from qualtran.bloqs.rotations.phase_gradient import PhaseGradientState @@ -74,3 +75,8 @@ def test_qft_phase_gradient_t_complexity(n: int): def test_qft_phase_gradient_small_auto(bloq_autotester): bloq_autotester(_qft_phase_gradient_small) + + +@pytest.mark.notebook +def test_qft_phase_gradient_notebook(): + qlt_testing.execute_notebook('qft_phase_gradient') diff --git a/qualtran/bloqs/qft/qft_text_book_test.py b/qualtran/bloqs/qft/qft_text_book_test.py index 031b499182..1ec1d43128 100644 --- a/qualtran/bloqs/qft/qft_text_book_test.py +++ b/qualtran/bloqs/qft/qft_text_book_test.py @@ -77,3 +77,8 @@ def test_qft_text_book_auto(bloq_autotester): def test_symbolic_qft_auto(bloq_autotester): bloq_autotester(_symbolic_qft) + + +@pytest.mark.notebook +def test_qft_text_book_notebook(): + qlt_testing.execute_notebook('qft_text_book') diff --git a/qualtran/bloqs/qft/two_bit_ffft_test.py b/qualtran/bloqs/qft/two_bit_ffft_test.py index 1fd5edf1be..bcbe622dca 100644 --- a/qualtran/bloqs/qft/two_bit_ffft_test.py +++ b/qualtran/bloqs/qft/two_bit_ffft_test.py @@ -17,6 +17,7 @@ import pytest from qualtran import Bloq +from qualtran import testing as qlt_testing from qualtran.bloqs.basic_gates import TGate from qualtran.bloqs.qft.two_bit_ffft import _fkn_matrix, _two_bit_ffft, TwoBitFFFT @@ -42,3 +43,8 @@ def test_tensors(k, n): # Eq. E11 in https://arxiv.org/pdf/2012.09238.pdf from_decomp = TwoBitFFFT(k, n).decompose_bloq().tensor_contract() cirq.testing.assert_allclose_up_to_global_phase(from_decomp, _fkn_matrix(k, n), atol=1e-12) + + +@pytest.mark.notebook +def test_two_bit_ffft_notebook(): + qlt_testing.execute_notebook('two_bit_ffft') diff --git a/qualtran/bloqs/rotations/hamming_weight_phasing_test.py b/qualtran/bloqs/rotations/hamming_weight_phasing_test.py index bad2b4c5b0..f392f903d6 100644 --- a/qualtran/bloqs/rotations/hamming_weight_phasing_test.py +++ b/qualtran/bloqs/rotations/hamming_weight_phasing_test.py @@ -137,3 +137,8 @@ def test_hamming_weight_phasing_via_phase_gradient_t_complexity(n: int, theta: f naive_total_t = naive_hwp_t_complexity.t_incl_rotations(eps=eps / n.bit_length()) assert total_t < naive_total_t + + +@pytest.mark.notebook +def test_hamming_weight_phasing_notebook(): + qlt_testing.execute_notebook('hamming_weight_phasing') diff --git a/qualtran/bloqs/rotations/phase_gradient_test.py b/qualtran/bloqs/rotations/phase_gradient_test.py index af6677a79b..c2bdfcd1b1 100644 --- a/qualtran/bloqs/rotations/phase_gradient_test.py +++ b/qualtran/bloqs/rotations/phase_gradient_test.py @@ -17,6 +17,7 @@ import numpy as np import pytest +import qualtran.testing as qlt_testing from qualtran import BloqBuilder, QFxp from qualtran.bloqs.rotations.phase_gradient import ( _phase_gradient_unitary_symbolic, @@ -199,3 +200,8 @@ def test_add_scaled_val_into_phase_reg_t_complexity(bloq): def test_phase_gradient_unitary_symbolic_auto(bloq_autotester): bloq_autotester(_phase_gradient_unitary_symbolic) + + +@pytest.mark.notebook +def test_phase_gradient_notebook(): + qlt_testing.execute_notebook('phase_gradient') diff --git a/qualtran/bloqs/rotations/phasing_via_cost_function_test.py b/qualtran/bloqs/rotations/phasing_via_cost_function_test.py index 959d1c2e1b..5e0a9a31b3 100644 --- a/qualtran/bloqs/rotations/phasing_via_cost_function_test.py +++ b/qualtran/bloqs/rotations/phasing_via_cost_function_test.py @@ -18,6 +18,7 @@ import numpy as np import pytest +import qualtran.testing as qlt_testing from qualtran import Bloq, BloqBuilder, BloqError, GateWithRegisters, Register, Signature, SoquetT from qualtran._infra.data_types import QFxp from qualtran.bloqs.arithmetic.hamming_weight import HammingWeightCompute @@ -207,3 +208,8 @@ def test_square_phasing_via_phase_gradient( pytest.xfail("https://github.com/quantumlib/Qualtran/issues/1069") hw_final_state = flat_cbloq.tensor_contract() np.testing.assert_allclose(expected_final_state, hw_final_state, atol=eps) + + +@pytest.mark.notebook +def test_phasing_via_cost_function_notebook(): + qlt_testing.execute_notebook('phasing_via_cost_function') diff --git a/qualtran/bloqs/rotations/programmable_rotation_gate_array_test.py b/qualtran/bloqs/rotations/programmable_rotation_gate_array_test.py index f3486cab51..b0041f5dae 100644 --- a/qualtran/bloqs/rotations/programmable_rotation_gate_array_test.py +++ b/qualtran/bloqs/rotations/programmable_rotation_gate_array_test.py @@ -20,6 +20,7 @@ import pytest from numpy.typing import NDArray +import qualtran.testing as qlt_testing from qualtran import QUInt, Register, Signature from qualtran._infra.gate_with_registers import merge_qubits from qualtran.bloqs.rotations.programmable_rotation_gate_array import ( @@ -153,3 +154,8 @@ def rotation_ops(theta: int) -> Iterator[cirq.OP_TREE]: def test_programmable_rotation_gate_array_consistent(): with pytest.raises(ValueError, match='must be of same length'): _ = CustomProgrammableRotationGateArray([1, 2], [1], kappa=1, rotation_gate=cirq.X) + + +@pytest.mark.notebook +def test_programmable_rotation_gate_array_notebook(): + qlt_testing.execute_notebook('programmable_rotation_gate_array') diff --git a/qualtran/bloqs/rotations/quantum_variable_rotation_test.py b/qualtran/bloqs/rotations/quantum_variable_rotation_test.py index 298c89138c..e2016e1e43 100644 --- a/qualtran/bloqs/rotations/quantum_variable_rotation_test.py +++ b/qualtran/bloqs/rotations/quantum_variable_rotation_test.py @@ -21,6 +21,7 @@ from numpy.typing import NDArray from qualtran import GateWithRegisters, QFxp, Register, Signature +from qualtran import testing as qlt_testing from qualtran.bloqs.basic_gates import ZPowGate from qualtran.bloqs.rotations.phase_gradient import PhaseGradientUnitary from qualtran.bloqs.rotations.quantum_variable_rotation import ( @@ -206,3 +207,8 @@ def test_qvr_phase_gradient_t_complexity( qvr = QvrPhaseGradient(cost_reg, gamma, eps) assert qvr.b_grad < n assert qvr.t_complexity().t == 4 * expected_additions * (qvr.b_grad - 2) + + +@pytest.mark.notebook +def test_quantum_variable_rotation_notebook(): + qlt_testing.execute_notebook('quantum_variable_rotation') diff --git a/qualtran/drawing/drawing_call_graph_test.py b/qualtran/drawing/drawing_call_graph_test.py new file mode 100644 index 0000000000..3db6be57b0 --- /dev/null +++ b/qualtran/drawing/drawing_call_graph_test.py @@ -0,0 +1,22 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from qualtran import testing as qlt_testing + + +@pytest.mark.notebook +def test_drawing_call_graph_notebook(): + qlt_testing.execute_notebook('drawing_call_graph') diff --git a/qualtran/l1/L1-Objectstring_test.py b/qualtran/l1/L1-Objectstring_test.py new file mode 100644 index 0000000000..b3be99d0e3 --- /dev/null +++ b/qualtran/l1/L1-Objectstring_test.py @@ -0,0 +1,22 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from qualtran import testing as qlt_testing + + +@pytest.mark.notebook +def test_L1_Objectstring_notebook(): + qlt_testing.execute_notebook('L1-Objectstring') diff --git a/qualtran/l1/L1-Text-IR_test.py b/qualtran/l1/L1-Text-IR_test.py new file mode 100644 index 0000000000..e91a5b9a7c --- /dev/null +++ b/qualtran/l1/L1-Text-IR_test.py @@ -0,0 +1,22 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from qualtran import testing as qlt_testing + + +@pytest.mark.notebook +def test_L1_Text_IR_notebook(): + qlt_testing.execute_notebook('L1-Text-IR') diff --git a/qualtran/qref_interop/bartiq_demo_test.py b/qualtran/qref_interop/bartiq_demo_test.py new file mode 100644 index 0000000000..94a5a41638 --- /dev/null +++ b/qualtran/qref_interop/bartiq_demo_test.py @@ -0,0 +1,22 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from qualtran import testing as qlt_testing + + +@pytest.mark.notebook +def test_bartiq_demo_notebook(): + qlt_testing.execute_notebook('bartiq_demo') diff --git a/qualtran/simulation/MBUC_test.py b/qualtran/simulation/MBUC_test.py new file mode 100644 index 0000000000..ac5acdb32b --- /dev/null +++ b/qualtran/simulation/MBUC_test.py @@ -0,0 +1,22 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from qualtran import testing as qlt_testing + + +@pytest.mark.notebook +def test_MBUC_notebook(): + qlt_testing.execute_notebook('MBUC') diff --git a/qualtran/simulation/supertensor_test.py b/qualtran/simulation/supertensor_test.py new file mode 100644 index 0000000000..746e1ca76b --- /dev/null +++ b/qualtran/simulation/supertensor_test.py @@ -0,0 +1,22 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from qualtran import testing as qlt_testing + + +@pytest.mark.notebook +def test_supertensor_notebook(): + qlt_testing.execute_notebook('supertensor') diff --git a/qualtran/simulation/tensor_test.py b/qualtran/simulation/tensor_test.py new file mode 100644 index 0000000000..29d471d453 --- /dev/null +++ b/qualtran/simulation/tensor_test.py @@ -0,0 +1,22 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from qualtran import testing as qlt_testing + + +@pytest.mark.notebook +def test_tensor_notebook(): + qlt_testing.execute_notebook('tensor') diff --git a/qualtran/surface_code/thc_compilation_test.py b/qualtran/surface_code/thc_compilation_test.py new file mode 100644 index 0000000000..303f1ed1a1 --- /dev/null +++ b/qualtran/surface_code/thc_compilation_test.py @@ -0,0 +1,22 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from qualtran import testing as qlt_testing + + +@pytest.mark.notebook +def test_thc_compilation_notebook(): + qlt_testing.execute_notebook('thc_compilation') diff --git a/qualtran/testing.py b/qualtran/testing.py index 39d90d7f01..131778d028 100644 --- a/qualtran/testing.py +++ b/qualtran/testing.py @@ -273,19 +273,49 @@ def assert_wire_symbols_match_expected(bloq: Bloq, expected_ws: List[Union[str, def execute_notebook(name: str): """Execute a jupyter notebook in the caller's directory. + Uses jupytext to convert the notebook to a Python script in-memory, + then executes it directly — no Jupyter kernel required. This is much + faster than the kernel-based approach and avoids flaky parallel kernel issues. + + Note: No .py files are written to disk. This is critical because most + notebooks share their basename with a Python module (e.g. addition.ipynb + and addition.py coexist), so writing a .py file would shadow the module. + Args: name: The name of the notebook without extension. """ - import nbformat - from nbconvert.preprocessors import ExecutePreprocessor + import os + import sys + + import jupytext # Assumes that the notebook is in the same path from where the function was called, # which may be different from `__file__`. notebook_path = Path(traceback.extract_stack()[-2].filename).parent / f"{name}.ipynb" - with notebook_path.open() as f: - nb = nbformat.read(f, as_version=4) - ep = ExecutePreprocessor(timeout=600, kernel_name="python3") - ep.preprocess(nb) + nb = jupytext.read(notebook_path) + script = jupytext.writes(nb, fmt="py:percent") + + # Execute in a temporary directory to avoid writing any files into the source tree. + # This prevents accidental module shadowing. + old_cwd = os.getcwd() + old_path = sys.path[:] + old_modules = sys.modules.copy() + os.chdir(notebook_path.parent) + sys.path.insert(0, str(notebook_path.parent)) + exec_globals: dict = { + "__name__": "__main__", + "__file__": str(notebook_path), + "get_ipython": lambda: None, # Mock to avoid NameError on magics + } + try: + exec(compile(script, str(notebook_path), "exec"), exec_globals) # pylint: disable=exec-used + finally: + os.chdir(old_cwd) + sys.path[:] = old_path + # Clean up sys.modules to prevent cross-test pollution + for m in list(sys.modules): + if m not in old_modules: + del sys.modules[m] class BloqCheckResult(Enum): diff --git a/tutorials/quantum-programming/quantum_programming_test.py b/tutorials/quantum-programming/quantum_programming_test.py new file mode 100644 index 0000000000..61e7ac7e84 --- /dev/null +++ b/tutorials/quantum-programming/quantum_programming_test.py @@ -0,0 +1,32 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from qualtran import testing as qlt_testing + + +@pytest.mark.notebook +def test_1_wiring_up_notebook(): + qlt_testing.execute_notebook('1-wiring-up') + + +@pytest.mark.notebook +def test_2_bloqs_notebook(): + qlt_testing.execute_notebook('2-bloqs') + + +@pytest.mark.notebook +def test_3_addressing_bits_notebook(): + qlt_testing.execute_notebook('3-addressing-bits') diff --git a/uv.lock b/uv.lock index 4aed9cfd9d..d0171338bd 100644 --- a/uv.lock +++ b/uv.lock @@ -1816,6 +1816,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl", hash = "sha256:45fa36d9c6422cf2559198e4db481aa243c7a32d9926b500781c830c80f7ecf8", size = 914926, upload-time = "2025-11-01T21:11:28.008Z" }, ] +[[package]] +name = "jupytext" +version = "1.19.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "mdit-py-plugins" }, + { name = "nbformat" }, + { name = "packaging" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/13/a5/80c02f307c8ce863cb33e27daf049315e9d96979e14eead700923b5ec9cc/jupytext-1.19.1.tar.gz", hash = "sha256:82587c07e299173c70ed5e8ec7e75183edf1be289ed518bab49ad0d4e3d5f433", size = 4307829, upload-time = "2026-01-25T21:35:13.276Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl", hash = "sha256:d8975035155d034bdfde5c0c37891425314b7ea8d3a6c4b5d18c294348714cd9", size = 170478, upload-time = "2026-01-25T21:35:11.17Z" }, +] + [[package]] name = "kiwisolver" version = "1.5.0" @@ -3589,6 +3605,7 @@ dev = [ { name = "ipykernel" }, { name = "ipywidgets" }, { name = "isort" }, + { name = "jupytext" }, { name = "mdit-py-plugins" }, { name = "mypy" }, { name = "mypy-protobuf" }, @@ -3630,6 +3647,7 @@ format = [ lint = [ { name = "filelock" }, { name = "griffe" }, + { name = "jupytext" }, { name = "mdit-py-plugins" }, { name = "openfermion", extra = ["resources"] }, { name = "pylint" }, @@ -3642,6 +3660,7 @@ lint = [ test = [ { name = "filelock" }, { name = "ipykernel" }, + { name = "jupytext" }, { name = "openfermion", extra = ["resources"] }, { name = "pytest" }, { name = "pytest-asyncio" }, @@ -3695,6 +3714,7 @@ dev = [ { name = "ipykernel" }, { name = "ipywidgets" }, { name = "isort", specifier = "~=5.10.1" }, + { name = "jupytext" }, { name = "mdit-py-plugins" }, { name = "mypy", specifier = "~=1.14" }, { name = "mypy-protobuf" }, @@ -3735,6 +3755,7 @@ format = [ lint = [ { name = "filelock" }, { name = "griffe" }, + { name = "jupytext" }, { name = "mdit-py-plugins" }, { name = "openfermion", extras = ["resources"] }, { name = "pylint", specifier = "~=3.3.1" }, @@ -3746,6 +3767,7 @@ lint = [ test = [ { name = "filelock" }, { name = "ipykernel" }, + { name = "jupytext" }, { name = "openfermion", extras = ["resources"] }, { name = "pytest" }, { name = "pytest-asyncio" },