From 19c3da9c206e16643cb09ee1a12455b22744b300 Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Tue, 21 Oct 2025 13:25:05 -0700 Subject: [PATCH 01/10] time notebooks --- .github/workflows/pr.yaml | 2 +- dev_tools/execute-notebooks.py | 3 +- .../qualtran_dev_tools/notebook_execution.py | 40 +++++++++++++++---- 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index b7ae5acfc1..ff0642ae1d 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 + python dev_tools/execute-notebooks.py --n-workers=1 env: NUMBA_NUM_THREADS: 4 diff --git a/dev_tools/execute-notebooks.py b/dev_tools/execute-notebooks.py index caa0f0cd73..74a93656f4 100644 --- a/dev_tools/execute-notebooks.py +++ b/dev_tools/execute-notebooks.py @@ -22,9 +22,10 @@ def parse_args(): p.add_argument('--output-nbs', action=argparse.BooleanOptionalAction, default=True) p.add_argument('--output-md', action=argparse.BooleanOptionalAction, default=False) p.add_argument('--only-out-of-date', action=argparse.BooleanOptionalAction, default=True) + p.add_argument('--n-workers', type=int, default=None) args = p.parse_args() execute_and_export_notebooks( - output_nbs=args.output_nbs, output_md=args.output_md, only_out_of_date=args.only_out_of_date + output_nbs=args.output_nbs, output_md=args.output_md, only_out_of_date=args.only_out_of_date, n_workers=args.n_workers, ) diff --git a/dev_tools/qualtran_dev_tools/notebook_execution.py b/dev_tools/qualtran_dev_tools/notebook_execution.py index 8ea8f1af11..007085c111 100644 --- a/dev_tools/qualtran_dev_tools/notebook_execution.py +++ b/dev_tools/qualtran_dev_tools/notebook_execution.py @@ -15,9 +15,11 @@ import multiprocessing import subprocess import sys +import time from pathlib import Path from typing import List, Optional, Tuple +import attrs import filelock import nbconvert import nbformat @@ -179,6 +181,13 @@ def execute_and_export_notebook(paths: _NBInOutPaths) -> Optional[Exception]: return None +@attrs.frozen +class _NotebookRunResult: + nb_in: Path + err: Optional[Exception] + duration_s: float + + class _NotebookRunClosure: """Used to run notebook execution logic in subprocesses.""" @@ -189,7 +198,7 @@ def __init__(self, reporoot: Path, output_nbs: bool, output_md: bool, only_out_o self.output_md = output_md self.only_out_of_date = only_out_of_date - def __call__(self, nb_rel_path: Path, sourceroot: Path) -> Tuple[Path, Optional[Exception]]: + def __call__(self, nb_rel_path: Path, sourceroot: Path) -> _NotebookRunResult: paths = _NBInOutPaths.from_nb_rel_path( nb_rel_path, reporoot=self.reporoot, @@ -200,15 +209,17 @@ def __call__(self, nb_rel_path: Path, sourceroot: Path) -> Tuple[Path, Optional[ if self.only_out_of_date and not paths.needs_reexport(): print(f'{nb_rel_path} up to date') - return paths.nb_in, None + return _NotebookRunResult(paths.nb_in, None, 0.0) + start = time.time() err = execute_and_export_notebook(paths) - print(f"Exported {nb_rel_path}") - return paths.nb_in, err + end = time.time() + print(f"Exported {nb_rel_path} in {end-start:.2f} seconds.") + return _NotebookRunResult(paths.nb_in, err, duration_s=end-start) def execute_and_export_notebooks( - *, output_nbs: bool, output_md: bool, only_out_of_date: bool = True + *, output_nbs: bool, output_md: bool, only_out_of_date: bool = True, n_workers=None, ): """Find, execute, and export all checked-in ipynbs. @@ -221,15 +232,22 @@ def execute_and_export_notebooks( reporoot = get_git_root() nb_rel_paths = get_nb_rel_paths(sourceroot=reporoot / 'qualtran') nb_rel_paths += get_nb_rel_paths(sourceroot=reporoot / 'tutorials') + print(f"Found {len(nb_rel_paths)} notebooks.") func = _NotebookRunClosure( reporoot=reporoot, output_nbs=output_nbs, output_md=output_md, only_out_of_date=only_out_of_date, ) - with multiprocessing.Pool() as pool: - results = pool.starmap(func, nb_rel_paths) - bad_nbs = [nbname for nbname, err in results if err is not None] + if n_workers == 1: + print("(Not using multiprocessing, n_workers=1)") + results = [func(nb_rel_path, sourceroot) for nb_rel_path, sourceroot in nb_rel_paths] + else: + print(f"Multiprocessing with {n_workers=}") + with multiprocessing.Pool(n_workers) as pool: + results = pool.starmap(func, nb_rel_paths) + assert results + bad_nbs = [result.nb_in for result in results if result.err is not None] if len(bad_nbs) > 0: print() @@ -237,3 +255,9 @@ def execute_and_export_notebooks( for nb in bad_nbs: print(' ', nb) sys.exit(1) + + duration_nbs = sorted(results, key=lambda r: r.duration_s, reverse=True) + print("Slowest 10 notebooks:") + for result in duration_nbs[:10]: + print(f'{result.duration_s:5.2f}s {result.nb_in}') + From 9fcaf12043c0db40fcce01c9186e534e582bd3b2 Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Tue, 21 Oct 2025 14:25:35 -0700 Subject: [PATCH 02/10] temporarily increase timeout --- .github/workflows/pr.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index ff0642ae1d..fdc1d81ec3 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -63,7 +63,7 @@ jobs: notebooks: runs-on: ubuntu-24.04 - timeout-minutes: 15 + timeout-minutes: 30 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - uses: ts-graphviz/setup-graphviz@c001ccfb5aff62e28bda6a6c39b59a7e061be5b9 # v1 From 8370d885145f9dc46b2a22cba567395cf1668eb7 Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Tue, 21 Oct 2025 14:55:54 -0700 Subject: [PATCH 03/10] re-speed up hubbard resource counting --- .../qubitization/select_hubbard.py | 57 ++++++++++++------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard.py b/qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard.py index 517f27d547..5ac96318b1 100644 --- a/qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard.py +++ b/qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard.py @@ -39,7 +39,7 @@ """ from functools import cached_property -from typing import Dict, Iterator, Optional, Tuple +from typing import Dict, Iterator, Optional, Tuple, Union, Set import attrs import cirq @@ -306,13 +306,9 @@ def _target_cirq_gate(self): else: raise ValueError(f"Unknown gate {self.gate}") - def build_composite_bloq( - self, bb: 'BloqBuilder', x, y, spin, target, control=None - ) -> Dict[str, 'SoquetT']: - if is_symbolic(self.x_dim, self.y_dim): - raise DecomposeTypeError(f"Cannot decompose symbolic x_dim, y_dim in {self}") - - smf = SelectedMajoranaFermion( + @cached_property + def selected_majoranna_fermion_bloq(self) -> SelectedMajoranaFermion: + return SelectedMajoranaFermion( selection_regs=( Register('x', BQUInt(self.log_m, self.x_dim)), Register('y', BQUInt(self.log_m, self.y_dim)), @@ -321,6 +317,15 @@ def build_composite_bloq( control_regs=self.control_registers, target_gate=self._target_cirq_gate, ) + + + def build_composite_bloq( + self, bb: 'BloqBuilder', x, y, spin, target, control=None + ) -> Dict[str, 'SoquetT']: + if is_symbolic(self.x_dim, self.y_dim): + raise DecomposeTypeError(f"Cannot decompose symbolic x_dim, y_dim in {self}") + + smf = self.selected_majoranna_fermion_bloq if self.control_val: control, x, y, spin, target = bb.add_from( smf, control=control, x=x, y=y, spin=spin, target=target @@ -330,6 +335,12 @@ def build_composite_bloq( x, y, spin, target = bb.add_from(smf, x=x, y=y, spin=spin, target=target) return {'x': x, 'y': y, 'spin': spin, 'target': target} + def build_call_graph( + self, ssa: 'SympySymbolAllocator' + ) -> Union['BloqCountDictT', Set['BloqCountT']]: + return self.selected_majoranna_fermion_bloq.build_call_graph(ssa) + + def wire_symbol( self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() ) -> 'WireSymbol': @@ -426,6 +437,18 @@ def signature(self) -> Signature: [*self.control_registers, *self.selection_registers, *self.target_registers] ) + @cached_property + def _apply_z_to_lth(self) -> ApplyGateToLthQubit: + c_size = 1 if self.control_val is None else 2 + return ApplyGateToLthQubit( + selection_regs=( + Register('y', BQUInt(self.signature.get_left('y').total_bits(), self.y_dim)), + Register('x', BQUInt(self.signature.get_left('x').total_bits(), self.x_dim)), + ), + nth_gate=lambda *_: cirq.Z, + control_regs=Register('control', QAny(c_size)), + ) + def build_composite_bloq( self, bb: 'BloqBuilder', V, x, y, target, control=None ) -> Dict[str, 'SoquetT']: @@ -435,10 +458,8 @@ def build_composite_bloq( # If we have a control bit, pack it into `ApplyToLthQubit` control register if self.control_val is not None: control = bb.join([V, control]) - c_size = 2 else: control = V - c_size = 1 # `target` is a QAny(xdim * ydim * 2). # We index into it like (alpha, y, x), @@ -448,15 +469,8 @@ def build_composite_bloq( # Delegate to `ApplyGateToLthQubit`. control, y, x, spin_up = bb.add_from( - ApplyGateToLthQubit( - selection_regs=( - Register('y', BQUInt(self.signature.get_left('y').total_bits(), self.y_dim)), - Register('x', BQUInt(self.signature.get_left('x').total_bits(), self.x_dim)), - ), - nth_gate=lambda *_: cirq.Z, - control_regs=Register('control', QAny(c_size)), - ), - x=x, + self._apply_z_to_lth, + x=x, y=y, control=control, target=spin_up, @@ -473,6 +487,11 @@ def build_composite_bloq( return ret | {'x': x, 'y': y, 'target': target} + def build_call_graph( + self, ssa: 'SympySymbolAllocator' + ) -> Union['BloqCountDictT', Set['BloqCountT']]: + return self._apply_z_to_lth.build_call_graph(ssa) + def wire_symbol( self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() ) -> 'WireSymbol': From 59597bb24b50fc4c974e6c67a64c4bfc026085d0 Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Tue, 21 Oct 2025 15:03:45 -0700 Subject: [PATCH 04/10] futz around with the parameters --- .github/workflows/pr.yaml | 2 +- dev_tools/qualtran_dev_tools/notebook_execution.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index fdc1d81ec3..c401cb0da2 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=1 + python dev_tools/execute-notebooks.py --n-workers=8 env: NUMBA_NUM_THREADS: 4 diff --git a/dev_tools/qualtran_dev_tools/notebook_execution.py b/dev_tools/qualtran_dev_tools/notebook_execution.py index 007085c111..fab8bac18e 100644 --- a/dev_tools/qualtran_dev_tools/notebook_execution.py +++ b/dev_tools/qualtran_dev_tools/notebook_execution.py @@ -13,6 +13,7 @@ # limitations under the License. import asyncio import multiprocessing +import random import subprocess import sys import time @@ -232,6 +233,7 @@ def execute_and_export_notebooks( reporoot = get_git_root() nb_rel_paths = get_nb_rel_paths(sourceroot=reporoot / 'qualtran') nb_rel_paths += get_nb_rel_paths(sourceroot=reporoot / 'tutorials') + random.shuffle(nb_rel_paths) print(f"Found {len(nb_rel_paths)} notebooks.") func = _NotebookRunClosure( reporoot=reporoot, @@ -244,7 +246,7 @@ def execute_and_export_notebooks( results = [func(nb_rel_path, sourceroot) for nb_rel_path, sourceroot in nb_rel_paths] else: print(f"Multiprocessing with {n_workers=}") - with multiprocessing.Pool(n_workers) as pool: + with multiprocessing.Pool(n_workers, maxtasksperchild=1) as pool: results = pool.starmap(func, nb_rel_paths) assert results bad_nbs = [result.nb_in for result in results if result.err is not None] From 52f53cca7c1be9b104fb34f4cee7aa1ba9e9269f Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Tue, 21 Oct 2025 15:32:00 -0700 Subject: [PATCH 05/10] maxtasksperchild experiment --- dev_tools/qualtran_dev_tools/notebook_execution.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev_tools/qualtran_dev_tools/notebook_execution.py b/dev_tools/qualtran_dev_tools/notebook_execution.py index fab8bac18e..f0886361b8 100644 --- a/dev_tools/qualtran_dev_tools/notebook_execution.py +++ b/dev_tools/qualtran_dev_tools/notebook_execution.py @@ -246,7 +246,7 @@ def execute_and_export_notebooks( results = [func(nb_rel_path, sourceroot) for nb_rel_path, sourceroot in nb_rel_paths] else: print(f"Multiprocessing with {n_workers=}") - with multiprocessing.Pool(n_workers, maxtasksperchild=1) as pool: + with multiprocessing.Pool(n_workers) as pool: results = pool.starmap(func, nb_rel_paths) assert results bad_nbs = [result.nb_in for result in results if result.err is not None] From e0ecdc52a38b07530fcac9e59fb912bc54a03483 Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Tue, 21 Oct 2025 15:45:43 -0700 Subject: [PATCH 06/10] revert --- dev_tools/qualtran_dev_tools/notebook_execution.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev_tools/qualtran_dev_tools/notebook_execution.py b/dev_tools/qualtran_dev_tools/notebook_execution.py index f0886361b8..fab8bac18e 100644 --- a/dev_tools/qualtran_dev_tools/notebook_execution.py +++ b/dev_tools/qualtran_dev_tools/notebook_execution.py @@ -246,7 +246,7 @@ def execute_and_export_notebooks( results = [func(nb_rel_path, sourceroot) for nb_rel_path, sourceroot in nb_rel_paths] else: print(f"Multiprocessing with {n_workers=}") - with multiprocessing.Pool(n_workers) as pool: + with multiprocessing.Pool(n_workers, maxtasksperchild=1) as pool: results = pool.starmap(func, nb_rel_paths) assert results bad_nbs = [result.nb_in for result in results if result.err is not None] From 72c139b3722efdfeeccb77070d3adc0a9aaedb63 Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Tue, 21 Oct 2025 15:52:59 -0700 Subject: [PATCH 07/10] nice --- .github/workflows/pr.yaml | 2 +- dev_tools/execute-notebooks.py | 5 ++++- dev_tools/qualtran_dev_tools/notebook_execution.py | 9 +++++---- .../hubbard_model/qubitization/select_hubbard.py | 11 +++-------- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index c401cb0da2..bf251ccf3f 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -63,7 +63,7 @@ jobs: notebooks: runs-on: ubuntu-24.04 - timeout-minutes: 30 + timeout-minutes: 15 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - uses: ts-graphviz/setup-graphviz@c001ccfb5aff62e28bda6a6c39b59a7e061be5b9 # v1 diff --git a/dev_tools/execute-notebooks.py b/dev_tools/execute-notebooks.py index 74a93656f4..e5f484ab89 100644 --- a/dev_tools/execute-notebooks.py +++ b/dev_tools/execute-notebooks.py @@ -25,7 +25,10 @@ def parse_args(): p.add_argument('--n-workers', type=int, default=None) args = p.parse_args() execute_and_export_notebooks( - output_nbs=args.output_nbs, output_md=args.output_md, only_out_of_date=args.only_out_of_date, n_workers=args.n_workers, + output_nbs=args.output_nbs, + output_md=args.output_md, + only_out_of_date=args.only_out_of_date, + n_workers=args.n_workers, ) diff --git a/dev_tools/qualtran_dev_tools/notebook_execution.py b/dev_tools/qualtran_dev_tools/notebook_execution.py index fab8bac18e..73e8c7bbbd 100644 --- a/dev_tools/qualtran_dev_tools/notebook_execution.py +++ b/dev_tools/qualtran_dev_tools/notebook_execution.py @@ -182,6 +182,7 @@ def execute_and_export_notebook(paths: _NBInOutPaths) -> Optional[Exception]: return None + @attrs.frozen class _NotebookRunResult: nb_in: Path @@ -189,7 +190,6 @@ class _NotebookRunResult: duration_s: float - class _NotebookRunClosure: """Used to run notebook execution logic in subprocesses.""" @@ -216,11 +216,11 @@ def __call__(self, nb_rel_path: Path, sourceroot: Path) -> _NotebookRunResult: err = execute_and_export_notebook(paths) end = time.time() print(f"Exported {nb_rel_path} in {end-start:.2f} seconds.") - return _NotebookRunResult(paths.nb_in, err, duration_s=end-start) + return _NotebookRunResult(paths.nb_in, err, duration_s=end - start) def execute_and_export_notebooks( - *, output_nbs: bool, output_md: bool, only_out_of_date: bool = True, n_workers=None, + *, output_nbs: bool, output_md: bool, only_out_of_date: bool = True, n_workers: Optional[int]=None ): """Find, execute, and export all checked-in ipynbs. @@ -229,6 +229,8 @@ def execute_and_export_notebooks( output_md: Whether to save the executed notebooks as markdown only_out_of_date: Only re-execute and re-export notebooks whose output files are out of date. + n_workers: If set to 1, do not use parallelization. Otherwise, use + `multiprocessing.Pool(n_workers)` to execute notebooks in parallel. """ reporoot = get_git_root() nb_rel_paths = get_nb_rel_paths(sourceroot=reporoot / 'qualtran') @@ -262,4 +264,3 @@ def execute_and_export_notebooks( print("Slowest 10 notebooks:") for result in duration_nbs[:10]: print(f'{result.duration_s:5.2f}s {result.nb_in}') - diff --git a/qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard.py b/qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard.py index 5ac96318b1..8b761b0a86 100644 --- a/qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard.py +++ b/qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard.py @@ -39,7 +39,7 @@ """ from functools import cached_property -from typing import Dict, Iterator, Optional, Tuple, Union, Set +from typing import Dict, Iterator, Optional, Set, Tuple, Union import attrs import cirq @@ -69,6 +69,7 @@ from qualtran.bloqs.multiplexers.selected_majorana_fermion import SelectedMajoranaFermion from qualtran.cirq_interop import decompose_from_cirq_style_method from qualtran.drawing import Circle, TextBox, WireSymbol +from qualtran.resource_counting import BloqCountDictT, BloqCountT, SympySymbolAllocator from qualtran.symbolics import ceil, is_symbolic, log2, SymbolicInt @@ -318,7 +319,6 @@ def selected_majoranna_fermion_bloq(self) -> SelectedMajoranaFermion: target_gate=self._target_cirq_gate, ) - def build_composite_bloq( self, bb: 'BloqBuilder', x, y, spin, target, control=None ) -> Dict[str, 'SoquetT']: @@ -340,7 +340,6 @@ def build_call_graph( ) -> Union['BloqCountDictT', Set['BloqCountT']]: return self.selected_majoranna_fermion_bloq.build_call_graph(ssa) - def wire_symbol( self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() ) -> 'WireSymbol': @@ -469,11 +468,7 @@ def build_composite_bloq( # Delegate to `ApplyGateToLthQubit`. control, y, x, spin_up = bb.add_from( - self._apply_z_to_lth, - x=x, - y=y, - control=control, - target=spin_up, + self._apply_z_to_lth, x=x, y=y, control=control, target=spin_up ) target = bb.add(Join2(n_half, n_half), y1=spin_down, y2=spin_up) From e542c95039a3e758ac6d1ce74e44a208d7d8966e Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Tue, 21 Oct 2025 15:55:50 -0700 Subject: [PATCH 08/10] nice2 --- dev_tools/qualtran_dev_tools/notebook_execution.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dev_tools/qualtran_dev_tools/notebook_execution.py b/dev_tools/qualtran_dev_tools/notebook_execution.py index 73e8c7bbbd..0f7d6346cd 100644 --- a/dev_tools/qualtran_dev_tools/notebook_execution.py +++ b/dev_tools/qualtran_dev_tools/notebook_execution.py @@ -220,7 +220,11 @@ def __call__(self, nb_rel_path: Path, sourceroot: Path) -> _NotebookRunResult: def execute_and_export_notebooks( - *, output_nbs: bool, output_md: bool, only_out_of_date: bool = True, n_workers: Optional[int]=None + *, + output_nbs: bool, + output_md: bool, + only_out_of_date: bool = True, + n_workers: Optional[int] = None, ): """Find, execute, and export all checked-in ipynbs. From bd443242cf194ceb7f1ff8af1ba1a28256916ffa Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Mon, 27 Oct 2025 12:00:10 -0700 Subject: [PATCH 09/10] docstring --- dev_tools/qualtran_dev_tools/notebook_execution.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/dev_tools/qualtran_dev_tools/notebook_execution.py b/dev_tools/qualtran_dev_tools/notebook_execution.py index 0f7d6346cd..ea04c8bc9f 100644 --- a/dev_tools/qualtran_dev_tools/notebook_execution.py +++ b/dev_tools/qualtran_dev_tools/notebook_execution.py @@ -233,8 +233,11 @@ def execute_and_export_notebooks( output_md: Whether to save the executed notebooks as markdown only_out_of_date: Only re-execute and re-export notebooks whose output files are out of date. - n_workers: If set to 1, do not use parallelization. Otherwise, use - `multiprocessing.Pool(n_workers)` to execute notebooks in parallel. + n_workers: If set to 1, do not use parallelization. If set to `None` (the detault), + `multiprocessing.Pool()` will be used, which uses the number of processors as + a default. Otherwise, this argument is passed to + `multiprocessing.Pool(n_workers)` to execute notebooks in parallel on this many + worker processes.. """ reporoot = get_git_root() nb_rel_paths = get_nb_rel_paths(sourceroot=reporoot / 'qualtran') From 2585eb1353d7fe9c755984ef72646bc5e3251439 Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Mon, 27 Oct 2025 12:00:32 -0700 Subject: [PATCH 10/10] my own call graph --- .../block_encoding/lcu_block_encoding_test.py | 2 +- .../qubitization/select_hubbard.py | 66 +++++++++++-------- .../qubitization/select_hubbard_test.py | 43 +++++++++++- qualtran/resource_counting/generalizers.py | 14 +++- 4 files changed, 92 insertions(+), 33 deletions(-) diff --git a/qualtran/bloqs/block_encoding/lcu_block_encoding_test.py b/qualtran/bloqs/block_encoding/lcu_block_encoding_test.py index ad1d21a132..0632799611 100644 --- a/qualtran/bloqs/block_encoding/lcu_block_encoding_test.py +++ b/qualtran/bloqs/block_encoding/lcu_block_encoding_test.py @@ -48,7 +48,7 @@ def test_ctrl_lcu_be_cost(): assert bloq.controlled(CtrlSpec(cvs=0)) == attrs.evolve(bloq, control_val=0) assert get_cost_value(bloq.controlled(), QECGatesCost()) == GateCounts( - cswap=28, and_bloq=77, clifford=438, rotation=16, measurement=77 + cswap=28, and_bloq=77, clifford=294, rotation=16, measurement=77 ) diff --git a/qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard.py b/qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard.py index 8b761b0a86..6d912f2ae1 100644 --- a/qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard.py +++ b/qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard.py @@ -62,8 +62,9 @@ Signature, SoquetT, ) -from qualtran.bloqs.basic_gates import CSwap, TwoBitCSwap +from qualtran.bloqs.basic_gates import CNOT, CSwap, CZ, TwoBitCSwap from qualtran.bloqs.bookkeeping import Join2, Split2 +from qualtran.bloqs.mcmt import And from qualtran.bloqs.multiplexers.apply_gate_to_lth_target import ApplyGateToLthQubit from qualtran.bloqs.multiplexers.select_base import SelectOracle from qualtran.bloqs.multiplexers.selected_majorana_fermion import SelectedMajoranaFermion @@ -276,6 +277,10 @@ def log_m(self) -> SymbolicInt: raise NotImplementedError("Currently only supports the case where x_dim=y_dim.") return ceil(log2(self.x_dim)) + @cached_property + def N(self): + return 2 * self.x_dim * self.y_dim + @cached_property def control_registers(self) -> Tuple[Register, ...]: return () if self.control_val is None else (Register('control', QBit()),) @@ -290,7 +295,7 @@ def selection_registers(self) -> Tuple[Register, ...]: @cached_property def target_registers(self) -> Tuple[Register, ...]: - return (Register('target', QAny(self.x_dim * self.y_dim * 2)),) + return (Register('target', QAny(self.N)),) @cached_property def signature(self) -> Signature: @@ -307,9 +312,13 @@ def _target_cirq_gate(self): else: raise ValueError(f"Unknown gate {self.gate}") - @cached_property - def selected_majoranna_fermion_bloq(self) -> SelectedMajoranaFermion: - return SelectedMajoranaFermion( + def build_composite_bloq( + self, bb: 'BloqBuilder', x, y, spin, target, control=None + ) -> Dict[str, 'SoquetT']: + if is_symbolic(self.x_dim, self.y_dim): + raise DecomposeTypeError(f"Cannot decompose symbolic x_dim, y_dim in {self}") + + smf = SelectedMajoranaFermion( selection_regs=( Register('x', BQUInt(self.log_m, self.x_dim)), Register('y', BQUInt(self.log_m, self.y_dim)), @@ -318,14 +327,6 @@ def selected_majoranna_fermion_bloq(self) -> SelectedMajoranaFermion: control_regs=self.control_registers, target_gate=self._target_cirq_gate, ) - - def build_composite_bloq( - self, bb: 'BloqBuilder', x, y, spin, target, control=None - ) -> Dict[str, 'SoquetT']: - if is_symbolic(self.x_dim, self.y_dim): - raise DecomposeTypeError(f"Cannot decompose symbolic x_dim, y_dim in {self}") - - smf = self.selected_majoranna_fermion_bloq if self.control_val: control, x, y, spin, target = bb.add_from( smf, control=control, x=x, y=y, spin=spin, target=target @@ -338,7 +339,11 @@ def build_composite_bloq( def build_call_graph( self, ssa: 'SympySymbolAllocator' ) -> Union['BloqCountDictT', Set['BloqCountT']]: - return self.selected_majoranna_fermion_bloq.build_call_graph(ssa) + + count = self.N - 1 + if self.control_val is None: + count -= 1 + return {And(): count, And().adjoint(): count} def wire_symbol( self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() @@ -436,18 +441,6 @@ def signature(self) -> Signature: [*self.control_registers, *self.selection_registers, *self.target_registers] ) - @cached_property - def _apply_z_to_lth(self) -> ApplyGateToLthQubit: - c_size = 1 if self.control_val is None else 2 - return ApplyGateToLthQubit( - selection_regs=( - Register('y', BQUInt(self.signature.get_left('y').total_bits(), self.y_dim)), - Register('x', BQUInt(self.signature.get_left('x').total_bits(), self.x_dim)), - ), - nth_gate=lambda *_: cirq.Z, - control_regs=Register('control', QAny(c_size)), - ) - def build_composite_bloq( self, bb: 'BloqBuilder', V, x, y, target, control=None ) -> Dict[str, 'SoquetT']: @@ -457,8 +450,10 @@ def build_composite_bloq( # If we have a control bit, pack it into `ApplyToLthQubit` control register if self.control_val is not None: control = bb.join([V, control]) + c_size = 2 else: control = V + c_size = 1 # `target` is a QAny(xdim * ydim * 2). # We index into it like (alpha, y, x), @@ -468,7 +463,18 @@ def build_composite_bloq( # Delegate to `ApplyGateToLthQubit`. control, y, x, spin_up = bb.add_from( - self._apply_z_to_lth, x=x, y=y, control=control, target=spin_up + ApplyGateToLthQubit( + selection_regs=( + Register('y', BQUInt(self.signature.get_left('y').total_bits(), self.y_dim)), + Register('x', BQUInt(self.signature.get_left('x').total_bits(), self.x_dim)), + ), + nth_gate=lambda *_: cirq.Z, + control_regs=Register('control', QAny(c_size)), + ), + x=x, + y=y, + control=control, + target=spin_up, ) target = bb.add(Join2(n_half, n_half), y1=spin_down, y2=spin_up) @@ -485,7 +491,11 @@ def build_composite_bloq( def build_call_graph( self, ssa: 'SympySymbolAllocator' ) -> Union['BloqCountDictT', Set['BloqCountT']]: - return self._apply_z_to_lth.build_call_graph(ssa) + half_N = self.x_dim * self.y_dim + count = half_N + if self.control_val is None: + count -= 1 + return {And(): count, And().adjoint(): count, CNOT(): half_N - 1, CZ(): half_N} def wire_symbol( self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() 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 a13ef0ef62..5372dbaa3b 100644 --- a/qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard_test.py +++ b/qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard_test.py @@ -16,8 +16,13 @@ import numpy as np import pytest +import qualtran.testing as qlt_testing from qualtran import QAny, QUInt -from qualtran.bloqs.chemistry.hubbard_model.qubitization import HubbardSpinUpZ, SelectHubbard +from qualtran.bloqs.chemistry.hubbard_model.qubitization import ( + HubbardMajorannaOperator, + HubbardSpinUpZ, + SelectHubbard, +) from qualtran.bloqs.chemistry.hubbard_model.qubitization.select_hubbard import ( _hubb_majoranna, _hubb_majoranna_small, @@ -26,6 +31,12 @@ _sel_hubb, ) from qualtran.resource_counting import GateCounts, get_cost_value, QECGatesCost +from qualtran.resource_counting.generalizers import ( + generalize_cvs, + ignore_alloc_free, + ignore_cliffords, + ignore_split_join, +) from qualtran.simulation.classical_sim import do_phased_classical_simulation @@ -62,6 +73,22 @@ def test_hubbard_majoranna_small(bloq_autotester): bloq_autotester(_hubb_majoranna_small) +def test_hubb_majoranna_bloq_counts(): + for n in [2, 3, 10, 11]: + bloq = HubbardMajorannaOperator(x_dim=n, y_dim=n, control_val=None) + qlt_testing.assert_equivalent_bloq_counts( + bloq, + generalizer=[ignore_cliffords, ignore_alloc_free, ignore_split_join, generalize_cvs], + ) + + for n in [2, 3, 10, 11]: + bloq = HubbardMajorannaOperator(x_dim=n, y_dim=n, control_val=1) + qlt_testing.assert_equivalent_bloq_counts( + bloq, + generalizer=[ignore_cliffords, ignore_alloc_free, ignore_split_join, generalize_cvs], + ) + + def test_hubbard_spin_up_z_symb(bloq_autotester): bloq_autotester(_hubb_spin_up_z) @@ -70,6 +97,20 @@ def test_hubbard_spin_up_z(bloq_autotester): bloq_autotester(_hubb_spin_up_z_small) +def test_hubbard_spin_up_z_bloq_counts(): + for n in [2, 3, 10, 11]: + bloq = HubbardSpinUpZ(x_dim=n, y_dim=n, control_val=None) + qlt_testing.assert_equivalent_bloq_counts( + bloq, generalizer=[ignore_alloc_free, ignore_split_join, generalize_cvs] + ) + + for n in [2, 3, 10, 11]: + bloq = HubbardSpinUpZ(x_dim=n, y_dim=n, control_val=1) + qlt_testing.assert_equivalent_bloq_counts( + bloq, generalizer=[ignore_alloc_free, ignore_split_join, generalize_cvs] + ) + + def test_hubbard_spin_up_z_classical(): rng = np.random.default_rng(52) diff --git a/qualtran/resource_counting/generalizers.py b/qualtran/resource_counting/generalizers.py index 9c2a3fefd3..b1d6b2ad8a 100644 --- a/qualtran/resource_counting/generalizers.py +++ b/qualtran/resource_counting/generalizers.py @@ -43,9 +43,17 @@ def _ignore_wrapper(f: Callable[[Bloq], Optional[Bloq]], b: Bloq) -> Optional[Bl def ignore_split_join(b: Bloq) -> Optional[Bloq]: """A generalizer that ignores split and join operations.""" - from qualtran.bloqs.bookkeeping import AutoPartition, Cast, Join, Partition, Split - - if isinstance(b, (Split, Join, Partition, Cast)): + from qualtran.bloqs.bookkeeping import ( + AutoPartition, + Cast, + Join, + Join2, + Partition, + Split, + Split2, + ) + + if isinstance(b, (Split, Split2, Join, Join2, Partition, Cast)): return None if isinstance(b, AutoPartition): return ignore_split_join(b.bloq)