diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7aa99955f..9740e2532 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -65,6 +65,8 @@ jobs: pip install --no-deps -e . - run: | python dev_tools/execute-notebooks.py + env: + NUMBA_NUM_THREADS: 4 format: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index d02120205..339809b29 100644 --- a/.gitignore +++ b/.gitignore @@ -135,6 +135,7 @@ dmypy.json mod_exp_bloq.json mod_exp_cbloq.json musical_score_example.json +ui_export/ # Jupyter jupyter_kernel.lock diff --git a/dev_tools/autogenerate-bloqs-notebooks-v2.py b/dev_tools/autogenerate-bloqs-notebooks-v2.py index c98c6cd3e..92e68c5ba 100644 --- a/dev_tools/autogenerate-bloqs-notebooks-v2.py +++ b/dev_tools/autogenerate-bloqs-notebooks-v2.py @@ -14,18 +14,15 @@ """Autogeneration of Jupyter notebooks. -For each notebook spec listed in the various global variables (in this file) -we write a notebook with a title, module docstring, -standard imports, and information on each bloq listed in the +For each notebook spec listed in the various global variables imported from +`qualtran_dev_tools.notebook_specs` we write a notebook with a title, module +docstring, standard imports, and information on each bloq listed in the `bloq_specs` field. For each bloq, we render a docstring and diagrams. ## Adding a new bloq. - 1. Create a qualtran.BloqExample perhaps using the `@bloq_example` decorator. Wrap it in a - `qualtran.BloqDocSpec`. This code should live alongside the bloq. - 2. If this is a new module: add a new entry to the appropriate notebook spec global variable - in this file (according to its category/organization). - 3. Update the `NotebookSpec` `bloq_specs` field to include the `BloqDocSpec` for your new bloq. +Follow the instructions in `dev_tools/qualtran_dev_tools/notebook_specs.py` to +add a new bloq. ## Autogen behavior. @@ -49,794 +46,8 @@ from typing import Iterable, List from qualtran_dev_tools.bloq_finder import get_bloqdocspecs -from qualtran_dev_tools.git_tools import get_git_root from qualtran_dev_tools.jupyter_autogen import NotebookSpecV2, render_notebook - -import qualtran.bloqs.arithmetic.addition -import qualtran.bloqs.arithmetic.bitwise -import qualtran.bloqs.arithmetic.comparison -import qualtran.bloqs.arithmetic.controlled_add_or_subtract -import qualtran.bloqs.arithmetic.controlled_addition -import qualtran.bloqs.arithmetic.conversions -import qualtran.bloqs.arithmetic.multiplication -import qualtran.bloqs.arithmetic.negate -import qualtran.bloqs.arithmetic.permutation -import qualtran.bloqs.arithmetic.sorting -import qualtran.bloqs.arithmetic.subtraction -import qualtran.bloqs.arithmetic.trigonometric -import qualtran.bloqs.basic_gates.swap -import qualtran.bloqs.block_encoding.block_encoding_base -import qualtran.bloqs.block_encoding.chebyshev_polynomial -import qualtran.bloqs.block_encoding.lcu_block_encoding -import qualtran.bloqs.block_encoding.linear_combination -import qualtran.bloqs.block_encoding.phase -import qualtran.bloqs.block_encoding.product -import qualtran.bloqs.block_encoding.sparse_matrix -import qualtran.bloqs.block_encoding.tensor_product -import qualtran.bloqs.block_encoding.unitary -import qualtran.bloqs.bookkeeping -import qualtran.bloqs.bookkeeping.allocate -import qualtran.bloqs.bookkeeping.auto_partition -import qualtran.bloqs.bookkeeping.cast -import qualtran.bloqs.bookkeeping.free -import qualtran.bloqs.bookkeeping.join -import qualtran.bloqs.bookkeeping.partition -import qualtran.bloqs.bookkeeping.split -import qualtran.bloqs.chemistry.df.double_factorization -import qualtran.bloqs.chemistry.hubbard_model.qubitization -import qualtran.bloqs.chemistry.pbc.first_quantization.prepare_t -import qualtran.bloqs.chemistry.pbc.first_quantization.prepare_uv -import qualtran.bloqs.chemistry.pbc.first_quantization.projectile.select_and_prepare -import qualtran.bloqs.chemistry.pbc.first_quantization.select_t -import qualtran.bloqs.chemistry.pbc.first_quantization.select_uv -import qualtran.bloqs.chemistry.quad_fermion.givens_bloq -import qualtran.bloqs.chemistry.sf.single_factorization -import qualtran.bloqs.chemistry.sparse.prepare -import qualtran.bloqs.chemistry.sparse.walk_operator -import qualtran.bloqs.chemistry.thc.prepare -import qualtran.bloqs.chemistry.trotter.grid_ham.inverse_sqrt -import qualtran.bloqs.chemistry.trotter.grid_ham.qvr -import qualtran.bloqs.chemistry.trotter.hubbard.hopping -import qualtran.bloqs.chemistry.trotter.hubbard.interaction -import qualtran.bloqs.chemistry.trotter.ising.unitaries -import qualtran.bloqs.chemistry.trotter.trotterized_unitary -import qualtran.bloqs.data_loading.qrom -import qualtran.bloqs.data_loading.qrom_base -import qualtran.bloqs.data_loading.select_swap_qrom -import qualtran.bloqs.factoring.ecc -import qualtran.bloqs.factoring.mod_exp -import qualtran.bloqs.hamiltonian_simulation.hamiltonian_simulation_by_gqsp -import qualtran.bloqs.mcmt.and_bloq -import qualtran.bloqs.mcmt.controlled_via_and -import qualtran.bloqs.mcmt.ctrl_spec_and -import qualtran.bloqs.mcmt.multi_control_pauli -import qualtran.bloqs.mcmt.multi_target_cnot -import qualtran.bloqs.mod_arithmetic.mod_addition -import qualtran.bloqs.multiplexers.apply_gate_to_lth_target -import qualtran.bloqs.multiplexers.apply_lth_bloq -import qualtran.bloqs.multiplexers.black_box_select -import qualtran.bloqs.multiplexers.select_base -import qualtran.bloqs.multiplexers.select_pauli_lcu -import qualtran.bloqs.phase_estimation.lp_resource_state -import qualtran.bloqs.phase_estimation.qubitization_qpe -import qualtran.bloqs.phase_estimation.text_book_qpe -import qualtran.bloqs.qft.approximate_qft -import qualtran.bloqs.qft.qft_phase_gradient -import qualtran.bloqs.qft.qft_text_book -import qualtran.bloqs.qft.two_bit_ffft -import qualtran.bloqs.qsp.generalized_qsp -import qualtran.bloqs.qubitization.qubitization_walk_operator -import qualtran.bloqs.reflections -import qualtran.bloqs.reflections.prepare_identity -import qualtran.bloqs.reflections.reflection_using_prepare -import qualtran.bloqs.rotations.hamming_weight_phasing -import qualtran.bloqs.rotations.phase_gradient -import qualtran.bloqs.rotations.phasing_via_cost_function -import qualtran.bloqs.rotations.programmable_rotation_gate_array -import qualtran.bloqs.rotations.quantum_variable_rotation -import qualtran.bloqs.state_preparation.black_box_prepare -import qualtran.bloqs.state_preparation.prepare_base -import qualtran.bloqs.state_preparation.prepare_uniform_superposition -import qualtran.bloqs.state_preparation.state_preparation_alias_sampling -import qualtran.bloqs.state_preparation.state_preparation_via_rotation -import qualtran.bloqs.swap_network.cswap_approx -import qualtran.bloqs.swap_network.multiplexed_cswap -import qualtran.bloqs.swap_network.swap_with_zero - -GIT_ROOT = get_git_root() -SOURCE_DIR = GIT_ROOT / 'qualtran/' - -# -------------------------------------------------------------------------- -# ----- Basic Gates ---------------------------------------------------- -# -------------------------------------------------------------------------- -BASIC_GATES: List[NotebookSpecV2] = [ - NotebookSpecV2( - title='T Gate', - module=qualtran.bloqs.basic_gates.t_gate, - bloq_specs=[qualtran.bloqs.basic_gates.t_gate._T_GATE_DOC], - ), - NotebookSpecV2( - title='Toffoli', - module=qualtran.bloqs.basic_gates.toffoli, - bloq_specs=[qualtran.bloqs.basic_gates.toffoli._TOFFOLI_DOC], - ), - NotebookSpecV2( - title='Hadamard', - module=qualtran.bloqs.basic_gates.hadamard, - bloq_specs=[ - qualtran.bloqs.basic_gates.hadamard._HADAMARD_DOC, - qualtran.bloqs.basic_gates.hadamard._CHADAMARD_DOC, - ], - ), - NotebookSpecV2( - title='CNOT', - module=qualtran.bloqs.basic_gates.cnot, - bloq_specs=[qualtran.bloqs.basic_gates.cnot._CNOT_DOC], - ), - NotebookSpecV2( - title='Z, S, and CZ', - module=qualtran.bloqs.basic_gates.z_basis, - path_stem='diag_gates', - bloq_specs=[ - qualtran.bloqs.basic_gates.z_basis._Z_GATE_DOC, - qualtran.bloqs.basic_gates.s_gate._S_GATE_DOC, - qualtran.bloqs.basic_gates.z_basis._CZ_DOC, - ], - ), - NotebookSpecV2( - title='Y Gate', - module=qualtran.bloqs.basic_gates.y_gate, - bloq_specs=[ - qualtran.bloqs.basic_gates.y_gate._Y_GATE_DOC, - qualtran.bloqs.basic_gates.y_gate._CY_GATE_DOC, - ], - ), - NotebookSpecV2( - title='And', - module=qualtran.bloqs.mcmt.and_bloq, - bloq_specs=[ - qualtran.bloqs.mcmt.and_bloq._AND_DOC, - qualtran.bloqs.mcmt.and_bloq._MULTI_AND_DOC, - ], - ), - NotebookSpecV2( - title='States and Effects', - module=qualtran.bloqs.basic_gates.z_basis, - path_stem='states_and_effects', - bloq_specs=[ - qualtran.bloqs.basic_gates.z_basis._ZERO_STATE_DOC, - qualtran.bloqs.basic_gates.z_basis._ZERO_EFFECT_DOC, - qualtran.bloqs.basic_gates.z_basis._ONE_STATE_DOC, - qualtran.bloqs.basic_gates.z_basis._ONE_EFFECT_DOC, - qualtran.bloqs.basic_gates.z_basis._INT_STATE_DOC, - qualtran.bloqs.basic_gates.z_basis._INT_EFFECT_DOC, - qualtran.bloqs.basic_gates.x_basis._PLUS_STATE_DOC, - qualtran.bloqs.basic_gates.x_basis._PLUS_EFFECT_DOC, - qualtran.bloqs.basic_gates.x_basis._MINUS_STATE_DOC, - qualtran.bloqs.basic_gates.x_basis._MINUS_EFFECT_DOC, - ], - ), - NotebookSpecV2( - title='Basic Swaps', - module=qualtran.bloqs.basic_gates.swap, - bloq_specs=[ - qualtran.bloqs.basic_gates.swap._TWO_BIT_SWAP_DOC, - qualtran.bloqs.basic_gates.swap._TWO_BIT_CSWAP_DOC, - qualtran.bloqs.basic_gates.swap._SWAP_DOC, - qualtran.bloqs.basic_gates.swap._CSWAP_DOC, - ], - ), - NotebookSpecV2( - title='Swap Networks', - module=qualtran.bloqs.swap_network, - bloq_specs=[ - qualtran.bloqs.swap_network.cswap_approx._APPROX_CSWAP_DOC, - qualtran.bloqs.swap_network.swap_with_zero._SWZ_DOC, - qualtran.bloqs.swap_network.multiplexed_cswap._MULTIPLEXED_CSWAP_DOC, - ], - ), - NotebookSpecV2( - title='Global Phase', - module=qualtran.bloqs.basic_gates.global_phase, - bloq_specs=[qualtran.bloqs.basic_gates.global_phase._GLOBAL_PHASE_DOC], - ), - NotebookSpecV2( - title='Identity Gate', - module=qualtran.bloqs.basic_gates.identity, - bloq_specs=[qualtran.bloqs.basic_gates.identity._IDENTITY_DOC], - ), - NotebookSpecV2( - title='Bookkeeping Bloqs', - module=qualtran.bloqs.bookkeeping, - bloq_specs=[ - qualtran.bloqs.bookkeeping.allocate._ALLOC_DOC, - qualtran.bloqs.bookkeeping.free._FREE_DOC, - qualtran.bloqs.bookkeeping.split._SPLIT_DOC, - qualtran.bloqs.bookkeeping.join._JOIN_DOC, - qualtran.bloqs.bookkeeping.partition._PARTITION_DOC, - qualtran.bloqs.bookkeeping.auto_partition._AUTO_PARTITION_DOC, - qualtran.bloqs.bookkeeping.cast._CAST_DOC, - ], - ), - NotebookSpecV2( - title='Control Specification (And)', - module=qualtran.bloqs.mcmt.ctrl_spec_and, - bloq_specs=[qualtran.bloqs.mcmt.ctrl_spec_and._CTRLSPEC_AND_DOC], - ), - NotebookSpecV2( - title='Multi control bloq via single control bloq and `And` ladder', - module=qualtran.bloqs.mcmt.controlled_via_and, - bloq_specs=[qualtran.bloqs.mcmt.controlled_via_and._CONTROLLED_VIA_AND_DOC], - ), -] - - -# -------------------------------------------------------------------------- -# ----- Chemistry ------------------------------------------------------ -# -------------------------------------------------------------------------- -CHEMISTRY: List[NotebookSpecV2] = [ - NotebookSpecV2( - title='Sparse', - module=qualtran.bloqs.chemistry.sparse, - bloq_specs=[ - qualtran.bloqs.chemistry.sparse.prepare._SPARSE_PREPARE, - qualtran.bloqs.chemistry.sparse.select_bloq._SPARSE_SELECT, - ], - directory=f'{SOURCE_DIR}/bloqs/chemistry/sparse', - ), - NotebookSpecV2( - title='Single Factorization', - module=qualtran.bloqs.chemistry.sf.single_factorization, - bloq_specs=[ - qualtran.bloqs.chemistry.sf.single_factorization._SF_ONE_BODY, - qualtran.bloqs.chemistry.sf.single_factorization._SF_BLOCK_ENCODING, - ], - directory=f'{SOURCE_DIR}/bloqs/chemistry/sf', - ), - NotebookSpecV2( - title='Double Factorization', - module=qualtran.bloqs.chemistry.df.double_factorization, - bloq_specs=[ - qualtran.bloqs.chemistry.df.double_factorization._DF_ONE_BODY, - qualtran.bloqs.chemistry.df.double_factorization._DF_BLOCK_ENCODING, - ], - directory=f'{SOURCE_DIR}/bloqs/chemistry/df', - ), - NotebookSpecV2( - title='Tensor Hypercontraction', - module=qualtran.bloqs.chemistry.thc, - bloq_specs=[ - qualtran.bloqs.chemistry.thc.prepare._THC_UNI_PREP, - qualtran.bloqs.chemistry.thc.prepare._THC_PREPARE, - qualtran.bloqs.chemistry.thc.select_bloq._THC_SELECT, - ], - directory=f'{SOURCE_DIR}/bloqs/chemistry/thc', - ), - NotebookSpecV2( - title='First Quantized Hamiltonian', - module=qualtran.bloqs.chemistry.pbc.first_quantization, - bloq_specs=[ - qualtran.bloqs.chemistry.pbc.first_quantization.select_and_prepare._FIRST_QUANTIZED_PREPARE_DOC, - qualtran.bloqs.chemistry.pbc.first_quantization.select_and_prepare._FIRST_QUANTIZED_SELECT_DOC, - qualtran.bloqs.chemistry.pbc.first_quantization.prepare_t._PREPARE_T, - qualtran.bloqs.chemistry.pbc.first_quantization.prepare_uv._PREPARE_UV, - qualtran.bloqs.chemistry.pbc.first_quantization.select_t._SELECT_T, - qualtran.bloqs.chemistry.pbc.first_quantization.select_uv._SELECT_UV, - ], - directory=f'{SOURCE_DIR}/bloqs/chemistry/pbc/first_quantization', - ), - NotebookSpecV2( - title='First Quantized Hamiltonian with Quantum Projectile', - module=qualtran.bloqs.chemistry.pbc.first_quantization.projectile, - bloq_specs=[ - qualtran.bloqs.chemistry.pbc.first_quantization.projectile.select_and_prepare._FIRST_QUANTIZED_WITH_PROJ_PREPARE_DOC, - qualtran.bloqs.chemistry.pbc.first_quantization.projectile.select_and_prepare._FIRST_QUANTIZED_WITH_PROJ_SELECT_DOC, - ], - directory=f'{SOURCE_DIR}/bloqs/chemistry/pbc/first_quantization/projectile', - ), - NotebookSpecV2( - title='Trotter Bloqs', - module=qualtran.bloqs.chemistry.trotter.grid_ham, - bloq_specs=[ - qualtran.bloqs.chemistry.trotter.grid_ham.inverse_sqrt._POLY_INV_SQRT, - qualtran.bloqs.chemistry.trotter.grid_ham.inverse_sqrt._NR_INV_SQRT, - qualtran.bloqs.chemistry.trotter.grid_ham.qvr._QVR, - qualtran.bloqs.chemistry.trotter.grid_ham.kinetic._KINETIC_ENERGY, - qualtran.bloqs.chemistry.trotter.grid_ham.potential._PAIR_POTENTIAL, - qualtran.bloqs.chemistry.trotter.grid_ham.potential._POTENTIAL_ENERGY, - ], - path_stem='trotter', - ), - NotebookSpecV2( - title='Trotterization', - module=qualtran.bloqs.chemistry.trotter.trotterized_unitary, - bloq_specs=[qualtran.bloqs.chemistry.trotter.trotterized_unitary._TROTT_UNITARY_DOC], - directory=f'{SOURCE_DIR}/bloqs/chemistry/trotter', - ), - NotebookSpecV2( - title='Ising Trotter Bloqs', - module=qualtran.bloqs.chemistry.trotter.ising, - bloq_specs=[ - qualtran.bloqs.chemistry.trotter.ising.unitaries._ISING_X_UNITARY_DOC, - qualtran.bloqs.chemistry.trotter.ising.unitaries._ISING_ZZ_UNITARY_DOC, - ], - directory=f'{SOURCE_DIR}/bloqs/chemistry/trotter/ising', - ), - NotebookSpecV2( - title='Trotterized Hubbard', - module=qualtran.bloqs.chemistry.trotter.hubbard, - bloq_specs=[ - qualtran.bloqs.chemistry.trotter.hubbard.hopping._HOPPING_DOC, - qualtran.bloqs.chemistry.trotter.hubbard.hopping._PLAQUETTE_DOC, - qualtran.bloqs.chemistry.trotter.hubbard.hopping._HOPPING_TILE_HWP_DOC, - qualtran.bloqs.chemistry.trotter.hubbard.interaction._INTERACTION_DOC, - qualtran.bloqs.chemistry.trotter.hubbard.interaction._INTERACTION_HWP_DOC, - ], - directory=f'{SOURCE_DIR}/bloqs/chemistry/trotter/hubbard', - ), - NotebookSpecV2( - title='Givens Rotations', - module=qualtran.bloqs.chemistry.quad_fermion.givens_bloq, - bloq_specs=[ - qualtran.bloqs.chemistry.quad_fermion.givens_bloq._REAL_GIVENS_DOC, - qualtran.bloqs.chemistry.quad_fermion.givens_bloq._CPLX_GIVENS_DOC, - ], - ), -] - -# -------------------------------------------------------------------------- -# ----- Arithmetic ----------------------------------------------------- -# -------------------------------------------------------------------------- -ARITHMETIC = [ - NotebookSpecV2( - title='Addition', - module=qualtran.bloqs.arithmetic.addition, - bloq_specs=[ - qualtran.bloqs.arithmetic.addition._ADD_DOC, - qualtran.bloqs.arithmetic.addition._ADD_OOP_DOC, - qualtran.bloqs.arithmetic.addition._ADD_K_DOC, - ], - ), - NotebookSpecV2( - title='Controlled Addition', - module=qualtran.bloqs.arithmetic.controlled_addition, - bloq_specs=[qualtran.bloqs.arithmetic.controlled_addition._CADD_DOC], - ), - NotebookSpecV2( - title='Negation', - module=qualtran.bloqs.arithmetic.negate, - bloq_specs=[qualtran.bloqs.arithmetic.negate._NEGATE_DOC], - ), - NotebookSpecV2( - title='Subtraction', - module=qualtran.bloqs.arithmetic.subtraction, - bloq_specs=[ - qualtran.bloqs.arithmetic.subtraction._SUB_DOC, - qualtran.bloqs.arithmetic.subtraction._SUB_FROM_DOC, - ], - ), - NotebookSpecV2( - title='Controlled Add-or-Subtract', - module=qualtran.bloqs.arithmetic.controlled_add_or_subtract, - bloq_specs=[ - qualtran.bloqs.arithmetic.controlled_add_or_subtract._CONTROLLED_ADD_OR_SUBTRACT_DOC - ], - ), - NotebookSpecV2( - title='Multiplication', - module=qualtran.bloqs.arithmetic.multiplication, - bloq_specs=[ - qualtran.bloqs.arithmetic.multiplication._PLUS_EQUALS_PRODUCT_DOC, - qualtran.bloqs.arithmetic.multiplication._PRODUCT_DOC, - qualtran.bloqs.arithmetic.multiplication._SQUARE_DOC, - qualtran.bloqs.arithmetic.multiplication._SUM_OF_SQUARES_DOC, - qualtran.bloqs.arithmetic.multiplication._SCALE_INT_BY_REAL_DOC, - qualtran.bloqs.arithmetic.multiplication._MULTIPLY_TWO_REALS_DOC, - qualtran.bloqs.arithmetic.multiplication._SQUARE_REAL_NUMBER_DOC, - qualtran.bloqs.arithmetic.multiplication._INVERT_REAL_NUMBER_DOC, - ], - ), - NotebookSpecV2( - title='Comparison', - module=qualtran.bloqs.arithmetic.comparison, - bloq_specs=[ - qualtran.bloqs.arithmetic.comparison._LT_K_DOC, - qualtran.bloqs.arithmetic.comparison._GREATER_THAN_DOC, - qualtran.bloqs.arithmetic.comparison._GREATER_THAN_K_DOC, - qualtran.bloqs.arithmetic.comparison._EQUALS_K_DOC, - qualtran.bloqs.arithmetic.comparison._BI_QUBITS_MIXER_DOC, - qualtran.bloqs.arithmetic.comparison._SQ_CMP_DOC, - qualtran.bloqs.arithmetic.comparison._LEQ_DOC, - qualtran.bloqs.arithmetic.comparison._CLinearDepthGreaterThan_DOC, - ], - ), - NotebookSpecV2( - title='Integer Conversions', - module=qualtran.bloqs.arithmetic.conversions, - bloq_specs=[ - qualtran.bloqs.arithmetic.conversions.ones_complement_to_twos_complement._SIGNED_TO_TWOS, - qualtran.bloqs.arithmetic.conversions.sign_extension._SIGN_EXTEND_DOC, - qualtran.bloqs.arithmetic.conversions.sign_extension._SIGN_TRUNCATE_DOC, - ], - ), - NotebookSpecV2( - title='Sorting', - module=qualtran.bloqs.arithmetic.sorting, - bloq_specs=[ - qualtran.bloqs.arithmetic.sorting._COMPARATOR_DOC, - qualtran.bloqs.arithmetic.sorting._PARALLEL_COMPARATORS_DOC, - qualtran.bloqs.arithmetic.sorting._BITONIC_MERGE_DOC, - qualtran.bloqs.arithmetic.sorting._BITONIC_SORT_DOC, - ], - directory=f'{SOURCE_DIR}/bloqs/arithmetic/', - ), - NotebookSpecV2( - title='Indexing', - module=qualtran.bloqs.arithmetic.conversions.contiguous_index, - bloq_specs=[qualtran.bloqs.arithmetic.conversions.contiguous_index._TO_CONTG_INDX], - ), - NotebookSpecV2( - title='Permutations', - module=qualtran.bloqs.arithmetic.permutation, - bloq_specs=[ - qualtran.bloqs.arithmetic.permutation._PERMUTATION_DOC, - qualtran.bloqs.arithmetic.permutation._PERMUTATION_CYCLE_DOC, - ], - ), - NotebookSpecV2( - title='Bitwise Operations', - module=qualtran.bloqs.arithmetic.bitwise, - bloq_specs=[ - qualtran.bloqs.arithmetic.bitwise._XOR_DOC, - qualtran.bloqs.arithmetic.bitwise._BITWISE_NOT_DOC, - ], - ), - NotebookSpecV2( - title='Trigonometric Functions', - module=qualtran.bloqs.arithmetic.trigonometric, - bloq_specs=[qualtran.bloqs.arithmetic.trigonometric.arcsin._ARCSIN_DOC], - ), -] - -MOD_ARITHMETIC = [ - NotebookSpecV2( - title='Modular Addition', - module=qualtran.bloqs.mod_arithmetic.mod_addition, - bloq_specs=[ - qualtran.bloqs.mod_arithmetic.mod_addition._MOD_ADD_DOC, - qualtran.bloqs.mod_arithmetic.mod_addition._MOD_ADD_K_DOC, - qualtran.bloqs.mod_arithmetic.mod_addition._C_MOD_ADD_DOC, - ], - ), - NotebookSpecV2( - title='Modular Subtraction', - module=qualtran.bloqs.mod_arithmetic.mod_subtraction, - bloq_specs=[ - qualtran.bloqs.mod_arithmetic.mod_subtraction._MOD_NEG_DOC, - qualtran.bloqs.mod_arithmetic.mod_subtraction._CMOD_NEG_DOC, - qualtran.bloqs.mod_arithmetic.mod_subtraction._MOD_SUB_DOC, - qualtran.bloqs.mod_arithmetic.mod_subtraction._CMOD_SUB_DOC, - ], - ), - NotebookSpecV2( - title='Modular Multiplication', - module=qualtran.bloqs.mod_arithmetic.mod_multiplication, - bloq_specs=[ - qualtran.bloqs.mod_arithmetic.mod_multiplication._MOD_DBL_DOC, - qualtran.bloqs.mod_arithmetic.mod_multiplication._C_MOD_MUL_K_DOC, - ], - ), - NotebookSpecV2( - title='Modular Exponentiation', - module=qualtran.bloqs.factoring.mod_exp, - bloq_specs=[qualtran.bloqs.factoring.mod_exp._MODEXP_DOC], - directory=f'{SOURCE_DIR}/bloqs/factoring', - ), - NotebookSpecV2( - title='Elliptic Curve Addition', - module=qualtran.bloqs.factoring.ecc.ec_add, - bloq_specs=[qualtran.bloqs.factoring.ecc.ec_add._EC_ADD_DOC], - ), - NotebookSpecV2( - title='Elliptic Curve Cryptography', - module=qualtran.bloqs.factoring.ecc, - bloq_specs=[ - qualtran.bloqs.factoring.ecc.find_ecc_private_key._ECC_BLOQ_DOC, - qualtran.bloqs.factoring.ecc.ec_phase_estimate_r._EC_PE_BLOQ_DOC, - qualtran.bloqs.factoring.ecc.ec_add_r._ECC_ADD_R_BLOQ_DOC, - qualtran.bloqs.factoring.ecc.ec_add_r._EC_WINDOW_ADD_BLOQ_DOC, - ], - ), -] - - -ROT_QFT_PE = [ - # -------------------------------------------------------------------------- - # ----- Rotations ----------------------------------------------------- - # -------------------------------------------------------------------------- - NotebookSpecV2( - title='Basic Rotation Gates', - module=qualtran.bloqs.basic_gates.rotation, - bloq_specs=[ - qualtran.bloqs.basic_gates.rotation._X_POW_DOC, - qualtran.bloqs.basic_gates.rotation._Y_POW_DOC, - qualtran.bloqs.basic_gates.rotation._Z_POW_DOC, - ], - ), - NotebookSpecV2( - title='SU2 Rotation', - module=qualtran.bloqs.basic_gates.su2_rotation, - bloq_specs=[qualtran.bloqs.basic_gates.su2_rotation._SU2_ROTATION_GATE_DOC], - ), - NotebookSpecV2( - title='Quantum Variable Rotation', - module=qualtran.bloqs.rotations.quantum_variable_rotation, - bloq_specs=[ - qualtran.bloqs.rotations.quantum_variable_rotation._QVR_ZPOW, - qualtran.bloqs.rotations.quantum_variable_rotation._QVR_PHASE_GRADIENT, - ], - directory=f'{SOURCE_DIR}/bloqs/rotations/', - ), - NotebookSpecV2( - title='Phasing via Cost function', - module=qualtran.bloqs.rotations.phasing_via_cost_function, - bloq_specs=[qualtran.bloqs.rotations.phasing_via_cost_function._PHASING_VIA_COST_FUNCTION], - directory=f'{SOURCE_DIR}/bloqs/rotations/', - ), - NotebookSpecV2( - title='Rotations via Phase Gradients', - module=qualtran.bloqs.rotations.phase_gradient, - bloq_specs=[ - qualtran.bloqs.rotations.phase_gradient._PHASE_GRADIENT_UNITARY_DOC, - qualtran.bloqs.rotations.phase_gradient._PHASE_GRADIENT_STATE_DOC, - qualtran.bloqs.rotations.phase_gradient._ADD_INTO_PHASE_GRAD_DOC, - qualtran.bloqs.rotations.phase_gradient._ADD_SCALED_VAL_INTO_PHASE_REG_DOC, - ], - ), - NotebookSpecV2( - title='Z Rotations via Hamming Weight Phasing', - module=qualtran.bloqs.rotations.hamming_weight_phasing, - bloq_specs=[ - qualtran.bloqs.rotations.hamming_weight_phasing._HAMMING_WEIGHT_PHASING_DOC, - qualtran.bloqs.rotations.hamming_weight_phasing._HAMMING_WEIGHT_PHASING_VIA_PHASE_GRADIENT_DOC, - ], - ), - NotebookSpecV2( - title='Programmable Rotation Gate Array', - module=qualtran.bloqs.rotations.programmable_rotation_gate_array, - bloq_specs=[ - qualtran.bloqs.rotations.programmable_rotation_gate_array._PROGRAMMABLE_ROTATAION_GATE_ARRAY_DOC - ], - ), - # -------------------------------------------------------------------------- - # ----- QFT ----------------------------------------------------- - # -------------------------------------------------------------------------- - NotebookSpecV2( - title='Two Bit FFFT Gate', - module=qualtran.bloqs.qft.two_bit_ffft, - bloq_specs=[qualtran.bloqs.qft.two_bit_ffft._TWO_BIT_FFFT_DOC], - ), - NotebookSpecV2( - title='Approximate QFT', - module=qualtran.bloqs.qft.approximate_qft, - bloq_specs=[qualtran.bloqs.qft.approximate_qft._CC_AQFT_DOC], - ), - NotebookSpecV2( - title='Textbook QFT', - module=qualtran.bloqs.qft.qft_text_book, - bloq_specs=[qualtran.bloqs.qft.qft_text_book._QFT_TEXT_BOOK_DOC], - ), - NotebookSpecV2( - title='Phase Gradient QFT', - module=qualtran.bloqs.qft.qft_phase_gradient, - bloq_specs=[qualtran.bloqs.qft.qft_phase_gradient._QFT_PHASE_GRADIENT_DOC], - ), - # -------------------------------------------------------------------------- - # ----- Phase Estimation ---------------------------------------- - # -------------------------------------------------------------------------- - NotebookSpecV2( - title='Optimal resource states for Phase Estimation by A. Luis and J. Peřina', - module=qualtran.bloqs.phase_estimation.lp_resource_state, - bloq_specs=[ - qualtran.bloqs.phase_estimation.lp_resource_state._CC_LPRS_INTERIM_PREP_DOC, - qualtran.bloqs.phase_estimation.lp_resource_state._CC_LP_RESOURCE_STATE_DOC, - ], - ), - NotebookSpecV2( - title='Textbook Quantum Phase Estimation', - module=qualtran.bloqs.phase_estimation.text_book_qpe, - bloq_specs=[ - qualtran.bloqs.phase_estimation.qpe_window_state._CC_RECTANGULAR_WINDOW_STATE_DOC, - qualtran.bloqs.phase_estimation.text_book_qpe._CC_TEXTBOOK_PHASE_ESTIMATION_DOC, - ], - ), - NotebookSpecV2( - title='Kaiser Window State for Quantum Phase Estimation', - module=qualtran.bloqs.phase_estimation.kaiser_window_state, - bloq_specs=[ - qualtran.bloqs.phase_estimation.kaiser_window_state._CC_KAISER_WINDOW_STATE_DOC - ], - ), - NotebookSpecV2( - title='Qubitization Walk Operator', - module=qualtran.bloqs.qubitization.qubitization_walk_operator, - bloq_specs=[qualtran.bloqs.qubitization.qubitization_walk_operator._QUBITIZATION_WALK_DOC], - ), - NotebookSpecV2( - title='Qubitization Phase Estimation', - module=qualtran.bloqs.phase_estimation.qubitization_qpe, - bloq_specs=[qualtran.bloqs.phase_estimation.qubitization_qpe._QUBITIZATION_QPE_DOC], - ), -] - -# -------------------------------------------------------------------------- -# ----- Block Encoding ---------------------------------------------------------- -# -------------------------------------------------------------------------- -BLOCK_ENCODING: List[NotebookSpecV2] = [ - NotebookSpecV2( - title='Block Encoding Interface', - module=qualtran.bloqs.block_encoding, - bloq_specs=[qualtran.bloqs.block_encoding.block_encoding_base._BLOCK_ENCODING_DOC], - ), - NotebookSpecV2( - title='Unitary', - module=qualtran.bloqs.block_encoding.unitary, - bloq_specs=[qualtran.bloqs.block_encoding.unitary._UNITARY_DOC], - ), - NotebookSpecV2( - title='Tensor Product', - module=qualtran.bloqs.block_encoding.tensor_product, - bloq_specs=[qualtran.bloqs.block_encoding.tensor_product._TENSOR_PRODUCT_DOC], - ), - NotebookSpecV2( - title='Product', - module=qualtran.bloqs.block_encoding.product, - bloq_specs=[qualtran.bloqs.block_encoding.product._PRODUCT_DOC], - ), - NotebookSpecV2( - title='Phase', - module=qualtran.bloqs.block_encoding.phase, - bloq_specs=[qualtran.bloqs.block_encoding.phase._PHASE_DOC], - ), - NotebookSpecV2( - title='Linear Combination', - module=qualtran.bloqs.block_encoding.linear_combination, - bloq_specs=[qualtran.bloqs.block_encoding.linear_combination._LINEAR_COMBINATION_DOC], - ), - NotebookSpecV2( - title='Sparse Matrix', - module=qualtran.bloqs.block_encoding.sparse_matrix, - bloq_specs=[qualtran.bloqs.block_encoding.sparse_matrix._SPARSE_MATRIX_DOC], - ), - NotebookSpecV2( - title='Chebyshev Polynomial', - module=qualtran.bloqs.block_encoding.chebyshev_polynomial, - bloq_specs=[ - qualtran.bloqs.block_encoding.chebyshev_polynomial._CHEBYSHEV_BLOQ_DOC, - qualtran.bloqs.block_encoding.chebyshev_polynomial._SCALED_CHEBYSHEV_BLOQ_DOC, - ], - ), - NotebookSpecV2( - title='LCU Select/Prepare Oracles', - module=qualtran.bloqs.block_encoding.lcu_block_encoding, - bloq_specs=[ - qualtran.bloqs.block_encoding.lcu_block_encoding._SELECT_BLOCK_ENCODING_DOC, - qualtran.bloqs.block_encoding.lcu_block_encoding._LCU_BLOCK_ENCODING_DOC, - qualtran.bloqs.multiplexers.select_base._SELECT_ORACLE_DOC, - qualtran.bloqs.state_preparation.prepare_base._PREPARE_ORACLE_DOC, - qualtran.bloqs.multiplexers.black_box_select._BLACK_BOX_SELECT_DOC, - qualtran.bloqs.state_preparation.black_box_prepare._BLACK_BOX_PREPARE_DOC, - ], - ), -] - -# -------------------------------------------------------------------------- -# ----- Other ---------------------------------------------------------- -# -------------------------------------------------------------------------- -OTHER: List[NotebookSpecV2] = [ - NotebookSpecV2( - title='Prepare Uniform Superposition', - module=qualtran.bloqs.state_preparation.prepare_uniform_superposition, - bloq_specs=[ - qualtran.bloqs.state_preparation.prepare_uniform_superposition._PREP_UNIFORM_DOC - ], - ), - NotebookSpecV2( - title='Qubitized Hubbard Model', - module=qualtran.bloqs.chemistry.hubbard_model.qubitization, - path_stem='hubbard_model', - bloq_specs=[ - qualtran.bloqs.chemistry.hubbard_model.qubitization.select_hubbard._SELECT_HUBBARD, - qualtran.bloqs.chemistry.hubbard_model.qubitization.prepare_hubbard._PREPARE_HUBBARD, - ], - ), - NotebookSpecV2( - title='Apply to Lth Target', - module=qualtran.bloqs.multiplexers.apply_gate_to_lth_target, - bloq_specs=[qualtran.bloqs.multiplexers.apply_gate_to_lth_target._APPLY_TO_LTH_TARGET_DOC], - ), - NotebookSpecV2( - title='Apply Lth Bloq', - module=qualtran.bloqs.multiplexers.apply_lth_bloq, - bloq_specs=[qualtran.bloqs.multiplexers.apply_lth_bloq._APPLY_LTH_BLOQ_DOC], - ), - NotebookSpecV2( - title='QROM', - module=qualtran.bloqs.data_loading.qrom, - bloq_specs=[ - qualtran.bloqs.data_loading.qrom_base._QROM_BASE_DOC, - qualtran.bloqs.data_loading.qrom._QROM_DOC, - ], - ), - NotebookSpecV2( - title='SelectSwapQROM', - module=qualtran.bloqs.data_loading.select_swap_qrom, - bloq_specs=[ - qualtran.bloqs.data_loading.qrom_base._QROM_BASE_DOC, - qualtran.bloqs.data_loading.select_swap_qrom._SELECT_SWAP_QROM_DOC, - ], - ), - NotebookSpecV2( - title='Advanced QROM (aka QROAM) using clean ancilla', - module=qualtran.bloqs.data_loading.qroam_clean, - bloq_specs=[ - qualtran.bloqs.data_loading.qrom_base._QROM_BASE_DOC, - qualtran.bloqs.data_loading.qroam_clean._QROAM_CLEAN_DOC, - ], - ), - NotebookSpecV2( - title='Reflections', - module=qualtran.bloqs.reflections, - bloq_specs=[ - qualtran.bloqs.reflections.prepare_identity._PREPARE_IDENTITY_DOC, - qualtran.bloqs.reflections.reflection_using_prepare._REFL_USING_PREP_DOC, - ], - directory=f'{SOURCE_DIR}/bloqs/reflections', - ), - NotebookSpecV2( - title='Multi-Paulis', - module=qualtran.bloqs.mcmt, - bloq_specs=[ - qualtran.bloqs.mcmt.multi_target_cnot._C_MULTI_NOT_DOC, - qualtran.bloqs.mcmt.multi_control_pauli._CC_PAULI_DOC, - ], - directory=f'{SOURCE_DIR}/bloqs/mcmt/', - path_stem='multi_control_multi_target_pauli', - ), - NotebookSpecV2( - title='Generic Select', - module=qualtran.bloqs.multiplexers.select_pauli_lcu, - bloq_specs=[qualtran.bloqs.multiplexers.select_pauli_lcu._SELECT_PAULI_LCU_DOC], - ), - NotebookSpecV2( - title='State Preparation via Alias Sampling', - module=qualtran.bloqs.state_preparation.state_preparation_alias_sampling, - bloq_specs=[ - qualtran.bloqs.state_preparation.state_preparation_alias_sampling._STATE_PREP_ALIAS_DOC, - qualtran.bloqs.state_preparation.state_preparation_alias_sampling._SPARSE_STATE_PREP_ALIAS_DOC, - ], - ), - NotebookSpecV2( - title='State Preparation Using Rotations', - module=qualtran.bloqs.state_preparation.state_preparation_via_rotation, - bloq_specs=[ - qualtran.bloqs.state_preparation.state_preparation_via_rotation._STATE_PREP_VIA_ROTATIONS_DOC - ], - directory=f'{SOURCE_DIR}/bloqs/state_preparation/', - ), - NotebookSpecV2( - title='Generalized Quantum Signal Processing', - module=qualtran.bloqs.qsp.generalized_qsp, - bloq_specs=[qualtran.bloqs.qsp.generalized_qsp._Generalized_QSP_DOC], - ), - NotebookSpecV2( - title='Hamiltonian Simulation by Generalized Quantum Signal Processing', - module=qualtran.bloqs.hamiltonian_simulation.hamiltonian_simulation_by_gqsp, - bloq_specs=[ - qualtran.bloqs.hamiltonian_simulation.hamiltonian_simulation_by_gqsp._Hamiltonian_Simulation_by_GQSP_DOC - ], - ), -] +from qualtran_dev_tools.notebook_specs import GIT_ROOT, NB_BY_SECTION, SOURCE_DIR # -------------------------------------------------------------------------- # ----- Concepts ------------------------------------------------------- @@ -856,16 +67,6 @@ 'state_preparation/state_preparation_via_rotation_tutorial.ipynb', ] -NB_BY_SECTION = [ - ('Basic Gates', BASIC_GATES), - ('Chemistry', CHEMISTRY), - ('Arithmetic', ARITHMETIC), - ('Modular Arithmetic', MOD_ARITHMETIC), - ('Rotations', ROT_QFT_PE), - ('Block Encoding', BLOCK_ENCODING), - ('Other', OTHER), -] - def _all_nbspecs() -> Iterable[NotebookSpecV2]: for _, nbspecs in NB_BY_SECTION: diff --git a/dev_tools/bibliography.ipynb b/dev_tools/bibliography.ipynb index 038aaabfa..f91482a3f 100644 --- a/dev_tools/bibliography.ipynb +++ b/dev_tools/bibliography.ipynb @@ -129,7 +129,7 @@ "metadata": {}, "outputs": [], "source": [ - "for url, clss in backrefs.items():\n", + "for url, clss in sorted(backrefs.items(), key=lambda x: -len(x[1])):\n", " text = f'### [{titles[url]}]({url})\\n'\n", " for cls in clss:\n", " text += f' - {cls.__name__}\\n'\n", diff --git a/dev_tools/conf/.pylintrc b/dev_tools/conf/.pylintrc index b8b9e295a..8fcb6dc09 100644 --- a/dev_tools/conf/.pylintrc +++ b/dev_tools/conf/.pylintrc @@ -7,6 +7,10 @@ score=no reports=no py-version=3.9 disable= + abstract-class-instantiated, # TODO: #1440 - enable and fix + deprecated-class, # TODO: #1440 - enable and fix + possibly-used-before-assignment, # TODO: #1440 - enable and fix + used-before-assignment, # TODO: #1440 - enable and fix C, R, missing-raises-doc, diff --git a/dev_tools/qualtran_dev_tools/notebook_specs.py b/dev_tools/qualtran_dev_tools/notebook_specs.py new file mode 100644 index 000000000..a35c468ce --- /dev/null +++ b/dev_tools/qualtran_dev_tools/notebook_specs.py @@ -0,0 +1,890 @@ +# Copyright 2023 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. + +"""List of Jupyter notebooks. + +The notebooks listed in this file are used to generate Jupyter notebooks that +document and provide examples for each of the bloqs. This list is also used to +generate the static exports for the Qualtran web UI. + +## Adding a new bloq. + + 1. Create a qualtran.BloqExample perhaps using the `@bloq_example` decorator. Wrap it in a + `qualtran.BloqDocSpec`. This code should live alongside the bloq. + 2. If this is a new module: add a new entry to the appropriate notebook spec global variable + in this file (according to its category/organization). + 3. Update the `NotebookSpec` `bloq_specs` field to include the `BloqDocSpec` for your new bloq. +""" + +from typing import List + +from qualtran_dev_tools.git_tools import get_git_root + +import qualtran.bloqs.arithmetic.addition +import qualtran.bloqs.arithmetic.bitwise +import qualtran.bloqs.arithmetic.comparison +import qualtran.bloqs.arithmetic.controlled_add_or_subtract +import qualtran.bloqs.arithmetic.controlled_addition +import qualtran.bloqs.arithmetic.conversions +import qualtran.bloqs.arithmetic.multiplication +import qualtran.bloqs.arithmetic.negate +import qualtran.bloqs.arithmetic.permutation +import qualtran.bloqs.arithmetic.sorting +import qualtran.bloqs.arithmetic.subtraction +import qualtran.bloqs.arithmetic.trigonometric +import qualtran.bloqs.basic_gates.swap +import qualtran.bloqs.block_encoding.block_encoding_base +import qualtran.bloqs.block_encoding.chebyshev_polynomial +import qualtran.bloqs.block_encoding.lcu_block_encoding +import qualtran.bloqs.block_encoding.linear_combination +import qualtran.bloqs.block_encoding.phase +import qualtran.bloqs.block_encoding.product +import qualtran.bloqs.block_encoding.sparse_matrix +import qualtran.bloqs.block_encoding.sparse_matrix_hermitian +import qualtran.bloqs.block_encoding.tensor_product +import qualtran.bloqs.block_encoding.unitary +import qualtran.bloqs.bookkeeping +import qualtran.bloqs.bookkeeping.allocate +import qualtran.bloqs.bookkeeping.auto_partition +import qualtran.bloqs.bookkeeping.cast +import qualtran.bloqs.bookkeeping.free +import qualtran.bloqs.bookkeeping.join +import qualtran.bloqs.bookkeeping.partition +import qualtran.bloqs.bookkeeping.split +import qualtran.bloqs.chemistry.df.double_factorization +import qualtran.bloqs.chemistry.hubbard_model.qubitization +import qualtran.bloqs.chemistry.pbc.first_quantization.prepare_t +import qualtran.bloqs.chemistry.pbc.first_quantization.prepare_uv +import qualtran.bloqs.chemistry.pbc.first_quantization.projectile.select_and_prepare +import qualtran.bloqs.chemistry.pbc.first_quantization.select_t +import qualtran.bloqs.chemistry.pbc.first_quantization.select_uv +import qualtran.bloqs.chemistry.quad_fermion.givens_bloq +import qualtran.bloqs.chemistry.sf.single_factorization +import qualtran.bloqs.chemistry.sparse.prepare +import qualtran.bloqs.chemistry.sparse.walk_operator +import qualtran.bloqs.chemistry.thc.prepare +import qualtran.bloqs.chemistry.trotter.grid_ham.inverse_sqrt +import qualtran.bloqs.chemistry.trotter.grid_ham.qvr +import qualtran.bloqs.chemistry.trotter.hubbard.hopping +import qualtran.bloqs.chemistry.trotter.hubbard.interaction +import qualtran.bloqs.chemistry.trotter.ising.unitaries +import qualtran.bloqs.chemistry.trotter.trotterized_unitary +import qualtran.bloqs.data_loading.qrom +import qualtran.bloqs.data_loading.qrom_base +import qualtran.bloqs.data_loading.select_swap_qrom +import qualtran.bloqs.factoring.ecc +import qualtran.bloqs.factoring.rsa +import qualtran.bloqs.gf_arithmetic.gf2_add_k +import qualtran.bloqs.gf_arithmetic.gf2_addition +import qualtran.bloqs.gf_arithmetic.gf2_inverse +import qualtran.bloqs.gf_arithmetic.gf2_multiplication +import qualtran.bloqs.gf_arithmetic.gf2_square +import qualtran.bloqs.hamiltonian_simulation.hamiltonian_simulation_by_gqsp +import qualtran.bloqs.mcmt.and_bloq +import qualtran.bloqs.mcmt.controlled_via_and +import qualtran.bloqs.mcmt.ctrl_spec_and +import qualtran.bloqs.mcmt.multi_control_pauli +import qualtran.bloqs.mcmt.multi_target_cnot +import qualtran.bloqs.mod_arithmetic.mod_addition +import qualtran.bloqs.multiplexers.apply_gate_to_lth_target +import qualtran.bloqs.multiplexers.apply_lth_bloq +import qualtran.bloqs.multiplexers.black_box_select +import qualtran.bloqs.multiplexers.select_base +import qualtran.bloqs.multiplexers.select_pauli_lcu +import qualtran.bloqs.phase_estimation.lp_resource_state +import qualtran.bloqs.phase_estimation.qubitization_qpe +import qualtran.bloqs.phase_estimation.text_book_qpe +import qualtran.bloqs.qft.approximate_qft +import qualtran.bloqs.qft.qft_phase_gradient +import qualtran.bloqs.qft.qft_text_book +import qualtran.bloqs.qft.two_bit_ffft +import qualtran.bloqs.qsp.generalized_qsp +import qualtran.bloqs.qubitization.qubitization_walk_operator +import qualtran.bloqs.reflections +import qualtran.bloqs.reflections.prepare_identity +import qualtran.bloqs.reflections.reflection_using_prepare +import qualtran.bloqs.rotations.hamming_weight_phasing +import qualtran.bloqs.rotations.phase_gradient +import qualtran.bloqs.rotations.phasing_via_cost_function +import qualtran.bloqs.rotations.programmable_rotation_gate_array +import qualtran.bloqs.rotations.quantum_variable_rotation +import qualtran.bloqs.state_preparation.black_box_prepare +import qualtran.bloqs.state_preparation.prepare_base +import qualtran.bloqs.state_preparation.prepare_uniform_superposition +import qualtran.bloqs.state_preparation.state_preparation_alias_sampling +import qualtran.bloqs.state_preparation.state_preparation_via_rotation +import qualtran.bloqs.swap_network.cswap_approx +import qualtran.bloqs.swap_network.multiplexed_cswap +import qualtran.bloqs.swap_network.swap_with_zero + +from .jupyter_autogen import NotebookSpecV2 + +GIT_ROOT = get_git_root() +SOURCE_DIR = GIT_ROOT / 'qualtran/' + +# -------------------------------------------------------------------------- +# ----- Basic Gates ---------------------------------------------------- +# -------------------------------------------------------------------------- +BASIC_GATES: List[NotebookSpecV2] = [ + NotebookSpecV2( + title='T Gate', + module=qualtran.bloqs.basic_gates.t_gate, + bloq_specs=[qualtran.bloqs.basic_gates.t_gate._T_GATE_DOC], + ), + NotebookSpecV2( + title='Toffoli', + module=qualtran.bloqs.basic_gates.toffoli, + bloq_specs=[qualtran.bloqs.basic_gates.toffoli._TOFFOLI_DOC], + ), + NotebookSpecV2( + title='Hadamard', + module=qualtran.bloqs.basic_gates.hadamard, + bloq_specs=[ + qualtran.bloqs.basic_gates.hadamard._HADAMARD_DOC, + qualtran.bloqs.basic_gates.hadamard._CHADAMARD_DOC, + ], + ), + NotebookSpecV2( + title='CNOT', + module=qualtran.bloqs.basic_gates.cnot, + bloq_specs=[qualtran.bloqs.basic_gates.cnot._CNOT_DOC], + ), + NotebookSpecV2( + title='Z, S, and CZ', + module=qualtran.bloqs.basic_gates.z_basis, + path_stem='diag_gates', + bloq_specs=[ + qualtran.bloqs.basic_gates.z_basis._Z_GATE_DOC, + qualtran.bloqs.basic_gates.s_gate._S_GATE_DOC, + qualtran.bloqs.basic_gates.z_basis._CZ_DOC, + ], + ), + NotebookSpecV2( + title='Y Gate', + module=qualtran.bloqs.basic_gates.y_gate, + bloq_specs=[ + qualtran.bloqs.basic_gates.y_gate._Y_GATE_DOC, + qualtran.bloqs.basic_gates.y_gate._CY_GATE_DOC, + ], + ), + NotebookSpecV2( + title='And', + module=qualtran.bloqs.mcmt.and_bloq, + bloq_specs=[ + qualtran.bloqs.mcmt.and_bloq._AND_DOC, + qualtran.bloqs.mcmt.and_bloq._MULTI_AND_DOC, + ], + ), + NotebookSpecV2( + title='States and Effects', + module=qualtran.bloqs.basic_gates.z_basis, + path_stem='states_and_effects', + bloq_specs=[ + qualtran.bloqs.basic_gates.z_basis._ZERO_STATE_DOC, + qualtran.bloqs.basic_gates.z_basis._ZERO_EFFECT_DOC, + qualtran.bloqs.basic_gates.z_basis._ONE_STATE_DOC, + qualtran.bloqs.basic_gates.z_basis._ONE_EFFECT_DOC, + qualtran.bloqs.basic_gates.z_basis._INT_STATE_DOC, + qualtran.bloqs.basic_gates.z_basis._INT_EFFECT_DOC, + qualtran.bloqs.basic_gates.x_basis._PLUS_STATE_DOC, + qualtran.bloqs.basic_gates.x_basis._PLUS_EFFECT_DOC, + qualtran.bloqs.basic_gates.x_basis._MINUS_STATE_DOC, + qualtran.bloqs.basic_gates.x_basis._MINUS_EFFECT_DOC, + ], + ), + NotebookSpecV2( + title='Basic Swaps', + module=qualtran.bloqs.basic_gates.swap, + bloq_specs=[ + qualtran.bloqs.basic_gates.swap._TWO_BIT_SWAP_DOC, + qualtran.bloqs.basic_gates.swap._TWO_BIT_CSWAP_DOC, + qualtran.bloqs.basic_gates.swap._SWAP_DOC, + qualtran.bloqs.basic_gates.swap._CSWAP_DOC, + ], + ), + NotebookSpecV2( + title='Swap Networks', + module=qualtran.bloqs.swap_network, + bloq_specs=[ + qualtran.bloqs.swap_network.cswap_approx._APPROX_CSWAP_DOC, + qualtran.bloqs.swap_network.swap_with_zero._SWZ_DOC, + qualtran.bloqs.swap_network.multiplexed_cswap._MULTIPLEXED_CSWAP_DOC, + ], + ), + NotebookSpecV2( + title='Global Phase', + module=qualtran.bloqs.basic_gates.global_phase, + bloq_specs=[qualtran.bloqs.basic_gates.global_phase._GLOBAL_PHASE_DOC], + ), + NotebookSpecV2( + title='Identity Gate', + module=qualtran.bloqs.basic_gates.identity, + bloq_specs=[qualtran.bloqs.basic_gates.identity._IDENTITY_DOC], + ), + NotebookSpecV2( + title='Bookkeeping Bloqs', + module=qualtran.bloqs.bookkeeping, + bloq_specs=[ + qualtran.bloqs.bookkeeping.allocate._ALLOC_DOC, + qualtran.bloqs.bookkeeping.free._FREE_DOC, + qualtran.bloqs.bookkeeping.split._SPLIT_DOC, + qualtran.bloqs.bookkeeping.join._JOIN_DOC, + qualtran.bloqs.bookkeeping.partition._PARTITION_DOC, + qualtran.bloqs.bookkeeping.auto_partition._AUTO_PARTITION_DOC, + qualtran.bloqs.bookkeeping.cast._CAST_DOC, + ], + ), + NotebookSpecV2( + title='Control Specification (And)', + module=qualtran.bloqs.mcmt.ctrl_spec_and, + bloq_specs=[qualtran.bloqs.mcmt.ctrl_spec_and._CTRLSPEC_AND_DOC], + ), + NotebookSpecV2( + title='Multi control bloq via single control bloq and `And` ladder', + module=qualtran.bloqs.mcmt.controlled_via_and, + bloq_specs=[qualtran.bloqs.mcmt.controlled_via_and._CONTROLLED_VIA_AND_DOC], + ), +] + + +# -------------------------------------------------------------------------- +# ----- Chemistry ------------------------------------------------------ +# -------------------------------------------------------------------------- +CHEMISTRY: List[NotebookSpecV2] = [ + NotebookSpecV2( + title='Sparse', + module=qualtran.bloqs.chemistry.sparse, + bloq_specs=[ + qualtran.bloqs.chemistry.sparse.prepare._SPARSE_PREPARE, + qualtran.bloqs.chemistry.sparse.select_bloq._SPARSE_SELECT, + ], + directory=f'{SOURCE_DIR}/bloqs/chemistry/sparse', + ), + NotebookSpecV2( + title='Single Factorization', + module=qualtran.bloqs.chemistry.sf.single_factorization, + bloq_specs=[ + qualtran.bloqs.chemistry.sf.single_factorization._SF_ONE_BODY, + qualtran.bloqs.chemistry.sf.single_factorization._SF_BLOCK_ENCODING, + ], + directory=f'{SOURCE_DIR}/bloqs/chemistry/sf', + ), + NotebookSpecV2( + title='Double Factorization', + module=qualtran.bloqs.chemistry.df.double_factorization, + bloq_specs=[ + qualtran.bloqs.chemistry.df.double_factorization._DF_ONE_BODY, + qualtran.bloqs.chemistry.df.double_factorization._DF_BLOCK_ENCODING, + ], + directory=f'{SOURCE_DIR}/bloqs/chemistry/df', + ), + NotebookSpecV2( + title='Tensor Hypercontraction', + module=qualtran.bloqs.chemistry.thc, + bloq_specs=[ + qualtran.bloqs.chemistry.thc.prepare._THC_UNI_PREP, + qualtran.bloqs.chemistry.thc.prepare._THC_PREPARE, + qualtran.bloqs.chemistry.thc.select_bloq._THC_SELECT, + ], + directory=f'{SOURCE_DIR}/bloqs/chemistry/thc', + ), + NotebookSpecV2( + title='First Quantized Hamiltonian', + module=qualtran.bloqs.chemistry.pbc.first_quantization, + bloq_specs=[ + qualtran.bloqs.chemistry.pbc.first_quantization.select_and_prepare._FIRST_QUANTIZED_PREPARE_DOC, + qualtran.bloqs.chemistry.pbc.first_quantization.select_and_prepare._FIRST_QUANTIZED_SELECT_DOC, + qualtran.bloqs.chemistry.pbc.first_quantization.prepare_t._PREPARE_T, + qualtran.bloqs.chemistry.pbc.first_quantization.prepare_uv._PREPARE_UV, + qualtran.bloqs.chemistry.pbc.first_quantization.select_t._SELECT_T, + qualtran.bloqs.chemistry.pbc.first_quantization.select_uv._SELECT_UV, + ], + directory=f'{SOURCE_DIR}/bloqs/chemistry/pbc/first_quantization', + ), + NotebookSpecV2( + title='First Quantized Hamiltonian with Quantum Projectile', + module=qualtran.bloqs.chemistry.pbc.first_quantization.projectile, + bloq_specs=[ + qualtran.bloqs.chemistry.pbc.first_quantization.projectile.select_and_prepare._FIRST_QUANTIZED_WITH_PROJ_PREPARE_DOC, + qualtran.bloqs.chemistry.pbc.first_quantization.projectile.select_and_prepare._FIRST_QUANTIZED_WITH_PROJ_SELECT_DOC, + ], + directory=f'{SOURCE_DIR}/bloqs/chemistry/pbc/first_quantization/projectile', + ), + NotebookSpecV2( + title='Trotter Bloqs', + module=qualtran.bloqs.chemistry.trotter.grid_ham, + bloq_specs=[ + qualtran.bloqs.chemistry.trotter.grid_ham.inverse_sqrt._POLY_INV_SQRT, + qualtran.bloqs.chemistry.trotter.grid_ham.inverse_sqrt._NR_INV_SQRT, + qualtran.bloqs.chemistry.trotter.grid_ham.qvr._QVR, + qualtran.bloqs.chemistry.trotter.grid_ham.kinetic._KINETIC_ENERGY, + qualtran.bloqs.chemistry.trotter.grid_ham.potential._PAIR_POTENTIAL, + qualtran.bloqs.chemistry.trotter.grid_ham.potential._POTENTIAL_ENERGY, + ], + path_stem='trotter', + ), + NotebookSpecV2( + title='Trotterization', + module=qualtran.bloqs.chemistry.trotter.trotterized_unitary, + bloq_specs=[qualtran.bloqs.chemistry.trotter.trotterized_unitary._TROTT_UNITARY_DOC], + directory=f'{SOURCE_DIR}/bloqs/chemistry/trotter', + ), + NotebookSpecV2( + title='Ising Trotter Bloqs', + module=qualtran.bloqs.chemistry.trotter.ising, + bloq_specs=[ + qualtran.bloqs.chemistry.trotter.ising.unitaries._ISING_X_UNITARY_DOC, + qualtran.bloqs.chemistry.trotter.ising.unitaries._ISING_ZZ_UNITARY_DOC, + ], + directory=f'{SOURCE_DIR}/bloqs/chemistry/trotter/ising', + ), + NotebookSpecV2( + title='Trotterized Hubbard', + module=qualtran.bloqs.chemistry.trotter.hubbard, + bloq_specs=[ + qualtran.bloqs.chemistry.trotter.hubbard.hopping._HOPPING_DOC, + qualtran.bloqs.chemistry.trotter.hubbard.hopping._PLAQUETTE_DOC, + qualtran.bloqs.chemistry.trotter.hubbard.hopping._HOPPING_TILE_HWP_DOC, + qualtran.bloqs.chemistry.trotter.hubbard.interaction._INTERACTION_DOC, + qualtran.bloqs.chemistry.trotter.hubbard.interaction._INTERACTION_HWP_DOC, + ], + directory=f'{SOURCE_DIR}/bloqs/chemistry/trotter/hubbard', + ), + NotebookSpecV2( + title='Givens Rotations', + module=qualtran.bloqs.chemistry.quad_fermion.givens_bloq, + bloq_specs=[ + qualtran.bloqs.chemistry.quad_fermion.givens_bloq._REAL_GIVENS_DOC, + qualtran.bloqs.chemistry.quad_fermion.givens_bloq._CPLX_GIVENS_DOC, + ], + ), +] + +# -------------------------------------------------------------------------- +# ----- Arithmetic ----------------------------------------------------- +# -------------------------------------------------------------------------- +ARITHMETIC = [ + NotebookSpecV2( + title='Addition', + module=qualtran.bloqs.arithmetic.addition, + bloq_specs=[ + qualtran.bloqs.arithmetic.addition._ADD_DOC, + qualtran.bloqs.arithmetic.addition._ADD_OOP_DOC, + qualtran.bloqs.arithmetic.addition._ADD_K_DOC, + ], + ), + NotebookSpecV2( + title='Controlled Addition', + module=qualtran.bloqs.arithmetic.controlled_addition, + bloq_specs=[qualtran.bloqs.arithmetic.controlled_addition._CADD_DOC], + ), + NotebookSpecV2( + title='Negation', + module=qualtran.bloqs.arithmetic.negate, + bloq_specs=[qualtran.bloqs.arithmetic.negate._NEGATE_DOC], + ), + NotebookSpecV2( + title='Subtraction', + module=qualtran.bloqs.arithmetic.subtraction, + bloq_specs=[ + qualtran.bloqs.arithmetic.subtraction._SUB_DOC, + qualtran.bloqs.arithmetic.subtraction._SUB_FROM_DOC, + ], + ), + NotebookSpecV2( + title='Controlled Add-or-Subtract', + module=qualtran.bloqs.arithmetic.controlled_add_or_subtract, + bloq_specs=[ + qualtran.bloqs.arithmetic.controlled_add_or_subtract._CONTROLLED_ADD_OR_SUBTRACT_DOC + ], + ), + NotebookSpecV2( + title='Multiplication', + module=qualtran.bloqs.arithmetic.multiplication, + bloq_specs=[ + qualtran.bloqs.arithmetic.multiplication._PLUS_EQUALS_PRODUCT_DOC, + qualtran.bloqs.arithmetic.multiplication._PRODUCT_DOC, + qualtran.bloqs.arithmetic.multiplication._SQUARE_DOC, + qualtran.bloqs.arithmetic.multiplication._SUM_OF_SQUARES_DOC, + qualtran.bloqs.arithmetic.multiplication._SCALE_INT_BY_REAL_DOC, + qualtran.bloqs.arithmetic.multiplication._MULTIPLY_TWO_REALS_DOC, + qualtran.bloqs.arithmetic.multiplication._SQUARE_REAL_NUMBER_DOC, + qualtran.bloqs.arithmetic.multiplication._INVERT_REAL_NUMBER_DOC, + ], + ), + NotebookSpecV2( + title='Comparison', + module=qualtran.bloqs.arithmetic.comparison, + bloq_specs=[ + qualtran.bloqs.arithmetic.comparison._LT_K_DOC, + qualtran.bloqs.arithmetic.comparison._GREATER_THAN_DOC, + qualtran.bloqs.arithmetic.comparison._GREATER_THAN_K_DOC, + qualtran.bloqs.arithmetic.comparison._EQUALS_DOC, + qualtran.bloqs.arithmetic.comparison._EQUALS_K_DOC, + qualtran.bloqs.arithmetic.comparison._BI_QUBITS_MIXER_DOC, + qualtran.bloqs.arithmetic.comparison._SQ_CMP_DOC, + qualtran.bloqs.arithmetic.comparison._LEQ_DOC, + qualtran.bloqs.arithmetic.comparison._CLinearDepthGreaterThan_DOC, + qualtran.bloqs.arithmetic.comparison._LINEAR_DEPTH_HALF_GREATERTHAN_DOC, + qualtran.bloqs.arithmetic.comparison._LINEAR_DEPTH_HALF_GREATERTHANEQUAL_DOC, + qualtran.bloqs.arithmetic.comparison._LINEAR_DEPTH_HALF_LESSTHAN_DOC, + qualtran.bloqs.arithmetic.comparison._LINEAR_DEPTH_HALF_LESSTHANEQUAL_DOC, + ], + ), + NotebookSpecV2( + title='Integer Conversions', + module=qualtran.bloqs.arithmetic.conversions, + bloq_specs=[ + qualtran.bloqs.arithmetic.conversions.ones_complement_to_twos_complement._SIGNED_TO_TWOS, + qualtran.bloqs.arithmetic.conversions.sign_extension._SIGN_EXTEND_DOC, + qualtran.bloqs.arithmetic.conversions.sign_extension._SIGN_TRUNCATE_DOC, + ], + ), + NotebookSpecV2( + title='Sorting', + module=qualtran.bloqs.arithmetic.sorting, + bloq_specs=[ + qualtran.bloqs.arithmetic.sorting._COMPARATOR_DOC, + qualtran.bloqs.arithmetic.sorting._PARALLEL_COMPARATORS_DOC, + qualtran.bloqs.arithmetic.sorting._BITONIC_MERGE_DOC, + qualtran.bloqs.arithmetic.sorting._BITONIC_SORT_DOC, + ], + directory=f'{SOURCE_DIR}/bloqs/arithmetic/', + ), + NotebookSpecV2( + title='Indexing', + module=qualtran.bloqs.arithmetic.conversions.contiguous_index, + bloq_specs=[qualtran.bloqs.arithmetic.conversions.contiguous_index._TO_CONTG_INDX], + ), + NotebookSpecV2( + title='Permutations', + module=qualtran.bloqs.arithmetic.permutation, + bloq_specs=[ + qualtran.bloqs.arithmetic.permutation._PERMUTATION_DOC, + qualtran.bloqs.arithmetic.permutation._PERMUTATION_CYCLE_DOC, + ], + ), + NotebookSpecV2( + title='Bitwise Operations', + module=qualtran.bloqs.arithmetic.bitwise, + bloq_specs=[ + qualtran.bloqs.arithmetic.bitwise._XOR_DOC, + qualtran.bloqs.arithmetic.bitwise._BITWISE_NOT_DOC, + ], + ), + NotebookSpecV2( + title='Trigonometric Functions', + module=qualtran.bloqs.arithmetic.trigonometric, + bloq_specs=[qualtran.bloqs.arithmetic.trigonometric.arcsin._ARCSIN_DOC], + ), +] + +MOD_ARITHMETIC = [ + NotebookSpecV2( + title='Modular Addition', + module=qualtran.bloqs.mod_arithmetic.mod_addition, + bloq_specs=[ + qualtran.bloqs.mod_arithmetic.mod_addition._MOD_ADD_DOC, + qualtran.bloqs.mod_arithmetic.mod_addition._MOD_ADD_K_DOC, + qualtran.bloqs.mod_arithmetic.mod_addition._C_MOD_ADD_DOC, + qualtran.bloqs.mod_arithmetic.mod_addition._C_MOD_ADD_K_DOC, + qualtran.bloqs.mod_arithmetic.mod_addition._CTRL_SCALE_MOD_ADD_DOC, + ], + ), + NotebookSpecV2( + title='Modular Subtraction', + module=qualtran.bloqs.mod_arithmetic.mod_subtraction, + bloq_specs=[ + qualtran.bloqs.mod_arithmetic.mod_subtraction._MOD_NEG_DOC, + qualtran.bloqs.mod_arithmetic.mod_subtraction._CMOD_NEG_DOC, + qualtran.bloqs.mod_arithmetic.mod_subtraction._MOD_SUB_DOC, + qualtran.bloqs.mod_arithmetic.mod_subtraction._CMOD_SUB_DOC, + ], + ), + NotebookSpecV2( + title='Modular Multiplication', + module=qualtran.bloqs.mod_arithmetic.mod_multiplication, + bloq_specs=[ + qualtran.bloqs.mod_arithmetic.mod_multiplication._MOD_DBL_DOC, + qualtran.bloqs.mod_arithmetic.mod_multiplication._C_MOD_MUL_K_DOC, + qualtran.bloqs.mod_arithmetic.mod_multiplication._DIRTY_OUT_OF_PLACE_MONTGOMERY_MOD_MUL_DOC, + ], + ), + NotebookSpecV2( + title='Modular Divison', + module=qualtran.bloqs.mod_arithmetic.mod_division, + bloq_specs=[qualtran.bloqs.mod_arithmetic.mod_division._KALISKI_MOD_INVERSE_DOC], + ), + NotebookSpecV2( + title='Factoring RSA', + module=qualtran.bloqs.factoring.rsa, + bloq_specs=[ + qualtran.bloqs.factoring.rsa.rsa_phase_estimate._RSA_PE_BLOQ_DOC, + qualtran.bloqs.factoring.rsa.rsa_mod_exp._RSA_MODEXP_DOC, + ], + ), + NotebookSpecV2( + title='Elliptic Curve Addition', + module=qualtran.bloqs.factoring.ecc.ec_add, + bloq_specs=[qualtran.bloqs.factoring.ecc.ec_add._EC_ADD_DOC], + ), + NotebookSpecV2( + title='Elliptic Curve Cryptography', + module=qualtran.bloqs.factoring.ecc, + bloq_specs=[ + qualtran.bloqs.factoring.ecc.find_ecc_private_key._ECC_BLOQ_DOC, + qualtran.bloqs.factoring.ecc.ec_phase_estimate_r._EC_PE_BLOQ_DOC, + qualtran.bloqs.factoring.ecc.ec_add_r._ECC_ADD_R_BLOQ_DOC, + qualtran.bloqs.factoring.ecc.ec_add_r._EC_WINDOW_ADD_BLOQ_DOC, + ], + ), +] + +GF_ARITHMETIC = [ + # -------------------------------------------------------------------------- + # ----- Galois Fields (GF) Arithmetic --------------------------------- + # -------------------------------------------------------------------------- + NotebookSpecV2( + title='GF($2^m$) Multiplication', + module=qualtran.bloqs.gf_arithmetic.gf2_multiplication, + bloq_specs=[qualtran.bloqs.gf_arithmetic.gf2_multiplication._GF2_MULTIPLICATION_DOC], + ), + NotebookSpecV2( + title='GF($2^m$) Addition', + module=qualtran.bloqs.gf_arithmetic.gf2_addition, + bloq_specs=[qualtran.bloqs.gf_arithmetic.gf2_addition._GF2_ADDITION_DOC], + ), + NotebookSpecV2( + title='GF($2^m$) Add Constant', + module=qualtran.bloqs.gf_arithmetic.gf2_add_k, + bloq_specs=[qualtran.bloqs.gf_arithmetic.gf2_add_k._GF2_ADD_K_DOC], + ), + NotebookSpecV2( + title='GF($2^m$) Square', + module=qualtran.bloqs.gf_arithmetic.gf2_square, + bloq_specs=[qualtran.bloqs.gf_arithmetic.gf2_square._GF2_SQUARE_DOC], + ), + NotebookSpecV2( + title='GF($2^m$) Inverse', + module=qualtran.bloqs.gf_arithmetic.gf2_inverse, + bloq_specs=[qualtran.bloqs.gf_arithmetic.gf2_inverse._GF2_INVERSE_DOC], + ), +] + + +ROT_QFT_PE = [ + # -------------------------------------------------------------------------- + # ----- Rotations ----------------------------------------------------- + # -------------------------------------------------------------------------- + NotebookSpecV2( + title='Basic Rotation Gates', + module=qualtran.bloqs.basic_gates.rotation, + bloq_specs=[ + qualtran.bloqs.basic_gates.rotation._X_POW_DOC, + qualtran.bloqs.basic_gates.rotation._Y_POW_DOC, + qualtran.bloqs.basic_gates.rotation._Z_POW_DOC, + ], + ), + NotebookSpecV2( + title='SU2 Rotation', + module=qualtran.bloqs.basic_gates.su2_rotation, + bloq_specs=[qualtran.bloqs.basic_gates.su2_rotation._SU2_ROTATION_GATE_DOC], + ), + NotebookSpecV2( + title='Quantum Variable Rotation', + module=qualtran.bloqs.rotations.quantum_variable_rotation, + bloq_specs=[ + qualtran.bloqs.rotations.quantum_variable_rotation._QVR_ZPOW, + qualtran.bloqs.rotations.quantum_variable_rotation._QVR_PHASE_GRADIENT, + ], + directory=f'{SOURCE_DIR}/bloqs/rotations/', + ), + NotebookSpecV2( + title='Phasing via Cost function', + module=qualtran.bloqs.rotations.phasing_via_cost_function, + bloq_specs=[qualtran.bloqs.rotations.phasing_via_cost_function._PHASING_VIA_COST_FUNCTION], + directory=f'{SOURCE_DIR}/bloqs/rotations/', + ), + NotebookSpecV2( + title='Rotations via Phase Gradients', + module=qualtran.bloqs.rotations.phase_gradient, + bloq_specs=[ + qualtran.bloqs.rotations.phase_gradient._PHASE_GRADIENT_UNITARY_DOC, + qualtran.bloqs.rotations.phase_gradient._PHASE_GRADIENT_STATE_DOC, + qualtran.bloqs.rotations.phase_gradient._ADD_INTO_PHASE_GRAD_DOC, + qualtran.bloqs.rotations.phase_gradient._ADD_SCALED_VAL_INTO_PHASE_REG_DOC, + ], + ), + NotebookSpecV2( + title='Z Rotations via Hamming Weight Phasing', + module=qualtran.bloqs.rotations.hamming_weight_phasing, + bloq_specs=[ + qualtran.bloqs.rotations.hamming_weight_phasing._HAMMING_WEIGHT_PHASING_DOC, + qualtran.bloqs.rotations.hamming_weight_phasing._HAMMING_WEIGHT_PHASING_VIA_PHASE_GRADIENT_DOC, + ], + ), + NotebookSpecV2( + title='Programmable Rotation Gate Array', + module=qualtran.bloqs.rotations.programmable_rotation_gate_array, + bloq_specs=[ + qualtran.bloqs.rotations.programmable_rotation_gate_array._PROGRAMMABLE_ROTATAION_GATE_ARRAY_DOC + ], + ), + # -------------------------------------------------------------------------- + # ----- QFT ----------------------------------------------------- + # -------------------------------------------------------------------------- + NotebookSpecV2( + title='Two Bit FFFT Gate', + module=qualtran.bloqs.qft.two_bit_ffft, + bloq_specs=[qualtran.bloqs.qft.two_bit_ffft._TWO_BIT_FFFT_DOC], + ), + NotebookSpecV2( + title='Approximate QFT', + module=qualtran.bloqs.qft.approximate_qft, + bloq_specs=[qualtran.bloqs.qft.approximate_qft._CC_AQFT_DOC], + ), + NotebookSpecV2( + title='Textbook QFT', + module=qualtran.bloqs.qft.qft_text_book, + bloq_specs=[qualtran.bloqs.qft.qft_text_book._QFT_TEXT_BOOK_DOC], + ), + NotebookSpecV2( + title='Phase Gradient QFT', + module=qualtran.bloqs.qft.qft_phase_gradient, + bloq_specs=[qualtran.bloqs.qft.qft_phase_gradient._QFT_PHASE_GRADIENT_DOC], + ), + # -------------------------------------------------------------------------- + # ----- Phase Estimation ---------------------------------------- + # -------------------------------------------------------------------------- + NotebookSpecV2( + title='Optimal resource states for Phase Estimation by A. Luis and J. Peřina', + module=qualtran.bloqs.phase_estimation.lp_resource_state, + bloq_specs=[ + qualtran.bloqs.phase_estimation.lp_resource_state._CC_LPRS_INTERIM_PREP_DOC, + qualtran.bloqs.phase_estimation.lp_resource_state._CC_LP_RESOURCE_STATE_DOC, + ], + ), + NotebookSpecV2( + title='Textbook Quantum Phase Estimation', + module=qualtran.bloqs.phase_estimation.text_book_qpe, + bloq_specs=[ + qualtran.bloqs.phase_estimation.qpe_window_state._CC_RECTANGULAR_WINDOW_STATE_DOC, + qualtran.bloqs.phase_estimation.text_book_qpe._CC_TEXTBOOK_PHASE_ESTIMATION_DOC, + ], + ), + NotebookSpecV2( + title='Kaiser Window State for Quantum Phase Estimation', + module=qualtran.bloqs.phase_estimation.kaiser_window_state, + bloq_specs=[ + qualtran.bloqs.phase_estimation.kaiser_window_state._CC_KAISER_WINDOW_STATE_DOC + ], + ), + NotebookSpecV2( + title='Qubitization Walk Operator', + module=qualtran.bloqs.qubitization.qubitization_walk_operator, + bloq_specs=[qualtran.bloqs.qubitization.qubitization_walk_operator._QUBITIZATION_WALK_DOC], + ), + NotebookSpecV2( + title='Qubitization Phase Estimation', + module=qualtran.bloqs.phase_estimation.qubitization_qpe, + bloq_specs=[qualtran.bloqs.phase_estimation.qubitization_qpe._QUBITIZATION_QPE_DOC], + ), +] + +# -------------------------------------------------------------------------- +# ----- Block Encoding ---------------------------------------------------------- +# -------------------------------------------------------------------------- +BLOCK_ENCODING: List[NotebookSpecV2] = [ + NotebookSpecV2( + title='Block Encoding Interface', + module=qualtran.bloqs.block_encoding, + bloq_specs=[qualtran.bloqs.block_encoding.block_encoding_base._BLOCK_ENCODING_DOC], + ), + NotebookSpecV2( + title='Unitary', + module=qualtran.bloqs.block_encoding.unitary, + bloq_specs=[qualtran.bloqs.block_encoding.unitary._UNITARY_DOC], + ), + NotebookSpecV2( + title='Tensor Product', + module=qualtran.bloqs.block_encoding.tensor_product, + bloq_specs=[qualtran.bloqs.block_encoding.tensor_product._TENSOR_PRODUCT_DOC], + ), + NotebookSpecV2( + title='Product', + module=qualtran.bloqs.block_encoding.product, + bloq_specs=[qualtran.bloqs.block_encoding.product._PRODUCT_DOC], + ), + NotebookSpecV2( + title='Phase', + module=qualtran.bloqs.block_encoding.phase, + bloq_specs=[qualtran.bloqs.block_encoding.phase._PHASE_DOC], + ), + NotebookSpecV2( + title='Linear Combination', + module=qualtran.bloqs.block_encoding.linear_combination, + bloq_specs=[qualtran.bloqs.block_encoding.linear_combination._LINEAR_COMBINATION_DOC], + ), + NotebookSpecV2( + title='Sparse Matrix', + module=qualtran.bloqs.block_encoding.sparse_matrix, + bloq_specs=[qualtran.bloqs.block_encoding.sparse_matrix._SPARSE_MATRIX_DOC], + ), + NotebookSpecV2( + title='Sparse Matrix Hermitian', + module=qualtran.bloqs.block_encoding.sparse_matrix_hermitian, + bloq_specs=[ + qualtran.bloqs.block_encoding.sparse_matrix_hermitian._SPARSE_MATRIX_HERMITIAN_DOC + ], + ), + NotebookSpecV2( + title='Chebyshev Polynomial', + module=qualtran.bloqs.block_encoding.chebyshev_polynomial, + bloq_specs=[ + qualtran.bloqs.block_encoding.chebyshev_polynomial._CHEBYSHEV_BLOQ_DOC, + qualtran.bloqs.block_encoding.chebyshev_polynomial._SCALED_CHEBYSHEV_BLOQ_DOC, + ], + ), + NotebookSpecV2( + title='LCU Select/Prepare Oracles', + module=qualtran.bloqs.block_encoding.lcu_block_encoding, + bloq_specs=[ + qualtran.bloqs.block_encoding.lcu_block_encoding._SELECT_BLOCK_ENCODING_DOC, + qualtran.bloqs.block_encoding.lcu_block_encoding._LCU_BLOCK_ENCODING_DOC, + qualtran.bloqs.multiplexers.select_base._SELECT_ORACLE_DOC, + qualtran.bloqs.state_preparation.prepare_base._PREPARE_ORACLE_DOC, + qualtran.bloqs.multiplexers.black_box_select._BLACK_BOX_SELECT_DOC, + qualtran.bloqs.state_preparation.black_box_prepare._BLACK_BOX_PREPARE_DOC, + ], + ), +] + +# -------------------------------------------------------------------------- +# ----- Other ---------------------------------------------------------- +# -------------------------------------------------------------------------- +OTHER: List[NotebookSpecV2] = [ + NotebookSpecV2( + title='Prepare Uniform Superposition', + module=qualtran.bloqs.state_preparation.prepare_uniform_superposition, + bloq_specs=[ + qualtran.bloqs.state_preparation.prepare_uniform_superposition._PREP_UNIFORM_DOC + ], + ), + NotebookSpecV2( + title='Qubitized Hubbard Model', + module=qualtran.bloqs.chemistry.hubbard_model.qubitization, + path_stem='hubbard_model', + bloq_specs=[ + qualtran.bloqs.chemistry.hubbard_model.qubitization.select_hubbard._SELECT_HUBBARD, + qualtran.bloqs.chemistry.hubbard_model.qubitization.prepare_hubbard._PREPARE_HUBBARD, + ], + ), + NotebookSpecV2( + title='Apply to Lth Target', + module=qualtran.bloqs.multiplexers.apply_gate_to_lth_target, + bloq_specs=[qualtran.bloqs.multiplexers.apply_gate_to_lth_target._APPLY_TO_LTH_TARGET_DOC], + ), + NotebookSpecV2( + title='Apply Lth Bloq', + module=qualtran.bloqs.multiplexers.apply_lth_bloq, + bloq_specs=[qualtran.bloqs.multiplexers.apply_lth_bloq._APPLY_LTH_BLOQ_DOC], + ), + NotebookSpecV2( + title='QROM', + module=qualtran.bloqs.data_loading.qrom, + bloq_specs=[ + qualtran.bloqs.data_loading.qrom_base._QROM_BASE_DOC, + qualtran.bloqs.data_loading.qrom._QROM_DOC, + ], + ), + NotebookSpecV2( + title='SelectSwapQROM', + module=qualtran.bloqs.data_loading.select_swap_qrom, + bloq_specs=[ + qualtran.bloqs.data_loading.qrom_base._QROM_BASE_DOC, + qualtran.bloqs.data_loading.select_swap_qrom._SELECT_SWAP_QROM_DOC, + ], + ), + NotebookSpecV2( + title='Advanced QROM (aka QROAM) using clean ancilla', + module=qualtran.bloqs.data_loading.qroam_clean, + bloq_specs=[ + qualtran.bloqs.data_loading.qrom_base._QROM_BASE_DOC, + qualtran.bloqs.data_loading.qroam_clean._QROAM_CLEAN_DOC, + ], + ), + NotebookSpecV2( + title='Reflections', + module=qualtran.bloqs.reflections, + bloq_specs=[ + qualtran.bloqs.reflections.prepare_identity._PREPARE_IDENTITY_DOC, + qualtran.bloqs.reflections.reflection_using_prepare._REFL_USING_PREP_DOC, + ], + directory=f'{SOURCE_DIR}/bloqs/reflections', + ), + NotebookSpecV2( + title='Multi-Paulis', + module=qualtran.bloqs.mcmt, + bloq_specs=[ + qualtran.bloqs.mcmt.multi_target_cnot._C_MULTI_NOT_DOC, + qualtran.bloqs.mcmt.multi_control_pauli._CC_PAULI_DOC, + ], + directory=f'{SOURCE_DIR}/bloqs/mcmt/', + path_stem='multi_control_multi_target_pauli', + ), + NotebookSpecV2( + title='Generic Select', + module=qualtran.bloqs.multiplexers.select_pauli_lcu, + bloq_specs=[qualtran.bloqs.multiplexers.select_pauli_lcu._SELECT_PAULI_LCU_DOC], + ), + NotebookSpecV2( + title='State Preparation via Alias Sampling', + module=qualtran.bloqs.state_preparation.state_preparation_alias_sampling, + bloq_specs=[ + qualtran.bloqs.state_preparation.state_preparation_alias_sampling._STATE_PREP_ALIAS_DOC, + qualtran.bloqs.state_preparation.state_preparation_alias_sampling._SPARSE_STATE_PREP_ALIAS_DOC, + ], + ), + NotebookSpecV2( + title='State Preparation Using Rotations', + module=qualtran.bloqs.state_preparation.state_preparation_via_rotation, + bloq_specs=[ + qualtran.bloqs.state_preparation.state_preparation_via_rotation._STATE_PREP_VIA_ROTATIONS_DOC + ], + directory=f'{SOURCE_DIR}/bloqs/state_preparation/', + ), + NotebookSpecV2( + title='Generalized Quantum Signal Processing', + module=qualtran.bloqs.qsp.generalized_qsp, + bloq_specs=[qualtran.bloqs.qsp.generalized_qsp._Generalized_QSP_DOC], + ), + NotebookSpecV2( + title='Hamiltonian Simulation by Generalized Quantum Signal Processing', + module=qualtran.bloqs.hamiltonian_simulation.hamiltonian_simulation_by_gqsp, + bloq_specs=[ + qualtran.bloqs.hamiltonian_simulation.hamiltonian_simulation_by_gqsp._Hamiltonian_Simulation_by_GQSP_DOC + ], + ), +] + +NB_BY_SECTION = [ + ('Basic Gates', BASIC_GATES), + ('Chemistry', CHEMISTRY), + ('Arithmetic', ARITHMETIC), + ('Modular Arithmetic', MOD_ARITHMETIC), + ('GF Arithmetic', GF_ARITHMETIC), + ('Rotations', ROT_QFT_PE), + ('Block Encoding', BLOCK_ENCODING), + ('Other', OTHER), +] diff --git a/dev_tools/qualtran_dev_tools/parse_docstrings.py b/dev_tools/qualtran_dev_tools/parse_docstrings.py index f20917960..36321bece 100644 --- a/dev_tools/qualtran_dev_tools/parse_docstrings.py +++ b/dev_tools/qualtran_dev_tools/parse_docstrings.py @@ -109,7 +109,7 @@ def _template(name, desc_lines): ] -def get_markdown_docstring_lines(cls: Type) -> List[str]: +def get_markdown_docstring(cls: Type) -> List[str]: """From a class `cls`, return its docstring as Markdown lines.""" # 1. Sphinx incantation @@ -117,11 +117,20 @@ def get_markdown_docstring_lines(cls: Type) -> List[str]: docstring = cls.__doc__ if cls.__doc__ else "" gds = _GoogleDocstringToMarkdown(inspect.cleandoc(docstring), config=config, what='class') - # 2. Pre-pend a header. - lines = [f'## `{cls.__name__}`'] + gds.lines() + # 2. Substitute restructured text inline-code blocks to markdown-style backticks. + lines = [re.sub(r':py:func:`(\w+)`', r'`\1`', line) for line in gds.lines()] + + return lines + - # 3. Substitute restructured text inline-code blocks to markdown-style backticks. - lines = [re.sub(r':py:func:`(\w+)`', r'`\1`', line) for line in lines] +def get_markdown_docstring_lines(cls: Type) -> List[str]: + """From a class `cls`, return its docstring as Markdown lines with a header.""" + + # 1. Get documentation lines + lines = get_markdown_docstring(cls) + + # 2. Pre-pend a header. + lines = [f'## `{cls.__name__}`'] + lines return lines diff --git a/dev_tools/qualtran_dev_tools/pylint_copyright_checker.py b/dev_tools/qualtran_dev_tools/pylint_copyright_checker.py index 497b012b5..d6e7a4c1b 100644 --- a/dev_tools/qualtran_dev_tools/pylint_copyright_checker.py +++ b/dev_tools/qualtran_dev_tools/pylint_copyright_checker.py @@ -12,23 +12,24 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + +from typing import TYPE_CHECKING + from astroid import nodes -from pylint.checkers import BaseChecker -from pylint.interfaces import IRawChecker +from pylint.checkers import BaseRawFileChecker + +if TYPE_CHECKING: + from pylint.lint import PyLinter -class CopyrightChecker(BaseChecker): - r"""Check for the copyright notices at the beginning of a Python source file. +class CopyrightChecker(BaseRawFileChecker): + """Check for the copyright notices at the beginning of a Python source file. This checker can be disabled by putting `# pylint: disable=wrong-or-nonexistent-copyright-notice` at the beginning of a file. """ - __implements__ = IRawChecker - - # The priority must be negative. Pylint runs plugins with smaller priorities first. - priority = -1 - name = "copyright-notice" msgs = { "R0001": ( @@ -40,7 +41,7 @@ class CopyrightChecker(BaseChecker): options = () def process_module(self, node: nodes.Module) -> None: - r"""Check whether the copyright notice is correctly placed in the source file of a module. + """Check whether the copyright notice is correctly placed in the source file of a module. Compare the first lines of a source file against the standard copyright notice (i.e., the `golden` variable below). Suffix whitespace (including newline symbols) is not considered @@ -108,8 +109,8 @@ def skip_shebang(stream): return -def register(linter): - r"""Register this checker to pylint. +def register(linter: PyLinter): + """Register this checker to pylint. The registration is done automatically if this file is in $PYTHONPATH. """ diff --git a/dev_tools/qualtran_dev_tools/reference_docs.py b/dev_tools/qualtran_dev_tools/reference_docs.py index 272b8b24b..b8e47c426 100644 --- a/dev_tools/qualtran_dev_tools/reference_docs.py +++ b/dev_tools/qualtran_dev_tools/reference_docs.py @@ -362,7 +362,7 @@ def apply_fixups(reporoot: Path): output_dir = reporoot / 'docs/reference' remove_extra_files(output_dir) - page_paths = output_dir.glob('qualtran/**/*.md') + page_paths = output_dir.glob('**/*.md') for path in page_paths: if fixup_all_symbols_page(path): continue diff --git a/dev_tools/qualtran_dev_tools/tensor_report_card.py b/dev_tools/qualtran_dev_tools/tensor_report_card.py new file mode 100644 index 000000000..c2e3fa2df --- /dev/null +++ b/dev_tools/qualtran_dev_tools/tensor_report_card.py @@ -0,0 +1,152 @@ +# 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 multiprocessing.connection +import time +from typing import Any, Callable, Dict, List, Optional, Tuple + +from attrs import define + +from qualtran import Bloq +from qualtran.simulation.tensor import cbloq_to_quimb + + +@define +class _Pending: + """Helper dataclass to track currently executing processes in `ExecuteWithTimeout`.""" + + p: multiprocessing.Process + recv: multiprocessing.connection.Connection + start_time: float + kwargs: Dict[str, Any] + + +class ExecuteWithTimeout: + """Execute tasks in processes where each task will be killed if it exceeds `timeout`. + + Seemingly all the existing "timeout" parameters in the various built-in concurrency + primitives in Python won't actually terminate the process. This one does. + """ + + def __init__(self, timeout: float, max_workers: int): + self.timeout = timeout + self.max_workers = max_workers + + self.queued: List[Tuple[Callable, Dict[str, Any]]] = [] + self.pending: List[_Pending] = [] + + @property + def work_to_be_done(self) -> int: + """The number of tasks currently executing or queued.""" + return len(self.queued) + len(self.pending) + + def submit(self, func: Callable, kwargs: Dict[str, Any]) -> None: + """Add a task to the queue. + + `func` must be a callable that can accept `kwargs` in addition to + a keyword argument `cxn` which is a multiprocessing `Connection` object that forms + the sending-half of a `mp.Pipe`. The callable must call `cxn.send(...)` + to return a result. + """ + self.queued.append((func, kwargs)) + + def _submit_from_queue(self): + # helper method that takes an item from the queue, launches a process, + # and records it in the `pending` attribute. This must only be called + # if we're allowed to spawn a new process. + func, kwargs = self.queued.pop(0) + recv, send = multiprocessing.Pipe(duplex=False) + kwargs['cxn'] = send + p = multiprocessing.Process(target=func, kwargs=kwargs) + start_time = time.time() + p.start() + self.pending.append(_Pending(p=p, recv=recv, start_time=start_time, kwargs=kwargs)) + + def _scan_pendings(self) -> Optional[_Pending]: + # helper method that goes through the currently pending tasks, terminates the ones + # that have been going on too long, and accounts for ones that have finished. + # Returns the `_Pending` of the killed or completed job or `None` if each pending + # task is still running but none have exceeded the timeout. + for i in range(len(self.pending)): + pen = self.pending[i] + + if not pen.p.is_alive(): + self.pending.pop(i) + pen.p.join() + return pen + + if time.time() - pen.start_time > self.timeout: + pen.p.terminate() + self.pending.pop(i) + return pen + + return None + + def next_result(self) -> Tuple[Dict[str, Any], Optional[Any]]: + """Get the next available result. + + This call is blocking, but should never take longer than `self.timeout`. This should + be called in a loop to make sure the queue continues to be processed. + + Returns: + task kwargs: The keyword arguments used to submit the task. + result: If the process finished successfully, this is the object that was + sent through the multiprocessing pipe as the result. Otherwise, the result + is None. + """ + while len(self.queued) > 0 and len(self.pending) < self.max_workers: + self._submit_from_queue() + + while True: + finished = self._scan_pendings() + if finished is not None: + break + + if finished.p.exitcode == 0: + result = finished.recv.recv() + else: + result = None + + finished.recv.close() + + while len(self.queued) > 0 and len(self.pending) < self.max_workers: + self._submit_from_queue() + + return (finished.kwargs, result) + + +def report_on_tensors(name: str, cls_name: str, bloq: Bloq, cxn) -> None: + """Get timing information for tensor functionality. + + This should be used with `ExecuteWithTimeout`. The resultant + record dictionary is sent over `cxn`. + """ + record: Dict[str, Any] = {'name': name, 'cls': cls_name} + + try: + start = time.perf_counter() + flat = bloq.as_composite_bloq().flatten() + record['flat_dur'] = time.perf_counter() - start + + start = time.perf_counter() + tn = cbloq_to_quimb(flat) + record['tn_dur'] = time.perf_counter() - start + + start = time.perf_counter() + record['width'] = tn.contraction_width() + record['width_dur'] = time.perf_counter() - start + + except Exception as e: # pylint: disable=broad-exception-caught + record['err'] = str(e) + + cxn.send(record) diff --git a/dev_tools/requirements/deps/format.txt b/dev_tools/requirements/deps/format.txt index c2161118a..13c83c1e3 100644 --- a/dev_tools/requirements/deps/format.txt +++ b/dev_tools/requirements/deps/format.txt @@ -1,3 +1,3 @@ flynt~=0.60 -black~=22.3.0 +black~=24.8.0 isort~=5.10.1 diff --git a/dev_tools/requirements/deps/pylint.txt b/dev_tools/requirements/deps/pylint.txt index 52b96772e..a4380741b 100644 --- a/dev_tools/requirements/deps/pylint.txt +++ b/dev_tools/requirements/deps/pylint.txt @@ -1,4 +1,4 @@ -pylint~=2.15 +pylint~=3.3.1 # for checking _test.py files pytest @@ -7,4 +7,4 @@ openfermion[resources] # dev tools tensorflow-docs sphinx -filelock \ No newline at end of file +filelock diff --git a/dev_tools/requirements/deps/runtime.txt b/dev_tools/requirements/deps/runtime.txt index f6ee834cb..f5bb0da86 100644 --- a/dev_tools/requirements/deps/runtime.txt +++ b/dev_tools/requirements/deps/runtime.txt @@ -6,6 +6,7 @@ numpy sympy cirq-core==1.4 fxpmath +galois # qualtran/testing.py nbconvert @@ -30,8 +31,8 @@ qsharp qsharp-widgets # qref bartiq interop -qref>=0.7.0 -bartiq>=0.5.1 +qref==0.7.0 +bartiq==0.6.0 # serialization protobuf diff --git a/dev_tools/requirements/envs/dev.env.txt b/dev_tools/requirements/envs/dev.env.txt index 78140912f..e536aa3d7 100644 --- a/dev_tools/requirements/envs/dev.env.txt +++ b/dev_tools/requirements/envs/dev.env.txt @@ -12,7 +12,7 @@ alabaster==1.0.0 # via sphinx annotated-types==0.7.0 # via pydantic -anyio==4.4.0 +anyio==4.6.0 # via # httpx # jupyter-server @@ -30,7 +30,7 @@ astor==0.8.1 # via # flynt # tensorflow-docs -astroid==2.15.8 +astroid==3.3.5 # via pylint asttokens==2.4.1 # via stack-data @@ -54,28 +54,28 @@ babel==2.16.0 # sphinx backports-tarfile==1.2.0 # via jaraco-context -bartiq==0.5.1 +bartiq==0.6.0 # via -r deps/runtime.txt beautifulsoup4==4.12.3 # via # nbconvert # pydata-sphinx-theme -black==22.3.0 +black==24.8.0 # via -r deps/format.txt bleach==6.1.0 # via nbconvert blinker==1.8.2 # via flask -build==1.2.1 +build==1.2.2 # via pip-tools -cachetools==5.4.0 +cachetools==5.5.0 # via -r deps/runtime.txt -certifi==2024.7.4 +certifi==2024.8.30 # via # httpcore # httpx # requests -cffi==1.17.0 +cffi==1.17.1 # via # argon2-cffi-bindings # cryptography @@ -95,19 +95,19 @@ comm==0.2.2 # via # ipykernel # ipywidgets -contourpy==1.2.1 +contourpy==1.3.0 # via matplotlib cotengra==0.6.2 # via quimb coverage[toml]==7.6.1 # via pytest-cov -cryptography==43.0.0 +cryptography==43.0.1 # via secretstorage cycler==0.12.1 # via matplotlib -cytoolz==0.12.3 +cytoolz==1.0.0 # via quimb -dash==2.17.1 +dash==2.18.1 # via -r deps/runtime.txt dash-core-components==2.0.0 # via dash @@ -115,7 +115,7 @@ dash-html-components==2.0.0 # via dash dash-table==5.0.0 # via dash -debugpy==1.8.5 +debugpy==1.8.6 # via ipykernel decorator==5.1.1 # via ipython @@ -123,7 +123,7 @@ defusedxml==0.7.1 # via nbconvert deprecation==2.1.0 # via openfermion -dill==0.3.8 +dill==0.3.9 # via pylint distlib==0.3.8 # via virtualenv @@ -142,11 +142,11 @@ exceptiongroup==1.2.2 # pytest execnet==2.1.1 # via pytest-xdist -executing==2.0.1 +executing==2.1.0 # via stack-data fastjsonschema==2.20.0 # via nbformat -filelock==3.15.4 +filelock==3.16.1 # via # -r deps/pylint.txt # -r deps/pytest.txt @@ -155,31 +155,33 @@ flask==3.0.3 # via dash flynt==0.78 # via -r deps/format.txt -fonttools==4.53.1 +fonttools==4.54.1 # via matplotlib fqdn==1.5.1 # via jsonschema fxpmath==0.4.9 # via -r deps/runtime.txt +galois==0.4.2 + # via -r deps/runtime.txt graphviz==0.20.3 # via qref -greenlet==3.0.3 +greenlet==3.1.1 # via sqlalchemy -grpcio==1.65.4 +grpcio==1.66.2 # via grpcio-tools -grpcio-tools==1.65.4 +grpcio-tools==1.66.2 # via -r deps/packaging.txt h11==0.14.0 # via httpcore -h5py==3.11.0 +h5py==3.12.1 # via # openfermion # pyscf -httpcore==1.0.5 +httpcore==1.0.6 # via httpx -httpx==0.27.0 +httpx==0.27.2 # via jupyterlab -idna==3.7 +idna==3.10 # via # anyio # httpx @@ -187,7 +189,7 @@ idna==3.7 # requests imagesize==1.4.1 # via sphinx -importlib-metadata==8.2.0 +importlib-metadata==8.5.0 # via # dash # jupyter-cache @@ -201,13 +203,13 @@ ipykernel==6.29.5 # -r deps/pytest.txt # jupyterlab # myst-nb -ipython==8.26.0 +ipython==8.28.0 # via # -r deps/runtime.txt # ipykernel # ipywidgets # myst-nb -ipywidgets==8.1.3 +ipywidgets==8.1.5 # via # -r deps/docs.txt # -r deps/runtime.txt @@ -222,13 +224,13 @@ itsdangerous==2.2.0 # via flask jaraco-classes==3.4.0 # via keyring -jaraco-context==5.3.0 +jaraco-context==6.0.1 # via keyring -jaraco-functools==4.0.2 +jaraco-functools==4.1.0 # via keyring -jax==0.4.31 +jax==0.4.34 # via openfermion -jaxlib==0.4.31 +jaxlib==0.4.34 # via # jax # openfermion @@ -261,7 +263,7 @@ jsonschema-specifications==2023.12.1 # via jsonschema jupyter-cache==1.0.0 # via myst-nb -jupyter-client==8.6.2 +jupyter-client==8.6.3 # via # ipykernel # jupyter-server @@ -296,14 +298,12 @@ jupyterlab-server==2.27.3 # via # jupyterlab # notebook -jupyterlab-widgets==3.0.11 +jupyterlab-widgets==3.0.13 # via ipywidgets -keyring==25.3.0 +keyring==25.4.1 # via twine -kiwisolver==1.4.5 +kiwisolver==1.4.7 # via matplotlib -lazy-object-proxy==1.10.0 - # via astroid llvmlite==0.43.0 # via numba markdown-it-py==3.0.0 @@ -327,23 +327,23 @@ matplotlib-inline==0.1.7 # ipython mccabe==0.7.0 # via pylint -mdit-py-plugins==0.4.1 +mdit-py-plugins==0.4.2 # via myst-parser mdurl==0.1.2 # via markdown-it-py mistune==3.0.2 # via nbconvert -ml-dtypes==0.4.0 +ml-dtypes==0.5.0 # via # jax # jaxlib -more-itertools==10.4.0 +more-itertools==10.5.0 # via # jaraco-classes # jaraco-functools mpmath==1.3.0 # via sympy -mypy==1.11.1 +mypy==1.11.2 # via -r deps/mypy.txt mypy-extensions==1.0.0 # via @@ -351,7 +351,7 @@ mypy-extensions==1.0.0 # mypy mypy-protobuf==3.6.0 # via -r deps/mypy.txt -myst-nb==1.1.1 +myst-nb==1.1.2 # via -r deps/docs.txt myst-parser==4.0.0 # via myst-nb @@ -386,7 +386,7 @@ networkx==3.3 # openfermion nh3==0.2.18 # via readme-renderer -notebook==7.2.1 +notebook==7.2.2 # via # -r deps/dev-tools.txt # -r deps/runtime.txt @@ -395,7 +395,9 @@ notebook-shim==0.2.4 # jupyterlab # notebook numba==0.60.0 - # via quimb + # via + # galois + # quimb numpy==1.26.4 # via # -r deps/runtime.txt @@ -403,6 +405,7 @@ numpy==1.26.4 # cirq-core # contourpy # fxpmath + # galois # h5py # jax # jaxlib @@ -410,7 +413,6 @@ numpy==1.26.4 # ml-dtypes # numba # openfermion - # opt-einsum # pandas # pyscf # quimb @@ -419,12 +421,13 @@ openfermion[resources]==1.6.1 # via # -r deps/pylint.txt # -r deps/pytest.txt -opt-einsum==3.3.0 +opt-einsum==3.4.0 # via jax overrides==7.7.0 # via jupyter-server packaging==24.1 # via + # black # build # deprecation # ipykernel @@ -437,7 +440,7 @@ packaging==24.1 # pydata-sphinx-theme # pytest # sphinx -pandas==2.2.2 +pandas==2.2.3 # via cirq-core pandocfilters==1.5.1 # via nbconvert @@ -453,23 +456,23 @@ pip-tools==7.4.1 # via -r deps/pip-tools.txt pkginfo==1.10.0 # via twine -platformdirs==4.2.2 +platformdirs==4.3.6 # via # black # jupyter-core # pylint # virtualenv -plotly==5.23.0 +plotly==5.24.1 # via # -r deps/runtime.txt # dash pluggy==1.5.0 # via pytest -prometheus-client==0.20.0 +prometheus-client==0.21.0 # via jupyter-server -prompt-toolkit==3.0.47 +prompt-toolkit==3.0.48 # via ipython -protobuf==5.27.3 +protobuf==5.28.2 # via # -r deps/runtime.txt # grpcio-tools @@ -491,15 +494,15 @@ pure-eval==0.2.3 # via stack-data pycparser==2.22 # via cffi -pydantic==2.8.2 +pydantic==2.9.2 # via # bartiq # qref -pydantic-core==2.20.1 +pydantic-core==2.23.4 # via pydantic pydata-sphinx-theme==0.15.4 # via -r deps/docs.txt -pydot==3.0.1 +pydot==3.0.2 # via -r deps/runtime.txt pygments==2.18.0 # via @@ -510,27 +513,27 @@ pygments==2.18.0 # readme-renderer # rich # sphinx -pylint==2.17.7 +pylint==3.3.1 # via -r deps/pylint.txt -pyparsing==3.1.2 +pyparsing==3.1.4 # via # bartiq # matplotlib # pydot -pyproject-hooks==1.1.0 +pyproject-hooks==1.2.0 # via # build # pip-tools -pyscf==2.6.2 +pyscf==2.7.0 # via openfermion -pytest==8.3.2 +pytest==8.3.3 # via # -r deps/pylint.txt # -r deps/pytest.txt # pytest-asyncio # pytest-cov # pytest-xdist -pytest-asyncio==0.23.8 +pytest-asyncio==0.24.0 # via -r deps/pytest.txt pytest-cov==5.0.0 # via -r deps/pytest.txt @@ -544,7 +547,7 @@ python-dateutil==2.9.0.post0 # pandas python-json-logger==2.0.7 # via jupyter-events -pytz==2024.1 +pytz==2024.2 # via pandas pyyaml==6.0.2 # via @@ -553,7 +556,7 @@ pyyaml==6.0.2 # myst-nb # myst-parser # tensorflow-docs -pyzmq==26.1.0 +pyzmq==26.2.0 # via # ipykernel # jupyter-client @@ -562,9 +565,9 @@ qref==0.7.0 # via # -r deps/runtime.txt # bartiq -qsharp==1.7.0 +qsharp==1.9.0 # via -r deps/runtime.txt -qsharp-widgets==1.7.0 +qsharp-widgets==1.9.0 # via -r deps/runtime.txt quimb==1.8.4 # via -r deps/runtime.txt @@ -597,13 +600,13 @@ rfc3986-validator==0.1.1 # via # jsonschema # jupyter-events -rich==13.7.1 +rich==13.9.2 # via twine rpds-py==0.20.0 # via # jsonschema # referencing -scipy==1.14.0 +scipy==1.14.1 # via # ase # cirq-core @@ -652,7 +655,7 @@ sphinxcontrib-qthelp==2.0.0 # via sphinx sphinxcontrib-serializinghtml==2.0.0 # via sphinx -sqlalchemy==2.0.32 +sqlalchemy==2.0.35 # via jupyter-cache stack-data==0.6.3 # via ipython @@ -677,7 +680,7 @@ terminado==0.18.1 # jupyter-server-terminals tinycss2==1.3.0 # via nbconvert -tomli==2.0.1 +tomli==2.0.2 # via # black # build @@ -691,7 +694,7 @@ tomli==2.0.1 # sphinx tomlkit==0.13.2 # via pylint -toolz==0.12.1 +toolz==1.0.0 # via cytoolz tornado==6.4.1 # via @@ -722,9 +725,9 @@ traitlets==5.14.3 # nbformat twine==5.1.1 # via -r deps/packaging.txt -types-protobuf==5.27.0.20240626 +types-protobuf==5.28.0.20240924 # via mypy-protobuf -types-python-dateutil==2.9.0.20240316 +types-python-dateutil==2.9.0.20241003 # via arrow typing-extensions==4.12.2 # via @@ -733,24 +736,27 @@ typing-extensions==4.12.2 # anywidget # astroid # async-lru + # black # cirq-core # dash + # galois # ipython # mypy # myst-nb # pydantic # pydantic-core # pydata-sphinx-theme + # rich # sqlalchemy -tzdata==2024.1 +tzdata==2024.2 # via pandas uri-template==1.3.0 # via jsonschema -urllib3==2.2.2 +urllib3==2.2.3 # via # requests # twine -virtualenv==20.26.3 +virtualenv==20.26.6 # via -r deps/packaging.txt wcwidth==0.2.13 # via prompt-toolkit @@ -762,7 +768,7 @@ webencodings==0.5.1 # tinycss2 websocket-client==1.8.0 # via jupyter-server -werkzeug==3.0.3 +werkzeug==3.0.4 # via # dash # flask @@ -770,11 +776,9 @@ wheel==0.44.0 # via # -r deps/packaging.txt # pip-tools -widgetsnbextension==4.0.11 +widgetsnbextension==4.0.13 # via ipywidgets -wrapt==1.16.0 - # via astroid -zipp==3.20.0 +zipp==3.20.2 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: diff --git a/dev_tools/requirements/envs/docs.env.txt b/dev_tools/requirements/envs/docs.env.txt index cb5020643..d712bd9b1 100644 --- a/dev_tools/requirements/envs/docs.env.txt +++ b/dev_tools/requirements/envs/docs.env.txt @@ -20,7 +20,7 @@ annotated-types==0.7.0 # via # -c envs/dev.env.txt # pydantic -anyio==4.4.0 +anyio==4.6.0 # via # -c envs/dev.env.txt # httpx @@ -72,7 +72,7 @@ babel==2.16.0 # jupyterlab-server # pydata-sphinx-theme # sphinx -bartiq==0.5.1 +bartiq==0.6.0 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -89,17 +89,17 @@ blinker==1.8.2 # via # -c envs/dev.env.txt # flask -cachetools==5.4.0 +cachetools==5.5.0 # via # -c envs/dev.env.txt # -r deps/runtime.txt -certifi==2024.7.4 +certifi==2024.8.30 # via # -c envs/dev.env.txt # httpcore # httpx # requests -cffi==1.17.0 +cffi==1.17.1 # via # -c envs/dev.env.txt # argon2-cffi-bindings @@ -121,7 +121,7 @@ comm==0.2.2 # -c envs/dev.env.txt # ipykernel # ipywidgets -contourpy==1.2.1 +contourpy==1.3.0 # via # -c envs/dev.env.txt # matplotlib @@ -133,11 +133,11 @@ cycler==0.12.1 # via # -c envs/dev.env.txt # matplotlib -cytoolz==0.12.3 +cytoolz==1.0.0 # via # -c envs/dev.env.txt # quimb -dash==2.17.1 +dash==2.18.1 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -153,7 +153,7 @@ dash-table==5.0.0 # via # -c envs/dev.env.txt # dash -debugpy==1.8.5 +debugpy==1.8.6 # via # -c envs/dev.env.txt # ipykernel @@ -180,7 +180,7 @@ exceptiongroup==1.2.2 # -c envs/dev.env.txt # anyio # ipython -executing==2.0.1 +executing==2.1.0 # via # -c envs/dev.env.txt # stack-data @@ -192,7 +192,7 @@ flask==3.0.3 # via # -c envs/dev.env.txt # dash -fonttools==4.53.1 +fonttools==4.54.1 # via # -c envs/dev.env.txt # matplotlib @@ -204,11 +204,15 @@ fxpmath==0.4.9 # via # -c envs/dev.env.txt # -r deps/runtime.txt +galois==0.4.2 + # via + # -c envs/dev.env.txt + # -r deps/runtime.txt graphviz==0.20.3 # via # -c envs/dev.env.txt # qref -greenlet==3.0.3 +greenlet==3.1.1 # via # -c envs/dev.env.txt # sqlalchemy @@ -216,15 +220,15 @@ h11==0.14.0 # via # -c envs/dev.env.txt # httpcore -httpcore==1.0.5 +httpcore==1.0.6 # via # -c envs/dev.env.txt # httpx -httpx==0.27.0 +httpx==0.27.2 # via # -c envs/dev.env.txt # jupyterlab -idna==3.7 +idna==3.10 # via # -c envs/dev.env.txt # anyio @@ -235,7 +239,7 @@ imagesize==1.4.1 # via # -c envs/dev.env.txt # sphinx -importlib-metadata==8.2.0 +importlib-metadata==8.5.0 # via # -c envs/dev.env.txt # dash @@ -246,14 +250,14 @@ ipykernel==6.29.5 # -c envs/dev.env.txt # jupyterlab # myst-nb -ipython==8.26.0 +ipython==8.28.0 # via # -c envs/dev.env.txt # -r deps/runtime.txt # ipykernel # ipywidgets # myst-nb -ipywidgets==8.1.3 +ipywidgets==8.1.5 # via # -c envs/dev.env.txt # -r deps/docs.txt @@ -304,7 +308,7 @@ jupyter-cache==1.0.0 # via # -c envs/dev.env.txt # myst-nb -jupyter-client==8.6.2 +jupyter-client==8.6.3 # via # -c envs/dev.env.txt # ipykernel @@ -353,11 +357,11 @@ jupyterlab-server==2.27.3 # -c envs/dev.env.txt # jupyterlab # notebook -jupyterlab-widgets==3.0.11 +jupyterlab-widgets==3.0.13 # via # -c envs/dev.env.txt # ipywidgets -kiwisolver==1.4.5 +kiwisolver==1.4.7 # via # -c envs/dev.env.txt # matplotlib @@ -386,7 +390,7 @@ matplotlib-inline==0.1.7 # -c envs/dev.env.txt # ipykernel # ipython -mdit-py-plugins==0.4.1 +mdit-py-plugins==0.4.2 # via # -c envs/dev.env.txt # myst-parser @@ -402,7 +406,7 @@ mpmath==1.3.0 # via # -c envs/dev.env.txt # sympy -myst-nb==1.1.1 +myst-nb==1.1.2 # via # -c envs/dev.env.txt # -r deps/docs.txt @@ -443,7 +447,7 @@ networkx==3.3 # -c envs/dev.env.txt # -r deps/runtime.txt # cirq-core -notebook==7.2.1 +notebook==7.2.2 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -455,6 +459,7 @@ notebook-shim==0.2.4 numba==0.60.0 # via # -c envs/dev.env.txt + # galois # quimb numpy==1.26.4 # via @@ -463,6 +468,7 @@ numpy==1.26.4 # cirq-core # contourpy # fxpmath + # galois # matplotlib # numba # pandas @@ -484,7 +490,7 @@ packaging==24.1 # plotly # pydata-sphinx-theme # sphinx -pandas==2.2.2 +pandas==2.2.3 # via # -c envs/dev.env.txt # cirq-core @@ -504,24 +510,24 @@ pillow==10.4.0 # via # -c envs/dev.env.txt # matplotlib -platformdirs==4.2.2 +platformdirs==4.3.6 # via # -c envs/dev.env.txt # jupyter-core -plotly==5.23.0 +plotly==5.24.1 # via # -c envs/dev.env.txt # -r deps/runtime.txt # dash -prometheus-client==0.20.0 +prometheus-client==0.21.0 # via # -c envs/dev.env.txt # jupyter-server -prompt-toolkit==3.0.47 +prompt-toolkit==3.0.48 # via # -c envs/dev.env.txt # ipython -protobuf==5.27.3 +protobuf==5.28.2 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -548,12 +554,12 @@ pycparser==2.22 # via # -c envs/dev.env.txt # cffi -pydantic==2.8.2 +pydantic==2.9.2 # via # -c envs/dev.env.txt # bartiq # qref -pydantic-core==2.20.1 +pydantic-core==2.23.4 # via # -c envs/dev.env.txt # pydantic @@ -561,7 +567,7 @@ pydata-sphinx-theme==0.15.4 # via # -c envs/dev.env.txt # -r deps/docs.txt -pydot==3.0.1 +pydot==3.0.2 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -573,7 +579,7 @@ pygments==2.18.0 # nbconvert # pydata-sphinx-theme # sphinx -pyparsing==3.1.2 +pyparsing==3.1.4 # via # -c envs/dev.env.txt # bartiq @@ -590,7 +596,7 @@ python-json-logger==2.0.7 # via # -c envs/dev.env.txt # jupyter-events -pytz==2024.1 +pytz==2024.2 # via # -c envs/dev.env.txt # pandas @@ -602,7 +608,7 @@ pyyaml==6.0.2 # myst-nb # myst-parser # tensorflow-docs -pyzmq==26.1.0 +pyzmq==26.2.0 # via # -c envs/dev.env.txt # ipykernel @@ -613,11 +619,11 @@ qref==0.7.0 # -c envs/dev.env.txt # -r deps/runtime.txt # bartiq -qsharp==1.7.0 +qsharp==1.9.0 # via # -c envs/dev.env.txt # -r deps/runtime.txt -qsharp-widgets==1.7.0 +qsharp-widgets==1.9.0 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -656,7 +662,7 @@ rpds-py==0.20.0 # -c envs/dev.env.txt # jsonschema # referencing -scipy==1.14.0 +scipy==1.14.1 # via # -c envs/dev.env.txt # cirq-core @@ -721,7 +727,7 @@ sphinxcontrib-serializinghtml==2.0.0 # via # -c envs/dev.env.txt # sphinx -sqlalchemy==2.0.32 +sqlalchemy==2.0.35 # via # -c envs/dev.env.txt # jupyter-cache @@ -756,12 +762,12 @@ tinycss2==1.3.0 # via # -c envs/dev.env.txt # nbconvert -tomli==2.0.1 +tomli==2.0.2 # via # -c envs/dev.env.txt # jupyterlab # sphinx -toolz==0.12.1 +toolz==1.0.0 # via # -c envs/dev.env.txt # cytoolz @@ -795,7 +801,7 @@ traitlets==5.14.3 # nbclient # nbconvert # nbformat -types-python-dateutil==2.9.0.20240316 +types-python-dateutil==2.9.0.20241003 # via # -c envs/dev.env.txt # arrow @@ -808,13 +814,14 @@ typing-extensions==4.12.2 # async-lru # cirq-core # dash + # galois # ipython # myst-nb # pydantic # pydantic-core # pydata-sphinx-theme # sqlalchemy -tzdata==2024.1 +tzdata==2024.2 # via # -c envs/dev.env.txt # pandas @@ -822,7 +829,7 @@ uri-template==1.3.0 # via # -c envs/dev.env.txt # jsonschema -urllib3==2.2.2 +urllib3==2.2.3 # via # -c envs/dev.env.txt # requests @@ -843,16 +850,16 @@ websocket-client==1.8.0 # via # -c envs/dev.env.txt # jupyter-server -werkzeug==3.0.3 +werkzeug==3.0.4 # via # -c envs/dev.env.txt # dash # flask -widgetsnbextension==4.0.11 +widgetsnbextension==4.0.13 # via # -c envs/dev.env.txt # ipywidgets -zipp==3.20.0 +zipp==3.20.2 # via # -c envs/dev.env.txt # importlib-metadata diff --git a/dev_tools/requirements/envs/format.env.txt b/dev_tools/requirements/envs/format.env.txt index e8be0e603..22872de48 100644 --- a/dev_tools/requirements/envs/format.env.txt +++ b/dev_tools/requirements/envs/format.env.txt @@ -8,7 +8,7 @@ annotated-types==0.7.0 # via # -c envs/dev.env.txt # pydantic -anyio==4.4.0 +anyio==4.6.0 # via # -c envs/dev.env.txt # httpx @@ -57,7 +57,7 @@ babel==2.16.0 # via # -c envs/dev.env.txt # jupyterlab-server -bartiq==0.5.1 +bartiq==0.6.0 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -65,7 +65,7 @@ beautifulsoup4==4.12.3 # via # -c envs/dev.env.txt # nbconvert -black==22.3.0 +black==24.8.0 # via # -c envs/dev.env.txt # -r deps/format.txt @@ -77,17 +77,17 @@ blinker==1.8.2 # via # -c envs/dev.env.txt # flask -cachetools==5.4.0 +cachetools==5.5.0 # via # -c envs/dev.env.txt # -r deps/runtime.txt -certifi==2024.7.4 +certifi==2024.8.30 # via # -c envs/dev.env.txt # httpcore # httpx # requests -cffi==1.17.0 +cffi==1.17.1 # via # -c envs/dev.env.txt # argon2-cffi-bindings @@ -109,7 +109,7 @@ comm==0.2.2 # -c envs/dev.env.txt # ipykernel # ipywidgets -contourpy==1.2.1 +contourpy==1.3.0 # via # -c envs/dev.env.txt # matplotlib @@ -121,11 +121,11 @@ cycler==0.12.1 # via # -c envs/dev.env.txt # matplotlib -cytoolz==0.12.3 +cytoolz==1.0.0 # via # -c envs/dev.env.txt # quimb -dash==2.17.1 +dash==2.18.1 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -141,7 +141,7 @@ dash-table==5.0.0 # via # -c envs/dev.env.txt # dash -debugpy==1.8.5 +debugpy==1.8.6 # via # -c envs/dev.env.txt # ipykernel @@ -162,7 +162,7 @@ exceptiongroup==1.2.2 # -c envs/dev.env.txt # anyio # ipython -executing==2.0.1 +executing==2.1.0 # via # -c envs/dev.env.txt # stack-data @@ -178,7 +178,7 @@ flynt==0.78 # via # -c envs/dev.env.txt # -r deps/format.txt -fonttools==4.53.1 +fonttools==4.54.1 # via # -c envs/dev.env.txt # matplotlib @@ -190,6 +190,10 @@ fxpmath==0.4.9 # via # -c envs/dev.env.txt # -r deps/runtime.txt +galois==0.4.2 + # via + # -c envs/dev.env.txt + # -r deps/runtime.txt graphviz==0.20.3 # via # -c envs/dev.env.txt @@ -198,22 +202,22 @@ h11==0.14.0 # via # -c envs/dev.env.txt # httpcore -httpcore==1.0.5 +httpcore==1.0.6 # via # -c envs/dev.env.txt # httpx -httpx==0.27.0 +httpx==0.27.2 # via # -c envs/dev.env.txt # jupyterlab -idna==3.7 +idna==3.10 # via # -c envs/dev.env.txt # anyio # httpx # jsonschema # requests -importlib-metadata==8.2.0 +importlib-metadata==8.5.0 # via # -c envs/dev.env.txt # dash @@ -221,13 +225,13 @@ ipykernel==6.29.5 # via # -c envs/dev.env.txt # jupyterlab -ipython==8.26.0 +ipython==8.28.0 # via # -c envs/dev.env.txt # -r deps/runtime.txt # ipykernel # ipywidgets -ipywidgets==8.1.3 +ipywidgets==8.1.5 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -274,7 +278,7 @@ jsonschema-specifications==2023.12.1 # via # -c envs/dev.env.txt # jsonschema -jupyter-client==8.6.2 +jupyter-client==8.6.3 # via # -c envs/dev.env.txt # ipykernel @@ -323,11 +327,11 @@ jupyterlab-server==2.27.3 # -c envs/dev.env.txt # jupyterlab # notebook -jupyterlab-widgets==3.0.11 +jupyterlab-widgets==3.0.13 # via # -c envs/dev.env.txt # ipywidgets -kiwisolver==1.4.5 +kiwisolver==1.4.7 # via # -c envs/dev.env.txt # matplotlib @@ -389,7 +393,7 @@ networkx==3.3 # -c envs/dev.env.txt # -r deps/runtime.txt # cirq-core -notebook==7.2.1 +notebook==7.2.2 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -401,6 +405,7 @@ notebook-shim==0.2.4 numba==0.60.0 # via # -c envs/dev.env.txt + # galois # quimb numpy==1.26.4 # via @@ -409,6 +414,7 @@ numpy==1.26.4 # cirq-core # contourpy # fxpmath + # galois # matplotlib # numba # pandas @@ -421,6 +427,7 @@ overrides==7.7.0 packaging==24.1 # via # -c envs/dev.env.txt + # black # ipykernel # jupyter-server # jupyterlab @@ -428,7 +435,7 @@ packaging==24.1 # matplotlib # nbconvert # plotly -pandas==2.2.2 +pandas==2.2.3 # via # -c envs/dev.env.txt # cirq-core @@ -452,25 +459,25 @@ pillow==10.4.0 # via # -c envs/dev.env.txt # matplotlib -platformdirs==4.2.2 +platformdirs==4.3.6 # via # -c envs/dev.env.txt # black # jupyter-core -plotly==5.23.0 +plotly==5.24.1 # via # -c envs/dev.env.txt # -r deps/runtime.txt # dash -prometheus-client==0.20.0 +prometheus-client==0.21.0 # via # -c envs/dev.env.txt # jupyter-server -prompt-toolkit==3.0.47 +prompt-toolkit==3.0.48 # via # -c envs/dev.env.txt # ipython -protobuf==5.27.3 +protobuf==5.28.2 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -496,16 +503,16 @@ pycparser==2.22 # via # -c envs/dev.env.txt # cffi -pydantic==2.8.2 +pydantic==2.9.2 # via # -c envs/dev.env.txt # bartiq # qref -pydantic-core==2.20.1 +pydantic-core==2.23.4 # via # -c envs/dev.env.txt # pydantic -pydot==3.0.1 +pydot==3.0.2 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -514,7 +521,7 @@ pygments==2.18.0 # -c envs/dev.env.txt # ipython # nbconvert -pyparsing==3.1.2 +pyparsing==3.1.4 # via # -c envs/dev.env.txt # bartiq @@ -531,7 +538,7 @@ python-json-logger==2.0.7 # via # -c envs/dev.env.txt # jupyter-events -pytz==2024.1 +pytz==2024.2 # via # -c envs/dev.env.txt # pandas @@ -539,7 +546,7 @@ pyyaml==6.0.2 # via # -c envs/dev.env.txt # jupyter-events -pyzmq==26.1.0 +pyzmq==26.2.0 # via # -c envs/dev.env.txt # ipykernel @@ -550,11 +557,11 @@ qref==0.7.0 # -c envs/dev.env.txt # -r deps/runtime.txt # bartiq -qsharp==1.7.0 +qsharp==1.9.0 # via # -c envs/dev.env.txt # -r deps/runtime.txt -qsharp-widgets==1.7.0 +qsharp-widgets==1.9.0 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -592,7 +599,7 @@ rpds-py==0.20.0 # -c envs/dev.env.txt # jsonschema # referencing -scipy==1.14.0 +scipy==1.14.1 # via # -c envs/dev.env.txt # cirq-core @@ -645,13 +652,13 @@ tinycss2==1.3.0 # via # -c envs/dev.env.txt # nbconvert -tomli==2.0.1 +tomli==2.0.2 # via # -c envs/dev.env.txt # black # flynt # jupyterlab -toolz==0.12.1 +toolz==1.0.0 # via # -c envs/dev.env.txt # cytoolz @@ -685,7 +692,7 @@ traitlets==5.14.3 # nbclient # nbconvert # nbformat -types-python-dateutil==2.9.0.20240316 +types-python-dateutil==2.9.0.20241003 # via # -c envs/dev.env.txt # arrow @@ -696,12 +703,14 @@ typing-extensions==4.12.2 # anyio # anywidget # async-lru + # black # cirq-core # dash + # galois # ipython # pydantic # pydantic-core -tzdata==2024.1 +tzdata==2024.2 # via # -c envs/dev.env.txt # pandas @@ -709,7 +718,7 @@ uri-template==1.3.0 # via # -c envs/dev.env.txt # jsonschema -urllib3==2.2.2 +urllib3==2.2.3 # via # -c envs/dev.env.txt # requests @@ -730,16 +739,16 @@ websocket-client==1.8.0 # via # -c envs/dev.env.txt # jupyter-server -werkzeug==3.0.3 +werkzeug==3.0.4 # via # -c envs/dev.env.txt # dash # flask -widgetsnbextension==4.0.11 +widgetsnbextension==4.0.13 # via # -c envs/dev.env.txt # ipywidgets -zipp==3.20.0 +zipp==3.20.2 # via # -c envs/dev.env.txt # importlib-metadata diff --git a/dev_tools/requirements/envs/pip-tools.env.txt b/dev_tools/requirements/envs/pip-tools.env.txt index 0ba1bf977..1503cbbc1 100644 --- a/dev_tools/requirements/envs/pip-tools.env.txt +++ b/dev_tools/requirements/envs/pip-tools.env.txt @@ -4,7 +4,7 @@ # # pip-compile --constraint=envs/dev.env.txt --output-file=envs/pip-tools.env.txt deps/pip-tools.txt # -build==1.2.1 +build==1.2.2 # via # -c envs/dev.env.txt # pip-tools @@ -20,12 +20,12 @@ pip-tools==7.4.1 # via # -c envs/dev.env.txt # -r deps/pip-tools.txt -pyproject-hooks==1.1.0 +pyproject-hooks==1.2.0 # via # -c envs/dev.env.txt # build # pip-tools -tomli==2.0.1 +tomli==2.0.2 # via # -c envs/dev.env.txt # build diff --git a/dev_tools/requirements/envs/pylint.env.txt b/dev_tools/requirements/envs/pylint.env.txt index 13ba1ea62..0e3dddb6e 100644 --- a/dev_tools/requirements/envs/pylint.env.txt +++ b/dev_tools/requirements/envs/pylint.env.txt @@ -16,7 +16,7 @@ annotated-types==0.7.0 # via # -c envs/dev.env.txt # pydantic -anyio==4.4.0 +anyio==4.6.0 # via # -c envs/dev.env.txt # httpx @@ -45,7 +45,7 @@ astor==0.8.1 # via # -c envs/dev.env.txt # tensorflow-docs -astroid==2.15.8 +astroid==3.3.5 # via # -c envs/dev.env.txt # pylint @@ -74,7 +74,7 @@ babel==2.16.0 # -c envs/dev.env.txt # jupyterlab-server # sphinx -bartiq==0.5.1 +bartiq==0.6.0 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -90,17 +90,17 @@ blinker==1.8.2 # via # -c envs/dev.env.txt # flask -cachetools==5.4.0 +cachetools==5.5.0 # via # -c envs/dev.env.txt # -r deps/runtime.txt -certifi==2024.7.4 +certifi==2024.8.30 # via # -c envs/dev.env.txt # httpcore # httpx # requests -cffi==1.17.0 +cffi==1.17.1 # via # -c envs/dev.env.txt # argon2-cffi-bindings @@ -122,7 +122,7 @@ comm==0.2.2 # -c envs/dev.env.txt # ipykernel # ipywidgets -contourpy==1.2.1 +contourpy==1.3.0 # via # -c envs/dev.env.txt # matplotlib @@ -134,11 +134,11 @@ cycler==0.12.1 # via # -c envs/dev.env.txt # matplotlib -cytoolz==0.12.3 +cytoolz==1.0.0 # via # -c envs/dev.env.txt # quimb -dash==2.17.1 +dash==2.18.1 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -154,7 +154,7 @@ dash-table==5.0.0 # via # -c envs/dev.env.txt # dash -debugpy==1.8.5 +debugpy==1.8.6 # via # -c envs/dev.env.txt # ipykernel @@ -170,7 +170,7 @@ deprecation==2.1.0 # via # -c envs/dev.env.txt # openfermion -dill==0.3.8 +dill==0.3.9 # via # -c envs/dev.env.txt # pylint @@ -188,7 +188,7 @@ exceptiongroup==1.2.2 # anyio # ipython # pytest -executing==2.0.1 +executing==2.1.0 # via # -c envs/dev.env.txt # stack-data @@ -196,7 +196,7 @@ fastjsonschema==2.20.0 # via # -c envs/dev.env.txt # nbformat -filelock==3.15.4 +filelock==3.16.1 # via # -c envs/dev.env.txt # -r deps/pylint.txt @@ -204,7 +204,7 @@ flask==3.0.3 # via # -c envs/dev.env.txt # dash -fonttools==4.53.1 +fonttools==4.54.1 # via # -c envs/dev.env.txt # matplotlib @@ -216,6 +216,10 @@ fxpmath==0.4.9 # via # -c envs/dev.env.txt # -r deps/runtime.txt +galois==0.4.2 + # via + # -c envs/dev.env.txt + # -r deps/runtime.txt graphviz==0.20.3 # via # -c envs/dev.env.txt @@ -224,20 +228,20 @@ h11==0.14.0 # via # -c envs/dev.env.txt # httpcore -h5py==3.11.0 +h5py==3.12.1 # via # -c envs/dev.env.txt # openfermion # pyscf -httpcore==1.0.5 +httpcore==1.0.6 # via # -c envs/dev.env.txt # httpx -httpx==0.27.0 +httpx==0.27.2 # via # -c envs/dev.env.txt # jupyterlab -idna==3.7 +idna==3.10 # via # -c envs/dev.env.txt # anyio @@ -248,7 +252,7 @@ imagesize==1.4.1 # via # -c envs/dev.env.txt # sphinx -importlib-metadata==8.2.0 +importlib-metadata==8.5.0 # via # -c envs/dev.env.txt # dash @@ -260,13 +264,13 @@ ipykernel==6.29.5 # via # -c envs/dev.env.txt # jupyterlab -ipython==8.26.0 +ipython==8.28.0 # via # -c envs/dev.env.txt # -r deps/runtime.txt # ipykernel # ipywidgets -ipywidgets==8.1.3 +ipywidgets==8.1.5 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -283,11 +287,11 @@ itsdangerous==2.2.0 # via # -c envs/dev.env.txt # flask -jax==0.4.31 +jax==0.4.34 # via # -c envs/dev.env.txt # openfermion -jaxlib==0.4.31 +jaxlib==0.4.34 # via # -c envs/dev.env.txt # jax @@ -324,7 +328,7 @@ jsonschema-specifications==2023.12.1 # via # -c envs/dev.env.txt # jsonschema -jupyter-client==8.6.2 +jupyter-client==8.6.3 # via # -c envs/dev.env.txt # ipykernel @@ -373,18 +377,14 @@ jupyterlab-server==2.27.3 # -c envs/dev.env.txt # jupyterlab # notebook -jupyterlab-widgets==3.0.11 +jupyterlab-widgets==3.0.13 # via # -c envs/dev.env.txt # ipywidgets -kiwisolver==1.4.5 +kiwisolver==1.4.7 # via # -c envs/dev.env.txt # matplotlib -lazy-object-proxy==1.10.0 - # via - # -c envs/dev.env.txt - # astroid llvmlite==0.43.0 # via # -c envs/dev.env.txt @@ -414,7 +414,7 @@ mistune==3.0.2 # via # -c envs/dev.env.txt # nbconvert -ml-dtypes==0.4.0 +ml-dtypes==0.5.0 # via # -c envs/dev.env.txt # jax @@ -451,7 +451,7 @@ networkx==3.3 # -r deps/runtime.txt # cirq-core # openfermion -notebook==7.2.1 +notebook==7.2.2 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -463,6 +463,7 @@ notebook-shim==0.2.4 numba==0.60.0 # via # -c envs/dev.env.txt + # galois # quimb numpy==1.26.4 # via @@ -472,6 +473,7 @@ numpy==1.26.4 # cirq-core # contourpy # fxpmath + # galois # h5py # jax # jaxlib @@ -479,7 +481,6 @@ numpy==1.26.4 # ml-dtypes # numba # openfermion - # opt-einsum # pandas # pyscf # quimb @@ -488,7 +489,7 @@ openfermion[resources]==1.6.1 # via # -c envs/dev.env.txt # -r deps/pylint.txt -opt-einsum==3.3.0 +opt-einsum==3.4.0 # via # -c envs/dev.env.txt # jax @@ -509,7 +510,7 @@ packaging==24.1 # plotly # pytest # sphinx -pandas==2.2.2 +pandas==2.2.3 # via # -c envs/dev.env.txt # cirq-core @@ -529,12 +530,12 @@ pillow==10.4.0 # via # -c envs/dev.env.txt # matplotlib -platformdirs==4.2.2 +platformdirs==4.3.6 # via # -c envs/dev.env.txt # jupyter-core # pylint -plotly==5.23.0 +plotly==5.24.1 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -543,15 +544,15 @@ pluggy==1.5.0 # via # -c envs/dev.env.txt # pytest -prometheus-client==0.20.0 +prometheus-client==0.21.0 # via # -c envs/dev.env.txt # jupyter-server -prompt-toolkit==3.0.47 +prompt-toolkit==3.0.48 # via # -c envs/dev.env.txt # ipython -protobuf==5.27.3 +protobuf==5.28.2 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -582,16 +583,16 @@ pycparser==2.22 # via # -c envs/dev.env.txt # cffi -pydantic==2.8.2 +pydantic==2.9.2 # via # -c envs/dev.env.txt # bartiq # qref -pydantic-core==2.20.1 +pydantic-core==2.23.4 # via # -c envs/dev.env.txt # pydantic -pydot==3.0.1 +pydot==3.0.2 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -601,21 +602,21 @@ pygments==2.18.0 # ipython # nbconvert # sphinx -pylint==2.17.7 +pylint==3.3.1 # via # -c envs/dev.env.txt # -r deps/pylint.txt -pyparsing==3.1.2 +pyparsing==3.1.4 # via # -c envs/dev.env.txt # bartiq # matplotlib # pydot -pyscf==2.6.2 +pyscf==2.7.0 # via # -c envs/dev.env.txt # openfermion -pytest==8.3.2 +pytest==8.3.3 # via # -c envs/dev.env.txt # -r deps/pylint.txt @@ -630,7 +631,7 @@ python-json-logger==2.0.7 # via # -c envs/dev.env.txt # jupyter-events -pytz==2024.1 +pytz==2024.2 # via # -c envs/dev.env.txt # pandas @@ -639,7 +640,7 @@ pyyaml==6.0.2 # -c envs/dev.env.txt # jupyter-events # tensorflow-docs -pyzmq==26.1.0 +pyzmq==26.2.0 # via # -c envs/dev.env.txt # ipykernel @@ -650,11 +651,11 @@ qref==0.7.0 # -c envs/dev.env.txt # -r deps/runtime.txt # bartiq -qsharp==1.7.0 +qsharp==1.9.0 # via # -c envs/dev.env.txt # -r deps/runtime.txt -qsharp-widgets==1.7.0 +qsharp-widgets==1.9.0 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -694,7 +695,7 @@ rpds-py==0.20.0 # -c envs/dev.env.txt # jsonschema # referencing -scipy==1.14.0 +scipy==1.14.1 # via # -c envs/dev.env.txt # ase @@ -789,7 +790,7 @@ tinycss2==1.3.0 # via # -c envs/dev.env.txt # nbconvert -tomli==2.0.1 +tomli==2.0.2 # via # -c envs/dev.env.txt # jupyterlab @@ -800,7 +801,7 @@ tomlkit==0.13.2 # via # -c envs/dev.env.txt # pylint -toolz==0.12.1 +toolz==1.0.0 # via # -c envs/dev.env.txt # cytoolz @@ -834,7 +835,7 @@ traitlets==5.14.3 # nbclient # nbconvert # nbformat -types-python-dateutil==2.9.0.20240316 +types-python-dateutil==2.9.0.20241003 # via # -c envs/dev.env.txt # arrow @@ -848,10 +849,11 @@ typing-extensions==4.12.2 # async-lru # cirq-core # dash + # galois # ipython # pydantic # pydantic-core -tzdata==2024.1 +tzdata==2024.2 # via # -c envs/dev.env.txt # pandas @@ -859,7 +861,7 @@ uri-template==1.3.0 # via # -c envs/dev.env.txt # jsonschema -urllib3==2.2.2 +urllib3==2.2.3 # via # -c envs/dev.env.txt # requests @@ -880,20 +882,16 @@ websocket-client==1.8.0 # via # -c envs/dev.env.txt # jupyter-server -werkzeug==3.0.3 +werkzeug==3.0.4 # via # -c envs/dev.env.txt # dash # flask -widgetsnbextension==4.0.11 +widgetsnbextension==4.0.13 # via # -c envs/dev.env.txt # ipywidgets -wrapt==1.16.0 - # via - # -c envs/dev.env.txt - # astroid -zipp==3.20.0 +zipp==3.20.2 # via # -c envs/dev.env.txt # importlib-metadata diff --git a/dev_tools/requirements/envs/pytest.env.txt b/dev_tools/requirements/envs/pytest.env.txt index 4e2e9059e..3ecafa454 100644 --- a/dev_tools/requirements/envs/pytest.env.txt +++ b/dev_tools/requirements/envs/pytest.env.txt @@ -8,7 +8,7 @@ annotated-types==0.7.0 # via # -c envs/dev.env.txt # pydantic -anyio==4.4.0 +anyio==4.6.0 # via # -c envs/dev.env.txt # httpx @@ -57,7 +57,7 @@ babel==2.16.0 # via # -c envs/dev.env.txt # jupyterlab-server -bartiq==0.5.1 +bartiq==0.6.0 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -73,17 +73,17 @@ blinker==1.8.2 # via # -c envs/dev.env.txt # flask -cachetools==5.4.0 +cachetools==5.5.0 # via # -c envs/dev.env.txt # -r deps/runtime.txt -certifi==2024.7.4 +certifi==2024.8.30 # via # -c envs/dev.env.txt # httpcore # httpx # requests -cffi==1.17.0 +cffi==1.17.1 # via # -c envs/dev.env.txt # argon2-cffi-bindings @@ -105,7 +105,7 @@ comm==0.2.2 # -c envs/dev.env.txt # ipykernel # ipywidgets -contourpy==1.2.1 +contourpy==1.3.0 # via # -c envs/dev.env.txt # matplotlib @@ -121,11 +121,11 @@ cycler==0.12.1 # via # -c envs/dev.env.txt # matplotlib -cytoolz==0.12.3 +cytoolz==1.0.0 # via # -c envs/dev.env.txt # quimb -dash==2.17.1 +dash==2.18.1 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -141,7 +141,7 @@ dash-table==5.0.0 # via # -c envs/dev.env.txt # dash -debugpy==1.8.5 +debugpy==1.8.6 # via # -c envs/dev.env.txt # ipykernel @@ -171,7 +171,7 @@ execnet==2.1.1 # via # -c envs/dev.env.txt # pytest-xdist -executing==2.0.1 +executing==2.1.0 # via # -c envs/dev.env.txt # stack-data @@ -179,7 +179,7 @@ fastjsonschema==2.20.0 # via # -c envs/dev.env.txt # nbformat -filelock==3.15.4 +filelock==3.16.1 # via # -c envs/dev.env.txt # -r deps/pytest.txt @@ -187,7 +187,7 @@ flask==3.0.3 # via # -c envs/dev.env.txt # dash -fonttools==4.53.1 +fonttools==4.54.1 # via # -c envs/dev.env.txt # matplotlib @@ -199,6 +199,10 @@ fxpmath==0.4.9 # via # -c envs/dev.env.txt # -r deps/runtime.txt +galois==0.4.2 + # via + # -c envs/dev.env.txt + # -r deps/runtime.txt graphviz==0.20.3 # via # -c envs/dev.env.txt @@ -207,27 +211,27 @@ h11==0.14.0 # via # -c envs/dev.env.txt # httpcore -h5py==3.11.0 +h5py==3.12.1 # via # -c envs/dev.env.txt # openfermion # pyscf -httpcore==1.0.5 +httpcore==1.0.6 # via # -c envs/dev.env.txt # httpx -httpx==0.27.0 +httpx==0.27.2 # via # -c envs/dev.env.txt # jupyterlab -idna==3.7 +idna==3.10 # via # -c envs/dev.env.txt # anyio # httpx # jsonschema # requests -importlib-metadata==8.2.0 +importlib-metadata==8.5.0 # via # -c envs/dev.env.txt # dash @@ -240,13 +244,13 @@ ipykernel==6.29.5 # -c envs/dev.env.txt # -r deps/pytest.txt # jupyterlab -ipython==8.26.0 +ipython==8.28.0 # via # -c envs/dev.env.txt # -r deps/runtime.txt # ipykernel # ipywidgets -ipywidgets==8.1.3 +ipywidgets==8.1.5 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -259,11 +263,11 @@ itsdangerous==2.2.0 # via # -c envs/dev.env.txt # flask -jax==0.4.31 +jax==0.4.34 # via # -c envs/dev.env.txt # openfermion -jaxlib==0.4.31 +jaxlib==0.4.34 # via # -c envs/dev.env.txt # jax @@ -298,7 +302,7 @@ jsonschema-specifications==2023.12.1 # via # -c envs/dev.env.txt # jsonschema -jupyter-client==8.6.2 +jupyter-client==8.6.3 # via # -c envs/dev.env.txt # ipykernel @@ -347,11 +351,11 @@ jupyterlab-server==2.27.3 # -c envs/dev.env.txt # jupyterlab # notebook -jupyterlab-widgets==3.0.11 +jupyterlab-widgets==3.0.13 # via # -c envs/dev.env.txt # ipywidgets -kiwisolver==1.4.5 +kiwisolver==1.4.7 # via # -c envs/dev.env.txt # matplotlib @@ -380,7 +384,7 @@ mistune==3.0.2 # via # -c envs/dev.env.txt # nbconvert -ml-dtypes==0.4.0 +ml-dtypes==0.5.0 # via # -c envs/dev.env.txt # jax @@ -416,7 +420,7 @@ networkx==3.3 # -r deps/runtime.txt # cirq-core # openfermion -notebook==7.2.1 +notebook==7.2.2 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -428,6 +432,7 @@ notebook-shim==0.2.4 numba==0.60.0 # via # -c envs/dev.env.txt + # galois # quimb numpy==1.26.4 # via @@ -437,6 +442,7 @@ numpy==1.26.4 # cirq-core # contourpy # fxpmath + # galois # h5py # jax # jaxlib @@ -444,7 +450,6 @@ numpy==1.26.4 # ml-dtypes # numba # openfermion - # opt-einsum # pandas # pyscf # quimb @@ -453,7 +458,7 @@ openfermion[resources]==1.6.1 # via # -c envs/dev.env.txt # -r deps/pytest.txt -opt-einsum==3.3.0 +opt-einsum==3.4.0 # via # -c envs/dev.env.txt # jax @@ -473,7 +478,7 @@ packaging==24.1 # nbconvert # plotly # pytest -pandas==2.2.2 +pandas==2.2.3 # via # -c envs/dev.env.txt # cirq-core @@ -493,11 +498,11 @@ pillow==10.4.0 # via # -c envs/dev.env.txt # matplotlib -platformdirs==4.2.2 +platformdirs==4.3.6 # via # -c envs/dev.env.txt # jupyter-core -plotly==5.23.0 +plotly==5.24.1 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -506,15 +511,15 @@ pluggy==1.5.0 # via # -c envs/dev.env.txt # pytest -prometheus-client==0.20.0 +prometheus-client==0.21.0 # via # -c envs/dev.env.txt # jupyter-server -prompt-toolkit==3.0.47 +prompt-toolkit==3.0.48 # via # -c envs/dev.env.txt # ipython -protobuf==5.27.3 +protobuf==5.28.2 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -544,16 +549,16 @@ pycparser==2.22 # via # -c envs/dev.env.txt # cffi -pydantic==2.8.2 +pydantic==2.9.2 # via # -c envs/dev.env.txt # bartiq # qref -pydantic-core==2.20.1 +pydantic-core==2.23.4 # via # -c envs/dev.env.txt # pydantic -pydot==3.0.1 +pydot==3.0.2 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -562,24 +567,24 @@ pygments==2.18.0 # -c envs/dev.env.txt # ipython # nbconvert -pyparsing==3.1.2 +pyparsing==3.1.4 # via # -c envs/dev.env.txt # bartiq # matplotlib # pydot -pyscf==2.6.2 +pyscf==2.7.0 # via # -c envs/dev.env.txt # openfermion -pytest==8.3.2 +pytest==8.3.3 # via # -c envs/dev.env.txt # -r deps/pytest.txt # pytest-asyncio # pytest-cov # pytest-xdist -pytest-asyncio==0.23.8 +pytest-asyncio==0.24.0 # via # -c envs/dev.env.txt # -r deps/pytest.txt @@ -602,7 +607,7 @@ python-json-logger==2.0.7 # via # -c envs/dev.env.txt # jupyter-events -pytz==2024.1 +pytz==2024.2 # via # -c envs/dev.env.txt # pandas @@ -610,7 +615,7 @@ pyyaml==6.0.2 # via # -c envs/dev.env.txt # jupyter-events -pyzmq==26.1.0 +pyzmq==26.2.0 # via # -c envs/dev.env.txt # ipykernel @@ -621,11 +626,11 @@ qref==0.7.0 # -c envs/dev.env.txt # -r deps/runtime.txt # bartiq -qsharp==1.7.0 +qsharp==1.9.0 # via # -c envs/dev.env.txt # -r deps/runtime.txt -qsharp-widgets==1.7.0 +qsharp-widgets==1.9.0 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -664,7 +669,7 @@ rpds-py==0.20.0 # -c envs/dev.env.txt # jsonschema # referencing -scipy==1.14.0 +scipy==1.14.1 # via # -c envs/dev.env.txt # ase @@ -723,13 +728,13 @@ tinycss2==1.3.0 # via # -c envs/dev.env.txt # nbconvert -tomli==2.0.1 +tomli==2.0.2 # via # -c envs/dev.env.txt # coverage # jupyterlab # pytest -toolz==0.12.1 +toolz==1.0.0 # via # -c envs/dev.env.txt # cytoolz @@ -763,7 +768,7 @@ traitlets==5.14.3 # nbclient # nbconvert # nbformat -types-python-dateutil==2.9.0.20240316 +types-python-dateutil==2.9.0.20241003 # via # -c envs/dev.env.txt # arrow @@ -776,10 +781,11 @@ typing-extensions==4.12.2 # async-lru # cirq-core # dash + # galois # ipython # pydantic # pydantic-core -tzdata==2024.1 +tzdata==2024.2 # via # -c envs/dev.env.txt # pandas @@ -787,7 +793,7 @@ uri-template==1.3.0 # via # -c envs/dev.env.txt # jsonschema -urllib3==2.2.2 +urllib3==2.2.3 # via # -c envs/dev.env.txt # requests @@ -808,16 +814,16 @@ websocket-client==1.8.0 # via # -c envs/dev.env.txt # jupyter-server -werkzeug==3.0.3 +werkzeug==3.0.4 # via # -c envs/dev.env.txt # dash # flask -widgetsnbextension==4.0.11 +widgetsnbextension==4.0.13 # via # -c envs/dev.env.txt # ipywidgets -zipp==3.20.0 +zipp==3.20.2 # via # -c envs/dev.env.txt # importlib-metadata diff --git a/dev_tools/requirements/envs/runtime.env.txt b/dev_tools/requirements/envs/runtime.env.txt index f3c9b12b5..18d4d020b 100644 --- a/dev_tools/requirements/envs/runtime.env.txt +++ b/dev_tools/requirements/envs/runtime.env.txt @@ -8,7 +8,7 @@ annotated-types==0.7.0 # via # -c envs/dev.env.txt # pydantic -anyio==4.4.0 +anyio==4.6.0 # via # -c envs/dev.env.txt # httpx @@ -53,7 +53,7 @@ babel==2.16.0 # via # -c envs/dev.env.txt # jupyterlab-server -bartiq==0.5.1 +bartiq==0.6.0 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -69,17 +69,17 @@ blinker==1.8.2 # via # -c envs/dev.env.txt # flask -cachetools==5.4.0 +cachetools==5.5.0 # via # -c envs/dev.env.txt # -r deps/runtime.txt -certifi==2024.7.4 +certifi==2024.8.30 # via # -c envs/dev.env.txt # httpcore # httpx # requests -cffi==1.17.0 +cffi==1.17.1 # via # -c envs/dev.env.txt # argon2-cffi-bindings @@ -100,7 +100,7 @@ comm==0.2.2 # -c envs/dev.env.txt # ipykernel # ipywidgets -contourpy==1.2.1 +contourpy==1.3.0 # via # -c envs/dev.env.txt # matplotlib @@ -112,11 +112,11 @@ cycler==0.12.1 # via # -c envs/dev.env.txt # matplotlib -cytoolz==0.12.3 +cytoolz==1.0.0 # via # -c envs/dev.env.txt # quimb -dash==2.17.1 +dash==2.18.1 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -132,7 +132,7 @@ dash-table==5.0.0 # via # -c envs/dev.env.txt # dash -debugpy==1.8.5 +debugpy==1.8.6 # via # -c envs/dev.env.txt # ipykernel @@ -153,7 +153,7 @@ exceptiongroup==1.2.2 # -c envs/dev.env.txt # anyio # ipython -executing==2.0.1 +executing==2.1.0 # via # -c envs/dev.env.txt # stack-data @@ -165,7 +165,7 @@ flask==3.0.3 # via # -c envs/dev.env.txt # dash -fonttools==4.53.1 +fonttools==4.54.1 # via # -c envs/dev.env.txt # matplotlib @@ -177,6 +177,10 @@ fxpmath==0.4.9 # via # -c envs/dev.env.txt # -r deps/runtime.txt +galois==0.4.2 + # via + # -c envs/dev.env.txt + # -r deps/runtime.txt graphviz==0.20.3 # via # -c envs/dev.env.txt @@ -185,22 +189,22 @@ h11==0.14.0 # via # -c envs/dev.env.txt # httpcore -httpcore==1.0.5 +httpcore==1.0.6 # via # -c envs/dev.env.txt # httpx -httpx==0.27.0 +httpx==0.27.2 # via # -c envs/dev.env.txt # jupyterlab -idna==3.7 +idna==3.10 # via # -c envs/dev.env.txt # anyio # httpx # jsonschema # requests -importlib-metadata==8.2.0 +importlib-metadata==8.5.0 # via # -c envs/dev.env.txt # dash @@ -208,13 +212,13 @@ ipykernel==6.29.5 # via # -c envs/dev.env.txt # jupyterlab -ipython==8.26.0 +ipython==8.28.0 # via # -c envs/dev.env.txt # -r deps/runtime.txt # ipykernel # ipywidgets -ipywidgets==8.1.3 +ipywidgets==8.1.5 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -257,7 +261,7 @@ jsonschema-specifications==2023.12.1 # via # -c envs/dev.env.txt # jsonschema -jupyter-client==8.6.2 +jupyter-client==8.6.3 # via # -c envs/dev.env.txt # ipykernel @@ -306,11 +310,11 @@ jupyterlab-server==2.27.3 # -c envs/dev.env.txt # jupyterlab # notebook -jupyterlab-widgets==3.0.11 +jupyterlab-widgets==3.0.13 # via # -c envs/dev.env.txt # ipywidgets -kiwisolver==1.4.5 +kiwisolver==1.4.7 # via # -c envs/dev.env.txt # matplotlib @@ -368,7 +372,7 @@ networkx==3.3 # -c envs/dev.env.txt # -r deps/runtime.txt # cirq-core -notebook==7.2.1 +notebook==7.2.2 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -380,6 +384,7 @@ notebook-shim==0.2.4 numba==0.60.0 # via # -c envs/dev.env.txt + # galois # quimb numpy==1.26.4 # via @@ -388,6 +393,7 @@ numpy==1.26.4 # cirq-core # contourpy # fxpmath + # galois # matplotlib # numba # pandas @@ -407,7 +413,7 @@ packaging==24.1 # matplotlib # nbconvert # plotly -pandas==2.2.2 +pandas==2.2.3 # via # -c envs/dev.env.txt # cirq-core @@ -427,24 +433,24 @@ pillow==10.4.0 # via # -c envs/dev.env.txt # matplotlib -platformdirs==4.2.2 +platformdirs==4.3.6 # via # -c envs/dev.env.txt # jupyter-core -plotly==5.23.0 +plotly==5.24.1 # via # -c envs/dev.env.txt # -r deps/runtime.txt # dash -prometheus-client==0.20.0 +prometheus-client==0.21.0 # via # -c envs/dev.env.txt # jupyter-server -prompt-toolkit==3.0.47 +prompt-toolkit==3.0.48 # via # -c envs/dev.env.txt # ipython -protobuf==5.27.3 +protobuf==5.28.2 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -470,16 +476,16 @@ pycparser==2.22 # via # -c envs/dev.env.txt # cffi -pydantic==2.8.2 +pydantic==2.9.2 # via # -c envs/dev.env.txt # bartiq # qref -pydantic-core==2.20.1 +pydantic-core==2.23.4 # via # -c envs/dev.env.txt # pydantic -pydot==3.0.1 +pydot==3.0.2 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -488,7 +494,7 @@ pygments==2.18.0 # -c envs/dev.env.txt # ipython # nbconvert -pyparsing==3.1.2 +pyparsing==3.1.4 # via # -c envs/dev.env.txt # bartiq @@ -505,7 +511,7 @@ python-json-logger==2.0.7 # via # -c envs/dev.env.txt # jupyter-events -pytz==2024.1 +pytz==2024.2 # via # -c envs/dev.env.txt # pandas @@ -513,7 +519,7 @@ pyyaml==6.0.2 # via # -c envs/dev.env.txt # jupyter-events -pyzmq==26.1.0 +pyzmq==26.2.0 # via # -c envs/dev.env.txt # ipykernel @@ -524,11 +530,11 @@ qref==0.7.0 # -c envs/dev.env.txt # -r deps/runtime.txt # bartiq -qsharp==1.7.0 +qsharp==1.9.0 # via # -c envs/dev.env.txt # -r deps/runtime.txt -qsharp-widgets==1.7.0 +qsharp-widgets==1.9.0 # via # -c envs/dev.env.txt # -r deps/runtime.txt @@ -566,7 +572,7 @@ rpds-py==0.20.0 # -c envs/dev.env.txt # jsonschema # referencing -scipy==1.14.0 +scipy==1.14.1 # via # -c envs/dev.env.txt # cirq-core @@ -619,11 +625,11 @@ tinycss2==1.3.0 # via # -c envs/dev.env.txt # nbconvert -tomli==2.0.1 +tomli==2.0.2 # via # -c envs/dev.env.txt # jupyterlab -toolz==0.12.1 +toolz==1.0.0 # via # -c envs/dev.env.txt # cytoolz @@ -657,7 +663,7 @@ traitlets==5.14.3 # nbclient # nbconvert # nbformat -types-python-dateutil==2.9.0.20240316 +types-python-dateutil==2.9.0.20241003 # via # -c envs/dev.env.txt # arrow @@ -670,10 +676,11 @@ typing-extensions==4.12.2 # async-lru # cirq-core # dash + # galois # ipython # pydantic # pydantic-core -tzdata==2024.1 +tzdata==2024.2 # via # -c envs/dev.env.txt # pandas @@ -681,7 +688,7 @@ uri-template==1.3.0 # via # -c envs/dev.env.txt # jsonschema -urllib3==2.2.2 +urllib3==2.2.3 # via # -c envs/dev.env.txt # requests @@ -702,16 +709,16 @@ websocket-client==1.8.0 # via # -c envs/dev.env.txt # jupyter-server -werkzeug==3.0.3 +werkzeug==3.0.4 # via # -c envs/dev.env.txt # dash # flask -widgetsnbextension==4.0.11 +widgetsnbextension==4.0.13 # via # -c envs/dev.env.txt # ipywidgets -zipp==3.20.0 +zipp==3.20.2 # via # -c envs/dev.env.txt # importlib-metadata diff --git a/dev_tools/tensor-report-card.ipynb b/dev_tools/tensor-report-card.ipynb new file mode 100644 index 000000000..49d08b1f9 --- /dev/null +++ b/dev_tools/tensor-report-card.ipynb @@ -0,0 +1,262 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "9a9335f1", + "metadata": {}, + "source": [ + "# Tensor Report Card\n", + "\n", + "Not all bloq examples support tensor simulation. This report card automatically determines which bloq examples should be tensor simulable.\n", + "\n", + " - State vector simulation uses $2^n$ numbers to simulate a quantum state. The tensor protocol uses quimb to try to find more efficient contraction orderings. Quimb reports the contraction width, which is the minimum size of any intermediate tensor encountered in the contraciton. The simulation uses $2^w$ numbers, where $w$ is the width. We consider a width under 25 qubits as simulable.\n", + " - Qualtran requires \"flattening\" out the bloq to turn it into an efficient tensor network. This may take too much time itself for large algorithms with many levels of abstraction. If the process of turning a bloq into a quimb tensor network and finding a contraction ordering takes longer than 8 seconds, we don't consider the bloq simulable.\n", + " - The flattened structure needs to have explicit tensors. For bloqs with symbolic parameters, we either can't decompose & flatten them, or the tensors would be symbolic, which we don't support." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4d2bf2bc-1b55-4f68-b0f4-68f62dd68bae", + "metadata": {}, + "outputs": [], + "source": [ + "from qualtran_dev_tools.bloq_finder import get_bloq_examples\n", + "from qualtran_dev_tools.tensor_report_card import report_on_tensors, ExecuteWithTimeout" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "86168342-d740-446a-bc88-06eaec8ae3a8", + "metadata": {}, + "outputs": [], + "source": [ + "bes = get_bloq_examples()\n", + "\n", + "# Imports to exclude certain bloqs, see following comment\n", + "from qualtran.bloqs.multiplexers.apply_gate_to_lth_target import ApplyGateToLthQubit" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "51781740-6417-4b3b-b5d8-86f51340a016", + "metadata": {}, + "outputs": [], + "source": [ + "exec = ExecuteWithTimeout(timeout=8., max_workers=4)\n", + "for i, be in enumerate(bes):\n", + "\n", + " if be.bloq_cls == ApplyGateToLthQubit:\n", + " # This bloq uses a lambda function as one of its attributes, which\n", + " # can't be pickled and used with multiprocessing.\n", + " continue\n", + " \n", + " exec.submit(report_on_tensors, kwargs=dict(name=be.name, cls_name=be.bloq_cls.__name__, bloq=be.make()))\n", + "\n", + "records = []\n", + "while exec.work_to_be_done:\n", + " kwargs, record = exec.next_result()\n", + " #print(kwargs['name'], end=' ', flush=True)\n", + " print('\\r', f'{exec.work_to_be_done:5d} remaining', end='', flush=True)\n", + " \n", + " if record is None:\n", + " records.append({\n", + " 'name': kwargs['name'],\n", + " 'cls': kwargs['cls_name'],\n", + " 'err': 'Timeout',\n", + " })\n", + " else:\n", + " records.append(record)\n", + "\n", + "import pandas as pd\n", + "df = pd.DataFrame(records)" + ] + }, + { + "cell_type": "markdown", + "id": "393a5aad-7a78-4167-8dd5-407d8a0b3241", + "metadata": {}, + "source": [ + "## Number of bloq examples considered" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5d674962-27d7-40d1-9916-f0aba3457934", + "metadata": {}, + "outputs": [], + "source": [ + "print(len(df))" + ] + }, + { + "cell_type": "markdown", + "id": "6a59804e-afc6-4227-b74f-cbf82f14b773", + "metadata": {}, + "source": [ + "## Number of bloq examples successfully flattened" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8b048464-083a-4e04-8135-176f449450ca", + "metadata": {}, + "outputs": [], + "source": [ + "print(len(df[df['flat_dur'] > 0]))" + ] + }, + { + "cell_type": "markdown", + "id": "a4194f99-1551-4a5e-b93d-fb043cc56f9e", + "metadata": {}, + "source": [ + "## Number of bloq examples with tensors" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5cdfa4e3-e1db-4869-832c-63695a3fd3f4", + "metadata": {}, + "outputs": [], + "source": [ + "print(len(df[df['width'] > 0]))" + ] + }, + { + "cell_type": "markdown", + "id": "05bffa4b-6bbe-4901-b1de-aba1abebe552", + "metadata": {}, + "source": [ + "## Bloqs that are tensor simulable" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "578fd2b5-9f7e-4edc-81b2-a8aad989be68", + "metadata": {}, + "outputs": [], + "source": [ + "print(len(df[df['width'] <= 25]))\n", + "df[df['width'] <= 25]" + ] + }, + { + "cell_type": "markdown", + "id": "4ef49322-cffb-4ff9-8cb1-6e5a87749492", + "metadata": {}, + "source": [ + "## Bloqs whose tensor network is too big" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d529d144-b3c5-45e5-ae14-f6d3fb86f153", + "metadata": {}, + "outputs": [], + "source": [ + "df[df['width'] > 25].sort_values(by='width')" + ] + }, + { + "cell_type": "markdown", + "id": "41141f6f-4377-4cee-9ca7-d2981c94d2e4", + "metadata": {}, + "source": [ + "## Bloqs without tensors\n", + "\n", + "Due to errors encountered in flattening or if the bloq's callees don't support tensor simulation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c3386091-491a-404f-b13d-72b401d2c267", + "metadata": {}, + "outputs": [], + "source": [ + "df[df['width'].isna()]" + ] + }, + { + "cell_type": "markdown", + "id": "46a79e7f-0715-4007-8c11-d959f8f2255b", + "metadata": {}, + "source": [ + "## Slowest to flatten\n", + "\n", + "Within the overall timeout" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "df522d3f-4392-46ea-851e-321d0fdfd52f", + "metadata": {}, + "outputs": [], + "source": [ + "df.sort_values(by='flat_dur', ascending=False).head()" + ] + }, + { + "cell_type": "markdown", + "id": "48f419ef-2723-42ea-8670-81a729164cf7", + "metadata": {}, + "source": [ + "## Flattening is the rate-limiting step.\n", + "\n", + "For bloqs that have been successfully flattened, the maximum tensor-network-construction and tensor-contraction-ordering durations are less than 0.5s. Note: the contraction finding code uses the fast, naive approach. One can choose more expensive approaches where the contraciton-ordering-finding is more expensive." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eae32349-ad93-4072-9704-044ef390e59e", + "metadata": {}, + "outputs": [], + "source": [ + "# Slowest tn_dur\n", + "df.sort_values(by='tn_dur', ascending=False).head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9249f813-1d42-47f4-bd1a-f198b79298f6", + "metadata": {}, + "outputs": [], + "source": [ + "# Slowest width_dur\n", + "df.sort_values(by='width_dur', ascending=False).head()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/dev_tools/ui-export.ipynb b/dev_tools/ui-export.ipynb new file mode 100644 index 000000000..a1b2c867a --- /dev/null +++ b/dev_tools/ui-export.ipynb @@ -0,0 +1,233 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from qualtran.bloqs.factoring.mod_exp import ModExp\n", + "from qualtran.drawing import get_musical_score_data, draw_musical_score\n", + "\n", + "modexp_small = ModExp(base=3, mod=16, exp_bitsize=3, x_bitsize=2048)\n", + "ms = get_musical_score_data(modexp_small.decompose_bloq())\n", + "\n", + "draw_musical_score(ms)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from sympy import Symbol\n", + "from typing import Any\n", + "\n", + "from qualtran.drawing.musical_score import MusicalScoreEncoder\n", + "from qualtran._infra.registers import Side\n", + "\n", + "class BloqEncoder(MusicalScoreEncoder):\n", + "\n", + " def default(self, o: Any) -> Any:\n", + " if isinstance(o, (Symbol)):\n", + " return f'Symbol({o})'\n", + " if isinstance(o, complex):\n", + " return f'{o.real}+{o.imag}i'\n", + " if isinstance(o, Side):\n", + " return 'Side.LEFT' if o == Side.LEFT else 'Side.RIGHT' if o == Side.RIGHT else 'Side.THRU'\n", + "\n", + " try:\n", + " return super().default(o)\n", + " except:\n", + " return 'NOT_SERIALIZABLE'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import attrs\n", + "import hashlib\n", + "import json\n", + "\n", + "from qualtran import Adjoint, CompositeBloq, Controlled\n", + "from qualtran.bloqs.rotations.programmable_rotation_gate_array import ProgrammableRotationGateArray\n", + "\n", + "def bloq_attrs(bloq):\n", + " if isinstance(bloq, CompositeBloq):\n", + " return {}\n", + " if isinstance(bloq, ProgrammableRotationGateArray):\n", + " return {}\n", + " if isinstance(bloq, (Adjoint, Controlled)):\n", + " return bloq_attrs(bloq.subbloq)\n", + "\n", + " return attrs.asdict(bloq)\n", + "\n", + "def bloq_filename(bloq):\n", + " attrs_dict = bloq_attrs(bloq)\n", + " attrs_keys = list(attrs_dict.keys())\n", + " attrs_keys.sort()\n", + "\n", + " prefix = ''\n", + " if isinstance(bloq, Adjoint):\n", + " prefix = 'Adjoint_'\n", + " if isinstance(bloq, Controlled):\n", + " prefix = 'Controlled_'\n", + "\n", + " attrs_list = [\n", + " [key, attrs_dict[key]]\n", + " for key in attrs_keys\n", + " ] if attrs_keys else []\n", + " unhashed = json.dumps(attrs_list, cls=BloqEncoder)\n", + "\n", + " return prefix + hashlib.md5(unhashed.encode(), usedforsecurity=False).hexdigest() + '.json'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "from pathlib import Path\n", + "\n", + "from qualtran_dev_tools.notebook_specs import NB_BY_SECTION\n", + "\n", + "docs_by_section = {\n", + " section[0]: {\n", + " notebook_spec.title: list(\n", + " {\n", + " 'name': bloq_spec.bloq_cls.__name__,\n", + " 'examples': list(\n", + " {\n", + " 'name': example.name,\n", + " 'filename': bloq_filename(example.make())\n", + " }\n", + " for example in bloq_spec.examples\n", + " )\n", + " }\n", + " for bloq_spec in notebook_spec.bloq_specs\n", + " )\n", + " for notebook_spec in section[1]\n", + " }\n", + " for section in NB_BY_SECTION\n", + "}\n", + "\n", + "Path('ui_export').mkdir(parents=True, exist_ok=True)\n", + "\n", + "with open('ui_export/navigation.json', 'w') as f:\n", + " json.dump(docs_by_section, f, indent=2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from pathlib import Path\n", + "\n", + "from qualtran_dev_tools.notebook_specs import NB_BY_SECTION\n", + "from qualtran_dev_tools.parse_docstrings import get_markdown_docstring\n", + "\n", + "for section in NB_BY_SECTION:\n", + " for notebook_spec in section[1]:\n", + " for bloq_spec in notebook_spec.bloq_specs:\n", + " Path(f'ui_export/{bloq_spec.bloq_cls.__name__}').mkdir(parents=True, exist_ok=True)\n", + "\n", + " doc_name = f'ui_export/{bloq_spec.bloq_cls.__name__}/docs.txt'\n", + " if not os.path.isfile(doc_name):\n", + " with open(doc_name, 'w') as doc_file:\n", + " doc_file.write('\\n'.join(get_markdown_docstring(bloq_spec.bloq_cls)))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "import os\n", + "from pathlib import Path\n", + "\n", + "from qualtran_dev_tools.all_call_graph import get_all_call_graph\n", + "from qualtran_dev_tools.notebook_specs import NB_BY_SECTION\n", + "from qualtran import Adjoint, Controlled\n", + "from qualtran.drawing.musical_score import get_musical_score_data\n", + "\n", + "examples = [\n", + " example\n", + " for section in NB_BY_SECTION\n", + " for notebook_spec in section[1]\n", + " for bloq_spec in notebook_spec.bloq_specs\n", + " for example in bloq_spec.examples\n", + "]\n", + "\n", + "call_graph = get_all_call_graph(examples)\n", + "\n", + "def bloq_score(bloq):\n", + " try:\n", + " return get_musical_score_data(bloq.decompose_bloq())\n", + " except:\n", + " return None\n", + "\n", + "def bloq_name(bloq):\n", + " if (isinstance(bloq, (Adjoint, Controlled))):\n", + " return bloq_name(bloq.subbloq)\n", + "\n", + " return bloq.__class__.__name__\n", + "\n", + "def write_example(bloq):\n", + " file_name = f'ui_export/{bloq_name(bloq)}/{bloq_filename(bloq)}'\n", + " if not os.path.isfile(file_name):\n", + " bloq_dict = {\n", + " 'name': str(bloq),\n", + " 'attrs': bloq_attrs(bloq),\n", + " 'msd': bloq_score(bloq),\n", + " 'callees': list(\n", + " {\n", + " 'name': bloq_name(child_bloq),\n", + " 'filename': bloq_filename(child_bloq)\n", + " }\n", + " for child_bloq in call_graph.neighbors(bloq)\n", + " )\n", + " }\n", + "\n", + " Path(f'ui_export/{bloq_name(bloq)}').mkdir(parents=True, exist_ok=True)\n", + "\n", + " with open(file_name, 'w') as f:\n", + " json.dump(bloq_dict, f, indent=2, cls=BloqEncoder)\n", + "\n", + "for bloq, _ in call_graph.nodes.items():\n", + " write_example(bloq)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "qualtran", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/bloqs/index.rst b/docs/bloqs/index.rst index 34df92ddb..d827dfaad 100644 --- a/docs/bloqs/index.rst +++ b/docs/bloqs/index.rst @@ -83,10 +83,21 @@ Bloqs Library mod_arithmetic/mod_addition.ipynb mod_arithmetic/mod_subtraction.ipynb mod_arithmetic/mod_multiplication.ipynb - factoring/mod_exp.ipynb + mod_arithmetic/mod_division.ipynb + factoring/rsa/rsa.ipynb factoring/ecc/ec_add.ipynb factoring/ecc/ecc.ipynb +.. toctree:: + :maxdepth: 2 + :caption: GF Arithmetic: + + gf_arithmetic/gf2_multiplication.ipynb + gf_arithmetic/gf2_addition.ipynb + gf_arithmetic/gf2_add_k.ipynb + gf_arithmetic/gf2_square.ipynb + gf_arithmetic/gf2_inverse.ipynb + .. toctree:: :maxdepth: 2 :caption: Rotations: @@ -119,6 +130,7 @@ Bloqs Library block_encoding/phase.ipynb block_encoding/linear_combination.ipynb block_encoding/sparse_matrix.ipynb + block_encoding/sparse_matrix_hermitian.ipynb block_encoding/chebyshev_polynomial.ipynb block_encoding/lcu_block_encoding.ipynb diff --git a/docs/reference/index.rst b/docs/reference/index.rst index a6bbcd0b9..ab83f0f8e 100644 --- a/docs/reference/index.rst +++ b/docs/reference/index.rst @@ -7,7 +7,7 @@ This section of the docs provides an API reference for all symbols in ``qualtran``. .. toctree:: - :hidden: + :maxdepth: 1 qualtran.md diff --git a/pyproject.toml b/pyproject.toml index f6ec0b606..7b1548955 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.black] line-length = 100 -target_version = ['py39', 'py310'] +target_version = ['py39', 'py310', 'py311', 'py312'] skip-string-normalization = true skip-magic-trailing-comma = true exclude = "qualtran/protos/*" diff --git a/qualtran/Autodoc.ipynb b/qualtran/Autodoc.ipynb index 3e3a510a3..b70bf764b 100644 --- a/qualtran/Autodoc.ipynb +++ b/qualtran/Autodoc.ipynb @@ -61,7 +61,7 @@ "\n", "### References\n", "\n", - "We provide custom support for a \"References\" section where you should reference the source(s) of the construction. References are newline seperated. They must start with a markdown-style link of `[title](url)`. This can optionally be followed by a period and any additional information in standard markdown format. To balance structure vs. readability, reference links should be kept to a single line and need not respect the 100-character line limit.\n", + "We provide custom support for a \"References\" section where you should reference the source(s) of the construction. References are newline seperated. They must start with a markdown-style link of `[title](url)`. This can optionally be followed by a period and any additional information in standard markdown format. To balance structure vs. readability, reference links should be kept to a single line and need not respect the 100-character line limit. Canonically, the next line should include one or two author last names, the first year of publication, and specific figures or equations relevant to the bloq. Prefer arxiv links. Link to the abstract page; not directly to the pdf.\n", "\n", "```python\n", "class QROM(Bloq):\n", @@ -69,10 +69,10 @@ "\n", " References:\n", " [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662).\n", - " Babbush et. al. (2018). Figure 1.\n", + " Babbush et al. (2018). Figure 1.\n", "\n", " [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization](https://arxiv.org/abs/2007.07391).\n", - " Babbush et. al. (2020). Figure 3.\n", + " Babbush et al. (2020). Figure 3.\n", " \"\"\"\n", "```" ] @@ -137,7 +137,7 @@ "\n", "This code should be located in the module defining the bloq class.\n", "\n", - "Each bloq class has one `BloqDocSpec` which may have multiple bloq examples. Each jupyter notebook roughly corresponds to one module or package and can document multiple bloq classes in it, so the final step is to plumb through the `BloqDocSpec` into a `NotebookSpec`. In `dev_tools/autogenerate-bloqs-notebooks-v2.py`, you can add a new `NotebookSpecV2` to the big list or add your `BloqDocSpec` to an existing one. If you execute this script, it will generate a new notebook or new cells in an existing notebook with documentation for your bloq. You may need to manually `git add` the new notebook.\n", + "Each bloq class has one `BloqDocSpec` which may have multiple bloq examples. Each jupyter notebook roughly corresponds to one module or package and can document multiple bloq classes in it, so the final step is to plumb through the `BloqDocSpec` into a `NotebookSpec`. In `dev_tools/qualtran_dev_tools/notebook_specs.py`, you can add a new `NotebookSpecV2` to the big list or add your `BloqDocSpec` to an existing one. If you execute this script, it will generate a new notebook or new cells in an existing notebook with documentation for your bloq. You may need to manually `git add` the new notebook.\n", "\n", "\n", "```python\n", @@ -166,7 +166,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.7" + "version": "3.11.8" } }, "nbformat": 4, diff --git a/qualtran/__init__.py b/qualtran/__init__.py index 6f04267a3..745842a5b 100644 --- a/qualtran/__init__.py +++ b/qualtran/__init__.py @@ -11,10 +11,17 @@ # 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. +# isort:skip_file -"""qualtran +"""The top-level Qualtran module. -isort:skip_file +Many fundamental objects for expressing quantum programs can be imported from this +top-level namespace like `qualtran.Bloq`, `qualtran.Register`, and the various quantum +data types (`qualtran.QBit`, `qualtran.QUInt`, ...). + +The standard library of quantum algorithms must be imported from the `qualtran.bloqs` submodule. +A variety of analysis protocols are available in submodules as well like +`qualtran.resource_counting`, `qualtran.drawing`, `qualtran.simulation`, and others. """ # -------------------------------------------------------------------------------------------------- @@ -53,6 +60,7 @@ QUInt, BQUInt, QMontgomeryUInt, + QGF, ) # Internal imports: none diff --git a/qualtran/_infra/Bloqs-Tutorial.ipynb b/qualtran/_infra/Bloqs-Tutorial.ipynb index 219dc48f4..ca64c4755 100644 --- a/qualtran/_infra/Bloqs-Tutorial.ipynb +++ b/qualtran/_infra/Bloqs-Tutorial.ipynb @@ -914,7 +914,7 @@ "metadata": {}, "outputs": [], "source": [ - "from qualtran.bloqs.factoring import ModExp\n", + "from qualtran.bloqs.factoring.rsa import ModExp\n", "\n", "mod_exp = ModExp(base=8, mod=13*17, exp_bitsize=3, x_bitsize=1024)\n", "show_bloq(mod_exp)" diff --git a/qualtran/_infra/adjoint.py b/qualtran/_infra/adjoint.py index 4fa14ff4e..0601a6248 100644 --- a/qualtran/_infra/adjoint.py +++ b/qualtran/_infra/adjoint.py @@ -16,7 +16,6 @@ from functools import cached_property from typing import Dict, List, Optional, Tuple, TYPE_CHECKING -import cirq from attrs import frozen from .composite_bloq import _binst_to_cxns, _cxns_to_soq_dict, _map_soqs, _reg_to_soq, BloqBuilder @@ -25,6 +24,8 @@ from .registers import Signature if TYPE_CHECKING: + import cirq + from qualtran import Bloq, CompositeBloq, Register, Signature, SoquetT from qualtran.drawing import WireSymbol from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator @@ -144,7 +145,9 @@ def decompose_bloq(self) -> 'CompositeBloq': def _circuit_diagram_info_( self, args: 'cirq.CircuitDiagramInfoArgs' - ) -> cirq.CircuitDiagramInfo: + ) -> 'cirq.CircuitDiagramInfo': + import cirq + sub_info = cirq.circuit_diagram_info(self.subbloq, args, default=NotImplemented) if sub_info is NotImplemented: return NotImplemented @@ -181,21 +184,3 @@ def wire_symbol( return self.subbloq.wire_symbol(reg=None).adjoint() return self.subbloq.wire_symbol(reg=reg.adjoint(), idx=idx).adjoint() - - def _t_complexity_(self): - """The cirq-style `_t_complexity_` delegates to the subbloq's method with a special shim. - - The cirq-style t complexity protocol does not leverage the heirarchical decomposition - of high-level bloqs, so we need to shim in an extra `adjoint` boolean flag. - """ - # TODO: https://github.com/quantumlib/Qualtran/issues/735 - if not hasattr(self.subbloq, '_t_complexity_'): - return NotImplemented - - try: - return self.subbloq._t_complexity_(adjoint=True) # type: ignore[call-arg] - except TypeError as e: - if 'adjoint' in str(e): - return self.subbloq._t_complexity_() - else: - raise e diff --git a/qualtran/_infra/bloq.py b/qualtran/_infra/bloq.py index 880224f38..ec5e2d735 100644 --- a/qualtran/_infra/bloq.py +++ b/qualtran/_infra/bloq.py @@ -394,7 +394,12 @@ def _my_add_controlled( add_controlled: A function with the signature documented above that the system can use to automatically wire up the new control registers. """ - from qualtran import Controlled + from qualtran import Controlled, CtrlSpec + from qualtran.bloqs.mcmt.controlled_via_and import ControlledViaAnd + + if ctrl_spec != CtrlSpec(): + # reduce controls to a single qubit + return ControlledViaAnd.make_ctrl_system(self, ctrl_spec=ctrl_spec) return Controlled.make_ctrl_system(self, ctrl_spec=ctrl_spec) @@ -432,9 +437,6 @@ def t_complexity(self) -> 'TComplexity': return t_complexity(self) - def _t_complexity_(self) -> 'TComplexity': - return NotImplemented - def as_cirq_op( self, qubit_manager: 'cirq.QubitManager', **cirq_quregs: 'CirqQuregT' ) -> Tuple[Union['cirq.Operation', None], Dict[str, 'CirqQuregT']]: diff --git a/qualtran/_infra/bloq_example.py b/qualtran/_infra/bloq_example.py index 23484ed41..8153ea6fb 100644 --- a/qualtran/_infra/bloq_example.py +++ b/qualtran/_infra/bloq_example.py @@ -78,15 +78,13 @@ def _bloq_cls_from_func_annotation(func: Callable[[], _BloqType]) -> Type[_BloqT @typing.overload -def bloq_example(_func: Callable[[], _BloqType], **kwargs: Any) -> BloqExample[_BloqType]: - ... +def bloq_example(_func: Callable[[], _BloqType], **kwargs: Any) -> BloqExample[_BloqType]: ... @typing.overload def bloq_example( _func: None = None, *, generalizer: _GeneralizerType = lambda x: x -) -> Callable[[Callable[[], _BloqType]], BloqExample[_BloqType]]: - ... +) -> Callable[[Callable[[], _BloqType]], BloqExample[_BloqType]]: ... def bloq_example( diff --git a/qualtran/_infra/composite_bloq.py b/qualtran/_infra/composite_bloq.py index f55ebb282..8ad8b50c4 100644 --- a/qualtran/_infra/composite_bloq.py +++ b/qualtran/_infra/composite_bloq.py @@ -880,12 +880,10 @@ def add_register_from_dtype( return None @overload - def add_register(self, reg: Register, bitsize: None = None) -> Union[None, SoquetT]: - ... + def add_register(self, reg: Register, bitsize: None = None) -> Union[None, SoquetT]: ... @overload - def add_register(self, reg: str, bitsize: int) -> SoquetT: - ... + def add_register(self, reg: str, bitsize: int) -> SoquetT: ... def add_register( self, reg: Union[str, Register], bitsize: Optional[int] = None diff --git a/qualtran/_infra/composite_bloq_test.py b/qualtran/_infra/composite_bloq_test.py index 58972c9d9..ffe39213c 100644 --- a/qualtran/_infra/composite_bloq_test.py +++ b/qualtran/_infra/composite_bloq_test.py @@ -16,7 +16,6 @@ from typing import Dict, List, Tuple import attrs -import cirq import networkx as nx import numpy as np import pytest @@ -164,7 +163,8 @@ def test_map_soqs(): assert isinstance(cbloq, CompositeBloq) -def test_bb_composite_bloq(): +def test_to_from_cirq_circuit(): + cirq = pytest.importorskip('cirq') cbloq_auto = TestTwoCNOT().decompose_bloq() circuit, _ = cbloq_auto.to_cirq_circuit_and_quregs( q1=[cirq.LineQubit(1)], q2=[cirq.LineQubit(2)] @@ -348,6 +348,7 @@ def build_composite_bloq( def test_complicated_target_register(): + cirq = pytest.importorskip('cirq') bloq = TestMultiCNOT() cbloq = qlt_testing.assert_valid_bloq_decomposition(bloq) assert len(cbloq.bloq_instances) == 2 * 3 diff --git a/qualtran/_infra/controlled.py b/qualtran/_infra/controlled.py index 2516141e7..bcfc1ee6f 100644 --- a/qualtran/_infra/controlled.py +++ b/qualtran/_infra/controlled.py @@ -23,20 +23,22 @@ Sequence, Tuple, TYPE_CHECKING, + TypeAlias, Union, ) import attrs -import cirq import numpy as np from numpy.typing import NDArray +from ..symbolics import is_symbolic, prod, Shaped, SymbolicInt from .bloq import Bloq, DecomposeNotImplementedError, DecomposeTypeError from .data_types import QBit, QDType from .gate_with_registers import GateWithRegisters from .registers import Register, Side, Signature if TYPE_CHECKING: + import cirq import quimb.tensor as qtn from qualtran import Bloq, BloqBuilder, CompositeBloq, ConnectionT, SoquetT @@ -45,24 +47,30 @@ from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator from qualtran.simulation.classical_sim import ClassicalValT +ControlBit: TypeAlias = int +"""A control bit, either 0 or 1.""" + def _cvs_convert( cvs: Union[ int, np.integer, NDArray[np.integer], + Shaped, Sequence[Union[int, np.integer]], Sequence[Sequence[Union[int, np.integer]]], - Sequence[NDArray[np.integer]], + Sequence[Union[NDArray[np.integer], Shaped]], ] -) -> Tuple[NDArray[np.integer], ...]: +) -> Tuple[Union[NDArray[np.integer], Shaped], ...]: + if isinstance(cvs, Shaped): + return (cvs,) if isinstance(cvs, (int, np.integer)): return (np.array(cvs),) if isinstance(cvs, np.ndarray): return (cvs,) if all(isinstance(cv, (int, np.integer)) for cv in cvs): return (np.asarray(cvs),) - return tuple(np.asarray(cv) for cv in cvs) + return tuple(cv if isinstance(cv, Shaped) else np.asarray(cv) for cv in cvs) @attrs.frozen(eq=False) @@ -111,7 +119,9 @@ class CtrlSpec: qdtypes: Tuple[QDType, ...] = attrs.field( default=QBit(), converter=lambda qt: (qt,) if isinstance(qt, QDType) else tuple(qt) ) - cvs: Tuple[NDArray[np.integer], ...] = attrs.field(default=1, converter=_cvs_convert) + cvs: Tuple[Union[NDArray[np.integer], Shaped], ...] = attrs.field( + default=1, converter=_cvs_convert + ) def __attrs_post_init__(self): assert len(self.qdtypes) == len(self.cvs) @@ -121,19 +131,29 @@ def num_ctrl_reg(self) -> int: return len(self.qdtypes) @cached_property - def shapes(self) -> Tuple[Tuple[int, ...], ...]: + def shapes(self) -> Tuple[Tuple[SymbolicInt, ...], ...]: """Tuple of shapes of control registers represented by this CtrlSpec.""" return tuple(cv.shape for cv in self.cvs) @cached_property - def num_qubits(self) -> int: + def concrete_shapes(self) -> tuple[tuple[int, ...], ...]: + """Tuple of shapes of control registers represented by this CtrlSpec.""" + shapes = self.shapes + if is_symbolic(*shapes): + raise ValueError(f"cannot get concrete shapes: found symbolic {self.shapes}") + return shapes # type: ignore + + @cached_property + def num_qubits(self) -> SymbolicInt: """Total number of qubits required for control registers represented by this CtrlSpec.""" return sum( - dtype.num_qubits * int(np.prod(shape)) - for dtype, shape in zip(self.qdtypes, self.shapes) + dtype.num_qubits * prod(shape) for dtype, shape in zip(self.qdtypes, self.shapes) ) - def activation_function_dtypes(self) -> Sequence[Tuple[QDType, Tuple[int, ...]]]: + def is_symbolic(self): + return is_symbolic(*self.qdtypes) or is_symbolic(*self.cvs) + + def activation_function_dtypes(self) -> Sequence[Tuple[QDType, Tuple[SymbolicInt, ...]]]: """The data types that serve as input to the 'activation function'. The activation function takes in (quantum) inputs of these types and shapes and determines @@ -161,6 +181,8 @@ def is_active(self, *vals: 'ClassicalValT') -> bool: Returns: True if the specific input values evaluate to `True` for this CtrlSpec. """ + if self.is_symbolic(): + raise ValueError(f"Cannot compute activation for symbolic {self}") if len(vals) != self.num_ctrl_reg: raise ValueError(f"Incorrect number of inputs for {self}: {len(vals)}.") @@ -176,19 +198,31 @@ def is_active(self, *vals: 'ClassicalValT') -> bool: return True def wire_symbol(self, i: int, reg: Register, idx: Tuple[int, ...] = tuple()) -> 'WireSymbol': - # Return a circle for bits; a box otherwise. from qualtran.drawing import Circle, TextBox + cvs = self.cvs[i] + + if is_symbolic(cvs): + # control value is not given + return TextBox('ctrl') + + # Return a circle for bits; a box otherwise. + cv = cvs[idx] if reg.bitsize == 1: - cv = self.cvs[i][idx] return Circle(filled=(cv == 1)) - - cv = self.cvs[i][idx] - return TextBox(f'{cv}') + else: + return TextBox(f'{cv}') @cached_property - def _cvs_tuple(self) -> Tuple[int, ...]: - return tuple(cv for cvs in self.cvs for cv in tuple(cvs.reshape(-1))) + def __cvs_tuple(self) -> Tuple[Union[tuple[int, ...], Shaped], ...]: + """Serialize the control values for hashing and equality checking.""" + + def _serialize(cvs) -> Union[tuple[int, ...], Shaped]: + if isinstance(cvs, Shaped): + return cvs + return tuple(cvs.reshape(-1)) + + return tuple(_serialize(cvs) for cvs in self.cvs) def __eq__(self, other: Any) -> bool: if not isinstance(other, CtrlSpec): @@ -197,16 +231,22 @@ def __eq__(self, other: Any) -> bool: return ( other.qdtypes == self.qdtypes and other.shapes == self.shapes - and other._cvs_tuple == self._cvs_tuple + and other.__cvs_tuple == self.__cvs_tuple ) def __hash__(self): - return hash((self.qdtypes, self.shapes, self._cvs_tuple)) + return hash((self.qdtypes, self.shapes, self.__cvs_tuple)) - def to_cirq_cv(self) -> cirq.SumOfProducts: + def to_cirq_cv(self) -> 'cirq.SumOfProducts': """Convert CtrlSpec to cirq.SumOfProducts representation of control values.""" + import cirq + + if self.is_symbolic(): + raise ValueError(f"Cannot convert symbolic {self} to cirq control values.") + cirq_cv = [] for qdtype, cv in zip(self.qdtypes, self.cvs): + assert isinstance(cv, np.ndarray) for idx in Register('', qdtype, cv.shape).all_idxs(): cirq_cv += [*qdtype.to_bits(cv[idx])] return cirq.SumOfProducts([tuple(cirq_cv)]) @@ -214,7 +254,7 @@ def to_cirq_cv(self) -> cirq.SumOfProducts: @classmethod def from_cirq_cv( cls, - cirq_cv: cirq.ops.AbstractControlValues, + cirq_cv: 'cirq.ops.AbstractControlValues', *, qdtypes: Optional[Sequence[QDType]] = None, shapes: Optional[Sequence[Tuple[int, ...]]] = None, @@ -248,6 +288,21 @@ def from_cirq_cv( bloq_cvs.append(curr_cvs) return CtrlSpec(tuple(qdtypes), tuple(bloq_cvs)) + def get_single_ctrl_bit(self) -> ControlBit: + """If controlled by a single qubit, return the control bit, otherwise raise""" + if self.is_symbolic(): + raise ValueError(f"cannot get ctrl bit for symbolic {self}") + if self.num_qubits != 1: + raise ValueError(f"expected a single qubit control, got {self.num_qubits}") + + (qdtype,) = self.qdtypes + (cv,) = self.cvs + assert isinstance(cv, np.ndarray) + (idx,) = Register('', qdtype, cv.shape).all_idxs() + (control_bit,) = qdtype.to_bits(cv[idx]) + + return int(control_bit) + class AddControlledT(Protocol): """The signature for the `add_controlled` callback part of `ctrl_system`. @@ -268,8 +323,7 @@ class AddControlledT(Protocol): def __call__( self, bb: 'BloqBuilder', ctrl_soqs: Sequence['SoquetT'], in_soqs: Dict[str, 'SoquetT'] - ) -> Tuple[Iterable['SoquetT'], Iterable['SoquetT']]: - ... + ) -> Tuple[Iterable['SoquetT'], Iterable['SoquetT']]: ... def _get_nice_ctrl_reg_names(reg_names: List[str], n: int) -> Tuple[str, ...]: @@ -439,9 +493,21 @@ def _tensor_data(self): def _unitary_(self): if isinstance(self.subbloq, GateWithRegisters): # subbloq is a cirq gate, use the cirq-style API to derive a unitary. - return cirq.unitary( - cirq.ControlledGate(self.subbloq, control_values=self.ctrl_spec.to_cirq_cv()) - ) + import cirq + + # TODO It would be ideal to use `tensor_contract` always, + # but at the moment it's about 5-10x slower than `cirq.unitary`. + # So we default to `cirq.unitary`, and only use `tensor_contract` if it fails. + # https://github.com/quantumlib/Qualtran/issues/1336 + # TODO `cirq.ControlledGate` fails to correctly verify `subbloq` using + # a compute-uncompute `And` pair is unitary. + # https://github.com/quantumlib/Qualtran/issues/1488 + try: + return cirq.unitary( + cirq.ControlledGate(self.subbloq, control_values=self.ctrl_spec.to_cirq_cv()) + ) + except ValueError: + pass # use the tensor contraction instead if all(reg.side == Side.THRU for reg in self.subbloq.signature): # subbloq has only THRU registers, so the tensor contraction corresponds # to a unitary matrix. @@ -495,7 +561,11 @@ def as_cirq_op( cirq_quregs | ctrl_regs, ) - def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo: + def _circuit_diagram_info_( + self, args: 'cirq.CircuitDiagramInfoArgs' + ) -> 'cirq.CircuitDiagramInfo': + import cirq + from qualtran.cirq_interop._bloq_to_cirq import _wire_symbol_to_cirq_diagram_info if isinstance(self.subbloq, cirq.Gate): diff --git a/qualtran/_infra/controlled_test.py b/qualtran/_infra/controlled_test.py index b25201b25..fcb9f207c 100644 --- a/qualtran/_infra/controlled_test.py +++ b/qualtran/_infra/controlled_test.py @@ -14,9 +14,9 @@ from typing import Dict, List, Tuple, TYPE_CHECKING import attrs -import cirq import numpy as np import pytest +import sympy import qualtran.testing as qlt_testing from qualtran import ( @@ -25,6 +25,7 @@ CompositeBloq, Controlled, CtrlSpec, + DecomposeTypeError, QBit, QInt, QUInt, @@ -53,8 +54,11 @@ from qualtran.drawing import get_musical_score_data from qualtran.drawing.musical_score import Circle, SoqData, TextBox from qualtran.simulation.tensor import cbloq_to_quimb, get_right_and_left_inds +from qualtran.symbolics import Shaped if TYPE_CHECKING: + import cirq + from qualtran import SoquetT @@ -72,8 +76,10 @@ def test_ctrl_spec(): cspec3 = CtrlSpec(QInt(64), cvs=np.int64(234234)) assert cspec3 != cspec1 assert cspec3.qdtypes[0].num_qubits == 64 - assert cspec3.cvs[0] == 234234 - assert cspec3.cvs[0][tuple()] == 234234 + (cvs,) = cspec3.cvs + assert isinstance(cvs, np.ndarray) + assert cvs == 234234 + assert cvs[tuple()] == 234234 def test_ctrl_spec_shape(): @@ -84,6 +90,7 @@ def test_ctrl_spec_shape(): def test_ctrl_spec_to_cirq_cv_roundtrip(): + cirq = pytest.importorskip('cirq') cirq_cv = cirq.ProductOfSums([0, 1, 0, 1]) assert CtrlSpec.from_cirq_cv(cirq_cv) == CtrlSpec(cvs=[0, 1, 0, 1]) @@ -95,20 +102,72 @@ def test_ctrl_spec_to_cirq_cv_roundtrip(): for ctrl_spec in ctrl_specs: assert ctrl_spec.to_cirq_cv() == cirq_cv.expand() - assert CtrlSpec.from_cirq_cv(cirq_cv, qdtypes=ctrl_spec.qdtypes, shapes=ctrl_spec.shapes) + assert CtrlSpec.from_cirq_cv( + cirq_cv, qdtypes=ctrl_spec.qdtypes, shapes=ctrl_spec.concrete_shapes + ) + + +@pytest.mark.parametrize( + "ctrl_spec", [CtrlSpec(), CtrlSpec(cvs=[1]), CtrlSpec(cvs=np.atleast_2d([1]))] +) +def test_ctrl_spec_single_bit_one(ctrl_spec: CtrlSpec): + assert ctrl_spec.get_single_ctrl_bit() == 1 + + +@pytest.mark.parametrize( + "ctrl_spec", [CtrlSpec(cvs=0), CtrlSpec(cvs=[0]), CtrlSpec(cvs=np.atleast_2d([0]))] +) +def test_ctrl_spec_single_bit_zero(ctrl_spec: CtrlSpec): + assert ctrl_spec.get_single_ctrl_bit() == 0 + + +@pytest.mark.parametrize("ctrl_spec", [CtrlSpec(cvs=[1, 1]), CtrlSpec(qdtypes=QUInt(2), cvs=0)]) +def test_ctrl_spec_single_bit_raises(ctrl_spec: CtrlSpec): + with pytest.raises(ValueError): + ctrl_spec.get_single_ctrl_bit() + + +@pytest.mark.parametrize("shape", [(1,), (10,), (10, 10)]) +def test_ctrl_spec_symbolic_cvs(shape: tuple[int, ...]): + ctrl_spec = CtrlSpec(cvs=Shaped(shape)) + assert ctrl_spec.is_symbolic() + assert ctrl_spec.num_qubits == np.prod(shape) + assert ctrl_spec.shapes == (shape,) + + +@pytest.mark.parametrize("shape", [(1,), (10,), (10, 10)]) +def test_ctrl_spec_symbolic_dtype(shape: tuple[int, ...]): + n = sympy.Symbol("n") + dtype = QUInt(n) + + ctrl_spec = CtrlSpec(qdtypes=dtype, cvs=Shaped(shape)) + + assert ctrl_spec.is_symbolic() + assert ctrl_spec.num_qubits == n * np.prod(shape) + assert ctrl_spec.shapes == (shape,) + + +def test_ctrl_spec_symbolic_wire_symbol(): + ctrl_spec = CtrlSpec(cvs=Shaped((10,))) + reg = Register('q', QBit()) + assert ctrl_spec.wire_symbol(0, reg) == TextBox('ctrl') + + +def _test_cirq_equivalence(bloq: Bloq, gate: 'cirq.Gate'): + import cirq + + left_quregs = get_named_qubits(bloq.signature.lefts()) + circuit1 = bloq.as_composite_bloq().to_cirq_circuit(cirq_quregs=left_quregs) + circuit2 = cirq.Circuit( + gate.on(*merge_qubits(bloq.signature, **get_named_qubits(bloq.signature))) + ) + cirq.testing.assert_same_circuits(circuit1, circuit2) def test_ctrl_bloq_as_cirq_op(): + cirq = pytest.importorskip('cirq') subbloq = XGate() - def _test_cirq_equivalence(bloq: Bloq, gate: cirq.Gate): - left_quregs = get_named_qubits(bloq.signature.lefts()) - circuit1 = bloq.as_composite_bloq().to_cirq_circuit(cirq_quregs=left_quregs) - circuit2 = cirq.Circuit( - gate.on(*merge_qubits(bloq.signature, **get_named_qubits(bloq.signature))) - ) - cirq.testing.assert_same_circuits(circuit1, circuit2) - # Simple ctrl spec _test_cirq_equivalence(subbloq, cirq.X) _test_cirq_equivalence(subbloq.controlled(), cirq.X.controlled()) @@ -340,7 +399,9 @@ def test_notebook(): qlt_testing.execute_notebook('../Controlled') -def _verify_ctrl_tensor_for_unitary(ctrl_spec: CtrlSpec, bloq: Bloq, gate: cirq.Gate): +def _verify_ctrl_tensor_for_unitary(ctrl_spec: CtrlSpec, bloq: Bloq, gate: 'cirq.Gate'): + import cirq + ctrl_bloq = Controlled(bloq, ctrl_spec) cgate = cirq.ControlledGate(gate, control_values=ctrl_spec.to_cirq_cv()) np.testing.assert_allclose(ctrl_bloq.tensor_contract(), cirq.unitary(cgate), atol=1e-8) @@ -357,6 +418,7 @@ def _verify_ctrl_tensor_for_unitary(ctrl_spec: CtrlSpec, bloq: Bloq, gate: cirq. @pytest.mark.parametrize('ctrl_spec', interesting_ctrl_specs) def test_controlled_tensor_for_unitary(ctrl_spec: CtrlSpec): + cirq = pytest.importorskip('cirq') # Test one qubit unitaries _verify_ctrl_tensor_for_unitary(ctrl_spec, XGate(), cirq.X) _verify_ctrl_tensor_for_unitary(ctrl_spec, YGate(), cirq.Y) @@ -366,6 +428,7 @@ def test_controlled_tensor_for_unitary(ctrl_spec: CtrlSpec): def test_controlled_tensor_without_decompose(): + cirq = pytest.importorskip('cirq') ctrl_spec = CtrlSpec() bloq = TwoBitCSwap() ctrl_bloq = Controlled(bloq, ctrl_spec) @@ -401,11 +464,15 @@ def signature(self) -> 'Signature': return Signature([Register('x', QBit(), shape=(3,), side=Side.RIGHT)]) def build_composite_bloq(self, bb: 'BloqBuilder') -> Dict[str, 'SoquetT']: + if self.ctrl_spec.is_symbolic(): + raise DecomposeTypeError(f"cannot decompose {self} with symbolic {self.ctrl_spec=}") + one_or_zero = [ZeroState(), OneState()] ctrl_bloq = Controlled(And(*self.and_ctrl), ctrl_spec=self.ctrl_spec) ctrl_soqs = {} for reg, cvs in zip(ctrl_bloq.ctrl_regs, self.ctrl_spec.cvs): + assert isinstance(cvs, np.ndarray) soqs = np.empty(shape=reg.shape, dtype=object) for idx in reg.all_idxs(): soqs[idx] = bb.add(IntState(val=cvs[idx], bitsize=reg.dtype.num_qubits)) @@ -417,6 +484,7 @@ def build_composite_bloq(self, bb: 'BloqBuilder') -> Dict[str, 'SoquetT']: out_soqs = np.asarray([*ctrl_soqs.pop('ctrl'), ctrl_soqs.pop('target')]) # type: ignore[misc] for reg, cvs in zip(ctrl_bloq.ctrl_regs, self.ctrl_spec.cvs): + assert isinstance(cvs, np.ndarray) for idx in reg.all_idxs(): ctrl_soq = np.asarray(ctrl_soqs[reg.name])[idx] bb.add(IntEffect(val=cvs[idx], bitsize=reg.dtype.num_qubits), val=ctrl_soq) @@ -442,6 +510,7 @@ def test_controlled_tensor_for_and_bloq(ctrl_spec: CtrlSpec): def test_controlled_diagrams(): + cirq = pytest.importorskip('cirq') ctrl_gate = XPowGate(0.25).controlled() cirq.testing.assert_has_diagram( cirq.Circuit(ctrl_gate.on_registers(**get_named_qubits(ctrl_gate.signature))), diff --git a/qualtran/_infra/data_types.py b/qualtran/_infra/data_types.py index 5ea2ac1f8..ee918d506 100644 --- a/qualtran/_infra/data_types.py +++ b/qualtran/_infra/data_types.py @@ -50,6 +50,7 @@ import abc from enum import Enum +from functools import cached_property from typing import Any, Iterable, List, Sequence, Union import attrs @@ -57,7 +58,7 @@ from fxpmath import Fxp from numpy.typing import NDArray -from qualtran.symbolics import is_symbolic, SymbolicInt +from qualtran.symbolics import bit_length, is_symbolic, SymbolicInt class QDType(metaclass=abc.ABCMeta): @@ -771,9 +772,14 @@ class QMontgomeryUInt(QDType): bitsize: The number of qubits used to represent the integer. References: - [Montgomery modular multiplication](https://en.wikipedia.org/wiki/Montgomery_modular_multiplication) + [Montgomery modular multiplication](https://en.wikipedia.org/wiki/Montgomery_modular_multiplication). + + [Performance Analysis of a Repetition Cat Code Architecture: Computing 256-bit Elliptic Curve Logarithm in 9 Hours with 126133 Cat Qubits](https://arxiv.org/abs/2302.06639). + Gouzien et al. 2023. + We follow Montgomery form as described in the above paper; namely, r = 2^bitsize. """ + # TODO(https://github.com/quantumlib/Qualtran/issues/1471): Add modulus p as a class member. bitsize: SymbolicInt @property @@ -809,9 +815,172 @@ def assert_valid_classical_val_array( if np.any(val_array >= 2**self.bitsize): raise ValueError(f"Too-large classical values encountered in {debug_str}") + def montgomery_inverse(self, xm: int, p: int) -> int: + """Returns the modular inverse of an integer in montgomery form. + + Args: + xm: An integer in montgomery form. + p: The modulus of the finite field. + """ + return ((pow(xm, -1, p)) * pow(2, 2 * self.bitsize, p)) % p + + def montgomery_product(self, xm: int, ym: int, p: int) -> int: + """Returns the modular product of two integers in montgomery form. + + Args: + xm: The first montgomery form integer for the product. + ym: The second montgomery form integer for the product. + p: The modulus of the finite field. + """ + return (xm * ym * pow(2, -self.bitsize, p)) % p + + def montgomery_to_uint(self, xm: int, p: int) -> int: + """Converts an integer in montgomery form to a normal form integer. + + Args: + xm: An integer in montgomery form. + p: The modulus of the finite field. + """ + return (xm * pow(2, -self.bitsize, p)) % p + + def uint_to_montgomery(self, x: int, p: int) -> int: + """Converts an integer into montgomery form. + + Args: + x: An integer. + p: The modulus of the finite field. + """ + return (x * pow(2, int(self.bitsize), p)) % p + + +@attrs.frozen +class QGF(QDType): + r"""Galois Field type to represent elements of a finite field. + + A Finite Field or Galois Field is a field that contains finite number of elements. The order + of a finite field is the number of elements in the field, which is either a prime number or + a prime power. For every prime number $p$ and every positive integer $m$ there are fields of + order $p^m$, all of which are isomorphic. When m=1, the finite field of order p can be + constructed via integers modulo p. + + Elements of a Galois Field $GF(p^m)$ may be conveniently viewed as polynomials + $a_{0} + a_{1}x + ... + a_{m−1}x_{m−1}$, where $a_0, a_1, ..., a_{m−1} \in F(p)$. + $GF(p^m)$ addition is defined as the component-wise (polynomial) addition over F(p) and + multiplication is defined as polynomial multiplication modulo an irreducible polynomial of + degree $m$. The selection of the specific irreducible polynomial affects the representation + of the given field, but all fields of a fixed size are isomorphic. + + The data type uses the [Galois library](https://mhostetter.github.io/galois/latest/) to + perform arithmetic over Galois Fields. By default, the Conway polynomial $C_{p, m}$ is used + as the irreducible polynomial. + + Attributes: + characteristic: The characteristic $p$ of the field $GF(p^m)$. + The characteristic must be prime. + degree: The degree $m$ of the field $GF(p^{m})$. The degree must be a positive integer. + + References + [Finite Field](https://en.wikipedia.org/wiki/Finite_field) + + [Intro to Prime Fields](https://mhostetter.github.io/galois/latest/tutorials/intro-to-prime-fields/) + + [Intro to Extension Fields](https://mhostetter.github.io/galois/latest/tutorials/intro-to-extension-fields/) + """ + + characteristic: SymbolicInt + degree: SymbolicInt + + @cached_property + def order(self) -> SymbolicInt: + return self.characteristic**self.degree + + @cached_property + def bitsize(self) -> SymbolicInt: + """Bitsize of qubit register required to represent a single instance of this data type.""" + return bit_length(self.order - 1) + + @cached_property + def num_qubits(self) -> SymbolicInt: + """Number of qubits required to represent a single instance of this data type.""" + return self.bitsize + + def get_classical_domain(self) -> Iterable[Any]: + """Yields all possible classical (computational basis state) values representable + by this type.""" + yield from self.gf_type.elements + + @cached_property + def _quint_equivalent(self) -> QUInt: + return QUInt(self.num_qubits) + + @cached_property + def gf_type(self): + from galois import GF + + return GF(int(self.characteristic), int(self.degree), compile='python-calculate') + + def to_bits(self, x) -> List[int]: + """Yields individual bits corresponding to binary representation of x""" + self.assert_valid_classical_val(x) + return self._quint_equivalent.to_bits(int(x)) + + def from_bits(self, bits: Sequence[int]): + """Combine individual bits to form x""" + return self.gf_type(self._quint_equivalent.from_bits(bits)) + + def from_bits_array(self, bits_array: NDArray[np.uint8]): + """Combine individual bits to form classical values. + + Often, converting an array can be performed faster than converting each element individually. + This operation accepts any NDArray of bits such that the last dimension equals `self.bitsize`, + and the output array satisfies `output_shape = input_shape[:-1]`. + """ + return self.gf_type(self._quint_equivalent.from_bits_array(bits_array)) + + def assert_valid_classical_val(self, val: Any, debug_str: str = 'val'): + """Raises an exception if `val` is not a valid classical value for this type. + + Args: + val: A classical value that should be in the domain of this QDType. + debug_str: Optional debugging information to use in exception messages. + """ + if not isinstance(val, self.gf_type): + raise ValueError(f"{debug_str} should be a {self.gf_type}, not {val!r}") + + def assert_valid_classical_val_array(self, val_array: NDArray[Any], debug_str: str = 'val'): + """Raises an exception if `val_array` is not a valid array of classical values + for this type. + + Often, validation on an array can be performed faster than validating each element + individually. + + Args: + val_array: A numpy array of classical values. Each value should be in the domain + of this QDType. + debug_str: Optional debugging information to use in exception messages. + """ + if np.any(val_array < 0): + raise ValueError(f"Negative classical values encountered in {debug_str}") + if np.any(val_array >= self.order): + raise ValueError(f"Too-large classical values encountered in {debug_str}") + + def is_symbolic(self) -> bool: + """Returns True if this qdtype is parameterized with symbolic objects.""" + return is_symbolic(self.characteristic, self.order) + + def iteration_length_or_zero(self) -> SymbolicInt: + """Safe version of iteration length. + + Returns the iteration_length if the type has it or else zero. + """ + return self.order + + def __str__(self): + return f'QGF({self.characteristic}**{self.degree})' + QAnyInt = (QInt, QUInt, BQUInt, QMontgomeryUInt) -QAnyUInt = (QUInt, BQUInt, QMontgomeryUInt) +QAnyUInt = (QUInt, BQUInt, QMontgomeryUInt, QGF) class QDTypeCheckingSeverity(Enum): @@ -827,7 +996,7 @@ class QDTypeCheckingSeverity(Enum): """Strictly enforce type checking between registers. Only single bit conversions are allowed.""" -def _check_uint_fxp_consistent(a: Union[QUInt, BQUInt, QMontgomeryUInt], b: QFxp) -> bool: +def _check_uint_fxp_consistent(a: Union[QUInt, BQUInt, QMontgomeryUInt, QGF], b: QFxp) -> bool: """A uint / qfxp is consistent with a whole or totally fractional unsigned QFxp.""" if b.signed: return False diff --git a/qualtran/_infra/data_types_test.py b/qualtran/_infra/data_types_test.py index 1dc0ced33..65252c5cb 100644 --- a/qualtran/_infra/data_types_test.py +++ b/qualtran/_infra/data_types_test.py @@ -15,13 +15,12 @@ import random from typing import Any, Sequence, Union -import cirq import numpy as np import pytest import sympy from numpy.typing import NDArray -from qualtran.symbolics import is_symbolic +from qualtran.symbolics import ceil, is_symbolic, log2 from .data_types import ( BQUInt, @@ -31,6 +30,7 @@ QBit, QDType, QFxp, + QGF, QInt, QIntOnesComp, QMontgomeryUInt, @@ -135,13 +135,49 @@ def test_qmontgomeryuint(): assert is_symbolic(QMontgomeryUInt(sympy.Symbol('x'))) +@pytest.mark.parametrize('p', [13, 17, 29]) +@pytest.mark.parametrize('val', [1, 5, 7, 9]) +def test_qmontgomeryuint_operations(val, p): + qmontgomeryuint_8 = QMontgomeryUInt(8) + # Convert value to montgomery form and get the modular inverse. + val_m = qmontgomeryuint_8.uint_to_montgomery(val, p) + mod_inv = qmontgomeryuint_8.montgomery_inverse(val_m, p) + + # Calculate the product in montgomery form and convert back to normal form for assertion. + assert ( + qmontgomeryuint_8.montgomery_to_uint( + qmontgomeryuint_8.montgomery_product(val_m, mod_inv, p), p + ) + == 1 + ) + + +@pytest.mark.parametrize('p', [13, 17, 29]) +@pytest.mark.parametrize('val', [1, 5, 7, 9]) +def test_qmontgomeryuint_conversions(val, p): + qmontgomeryuint_8 = QMontgomeryUInt(8) + assert val == qmontgomeryuint_8.montgomery_to_uint( + qmontgomeryuint_8.uint_to_montgomery(val, p), p + ) + + +def test_qgf(): + qgf_256 = QGF(characteristic=2, degree=8) + assert str(qgf_256) == 'QGF(2**8)' + assert qgf_256.num_qubits == 8 + p, m = sympy.symbols('p, m', integer=True, positive=True) + qgf_pm = QGF(characteristic=p, degree=m) + assert qgf_pm.num_qubits == ceil(log2(p**m)) + assert is_symbolic(qgf_pm) + + @pytest.mark.parametrize('qdtype', [QBit(), QInt(4), QUInt(4), BQUInt(3, 5)]) def test_domain_and_validation(qdtype: QDType): for v in qdtype.get_classical_domain(): qdtype.assert_valid_classical_val(v) -@pytest.mark.parametrize('qdtype', [QBit(), QInt(4), QUInt(4), BQUInt(3, 5)]) +@pytest.mark.parametrize('qdtype', [QBit(), QInt(4), QUInt(4), BQUInt(3, 5), QGF(2, 8)]) def test_domain_and_validation_arr(qdtype: QDType): arr = np.array(list(qdtype.get_classical_domain())) qdtype.assert_valid_classical_val_array(arr) @@ -172,6 +208,9 @@ def test_validation_errs(): with pytest.raises(ValueError): QUInt(3).assert_valid_classical_val(-1) + with pytest.raises(ValueError): + QGF(2, 8).assert_valid_classical_val(2**8) + def test_validate_arrays(): rs = np.random.RandomState(52) @@ -233,10 +272,11 @@ def test_single_qubit_consistency(): assert check_dtypes_consistent(QAny(1), QBit()) assert check_dtypes_consistent(BQUInt(1), QBit()) assert check_dtypes_consistent(QFxp(1, 1), QBit()) + assert check_dtypes_consistent(QGF(characteristic=2, degree=1), QBit()) def assert_to_and_from_bits_array_consistent(qdtype: QDType, values: Union[Sequence[Any], NDArray]): - values = np.asarray(values) + values = np.asanyarray(values) bits_array = qdtype.to_bits_array(values) # individual values @@ -263,6 +303,23 @@ def test_qint_to_and_from_bits(): assert_to_and_from_bits_array_consistent(qint4, range(-8, 8)) +def test_qgf_to_and_from_bits(): + from galois import GF + + qgf_256 = QGF(2, 8) + gf256 = GF(2**8) + assert [*qgf_256.get_classical_domain()] == [*range(256)] + a, b = qgf_256.to_bits(gf256(21)), qgf_256.to_bits(gf256(22)) + c = qgf_256.from_bits(list(np.bitwise_xor(a, b))) + assert c == gf256(21) + gf256(22) + for x in gf256.elements: + assert x == gf256.Vector(qgf_256.to_bits(x)) + + with pytest.raises(ValueError): + qgf_256.to_bits(21) + assert_to_and_from_bits_array_consistent(qgf_256, gf256([*range(256)])) + + def test_quint_to_and_from_bits(): quint4 = QUInt(4) assert [*quint4.get_classical_domain()] == [*range(0, 16)] @@ -280,6 +337,7 @@ def test_quint_to_and_from_bits(): def test_bits_to_int(): + cirq = pytest.importorskip('cirq') rs = np.random.RandomState(52) bitstrings = rs.choice([0, 1], size=(100, 23)) @@ -296,6 +354,7 @@ def test_bits_to_int(): def test_int_to_bits(): + cirq = pytest.importorskip('cirq') rs = np.random.RandomState(52) nums = rs.randint(0, 2**23 - 1, size=(100,), dtype=np.uint64) bitstrings = QUInt(23).to_bits_array(nums) diff --git a/qualtran/_infra/gate_with_registers.py b/qualtran/_infra/gate_with_registers.py index 6afd4265d..e59bd4a20 100644 --- a/qualtran/_infra/gate_with_registers.py +++ b/qualtran/_infra/gate_with_registers.py @@ -49,8 +49,8 @@ def total_bits(registers: Iterable[Register]) -> int: def split_qubits( - registers: Iterable[Register], qubits: Sequence[cirq.Qid] -) -> Dict[str, NDArray[cirq.Qid]]: # type: ignore[type-var] + registers: Iterable[Register], qubits: Sequence['cirq.Qid'] +) -> Dict[str, NDArray['cirq.Qid']]: # type: ignore[type-var] """Splits the flat list of qubits into a dictionary of appropriately shaped qubit arrays.""" qubit_regs = {} @@ -65,11 +65,11 @@ def split_qubits( def merge_qubits( registers: Iterable[Register], - **qubit_regs: Union[cirq.Qid, Sequence[cirq.Qid], NDArray[cirq.Qid]], -) -> List[cirq.Qid]: + **qubit_regs: Union['cirq.Qid', Sequence['cirq.Qid'], NDArray['cirq.Qid']], +) -> List['cirq.Qid']: """Merges the dictionary of appropriately shaped qubit arrays into a flat list of qubits.""" - ret: List[cirq.Qid] = [] + ret: List['cirq.Qid'] = [] for reg in registers: if reg.name not in qubit_regs: raise ValueError(f"All qubit registers must be present. {reg.name} not in qubit_regs") @@ -84,7 +84,7 @@ def merge_qubits( return ret -def get_named_qubits(registers: Iterable[Register]) -> Dict[str, NDArray[cirq.Qid]]: +def get_named_qubits(registers: Iterable[Register]) -> Dict[str, NDArray['cirq.Qid']]: """Returns a dictionary of appropriately shaped named qubit signature for input `signature`.""" def _qubit_array(reg: Register): @@ -101,9 +101,11 @@ def _qubits_for_reg(reg: Register): return _qubit_array(reg) return np.array( - [cirq.NamedQubit(f"{reg.name}")] - if reg.total_bits() == 1 - else cirq.NamedQubit.range(reg.total_bits(), prefix=reg.name), + ( + [cirq.NamedQubit(f"{reg.name}")] + if reg.total_bits() == 1 + else cirq.NamedQubit.range(reg.total_bits(), prefix=reg.name) + ), dtype=object, ) @@ -112,7 +114,7 @@ def _qubits_for_reg(reg: Register): def _get_all_and_output_quregs_from_input( registers: Iterable[Register], - qubit_manager: cirq.QubitManager, + qubit_manager: 'cirq.QubitManager', in_quregs: Dict[str, 'CirqQuregT'], ) -> Tuple[Dict[str, 'CirqQuregT'], Dict[str, 'CirqQuregT']]: """Takes care of necessary (de-/)allocations to obtain output & all qubit registers from input. @@ -169,7 +171,7 @@ def _get_cirq_cv( num_controls: Optional[int] = None, control_values=None, control_qid_shape: Optional[Tuple[int, ...]] = None, -) -> cirq.ops.AbstractControlValues: +) -> 'cirq.ops.AbstractControlValues': """Logic copied from `cirq.ControlledGate` to help convert cirq-style spec to `CtrlSpec`""" if isinstance(control_values, cirq.SumOfProducts) and len(control_values._conjunctions) == 1: control_values = control_values._conjunctions[0] @@ -323,13 +325,13 @@ def _num_qubits_(self) -> int: return total_bits(self.signature) def decompose_from_registers( - self, *, context: cirq.DecompositionContext, **quregs: NDArray[cirq.Qid] - ) -> cirq.OP_TREE: + self, *, context: 'cirq.DecompositionContext', **quregs: NDArray['cirq.Qid'] + ) -> 'cirq.OP_TREE': raise DecomposeNotImplementedError(f"{self} does not declare a decomposition.") def _decompose_with_context_( - self, qubits: Sequence[cirq.Qid], context: Optional[cirq.DecompositionContext] = None - ) -> cirq.OP_TREE: + self, qubits: Sequence['cirq.Qid'], context: Optional['cirq.DecompositionContext'] = None + ) -> 'cirq.OP_TREE': from qualtran.cirq_interop._bloq_to_cirq import _cirq_style_decompose_from_decompose_bloq quregs = split_qubits(self.signature, qubits) @@ -347,7 +349,7 @@ def _decompose_with_context_( pass return NotImplemented - def _decompose_(self, qubits: Sequence[cirq.Qid]) -> cirq.OP_TREE: + def _decompose_(self, qubits: Sequence['cirq.Qid']) -> 'cirq.OP_TREE': return self._decompose_with_context_(qubits) def on(self, *qubits) -> 'cirq.Operation': @@ -357,8 +359,8 @@ def on(self, *qubits) -> 'cirq.Operation': return cirq.Gate.on(self, *qubits) def on_registers( - self, **qubit_regs: Union[cirq.Qid, Sequence[cirq.Qid], NDArray[cirq.Qid]] - ) -> cirq.Operation: + self, **qubit_regs: Union['cirq.Qid', Sequence['cirq.Qid'], NDArray['cirq.Qid']] + ) -> 'cirq.Operation': return self.on(*merge_qubits(self.signature, **qubit_regs)) def __pow__(self, power: int) -> 'GateWithRegisters': @@ -371,8 +373,9 @@ def __pow__(self, power: int) -> 'GateWithRegisters': return Power(bloq, abs(power)) raise NotImplementedError(f"{self} does not implemented __pow__ for {power=}.") + @classmethod def _get_ctrl_spec( - self, + cls, num_controls: Union[Optional[int], 'CtrlSpec'] = None, control_values=None, control_qid_shape: Optional[Tuple[int, ...]] = None, @@ -438,7 +441,7 @@ def controlled( self, num_controls: Optional[int] = None, control_values: Optional[ - Union[cirq.ops.AbstractControlValues, Sequence[Union[int, Collection[int]]]] + Union['cirq.ops.AbstractControlValues', Sequence[Union[int, Collection[int]]]] ] = None, control_qid_shape: Optional[Tuple[int, ...]] = None, ) -> 'GateWithRegisters': @@ -453,7 +456,7 @@ def controlled( self, num_controls: Union[Optional[int], 'CtrlSpec'] = None, control_values: Optional[ - Union[cirq.ops.AbstractControlValues, Sequence[Union[int, Collection[int]]]] + Union['cirq.ops.AbstractControlValues', Sequence[Union[int, Collection[int]]]] ] = None, control_qid_shape: Optional[Tuple[int, ...]] = None, *, @@ -496,7 +499,7 @@ def controlled( Returns: A controlled version of the bloq. """ - ctrl_spec = self._get_ctrl_spec( + ctrl_spec = GateWithRegisters._get_ctrl_spec( num_controls, control_values, control_qid_shape, ctrl_spec=ctrl_spec ) controlled_bloq, _ = self.get_ctrl_system(ctrl_spec=ctrl_spec) @@ -515,7 +518,9 @@ def my_tensors( else: return super().my_tensors(incoming=incoming, outgoing=outgoing) - def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo: + def _circuit_diagram_info_( + self, args: 'cirq.CircuitDiagramInfoArgs' + ) -> 'cirq.CircuitDiagramInfo': """Default diagram info that uses register names to name the boxes in multi-qubit gates. Descendants can override this method with more meaningful circuit diagram information. diff --git a/qualtran/_infra/gate_with_registers_test.py b/qualtran/_infra/gate_with_registers_test.py index a3735a889..31fd6f863 100644 --- a/qualtran/_infra/gate_with_registers_test.py +++ b/qualtran/_infra/gate_with_registers_test.py @@ -151,8 +151,10 @@ def test_gate_with_registers_decompose_from_context_auto_generated(): def test_non_unitary_controlled(): + from qualtran.bloqs.mcmt.controlled_via_and import ControlledViaAnd + bloq = BloqWithDecompose() - assert bloq.controlled(control_values=[0]) == Controlled(bloq, CtrlSpec(cvs=0)) + assert bloq.controlled(control_values=[0]) == ControlledViaAnd(bloq, CtrlSpec(cvs=0)) @pytest.mark.notebook diff --git a/qualtran/_infra/registers_test.py b/qualtran/_infra/registers_test.py index 8beb0163a..f52b1af7a 100644 --- a/qualtran/_infra/registers_test.py +++ b/qualtran/_infra/registers_test.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import cirq import numpy as np import pytest import sympy @@ -100,6 +99,13 @@ def test_signature(): assert list(signature) == [r1, r2, r3] + +def test_get_named_qubits(): + cirq = pytest.importorskip('cirq') + r1 = Register("r1", QAny(5)) + r2 = Register("r2", QAny(2)) + r3 = Register("r3", QBit()) + signature = Signature([r1, r2, r3]) expected_named_qubits = { "r1": cirq.NamedQubit.range(5, prefix="r1"), "r2": cirq.NamedQubit.range(2, prefix="r2"), @@ -179,6 +185,7 @@ def test_agg_split(): def test_get_named_qubits_multidim(): + cirq = pytest.importorskip('cirq') regs = Signature([Register('q', shape=(2, 3), dtype=QAny(4))]) quregs = get_named_qubits(regs.lefts()) assert quregs['q'].shape == (2, 3, 4) diff --git a/qualtran/_infra/single_qubit_controlled.py b/qualtran/_infra/single_qubit_controlled.py deleted file mode 100644 index 84ca01129..000000000 --- a/qualtran/_infra/single_qubit_controlled.py +++ /dev/null @@ -1,79 +0,0 @@ -# Copyright 2023 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 abc -from typing import Iterable, Optional, Sequence, Tuple, TYPE_CHECKING - -import attrs - -from qualtran._infra.bloq import Bloq -from qualtran._infra.controlled import CtrlSpec -from qualtran._infra.registers import Register - -if TYPE_CHECKING: - from qualtran import AddControlledT, BloqBuilder, SoquetT - - -class SpecializedSingleQubitControlledExtension(Bloq): - """Add a specialized single-qubit controlled version of a Bloq. - - `control_val` is an optional single-bit control. When `control_val` is provided, - the `control_registers` property should return a single named qubit register, - and otherwise return an empty tuple. - - Example usage: - - @attrs.frozen - class MyGate(SpecializedSingleQubitControlledExtension): - control_val: Optional[int] = None - - @property - def control_registers() -> Tuple[Register, ...]: - return () if self.control_val is None else (Register('control', QBit()),) - """ - - control_val: Optional[int] - - @property - @abc.abstractmethod - def control_registers(self) -> Tuple[Register, ...]: - ... - - def get_single_qubit_controlled_bloq( - self, control_val: int - ) -> 'SpecializedSingleQubitControlledExtension': - """Override this to provide a custom controlled bloq""" - return attrs.evolve(self, control_val=control_val) # type: ignore[misc] - - def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> Tuple['Bloq', 'AddControlledT']: - if self.control_val is None and ctrl_spec.shapes in [((),), ((1,),)]: - control_val = int(ctrl_spec.cvs[0].item()) - cbloq = self.get_single_qubit_controlled_bloq(control_val) - - if not hasattr(cbloq, 'control_registers'): - raise TypeError("{cbloq} should have attribute `control_registers`") - - (ctrl_reg,) = cbloq.control_registers - - def adder( - bb: 'BloqBuilder', ctrl_soqs: Sequence['SoquetT'], in_soqs: dict[str, 'SoquetT'] - ) -> tuple[Iterable['SoquetT'], Iterable['SoquetT']]: - soqs = {ctrl_reg.name: ctrl_soqs[0]} | in_soqs - soqs = bb.add_d(cbloq, **soqs) - ctrl_soqs = [soqs.pop(ctrl_reg.name)] - return ctrl_soqs, soqs.values() - - return cbloq, adder - - return super().get_ctrl_system(ctrl_spec) diff --git a/qualtran/bloqs/arithmetic/__init__.py b/qualtran/bloqs/arithmetic/__init__.py index 694b36bd2..59ca8a5af 100644 --- a/qualtran/bloqs/arithmetic/__init__.py +++ b/qualtran/bloqs/arithmetic/__init__.py @@ -17,11 +17,16 @@ from qualtran.bloqs.arithmetic.comparison import ( BiQubitsMixer, CLinearDepthGreaterThan, + Equals, EqualsAConstant, GreaterThan, GreaterThanConstant, LessThanConstant, LessThanEqual, + LinearDepthHalfGreaterThan, + LinearDepthHalfGreaterThanEqual, + LinearDepthHalfLessThan, + LinearDepthHalfLessThanEqual, SingleQubitCompare, ) from qualtran.bloqs.arithmetic.controlled_addition import CAdd diff --git a/qualtran/bloqs/arithmetic/_shims.py b/qualtran/bloqs/arithmetic/_shims.py index 0d40daba7..d75e3d72f 100644 --- a/qualtran/bloqs/arithmetic/_shims.py +++ b/qualtran/bloqs/arithmetic/_shims.py @@ -22,8 +22,11 @@ from attrs import frozen -from qualtran import Bloq, QBit, QUInt, Register, Signature +from qualtran import Bloq, QBit, QMontgomeryUInt, QUInt, Register, Signature +from qualtran.bloqs.arithmetic.bitwise import BitwiseNot +from qualtran.bloqs.arithmetic.controlled_addition import CAdd from qualtran.bloqs.basic_gates import Toffoli +from qualtran.bloqs.basic_gates.swap import TwoBitCSwap from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator @@ -39,6 +42,20 @@ def build_call_graph(self, ssa: SympySymbolAllocator) -> BloqCountDictT: return {Toffoli(): self.n - 2} +@frozen +class CSub(Bloq): + n: int + + @cached_property + def signature(self) -> 'Signature': + return Signature( + [Register('ctrl', QBit()), Register('x', QUInt(self.n)), Register('y', QUInt(self.n))] + ) + + def build_call_graph(self, ssa: SympySymbolAllocator) -> BloqCountDictT: + return {CAdd(QMontgomeryUInt(self.n)): 1, BitwiseNot(QMontgomeryUInt(self.n)): 3} + + @frozen class Lt(Bloq): n: int @@ -62,3 +79,6 @@ class CHalf(Bloq): @cached_property def signature(self) -> 'Signature': return Signature([Register('ctrl', QBit()), Register('x', QUInt(self.n))]) + + def build_call_graph(self, ssa: SympySymbolAllocator) -> BloqCountDictT: + return {TwoBitCSwap(): self.n} diff --git a/qualtran/bloqs/arithmetic/addition.ipynb b/qualtran/bloqs/arithmetic/addition.ipynb index ab22770e1..d379603de 100644 --- a/qualtran/bloqs/arithmetic/addition.ipynb +++ b/qualtran/bloqs/arithmetic/addition.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "c231998b", + "id": "c9483021", "metadata": { "cq.autogen": "title_cell" }, @@ -13,7 +13,7 @@ { "cell_type": "code", "execution_count": null, - "id": "b572d769", + "id": "2dd760e6", "metadata": { "cq.autogen": "top_imports" }, @@ -30,7 +30,7 @@ }, { "cell_type": "markdown", - "id": "d3f4ce95", + "id": "95d8998c", "metadata": { "cq.autogen": "Add.bloq_doc.md" }, @@ -55,7 +55,7 @@ { "cell_type": "code", "execution_count": null, - "id": "6426bb53", + "id": "4cb5b704", "metadata": { "cq.autogen": "Add.bloq_doc.py" }, @@ -66,7 +66,7 @@ }, { "cell_type": "markdown", - "id": "3c8caad1", + "id": "69f415a2", "metadata": { "cq.autogen": "Add.example_instances.md" }, @@ -77,46 +77,46 @@ { "cell_type": "code", "execution_count": null, - "id": "73929b65", + "id": "b0c9815d", "metadata": { - "cq.autogen": "Add.add_small" + "cq.autogen": "Add.add_symb" }, "outputs": [], "source": [ - "add_small = Add(QUInt(bitsize=4))" + "n = sympy.Symbol('n')\n", + "add_symb = Add(QInt(bitsize=n))" ] }, { "cell_type": "code", "execution_count": null, - "id": "fbd4dbe9", + "id": "b481bd7d", "metadata": { - "cq.autogen": "Add.add_large" + "cq.autogen": "Add.add_small" }, "outputs": [], "source": [ - "add_large = Add(QUInt(bitsize=64))" + "add_small = Add(QUInt(bitsize=4))" ] }, { "cell_type": "code", "execution_count": null, - "id": "e749abdb", + "id": "90bc9e29", "metadata": { - "cq.autogen": "Add.add_symb" + "cq.autogen": "Add.add_large" }, "outputs": [], "source": [ - "n = sympy.Symbol('n')\n", - "add_symb = Add(QInt(bitsize=n))" + "add_large = Add(QUInt(bitsize=64))" ] }, { "cell_type": "code", "execution_count": null, - "id": "e5b746c1", + "id": "2fbbb318", "metadata": { - "collapsed": false + "cq.autogen": "Add.add_diff_size_regs" }, "outputs": [], "source": [ @@ -125,7 +125,7 @@ }, { "cell_type": "markdown", - "id": "c3c1a7e4", + "id": "253212ec", "metadata": { "cq.autogen": "Add.graphical_signature.md" }, @@ -136,7 +136,7 @@ { "cell_type": "code", "execution_count": null, - "id": "0f92be09", + "id": "ed24479f", "metadata": { "cq.autogen": "Add.graphical_signature.py" }, @@ -149,7 +149,7 @@ }, { "cell_type": "markdown", - "id": "350cb374", + "id": "1c55412d", "metadata": { "cq.autogen": "Add.call_graph.md" }, @@ -160,7 +160,7 @@ { "cell_type": "code", "execution_count": null, - "id": "c36a3fc4", + "id": "90569a0c", "metadata": { "cq.autogen": "Add.call_graph.py" }, @@ -174,7 +174,7 @@ }, { "cell_type": "markdown", - "id": "ffc76cc5", + "id": "8ae20541", "metadata": { "cq.autogen": "OutOfPlaceAdder.bloq_doc.md" }, @@ -186,12 +186,14 @@ "using $4n - 4 T$ gates. Uncomputation requires 0 T-gates.\n", "\n", "#### Parameters\n", - " - `bitsize`: Number of bits used to represent each input integer. The allocated output register is of size `bitsize+1` so it has enough space to hold the sum of `a+b`. \n", + " - `bitsize`: Number of bits used to represent each input integer. The allocated output register is of size `bitsize+1` so it has enough space to hold the sum of `a+b`.\n", + " - `is_adjoint`: Whether this is compute or uncompute version.\n", + " - `include_most_significant_bit`: Whether to add an extra most significant (i.e. carry) bit. \n", "\n", "#### Registers\n", " - `a`: A bitsize-sized input register (register a above).\n", " - `b`: A bitsize-sized input register (register b above).\n", - " - `c`: A bitize+1-sized LEFT/RIGHT register depending on whether the gate adjoint or not. \n", + " - `c`: The LEFT/RIGHT register depending on whether the gate adjoint or not. This register size is either bitsize or bitsize+1 depending on the value of `include_most_significant_bit`. \n", "\n", "#### References\n", " - [Halving the cost of quantum addition](https://arxiv.org/abs/1709.06648). \n" @@ -200,7 +202,7 @@ { "cell_type": "code", "execution_count": null, - "id": "c2450d62", + "id": "7c51ccda", "metadata": { "cq.autogen": "OutOfPlaceAdder.bloq_doc.py" }, @@ -211,7 +213,7 @@ }, { "cell_type": "markdown", - "id": "b5c79e25", + "id": "6e0af527", "metadata": { "cq.autogen": "OutOfPlaceAdder.example_instances.md" }, @@ -222,7 +224,7 @@ { "cell_type": "code", "execution_count": null, - "id": "e31acd2b", + "id": "f062b497", "metadata": { "cq.autogen": "OutOfPlaceAdder.add_oop_symb" }, @@ -235,7 +237,7 @@ { "cell_type": "code", "execution_count": null, - "id": "ef8871a6", + "id": "0bde421f", "metadata": { "cq.autogen": "OutOfPlaceAdder.add_oop_small" }, @@ -247,7 +249,7 @@ { "cell_type": "code", "execution_count": null, - "id": "e57b8c8d", + "id": "ce284cd8", "metadata": { "cq.autogen": "OutOfPlaceAdder.add_oop_large" }, @@ -258,7 +260,7 @@ }, { "cell_type": "markdown", - "id": "01915f46", + "id": "052cf86a", "metadata": { "cq.autogen": "OutOfPlaceAdder.graphical_signature.md" }, @@ -269,7 +271,7 @@ { "cell_type": "code", "execution_count": null, - "id": "d117e345", + "id": "672073d0", "metadata": { "cq.autogen": "OutOfPlaceAdder.graphical_signature.py" }, @@ -282,7 +284,7 @@ }, { "cell_type": "markdown", - "id": "3b6469e0", + "id": "ab488ede", "metadata": { "cq.autogen": "OutOfPlaceAdder.call_graph.md" }, @@ -293,7 +295,7 @@ { "cell_type": "code", "execution_count": null, - "id": "ff84301f", + "id": "311c752e", "metadata": { "cq.autogen": "OutOfPlaceAdder.call_graph.py" }, @@ -307,7 +309,7 @@ }, { "cell_type": "markdown", - "id": "2813f173", + "id": "62b014b7", "metadata": { "cq.autogen": "AddK.bloq_doc.md" }, @@ -322,22 +324,21 @@ "only clifford operations.\n", "\n", "#### Parameters\n", - " - `bitsize`: Number of bits used to represent each integer.\n", + " - `dtype`: data type of the input register `x`\n", " - `k`: The classical integer value to be added to x.\n", - " - `cvs`: A tuple of control values. Each entry specifies whether that control line is a \"positive\" control (`cv[i]=1`) or a \"negative\" control (`cv[i]=0`).\n", - " - `signed`: A boolean condition which controls whether the x register holds a value represented in 2's Complement or Unsigned. This affects the ability to add a negative constant. \n", + " - `is_controlled`: if True, construct a singly-controlled bloq. \n", "\n", "#### Registers\n", - " - `x`: A bitsize-sized input register (register x above). \n", + " - `x`: register of type `self.dtype` \n", "\n", "#### References\n", - " - [Improved quantum circuits for elliptic curve discrete logarithms](https://arxiv.org/abs/2001.09580). Haner et. al. 2020. Section 3: Components. \"Integer addition\" and Fig 2a.\n" + " - [Improved quantum circuits for elliptic curve discrete logarithms](https://arxiv.org/abs/2001.09580). Haner et al. 2020. Section 3: Components. \"Integer addition\" and Fig 2a.\n" ] }, { "cell_type": "code", "execution_count": null, - "id": "cd255bf9", + "id": "3155864a", "metadata": { "cq.autogen": "AddK.bloq_doc.py" }, @@ -348,7 +349,7 @@ }, { "cell_type": "markdown", - "id": "7538f9a5", + "id": "9009b701", "metadata": { "cq.autogen": "AddK.example_instances.md" }, @@ -359,43 +360,43 @@ { "cell_type": "code", "execution_count": null, - "id": "4305289f", + "id": "7e2a9fdf", "metadata": { "cq.autogen": "AddK.add_k" }, "outputs": [], "source": [ "n, k = sympy.symbols('n k')\n", - "add_k = AddK(bitsize=n, k=k)" + "add_k = AddK(QUInt(n), k=k)" ] }, { "cell_type": "code", "execution_count": null, - "id": "f6048819", + "id": "f4e218da", "metadata": { "cq.autogen": "AddK.add_k_small" }, "outputs": [], "source": [ - "add_k_small = AddK(bitsize=4, k=2, signed=False)" + "add_k_small = AddK(QUInt(4), k=2)" ] }, { "cell_type": "code", "execution_count": null, - "id": "b67fd469", + "id": "97073a76", "metadata": { "cq.autogen": "AddK.add_k_large" }, "outputs": [], "source": [ - "add_k_large = AddK(bitsize=64, k=-23, signed=True)" + "add_k_large = AddK(QInt(64), k=-23)" ] }, { "cell_type": "markdown", - "id": "b8b04228", + "id": "57f1032d", "metadata": { "cq.autogen": "AddK.graphical_signature.md" }, @@ -406,7 +407,7 @@ { "cell_type": "code", "execution_count": null, - "id": "e93e7f2e", + "id": "d5d7dad4", "metadata": { "cq.autogen": "AddK.graphical_signature.py" }, @@ -419,7 +420,7 @@ }, { "cell_type": "markdown", - "id": "13552795", + "id": "f1a51ffb", "metadata": { "cq.autogen": "AddK.call_graph.md" }, @@ -430,7 +431,7 @@ { "cell_type": "code", "execution_count": null, - "id": "d8d6584e", + "id": "cbfc2d39", "metadata": { "cq.autogen": "AddK.call_graph.py" }, @@ -441,37 +442,16 @@ "show_call_graph(add_k_g)\n", "show_counts_sigma(add_k_sigma)" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8853ae5d", - "metadata": { - "cq.autogen": "Add.add_diff_size_regs" - }, - "outputs": [], - "source": [ - "add_diff_size_regs = Add(QUInt(bitsize=4), QUInt(bitsize=16))" - ] } ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.7" + "name": "python" } }, "nbformat": 4, diff --git a/qualtran/bloqs/arithmetic/addition.py b/qualtran/bloqs/arithmetic/addition.py index 17ca331a3..d2292ea95 100644 --- a/qualtran/bloqs/arithmetic/addition.py +++ b/qualtran/bloqs/arithmetic/addition.py @@ -11,19 +11,9 @@ # 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. +from collections import Counter from functools import cached_property -from typing import ( - Dict, - Iterable, - Iterator, - List, - Optional, - Sequence, - Set, - Tuple, - TYPE_CHECKING, - Union, -) +from typing import Dict, Iterator, List, Optional, Sequence, Tuple, TYPE_CHECKING, Union import attrs import cirq @@ -42,7 +32,7 @@ CtrlSpec, DecomposeTypeError, GateWithRegisters, - QBit, + QAny, QInt, QMontgomeryUInt, QUInt, @@ -52,24 +42,18 @@ Soquet, SoquetT, ) -from qualtran.bloqs.basic_gates import CNOT, XGate -from qualtran.bloqs.mcmt import MultiControlX +from qualtran.bloqs.basic_gates import CNOT from qualtran.bloqs.mcmt.and_bloq import And from qualtran.cirq_interop import decompose_from_cirq_style_method from qualtran.drawing import directional_text_box, Text from qualtran.resource_counting.generalizers import ignore_split_join from qualtran.simulation.classical_sim import add_ints +from qualtran.symbolics import is_symbolic, SymbolicInt if TYPE_CHECKING: from qualtran.drawing import WireSymbol - from qualtran.resource_counting import ( - BloqCountDictT, - BloqCountT, - MutableBloqCountDictT, - SympySymbolAllocator, - ) + from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator from qualtran.simulation.classical_sim import ClassicalValT - from qualtran.symbolics import SymbolicInt @frozen @@ -260,11 +244,15 @@ class OutOfPlaceAdder(GateWithRegisters, cirq.ArithmeticGate): # type: ignore[m Args: bitsize: Number of bits used to represent each input integer. The allocated output register is of size `bitsize+1` so it has enough space to hold the sum of `a+b`. + is_adjoint: Whether this is compute or uncompute version. + include_most_significant_bit: Whether to add an extra most significant (i.e. carry) bit. Registers: a: A bitsize-sized input register (register a above). b: A bitsize-sized input register (register b above). - c: A bitize+1-sized LEFT/RIGHT register depending on whether the gate adjoint or not. + c: The LEFT/RIGHT register depending on whether the gate adjoint or not. + This register size is either bitsize or bitsize+1 depending on + the value of `include_most_significant_bit`. References: [Halving the cost of quantum addition](https://arxiv.org/abs/1709.06648) @@ -272,6 +260,11 @@ class OutOfPlaceAdder(GateWithRegisters, cirq.ArithmeticGate): # type: ignore[m bitsize: 'SymbolicInt' is_adjoint: bool = False + include_most_significant_bit: bool = True + + @property + def out_bitsize(self): + return self.bitsize + (1 if self.include_most_significant_bit else 0) @property def signature(self): @@ -280,14 +273,14 @@ def signature(self): [ Register('a', QUInt(self.bitsize)), Register('b', QUInt(self.bitsize)), - Register('c', QUInt(self.bitsize + 1), side=side), + Register('c', QUInt(self.out_bitsize), side=side), ] ) def registers(self) -> Sequence[Union[int, Sequence[int]]]: if not isinstance(self.bitsize, int): raise ValueError(f'Symbolic bitsize {self.bitsize} not supported') - return [2] * self.bitsize, [2] * self.bitsize, [2] * (self.bitsize + 1) + return [2] * self.bitsize, [2] * self.bitsize, [2] * self.out_bitsize def apply(self, a: int, b: int, c: int) -> Tuple[int, int, int]: return a, b, c + a + b @@ -307,7 +300,7 @@ def on_classical_vals( return { 'a': a, 'b': b, - 'c': add_ints(int(a), int(b), num_bits=self.bitsize + 1, is_signed=False), + 'c': add_ints(int(a), int(b), num_bits=self.out_bitsize, is_signed=False), } def with_registers(self, *new_registers: Union[int, Sequence[int]]): @@ -328,12 +321,19 @@ def decompose_from_registers( cirq.CX(a[i], c[i + 1]), cirq.CX(b[i], c[i]), ] - for i in range(self.bitsize) + for i in range(self.out_bitsize - 1) ] + if not self.include_most_significant_bit: + # Update c[-1] as c[-1] ^= a[-1]^b[-1] + i = self.bitsize - 1 + optree.append([cirq.CX(a[i], c[i]), cirq.CX(b[i], c[i])]) return cirq.inverse(optree) if self.is_adjoint else optree def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': - return {And(uncompute=self.is_adjoint): self.bitsize, CNOT(): 5 * self.bitsize} + return { + And(uncompute=self.is_adjoint): self.out_bitsize - 1, + CNOT(): 5 * (self.bitsize - 1) + 2 + (3 if self.include_most_significant_bit else 0), + } def __pow__(self, power: int): if power == 1: @@ -389,180 +389,119 @@ class AddK(Bloq): only clifford operations. Args: - bitsize: Number of bits used to represent each integer. + dtype: data type of the input register `x` k: The classical integer value to be added to x. - cvs: A tuple of control values. Each entry specifies whether that control line is a - "positive" control (`cv[i]=1`) or a "negative" control (`cv[i]=0`). - signed: A boolean condition which controls whether the x register holds a value represented - in 2's Complement or Unsigned. This affects the ability to add a negative constant. + is_controlled: if True, construct a singly-controlled bloq. Registers: - x: A bitsize-sized input register (register x above). + x: register of type `self.dtype` References: [Improved quantum circuits for elliptic curve discrete logarithms](https://arxiv.org/abs/2001.09580). - Haner et. al. 2020. Section 3: Components. "Integer addition" and Fig 2a. + Haner et al. 2020. Section 3: Components. "Integer addition" and Fig 2a. """ - bitsize: 'SymbolicInt' + dtype: Union[QInt, QUInt, QMontgomeryUInt] k: 'SymbolicInt' - cvs: Tuple[int, ...] = field(converter=_cvs_converter, default=()) - signed: bool = False + is_controlled: bool = False + + def __attrs_post_init__(self): + if not isinstance(self.dtype, (QInt, QUInt, QMontgomeryUInt)): + raise NotImplementedError( + "Only QInt, QUInt and QMontgomeryUInt types are supported for composite addition." + ) @cached_property def signature(self) -> 'Signature': - if len(self.cvs) > 0: - return Signature( - [ - Register('ctrls', QBit(), shape=(len(self.cvs),)), - Register('x', QInt(self.bitsize) if self.signed else QUInt(self.bitsize)), - ] - ) - else: - return Signature( - [Register('x', QInt(bitsize=self.bitsize) if self.signed else QUInt(self.bitsize))] - ) + return Signature.build_from_dtypes(ctrl=QAny(1 if self.is_controlled else 0), x=self.dtype) def on_classical_vals( self, x: 'ClassicalValT', **vals: 'ClassicalValT' ) -> Dict[str, 'ClassicalValT']: - if isinstance(self.k, sympy.Expr) or isinstance(self.bitsize, sympy.Expr): + if is_symbolic(self.k) or is_symbolic(self.dtype): raise ValueError(f"Classical simulation isn't supported for symbolic block {self}") - N = 2**self.bitsize - if len(self.cvs) > 0: - ctrls = vals['ctrls'] - else: - return { - 'x': add_ints(int(x), int(self.k), num_bits=self.bitsize, is_signed=self.signed) - } - if np.all(self.cvs == ctrls): - x = add_ints(int(x), int(self.k), num_bits=self.bitsize, is_signed=self.signed) + if not self.is_controlled or vals['ctrl']: + is_signed = isinstance(self.dtype, QInt) + x = add_ints(int(x), int(self.k), num_bits=self.dtype.num_qubits, is_signed=is_signed) + + return vals | {'x': x} + + @cached_property + def _load_k_bloq(self) -> Bloq: + from qualtran.bloqs.arithmetic.bitwise import XorK - return {'ctrls': ctrls, 'x': x} + k = self.k + if not is_symbolic(k) and k < 0 and isinstance(self.dtype, (QUInt, QMontgomeryUInt)): + # Since this is unsigned addition, adding `-v` is equivalent to adding `2**bitsize - v` + k %= 2**self.dtype.bitsize + + xork = XorK(self.dtype, k) + return xork.controlled() if self.is_controlled else xork def build_composite_bloq( - self, bb: 'BloqBuilder', x: Soquet, **regs: SoquetT + self, bb: 'BloqBuilder', x: Soquet, **soqs: Soquet ) -> Dict[str, 'SoquetT']: - if isinstance(self.k, sympy.Expr) or isinstance(self.bitsize, sympy.Expr): + if is_symbolic(self.k) or is_symbolic(self.dtype): raise DecomposeTypeError(f"Cannot decompose symbolic {self}.") - # Assign registers to variables and allocate ancilla bits for classical integer k. - if len(self.cvs) > 0: - ctrls = regs['ctrls'] - else: - ctrls = None - k = bb.allocate(dtype=x.reg.dtype) - - # Get binary representation of k and split k into separate wires. - k_split = bb.split(k) - if self.signed: - binary_rep = QInt(self.bitsize).to_bits(self.k) - else: - val = self.k - if val < 0: - # Since this is unsigned addition adding -v is equivalent to - # adding 2^bitsize - v - val %= 2**self.bitsize - binary_rep = QUInt(self.bitsize).to_bits(val) - - # Apply XGates to qubits in k where the bitstring has value 1. Apply CNOTs when the gate is - # controlled. - for i in range(self.bitsize): - if binary_rep[i] == 1: - if len(self.cvs) > 0 and ctrls is not None: - ctrls, k_split[i] = bb.add( - MultiControlX(cvs=self.cvs), controls=ctrls, target=k_split[i] - ) - else: - k_split[i] = bb.add(XGate(), q=k_split[i]) - - # Rejoin the qubits representing k for in-place addition. - k = bb.join(k_split, dtype=x.reg.dtype) - if not isinstance(x.reg.dtype, (QInt, QUInt, QMontgomeryUInt)): - raise ValueError( - "Only QInt, QUInt and QMontgomerUInt types are supported for composite addition." - ) - k, x = bb.add(Add(x.reg.dtype, x.reg.dtype), a=k, b=x) - - # Resplit the k qubits in order to undo the original bit flips to go from the binary - # representation back to the zero state. - k_split = bb.split(k) - for i in range(self.bitsize): - if binary_rep[i] == 1: - if len(self.cvs) > 0 and ctrls is not None: - ctrls, k_split[i] = bb.add( - MultiControlX(cvs=self.cvs), controls=ctrls, target=k_split[i] - ) - else: - k_split[i] = bb.add(XGate(), q=k_split[i]) - - # Free the ancilla qubits. - k = bb.join(k_split, dtype=x.reg.dtype) + # load `k` (conditional on ctrl if present) + k = bb.allocate(dtype=self.dtype) + load_soqs = {'x': k} + if self.is_controlled: + load_soqs |= {'ctrl': soqs.pop('ctrl')} + load_soqs = bb.add_d(self._load_k_bloq, **load_soqs) + k = load_soqs.pop('x') + + # quantum-quantum addition + k, x = bb.add(Add(self.dtype, self.dtype), a=k, b=x) + + # unload `k` + load_soqs['x'] = k + load_soqs = bb.add_d(self._load_k_bloq.adjoint(), **load_soqs) + k = load_soqs.pop('x') + assert isinstance(k, Soquet) bb.free(k) - # Return the output registers. - if len(self.cvs) > 0 and ctrls is not None: - return {'ctrls': ctrls, 'x': x} - else: - return {'x': x} - - def build_call_graph( - self, ssa: 'SympySymbolAllocator' - ) -> Union['BloqCountDictT', Set['BloqCountT']]: - loading_cost: MutableBloqCountDictT - if len(self.cvs) == 0: - loading_cost = {XGate(): self.bitsize} # upper bound; depends on the data. - elif len(self.cvs) == 1: - loading_cost = {CNOT(): self.bitsize} # upper bound; depends on the data. - else: - # Otherwise, use the decomposition - return super().build_call_graph(ssa=ssa) - loading_cost[Add(QUInt(self.bitsize))] = 1 - return loading_cost - - def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> Tuple['Bloq', 'AddControlledT']: - if self.cvs: - # We're already controlled, use default fallback - return super().get_ctrl_system(ctrl_spec) - - if ctrl_spec.num_ctrl_reg != 1: - # Multiple control registers, use default fallback - return super().get_ctrl_system(ctrl_spec) + return {'x': x} | load_soqs - ((qdtype, cv_shape),) = ctrl_spec.activation_function_dtypes() - if qdtype != QBit(): - # Control values aren't bits, use default fallback - return super().get_ctrl_system(ctrl_spec) + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': + counts = Counter[Bloq]() - # Supported via this class's custom `cvs` attribute. - bloq = attrs.evolve(self, cvs=ctrl_spec.cvs) + counts[self._load_k_bloq] += 1 + counts[Add(self.dtype, self.dtype)] += 1 + counts[self._load_k_bloq.adjoint()] += 1 - def _add_ctrled( - bb: 'BloqBuilder', ctrl_soqs: Sequence['SoquetT'], in_soqs: Dict[str, 'SoquetT'] - ) -> Tuple[Iterable['SoquetT'], Iterable['SoquetT']]: - ctrls, x = bb.add_t(bloq, ctrls=np.asarray(ctrl_soqs), **in_soqs) - return np.asarray(ctrls).tolist(), (x,) + return counts - return bloq, _add_ctrled + def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> Tuple['Bloq', 'AddControlledT']: + from qualtran.bloqs.mcmt.specialized_ctrl import get_ctrl_system_1bit_cv_from_bloqs + + return get_ctrl_system_1bit_cv_from_bloqs( + self, + ctrl_spec, + current_ctrl_bit=1 if self.is_controlled else None, + bloq_with_ctrl=attrs.evolve(self, is_controlled=True), + ctrl_reg_name='ctrl', + ) @bloq_example(generalizer=ignore_split_join) def _add_k() -> AddK: n, k = sympy.symbols('n k') - add_k = AddK(bitsize=n, k=k) + add_k = AddK(QUInt(n), k=k) return add_k @bloq_example(generalizer=ignore_split_join) def _add_k_small() -> AddK: - add_k_small = AddK(bitsize=4, k=2, signed=False) + add_k_small = AddK(QUInt(4), k=2) return add_k_small @bloq_example(generalizer=ignore_split_join) def _add_k_large() -> AddK: - add_k_large = AddK(bitsize=64, k=-23, signed=True) + add_k_large = AddK(QInt(64), k=-23) return add_k_large diff --git a/qualtran/bloqs/arithmetic/addition_test.py b/qualtran/bloqs/arithmetic/addition_test.py index 7f3576862..0a055595d 100644 --- a/qualtran/bloqs/arithmetic/addition_test.py +++ b/qualtran/bloqs/arithmetic/addition_test.py @@ -20,7 +20,7 @@ import sympy import qualtran.testing as qlt_testing -from qualtran import BloqBuilder, CtrlSpec, QInt, QUInt +from qualtran import Bloq, BloqBuilder, CtrlSpec, QInt, QUInt from qualtran.bloqs.arithmetic.addition import ( _add_diff_size_regs, _add_k, @@ -36,6 +36,7 @@ AddK, OutOfPlaceAdder, ) +from qualtran.bloqs.basic_gates import XGate from qualtran.bloqs.mcmt.and_bloq import And from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.cirq_interop.testing import assert_circuit_inp_out_cirqsim, GateHelper @@ -315,16 +316,19 @@ def test_out_of_place_adder(): def test_controlled_add_k(): n, k = sympy.symbols('n k') - addk = AddK(n, k) - assert addk.controlled() == AddK(n, k, cvs=(1,)) - assert addk.controlled(CtrlSpec(cvs=0)) == AddK(n, k, cvs=(0,)) + addk = AddK(QUInt(n), k) + assert addk.controlled() == AddK(QUInt(n), k, is_controlled=True) + _, sigma = addk.controlled(CtrlSpec(cvs=0)).call_graph(max_depth=1) + assert sigma == {addk.controlled(): 1, XGate(): 2} @pytest.mark.parametrize('bitsize', [5]) @pytest.mark.parametrize('k', [5, 8]) @pytest.mark.parametrize('cvs', [[], [0, 1], [1, 0], [1, 1]]) def test_add_k_decomp_unsigned(bitsize, k, cvs): - bloq = AddK(bitsize=bitsize, k=k, cvs=cvs, signed=False) + bloq: Bloq = AddK(QUInt(bitsize), k=k) + if cvs: + bloq = bloq.controlled(CtrlSpec(cvs=cvs)) qlt_testing.assert_valid_bloq_decomposition(bloq) @@ -332,7 +336,9 @@ def test_add_k_decomp_unsigned(bitsize, k, cvs): @pytest.mark.parametrize('k', [-5, 8]) @pytest.mark.parametrize('cvs', [[], [0, 1], [1, 0], [1, 1]]) def test_add_k_decomp_signed(bitsize, k, cvs): - bloq = AddK(bitsize=bitsize, k=k, cvs=cvs, signed=True) + bloq: Bloq = AddK(QInt(bitsize), k=k) + if cvs: + bloq = bloq.controlled(CtrlSpec(cvs=cvs)) qlt_testing.assert_valid_bloq_decomposition(bloq) @@ -340,16 +346,18 @@ def test_add_k_decomp_signed(bitsize, k, cvs): 'bitsize,k,x,cvs,ctrls,result', [ (5, 1, 2, (), (), 3), - (5, 3, 2, (1,), (1,), 5), + (5, 3, 2, (1,), 1, 5), (5, 2, 0, (1, 0), (1, 0), 2), (5, 1, 2, (1, 0, 1), (0, 0, 0), 2), ], ) def test_classical_add_k_unsigned(bitsize, k, x, cvs, ctrls, result): - bloq = AddK(bitsize=bitsize, k=k, cvs=cvs, signed=False) + bloq: Bloq = AddK(QUInt(bitsize), k=k) + if cvs: + bloq = bloq.controlled(CtrlSpec(cvs=cvs)) cbloq = bloq.decompose_bloq() - bloq_classical = bloq.call_classically(ctrls=ctrls, x=x) - cbloq_classical = cbloq.call_classically(ctrls=ctrls, x=x) + bloq_classical = bloq.call_classically(ctrl=ctrls, x=x) + cbloq_classical = cbloq.call_classically(ctrl=ctrls, x=x) assert len(bloq_classical) == len(cbloq_classical) for i in range(len(bloq_classical)): @@ -369,10 +377,12 @@ def test_classical_add_signed_overflow(bitsize): 'bitsize,k,x,cvs,ctrls,result', [(5, 2, 0, (1, 0), (1, 0), 2), (6, -3, 2, (), (), -1)] ) def test_classical_add_k_signed(bitsize, k, x, cvs, ctrls, result): - bloq = AddK(bitsize=bitsize, k=k, cvs=cvs, signed=True) + bloq: Bloq = AddK(QInt(bitsize), k=k) + if cvs: + bloq = bloq.controlled(CtrlSpec(cvs=cvs)) cbloq = bloq.decompose_bloq() - bloq_classical = bloq.call_classically(ctrls=ctrls, x=x) - cbloq_classical = cbloq.call_classically(ctrls=ctrls, x=x) + bloq_classical = bloq.call_classically(ctrl=ctrls, x=x) + cbloq_classical = cbloq.call_classically(ctrl=ctrls, x=x) assert len(bloq_classical) == len(cbloq_classical) for i in range(len(bloq_classical)): diff --git a/qualtran/bloqs/arithmetic/comparison.ipynb b/qualtran/bloqs/arithmetic/comparison.ipynb index 7001178f8..5183ede00 100644 --- a/qualtran/bloqs/arithmetic/comparison.ipynb +++ b/qualtran/bloqs/arithmetic/comparison.ipynb @@ -485,7 +485,7 @@ "https://github.com/quantumlib/Qualtran/issues/389\n", "\n", "#### References\n", - " - Supplementary Materials: Improved Techniques for Preparing Eigenstates of Fermionic Hamiltonians https://static-content.springer.com/esm/art%3A10.1038%2Fs41534-018-0071-5/MediaObjects/41534_2018_71_MOESM1_ESM.pdf\n" + " - [Supplementary Materials: Improved Techniques for Preparing Eigenstates of Fermionic Hamiltonians](https://static-content.springer.com/esm/art%3A10.1038%2Fs41534-018-0071-5/MediaObjects/41534_2018_71_MOESM1_ESM.pdf).\n" ] }, { @@ -702,7 +702,7 @@ "\n", "#### References\n", " - [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). \n", - " - Supplementary Materials: Improved Techniques for Preparing Eigenstates of Fermionic Hamiltonians. https://static-content.springer.com/esm/art%3A10.1038%2Fs41534-018-0071-5/MediaObjects/41534_2018_71_MOESM1_ESM.pdf\n" + " - [Supplementary Materials: Improved Techniques for Preparing Eigenstates of Fermionic Hamiltonians](https://static-content.springer.com/esm/art%3A10.1038%2Fs41534-018-0071-5/MediaObjects/41534_2018_71_MOESM1_ESM.pdf).\n" ] }, { @@ -918,6 +918,560 @@ "show_call_graph(clineardepthgreaterthan_example_g)\n", "show_counts_sigma(clineardepthgreaterthan_example_sigma)" ] + }, + { + "cell_type": "markdown", + "id": "126df51e", + "metadata": { + "cq.autogen": "Equals.bloq_doc.md" + }, + "source": [ + "## `Equals`\n", + "Implements |x>|y>|t> => |x>|y>|t ⨁ (x = y)> using $n-1$ Toffoli gates.\n", + "\n", + "#### Parameters\n", + " - `dtype`: Data type of the input registers `x` and `y`. \n", + "\n", + "#### Registers\n", + " - `x`: First input register.\n", + " - `y`: Second input register.\n", + " - `target`: Register to hold result of comparison.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ef979281", + "metadata": { + "cq.autogen": "Equals.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.arithmetic import Equals" + ] + }, + { + "cell_type": "markdown", + "id": "2ceb3c0c", + "metadata": { + "cq.autogen": "Equals.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b311ac0f", + "metadata": { + "cq.autogen": "Equals.equals" + }, + "outputs": [], + "source": [ + "equals = Equals(QUInt(4))" + ] + }, + { + "cell_type": "markdown", + "id": "cc0f1db8", + "metadata": { + "cq.autogen": "Equals.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "088efd0f", + "metadata": { + "cq.autogen": "Equals.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([equals],\n", + " ['`equals`'])" + ] + }, + { + "cell_type": "markdown", + "id": "9f8b5abc", + "metadata": { + "cq.autogen": "Equals.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7474f07a", + "metadata": { + "cq.autogen": "Equals.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "equals_g, equals_sigma = equals.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(equals_g)\n", + "show_counts_sigma(equals_sigma)" + ] + }, + { + "cell_type": "markdown", + "id": "e2db7c5d", + "metadata": { + "cq.autogen": "LinearDepthHalfGreaterThan.bloq_doc.md" + }, + "source": [ + "## `LinearDepthHalfGreaterThan`\n", + "Compare two integers while keeping necessary ancillas for zero cost uncomputation.\n", + "\n", + "Implements $\\ket{a}\\ket{b}\\ket{0}\\ket{0} \\rightarrow \\ket{a}\\ket{b}\\ket{b-a}\\ket{a>b}$ using $n$ And gates.\n", + "\n", + "This comparator relies on the fact that c = (b' + a)' = b - a. If a > b, then b - a < 0. We\n", + "implement it by flipping all the bits in b, computing the first half of the addition circuit,\n", + "copying out the carry, and keeping $c$ for the uncomputation.\n", + "\n", + "#### Parameters\n", + " - `dtype`: dtype of the two integers a and b.\n", + " - `uncompute`: whether this bloq uncomputes or computes the comparison. \n", + "\n", + "#### Registers\n", + " - `a`: first input register.\n", + " - `b`: second input register.\n", + " - `c`: ancilla register that will contain $b-a$ and will be used for uncomputation.\n", + " - `target`: A single bit output register to store the result of a > b. \n", + "\n", + "#### References\n", + " - [Halving the cost of quantum addition](https://arxiv.org/abs/1709.06648). \n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "758a6e35", + "metadata": { + "cq.autogen": "LinearDepthHalfGreaterThan.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.arithmetic import LinearDepthHalfGreaterThan" + ] + }, + { + "cell_type": "markdown", + "id": "3ea5a7bc", + "metadata": { + "cq.autogen": "LinearDepthHalfGreaterThan.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "26e4245f", + "metadata": { + "cq.autogen": "LinearDepthHalfGreaterThan.lineardepthhalfgreaterthan_small" + }, + "outputs": [], + "source": [ + "lineardepthhalfgreaterthan_small = LinearDepthHalfGreaterThan(QUInt(3))" + ] + }, + { + "cell_type": "markdown", + "id": "29abac9f", + "metadata": { + "cq.autogen": "LinearDepthHalfGreaterThan.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d065b007", + "metadata": { + "cq.autogen": "LinearDepthHalfGreaterThan.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([lineardepthhalfgreaterthan_small],\n", + " ['`lineardepthhalfgreaterthan_small`'])" + ] + }, + { + "cell_type": "markdown", + "id": "d67e1888", + "metadata": { + "cq.autogen": "LinearDepthHalfGreaterThan.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "32952025", + "metadata": { + "cq.autogen": "LinearDepthHalfGreaterThan.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "lineardepthhalfgreaterthan_small_g, lineardepthhalfgreaterthan_small_sigma = lineardepthhalfgreaterthan_small.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(lineardepthhalfgreaterthan_small_g)\n", + "show_counts_sigma(lineardepthhalfgreaterthan_small_sigma)" + ] + }, + { + "cell_type": "markdown", + "id": "9c39992e", + "metadata": { + "cq.autogen": "LinearDepthHalfGreaterThanEqual.bloq_doc.md" + }, + "source": [ + "## `LinearDepthHalfGreaterThanEqual`\n", + "Compare two integers while keeping necessary ancillas for zero cost uncomputation.\n", + "\n", + "Implements $\\ket{a}\\ket{b}\\ket{0}\\ket{0} \\rightarrow \\ket{a}\\ket{b}\\ket{a-b}\\ket{a \\geq b}$ using $n$ And gates.\n", + "\n", + "This comparator relies on the fact that c = (b' + a)' = b - a. If a > b, then b - a < 0. We\n", + "implement it by flipping all the bits in b, computing the first half of the addition circuit,\n", + "copying out the carry, and keeping $c$ for the uncomputation.\n", + "\n", + "#### Parameters\n", + " - `dtype`: dtype of the two integers a and b.\n", + " - `uncompute`: whether this bloq uncomputes or computes the comparison. \n", + "\n", + "#### Registers\n", + " - `a`: first input register.\n", + " - `b`: second input register.\n", + " - `c`: ancilla register that will contain $b-a$ and will be used for uncomputation.\n", + " - `target`: A single bit output register to store the result of a >= b. \n", + "\n", + "#### References\n", + " - [Halving the cost of quantum addition](https://arxiv.org/abs/1709.06648). \n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "58e6973f", + "metadata": { + "cq.autogen": "LinearDepthHalfGreaterThanEqual.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.arithmetic import LinearDepthHalfGreaterThanEqual" + ] + }, + { + "cell_type": "markdown", + "id": "32dae58a", + "metadata": { + "cq.autogen": "LinearDepthHalfGreaterThanEqual.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eded8868", + "metadata": { + "cq.autogen": "LinearDepthHalfGreaterThanEqual.lineardepthhalfgreaterthanequal_small" + }, + "outputs": [], + "source": [ + "lineardepthhalfgreaterthanequal_small = LinearDepthHalfGreaterThanEqual(QUInt(3))" + ] + }, + { + "cell_type": "markdown", + "id": "0edbf55b", + "metadata": { + "cq.autogen": "LinearDepthHalfGreaterThanEqual.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5326975b", + "metadata": { + "cq.autogen": "LinearDepthHalfGreaterThanEqual.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([lineardepthhalfgreaterthanequal_small],\n", + " ['`lineardepthhalfgreaterthanequal_small`'])" + ] + }, + { + "cell_type": "markdown", + "id": "eecfbf65", + "metadata": { + "cq.autogen": "LinearDepthHalfGreaterThanEqual.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "abfbe19f", + "metadata": { + "cq.autogen": "LinearDepthHalfGreaterThanEqual.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "lineardepthhalfgreaterthanequal_small_g, lineardepthhalfgreaterthanequal_small_sigma = lineardepthhalfgreaterthanequal_small.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(lineardepthhalfgreaterthanequal_small_g)\n", + "show_counts_sigma(lineardepthhalfgreaterthanequal_small_sigma)" + ] + }, + { + "cell_type": "markdown", + "id": "19744b75", + "metadata": { + "cq.autogen": "LinearDepthHalfLessThan.bloq_doc.md" + }, + "source": [ + "## `LinearDepthHalfLessThan`\n", + "Compare two integers while keeping necessary ancillas for zero cost uncomputation.\n", + "\n", + "Implements $\\ket{a}\\ket{b}\\ket{0}\\ket{0} \\rightarrow \\ket{a}\\ket{b}\\ket{a-b}\\ket{a b, then b - a < 0. We\n", + "implement it by flipping all the bits in b, computing the first half of the addition circuit,\n", + "copying out the carry, and keeping $c$ for the uncomputation.\n", + "\n", + "#### Parameters\n", + " - `dtype`: dtype of the two integers a and b.\n", + " - `uncompute`: whether this bloq uncomputes or computes the comparison. \n", + "\n", + "#### Registers\n", + " - `a`: first input register.\n", + " - `b`: second input register.\n", + " - `c`: ancilla register that will contain $b-a$ and will be used for uncomputation.\n", + " - `target`: A single bit output register to store the result of a < b. \n", + "\n", + "#### References\n", + " - [Halving the cost of quantum addition](https://arxiv.org/abs/1709.06648). \n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1eec63a1", + "metadata": { + "cq.autogen": "LinearDepthHalfLessThan.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.arithmetic import LinearDepthHalfLessThan" + ] + }, + { + "cell_type": "markdown", + "id": "75142163", + "metadata": { + "cq.autogen": "LinearDepthHalfLessThan.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4759ae6d", + "metadata": { + "cq.autogen": "LinearDepthHalfLessThan.lineardepthhalflessthan_small" + }, + "outputs": [], + "source": [ + "lineardepthhalflessthan_small = LinearDepthHalfLessThan(QUInt(3))" + ] + }, + { + "cell_type": "markdown", + "id": "903efca4", + "metadata": { + "cq.autogen": "LinearDepthHalfLessThan.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bbbefc84", + "metadata": { + "cq.autogen": "LinearDepthHalfLessThan.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([lineardepthhalflessthan_small],\n", + " ['`lineardepthhalflessthan_small`'])" + ] + }, + { + "cell_type": "markdown", + "id": "8862065f", + "metadata": { + "cq.autogen": "LinearDepthHalfLessThan.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9be045c4", + "metadata": { + "cq.autogen": "LinearDepthHalfLessThan.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "lineardepthhalflessthan_small_g, lineardepthhalflessthan_small_sigma = lineardepthhalflessthan_small.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(lineardepthhalflessthan_small_g)\n", + "show_counts_sigma(lineardepthhalflessthan_small_sigma)" + ] + }, + { + "cell_type": "markdown", + "id": "8a868719", + "metadata": { + "cq.autogen": "LinearDepthHalfLessThanEqual.bloq_doc.md" + }, + "source": [ + "## `LinearDepthHalfLessThanEqual`\n", + "Compare two integers while keeping necessary ancillas for zero cost uncomputation.\n", + "\n", + "Implements $\\ket{a}\\ket{b}\\ket{0}\\ket{0} \\rightarrow \\ket{a}\\ket{b}\\ket{b-a}\\ket{a \\leq b}$ using $n$ And gates.\n", + "\n", + "This comparator relies on the fact that c = (b' + a)' = b - a. If a > b, then b - a < 0. We\n", + "implement it by flipping all the bits in b, computing the first half of the addition circuit,\n", + "copying out the carry, and keeping $c$ for the uncomputation.\n", + "\n", + "#### Parameters\n", + " - `dtype`: dtype of the two integers a and b.\n", + " - `uncompute`: whether this bloq uncomputes or computes the comparison. \n", + "\n", + "#### Registers\n", + " - `a`: first input register.\n", + " - `b`: second input register.\n", + " - `c`: ancilla register that will contain $b-a$ and will be used for uncomputation.\n", + " - `target`: A single bit output register to store the result of a <= b. \n", + "\n", + "#### References\n", + " - [Halving the cost of quantum addition](https://arxiv.org/abs/1709.06648). \n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5b4c9b03", + "metadata": { + "cq.autogen": "LinearDepthHalfLessThanEqual.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.arithmetic import LinearDepthHalfLessThanEqual" + ] + }, + { + "cell_type": "markdown", + "id": "bae993de", + "metadata": { + "cq.autogen": "LinearDepthHalfLessThanEqual.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c6610fd4", + "metadata": { + "cq.autogen": "LinearDepthHalfLessThanEqual.lineardepthhalflessthanequal_small" + }, + "outputs": [], + "source": [ + "lineardepthhalflessthanequal_small = LinearDepthHalfLessThanEqual(QUInt(3))" + ] + }, + { + "cell_type": "markdown", + "id": "72de4f8e", + "metadata": { + "cq.autogen": "LinearDepthHalfLessThanEqual.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fdd8d4c4", + "metadata": { + "cq.autogen": "LinearDepthHalfLessThanEqual.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([lineardepthhalflessthanequal_small],\n", + " ['`lineardepthhalflessthanequal_small`'])" + ] + }, + { + "cell_type": "markdown", + "id": "973be9d4", + "metadata": { + "cq.autogen": "LinearDepthHalfLessThanEqual.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9efd6db6", + "metadata": { + "cq.autogen": "LinearDepthHalfLessThanEqual.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "lineardepthhalflessthanequal_small_g, lineardepthhalflessthanequal_small_sigma = lineardepthhalflessthanequal_small.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(lineardepthhalflessthanequal_small_g)\n", + "show_counts_sigma(lineardepthhalflessthanequal_small_sigma)" + ] } ], "metadata": { diff --git a/qualtran/bloqs/arithmetic/comparison.py b/qualtran/bloqs/arithmetic/comparison.py index ff4986262..a47d95cff 100644 --- a/qualtran/bloqs/arithmetic/comparison.py +++ b/qualtran/bloqs/arithmetic/comparison.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import abc from collections import defaultdict from functools import cached_property from typing import Dict, Iterable, Iterator, List, Optional, Sequence, Tuple, TYPE_CHECKING, Union @@ -31,6 +32,7 @@ GateWithRegisters, QAny, QBit, + QDType, QInt, QMontgomeryUInt, QUInt, @@ -41,7 +43,7 @@ SoquetT, ) from qualtran.bloqs.arithmetic.addition import OutOfPlaceAdder -from qualtran.bloqs.arithmetic.bitwise import BitwiseNot +from qualtran.bloqs.arithmetic.bitwise import BitwiseNot, Xor from qualtran.bloqs.arithmetic.conversions.sign_extension import SignExtend from qualtran.bloqs.basic_gates import CNOT, XGate from qualtran.bloqs.bookkeeping import Cast @@ -50,6 +52,7 @@ from qualtran.drawing import WireSymbol from qualtran.drawing.musical_score import Circle, Text, TextBox from qualtran.resource_counting.generalizers import ignore_split_join +from qualtran.simulation.classical_sim import add_ints from qualtran.symbolics import HasLength, is_symbolic, SymbolicInt if TYPE_CHECKING: @@ -232,8 +235,7 @@ class BiQubitsMixer(GateWithRegisters): https://github.com/quantumlib/Qualtran/issues/389 References: - Supplementary Materials: Improved Techniques for Preparing Eigenstates of Fermionic Hamiltonians - https://static-content.springer.com/esm/art%3A10.1038%2Fs41534-018-0071-5/MediaObjects/41534_2018_71_MOESM1_ESM.pdf + [Supplementary Materials: Improved Techniques for Preparing Eigenstates of Fermionic Hamiltonians](https://static-content.springer.com/esm/art%3A10.1038%2Fs41534-018-0071-5/MediaObjects/41534_2018_71_MOESM1_ESM.pdf). """ is_adjoint: bool = False @@ -431,8 +433,7 @@ class LessThanEqual(GateWithRegisters, cirq.ArithmeticGate): # type: ignore[mis References: [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). - Supplementary Materials: Improved Techniques for Preparing Eigenstates of Fermionic Hamiltonians. - https://static-content.springer.com/esm/art%3A10.1038%2Fs41534-018-0071-5/MediaObjects/41534_2018_71_MOESM1_ESM.pdf + [Supplementary Materials: Improved Techniques for Preparing Eigenstates of Fermionic Hamiltonians](https://static-content.springer.com/esm/art%3A10.1038%2Fs41534-018-0071-5/MediaObjects/41534_2018_71_MOESM1_ESM.pdf). """ x_bitsize: 'SymbolicInt' @@ -947,6 +948,84 @@ def _gt_k() -> GreaterThanConstant: _GREATER_THAN_K_DOC = BloqDocSpec(bloq_cls=GreaterThanConstant, examples=[_gt_k]) +@frozen +class Equals(Bloq): + r"""Implements |x>|y>|t> => |x>|y>|t ⨁ (x = y)> using $n-1$ Toffoli gates. + + Args: + dtype: Data type of the input registers `x` and `y`. + + Registers: + x: First input register. + y: Second input register. + target: Register to hold result of comparison. + """ + + dtype: QDType + + @cached_property + def signature(self) -> Signature: + return Signature.build_from_dtypes(x=self.dtype, y=self.dtype, target=QBit()) + + @cached_property + def bitsize(self) -> SymbolicInt: + return self.dtype.num_qubits + + def is_symbolic(self): + return is_symbolic(self.dtype) + + def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -> WireSymbol: + if reg is None: + return Text("") + if reg.name == 'x': + return TextBox("In(x)") + if reg.name == 'y': + return TextBox("In(y)") + elif reg.name == 'target': + return TextBox("⨁(x = y)") + raise ValueError(f'Unknown register symbol {reg.name}') + + def build_composite_bloq( + self, bb: 'BloqBuilder', x: 'Soquet', y: 'Soquet', target: 'Soquet' + ) -> Dict[str, 'SoquetT']: + if is_symbolic(self.bitsize): + raise DecomposeTypeError(f"Cannot decompose {self} with symbolic `bitsize`.") + + cvs: Union[list[int], HasLength] + if isinstance(self.bitsize, int): + cvs = [0] * self.bitsize + else: + cvs = HasLength(self.bitsize) + + x, y = bb.add(Xor(self.dtype), x=x, y=y) + y_split = bb.split(y) + y_split, target = bb.add(MultiControlX(cvs=cvs), controls=y_split, target=target) + y = bb.join(y_split, self.dtype) + x, y = bb.add(Xor(self.dtype), x=x, y=y) + + return {'x': x, 'y': y, 'target': target} + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': + cvs: Union[list[int], HasLength] + if isinstance(self.bitsize, int): + cvs = [0] * self.bitsize + else: + cvs = HasLength(self.bitsize) + return {Xor(self.dtype): 2, MultiControlX(cvs=cvs): 1} + + def on_classical_vals(self, x: int, y: int, target: int) -> Dict[str, 'ClassicalValT']: + return {'x': x, 'y': y, 'target': target ^ (x == y)} + + +@bloq_example +def _equals() -> Equals: + equals = Equals(QUInt(4)) + return equals + + +_EQUALS_DOC = BloqDocSpec(bloq_cls=Equals, examples=[_equals]) + + @frozen class EqualsAConstant(Bloq): r"""Implements $U_a|x\rangle|z\rangle = |x\rangle |z \oplus (x = a)\rangle$ @@ -1075,6 +1154,8 @@ def wire_symbol( def build_composite_bloq( self, bb: 'BloqBuilder', ctrl: 'Soquet', a: 'Soquet', b: 'Soquet', target: 'Soquet' ) -> Dict[str, 'SoquetT']: + if is_symbolic(self.dtype.bitsize): + raise DecomposeTypeError(f"Cannot decompose {self} with symbolic `bitsize`.") if isinstance(self.dtype, QInt): a = bb.add(SignExtend(self.dtype, QInt(self.dtype.bitsize + 1)), x=a) @@ -1154,3 +1235,471 @@ def _clineardepthgreaterthan_example() -> CLinearDepthGreaterThan: _CLinearDepthGreaterThan_DOC = BloqDocSpec( bloq_cls=CLinearDepthGreaterThan, examples=[_clineardepthgreaterthan_example] ) + + +@frozen +class _HalfLinearDepthGreaterThan(Bloq): + """A concrete implementation of half-circuit for greater than. + + This bloq can be returned by the _HalfComparisonBase._half_greater_than_bloq abstract property. + + Args: + dtype: dtype of the two integers a and b. + uncompute: whether this bloq uncomputes or computes the comparison. + + Registers: + a: first input register. + b: second input register. + c: ancilla register that will contain $b-a$ and will be used for uncomputation. + target: A single bit output register to store the result of a > b. + + References: + [Halving the cost of quantum addition](https://arxiv.org/abs/1709.06648). + """ + + dtype: Union[QInt, QUInt, QMontgomeryUInt] + uncompute: bool = False + + @cached_property + def signature(self) -> Signature: + side = Side.LEFT if self.uncompute else Side.RIGHT + return Signature( + [ + Register('a', self.dtype), + Register('b', self.dtype), + Register('c', QUInt(bitsize=self.dtype.bitsize + 1), side=side), + Register('target', QBit(), side=side), + ] + ) + + def adjoint(self) -> '_HalfLinearDepthGreaterThan': + return attrs.evolve(self, uncompute=self.uncompute ^ True) + + def _compute(self, bb: 'BloqBuilder', a: 'Soquet', b: 'Soquet') -> Dict[str, 'SoquetT']: + if isinstance(self.dtype, QInt): + a = bb.add(SignExtend(self.dtype, QInt(self.dtype.bitsize + 1)), x=a) + b = bb.add(SignExtend(self.dtype, QInt(self.dtype.bitsize + 1)), x=b) + else: + a = bb.join(np.concatenate([[bb.allocate(1)], bb.split(a)])) + b = bb.join(np.concatenate([[bb.allocate(1)], bb.split(b)])) + + dtype = attrs.evolve(self.dtype, bitsize=self.dtype.bitsize + 1) + b = bb.add(BitwiseNot(dtype), x=b) # b := -b-1 + a = bb.add(Cast(dtype, QUInt(dtype.bitsize)), reg=a) + b = bb.add(Cast(dtype, QUInt(dtype.bitsize)), reg=b) + a, b, c = bb.add( + OutOfPlaceAdder(self.dtype.bitsize + 1, include_most_significant_bit=False), a=a, b=b + ) # c := a - b - 1 + c = bb.add(BitwiseNot(QUInt(dtype.bitsize)), x=c) # c := b - a + + # Update `target` + c_arr = bb.split(c) + target = bb.allocate(1) + c_arr[0], target = bb.add(CNOT(), ctrl=c_arr[0], target=target) + c = bb.join(c_arr) + + a = bb.add(Cast(dtype, QUInt(dtype.bitsize)).adjoint(), reg=a) + b = bb.add(Cast(dtype, QUInt(dtype.bitsize)).adjoint(), reg=b) + b = bb.add(BitwiseNot(dtype), x=b) + + if isinstance(self.dtype, QInt): + a = bb.add(SignExtend(self.dtype, QInt(self.dtype.bitsize + 1)).adjoint(), x=a) + b = bb.add(SignExtend(self.dtype, QInt(self.dtype.bitsize + 1)).adjoint(), x=b) + else: + a_arr = bb.split(a) + a = bb.join(a_arr[1:]) + b_arr = bb.split(b) + b = bb.join(b_arr[1:]) + bb.free(a_arr[0]) + bb.free(b_arr[0]) + return {'a': a, 'b': b, 'c': c, 'target': target} + + def _uncompute( + self, bb: 'BloqBuilder', a: 'Soquet', b: 'Soquet', c: 'Soquet', target: 'Soquet' + ) -> Dict[str, 'SoquetT']: + if isinstance(self.dtype, QInt): + a = bb.add(SignExtend(self.dtype, QInt(self.dtype.bitsize + 1)), x=a) + b = bb.add(SignExtend(self.dtype, QInt(self.dtype.bitsize + 1)), x=b) + else: + a = bb.join(np.concatenate([[bb.allocate(1)], bb.split(a)])) + b = bb.join(np.concatenate([[bb.allocate(1)], bb.split(b)])) + + dtype = attrs.evolve(self.dtype, bitsize=self.dtype.bitsize + 1) + b = bb.add(BitwiseNot(dtype), x=b) # b := -b-1 + a = bb.add(Cast(dtype, QUInt(dtype.bitsize)), reg=a) + b = bb.add(Cast(dtype, QUInt(dtype.bitsize)), reg=b) + + c_arr = bb.split(c) + c_arr[0], target = bb.add(CNOT(), ctrl=c_arr[0], target=target) + c = bb.join(c_arr) + + c = bb.add(BitwiseNot(QUInt(dtype.bitsize)), x=c) + a, b = bb.add( + OutOfPlaceAdder(self.dtype.bitsize + 1, include_most_significant_bit=False).adjoint(), + a=a, + b=b, + c=c, + ) + a = bb.add(Cast(dtype, QUInt(dtype.bitsize)).adjoint(), reg=a) + b = bb.add(Cast(dtype, QUInt(dtype.bitsize)).adjoint(), reg=b) + b = bb.add(BitwiseNot(dtype), x=b) + + if isinstance(self.dtype, QInt): + a = bb.add(SignExtend(self.dtype, QInt(self.dtype.bitsize + 1)).adjoint(), x=a) + b = bb.add(SignExtend(self.dtype, QInt(self.dtype.bitsize + 1)).adjoint(), x=b) + else: + a_arr = bb.split(a) + a = bb.join(a_arr[1:]) + b_arr = bb.split(b) + b = bb.join(b_arr[1:]) + bb.free(a_arr[0]) + bb.free(b_arr[0]) + bb.free(target) + return {'a': a, 'b': b} + + def build_composite_bloq( + self, + bb: 'BloqBuilder', + a: 'Soquet', + b: 'Soquet', + c: Optional['Soquet'] = None, + target: Optional['Soquet'] = None, + ) -> Dict[str, 'SoquetT']: + if is_symbolic(self.dtype.bitsize): + raise DecomposeTypeError(f"Cannot decompose {self} with symbolic `bitsize`.") + + if self.uncompute: + # Uncompute + assert c is not None + assert target is not None + return self._uncompute(bb, a, b, c, target) + else: + assert c is None + assert target is None + return self._compute(bb, a, b) + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': + dtype = attrs.evolve(self.dtype, bitsize=self.dtype.bitsize + 1) + counts: 'BloqCountDictT' + if isinstance(self.dtype, QUInt): + counts = {BitwiseNot(dtype): 3} + else: + counts = {BitwiseNot(dtype): 2, BitwiseNot(QUInt(dtype.bitsize)): 1} + + counts[CNOT()] = 1 + + adder = OutOfPlaceAdder(self.dtype.bitsize + 1, include_most_significant_bit=False) + if self.uncompute: + adder = adder.adjoint() + counts[adder] = 1 + + return counts + + +@frozen +class _HalfComparisonBase(Bloq): + """Parent class for the 4 comparison operations (>, >=, <, <=). + + The four comparison operations can be implemented by implementing only one of them + and computing the others either by reversing the input order, flipping the result or both. + + The choice made is to build the four opertions around greater than. Namely the greater than + bloq returned by `._half_greater_than_bloq`; By changing this property we can change + change the properties of the constructed circuit (e.g. complexity, depth, ..etc). + + For example _LinearDepthHalfComparisonBase sets the property to a linear depth construction, + other implementations can set the property to a log depth construction. + + Args: + dtype: dtype of the two integers a and b. + _op_symbol: The symbol of the comparison operation. + uncompute: whether this bloq uncomputes or computes the comparison. + + Registers: + a: first input register. + b: second input register. + c: ancilla register that will contain $b-a$ and will be used for uncomputation. + target: A single bit output register to store the result of a > b. + + References: + [Halving the cost of quantum addition](https://arxiv.org/abs/1709.06648). + """ + + dtype: Union[QInt, QUInt, QMontgomeryUInt] + _op_symbol: str = attrs.field( + default='>', validator=lambda _, __, s: s in ('>', '<', '>=', '<='), repr=False + ) + uncompute: bool = False + + @cached_property + def signature(self) -> Signature: + side = Side.LEFT if self.uncompute else Side.RIGHT + return Signature( + [ + Register('a', self.dtype), + Register('b', self.dtype), + Register('c', QUInt(bitsize=self.dtype.bitsize + 1), side=side), + Register('target', QBit(), side=side), + ] + ) + + def adjoint(self) -> '_HalfComparisonBase': + return attrs.evolve(self, uncompute=self.uncompute ^ True) + + @cached_property + @abc.abstractmethod + def _half_greater_than_bloq(self) -> Bloq: + raise NotImplementedError() + + def _classical_comparison( + self, a: 'ClassicalValT', b: 'ClassicalValT' + ) -> Union[bool, np.bool_, NDArray[np.bool_]]: + if self._op_symbol == '>': + return a > b + elif self._op_symbol == '<': + return a < b + elif self._op_symbol == '>=': + return a >= b + else: + return a <= b + + def on_classical_vals( + self, + a: 'ClassicalValT', + b: 'ClassicalValT', + c: Optional['ClassicalValT'] = None, + target: Optional['ClassicalValT'] = None, + ) -> Dict[str, 'ClassicalValT']: + if self._op_symbol in ('>', '<='): + c_val = add_ints(-int(a), int(b), num_bits=self.dtype.bitsize + 1, is_signed=False) + else: + c_val = add_ints(int(a), -int(b), num_bits=self.dtype.bitsize + 1, is_signed=False) + if self.uncompute: + assert c == c_val + assert target == self._classical_comparison(a, b) + return {'a': a, 'b': b} + assert c is None + assert target is None + return {'a': a, 'b': b, 'c': c_val, 'target': int(self._classical_comparison(a, b))} + + def _compute(self, bb: 'BloqBuilder', a: 'Soquet', b: 'Soquet') -> Dict[str, 'SoquetT']: + if self._op_symbol in ('>', '<='): + a, b, c, target = bb.add_from(self._half_greater_than_bloq, a=a, b=b) # type: ignore + else: + b, a, c, target = bb.add_from(self._half_greater_than_bloq, a=b, b=a) # type: ignore + + if self._op_symbol in ('<=', '>='): + target = bb.add(XGate(), q=target) + + return {'a': a, 'b': b, 'c': c, 'target': target} + + def _uncompute( + self, bb: 'BloqBuilder', a: 'Soquet', b: 'Soquet', c: 'Soquet', target: 'Soquet' + ) -> Dict[str, 'SoquetT']: + if self._op_symbol in ('<=', '>='): + target = bb.add(XGate(), q=target) + + if self._op_symbol in ('>', '<='): + a, b = bb.add_from(self._half_greater_than_bloq.adjoint(), a=a, b=b, c=c, target=target) # type: ignore + else: + a, b = bb.add_from(self._half_greater_than_bloq.adjoint(), a=b, b=a, c=c, target=target) # type: ignore + + return {'a': a, 'b': b} + + def build_composite_bloq( + self, + bb: 'BloqBuilder', + a: 'Soquet', + b: 'Soquet', + c: Optional['Soquet'] = None, + target: Optional['Soquet'] = None, + ) -> Dict[str, 'SoquetT']: + if self.uncompute: + assert c is not None + assert target is not None + return self._uncompute(bb, a, b, c, target) + else: + assert c is None + assert target is None + return self._compute(bb, a, b) + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': + extra_ops = {} + if isinstance(self.dtype, QInt): + extra_ops = { + SignExtend(self.dtype, QInt(self.dtype.bitsize + 1)): 2, + SignExtend(self.dtype, QInt(self.dtype.bitsize + 1)).adjoint(): 2, + } + if self._op_symbol in ('>=', '<='): + extra_ops[XGate()] = 1 + adder = self._half_greater_than_bloq + if self.uncompute: + adder = adder.adjoint() + adder_call_graph = adder.build_call_graph(ssa) + assert isinstance(adder_call_graph, dict) + counts: defaultdict['Bloq', Union[int, sympy.Expr]] = defaultdict(lambda: 0) + counts.update(adder_call_graph) + for k, v in extra_ops.items(): + counts[k] += v + return counts + + +@frozen +class _LinearDepthHalfComparisonBase(_HalfComparisonBase): + """A wrapper around _HalfComparisonBase that sets ._half_greater_than_bloq property to a construction with linear depth.""" + + @cached_property + def _half_greater_than_bloq(self) -> Bloq: + return _HalfLinearDepthGreaterThan(self.dtype, uncompute=False) + + +@frozen +class LinearDepthHalfGreaterThan(_LinearDepthHalfComparisonBase): + r"""Compare two integers while keeping necessary ancillas for zero cost uncomputation. + + Implements $\ket{a}\ket{b}\ket{0}\ket{0} \rightarrow \ket{a}\ket{b}\ket{b-a}\ket{a>b}$ using $n$ And gates. + + This comparator relies on the fact that c = (b' + a)' = b - a. If a > b, then b - a < 0. We + implement it by flipping all the bits in b, computing the first half of the addition circuit, + copying out the carry, and keeping $c$ for the uncomputation. + + Args: + dtype: dtype of the two integers a and b. + uncompute: whether this bloq uncomputes or computes the comparison. + + Registers: + a: first input register. + b: second input register. + c: ancilla register that will contain $b-a$ and will be used for uncomputation. + target: A single bit output register to store the result of a > b. + + References: + [Halving the cost of quantum addition](https://arxiv.org/abs/1709.06648). + """ + + _op_symbol: str = attrs.field(default='>', repr=False, init=False) + + +@frozen +class LinearDepthHalfGreaterThanEqual(_LinearDepthHalfComparisonBase): + r"""Compare two integers while keeping necessary ancillas for zero cost uncomputation. + + Implements $\ket{a}\ket{b}\ket{0}\ket{0} \rightarrow \ket{a}\ket{b}\ket{a-b}\ket{a \geq b}$ using $n$ And gates. + + This comparator relies on the fact that c = (b' + a)' = b - a. If a > b, then b - a < 0. We + implement it by flipping all the bits in b, computing the first half of the addition circuit, + copying out the carry, and keeping $c$ for the uncomputation. + + Args: + dtype: dtype of the two integers a and b. + uncompute: whether this bloq uncomputes or computes the comparison. + + Registers: + a: first input register. + b: second input register. + c: ancilla register that will contain $b-a$ and will be used for uncomputation. + target: A single bit output register to store the result of a >= b. + + References: + [Halving the cost of quantum addition](https://arxiv.org/abs/1709.06648). + """ + + _op_symbol: str = attrs.field(default='>=', repr=False, init=False) + + +@frozen +class LinearDepthHalfLessThan(_LinearDepthHalfComparisonBase): + r"""Compare two integers while keeping necessary ancillas for zero cost uncomputation. + + Implements $\ket{a}\ket{b}\ket{0}\ket{0} \rightarrow \ket{a}\ket{b}\ket{a-b}\ket{a b, then b - a < 0. We + implement it by flipping all the bits in b, computing the first half of the addition circuit, + copying out the carry, and keeping $c$ for the uncomputation. + + Args: + dtype: dtype of the two integers a and b. + uncompute: whether this bloq uncomputes or computes the comparison. + + Registers: + a: first input register. + b: second input register. + c: ancilla register that will contain $b-a$ and will be used for uncomputation. + target: A single bit output register to store the result of a < b. + + References: + [Halving the cost of quantum addition](https://arxiv.org/abs/1709.06648). + """ + + _op_symbol: str = attrs.field(default='<', repr=False, init=False) + + +@frozen +class LinearDepthHalfLessThanEqual(_LinearDepthHalfComparisonBase): + r"""Compare two integers while keeping necessary ancillas for zero cost uncomputation. + + Implements $\ket{a}\ket{b}\ket{0}\ket{0} \rightarrow \ket{a}\ket{b}\ket{b-a}\ket{a \leq b}$ using $n$ And gates. + + This comparator relies on the fact that c = (b' + a)' = b - a. If a > b, then b - a < 0. We + implement it by flipping all the bits in b, computing the first half of the addition circuit, + copying out the carry, and keeping $c$ for the uncomputation. + + Args: + dtype: dtype of the two integers a and b. + uncompute: whether this bloq uncomputes or computes the comparison. + + Registers: + a: first input register. + b: second input register. + c: ancilla register that will contain $b-a$ and will be used for uncomputation. + target: A single bit output register to store the result of a <= b. + + References: + [Halving the cost of quantum addition](https://arxiv.org/abs/1709.06648). + """ + + _op_symbol: str = attrs.field(default='<=', repr=False, init=False) + + +@bloq_example +def _lineardepthhalfgreaterthan_small() -> LinearDepthHalfGreaterThan: + lineardepthhalfgreaterthan_small = LinearDepthHalfGreaterThan(QUInt(3)) + return lineardepthhalfgreaterthan_small + + +@bloq_example +def _lineardepthhalflessthan_small() -> LinearDepthHalfLessThan: + lineardepthhalflessthan_small = LinearDepthHalfLessThan(QUInt(3)) + return lineardepthhalflessthan_small + + +@bloq_example +def _lineardepthhalfgreaterthanequal_small() -> LinearDepthHalfGreaterThanEqual: + lineardepthhalfgreaterthanequal_small = LinearDepthHalfGreaterThanEqual(QUInt(3)) + return lineardepthhalfgreaterthanequal_small + + +@bloq_example +def _lineardepthhalflessthanequal_small() -> LinearDepthHalfLessThanEqual: + lineardepthhalflessthanequal_small = LinearDepthHalfLessThanEqual(QUInt(3)) + return lineardepthhalflessthanequal_small + + +_LINEAR_DEPTH_HALF_GREATERTHAN_DOC = BloqDocSpec( + bloq_cls=LinearDepthHalfGreaterThan, examples=[_lineardepthhalfgreaterthan_small] +) + + +_LINEAR_DEPTH_HALF_LESSTHAN_DOC = BloqDocSpec( + bloq_cls=LinearDepthHalfLessThan, examples=[_lineardepthhalflessthan_small] +) + + +_LINEAR_DEPTH_HALF_GREATERTHANEQUAL_DOC = BloqDocSpec( + bloq_cls=LinearDepthHalfGreaterThanEqual, examples=[_lineardepthhalfgreaterthanequal_small] +) + + +_LINEAR_DEPTH_HALF_LESSTHANEQUAL_DOC = BloqDocSpec( + bloq_cls=LinearDepthHalfLessThanEqual, examples=[_lineardepthhalflessthanequal_small] +) diff --git a/qualtran/bloqs/arithmetic/comparison_test.py b/qualtran/bloqs/arithmetic/comparison_test.py index 368d45d3c..9876537e8 100644 --- a/qualtran/bloqs/arithmetic/comparison_test.py +++ b/qualtran/bloqs/arithmetic/comparison_test.py @@ -20,26 +20,37 @@ import sympy import qualtran.testing as qlt_testing -from qualtran import BloqBuilder, QInt, QMontgomeryUInt, QUInt +from qualtran import BloqBuilder, QBit, QInt, QMontgomeryUInt, QUInt from qualtran.bloqs.arithmetic.comparison import ( _clineardepthgreaterthan_example, _eq_k, + _equals, _greater_than, _gt_k, _leq_symb, + _lineardepthhalfgreaterthan_small, + _lineardepthhalfgreaterthanequal_small, + _lineardepthhalflessthan_small, + _lineardepthhalflessthanequal_small, _lt_k_symb, BiQubitsMixer, CLinearDepthGreaterThan, + Equals, EqualsAConstant, GreaterThan, GreaterThanConstant, LessThanConstant, LessThanEqual, LinearDepthGreaterThan, + LinearDepthHalfGreaterThan, + LinearDepthHalfGreaterThanEqual, + LinearDepthHalfLessThan, + LinearDepthHalfLessThanEqual, SingleQubitCompare, ) from qualtran.cirq_interop.t_complexity_protocol import t_complexity, TComplexity from qualtran.cirq_interop.testing import assert_circuit_inp_out_cirqsim +from qualtran.resource_counting import get_cost_value, QECGatesCost from qualtran.resource_counting.generalizers import ignore_alloc_free, ignore_split_join @@ -63,6 +74,10 @@ def test_leq_symb(bloq_autotester): bloq_autotester(_leq_symb) +def test_equals(bloq_autotester): + bloq_autotester(_equals) + + def test_eq_k(bloq_autotester): bloq_autotester(_eq_k) @@ -296,6 +311,20 @@ def test_greater_than_constant(): ) +@pytest.mark.parametrize('dtype', [QBit(), QUInt(2), QInt(3), QMontgomeryUInt(4), QUInt(5)]) +def test_classical_equals(dtype): + bloq = Equals(dtype) + qlt_testing.assert_consistent_classical_action( + bloq, x=dtype.get_classical_domain(), y=dtype.get_classical_domain(), target=range(2) + ) + + +def test_equals_call_graph(): + bloq = Equals(QUInt(4)) + + qlt_testing.assert_equivalent_bloq_counts(bloq, ignore_split_join) + + def test_equals_a_constant(): bb = BloqBuilder() bitsize = 5 @@ -337,7 +366,7 @@ def test_clineardepthgreaterthan_classical_action_unsigned(ctrl, dtype, bitsize) b = CLinearDepthGreaterThan(dtype(bitsize), ctrl) cb = b.decompose_bloq() for c, target in itertools.product(range(2), repeat=2): - for (x, y) in itertools.product(range(2**bitsize), repeat=2): + for x, y in itertools.product(range(2**bitsize), repeat=2): assert b.call_classically(ctrl=c, a=x, b=y, target=target) == cb.call_classically( ctrl=c, a=x, b=y, target=target ) @@ -349,7 +378,7 @@ def test_clineardepthgreaterthan_classical_action_signed(ctrl, bitsize): b = CLinearDepthGreaterThan(QInt(bitsize), ctrl) cb = b.decompose_bloq() for c, target in itertools.product(range(2), repeat=2): - for (x, y) in itertools.product(range(-(2 ** (bitsize - 1)), 2 ** (bitsize - 1)), repeat=2): + for x, y in itertools.product(range(-(2 ** (bitsize - 1)), 2 ** (bitsize - 1)), repeat=2): assert b.call_classically(ctrl=c, a=x, b=y, target=target) == cb.call_classically( ctrl=c, a=x, b=y, target=target ) @@ -378,3 +407,97 @@ def test_clineardepthgreaterthan_tcomplexity(ctrl, dtype): c = CLinearDepthGreaterThan(dtype(n), ctrl).t_complexity() assert c.t == 4 * (n + 2) assert c.rotations == 0 + + +@pytest.mark.parametrize( + 'comp_cls', + [ + LinearDepthHalfGreaterThan, + LinearDepthHalfGreaterThanEqual, + LinearDepthHalfLessThan, + LinearDepthHalfLessThanEqual, + ], +) +@pytest.mark.parametrize('dtype', [QInt, QUInt, QMontgomeryUInt]) +@pytest.mark.parametrize('bitsize', range(2, 5)) +@pytest.mark.parametrize('uncompute', [True, False]) +def test_linear_half_comparison_decomposition(comp_cls, dtype, bitsize, uncompute): + b = comp_cls(dtype(bitsize), uncompute) + qlt_testing.assert_valid_bloq_decomposition(b) + + +@pytest.mark.parametrize( + 'comp_cls', + [ + LinearDepthHalfGreaterThan, + LinearDepthHalfGreaterThanEqual, + LinearDepthHalfLessThan, + LinearDepthHalfLessThanEqual, + ], +) +@pytest.mark.parametrize('dtype', [QInt, QUInt, QMontgomeryUInt]) +@pytest.mark.parametrize('bitsize', range(2, 5)) +@pytest.mark.parametrize('uncompute', [True, False]) +def test_linear_half_comparison_bloq_counts(comp_cls, dtype, bitsize, uncompute): + b = comp_cls(dtype(bitsize), uncompute) + qlt_testing.assert_equivalent_bloq_counts(b, [ignore_alloc_free, ignore_split_join]) + + +@pytest.mark.parametrize( + 'comp_cls', + [ + LinearDepthHalfGreaterThan, + LinearDepthHalfGreaterThanEqual, + LinearDepthHalfLessThan, + LinearDepthHalfLessThanEqual, + ], +) +@pytest.mark.parametrize('dtype', [QInt, QUInt, QMontgomeryUInt]) +@pytest.mark.parametrize('bitsize', range(2, 5)) +def test_linear_half_comparison_classical_action(comp_cls, dtype, bitsize): + b = comp_cls(dtype(bitsize)) + qlt_testing.assert_consistent_classical_action( + b, a=dtype(bitsize).get_classical_domain(), b=dtype(bitsize).get_classical_domain() + ) + + +@pytest.mark.parametrize( + 'comp_cls', + [ + LinearDepthHalfGreaterThan, + LinearDepthHalfGreaterThanEqual, + LinearDepthHalfLessThan, + LinearDepthHalfLessThanEqual, + ], +) +@pytest.mark.parametrize('dtype', [QInt, QUInt, QMontgomeryUInt]) +def test_linear_half_comparison_symbolic_complexity(comp_cls, dtype): + n = sympy.Symbol('n') + b = comp_cls(dtype(n)) + + cost = get_cost_value(b, QECGatesCost()).total_t_and_ccz_count() + + assert cost['n_t'] == 0 + assert cost['n_ccz'] == n + + # uncomputation has zero cost. + cost = get_cost_value(b.adjoint(), QECGatesCost()).total_t_and_ccz_count() + + assert cost['n_t'] == 0 + assert cost['n_ccz'] == 0 + + +def test_lineardepthhalfgreaterthan_small(bloq_autotester): + bloq_autotester(_lineardepthhalfgreaterthan_small) + + +def test_lineardepthhalflessthan_small(bloq_autotester): + bloq_autotester(_lineardepthhalflessthan_small) + + +def test_lineardepthhalfgreaterthanequal_small(bloq_autotester): + bloq_autotester(_lineardepthhalfgreaterthanequal_small) + + +def test_lineardepthhalflessthanequal_small(bloq_autotester): + bloq_autotester(_lineardepthhalflessthanequal_small) diff --git a/qualtran/bloqs/arithmetic/controlled_add_or_subtract.ipynb b/qualtran/bloqs/arithmetic/controlled_add_or_subtract.ipynb index ad3ef6e93..97da1cd23 100644 --- a/qualtran/bloqs/arithmetic/controlled_add_or_subtract.ipynb +++ b/qualtran/bloqs/arithmetic/controlled_add_or_subtract.ipynb @@ -67,7 +67,7 @@ " - `b`: an integer value. \n", "\n", "#### References\n", - " - [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization](https://arxiv.org/abs/2007.07391). Sanders et. al. Section II-A-1, Algorithm 1.\n" + " - [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization](https://arxiv.org/abs/2007.07391). Sanders et al. Section II-A-1, Algorithm 1.\n" ] }, { diff --git a/qualtran/bloqs/arithmetic/controlled_add_or_subtract.py b/qualtran/bloqs/arithmetic/controlled_add_or_subtract.py index 3d74bc932..c082e6547 100644 --- a/qualtran/bloqs/arithmetic/controlled_add_or_subtract.py +++ b/qualtran/bloqs/arithmetic/controlled_add_or_subtract.py @@ -62,7 +62,7 @@ class ControlledAddOrSubtract(Bloq): References: [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization](https://arxiv.org/abs/2007.07391). - Sanders et. al. Section II-A-1, Algorithm 1. + Sanders et al. Section II-A-1, Algorithm 1. """ a_dtype: Union[QInt, QUInt, QMontgomeryUInt] = field() diff --git a/qualtran/bloqs/arithmetic/controlled_addition.py b/qualtran/bloqs/arithmetic/controlled_addition.py index cfe4bcc5d..dc6751d23 100644 --- a/qualtran/bloqs/arithmetic/controlled_addition.py +++ b/qualtran/bloqs/arithmetic/controlled_addition.py @@ -23,6 +23,7 @@ bloq_example, BloqBuilder, BloqDocSpec, + DecomposeTypeError, QBit, QInt, QUInt, @@ -37,6 +38,7 @@ from qualtran.bloqs.mcmt.and_bloq import And from qualtran.resource_counting.generalizers import ignore_split_join from qualtran.simulation.classical_sim import add_ints +from qualtran.symbolics.types import is_symbolic if TYPE_CHECKING: import quimb.tensor as qtn @@ -134,6 +136,9 @@ def wire_symbol(self, soq: 'Soquet') -> 'WireSymbol': def build_composite_bloq( self, bb: 'BloqBuilder', ctrl: 'Soquet', a: 'Soquet', b: 'Soquet' ) -> Dict[str, 'SoquetT']: + if is_symbolic(self.a_dtype.bitsize, self.b_dtype.bitsize): + raise DecomposeTypeError(f"Cannot decompose {self} with symbolic `bitsize`.") + a_arr = bb.split(a) ctrl_q = bb.split(ctrl)[0] ancilla_arr = [] diff --git a/qualtran/bloqs/arithmetic/conversions/contiguous_index.py b/qualtran/bloqs/arithmetic/conversions/contiguous_index.py index 5444e62d3..79f552805 100644 --- a/qualtran/bloqs/arithmetic/conversions/contiguous_index.py +++ b/qualtran/bloqs/arithmetic/conversions/contiguous_index.py @@ -18,7 +18,6 @@ from qualtran import Bloq, bloq_example, BloqDocSpec, QUInt, Register, Signature from qualtran.bloqs.basic_gates import Toffoli -from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.drawing import WireSymbol from qualtran.drawing.musical_score import Text, TextBox @@ -70,10 +69,6 @@ def on_classical_vals( ) -> Dict[str, 'ClassicalValT']: return {'mu': mu, 'nu': nu, 's': nu * (nu + 1) // 2 + mu} - def _t_complexity_(self) -> 'TComplexity': - num_toffoli = self.bitsize**2 + self.bitsize - 1 - return TComplexity(t=4 * num_toffoli) - def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -> WireSymbol: if reg is None: return Text('') diff --git a/qualtran/bloqs/arithmetic/multiplication.ipynb b/qualtran/bloqs/arithmetic/multiplication.ipynb index 0909cd39a..40eff7a98 100644 --- a/qualtran/bloqs/arithmetic/multiplication.ipynb +++ b/qualtran/bloqs/arithmetic/multiplication.ipynb @@ -474,7 +474,7 @@ " - `result`: a r_bitsize sized output fixed-point register. \n", "\n", "#### References\n", - " - [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization]( https://arxiv.org/pdf/2007.07391.pdf) pg 70.\n" + " - [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization](https://arxiv.org/abs/2007.07391). pg 70.\n" ] }, { diff --git a/qualtran/bloqs/arithmetic/multiplication.py b/qualtran/bloqs/arithmetic/multiplication.py index 464eacf0c..efed2d665 100644 --- a/qualtran/bloqs/arithmetic/multiplication.py +++ b/qualtran/bloqs/arithmetic/multiplication.py @@ -315,9 +315,8 @@ class Product(Bloq): result: A 2*`max(a_bitsize, b_bitsize)` bit-sized output register to store the result a*b. References: - [Fault-Tolerant Quantum Simulations of Chemistry in First - Quantization](https://arxiv.org/abs/2105.12767) pg 81 gives a Toffoli - complexity for multiplying two numbers. + [Fault-Tolerant Quantum Simulations of Chemistry in First Quantization](https://arxiv.org/abs/2105.12767). + pg 81 gives a Toffoli complexity for multiplying two numbers. """ a_bitsize: SymbolicInt @@ -377,8 +376,8 @@ class ScaleIntByReal(Bloq): result: a r_bitsize sized output fixed-point register. References: - [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization]( - https://arxiv.org/pdf/2007.07391.pdf) pg 70. + [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization](https://arxiv.org/abs/2007.07391). + pg 70. """ r_bitsize: SymbolicInt diff --git a/qualtran/bloqs/arithmetic/negate.py b/qualtran/bloqs/arithmetic/negate.py index c3910fc1e..0d42dcb3f 100644 --- a/qualtran/bloqs/arithmetic/negate.py +++ b/qualtran/bloqs/arithmetic/negate.py @@ -12,11 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. from functools import cached_property -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Union from attrs import frozen -from qualtran import Bloq, bloq_example, BloqDocSpec, QDType, QInt, Signature +from qualtran import Bloq, bloq_example, BloqDocSpec, QInt, QMontgomeryUInt, QUInt, Signature from qualtran.bloqs.arithmetic import AddK from qualtran.bloqs.arithmetic.bitwise import BitwiseNot @@ -53,7 +53,7 @@ class Negate(Bloq): Operator "Unary Minus". Last accessed 17 July 2024. """ - dtype: QDType + dtype: Union[QUInt, QInt, QMontgomeryUInt] @cached_property def signature(self) -> 'Signature': @@ -61,7 +61,7 @@ def signature(self) -> 'Signature': def build_composite_bloq(self, bb: 'BloqBuilder', x: 'SoquetT') -> dict[str, 'SoquetT']: x = bb.add(BitwiseNot(self.dtype), x=x) # ~x - x = bb.add(AddK(self.dtype.num_qubits, k=1, signed=isinstance(self.dtype, QInt)), x=x) # -x + x = bb.add(AddK(self.dtype, k=1), x=x) # -x return {'x': x} diff --git a/qualtran/bloqs/arithmetic/permutation.ipynb b/qualtran/bloqs/arithmetic/permutation.ipynb index a2f9dc5d7..3f5d69000 100644 --- a/qualtran/bloqs/arithmetic/permutation.ipynb +++ b/qualtran/bloqs/arithmetic/permutation.ipynb @@ -50,7 +50,8 @@ "\n", "#### Parameters\n", " - `N`: the total size the permutation acts on.\n", - " - `cycles`: a sequence of permutation cycles that form the permutation. \n", + " - `cycles`: a sequence of permutation cycles that form the permutation.\n", + " - `bitsize`: number of bits to store the indices, defaults to $\\ceil(\\log_2(N))$. \n", "\n", "#### Registers\n", " - `x`: integer register storing a value in [0, ..., N - 1] \n", @@ -235,7 +236,8 @@ "\n", "#### Parameters\n", " - `N`: the total size the permutation acts on.\n", - " - `cycle`: the permutation cycle to apply. \n", + " - `cycle`: the permutation cycle to apply.\n", + " - `bitsize`: number of bits to store the indices, defaults to $\\ceil(\\log_2(N))$. \n", "\n", "#### Registers\n", " - `x`: integer register storing a value in [0, ..., N - 1] \n", diff --git a/qualtran/bloqs/arithmetic/permutation.py b/qualtran/bloqs/arithmetic/permutation.py index f3ad8f21f..8c294d593 100644 --- a/qualtran/bloqs/arithmetic/permutation.py +++ b/qualtran/bloqs/arithmetic/permutation.py @@ -84,6 +84,7 @@ class PermutationCycle(Bloq): Args: N: the total size the permutation acts on. cycle: the permutation cycle to apply. + bitsize: number of bits to store the indices, defaults to $\ceil(\log_2(N))$. Registers: x: integer register storing a value in [0, ..., N - 1] @@ -95,13 +96,14 @@ class PermutationCycle(Bloq): N: SymbolicInt cycle: Union[tuple[int, ...], Shaped] = field(converter=_convert_cycle) + bitsize: SymbolicInt = field() @cached_property def signature(self) -> Signature: return Signature.build_from_dtypes(x=BQUInt(self.bitsize, self.N)) - @cached_property - def bitsize(self): + @bitsize.default + def _default_bitsize(self): return bit_length(self.N - 1) def build_composite_bloq(self, bb: 'BloqBuilder', x: 'SoquetT') -> dict[str, 'SoquetT']: @@ -194,6 +196,7 @@ class Permutation(Bloq): Args: N: the total size the permutation acts on. cycles: a sequence of permutation cycles that form the permutation. + bitsize: number of bits to store the indices, defaults to $\ceil(\log_2(N))$. Registers: x: integer register storing a value in [0, ..., N - 1] @@ -205,13 +208,14 @@ class Permutation(Bloq): N: SymbolicInt cycles: Union[tuple[SymbolicCycleT, ...], Shaped] = field(converter=_convert_cycles) + bitsize: SymbolicInt = field() @cached_property def signature(self) -> Signature: return Signature.build_from_dtypes(x=BQUInt(self.bitsize, self.N)) - @cached_property - def bitsize(self): + @bitsize.default + def _default_bitsize(self): return bit_length(self.N - 1) def is_symbolic(self): @@ -265,7 +269,7 @@ def build_composite_bloq(self, bb: 'BloqBuilder', x: 'Soquet') -> dict[str, 'Soq raise DecomposeTypeError(f"cannot decompose symbolic {self}") for cycle in self.cycles: - x = bb.add(PermutationCycle(self.N, cycle), x=x) + x = bb.add(PermutationCycle(self.N, cycle, self.bitsize), x=x) return {'x': x} @@ -275,7 +279,7 @@ def build_call_graph( if is_symbolic(self.cycles): # worst case cost: single cycle of length N cycle = Shaped((self.N,)) - return {PermutationCycle(self.N, cycle): 1} + return {PermutationCycle(self.N, cycle, self.bitsize): 1} return super().build_call_graph(ssa) diff --git a/qualtran/bloqs/arithmetic/subtraction.ipynb b/qualtran/bloqs/arithmetic/subtraction.ipynb index 584a0dce4..ce9e32211 100644 --- a/qualtran/bloqs/arithmetic/subtraction.ipynb +++ b/qualtran/bloqs/arithmetic/subtraction.ipynb @@ -53,7 +53,7 @@ " - `b`: A b_dtype.bitsize-sized input/output register (register b above). \n", "\n", "#### References\n", - " - [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization, page 9](https://arxiv.org/pdf/2007.07391). \n" + " - [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization](https://arxiv.org/abs/2007.07391). Page 9.\n" ] }, { diff --git a/qualtran/bloqs/arithmetic/subtraction.py b/qualtran/bloqs/arithmetic/subtraction.py index f9bb91437..ef5cb5199 100644 --- a/qualtran/bloqs/arithmetic/subtraction.py +++ b/qualtran/bloqs/arithmetic/subtraction.py @@ -68,7 +68,8 @@ class Subtract(Bloq): b: A b_dtype.bitsize-sized input/output register (register b above). References: - [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization, page 9](https://arxiv.org/pdf/2007.07391) + [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization](https://arxiv.org/abs/2007.07391). + Page 9. """ a_dtype: Union[QInt, QUInt, QMontgomeryUInt] = field() diff --git a/qualtran/bloqs/arithmetic/subtraction_test.py b/qualtran/bloqs/arithmetic/subtraction_test.py index 5aa3c488b..40e2bc6fd 100644 --- a/qualtran/bloqs/arithmetic/subtraction_test.py +++ b/qualtran/bloqs/arithmetic/subtraction_test.py @@ -122,7 +122,7 @@ def test_against_classical_values(dtype): else: R1 = range(8) R2 = range(32) - for (a, b) in itertools.product(R1, R2): + for a, b in itertools.product(R1, R2): ref = subtract.call_classically(a=a, b=b) comp = cbloq.call_classically(a=a, b=b) assert ref == comp diff --git a/qualtran/bloqs/basic_gates/cnot.py b/qualtran/bloqs/basic_gates/cnot.py index bf19f090c..a6e762803 100644 --- a/qualtran/bloqs/basic_gates/cnot.py +++ b/qualtran/bloqs/basic_gates/cnot.py @@ -33,7 +33,6 @@ Signature, SoquetT, ) -from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.drawing import Circle, ModPlus, Text, WireSymbol if TYPE_CHECKING: @@ -132,9 +131,6 @@ def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) - return ModPlus() raise ValueError(f'Unknown wire symbol register name: {reg.name}') - def _t_complexity_(self) -> 'TComplexity': - return TComplexity(clifford=1) - @bloq_example def _cnot() -> CNOT: diff --git a/qualtran/bloqs/basic_gates/global_phase.py b/qualtran/bloqs/basic_gates/global_phase.py index 7fcf2bafd..cae67a2fa 100644 --- a/qualtran/bloqs/basic_gates/global_phase.py +++ b/qualtran/bloqs/basic_gates/global_phase.py @@ -32,7 +32,6 @@ ) from qualtran.bloqs.basic_gates.rotation import ZPowGate from qualtran.cirq_interop import CirqGateAsBloqBase -from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.symbolics import pi, sarg, sexp, SymbolicComplex, SymbolicFloat if TYPE_CHECKING: @@ -109,9 +108,6 @@ def _add_ctrled( def __str__(self) -> str: return f'GPhase({self.coefficient})' - def _t_complexity_(self) -> 'TComplexity': - return TComplexity() - @bloq_example def _global_phase() -> GlobalPhase: diff --git a/qualtran/bloqs/basic_gates/hadamard.ipynb b/qualtran/bloqs/basic_gates/hadamard.ipynb index 1315f0a15..60247421e 100644 --- a/qualtran/bloqs/basic_gates/hadamard.ipynb +++ b/qualtran/bloqs/basic_gates/hadamard.ipynb @@ -40,10 +40,12 @@ "\n", "This converts between the X and Z basis.\n", "\n", - "$$\\begin{aligned}\n", + "$$\n", + "\\begin{aligned}\n", "H |0\\rangle = |+\\rangle \\\\\n", "H |-\\rangle = |1\\rangle\n", - "\\end{aligned}$$\n", + "\\end{aligned}\n", + "$$\n", "\n", "#### Registers\n", " - `q`: The qubit\n" diff --git a/qualtran/bloqs/basic_gates/hadamard.py b/qualtran/bloqs/basic_gates/hadamard.py index eca9c99e8..5efa9c2f5 100644 --- a/qualtran/bloqs/basic_gates/hadamard.py +++ b/qualtran/bloqs/basic_gates/hadamard.py @@ -32,7 +32,6 @@ Signature, SoquetT, ) -from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.drawing import Circle, Text, TextBox, WireSymbol if TYPE_CHECKING: @@ -51,10 +50,12 @@ class Hadamard(Bloq): This converts between the X and Z basis. - $$\begin{aligned} + $$ + \begin{aligned} H |0\rangle = |+\rangle \\ H |-\rangle = |1\rangle - \end{aligned}$$ + \end{aligned} + $$ Registers: q: The qubit @@ -104,9 +105,6 @@ def as_cirq_op( (q,) = q return cirq.H(q), {'q': np.array([q])} - def _t_complexity_(self): - return TComplexity(clifford=1) - def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -> 'WireSymbol': if reg is None: return Text('') @@ -173,14 +171,6 @@ def as_cirq_op( 'target': np.array([target]), } - def _t_complexity_(self) -> 'TComplexity': - # This is based on the decomposition provided by `cirq.decompose_multi_controlled_rotation` - # which uses three cirq.MatrixGate's to do a controlled version of any single-qubit gate. - # The first MatrixGate happens to be a clifford, Hadamard operation in this case. - # The other two are considered 'rotations'. - # https://github.com/quantumlib/Qualtran/issues/237 - return TComplexity(rotations=2, clifford=4) - def my_static_costs(self, cost_key: 'CostKey'): from qualtran.resource_counting import GateCounts, QECGatesCost diff --git a/qualtran/bloqs/basic_gates/identity.py b/qualtran/bloqs/basic_gates/identity.py index 0b81fe34e..c5eeafa1a 100644 --- a/qualtran/bloqs/basic_gates/identity.py +++ b/qualtran/bloqs/basic_gates/identity.py @@ -32,7 +32,6 @@ Signature, SoquetT, ) -from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.drawing import Text, TextBox, WireSymbol from qualtran.symbolics import is_symbolic, SymbolicInt @@ -54,6 +53,7 @@ class Identity(Bloq): Registers: q: register of `n` qubits """ + bitsize: SymbolicInt = 1 @cached_property @@ -88,9 +88,6 @@ def as_cirq_op( return cirq.IdentityGate(self.bitsize).on(*q), {'q': q} - def _t_complexity_(self): - return TComplexity() - def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -> 'WireSymbol': if reg is None: return Text('') diff --git a/qualtran/bloqs/basic_gates/identity_test.py b/qualtran/bloqs/basic_gates/identity_test.py index ea3a4255e..3a45b8980 100644 --- a/qualtran/bloqs/basic_gates/identity_test.py +++ b/qualtran/bloqs/basic_gates/identity_test.py @@ -15,16 +15,14 @@ import numpy as np import pytest import sympy -from attrs import frozen -from qualtran import Bloq, BloqBuilder, CtrlSpec, QInt, QUInt, Signature, Soquet, SoquetT +from qualtran import BloqBuilder from qualtran.bloqs.basic_gates import OneState from qualtran.bloqs.basic_gates.identity import _identity, _identity_n, _identity_symb, Identity from qualtran.simulation.classical_sim import ( format_classical_truth_table, get_classical_truth_table, ) -from qualtran.symbolics import SymbolicInt from qualtran.testing import execute_notebook @@ -95,42 +93,6 @@ def test_identity_controlled(): assert Identity(n).controlled() == Identity(n + 1) -@frozen -class TestIdentityDecomposition(Bloq): - """helper to test Identity.get_ctrl_system""" - - bitsize: SymbolicInt - - @property - def signature(self) -> 'Signature': - return Signature.build(q=self.bitsize) - - def build_composite_bloq(self, bb: 'BloqBuilder', q: Soquet) -> dict[str, 'SoquetT']: - q = bb.add(Identity(self.bitsize), q=q) - q = bb.add(Identity(self.bitsize), q=q) - return {'q': q} - - -@pytest.mark.parametrize("n", [4, sympy.Symbol("n")]) -@pytest.mark.parametrize( - "ctrl_spec", - [ - CtrlSpec(cvs=(np.array([1, 0, 1]),)), - CtrlSpec(qdtypes=(QUInt(3), QInt(3)), cvs=(np.array(0b010), np.array(0b001))), - ], -) -def test_identity_get_ctrl_system(n: SymbolicInt, ctrl_spec: CtrlSpec): - m = ctrl_spec.num_qubits - - bloq = TestIdentityDecomposition(n) - ctrl_bloq = bloq.controlled(ctrl_spec) - - _ = ctrl_bloq.decompose_bloq() - - _, sigma = ctrl_bloq.call_graph() - assert sigma == {Identity(n + m): 2} - - @pytest.mark.notebook def test_notebook(): execute_notebook('identity') diff --git a/qualtran/bloqs/basic_gates/rotation.py b/qualtran/bloqs/basic_gates/rotation.py index 621c7b760..9adb439e1 100644 --- a/qualtran/bloqs/basic_gates/rotation.py +++ b/qualtran/bloqs/basic_gates/rotation.py @@ -22,7 +22,6 @@ from qualtran import bloq_example, BloqDocSpec, CompositeBloq, DecomposeTypeError, Register from qualtran.cirq_interop import CirqGateAsBloqBase -from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.drawing import Text, TextBox, WireSymbol from qualtran.symbolics import SymbolicFloat @@ -120,11 +119,6 @@ def decompose_bloq(self) -> 'CompositeBloq': def cirq_gate(self) -> cirq.Gate: return cirq.CZPowGate(exponent=self.exponent, global_shift=self.global_shift) - def _t_complexity_(self) -> 'TComplexity': - if cirq.has_stabilizer_effect(self.cirq_gate): - return TComplexity(clifford=1) - return TComplexity(rotations=1) - def __pow__(self, power): g = self.cirq_gate**power return CZPowGate(g.exponent, g.global_shift, self.eps) @@ -177,6 +171,7 @@ class XPowGate(CirqGateAsBloqBase): [Optimal ancilla-free Clifford+T approximation of z-rotations](https://arxiv.org/pdf/1403.2975.pdf). """ + exponent: Union[sympy.Expr, float] = 1.0 global_shift: float = 0.0 eps: SymbolicFloat = 1e-11 @@ -250,6 +245,7 @@ class YPowGate(CirqGateAsBloqBase): [Optimal ancilla-free Clifford+T approximation of z-rotations](https://arxiv.org/pdf/1403.2975.pdf). """ + exponent: Union[sympy.Expr, float] = 1.0 global_shift: float = 0.0 eps: SymbolicFloat = 1e-11 diff --git a/qualtran/bloqs/basic_gates/s_gate.py b/qualtran/bloqs/basic_gates/s_gate.py index 8ea293564..3971f59d0 100644 --- a/qualtran/bloqs/basic_gates/s_gate.py +++ b/qualtran/bloqs/basic_gates/s_gate.py @@ -20,7 +20,6 @@ from attrs import frozen from qualtran import Bloq, bloq_example, BloqDocSpec, ConnectionT, Register, Signature -from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.drawing import Text, TextBox, WireSymbol if TYPE_CHECKING: @@ -50,15 +49,13 @@ class SGate(Bloq): Registers: q: The qubit """ + is_adjoint: bool = False @cached_property def signature(self) -> 'Signature': return Signature.build(q=1) - def _t_complexity_(self) -> 'TComplexity': - return TComplexity(clifford=1) - def my_tensors( self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] ) -> List['qtn.Tensor']: diff --git a/qualtran/bloqs/basic_gates/swap.ipynb b/qualtran/bloqs/basic_gates/swap.ipynb index bb9fa0cf2..b2ccb653c 100644 --- a/qualtran/bloqs/basic_gates/swap.ipynb +++ b/qualtran/bloqs/basic_gates/swap.ipynb @@ -121,7 +121,7 @@ " - `y`: the second bit \n", "\n", "#### References\n", - " - [An algorithm for the T-count](https://arxiv.org/abs/1308.4134). Gosset et. al. 2013. Figure 5.2.\n" + " - [An algorithm for the T-count](https://arxiv.org/abs/1308.4134). Gosset et al. 2013. Figure 5.2.\n" ] }, { diff --git a/qualtran/bloqs/basic_gates/swap.py b/qualtran/bloqs/basic_gates/swap.py index 9df8148a3..e251333a3 100644 --- a/qualtran/bloqs/basic_gates/swap.py +++ b/qualtran/bloqs/basic_gates/swap.py @@ -15,7 +15,6 @@ from functools import cached_property from typing import Dict, Iterable, List, Optional, Sequence, Tuple, TYPE_CHECKING, Union -import cirq import numpy as np import sympy from attrs import frozen @@ -36,14 +35,15 @@ SoquetT, ) from qualtran.cirq_interop import CirqQuregT -from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.drawing import Circle, Text, TextBox, WireSymbol from qualtran.resource_counting.generalizers import ignore_split_join if TYPE_CHECKING: + import cirq import quimb.tensor as qtn from qualtran import AddControlledT, CompositeBloq + from qualtran.cirq_interop import CirqQuregT from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator from qualtran.simulation.classical_sim import ClassicalValT @@ -82,10 +82,9 @@ def as_cirq_op( ) -> Tuple['cirq.Operation', Dict[str, 'CirqQuregT']]: # type: ignore[type-var] (x,) = x (y,) = y - return cirq.SWAP.on(x, y), {'x': np.asarray([x]), 'y': np.asarray([y])} + import cirq - def _t_complexity_(self) -> 'TComplexity': - return TComplexity(clifford=1) + return cirq.SWAP.on(x, y), {'x': np.asarray([x]), 'y': np.asarray([y])} def my_tensors( self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] @@ -106,19 +105,15 @@ def adjoint(self) -> 'Bloq': return self def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> tuple['Bloq', 'AddControlledT']: - if ctrl_spec != CtrlSpec(): - return super().get_ctrl_system(ctrl_spec=ctrl_spec) - - cswap = TwoBitCSwap() - - def adder( - bb: 'BloqBuilder', ctrl_soqs: Sequence['SoquetT'], in_soqs: Dict[str, 'SoquetT'] - ) -> Tuple[Iterable['SoquetT'], Iterable['SoquetT']]: - (ctrl,) = ctrl_soqs - ctrl, x, y = bb.add(cswap, ctrl=ctrl, x=in_soqs['x'], y=in_soqs['y']) - return [ctrl], [x, y] + from qualtran.bloqs.mcmt.specialized_ctrl import get_ctrl_system_1bit_cv_from_bloqs - return cswap, adder + return get_ctrl_system_1bit_cv_from_bloqs( + self, + ctrl_spec, + current_ctrl_bit=None, + bloq_with_ctrl=TwoBitCSwap(), + ctrl_reg_name='ctrl', + ) @bloq_example @@ -143,7 +138,7 @@ class TwoBitCSwap(Bloq): References: [An algorithm for the T-count](https://arxiv.org/abs/1308.4134). - Gosset et. al. 2013. Figure 5.2. + Gosset et al. 2013. Figure 5.2. """ @cached_property @@ -154,6 +149,8 @@ def decompose_bloq(self) -> 'CompositeBloq': raise DecomposeTypeError(f"{self} is atomic.") def to_clifford_t_circuit(self) -> 'cirq.FrozenCircuit': + import cirq + ctrl = cirq.NamedQubit('ctrl') x = cirq.NamedQubit('x') y = cirq.NamedQubit('y') @@ -200,6 +197,13 @@ def wire_symbol(self, reg: Optional['Register'], idx: Tuple[int, ...] = ()) -> ' else: return TextBox('×') + def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> tuple['Bloq', 'AddControlledT']: + from qualtran.bloqs.mcmt.specialized_ctrl import get_ctrl_system_1bit_cv_from_bloqs + + return get_ctrl_system_1bit_cv_from_bloqs( + self, ctrl_spec, current_ctrl_bit=1, bloq_with_ctrl=self, ctrl_reg_name='ctrl' + ) + @bloq_example def _cswap_bit() -> TwoBitCSwap: @@ -354,12 +358,16 @@ def on_classical_vals( @classmethod def make_on( - cls, **quregs: Union[Sequence[cirq.Qid], NDArray[cirq.Qid]] # type: ignore[type-var] - ) -> cirq.Operation: + cls, **quregs: Union[Sequence['cirq.Qid'], NDArray['cirq.Qid']] # type: ignore[type-var] + ) -> 'cirq.Operation': """Helper constructor to automatically deduce bitsize attributes.""" return cls(bitsize=len(quregs['x'])).on_registers(**quregs) - def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo: + def _circuit_diagram_info_( + self, args: 'cirq.CircuitDiagramInfoArgs' + ) -> 'cirq.CircuitDiagramInfo': + import cirq + if not args.use_unicode_characters: return cirq.CircuitDiagramInfo( ("@",) + ("swap_x",) * self.bitsize + ("swap_y",) * self.bitsize diff --git a/qualtran/bloqs/basic_gates/t_gate.ipynb b/qualtran/bloqs/basic_gates/t_gate.ipynb index b79dc9e89..cbdf39584 100644 --- a/qualtran/bloqs/basic_gates/t_gate.ipynb +++ b/qualtran/bloqs/basic_gates/t_gate.ipynb @@ -61,7 +61,7 @@ "\n", "#### References\n", " - [Universal Quantum Computation with ideal Clifford gates and noisy ancillas](https://arxiv.org/abs/quant-ph/0403025). Bravyi and Kitaev. 2004.\n", - " - [Fast and efficient exact synthesis of single qubit unitaries generated by Clifford and T gates](https://arxiv.org/abs/1206.5236). Kliuchnikov et. al. 2012.\n", + " - [Fast and efficient exact synthesis of single qubit unitaries generated by Clifford and T gates](https://arxiv.org/abs/1206.5236). Kliuchnikov et al. 2012.\n", " - [Universal Gate Set, Magic States, and costliness of the T gate](https://quantumcomputing.stackexchange.com/a/33358). Gidney. 2023.\n" ] }, diff --git a/qualtran/bloqs/basic_gates/t_gate.py b/qualtran/bloqs/basic_gates/t_gate.py index a21b43f46..ca71cfb80 100644 --- a/qualtran/bloqs/basic_gates/t_gate.py +++ b/qualtran/bloqs/basic_gates/t_gate.py @@ -62,7 +62,7 @@ class TGate(Bloq): Bravyi and Kitaev. 2004. [Fast and efficient exact synthesis of single qubit unitaries generated by Clifford and T gates](https://arxiv.org/abs/1206.5236). - Kliuchnikov et. al. 2012. + Kliuchnikov et al. 2012. [Universal Gate Set, Magic States, and costliness of the T gate](https://quantumcomputing.stackexchange.com/a/33358). Gidney. 2023. diff --git a/qualtran/bloqs/basic_gates/toffoli.py b/qualtran/bloqs/basic_gates/toffoli.py index ee3b96ae6..597193856 100644 --- a/qualtran/bloqs/basic_gates/toffoli.py +++ b/qualtran/bloqs/basic_gates/toffoli.py @@ -30,7 +30,6 @@ Register, Signature, ) -from qualtran.cirq_interop.t_complexity_protocol import TComplexity if TYPE_CHECKING: import cirq @@ -66,9 +65,6 @@ def adjoint(self) -> 'Bloq': def decompose_bloq(self) -> 'CompositeBloq': raise DecomposeTypeError(f"{self} is atomic") - def _t_complexity_(self): - return TComplexity(t=4) - def my_tensors( self, incoming: Dict[str, NDArray[Connection]], # type: ignore[type-var] diff --git a/qualtran/bloqs/basic_gates/x_basis.py b/qualtran/bloqs/basic_gates/x_basis.py index 76fd85dcb..c215a4ad2 100644 --- a/qualtran/bloqs/basic_gates/x_basis.py +++ b/qualtran/bloqs/basic_gates/x_basis.py @@ -32,7 +32,6 @@ Signature, SoquetT, ) -from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.drawing import directional_text_box, Text, WireSymbol if TYPE_CHECKING: @@ -257,9 +256,6 @@ def as_cirq_op( (q,) = q return cirq.X(q), {'q': np.asarray([q])} - def _t_complexity_(self): - return TComplexity(clifford=1) - def wire_symbol(self, reg: Register, idx: Tuple[int, ...] = tuple()) -> 'WireSymbol': from qualtran.drawing import ModPlus diff --git a/qualtran/bloqs/basic_gates/x_basis_test.py b/qualtran/bloqs/basic_gates/x_basis_test.py index 58851757e..d6b3b670d 100644 --- a/qualtran/bloqs/basic_gates/x_basis_test.py +++ b/qualtran/bloqs/basic_gates/x_basis_test.py @@ -92,3 +92,17 @@ def test_x_truth_table(): 0 -> 1 1 -> 0""" ) + + +def test_controlled_x(): + from qualtran import CtrlSpec, QUInt + from qualtran.bloqs.basic_gates import CNOT + from qualtran.bloqs.mcmt import And + + def _keep_and(b): + return isinstance(b, And) + + n = 8 + bloq = XGate().controlled(CtrlSpec(qdtypes=QUInt(n), cvs=1)) + _, sigma = bloq.call_graph(keep=_keep_and) + assert sigma == {And(): n - 1, CNOT(): 1, And().adjoint(): n - 1, XGate(): 4 * (n - 1)} diff --git a/qualtran/bloqs/basic_gates/y_gate.py b/qualtran/bloqs/basic_gates/y_gate.py index e4819a235..5212bb5ca 100644 --- a/qualtran/bloqs/basic_gates/y_gate.py +++ b/qualtran/bloqs/basic_gates/y_gate.py @@ -32,7 +32,6 @@ Signature, SoquetT, ) -from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.drawing import Circle, Text, TextBox, WireSymbol if TYPE_CHECKING: @@ -98,9 +97,6 @@ def as_cirq_op( (q,) = q return cirq.Y(q), {'q': np.asarray([q])} - def _t_complexity_(self) -> 'TComplexity': - return TComplexity(clifford=1) - def wire_symbol( self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() ) -> 'WireSymbol': @@ -177,6 +173,13 @@ def wire_symbol( return TextBox('Y') raise ValueError(f"Unknown register {reg}.") + def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> Tuple['Bloq', 'AddControlledT']: + from qualtran.bloqs.mcmt.specialized_ctrl import get_ctrl_system_1bit_cv_from_bloqs + + return get_ctrl_system_1bit_cv_from_bloqs( + self, ctrl_spec, current_ctrl_bit=1, bloq_with_ctrl=self, ctrl_reg_name='ctrl' + ) + @bloq_example def _cy_gate() -> CYGate: diff --git a/qualtran/bloqs/basic_gates/z_basis.py b/qualtran/bloqs/basic_gates/z_basis.py index e7b017eeb..599acfc89 100644 --- a/qualtran/bloqs/basic_gates/z_basis.py +++ b/qualtran/bloqs/basic_gates/z_basis.py @@ -41,8 +41,8 @@ SoquetT, ) from qualtran.bloqs.bookkeeping import ArbitraryClifford -from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.drawing import Circle, directional_text_box, Text, TextBox, WireSymbol +from qualtran.symbolics import SymbolicInt if TYPE_CHECKING: import cirq @@ -277,9 +277,6 @@ def as_cirq_op( (q,) = q return cirq.Z(q), {'q': np.asarray([q])} - def _t_complexity_(self) -> 'TComplexity': - return TComplexity(clifford=1) - def wire_symbol( self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() ) -> 'WireSymbol': @@ -343,6 +340,13 @@ def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) - return Circle() raise ValueError(f'Unknown wire symbol register name: {reg.name}') + def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> Tuple['Bloq', 'AddControlledT']: + from qualtran.bloqs.mcmt.specialized_ctrl import get_ctrl_system_1bit_cv_from_bloqs + + return get_ctrl_system_1bit_cv_from_bloqs( + self, ctrl_spec, current_ctrl_bit=1, bloq_with_ctrl=self, ctrl_reg_name='q1' + ) + @bloq_example def _cz() -> CZ: @@ -457,7 +461,7 @@ class IntState(_IntVector): val: The register of size `bitsize` which initializes the value `val`. """ - def __init__(self, val: Union[int, sympy.Expr], bitsize: Union[int, sympy.Expr]): + def __init__(self, val: SymbolicInt, bitsize: SymbolicInt): self.__attrs_init__(val=val, bitsize=bitsize, state=True) @@ -482,7 +486,7 @@ class IntEffect(_IntVector): val: The register of size `bitsize` which de-allocates the value `val`. """ - def __init__(self, val: int, bitsize: int): + def __init__(self, val: SymbolicInt, bitsize: SymbolicInt): self.__attrs_init__(val=val, bitsize=bitsize, state=False) diff --git a/qualtran/bloqs/block_encoding/__init__.py b/qualtran/bloqs/block_encoding/__init__.py index 6bf24ff4a..03c07b298 100644 --- a/qualtran/bloqs/block_encoding/__init__.py +++ b/qualtran/bloqs/block_encoding/__init__.py @@ -23,5 +23,6 @@ from qualtran.bloqs.block_encoding.phase import Phase from qualtran.bloqs.block_encoding.product import Product from qualtran.bloqs.block_encoding.sparse_matrix import SparseMatrix +from qualtran.bloqs.block_encoding.sparse_matrix_hermitian import SparseMatrixHermitian from qualtran.bloqs.block_encoding.tensor_product import TensorProduct from qualtran.bloqs.block_encoding.unitary import Unitary diff --git a/qualtran/bloqs/block_encoding/lcu_block_encoding.py b/qualtran/bloqs/block_encoding/lcu_block_encoding.py index 308bb85ab..98af6a8f9 100644 --- a/qualtran/bloqs/block_encoding/lcu_block_encoding.py +++ b/qualtran/bloqs/block_encoding/lcu_block_encoding.py @@ -23,11 +23,12 @@ BloqBuilder, BloqDocSpec, CtrlSpec, + QBit, Register, + Side, Signature, SoquetT, ) -from qualtran._infra.single_qubit_controlled import SpecializedSingleQubitControlledExtension from qualtran.bloqs.block_encoding.block_encoding_base import BlockEncoding from qualtran.bloqs.multiplexers.black_box_select import BlackBoxSelect from qualtran.bloqs.multiplexers.select_base import SelectOracle @@ -44,7 +45,7 @@ def _total_bits(registers: Union[Tuple[Register, ...], Signature]) -> int: @attrs.frozen -class SelectBlockEncoding(BlockEncoding, SpecializedSingleQubitControlledExtension): +class SelectBlockEncoding(BlockEncoding): r"""LCU based block encoding using SELECT and PREPARE oracles. Builds the block encoding via @@ -95,11 +96,6 @@ class SelectBlockEncoding(BlockEncoding, SpecializedSingleQubitControlledExtensi select: Union[BlackBoxSelect, SelectOracle] prepare: Union[BlackBoxPrepare, PrepareOracle] - control_val: Optional[int] = None - - @cached_property - def control_registers(self) -> Tuple[Register, ...]: - return self.select.control_registers @cached_property def ancilla_bitsize(self) -> int: @@ -136,14 +132,7 @@ def epsilon(self) -> SymbolicFloat: @cached_property def signature(self) -> Signature: - return Signature( - [ - *self.control_registers, - *self.selection_registers, - *self.junk_registers, - *self.target_registers, - ] - ) + return Signature([*self.selection_registers, *self.junk_registers, *self.target_registers]) @cached_property def signal_state(self) -> Union[BlackBoxPrepare, PrepareOracle]: @@ -157,26 +146,12 @@ def build_composite_bloq(self, bb: 'BloqBuilder', **soqs: SoquetT) -> Dict[str, def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -> 'WireSymbol': if reg is None: return Text('') - if reg.name == 'control': - return Circle(filled=bool(self.control_val)) else: return TextBox('B[H]') - def get_single_qubit_controlled_bloq(self, control_val: int) -> 'SelectBlockEncoding': - if self.control_val is not None: - raise ValueError( - "control_val is not None but trying to build controlled SelectBlockEncoding." - ) - c_select = self.select.controlled(ctrl_spec=CtrlSpec(cvs=control_val)) - if not isinstance(c_select, SelectOracle): - raise TypeError( - f"controlled version of {self.select} = {c_select} must also be a SelectOracle" - ) - return attrs.evolve(self, select=c_select, control_val=control_val) - @attrs.frozen -class LCUBlockEncoding(BlockEncoding, SpecializedSingleQubitControlledExtension): +class LCUBlockEncoding(BlockEncoding): r"""LCU based block encoding using SELECT and PREPARE oracles. Builds the standard block encoding from an LCU as @@ -230,7 +205,7 @@ class LCUBlockEncoding(BlockEncoding, SpecializedSingleQubitControlledExtension) @cached_property def control_registers(self) -> Tuple[Register, ...]: - return self.select.control_registers + return () if self.control_val is None else (Register('ctrl', QBit()),) @cached_property def ancilla_bitsize(self) -> int: @@ -250,7 +225,7 @@ def selection_registers(self) -> Tuple[Register, ...]: @cached_property def junk_registers(self) -> Tuple[Register, ...]: - return self.prepare.junk_registers + return tuple(reg for reg in self.prepare.junk_registers if reg.side == Side.THRU) @cached_property def target_registers(self) -> Tuple[Register, ...]: @@ -286,8 +261,18 @@ def _extract_soqs(bloq: Bloq) -> Dict[str, 'SoquetT']: return {reg.name: soqs.pop(reg.name) for reg in bloq.signature.lefts()} soqs |= bb.add_d(self.prepare, **_extract_soqs(self.prepare)) - soqs |= bb.add_d(self.select, **_extract_soqs(self.select)) + + select_soqs = _extract_soqs(self.select) + if self.control_val is None: + soqs |= bb.add_d(self.select, **select_soqs) + else: + _, ctrl_select_adder = self.select.get_ctrl_system(CtrlSpec(cvs=self.control_val)) + (ctrl,), select_soqs_t = ctrl_select_adder(bb, [soqs.pop('ctrl')], select_soqs) + soqs |= {'ctrl': ctrl} + soqs |= dict(zip([reg.name for reg in self.select.signature], select_soqs_t)) + soqs |= bb.add_d(self.prepare.adjoint(), **_extract_soqs(self.prepare.adjoint())) + return soqs def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -> 'WireSymbol': @@ -298,17 +283,13 @@ def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) - else: return TextBox('B[H]') - def get_single_qubit_controlled_bloq(self, control_val: int) -> 'LCUBlockEncoding': - if self.control_val is not None: - raise ValueError( - "control_val is not None but trying to build controlled SelectBlockEncoding." - ) - c_select = self.select.controlled(ctrl_spec=CtrlSpec(cvs=control_val)) - if not isinstance(c_select, SelectOracle): - raise TypeError( - f"controlled version of {self.select} = {c_select} must also be a SelectOracle" - ) - return attrs.evolve(self, select=c_select, control_val=control_val) + def adjoint(self) -> 'Bloq': + from qualtran.bloqs.mcmt.specialized_ctrl import ( + AdjointWithSpecializedCtrl, + SpecializeOnCtrlBit, + ) + + return AdjointWithSpecializedCtrl(self, SpecializeOnCtrlBit.ONE) @bloq_example diff --git a/qualtran/bloqs/block_encoding/linear_combination.ipynb b/qualtran/bloqs/block_encoding/linear_combination.ipynb index 085c41cda..415f99e84 100644 --- a/qualtran/bloqs/block_encoding/linear_combination.ipynb +++ b/qualtran/bloqs/block_encoding/linear_combination.ipynb @@ -64,7 +64,7 @@ " - `resource`: The resource register (present only if bitsize > 0). \n", "\n", "#### References\n", - " - [Quantum algorithms: A survey of applications and end-to-end complexities]( https://arxiv.org/abs/2310.03011). Dalzell et al. (2023). Ch. 10.2.\n" + " - [Quantum algorithms: A survey of applications and end-to-end complexities](https://arxiv.org/abs/2310.03011). Dalzell et al. (2023). Ch. 10.2.\n" ] }, { diff --git a/qualtran/bloqs/block_encoding/linear_combination.py b/qualtran/bloqs/block_encoding/linear_combination.py index f890166d6..16586cf61 100644 --- a/qualtran/bloqs/block_encoding/linear_combination.py +++ b/qualtran/bloqs/block_encoding/linear_combination.py @@ -75,8 +75,8 @@ class LinearCombination(BlockEncoding): resource: The resource register (present only if bitsize > 0). References: - [Quantum algorithms: A survey of applications and end-to-end complexities]( - https://arxiv.org/abs/2310.03011). Dalzell et al. (2023). Ch. 10.2. + [Quantum algorithms: A survey of applications and end-to-end complexities](https://arxiv.org/abs/2310.03011). + Dalzell et al. (2023). Ch. 10.2. """ _block_encodings: Tuple[BlockEncoding, ...] = field( diff --git a/qualtran/bloqs/block_encoding/product.ipynb b/qualtran/bloqs/block_encoding/product.ipynb index 7e097923d..727fb156b 100644 --- a/qualtran/bloqs/block_encoding/product.ipynb +++ b/qualtran/bloqs/block_encoding/product.ipynb @@ -69,7 +69,7 @@ " - `resource`: The resource register (present only if bitsize > 0). \n", "\n", "#### References\n", - " - [Quantum algorithms: A survey of applications and end-to-end complexities]( https://arxiv.org/abs/2310.03011). Dalzell et al. (2023). Ch. 10.2.\n" + " - [Quantum algorithms: A survey of applications and end-to-end complexities](https://arxiv.org/abs/2310.03011). Dalzell et al. (2023). Ch. 10.2.\n" ] }, { diff --git a/qualtran/bloqs/block_encoding/product.py b/qualtran/bloqs/block_encoding/product.py index 0dc50007d..aa2507f99 100644 --- a/qualtran/bloqs/block_encoding/product.py +++ b/qualtran/bloqs/block_encoding/product.py @@ -81,8 +81,8 @@ class Product(BlockEncoding): resource: The resource register (present only if bitsize > 0). References: - [Quantum algorithms: A survey of applications and end-to-end complexities]( - https://arxiv.org/abs/2310.03011). Dalzell et al. (2023). Ch. 10.2. + [Quantum algorithms: A survey of applications and end-to-end complexities](https://arxiv.org/abs/2310.03011). + Dalzell et al. (2023). Ch. 10.2. """ block_encodings: Tuple[BlockEncoding, ...] = field( diff --git a/qualtran/bloqs/block_encoding/sparse_matrix.py b/qualtran/bloqs/block_encoding/sparse_matrix.py index 60f8a39cf..7c126c77a 100644 --- a/qualtran/bloqs/block_encoding/sparse_matrix.py +++ b/qualtran/bloqs/block_encoding/sparse_matrix.py @@ -30,6 +30,7 @@ DecomposeTypeError, QAny, QBit, + QInt, QUInt, Register, Signature, @@ -334,7 +335,7 @@ def call_classically(self, l: ClassicalValT, i: ClassicalValT) -> Tuple[Classica def build_call_graph(self, ssa: 'SympySymbolAllocator') -> BloqCountDictT: return { Add(QUInt(self.system_bitsize), QUInt(self.system_bitsize)): 1, - AddK(self.system_bitsize, -self.bandsize, signed=True): 1, + AddK(QInt(self.system_bitsize), -self.bandsize): 1, } def build_composite_bloq(self, bb: BloqBuilder, l: SoquetT, i: SoquetT) -> Dict[str, SoquetT]: @@ -342,7 +343,7 @@ def build_composite_bloq(self, bb: BloqBuilder, l: SoquetT, i: SoquetT) -> Dict[ raise DecomposeTypeError(f"Cannot decompose symbolic {self=}") i, l = bb.add(Add(QUInt(self.system_bitsize), QUInt(self.system_bitsize)), a=i, b=l) - l = bb.add(AddK(self.system_bitsize, -self.bandsize, signed=True), x=l) + l = bb.add(AddK(QInt(self.system_bitsize), -self.bandsize), x=l) return {"l": l, "i": i} diff --git a/qualtran/bloqs/block_encoding/sparse_matrix_hermitian.ipynb b/qualtran/bloqs/block_encoding/sparse_matrix_hermitian.ipynb new file mode 100644 index 000000000..117edcce9 --- /dev/null +++ b/qualtran/bloqs/block_encoding/sparse_matrix_hermitian.ipynb @@ -0,0 +1,204 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "8e5e678f", + "metadata": { + "cq.autogen": "title_cell" + }, + "source": [ + "# Sparse Matrix Hermitian" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "70980f2b", + "metadata": { + "cq.autogen": "top_imports" + }, + "outputs": [], + "source": [ + "from qualtran import Bloq, CompositeBloq, BloqBuilder, Signature, Register\n", + "from qualtran import QBit, QInt, QUInt, QAny\n", + "from qualtran.drawing import show_bloq, show_call_graph, show_counts_sigma\n", + "from typing import *\n", + "import numpy as np\n", + "import sympy\n", + "import cirq" + ] + }, + { + "cell_type": "markdown", + "id": "8db414b7", + "metadata": { + "cq.autogen": "SparseMatrixHermitian.bloq_doc.md" + }, + "source": [ + "## `SparseMatrixHermitian`\n", + "Hermitian Block encoding of a sparse-access Hermitian matrix.\n", + "\n", + "Given column and entry oracles $O_c$ and $O_A$ for an $s$-sparse Hermitian matrix\n", + "$A \\in \\mathbb{C}^{2^n \\times 2^n}$, i.e. one where each row / column has exactly $s$ non-zero\n", + "entries, computes a $(s, n+1, \\epsilon)$-block encoding of $A$ as follows:\n", + "```\n", + " ┌────┐\n", + "a |0> ─┤ ├─ |0> ───────────────────────X────────────────────\n", + " │ │ ┌──┐ | ┌──┐\n", + " │ U │ = │ n│ ┌────┐ ┌────┐ | ┌────┐ ┌────┐ │ n│\n", + "l |0^n> ─┤ A ├─ |0^n> ─┤H ├─┤ O ├─┤ ├─X──|─┤ ├─┤ O* ├─┤H ├─\n", + " │ │ └──┘ | c | │ │ | | │ │ | c | └──┘\n", + " │ │ └────┘ │ O │ │ | │ O* │ └────┘\n", + "b |0> ─┤ ├─ |0> ────────|────┤ A ├─|──X─┤ A ├───|─────────\n", + " | | ┌────┐ | | | | | ┌────┐\n", + " | | | O | | | | | | | O* |\n", + "j |Psi> ─┤ ├─ |Psi> ──────┤ c ├─┤ ├─X────┤ ├─┤ c ├──────\n", + " └────┘ └────┘ └────┘ └────┘ └────┘\n", + "```\n", + "\n", + "To encode a matrix of irregular dimension, the matrix should first be embedded into one of\n", + "dimension $2^n \\times 2^n$ for suitable $n$.\n", + "To encode a matrix where each row / column has at most $s$ non-zero entries, some zeroes should\n", + "be treated as if they were non-zero so that each row / column has exactly $s$ non-zero entries.\n", + "\n", + "For encoding a non-hermitian matrix, or a slightly more efficient (but non Hermitian-encoding)\n", + "of a matrix, use :class:`SparseMatrix` instead.\n", + "\n", + "#### Parameters\n", + " - `col_oracle`: The column oracle $O_c$. See `RowColumnOracle` for definition.\n", + " - `entry_oracle`: The entry oracle $O_A$. See `EntryOracle` for definition.\n", + " - `eps`: The precision of the block encoding.\n", + " - `is_controlled`: if True, returns the controlled block-encoding. \n", + "\n", + "#### Registers\n", + " - `ctrl`: The single qubit control register. (present only if `is_controlled` is `True`)\n", + " - `system`: The system register.\n", + " - `ancilla`: The ancilla register.\n", + " - `resource`: The resource register (present only if `bitsize > 0`). \n", + "\n", + "#### References\n", + " - [Lecture Notes on Quantum Algorithms for Scientific Computation](https://arxiv.org/abs/2201.08309). Lin Lin (2022). Ch. 6.5. Proposition 6.8, Fig 6.7.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f31bfd74", + "metadata": { + "cq.autogen": "SparseMatrixHermitian.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.block_encoding import SparseMatrixHermitian" + ] + }, + { + "cell_type": "markdown", + "id": "435f31d2", + "metadata": { + "cq.autogen": "SparseMatrixHermitian.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "720f3f9b", + "metadata": { + "cq.autogen": "SparseMatrixHermitian.sparse_matrix_symb_hermitian_block_encoding" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.block_encoding.sparse_matrix import TopLeftRowColumnOracle\n", + "from qualtran.bloqs.block_encoding.sparse_matrix_hermitian import UniformSqrtEntryOracle\n", + "\n", + "n = sympy.Symbol('n', positive=True, integer=True)\n", + "col_oracle = TopLeftRowColumnOracle(system_bitsize=n)\n", + "entry_oracle = UniformSqrtEntryOracle(system_bitsize=n, entry=0.3)\n", + "sparse_matrix_symb_hermitian_block_encoding = SparseMatrixHermitian(\n", + " col_oracle, entry_oracle, eps=0\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "70e512ff", + "metadata": { + "cq.autogen": "SparseMatrixHermitian.sparse_matrix_hermitian_block_encoding" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.block_encoding.sparse_matrix import TopLeftRowColumnOracle\n", + "from qualtran.bloqs.block_encoding.sparse_matrix_hermitian import UniformSqrtEntryOracle\n", + "\n", + "col_oracle = TopLeftRowColumnOracle(system_bitsize=2)\n", + "entry_oracle = UniformSqrtEntryOracle(system_bitsize=2, entry=0.3)\n", + "sparse_matrix_hermitian_block_encoding = SparseMatrixHermitian(col_oracle, entry_oracle, eps=0)" + ] + }, + { + "cell_type": "markdown", + "id": "6e8d3efa", + "metadata": { + "cq.autogen": "SparseMatrixHermitian.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "96d58575", + "metadata": { + "cq.autogen": "SparseMatrixHermitian.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([sparse_matrix_symb_hermitian_block_encoding, sparse_matrix_hermitian_block_encoding],\n", + " ['`sparse_matrix_symb_hermitian_block_encoding`', '`sparse_matrix_hermitian_block_encoding`'])" + ] + }, + { + "cell_type": "markdown", + "id": "e0108dfc", + "metadata": { + "cq.autogen": "SparseMatrixHermitian.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "87933b2f", + "metadata": { + "cq.autogen": "SparseMatrixHermitian.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "sparse_matrix_symb_hermitian_block_encoding_g, sparse_matrix_symb_hermitian_block_encoding_sigma = sparse_matrix_symb_hermitian_block_encoding.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(sparse_matrix_symb_hermitian_block_encoding_g)\n", + "show_counts_sigma(sparse_matrix_symb_hermitian_block_encoding_sigma)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/qualtran/bloqs/block_encoding/sparse_matrix_hermitian.py b/qualtran/bloqs/block_encoding/sparse_matrix_hermitian.py new file mode 100644 index 000000000..43ff03547 --- /dev/null +++ b/qualtran/bloqs/block_encoding/sparse_matrix_hermitian.py @@ -0,0 +1,316 @@ +# 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 abc +from collections import Counter +from functools import cached_property + +import attrs +import numpy as np +import sympy +from attrs import frozen + +from qualtran import ( + AddControlledT, + Bloq, + bloq_example, + BloqBuilder, + BloqDocSpec, + CtrlSpec, + DecomposeTypeError, + QAny, + QBit, + Register, + Signature, + Soquet, + SoquetT, +) +from qualtran.bloqs.basic_gates import CSwap, Ry, Swap +from qualtran.bloqs.block_encoding import BlockEncoding +from qualtran.bloqs.block_encoding.sparse_matrix import RowColumnOracle +from qualtran.bloqs.bookkeeping import Partition +from qualtran.bloqs.bookkeeping.auto_partition import AutoPartition, Unused +from qualtran.bloqs.reflections.prepare_identity import PrepareIdentity +from qualtran.bloqs.state_preparation.black_box_prepare import BlackBoxPrepare +from qualtran.bloqs.state_preparation.prepare_uniform_superposition import ( + PrepareUniformSuperposition, +) +from qualtran.resource_counting import BloqCountT, SympySymbolAllocator +from qualtran.symbolics import is_symbolic, SymbolicFloat, SymbolicInt +from qualtran.symbolics.math_funcs import bit_length + + +class SqrtEntryOracle(Bloq): + r"""Oracle specifying the sqrt of entries of a sparse-access matrix. + + In the reference, this is the interface of + $$O_A : \ket{0}\ket{i}\ket{j} \mapsto (\sqrt{A_{ij}} \ket{0} + \sqrt{1 - |A_{ij}|}\ket{i}\ket{j}).$$ + + Registers: + q: The flag qubit that is rotated proportionally to the value of the entry. + i: The row index. + j: The column index. + + References: + [Lecture Notes on Quantum Algorithms for Scientific Computation](https://arxiv.org/abs/2201.08309). Lin Lin (2022). Ch. 6.5. + """ + + @property + @abc.abstractmethod + def system_bitsize(self) -> SymbolicInt: + """The number of bits used to represent an index.""" + + @property + @abc.abstractmethod + def epsilon(self) -> SymbolicFloat: + """The number of bits used to represent an index.""" + + @cached_property + def signature(self) -> Signature: + return Signature.build_from_dtypes( + q=QBit(), i=QAny(self.system_bitsize), j=QAny(self.system_bitsize) + ) + + +@frozen +class SparseMatrixHermitian(BlockEncoding): + r"""Hermitian Block encoding of a sparse-access Hermitian matrix. + + Given column and entry oracles $O_c$ and $O_A$ for an $s$-sparse Hermitian matrix + $A \in \mathbb{C}^{2^n \times 2^n}$, i.e. one where each row / column has exactly $s$ non-zero + entries, computes a $(s, n+1, \epsilon)$-block encoding of $A$ as follows: + ``` + ┌────┐ + a |0> ─┤ ├─ |0> ───────────────────────X──────────────────── + │ │ ┌──┐ | ┌──┐ + │ U │ = │ n│ ┌────┐ ┌────┐ | ┌────┐ ┌────┐ │ n│ + l |0^n> ─┤ A ├─ |0^n> ─┤H ├─┤ O ├─┤ ├─X──|─┤ ├─┤ O* ├─┤H ├─ + │ │ └──┘ | c | │ │ | | │ │ | c | └──┘ + │ │ └────┘ │ O │ │ | │ O* │ └────┘ + b |0> ─┤ ├─ |0> ────────|────┤ A ├─|──X─┤ A ├───|───────── + | | ┌────┐ | | | | | ┌────┐ + | | | O | | | | | | | O* | + j |Psi> ─┤ ├─ |Psi> ──────┤ c ├─┤ ├─X────┤ ├─┤ c ├────── + └────┘ └────┘ └────┘ └────┘ └────┘ + ``` + + To encode a matrix of irregular dimension, the matrix should first be embedded into one of + dimension $2^n \times 2^n$ for suitable $n$. + To encode a matrix where each row / column has at most $s$ non-zero entries, some zeroes should + be treated as if they were non-zero so that each row / column has exactly $s$ non-zero entries. + + For encoding a non-hermitian matrix, or a slightly more efficient (but non Hermitian-encoding) + of a matrix, use :class:`SparseMatrix` instead. + + Args: + col_oracle: The column oracle $O_c$. See `RowColumnOracle` for definition. + entry_oracle: The entry oracle $O_A$. See `EntryOracle` for definition. + eps: The precision of the block encoding. + is_controlled: if True, returns the controlled block-encoding. + + Registers: + ctrl: The single qubit control register. (present only if `is_controlled` is `True`) + system: The system register. + ancilla: The ancilla register. + resource: The resource register (present only if `bitsize > 0`). + + References: + [Lecture Notes on Quantum Algorithms for Scientific Computation](https://arxiv.org/abs/2201.08309). + Lin Lin (2022). Ch. 6.5. Proposition 6.8, Fig 6.7. + """ + + col_oracle: RowColumnOracle + entry_oracle: SqrtEntryOracle + eps: SymbolicFloat + is_controlled: bool = False + + def __attrs_post_init__(self): + if self.col_oracle.system_bitsize != self.entry_oracle.system_bitsize: + raise ValueError("column and entry oracles must have same bitsize") + + @cached_property + def signature(self) -> Signature: + n_ctrls = 1 if self.is_controlled else 0 + + return Signature.build_from_dtypes( + ctrl=QAny(n_ctrls), + system=QAny(self.system_bitsize), + ancilla=QAny(self.ancilla_bitsize), + resource=QAny(self.resource_bitsize), # if resource_bitsize is 0, not present + ) + + @cached_property + def system_bitsize(self) -> SymbolicInt: + return self.entry_oracle.system_bitsize + + def __str__(self) -> str: + return "B[SparseMatrixHermitian]" + + @cached_property + def alpha(self) -> SymbolicFloat: + return self.col_oracle.num_nonzero + + @cached_property + def ancilla_bitsize(self) -> SymbolicInt: + return self.system_bitsize + 2 + + @cached_property + def resource_bitsize(self) -> SymbolicInt: + return 0 + + @cached_property + def epsilon(self) -> SymbolicFloat: + return self.eps + + @property + def signal_state(self) -> BlackBoxPrepare: + return BlackBoxPrepare(PrepareIdentity.from_bitsizes([self.ancilla_bitsize])) + + @cached_property + def diffusion(self): + unused = self.system_bitsize - bit_length(self.col_oracle.num_nonzero - 1) + return AutoPartition( + PrepareUniformSuperposition(n=self.col_oracle.num_nonzero), + partitions=[ + (Register("target", QAny(self.system_bitsize)), [Unused(unused), "target"]) + ], + ) + + def build_call_graph(self, ssa: SympySymbolAllocator) -> set[BloqCountT]: + counts = Counter[Bloq]() + + counts[self.diffusion] += 1 + counts[self.col_oracle] += 1 + counts[self.entry_oracle] += 1 + if self.is_controlled: + counts[CSwap(self.system_bitsize)] += 1 + counts[CSwap(1)] += 1 + else: + counts[Swap(self.system_bitsize)] += 1 + counts[Swap(1)] += 1 + counts[self.entry_oracle.adjoint()] += 1 + counts[self.col_oracle.adjoint()] += 1 + counts[self.diffusion.adjoint()] += 1 + + return set(counts.items()) + + def build_composite_bloq( + self, bb: BloqBuilder, system: SoquetT, ancilla: SoquetT, **soqs + ) -> dict[str, SoquetT]: + if is_symbolic(self.system_bitsize) or is_symbolic(self.col_oracle.num_nonzero): + raise DecomposeTypeError(f"Cannot decompose symbolic {self=}") + + ctrl = soqs.pop('ctrl', None) + + assert not isinstance(ancilla, np.ndarray) + partition_ancilla = Partition( + n=self.ancilla_bitsize, + regs=( + Register('a', QBit()), + Register('l', QAny(self.system_bitsize)), + Register('b', QBit()), + ), + ) + + a, l, b = bb.add(partition_ancilla, x=ancilla) + + l = bb.add(self.diffusion, target=l) + l, system = bb.add(self.col_oracle, l=l, i=system) + b, l, system = bb.add(self.entry_oracle, q=b, i=l, j=system) + + if self.is_controlled: + ctrl, l, system = bb.add(CSwap(self.system_bitsize), ctrl=ctrl, x=l, y=system) + ctrl, a, b = bb.add(CSwap(1), ctrl=ctrl, x=a, y=b) + else: + l, system = bb.add(Swap(self.system_bitsize), x=l, y=system) + a, b = bb.add(Swap(1), x=a, y=b) + + b, l, system = bb.add(self.entry_oracle.adjoint(), q=b, i=l, j=system) + l, system = bb.add(self.col_oracle.adjoint(), l=l, i=system) + l = bb.add(self.diffusion.adjoint(), target=l) + + ancilla = bb.add(partition_ancilla.adjoint(), a=a, l=l, b=b) + + out_soqs = {"system": system, "ancilla": ancilla} + if self.is_controlled: + out_soqs |= {"ctrl": ctrl} + return out_soqs + + def adjoint(self) -> 'SparseMatrixHermitian': + return self + + def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> tuple['Bloq', 'AddControlledT']: + from qualtran.bloqs.mcmt.specialized_ctrl import get_ctrl_system_1bit_cv_from_bloqs + + return get_ctrl_system_1bit_cv_from_bloqs( + self, + ctrl_spec, + current_ctrl_bit=1 if self.is_controlled else None, + bloq_with_ctrl=self if self.is_controlled else attrs.evolve(self, is_controlled=True), + ctrl_reg_name='ctrl', + ) + + +@frozen +class UniformSqrtEntryOracle(SqrtEntryOracle): + """Oracle specifying the entries of a matrix with uniform entries.""" + + system_bitsize: SymbolicInt + entry: float + eps: float = 1e-11 + + @property + def epsilon(self) -> SymbolicFloat: + return self.eps + + def build_composite_bloq( + self, bb: BloqBuilder, q: Soquet, **soqs: SoquetT + ) -> dict[str, SoquetT]: + # Either Rx or Ry work here; Rx would induce a phase on the subspace with non-zero ancilla + # See https://arxiv.org/abs/2302.10949 for reference that uses Rx + soqs["q"] = bb.add(Ry(2 * np.arccos(np.sqrt(self.entry))), q=q) + return soqs + + +@bloq_example +def _sparse_matrix_hermitian_block_encoding() -> SparseMatrixHermitian: + from qualtran.bloqs.block_encoding.sparse_matrix import TopLeftRowColumnOracle + from qualtran.bloqs.block_encoding.sparse_matrix_hermitian import UniformSqrtEntryOracle + + col_oracle = TopLeftRowColumnOracle(system_bitsize=2) + entry_oracle = UniformSqrtEntryOracle(system_bitsize=2, entry=0.3) + sparse_matrix_hermitian_block_encoding = SparseMatrixHermitian(col_oracle, entry_oracle, eps=0) + return sparse_matrix_hermitian_block_encoding + + +@bloq_example +def _sparse_matrix_symb_hermitian_block_encoding() -> SparseMatrixHermitian: + from qualtran.bloqs.block_encoding.sparse_matrix import TopLeftRowColumnOracle + from qualtran.bloqs.block_encoding.sparse_matrix_hermitian import UniformSqrtEntryOracle + + n = sympy.Symbol('n', positive=True, integer=True) + col_oracle = TopLeftRowColumnOracle(system_bitsize=n) + entry_oracle = UniformSqrtEntryOracle(system_bitsize=n, entry=0.3) + sparse_matrix_symb_hermitian_block_encoding = SparseMatrixHermitian( + col_oracle, entry_oracle, eps=0 + ) + return sparse_matrix_symb_hermitian_block_encoding + + +_SPARSE_MATRIX_HERMITIAN_DOC = BloqDocSpec( + bloq_cls=SparseMatrixHermitian, + examples=[ + _sparse_matrix_symb_hermitian_block_encoding, + _sparse_matrix_hermitian_block_encoding, + ], +) diff --git a/qualtran/bloqs/block_encoding/sparse_matrix_hermitian_test.py b/qualtran/bloqs/block_encoding/sparse_matrix_hermitian_test.py new file mode 100644 index 000000000..367de6a3a --- /dev/null +++ b/qualtran/bloqs/block_encoding/sparse_matrix_hermitian_test.py @@ -0,0 +1,118 @@ +# 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. +from typing import cast + +import numpy as np +import sympy + +import qualtran.testing as qlt_testing +from qualtran import BloqBuilder, Soquet +from qualtran.bloqs.basic_gates import Hadamard, IntEffect, IntState +from qualtran.bloqs.block_encoding.sparse_matrix import TopLeftRowColumnOracle +from qualtran.bloqs.block_encoding.sparse_matrix_hermitian import ( + _sparse_matrix_hermitian_block_encoding, + _sparse_matrix_symb_hermitian_block_encoding, + SparseMatrixHermitian, + UniformSqrtEntryOracle, +) +from qualtran.resource_counting.generalizers import ignore_split_join + + +def test_sparse_matrix(bloq_autotester): + bloq_autotester(_sparse_matrix_hermitian_block_encoding) + + +def test_sparse_matrix_symb(bloq_autotester): + bloq_autotester(_sparse_matrix_symb_hermitian_block_encoding) + + +def test_sparse_matrix_params(): + bloq = _sparse_matrix_hermitian_block_encoding() + assert bloq.system_bitsize == 2 + assert bloq.alpha == 4 + assert bloq.epsilon == 0 + assert bloq.ancilla_bitsize == 2 + 2 + assert bloq.resource_bitsize == 0 + + bloq = _sparse_matrix_symb_hermitian_block_encoding() + n = sympy.Symbol('n', positive=True, integer=True) + assert bloq.system_bitsize == n + assert bloq.alpha == 2**n + assert bloq.epsilon == 0 + assert bloq.ancilla_bitsize == n + 2 + assert bloq.resource_bitsize == 0 + + +def test_call_graph(): + bloq = _sparse_matrix_hermitian_block_encoding() + _, sigma = bloq.call_graph(generalizer=ignore_split_join) + assert sigma[Hadamard()] == 4 + + bloq = _sparse_matrix_symb_hermitian_block_encoding() + _, sigma = bloq.call_graph(generalizer=ignore_split_join) + n = sympy.Symbol('n', integer=True, positive=True) + assert sigma[Hadamard()] == 6 * n + + +def test_sparse_matrix_tensors(): + bloq = _sparse_matrix_hermitian_block_encoding() + alpha = bloq.alpha + bb = BloqBuilder() + system = bb.add_register("system", 2) + ancilla = cast(Soquet, bb.add(IntState(0, 4))) + system, ancilla = bb.add_t(bloq, system=system, ancilla=ancilla) + bb.add(IntEffect(0, 4), val=ancilla) + bloq = bb.finalize(system=system) + + from_gate = np.full((4, 4), 0.3) + from_tensors = bloq.tensor_contract() * alpha + np.testing.assert_allclose(from_gate, from_tensors) + + +topleft_matrix = [ + [0.3, 0.3, 0.3, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.3, 0.3, 0.3, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.3, 0.3, 0.3, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], +] + + +def test_top_left_matrix(): + col_oracle = TopLeftRowColumnOracle(system_bitsize=3, num_nonzero=3) + entry_oracle = UniformSqrtEntryOracle(system_bitsize=3, entry=0.3) + bloq = SparseMatrixHermitian(col_oracle, entry_oracle, eps=0) + alpha = bloq.alpha + + bb = BloqBuilder() + system = bb.add_register("system", 3) + ancilla = cast(Soquet, bb.add(IntState(0, 3 + 2))) + system, ancilla = bb.add_t(bloq, system=system, ancilla=ancilla) + bb.add(IntEffect(0, 3 + 2), val=ancilla) + bloq = bb.finalize(system=system) + + from_tensors = bloq.tensor_contract() * alpha + np.testing.assert_allclose(topleft_matrix, from_tensors, atol=0.003) + + +def test_counts(): + qlt_testing.assert_equivalent_bloq_counts( + _sparse_matrix_hermitian_block_encoding(), generalizer=ignore_split_join + ) + qlt_testing.assert_equivalent_bloq_counts( + _sparse_matrix_hermitian_block_encoding().controlled(), generalizer=ignore_split_join + ) diff --git a/qualtran/bloqs/bookkeeping/_bookkeeping_bloq.py b/qualtran/bloqs/bookkeeping/_bookkeeping_bloq.py index 5e2dab7ec..32b9b7498 100644 --- a/qualtran/bloqs/bookkeeping/_bookkeeping_bloq.py +++ b/qualtran/bloqs/bookkeeping/_bookkeeping_bloq.py @@ -16,7 +16,6 @@ from typing import Dict, Iterable, Optional, Sequence, Tuple, TYPE_CHECKING from qualtran import Bloq, BloqBuilder, SoquetT -from qualtran.cirq_interop.t_complexity_protocol import TComplexity if TYPE_CHECKING: @@ -42,6 +41,3 @@ def add_controlled( return ctrl_soqs, out_soqs return self, add_controlled - - def _t_complexity_(self) -> 'TComplexity': - return TComplexity() diff --git a/qualtran/bloqs/bookkeeping/allocate.py b/qualtran/bloqs/bookkeeping/allocate.py index 7f566a714..cbb779a22 100644 --- a/qualtran/bloqs/bookkeeping/allocate.py +++ b/qualtran/bloqs/bookkeeping/allocate.py @@ -69,7 +69,7 @@ def adjoint(self) -> 'Bloq': return Free(self.dtype, self.dirty) def on_classical_vals(self) -> Dict[str, int]: - return {'reg': 0} + return {'reg': self.dtype.from_bits([0] * self.dtype.num_qubits)} def my_tensors( self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] diff --git a/qualtran/bloqs/bookkeeping/arbitrary_clifford.py b/qualtran/bloqs/bookkeeping/arbitrary_clifford.py index e9f7a751c..619ac9275 100644 --- a/qualtran/bloqs/bookkeeping/arbitrary_clifford.py +++ b/qualtran/bloqs/bookkeeping/arbitrary_clifford.py @@ -18,7 +18,6 @@ from sympy import Expr from qualtran import Bloq, QAny, Register, Signature -from qualtran.cirq_interop.t_complexity_protocol import TComplexity @frozen @@ -40,8 +39,5 @@ class ArbitraryClifford(Bloq): def signature(self) -> Signature: return Signature([Register('x', QAny(bitsize=self.n))]) - def _t_complexity_(self) -> 'TComplexity': - return TComplexity(clifford=1) - def __str__(self): return f'ArbitraryClifford(n={self.n})' diff --git a/qualtran/bloqs/bookkeeping/partition.py b/qualtran/bloqs/bookkeeping/partition.py index b568cc1e5..09d268687 100644 --- a/qualtran/bloqs/bookkeeping/partition.py +++ b/qualtran/bloqs/bookkeeping/partition.py @@ -145,7 +145,7 @@ def _classical_partition(self, x: 'ClassicalValT') -> Dict[str, 'ClassicalValT'] def _classical_unpartition_to_bits(self, **vals: 'ClassicalValT') -> NDArray[np.uint8]: out_vals: list[NDArray[np.uint8]] = [] for reg in self.regs: - reg_val = np.asarray(vals[reg.name]) + reg_val = np.asanyarray(vals[reg.name]) bitstrings = reg.dtype.to_bits_array(reg_val.ravel()) out_vals.append(bitstrings.ravel()) return np.concatenate(out_vals) diff --git a/qualtran/bloqs/bookkeeping/partition_test.py b/qualtran/bloqs/bookkeeping/partition_test.py index c0869ea9b..42dce656e 100644 --- a/qualtran/bloqs/bookkeeping/partition_test.py +++ b/qualtran/bloqs/bookkeeping/partition_test.py @@ -20,7 +20,7 @@ import pytest from attrs import frozen -from qualtran import Bloq, BloqBuilder, QAny, Register, Signature, Soquet, SoquetT +from qualtran import Bloq, BloqBuilder, QAny, QGF, Register, Signature, Soquet, SoquetT from qualtran._infra.gate_with_registers import get_named_qubits from qualtran.bloqs.basic_gates import CNOT from qualtran.bloqs.bookkeeping import Partition @@ -117,3 +117,14 @@ def test_partition_call_classically(): assert flat_out[2] == 2 out = bloq.adjoint().call_classically(**{reg.name: val for (reg, val) in zip(regs, out)}) assert out[0] == 64 + + +def test_partition_call_classically_gf(): + dtypes = [QGF(2, 2), QGF(2, 3)] + regs = (Register('xx', dtypes[0]), Register('yy', dtypes[1])) + partition = Partition(n=5, regs=regs) + unpartition = partition.adjoint() + for x in range(2**5): + xx, yy = partition.call_classically(x=x) + assert isinstance(xx, dtypes[0].gf_type) and isinstance(yy, dtypes[1].gf_type) + assert (x,) == unpartition.call_classically(xx=xx, yy=yy) diff --git a/qualtran/bloqs/chemistry/hubbard_model/qubitization/__init__.py b/qualtran/bloqs/chemistry/hubbard_model/qubitization/__init__.py index c6c019471..6cda5b775 100644 --- a/qualtran/bloqs/chemistry/hubbard_model/qubitization/__init__.py +++ b/qualtran/bloqs/chemistry/hubbard_model/qubitization/__init__.py @@ -14,7 +14,7 @@ r"""Simulating the Hubbard model Hamiltonian using qubitization. This module follows section V. of Encoding Electronic Spectra in Quantum Circuits with Linear T -Complexity. Babbush et. al. 2018. [arxiv:1805.03662](https://arxiv.org/abs/1805.03662). +Complexity. Babbush et al. 2018. [arxiv:1805.03662](https://arxiv.org/abs/1805.03662). The 2D Hubbard model is a special case of the electronic structure Hamiltonian restricted to spins on a planar grid. diff --git a/qualtran/bloqs/chemistry/hubbard_model/qubitization/hubbard_model.ipynb b/qualtran/bloqs/chemistry/hubbard_model/qubitization/hubbard_model.ipynb index e751ee01b..4b5d92aaa 100644 --- a/qualtran/bloqs/chemistry/hubbard_model/qubitization/hubbard_model.ipynb +++ b/qualtran/bloqs/chemistry/hubbard_model/qubitization/hubbard_model.ipynb @@ -12,7 +12,7 @@ "Simulating the Hubbard model Hamiltonian using qubitization.\n", "\n", "This module follows section V. of Encoding Electronic Spectra in Quantum Circuits with Linear T\n", - "Complexity. Babbush et. al. 2018. [arxiv:1805.03662](https://arxiv.org/abs/1805.03662).\n", + "Complexity. Babbush et al. 2018. [arxiv:1805.03662](https://arxiv.org/abs/1805.03662).\n", "\n", "The 2D Hubbard model is a special case of the electronic structure Hamiltonian\n", "restricted to spins on a planar grid.\n", diff --git a/qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard.py b/qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard.py index efa0c9e7c..66a123fa5 100644 --- a/qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard.py +++ b/qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard.py @@ -20,9 +20,19 @@ import numpy as np from numpy.typing import NDArray -from qualtran import bloq_example, BloqDocSpec, BQUInt, QAny, QBit, Register, Signature +from qualtran import ( + AddControlledT, + Bloq, + bloq_example, + BloqDocSpec, + BQUInt, + CtrlSpec, + QAny, + QBit, + Register, + Signature, +) from qualtran._infra.gate_with_registers import total_bits -from qualtran._infra.single_qubit_controlled import SpecializedSingleQubitControlledExtension from qualtran.bloqs.basic_gates import CSwap from qualtran.bloqs.multiplexers.apply_gate_to_lth_target import ApplyGateToLthQubit from qualtran.bloqs.multiplexers.select_base import SelectOracle @@ -30,7 +40,7 @@ @attrs.frozen -class SelectHubbard(SelectOracle, SpecializedSingleQubitControlledExtension): # type: ignore[misc] +class SelectHubbard(SelectOracle): r"""The SELECT operation optimized for the 2D Hubbard model. In contrast to SELECT for an arbitrary chemistry Hamiltonian, we: @@ -80,6 +90,10 @@ class SelectHubbard(SelectOracle, SpecializedSingleQubitControlledExtension): # def __attrs_post_init__(self): if self.x_dim != self.y_dim: raise NotImplementedError("Currently only supports the case where x_dim=y_dim.") + if self.control_val == 0: + raise NotImplementedError( + "control_val=0 not supported, use `SelectHubbard(x, y).controlled(CtrlSpec(cvs=0))` instead" + ) @cached_property def control_registers(self) -> Tuple[Register, ...]: @@ -145,8 +159,8 @@ def decompose_from_registers( yield CSwap.make_on(ctrl=V, x=p_y, y=q_y) yield CSwap.make_on(ctrl=V, x=p_x, y=q_x) - yield cirq.S(*control) ** -1 if control else cirq.global_phase_operation( - -1j + yield ( + cirq.S(*control) ** -1 if control else cirq.global_phase_operation(-1j) ) # Fix errant i from XY=iZ yield cirq.Z(*U).controlled_by(*control) # Fix errant -1 from multiple pauli applications @@ -180,6 +194,25 @@ def __str__(self): return f'C{s}' return s + def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> Tuple['Bloq', 'AddControlledT']: + from qualtran.bloqs.mcmt.specialized_ctrl import get_ctrl_system_1bit_cv_from_bloqs + + return get_ctrl_system_1bit_cv_from_bloqs( + self, + ctrl_spec=ctrl_spec, + current_ctrl_bit=self.control_val, + bloq_with_ctrl=attrs.evolve(self, control_val=1), + ctrl_reg_name='control', + ) + + def adjoint(self) -> 'Bloq': + from qualtran.bloqs.mcmt.specialized_ctrl import ( + AdjointWithSpecializedCtrl, + SpecializeOnCtrlBit, + ) + + return AdjointWithSpecializedCtrl(self, specialize_on_ctrl=SpecializeOnCtrlBit.ONE) + @bloq_example def _sel_hubb() -> SelectHubbard: 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 da6ce3deb..da964a804 100644 --- a/qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard_test.py +++ b/qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard_test.py @@ -11,6 +11,7 @@ # 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. +from unittest.mock import ANY import pytest @@ -18,7 +19,7 @@ _sel_hubb, SelectHubbard, ) -from qualtran.cirq_interop.t_complexity_protocol import t_complexity +from qualtran.resource_counting import GateCounts, get_cost_value, QECGatesCost def test_sel_hubb_auto(bloq_autotester): @@ -28,8 +29,19 @@ def test_sel_hubb_auto(bloq_autotester): @pytest.mark.parametrize('dim', [*range(2, 10)]) def test_select_t_complexity(dim): select = SelectHubbard(x_dim=dim, y_dim=dim, control_val=1) - cost = t_complexity(select) + cost = get_cost_value(select, QECGatesCost()) N = 2 * dim * dim logN = 2 * (dim - 1).bit_length() + 1 - assert cost.t == 10 * N + 14 * logN - 8 - assert cost.rotations == 0 + assert cost == GateCounts( + cswap=2 * logN, and_bloq=5 * (N // 2) - 2, measurement=5 * (N // 2) - 2, clifford=ANY + ) + assert cost.total_t_count() == 10 * N + 14 * logN - 8 + + +def test_adjoint_controlled(): + bloq = _sel_hubb() + + adj_ctrl_bloq = bloq.controlled().adjoint() + ctrl_adj_bloq = bloq.adjoint().controlled() + + assert adj_ctrl_bloq == ctrl_adj_bloq diff --git a/qualtran/bloqs/chemistry/ising/walk_operator.py b/qualtran/bloqs/chemistry/ising/walk_operator.py index 6867a20f1..7c237150f 100644 --- a/qualtran/bloqs/chemistry/ising/walk_operator.py +++ b/qualtran/bloqs/chemistry/ising/walk_operator.py @@ -52,7 +52,7 @@ def get_prepare_precision_from_eigenphase_precision( References: [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). - Babbush et. al. (2018). Eq (A9). + Babbush et al. (2018). Eq (A9). """ return ((eps_eigenphase * sum_of_coeffs) / ((1 + eps_eigenphase**2) * num_coeffs)) * ( 1 - (hamiltonian_l2_norm / sum_of_coeffs) ** 2 @@ -126,7 +126,7 @@ def walk_operator_for_pauli_hamiltonian( References: [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). - Babbush et. al. (2018). Eq (A9). + Babbush et al. (2018). Eq (A9). """ q = sorted(ham.qubits) ham_dps = [ps.dense(q) for ps in ham] diff --git a/qualtran/bloqs/chemistry/pbc/first_quantization/prepare.py b/qualtran/bloqs/chemistry/pbc/first_quantization/prepare.py index d85665de0..aba6fd476 100644 --- a/qualtran/bloqs/chemistry/pbc/first_quantization/prepare.py +++ b/qualtran/bloqs/chemistry/pbc/first_quantization/prepare.py @@ -42,6 +42,7 @@ class UniformSuperpostionIJFirstQuantization(Bloq): [Fault-Tolerant Quantum Simulations of Chemistry in First Quantization](https://arxiv.org/abs/2105.12767). page 18, section A, around Eq 62. """ + eta: int num_bits_rot_aa: int diff --git a/qualtran/bloqs/chemistry/pbc/first_quantization/prepare_nu.py b/qualtran/bloqs/chemistry/pbc/first_quantization/prepare_nu.py index 74998758e..ae1e4e6bc 100644 --- a/qualtran/bloqs/chemistry/pbc/first_quantization/prepare_nu.py +++ b/qualtran/bloqs/chemistry/pbc/first_quantization/prepare_nu.py @@ -50,6 +50,7 @@ class PrepareMuUnaryEncodedOneHot(Bloq): [Fault-Tolerant Quantum Simulations of Chemistry in First Quantization](https://arxiv.org/abs/2105.12767) page 21, Eq 77. """ + num_bits_p: int @cached_property @@ -93,6 +94,7 @@ class PrepareNuSuperPositionState(Bloq): [Fault-Tolerant Quantum Simulations of Chemistry in First Quantization](https://arxiv.org/abs/2105.12767) page 21, Eq 78. """ + num_bits_p: int is_adjoint: bool = False @@ -135,6 +137,7 @@ class FlagZeroAsFailure(Bloq): [Fault-Tolerant Quantum Simulations of Chemistry in First Quantization](https://arxiv.org/abs/2105.12767) page 21, Eq 80. """ + num_bits_p: int is_adjoint: bool = False @@ -184,6 +187,7 @@ class TestNuLessThanMu(Bloq): [Fault-Tolerant Quantum Simulations of Chemistry in First Quantization](https://arxiv.org/abs/2105.12767) page 21, Eq 80. """ + num_bits_p: int is_adjoint: bool = False @@ -250,6 +254,7 @@ class TestNuInequality(Bloq): [Fault-Tolerant Quantum Simulations of Chemistry in First Quantization](https://arxiv.org/abs/2105.12767) page 21, Eq 80. """ + num_bits_p: int num_bits_m: int is_adjoint: bool = False @@ -345,6 +350,7 @@ class PrepareNuState(Bloq): [Fault-Tolerant Quantum Simulations of Chemistry in First Quantization](https://arxiv.org/abs/2105.12767) page 19, section B """ + num_bits_p: int m_param: int diff --git a/qualtran/bloqs/chemistry/pbc/first_quantization/prepare_t.py b/qualtran/bloqs/chemistry/pbc/first_quantization/prepare_t.py index 26e9f24f1..cb69fc70b 100644 --- a/qualtran/bloqs/chemistry/pbc/first_quantization/prepare_t.py +++ b/qualtran/bloqs/chemistry/pbc/first_quantization/prepare_t.py @@ -50,6 +50,7 @@ class PreparePowerTwoState(Bloq): [Fault-Tolerant Quantum Simulations of Chemistry in First Quantization](https://arxiv.org/abs/2105.12767). Eq 67-69, pg 19-20 """ + bitsize: int @cached_property diff --git a/qualtran/bloqs/chemistry/pbc/first_quantization/prepare_uv.py b/qualtran/bloqs/chemistry/pbc/first_quantization/prepare_uv.py index de5273458..2c5e79f0c 100644 --- a/qualtran/bloqs/chemistry/pbc/first_quantization/prepare_uv.py +++ b/qualtran/bloqs/chemistry/pbc/first_quantization/prepare_uv.py @@ -59,6 +59,7 @@ class PrepareUVFirstQuantization(Bloq): [Fault-Tolerant Quantum Simulations of Chemistry in First Quantization](https://arxiv.org/abs/2105.12767) page 19, section B """ + num_bits_p: int eta: int num_atoms: int diff --git a/qualtran/bloqs/chemistry/pbc/first_quantization/prepare_zeta.py b/qualtran/bloqs/chemistry/pbc/first_quantization/prepare_zeta.py index 48df42a2f..de328009e 100644 --- a/qualtran/bloqs/chemistry/pbc/first_quantization/prepare_zeta.py +++ b/qualtran/bloqs/chemistry/pbc/first_quantization/prepare_zeta.py @@ -44,6 +44,7 @@ class PrepareZetaState(Bloq): [Fault-Tolerant Quantum Simulations of Chemistry in First Quantization](https://arxiv.org/abs/2105.12767) page 23-24, last 3 paragraphs. """ + num_atoms: int lambda_zeta: int num_bits_nuc_pos: int diff --git a/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/prepare_nu.py b/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/prepare_nu.py index 4d06eab32..682ab3c65 100644 --- a/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/prepare_nu.py +++ b/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/prepare_nu.py @@ -60,6 +60,7 @@ class PrepareMuUnaryEncodedOneHotWithProj(Bloq): [Fault-Tolerant Quantum Simulations of Chemistry in First Quantization](https://arxiv.org/abs/2105.12767) page 21, Eq 77. """ + bitsize_n: int bitsize_p: int is_adjoint: bool = False @@ -115,6 +116,7 @@ class PrepareNuStateWithProj(Bloq): [Fault-Tolerant Quantum Simulations of Chemistry in First Quantization](https://arxiv.org/abs/2105.12767) page 19, section B """ + num_bits_p: int num_bits_n: int m_param: int diff --git a/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/prepare_nu_test.py b/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/prepare_nu_test.py index c18e58d04..020c80caa 100644 --- a/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/prepare_nu_test.py +++ b/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/prepare_nu_test.py @@ -39,9 +39,7 @@ def test_prepare_nu_with_proj_t_counts(): expected_cost += ( 2 * 4 * (num_bits_n - 1) + (num_bits_n - num_bits_p - 1) + 6 * num_bits_n + 2 + 2 ) - eq_c6 = ( - 3 * num_bits_n**2 + 16 * num_bits_n - num_bits_p - 6 + 4 * num_bits_m * (num_bits_n + 1) - ) + eq_c6 = 3 * num_bits_n**2 + 16 * num_bits_n - num_bits_p - 6 + 4 * num_bits_m * (num_bits_n + 1) assert expected_cost == eq_c6 + 5 prep = PrepareNuStateWithProj(num_bits_p, num_bits_n, m_param) qual_cost = get_cost_value(prep, QECGatesCost()).total_t_count() diff --git a/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/prepare_t.py b/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/prepare_t.py index 2603b51c7..211c6cc15 100644 --- a/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/prepare_t.py +++ b/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/prepare_t.py @@ -53,6 +53,7 @@ class PreparePowerTwoStateWithProj(Bloq): [Fault-Tolerant Quantum Simulations of Chemistry in First Quantization](https://arxiv.org/abs/2105.12767) page 19, section B """ + bitsize_n: int bitsize_p: int is_adjoint: bool = False @@ -109,10 +110,11 @@ class PrepareTFirstQuantizationWithProj(Bloq): s: a register encoding bits for each component of the momenta. References: - [Quantum computation of stopping power for inertial fusion target design]( - https://arxiv.org/abs/2308.12352) page 11, C3 also page 31 App A. Sec 2 b. - [Fault-Tolerant Quantum Simulations of Chemistry in First Quantization]( - https://arxiv.org/abs/2105.12767) page 19, section B + [Quantum computation of stopping power for inertial fusion target design](https://arxiv.org/abs/2308.12352). + page 11, C3 also page 31 App A. Sec 2 b. + + [Fault-Tolerant Quantum Simulations of Chemistry in First Quantization](https://arxiv.org/abs/2105.12767). + page 19, section B """ num_bits_p: int diff --git a/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/prepare_uv.py b/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/prepare_uv.py index 6fd41776c..f66875a9c 100644 --- a/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/prepare_uv.py +++ b/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/prepare_uv.py @@ -54,6 +54,7 @@ class PrepareUVFirstQuantizationWithProj(Bloq): [Fault-Tolerant Quantum Simulations of Chemistry in First Quantization](https://arxiv.org/abs/2105.12767) page 19, section B """ + num_bits_p: int num_bits_n: int eta: int diff --git a/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/projectile.ipynb b/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/projectile.ipynb index b9a407ca6..3184e60fe 100644 --- a/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/projectile.ipynb +++ b/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/projectile.ipynb @@ -128,7 +128,7 @@ " - `flags`: A 4 qubit flag register indicating which component of the Hamiltonian to apply. \n", "\n", "#### References\n", - " - [Fault-Tolerant Quantum Simulations of Chemistry in First Quantization]( https://arxiv.org/abs/2105.12767)\n" + " - [Fault-Tolerant Quantum Simulations of Chemistry in First Quantization](https://arxiv.org/abs/2105.12767). \n" ] }, { @@ -260,7 +260,7 @@ " - `proj`: The system register. Will store a single register (x, y and z) components of size num_bits_n. \n", "\n", "#### References\n", - " - [Fault-Tolerant Quantum Simulations of Chemistry in First Quantization]( https://arxiv.org/abs/2105.12767)\n" + " - [Fault-Tolerant Quantum Simulations of Chemistry in First Quantization](https://arxiv.org/abs/2105.12767). \n" ] }, { diff --git a/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/select_and_prepare.py b/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/select_and_prepare.py index c41428f82..dde8e7eb6 100644 --- a/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/select_and_prepare.py +++ b/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/select_and_prepare.py @@ -83,6 +83,7 @@ class PrepareTUVSuperpositions(Bloq): [Fault-Tolerant Quantum Simulations of Chemistry in First Quantization](https://arxiv.org/abs/2105.12767) page 15, section A """ + num_bits_t: int eta: int lambda_zeta: int @@ -232,8 +233,7 @@ class PrepareFirstQuantizationWithProj(PrepareOracle): flags: A 4 qubit flag register indicating which component of the Hamiltonian to apply. References: - [Fault-Tolerant Quantum Simulations of Chemistry in First Quantization]( - https://arxiv.org/abs/2105.12767) + [Fault-Tolerant Quantum Simulations of Chemistry in First Quantization](https://arxiv.org/abs/2105.12767). """ num_bits_p: int @@ -408,8 +408,7 @@ class SelectFirstQuantizationWithProj(SelectOracle): components of size num_bits_n. References: - [Fault-Tolerant Quantum Simulations of Chemistry in First Quantization]( - https://arxiv.org/abs/2105.12767) + [Fault-Tolerant Quantum Simulations of Chemistry in First Quantization](https://arxiv.org/abs/2105.12767). """ num_bits_p: int diff --git a/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/select_t.py b/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/select_t.py index 4c96e0dd9..efa2e943f 100644 --- a/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/select_t.py +++ b/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/select_t.py @@ -52,6 +52,7 @@ class SelectTFirstQuantizationWithProj(Bloq): [Fault-Tolerant Quantum Simulations of Chemistry in First Quantization](https://arxiv.org/abs/2105.12767) page 20, section B """ + num_bits_n: int eta: int diff --git a/qualtran/bloqs/chemistry/pbc/first_quantization/select_and_prepare.py b/qualtran/bloqs/chemistry/pbc/first_quantization/select_and_prepare.py index ca95dc44d..5f5001c60 100644 --- a/qualtran/bloqs/chemistry/pbc/first_quantization/select_and_prepare.py +++ b/qualtran/bloqs/chemistry/pbc/first_quantization/select_and_prepare.py @@ -64,6 +64,7 @@ class PrepareTUVSuperpositions(Bloq): [Fault-Tolerant Quantum Simulations of Chemistry in First Quantization](https://arxiv.org/abs/2105.12767) page 15, section A """ + num_bits_t: int eta: int lambda_zeta: int @@ -102,6 +103,7 @@ class UniformSuperpostionIJFirstQuantization(Bloq): [Fault-Tolerant Quantum Simulations of Chemistry in First Quantization](https://arxiv.org/abs/2105.12767). page 18, section A, around Eq 62. """ + eta: int num_bits_rot_aa: int diff --git a/qualtran/bloqs/chemistry/pbc/first_quantization/select_t.py b/qualtran/bloqs/chemistry/pbc/first_quantization/select_t.py index 50999c43c..ca7938daa 100644 --- a/qualtran/bloqs/chemistry/pbc/first_quantization/select_t.py +++ b/qualtran/bloqs/chemistry/pbc/first_quantization/select_t.py @@ -42,6 +42,7 @@ class SelectTFirstQuantization(Bloq): [Fault-Tolerant Quantum Simulations of Chemistry in First Quantization](https://arxiv.org/abs/2105.12767) page 20, section B """ + num_bits_p: int eta: int diff --git a/qualtran/bloqs/chemistry/quad_fermion/givens_bloq.ipynb b/qualtran/bloqs/chemistry/quad_fermion/givens_bloq.ipynb index beaf8caba..c0f4c81e2 100644 --- a/qualtran/bloqs/chemistry/quad_fermion/givens_bloq.ipynb +++ b/qualtran/bloqs/chemistry/quad_fermion/givens_bloq.ipynb @@ -98,7 +98,7 @@ " - `phase_gradient`: QFxp data type representing the phase gradient register \n", "\n", "#### References\n", - " - [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization]( https://arxiv.org/abs/2007.07391). Section II-C: Oracles for phasing by cost function. Appendix A: Addition for controlled rotations\n" + " - [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization](https://arxiv.org/abs/2007.07391). Section II-C: Oracles for phasing by cost function. Appendix A: Addition for controlled rotations.\n" ] }, { diff --git a/qualtran/bloqs/chemistry/quad_fermion/givens_bloq.py b/qualtran/bloqs/chemistry/quad_fermion/givens_bloq.py index 3c2a2d24f..8546f518d 100644 --- a/qualtran/bloqs/chemistry/quad_fermion/givens_bloq.py +++ b/qualtran/bloqs/chemistry/quad_fermion/givens_bloq.py @@ -86,11 +86,11 @@ class RealGivensRotationByPhaseGradient(Bloq): phase_gradient: QFxp data type representing the phase gradient register References: - [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization]( - https://arxiv.org/abs/2007.07391). + [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization](https://arxiv.org/abs/2007.07391). Section II-C: Oracles for phasing by cost function. Appendix A: Addition for controlled - rotations + rotations. """ + phasegrad_bitsize: int @cached_property diff --git a/qualtran/bloqs/chemistry/resource_estimation.ipynb b/qualtran/bloqs/chemistry/resource_estimation.ipynb index adf7a2c20..9218086ee 100644 --- a/qualtran/bloqs/chemistry/resource_estimation.ipynb +++ b/qualtran/bloqs/chemistry/resource_estimation.ipynb @@ -84,7 +84,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "2fb17f7f", "metadata": {}, "outputs": [], @@ -113,42 +113,24 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "b9991178", "metadata": {}, "outputs": [], "source": [ "from qualtran.bloqs.block_encoding import LCUBlockEncoding\n", - "from qualtran.bloqs.multiplexers.black_box_select import BlackBoxSelect\n", - "from qualtran.bloqs.state_preparation.black_box_prepare import BlackBoxPrepare\n", - "\n", "epsilon = 1e-4 # choosing this arbitrarily at this point. See: https://github.com/quantumlib/Qualtran/issues/985\n", "block_encoding_bloq = LCUBlockEncoding(\n", - " select=BlackBoxSelect(sel_thc), prepare=BlackBoxPrepare(prep_thc)\n", + " select=sel_thc, prepare=prep_thc\n", ")" ] }, - { - "cell_type": "code", - "execution_count": null, - "id": "55ce02c5", - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "from qualtran.drawing.musical_score import get_musical_score_data, draw_musical_score\n", - "msd = get_musical_score_data(block_encoding_bloq.decompose_bloq())\n", - "fig, ax = draw_musical_score(msd)\n", - "plt.tick_params(left=False, right=False, labelleft=False, labelbottom=False, bottom=False)\n", - "fig.set_size_inches(8, 4)" - ] - }, { "cell_type": "markdown", "id": "deb5c0fa", "metadata": {}, "source": [ - "This looks like our math expression above. Now given our block encoding block we can determine the resources required as follows" + "Now given our block encoding block we can determine the resources required as follows" ] }, { @@ -158,35 +140,22 @@ "metadata": {}, "outputs": [], "source": [ + "from qualtran import Bloq\n", + "from qualtran.symbolics import SymbolicInt\n", + "from qualtran.bloqs import block_encoding\n", "from qualtran.resource_counting import get_bloq_call_graph\n", "import attrs\n", "from qualtran.bloqs.bookkeeping import Partition, Split, Join, Allocate, Free\n", "from qualtran.bloqs.basic_gates import CSwap, TGate\n", "from qualtran.drawing import show_call_graph\n", + "from qualtran.resource_counting import QECGatesCost, get_cost_value\n", + "from qualtran.resource_counting.generalizers import generalize_cswap_approx\n", "\n", - "def keeper(bloq):\n", - " # intercept CSwaps which are lumped in with Toffolis in the reference papers\n", - " if isinstance(bloq, CSwap):\n", - " return True\n", - " return False\n", - "\n", - "def generalizer(bloq):\n", - " if isinstance(bloq, (Partition, Split, Join, Allocate, Free)):\n", - " return None\n", - " return bloq\n", - "\n", - "\n", - "def get_toffoli_counts(bloq):\n", - " _, sigma = get_bloq_call_graph(bloq, generalizer=generalizer, keep=keeper)\n", - " toffolis = 0\n", - " for k, v in sigma.items():\n", - " if isinstance(k, CSwap):\n", - " toffolis += v * k.bitsize\n", - " elif isinstance(k, TGate):\n", - " toffolis += v // 4\n", - " return toffolis\n", + "def get_toffoli_counts(bloq: Bloq) -> SymbolicInt:\n", + " return get_cost_value(bloq, QECGatesCost(), generalizer=generalize_cswap_approx).total_t_and_ccz_count(ts_per_rotation=0)['n_ccz']\n", "\n", "num_toff = get_toffoli_counts(block_encoding_bloq)\n", + "print(num_toff)\n", "# note the cost here is from openfermion, the reference number excludes the reflection\n", "print(f'qualtran = {num_toff} vs. ref = 10880, delta = {num_toff - 10880}')" ] @@ -219,6 +188,7 @@ "metadata": {}, "outputs": [], "source": [ + "import matplotlib.pyplot as plt\n", "plt.pie(toffoli_counts, labels=['SELECT', 'PREPARE', r'PREPARE$^{\\dagger}$'], autopct='%1.1f%%')" ] }, @@ -227,34 +197,12 @@ "id": "94fe7d78", "metadata": {}, "source": [ - "We see SELECT is the dominant cost and the inverse state preparation is significantly more expensive than its inverse. Let's have a look at the circuits to see where the costs are coming from." + "We see SELECT is the dominant cost and that state preparation is significantly more expensive than its inverse. Let's look at a breakdown of the costs" ] }, { "cell_type": "code", - "execution_count": null, - "id": "f671f121", - "metadata": {}, - "outputs": [], - "source": [ - "msd = get_musical_score_data(prep_thc.decompose_bloq())\n", - "fig, ax = draw_musical_score(msd)\n", - "fig.set_size_inches(12, 8)\n", - "ax.set_title('Prepare')\n", - "plt.tick_params(left=False, right=False, labelleft=False, labelbottom=False, bottom=False)" - ] - }, - { - "cell_type": "markdown", - "id": "cec920ff", - "metadata": {}, - "source": [ - "This figure should resemble Fig. 4 in the [THC paper](https://arxiv.org/abs/2011.03494). This circuit takes a familiar form, uniform state preparation followed by coherent alias sampling (QROM + swaps). Let's see a breakdown of these costs." - ] - }, - { - "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "5efd66ae", "metadata": {}, "outputs": [], @@ -306,6 +254,7 @@ "metadata": {}, "outputs": [], "source": [ + "from qualtran.drawing import get_musical_score_data, draw_musical_score\n", "msd = get_musical_score_data(sel_thc.decompose_bloq())\n", "fig, ax = draw_musical_score(msd)\n", "fig.set_size_inches(12, 8)\n", @@ -356,7 +305,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "id": "614bfb21", "metadata": {}, "outputs": [], @@ -414,7 +363,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "id": "349b70ab", "metadata": {}, "outputs": [], @@ -435,7 +384,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "id": "2ef506ab", "metadata": {}, "outputs": [], @@ -462,7 +411,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "id": "e34dcb1c", "metadata": {}, "outputs": [], @@ -471,6 +420,8 @@ " SelectFirstQuantization,\n", " PrepareFirstQuantization,\n", ")\n", + "from qualtran.bloqs.state_preparation.black_box_prepare import BlackBoxPrepare\n", + "from qualtran.bloqs.multiplexers.black_box_select import BlackBoxSelect\n", "\n", "# keep the electron count small to make the diagrams nicer\n", "rs = 3.0\n", @@ -547,15 +498,16 @@ "metadata": {}, "outputs": [], "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join, ignore_alloc_free\n", "# let's just get the names of the relevant bloqs in select first\n", "fig, ax = plt.subplots(nrows=1, ncols=5)\n", "eta_vals = [10, 20, 40, 60, 80]\n", "for ieta, eta in enumerate(eta_vals):\n", " sel_fq = SelectFirstQuantization(num_bits_p, eta, eta, eta)\n", - " bloq_counts = sel_fq.bloq_counts(generalizer=generalizer)\n", + " bloq_counts = sel_fq.bloq_counts(generalizer=[ignore_split_join, ignore_alloc_free])\n", " # dictionary returned does not preserve any order so sort by the pretty names of the bloqs\n", " sorted_bloqs = sorted(\n", - " [bloq for bloq in sel_fq.bloq_counts(generalizer=generalizer).keys()],\n", + " [bloq for bloq in sel_fq.bloq_counts(generalizer=[ignore_split_join, ignore_alloc_free]).keys()],\n", " key=lambda x: str(x),\n", " )\n", " keys = [str(b) for b in sorted_bloqs]\n", @@ -582,7 +534,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "qualtran", "language": "python", "name": "python3" }, diff --git a/qualtran/bloqs/chemistry/sparse/select_bloq.py b/qualtran/bloqs/chemistry/sparse/select_bloq.py index 06d9c42c1..1c7fd5595 100644 --- a/qualtran/bloqs/chemistry/sparse/select_bloq.py +++ b/qualtran/bloqs/chemistry/sparse/select_bloq.py @@ -16,11 +16,23 @@ from functools import cached_property from typing import Dict, Optional, Tuple, TYPE_CHECKING +import attrs import cirq from attrs import frozen -from qualtran import bloq_example, BloqBuilder, BloqDocSpec, BQUInt, QAny, QBit, Register, SoquetT -from qualtran._infra.single_qubit_controlled import SpecializedSingleQubitControlledExtension +from qualtran import ( + AddControlledT, + Bloq, + bloq_example, + BloqBuilder, + BloqDocSpec, + BQUInt, + CtrlSpec, + QAny, + QBit, + Register, + SoquetT, +) from qualtran.bloqs.basic_gates import SGate from qualtran.bloqs.multiplexers.select_base import SelectOracle from qualtran.bloqs.multiplexers.selected_majorana_fermion import SelectedMajoranaFermion @@ -30,7 +42,7 @@ @frozen -class SelectSparse(SpecializedSingleQubitControlledExtension, SelectOracle): # type: ignore[misc] +class SelectSparse(SelectOracle): r"""SELECT oracle for the sparse Hamiltonian. Implements the two applications of Fig. 13. @@ -157,6 +169,19 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': c_maj_y = SelectedMajoranaFermion(sel_pa, target_gate=cirq.Y) return {SGate(): 1, maj_x: 1, c_maj_x: 1, maj_y: 1, c_maj_y: 1} + def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> Tuple['Bloq', 'AddControlledT']: + from qualtran.bloqs.mcmt.specialized_ctrl import get_ctrl_system_1bit_cv + + return get_ctrl_system_1bit_cv( + self, + ctrl_spec=ctrl_spec, + current_ctrl_bit=self.control_val, + get_ctrl_bloq_and_ctrl_reg_name=lambda cv: ( + attrs.evolve(self, control_val=cv), + 'control', + ), + ) + @bloq_example def _sel_sparse() -> SelectSparse: diff --git a/qualtran/bloqs/chemistry/sparse/sparse_test.py b/qualtran/bloqs/chemistry/sparse/sparse_test.py index bb7823e8f..4b0c04ec1 100644 --- a/qualtran/bloqs/chemistry/sparse/sparse_test.py +++ b/qualtran/bloqs/chemistry/sparse/sparse_test.py @@ -79,6 +79,9 @@ def test_sparse_costs_against_openfermion(num_spin_orb, num_bits_rot_aa): # Qualtran (constants are not ignored). The difference arises from # uncontrolled unary iteration used by QROM, which QROAMClean delegates to. delta_qrom = -2 + # The -4 comes from QROAMCleanAdjoint, which delegates to a QROM and SwapWithZero + # and each of them contributes a -2 factor. + delta_qrom_adjoint = -4 # inequality test difference # https://github.com/quantumlib/Qualtran/issues/235 lte = LessThanEqual(prep_sparse.num_bits_state_prep, prep_sparse.num_bits_state_prep) @@ -86,7 +89,9 @@ def test_sparse_costs_against_openfermion(num_spin_orb, num_bits_rot_aa): lte_cost_paper = prep_sparse.num_bits_state_prep # inverted at zero cost delta_ineq = lte_cost - lte_cost_paper swap_cost = 8 * (num_spin_orb // 2 - 1).bit_length() + 1 # inverted at zero cost - adjusted_cost_qualtran = cost - delta_qrom - delta_uni_prep - delta_ineq - swap_cost + adjusted_cost_qualtran = ( + cost - delta_qrom - delta_uni_prep - delta_ineq - swap_cost - delta_qrom_adjoint + ) cost_of = cost_sparse( num_spin_orb, unused_lambda, num_non_zero, unused_de, num_bits_state_prep, unused_stps )[0] diff --git a/qualtran/bloqs/chemistry/thc/prepare.py b/qualtran/bloqs/chemistry/thc/prepare.py index e07978aef..f93891e72 100644 --- a/qualtran/bloqs/chemistry/thc/prepare.py +++ b/qualtran/bloqs/chemistry/thc/prepare.py @@ -15,7 +15,6 @@ from functools import cached_property from typing import Dict, Optional, Tuple, TYPE_CHECKING -import cirq import numpy as np from attrs import field, frozen from numpy.typing import NDArray @@ -28,8 +27,8 @@ QAny, QBit, Register, + Side, Signature, - Soquet, SoquetT, ) from qualtran._infra.data_types import BQUInt @@ -40,17 +39,19 @@ LessThanEqual, ToContiguousIndex, ) -from qualtran.bloqs.basic_gates import CSwap, Hadamard, Ry, Toffoli, XGate +from qualtran.bloqs.basic_gates import CSwap, CZ, Hadamard, Ry, Toffoli, XGate from qualtran.bloqs.basic_gates.on_each import OnEach -from qualtran.bloqs.data_loading.select_swap_qrom import SelectSwapQROM +from qualtran.bloqs.data_loading.qroam_clean import ( + get_optimal_log_block_size_clean_ancilla, + QROAMClean, +) from qualtran.bloqs.mcmt import MultiControlX from qualtran.bloqs.reflections.reflection_using_prepare import ReflectionUsingPrepare from qualtran.bloqs.state_preparation.prepare_base import PrepareOracle -from qualtran.cirq_interop import CirqGateAsBloq from qualtran.drawing import Text, WireSymbol from qualtran.linalg.lcu_util import preprocess_probabilities_for_reversible_sampling from qualtran.resource_counting.generalizers import ignore_cliffords, ignore_split_join -from qualtran.symbolics import SymbolicFloat +from qualtran.symbolics import SymbolicFloat, SymbolicInt if TYPE_CHECKING: from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator @@ -153,7 +154,6 @@ def build_composite_bloq( # 7. Control off of 5 and 6 to not prepare if these conditions are met (nu_eq_mp1, gt_mu_n), junk = bb.add(Toffoli(), ctrl=[nu_eq_mp1, gt_mu_n], target=junk) # 6. Reflect on comparitors, rotated qubit and |+>. - # ctrls = bb.join(np.array([rot, lte_nu_mp1, lte_mu_nu])) rot, lte_nu_mp1, lte_mu_nu, junk = bb.add( ReflectionUsingPrepare.reflection_around_zero(bitsizes=(1, 1, 1, 1), global_phase=1), reg0_=rot, @@ -161,7 +161,6 @@ def build_composite_bloq( reg2_=lte_mu_nu, reg3_=junk, ) - # (rot, lte_nu_mp1, lte_mu_nu) = bb.split(ctrls) # We now undo comparitors and rotations and repeat the steps nu, lte_nu_mp1 = bb.add(lt_gate, x=nu, target=lte_nu_mp1) mu, nu, lte_mu_nu = bb.add(lte_gate, x=mu, y=nu, target=lte_mu_nu) @@ -183,7 +182,6 @@ def build_composite_bloq( reg1_=nu, reg2_=rot, ) - # amp = trg[0] mu = bb.add(OnEach(num_bits_mu, Hadamard()), q=mu) nu = bb.add(OnEach(num_bits_mu, Hadamard()), q=nu) nu, lte_nu_mp1 = bb.add(lt_gate, x=nu, target=lte_nu_mp1) @@ -269,6 +267,7 @@ class PrepareTHC(PrepareOracle): keep: Tuple[int, ...] = field(repr=False) keep_bitsize: int sum_of_l1_coeffs: SymbolicFloat + log_block_size: SymbolicInt = 0 @classmethod def from_hamiltonian_coeffs( @@ -277,6 +276,7 @@ def from_hamiltonian_coeffs( eta: NDArray[np.float64], zeta: NDArray[np.float64], num_bits_state_prep: int = 8, + log_block_size: Optional[SymbolicInt] = None, ) -> 'PrepareTHC': """Factory method to build PrepareTHC from Hamiltonian coefficients. @@ -285,6 +285,7 @@ def from_hamiltonian_coeffs( eta: The THC leaf tensors. zeta: THC central tensor. num_bits_state_prep: The number of bits for the state prepared during alias sampling. + log_block_size: (log) Block size for qroam. Returns: Constructed PrepareTHC object. @@ -324,6 +325,11 @@ def from_hamiltonian_coeffs( zeta_normalized = norm_fac.dot(zeta).dot(norm_fac) # Eq. 11 & 12 lambda_t = np.sum(np.abs(t_l)) # Eq. 19 lambda_z = 0.5 * np.sum(np.abs(zeta_normalized)) # Eq. 20 + if log_block_size is None: + target_bitsizes = (1, 1, num_mu.bit_length(), num_mu.bit_length(), mu) + log_block_size = get_optimal_log_block_size_clean_ancilla( + len(alt_mu), sum(target_bitsizes) + ) return PrepareTHC( num_mu, 2 * num_spat, @@ -334,6 +340,7 @@ def from_hamiltonian_coeffs( keep=tuple(keep), keep_bitsize=mu, sum_of_l1_coeffs=lambda_t + lambda_z, + log_block_size=log_block_size, ) @property @@ -361,106 +368,120 @@ def selection_registers(self) -> Tuple[Register, ...]: @cached_property def junk_registers(self) -> Tuple[Register, ...]: data_size = self.num_spin_orb // 2 + self.num_mu * (self.num_mu + 1) // 2 - log_mu = self.num_mu.bit_length() - return ( - Register('theta', QBit()), + junk = ( Register('s', QAny(bitsize=(data_size - 1).bit_length())), - Register('alt_mn', QAny(bitsize=log_mu), shape=(2,)), - Register('alt_theta', QBit()), - Register('keep', QAny(bitsize=self.keep_bitsize)), Register('less_than', QBit()), Register('extra_ctrl', QBit()), ) + return junk + self.qroam_target_registers + self.qroam_extra_target_registers - def build_composite_bloq( - self, - bb: 'BloqBuilder', - mu: SoquetT, - nu: SoquetT, - plus_mn: SoquetT, - plus_a: SoquetT, - plus_b: SoquetT, - sigma: SoquetT, - rot: SoquetT, - succ: SoquetT, - nu_eq_mp1: SoquetT, - theta: SoquetT, - s: SoquetT, - alt_mn: NDArray[Soquet], # type: ignore[type-var] - alt_theta: SoquetT, - keep: SoquetT, - less_than: SoquetT, - extra_ctrl: SoquetT, - ) -> Dict[str, 'SoquetT']: + @cached_property + def qroam_target_registers(self) -> Tuple[Register, ...]: + """Target registers for QROAMClean.""" + return ( + Register('theta', QBit(), side=Side.RIGHT), + Register('alt_theta', QBit(), side=Side.RIGHT), + Register('alt_mu', QAny(bitsize=self.num_mu.bit_length()), side=Side.RIGHT), + Register('alt_nu', QAny(bitsize=self.num_mu.bit_length()), side=Side.RIGHT), + Register('keep', QAny(bitsize=self.keep_bitsize), side=Side.RIGHT), + ) + + @cached_property + def qroam_extra_target_registers(self) -> Tuple[Register, ...]: + """Extra registers required for QROAMClean.""" + return tuple( + Register( + name=f'junk_{reg.name}', + dtype=reg.dtype, + shape=reg.shape + (2**self.log_block_size - 1,), + side=Side.RIGHT, + ) + for reg in self.qroam_target_registers + ) + + def build_qrom_bloq(self) -> 'Bloq': + log_mu = self.num_mu.bit_length() + qroam = QROAMClean.build_from_data( + self.theta, + self.alt_theta, + self.alt_mu, + self.alt_nu, + self.keep, + target_bitsizes=(1, 1, log_mu, log_mu, self.keep_bitsize), + log_block_sizes=(self.log_block_size,), + ) + return qroam + + def add_qrom(self, bb: 'BloqBuilder', **soqs: 'SoquetT') -> Dict[str, 'SoquetT']: + qrom = self.build_qrom_bloq() + # The qroam_junk_regs won't be present initially when building the + # composite bloq as they're RIGHT registers. + qroam_out_soqs = bb.add_d(qrom, selection=soqs['s']) + out_soqs: Dict[str, 'SoquetT'] = {'s': qroam_out_soqs.pop('selection')} + # map output soqs to Prepare junk registers names + out_soqs |= { + reg.name: qroam_out_soqs.pop(f'target{i}_') + for (i, reg) in enumerate(self.qroam_target_registers) + } + out_soqs |= { + reg.name: qroam_out_soqs.pop(f'junk_target{i}_') + for (i, reg) in enumerate(self.qroam_extra_target_registers) + } + return soqs | out_soqs + + def build_composite_bloq(self, bb: 'BloqBuilder', **soqs: 'SoquetT') -> Dict[str, 'SoquetT']: # 1. Prepare THC uniform superposition over mu, nu. succ flags success. - mu, nu, succ, nu_eq_mp1, rot = bb.add( + soqs['mu'], soqs['nu'], soqs['succ'], soqs['nu_eq_mp1'], soqs['rot'] = bb.add( UniformSuperpositionTHC(num_mu=self.num_mu, num_spin_orb=self.num_spin_orb), - mu=mu, - nu=nu, - succ=succ, - nu_eq_mp1=nu_eq_mp1, - rot=rot, + mu=soqs['mu'], + nu=soqs['nu'], + succ=soqs['succ'], + nu_eq_mp1=soqs['nu_eq_mp1'], + rot=soqs['rot'], ) data_size = self.num_spin_orb // 2 + self.num_mu * (self.num_mu + 1) // 2 log_mu = self.num_mu.bit_length() log_d = (data_size - 1).bit_length() # 2. Make contiguous register from mu and nu and store in register `s`. - mu, nu, s = bb.add(ToContiguousIndex(log_mu, log_d), mu=mu, nu=nu, s=s) + soqs['mu'], soqs['nu'], soqs['s'] = bb.add( + ToContiguousIndex(log_mu, log_d), mu=soqs['mu'], nu=soqs['nu'], s=soqs['s'] + ) # 3. Load alt / keep values - qroam = SelectSwapQROM.build_from_data( - *(self.theta, self.alt_theta, self.alt_mu, self.alt_nu, self.keep), - target_bitsizes=(1, 1, log_mu, log_mu, self.keep_bitsize), - use_dirty_ancilla=False, + soqs |= self.add_qrom(bb, **soqs) + soqs['sigma'] = bb.add(OnEach(self.keep_bitsize, Hadamard()), q=soqs['sigma']) + lte_gate = LessThanEqual(self.keep_bitsize, self.keep_bitsize) + soqs['keep'], soqs['sigma'], soqs['less_than'] = bb.add( + lte_gate, x=soqs['keep'], y=soqs['sigma'], target=soqs['less_than'] ) - alt_mu, alt_nu = alt_mn - s, theta, alt_theta, alt_mu, alt_nu, keep = bb.add( - qroam, - selection=s, - target0_=theta, - target1_=alt_theta, - target2_=alt_mu, - target3_=alt_nu, - target4_=keep, + soqs['alt_theta'], soqs['less_than'] = bb.add( + CZ(), q1=soqs['alt_theta'], q2=soqs['less_than'] + ) + # off-control + soqs['less_than'] = bb.add(XGate(), q=soqs['less_than']) + soqs['less_than'], soqs['theta'] = bb.add(CZ(), q1=soqs['less_than'], q2=soqs['theta']) + soqs['less_than'] = bb.add(XGate(), q=soqs['less_than']) + soqs['less_than'], soqs['alt_mu'], soqs['mu'] = bb.add( + CSwap(bitsize=log_mu), ctrl=soqs['less_than'], x=soqs['alt_mu'], y=soqs['mu'] + ) + soqs['less_than'], soqs['alt_nu'], soqs['nu'] = bb.add( + CSwap(bitsize=log_mu), ctrl=soqs['less_than'], x=soqs['alt_nu'], y=soqs['nu'] + ) + soqs['keep'], soqs['sigma'], soqs['less_than'] = bb.add( + lte_gate, x=soqs['keep'], y=soqs['sigma'], target=soqs['less_than'] ) - sigma = bb.add(OnEach(self.keep_bitsize, Hadamard()), q=sigma) - lte_gate = LessThanEqual(self.keep_bitsize, self.keep_bitsize) - keep, sigma, less_than = bb.add(lte_gate, x=keep, y=sigma, target=less_than) - cz = CirqGateAsBloq(cirq.ControlledGate(cirq.Z)) - alt_theta, less_than = bb.add(cz, q=[alt_theta, less_than]) - cz = CirqGateAsBloq(cirq.ControlledGate(cirq.Z, control_values=(0,))) - # negative control on the less_than register - less_than, theta = bb.add(cz, q=[less_than, theta]) - less_than, alt_mu, mu = bb.add(CSwap(bitsize=log_mu), ctrl=less_than, x=alt_mu, y=mu) - less_than, alt_nu, nu = bb.add(CSwap(bitsize=log_mu), ctrl=less_than, x=alt_nu, y=nu) - keep, sigma, less_than = bb.add(lte_gate, x=keep, y=sigma, target=less_than) - # delete the QROM # Select expects three plus states so set them up here. - plus_a = bb.add(Hadamard(), q=plus_a) - plus_b = bb.add(Hadamard(), q=plus_b) - plus_mn = bb.add(Hadamard(), q=plus_mn) - (nu_eq_mp1, plus_a), extra_ctrl = bb.add( - MultiControlX(cvs=(0, 1)), controls=np.array([nu_eq_mp1, plus_a]), target=extra_ctrl + soqs['plus_a'] = bb.add(Hadamard(), q=soqs['plus_a']) + soqs['plus_b'] = bb.add(Hadamard(), q=soqs['plus_b']) + soqs['plus_mn'] = bb.add(Hadamard(), q=soqs['plus_mn']) + (soqs['nu_eq_mp1'], soqs['plus_a']), soqs['extra_ctrl'] = bb.add( + MultiControlX(cvs=(0, 1)), + controls=np.array([soqs['nu_eq_mp1'], soqs['plus_a']]), + target=soqs['extra_ctrl'], ) - extra_ctrl, mu, nu = bb.add(CSwap(bitsize=log_mu), ctrl=extra_ctrl, x=mu, y=nu) - out_regs = { - 'mu': mu, - 'nu': nu, - 'plus_mn': plus_mn, - 'plus_a': plus_a, - 'plus_b': plus_b, - 'sigma': sigma, - 'rot': rot, - 'succ': succ, - 'nu_eq_mp1': nu_eq_mp1, - 'theta': theta, - 's': s, - 'alt_mn': [alt_mu, alt_nu], - 'alt_theta': alt_theta, - 'keep': keep, - 'less_than': less_than, - 'extra_ctrl': extra_ctrl, - } - return out_regs + soqs['extra_ctrl'], soqs['mu'], soqs['nu'] = bb.add( + CSwap(bitsize=log_mu), ctrl=soqs['extra_ctrl'], x=soqs['mu'], y=soqs['nu'] + ) + return soqs def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': cost_1 = (UniformSuperpositionTHC(self.num_mu, self.num_spin_orb), 1) @@ -468,17 +489,18 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': data_size = self.num_spin_orb // 2 + self.num_mu * (self.num_mu + 1) // 2 nd = (data_size - 1).bit_length() cost_2 = (ToContiguousIndex(nmu, nd), 1) - qroam = SelectSwapQROM.build_from_data( - *(self.theta, self.alt_theta, self.alt_mu, self.alt_nu, self.keep), - target_bitsizes=(1, 1, nmu, nmu, self.keep_bitsize), - use_dirty_ancilla=False, - ) + qroam = self.build_qrom_bloq() cost_3 = (qroam, 1) cost_4 = (OnEach(self.keep_bitsize, Hadamard()), 1) cost_5 = (LessThanEqual(self.keep_bitsize, self.keep_bitsize), 2) cost_6 = (CSwap(nmu), 3) - cost_7 = (Toffoli(), 1) - return dict([cost_1, cost_2, cost_3, cost_4, cost_5, cost_6, cost_7]) + cost_7 = (MultiControlX(cvs=(0, 1)), 1) + cost_8 = (XGate(), 2) + cost_9 = (CZ(), 2) + cost_10 = (Hadamard(), 3) + return dict( + [cost_1, cost_2, cost_3, cost_4, cost_5, cost_6, cost_7, cost_8, cost_9, cost_10] + ) @bloq_example @@ -496,7 +518,9 @@ def _thc_prep() -> PrepareTHC: num_spat = 4 num_mu = 8 t_l, eta, zeta = build_random_test_integrals(num_mu, num_spat, seed=7) - thc_prep = PrepareTHC.from_hamiltonian_coeffs(t_l, eta, zeta, num_bits_state_prep=8) + thc_prep = PrepareTHC.from_hamiltonian_coeffs( + t_l, eta, zeta, num_bits_state_prep=8, log_block_size=2 + ) return thc_prep diff --git a/qualtran/bloqs/chemistry/thc/prepare_test.py b/qualtran/bloqs/chemistry/thc/prepare_test.py index f50f8c18d..8f3da8352 100644 --- a/qualtran/bloqs/chemistry/thc/prepare_test.py +++ b/qualtran/bloqs/chemistry/thc/prepare_test.py @@ -27,7 +27,9 @@ ) from qualtran.drawing.musical_score import get_musical_score_data, MusicalScoreData from qualtran.linalg.lcu_util import preprocess_probabilities_for_reversible_sampling +from qualtran.resource_counting import get_cost_value, QECGatesCost from qualtran.resource_counting.classify_bloqs import classify_t_count_by_bloq_type +from qualtran.resource_counting.generalizers import generalize_cswap_approx, ignore_split_join from qualtran.testing import execute_notebook @@ -112,11 +114,17 @@ def test_prepare_qrom_counts(): t_l, eta, zeta = build_random_test_integrals(num_mu, num_spat, seed=7) thc_prep = PrepareTHC.from_hamiltonian_coeffs(t_l, eta, zeta, num_bits_state_prep=8) binned_counts = classify_t_count_by_bloq_type(thc_prep) - assert binned_counts['data_loading'] == 304, binned_counts['data_loading'] - t_l, eta, zeta = build_random_test_integrals(num_mu, num_spat, seed=23) - thc_prep = PrepareTHC.from_hamiltonian_coeffs(t_l, eta, zeta, num_bits_state_prep=8) - binned_counts = classify_t_count_by_bloq_type(thc_prep) - assert binned_counts['data_loading'] == 296, binned_counts['data_loading'] + qroam = thc_prep.build_qrom_bloq() + + counts = get_cost_value( + qroam, QECGatesCost(), generalizer=generalize_cswap_approx + ).total_t_and_ccz_count() + assert binned_counts['data_loading'] == counts['n_ccz'] * 4, binned_counts['data_loading'] + + +def test_equivalent_bloq_counts(): + prepare = _thc_prep.make() + qlt_testing.assert_equivalent_bloq_counts(prepare, ignore_split_join) def test_musical_score(): diff --git a/qualtran/bloqs/chemistry/thc/select_bloq.py b/qualtran/bloqs/chemistry/thc/select_bloq.py index 929411e4d..a3b825950 100644 --- a/qualtran/bloqs/chemistry/thc/select_bloq.py +++ b/qualtran/bloqs/chemistry/thc/select_bloq.py @@ -20,18 +20,19 @@ from attrs import evolve, frozen from qualtran import ( + AddControlledT, Bloq, bloq_example, BloqBuilder, BloqDocSpec, BQUInt, + CtrlSpec, QAny, QBit, Register, Signature, SoquetT, ) -from qualtran._infra.single_qubit_controlled import SpecializedSingleQubitControlledExtension from qualtran.bloqs.basic_gates import CSwap, Toffoli, XGate from qualtran.bloqs.chemistry.black_boxes import ApplyControlledZs from qualtran.bloqs.multiplexers.select_base import SelectOracle @@ -66,7 +67,7 @@ class THCRotations(Bloq): [Even more efficient quantum computations of chemistry through tensor hypercontraction](https://arxiv.org/pdf/2011.03494.pdf) Fig. 7. [Quantum computing enhanced computational catalysis](https://arxiv.org/abs/2007.14460). - Burg, Low et. al. 2021. Eq. 73 + Burg, Low, et al. 2021. Eq. 73 """ num_mu: int @@ -120,7 +121,7 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': @frozen -class SelectTHC(SpecializedSingleQubitControlledExtension, SelectOracle): # type: ignore[misc] +class SelectTHC(SelectOracle): r"""SELECT for THC Hamiltonian. Args: @@ -313,6 +314,16 @@ def build_composite_bloq(self, bb: 'BloqBuilder', **soqs: 'SoquetT') -> Dict[str return out_soqs + def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> Tuple['Bloq', 'AddControlledT']: + from qualtran.bloqs.mcmt.specialized_ctrl import get_ctrl_system_1bit_cv + + return get_ctrl_system_1bit_cv( + self, + ctrl_spec=ctrl_spec, + current_ctrl_bit=self.control_val, + get_ctrl_bloq_and_ctrl_reg_name=lambda cv: (evolve(self, control_val=cv), 'control'), + ) + @bloq_example def _thc_sel() -> SelectTHC: diff --git a/qualtran/bloqs/chemistry/thc/thc.ipynb b/qualtran/bloqs/chemistry/thc/thc.ipynb index e5a8b65da..17e405ec4 100644 --- a/qualtran/bloqs/chemistry/thc/thc.ipynb +++ b/qualtran/bloqs/chemistry/thc/thc.ipynb @@ -327,7 +327,9 @@ "num_spat = 4\n", "num_mu = 8\n", "t_l, eta, zeta = build_random_test_integrals(num_mu, num_spat, seed=7)\n", - "thc_prep = PrepareTHC.from_hamiltonian_coeffs(t_l, eta, zeta, num_bits_state_prep=8)" + "thc_prep = PrepareTHC.from_hamiltonian_coeffs(\n", + " t_l, eta, zeta, num_bits_state_prep=8, log_block_size=2\n", + ")" ] }, { @@ -433,7 +435,7 @@ " print(f\"{k+':':20s} qualtran = {binned_counts[k]:5d} vs paper cost = {v:5d}.\")\n", "\n", "print(f\"Total cost = {sum(v for v in binned_counts.values())}\")\n", - "assert binned_counts['data_loading'] == 304" + "assert binned_counts['data_loading'] == 248" ] }, { @@ -441,7 +443,7 @@ "id": "807674de", "metadata": {}, "source": [ - "The main discrepancies arise from QROAM assumptions and the difference in comparator cost seen before. " + "The main discrepancies arise the differences in comparator cost seen before. The QROAM cost in qualtran is more accurate as it includes the constant factor of -2 Toffolis arising from uncontrolled QROM. " ] }, { @@ -618,7 +620,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.8" + "version": "3.11.9" } }, "nbformat": 4, diff --git a/qualtran/bloqs/chemistry/trotter/grid_ham/inverse_sqrt.py b/qualtran/bloqs/chemistry/trotter/grid_ham/inverse_sqrt.py index 1434f8760..d9beebf87 100644 --- a/qualtran/bloqs/chemistry/trotter/grid_ham/inverse_sqrt.py +++ b/qualtran/bloqs/chemistry/trotter/grid_ham/inverse_sqrt.py @@ -200,8 +200,7 @@ class PolynmomialEvaluationInverseSquareRoot(Bloq): out: Output register to store polynomial approximation to inverse square root. References: - [Quantum computation of stopping power for inertial fusion target design]( - https://arxiv.org/pdf/2308.12352.pdf) + [Quantum computation of stopping power for inertial fusion target design](https://arxiv.org/abs/2308.12352). """ x_sq_bitsize: int diff --git a/qualtran/bloqs/chemistry/trotter/grid_ham/inverse_sqrt_test.py b/qualtran/bloqs/chemistry/trotter/grid_ham/inverse_sqrt_test.py index 4c984bc09..a1af126a1 100644 --- a/qualtran/bloqs/chemistry/trotter/grid_ham/inverse_sqrt_test.py +++ b/qualtran/bloqs/chemistry/trotter/grid_ham/inverse_sqrt_test.py @@ -81,15 +81,11 @@ def test_build_qrom_data(bitsize, poly_bitsize): a_bits = unique[::2] coeff_as_float = [fixed_point_to_float(c, poly_bitsize) for c in a_bits] for k in range(2, len(unique) // 2): - np.isclose( - coeff_as_float[k], poly_coeffs_a[c] / 2 ** (k / 2), atol=1 / 2**poly_bitsize - ) + np.isclose(coeff_as_float[k], poly_coeffs_a[c] / 2 ** (k / 2), atol=1 / 2**poly_bitsize) b_bits = unique[1::2] coeff_as_float = [fixed_point_to_float(c, poly_bitsize) for c in b_bits] for k in range(2, len(unique) // 2): - np.isclose( - coeff_as_float[k], poly_coeffs_b[c] / 2 ** (k / 2), atol=1 / 2**poly_bitsize - ) + np.isclose(coeff_as_float[k], poly_coeffs_b[c] / 2 ** (k / 2), atol=1 / 2**poly_bitsize) def multiply_fixed_point_float_by_int( diff --git a/qualtran/bloqs/chemistry/trotter/grid_ham/qvr.py b/qualtran/bloqs/chemistry/trotter/grid_ham/qvr.py index 99075b88a..0809fd98b 100644 --- a/qualtran/bloqs/chemistry/trotter/grid_ham/qvr.py +++ b/qualtran/bloqs/chemistry/trotter/grid_ham/qvr.py @@ -47,6 +47,7 @@ class QuantumVariableRotation(Bloq): computers](https://iopscience.iop.org/article/10.1088/1367-2630/14/11/115023/meta) Fig 14. """ + phi_bitsize: int @cached_property diff --git a/qualtran/bloqs/chemistry/trotter/grid_ham/trotter.ipynb b/qualtran/bloqs/chemistry/trotter/grid_ham/trotter.ipynb index 1a9d0dda8..27a61ad02 100644 --- a/qualtran/bloqs/chemistry/trotter/grid_ham/trotter.ipynb +++ b/qualtran/bloqs/chemistry/trotter/grid_ham/trotter.ipynb @@ -49,7 +49,7 @@ " - `out`: Output register to store polynomial approximation to inverse square root. \n", "\n", "#### References\n", - " - [Quantum computation of stopping power for inertial fusion target design]( https://arxiv.org/pdf/2308.12352.pdf)\n" + " - [Quantum computation of stopping power for inertial fusion target design](https://arxiv.org/abs/2308.12352). \n" ] }, { diff --git a/qualtran/bloqs/chemistry/trotter/grid_ham/trotter_costs.ipynb b/qualtran/bloqs/chemistry/trotter/grid_ham/trotter_costs.ipynb index d2c9938e5..2472a3888 100644 --- a/qualtran/bloqs/chemistry/trotter/grid_ham/trotter_costs.ipynb +++ b/qualtran/bloqs/chemistry/trotter/grid_ham/trotter_costs.ipynb @@ -57,9 +57,27 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "delta = 0.25\n", + "unscaled grid points = [-30 -29 -28 -27 -26 -25 -24 -23 -22 -21 -20 -19 -18 -17 -16 -15 -14 -13\n", + " -12 -11 -10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5\n", + " 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23\n", + " 24 25 26 27 28 29 30]\n", + "scaled grid points = [-7.5 -7.25 -7. -6.75 -6.5 -6.25 -6. -5.75 -5.5 -5.25 -5. -4.75\n", + " -4.5 -4.25 -4. -3.75 -3.5 -3.25 -3. -2.75 -2.5 -2.25 -2. -1.75\n", + " -1.5 -1.25 -1. -0.75 -0.5 -0.25 0. 0.25 0.5 0.75 1. 1.25\n", + " 1.5 1.75 2. 2.25 2.5 2.75 3. 3.25 3.5 3.75 4. 4.25\n", + " 4.5 4.75 5. 5.25 5.5 5.75 6. 6.25 6.5 6.75 7. 7.25\n", + " 7.5 ]\n" + ] + } + ], "source": [ "import numpy as np\n", "ng = 30\n", @@ -83,9 +101,30 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Coulomb potential on a grid.')" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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fYEj4wDs+h80+zPY/WJRm4iISEArdk9OWloao0aN4vnnn6dp06aEhoYyZMgQBZz8HEiC/w6H07p/lYiIiC8Uu0uhbt26NGrUiFmzZtGnTx/mzZvHbbfdxp49e3j44YdLssbA0KAbPH4AQstYXYmIiEhQKHbImTp1Krfffrv7ea9evViyZAk33ngju3bt4u233y6RAgNGiAOw6N5ZIiIiQajYw1XnB5xz2rRpw4oVK1i8ePFlFRWQck7DrCGwe4XVlYiIiASFIoecp59+mnXr1uX7ep06dVixQl/kF7E74PQxyD1jdSUiIiJBocjDVfv27aN3796EhYXRt29fbrrpJrp160ZYWJi7Tfny5Uu0yIBgs8GgL62uQkREJGgUuSdn6tSppKam8sknnxAVFcXo0aOpVKkSf/3rX/nwww85elSrh/JlmuDMsboKERGRoFAiN+j85Zdf+Prrr/nyyy9Zt24d7du356abbmLAgAFUr169JOoscZbcoPODmyAqFv4yxTfHExERCTBF+f4u8buQHzp0iK+++oqvvvqKzp07849//KMk377EWBJyts0/u4S8bmffHE9ERCTAWBpySgtLQo6IiIhclqJ8fxd7CXl+9u7dy1133VXSbxsYDm+HX762ugoREZGgUOIh5+jRo3zwwQcl/baBYds8mH2f1VWIiIgEhSIvIf/qq68KfH3Hjh3FLibgXTn07ENERES8rsghp1+/fhiGQUFTeQzDuKyiAlZYpNUViIiIBI0iD1fFxcXx3//+F5fLlecjMTHRG3UGhuN74LPBcOQ3qysREREJeEUOOW3bti3wtg6X6uUJaoYdzhyH3CyrKxEREQl4RR6uevjhh8nMzMz39QYNGrBkyZLLKipgxVTXrR1ERER8RNfJ8fV1ckwTTBfY7L47poiISICw9Do5cglvtoAfXra6ChERkYBX5OEquUzXPQUVG1hdhYiISMBTyPG1Fv2trkBERCQoXNZw1b59+3C5XBf9LAVI+Rl2LLW6ChERkYB3WSGnadOm7Nq166KfpQCJH8D3T1pdhYiISMC7rOGq8xdmBekiraLrNg5sGiUUERHxNn3b+lq4D5eri4iIBDEtIfe1/Ylnb+2Qnf8FFUVEROTy+UXISUhIoF27dkRFRVGlShX69evH1q1bL7nfrFmzaNy4MeHh4VxxxRV89913Pqi2+Jwuk417j3LwUBo//XoAp0tDfCIiIt7iFyHnhx9+YMSIEaxatYoFCxaQk5PD9ddfX+DtI1asWMGAAQMYNmwY69evp1+/fvTr14/k5GQfVl5485JTuHrCYvrOyaL93ge49aNtXD1hMfOSU6wuTUREJCBd1m0doqKi2LBhA/Xq1fP4+XIdOnSIKlWq8MMPP3DNNdfk2ea2224jMzOTb775xr2tY8eOtGrVismTJ1/yGL68rcO85BTu/ziR86ZpY2ByLmNOuqMNvZrHebUGERGRQFDqb+uQnp4OQIUKFfJts3LlSrp37+6xrWfPnqxcudKrtRWV02Uy/uvN7oDjIJutjsHcZFvh3jb+680auhIRESlhlxVyHn/8cXcQOf/ny+FyuRg9ejRXXXUVzZs3z7ddamoqVatW9dhWtWpVUlNT82yflZVFRkaGx8MX1uw8Skr6mT/qIIzncu9ko3m2x8sEUtLPsGbnUZ/UIyIiEiwuawn52LFj8/z5cowYMYLk5GSWL19eIu93TkJCAuPHjy/R9yyMgyfOXLTtY2ePQrUTERGR4vOr4aqRI0fyzTffsGTJEmrUqFFg29jYWNLS0jy2paWlERsbm2f7sWPHkp6e7n7s3bu3xOouSJWo8Iu2tTG20crYfsl2IiIiUnx+EXJM02TkyJHMnj2bxYsXU7du3UvuEx8fz6JFizy2LViwgPj4+DzbOxwOoqOjPR6+0L5uBeJiwjHO2zY65AvuCTk7YdoA4mLCaV/38of6RERE5A9+ccXjESNGMGPGDL788kuioqLc82piYmKIiIgAYNCgQVSvXp2EhAQARo0aRZcuXXj11Vfp06cPM2fOZO3atUyZMsWy88iL3WYwrm9T7v84EYOzc3AeyHmAM4S5g8+4vk2x24wC3kVERESK6rJ6cnJycti7dy9bt27l6NHiT5ydNGkS6enpdO3albi4OPfj008/dbfZs2cPKSl/XFOmU6dOzJgxgylTptCyZUs+//xz5syZU+BkZav0ah7HpDvaEBtzdkgqnbJkEUZsTLiWj4uIiHhJka+Tc+LECT7++GNmzpzJmjVryM7OxjRNDMOgRo0aXH/99dxzzz20a9fOWzWXCF9eJ+ccp8s8u4rq1/nUO7yESn+boh4cERGRIvDadXJee+016tSpw7Rp0+jevTtz5swhKSmJbdu2sXLlSsaNG0dubi7XX389vXr14tdff72sEwk0dptBfP2KxNcsQ1WOKeCIiIh4UZF6cgYMGMCTTz5Js2bNCmyXlZXFtGnTCAsL46677rrsIr3Bip4cERERuTxF+f6+rNs6lGaWh5xzH7uh3hwREZHC8sltHU6cOFHcXeXQVniuMuxba3UlIiIiAavYIadz58753kJBLiEqDnolQEzBFzwUERGR4it2yGndujUdOnRgy5YtHtuTkpK44YYbLruwgBYeDe3vhmgtHRcREfGWYoecadOmMWTIEK6++mqWL1/Otm3b6N+/P23btsVut5dkjYFpx1I4uOWSzURERKR4LuuKx+PHj8fhcNCjRw+cTifdunVj5cqVtG/fvqTqC1xfj4amN0MP3980VEREJBgUO+SkpaXx4osv8u6779K0aVO2bNnCkCFDFHAKa9gCcERZXYWIiEjAKvZwVd26dVm2bBmzZs1i3bp1fPHFF9xzzz288sorJVlf4CpbGUJ153ERERFvKXZPztSpU7n99tvdz3v16sWSJUu48cYb2bVrF2+//XaJFBiwts2HX76Cm/U5iYiIeEOxe3LODzjntGnThhUrVrB48eLLKioo5J6BzMN/XBRQRERESlSRQs6ePXsu2aZOnTqsWLECgP379xevqmDQ9Gb426e64rGIiIiXFCnktGvXjnvvvZeffvop3zbp6el8/vnnNG/enC+++OKyCwxoLhfkZlldhYiISEAq0pyczZs388ILL9CjRw/Cw8Np27Yt1apVIzw8nGPHjrF582Y2bdpEmzZtePnll3VRwIK4XPBSLej2NHS4x+pqREREAk6Rb9B57NgxwsPD+fbbb1m+fDm7d+/m9OnTVKpUidatW9OzZ0+aN2/urXpLjOU36ARI+gSqtYYqja05voiISCnj1buQV65cmeeee457770XoxTPJ/GLkCMiIiJF4tW7kI8ZM4ZHHnmE1q1b8+OPPxa7SAEObYNkzVsSERHxhiKHnLFjx7J161Zat27Ntddey4ABA7SKqri2L4Sv/q5l5CIiIl5QrOvkxMXFMW3aNFavXs2+ffto1KgRzz//PFlZWilUJFcOhUd3aRm5iIiIFxT7YoAAbdu25ccff+T999/n/fffp0mTJsyePbukagt8oRFgD7W6ChERkYB0WSHnnNtuu40tW7YwbNgwBg8eTI8ePUribQOfywUzB8KWb62uREREJOAU+95VANnZ2WzZsoXk5GT3IyIiQrd1KCyb7WxvjlEiWVNERETOU+SQM378eHeg+e2338jNzSUmJobmzZvTokULbrjhBlq0aOGNWgPTX9+zugIREZGAVOSQM2vWLK644goGDRrEFVdcQYsWLahVq5Y3agsOLhecPgqRlayuREREJKAUOeQkJyd7o47g9b/XYcVbZ1dZiYiISIm5rDk5UgKa3ARVmp29Vo6WkouIiJQYhRyrVWp49iEiIiIlSst6rOZywbrpkKphQBERkZKkkGM1mw0WPQu7V1hdiYiISEDRcJU/eGgb2PVPISIiUpLUk+MPFHBERERKnF+EnGXLltG3b1+qVauGYRjMmTOnwPZLly7FMIyLHqmpqb4puKT9/Bl89BerqxAREQkofhFyMjMzadmyJW+//XaR9tu6dSspKSnuR5UqVbxUoZeVqQAV6p5dRi4iIiIlwi/GSXr37k3v3r2LvF+VKlUoV65cyRfkaw26n32IiIhIifGLnpziatWqFXFxcfTo0YP//e9/BbbNysoiIyPD4+E3TBMyUuD0MasrERERCRilMuTExcUxefJkvvjiC7744gtq1qxJ165dSUxMzHefhIQEYmJi3I+aNWv6sOJLME14syVs+NTqSkRERAKGYZr+NRHEMAxmz55Nv379irRfly5dqFWrFh999FGer2dlZZGVleV+npGRQc2aNUlPTyc6OvpySi4ZO5ZCpT9BdDWrKxEREfFbGRkZxMTEFOr72y/m5JSE9u3bs3z58nxfdzgcOBwOH1ZURPW6Wl2BiIhIQCmVw1V5SUpKIi4uzuoyim/vT7BiotVViIiIBAy/6Mk5efIk27dvdz/fuXMnSUlJVKhQgVq1ajF27Fj279/Phx9+CMAbb7xB3bp1adasGWfOnOG9995j8eLFfP/991adwuU7sB7WToX4EbobuYiISAnwi5Czdu1arr32WvfzMWPGADB48GCmT59OSkoKe/bscb+enZ3NQw89xP79+ylTpgwtWrRg4cKFHu9R6nS45+xDRERESoTfTTz2laJMXBIRERH/UJTv74CZkxMQPv4rLHvF6ipEREQCgl8MV8nv6l0LFRtYXYWIiEhAUMjxJ51GWl2BiIhIwFDI8SPOnCx++Wkxe0JqUb5iLO3rVsBu00orERGR4lDI8RPzklN446tVzMsewpTskXzl6kRcTDjj+jalV/NSfP0fERERi2jisR+Yl5zC/R8nsiUjjO5ZL/OtqwMAqelnuP/jROYlp1hcoYiISOmjkGMxp8tk/NebObeOf7tZAyd2APe28V9vxukKypX+IiIixaaQY7E1O4+Skn7G/bylsZ1Joa8TQi5wNuikpJ9hzc6jFlUoIiJSOinkWOzgiTMXbSvLacqRecl2IiIikj9NPLZYlahwj+cbzAbcmfP4JduJiIhIwdSTY7H2dSsQFxOO50Jxk/JkAGAAcTHhtK9bwYLqRERESi+FHIvZbQbj+jYFcAedcSEf8knYC38879tU18sREREpIoUcP9CreRyT7mhDbMzZIanPnV14PvcOYmPCmXRHG10nR0REpBg0J8dP9GoeR4+msazZeZSDJ1pRJSpcVzwWERG5DAo5fsRuM4ivX/Hsk42fw84KUP86a4sSEREppTRc5a/WTYdt31tdhYiISKmlnhx/deccsOufR0REpLjUk+OvFHBEREQui0KOvzp9DN7uANvmW12JiIhIqaSQ46/Cy0H9blC2itWViIiIlEoaE/FXhgG9XrS6ChERkVJLPTn+LOfM2eGqrBNWVyIiIlLqKOT4s5OpMKM/7FpudSUiIiKljoar/Fn5OvBAIlSoZ3UlIiIipY5Cjr+rWN/qCkREREolDVf5u5MHYdoNsGe11ZWIiIiUKgo5/q5MRYiKA5vd6kpERERKFQ1X+TubHW553+oqRERESh315JQGLhfsW3d26EpEREQKRSGnNMg5BdN6wabZVlciIiJSami4qjRwlIV7lkKlRlZXIiIiUmoo5JQWVZvhdJms+e0IB0+coUpUOO3rVsBuM6yuTERExC/5xXDVsmXL6Nu3L9WqVcMwDObMmXPJfZYuXUqbNm1wOBw0aNCA6dOne71OK83/eTdLn7+Bqe9PZNTMJAa8u4qrJyxmXnKK1aWJiIj4Jb8IOZmZmbRs2ZK33367UO137txJnz59uPbaa0lKSmL06NEMHz6c+fPne7lSa8xLTuG+Gckcz7FhYLq3p6af4f6PExV0RERE8mCYpmleupnvGIbB7Nmz6devX75tHn30Ub799luSk5Pd226//XaOHz/OvHnzCnWcjIwMYmJiSE9PJzo6+nLL9hqny+TqCYtJST+T5+sGEBsTzvJHr9PQlYiIBLyifH/7RU9OUa1cuZLu3bt7bOvZsycrV67Md5+srCwyMjI8HqXBmp1HPQJOdQ5Ry0hzPzeBlPQzrNl51ILqRERE/FepDDmpqalUrVrVY1vVqlXJyMjg9OnTee6TkJBATEyM+1GzZk1flHrZDp44vwfH5IOwCTxgv3gpuWc7ERERKZUhpzjGjh1Lenq6+7F3716rSyqUKlHh5z0z+L+cUTydO+QS7URERKRULiGPjY0lLS3NY1taWhrR0dFERETkuY/D4cDhcPiivBLVvm4F4mLCSU0/gwlsM8/1QJmA4Z6T075uBeuKFBER8UOlsicnPj6eRYsWeWxbsGAB8fHxFlXkPXabwbi+TYGzk4wBbrStZE7Y09hwATCub1NNOhYREbmAX4SckydPkpSURFJSEnB2iXhSUhJ79uwBzg41DRo0yN3+vvvuY8eOHTzyyCNs2bKFf//733z22Wc8+OCDVpTvdb2axzHpjjbExpwdktprVuYnVyNqR9uYdEcbejWPs7hCERER/+MXS8iXLl3Ktddee9H2wYMHM336dIYMGcKuXbtYunSpxz4PPvggmzdvpkaNGjz11FMMGTKk0McsLUvIz+d0mazZeVRXPBYRkaBVlO9vvwg5ViiNIcdDzhnY+Bk06A7R1ayuRkRExCcC/jo5AjizYf4TsHOZ1ZWIiIj4pVK5ukqA8GgYvREiylldiYiIiF9ST05pFlEOTBPOlI6rN4uIiPiSQk5pN+d++OxOq6sQERHxOxquKu1a3s4fV9ARERGRcxRySrt6XQEtLxcREbmQQk4AWJD0G6Ff3c/0M9ew1NUagLiYcMb1baoLBYqISNDSnJxSbl5yCvfM/IUjOaEeg1ap6We4/+NE5iWnWFabiIiIlRRySjGny2T815sxMXgo5/9Y8nsvDpy9fSfA+K8343QF5fUeRUQkyCnklGJrdh4lJf2M+3lF0nkwZBYh5AJng05K+hnW7DxqUYUiIiLWUcgpxQ6eOOPxvJKRziD7Av5k7CuwnYiISDDQxONSrEpUuMfzrWYtOmZNJIuwAtuJiIgEA/XklGLt61YgLibcY8JxFmGEk0UX2wYMzq6yal+3glUlioiIWEYhpxSz2wzG9W0KeF4O8G/2xbwd+iYxnGRc36a6Xo6IiAQlhZxSrlfzOCbd0YbYmD+GpD509mBo2D956Y5rdJ0cEREJWpqTEwB6NY+jR9PYi694bDph90qoHW91iSIiIj6nkBMg7DaD+PoVPTeu/RDmPgqjN0JUrDWFiYiIWEQhJ5C1vhOqtYaoWN3bSkREgo5CTiCzh0K11szbeIBPvvqWH05Uc7+ke1uJiEig08TjADcvOYW5n0zk/exHqG2kurfr3lYiIhLo1JMTwM7d2yrNFc/BnHLsNv+Yl2Nydtn5+K8306NprIauREQk4KgnJ4Cdu7eVCxsrXc0A6G5bRzSZgO5tJSIigU0hJ4BdeM+qaE7yz9DJ3GpfWmA7ERGRQKDhqgB24T2rMijLjdkvsM+sXGA7ERGRQKCenACW172t9plVAIN2xhb+alume1uJiEjAUsgJYPnd2wqgt30N/ez/Y9yNjbHbDJwuk5W/HeHLpP2s/O0ITpfp+4JFRERKkGGaZlB+m2VkZBATE0N6ejrR0dFWl+NV85JTGP/1ZlLS/5h7Uy06jGf6NOT6lnX4fsMuxn33m8fruo6OiIj4o6J8fyvkBEHIAfK94vHitcnU++ovTMi9nbmuDu7253p+Jt3RRkFHRET8RlG+vzXxOEjkdW8rp8vkye9TGOCKZ4Orvsdruo6OiIiUdpqTE8TW7DzKgYxsXs3tzwEqUYYz9LMt52zE0XV0RESkdFPICWIXXh+nl20Nz4dOJZajBbYTEREpDTRcFcQuvD7Of13XsCKrGalUxIaLCLLIJIJKkQ5W/nZEdzAXEZFSxa96ct5++23q1KlDeHg4HTp0YM2aNfm2nT59OoZheDzCw3VRu6LI6zo6qZydt/NwyKfMCnuWShEGD83awIB3VzFqZhID3l3F1RMW68aeIiLi9/wm5Hz66aeMGTOGcePGkZiYSMuWLenZsycHDx7Md5/o6GhSUlLcj927d/uw4tKvoOvofOG8hndy+3D4tElqxmmP13QHcxERKQ38JuS89tpr3H333QwdOpSmTZsyefJkypQpw9SpU/PdxzAMYmNj3Y+qVav6sOLA0Kt5HJPuaENsjGcv2MmoevwQfi0Aw+3f8XzI+5w/IRnOrrzSRQNFRMRf+cWcnOzsbNatW8fYsWPd22w2G927d2flypX57nfy5Elq166Ny+WiTZs2vPjiizRr1izPtllZWWRlZbmfZ2RklNwJlHK9msfRo2msx3V0XKbJwPdWA5BOJEeI5vz+nvNXXl24NF1ERMQf+EXIOXz4ME6n86KemKpVq7Jly5Y892nUqBFTp06lRYsWpKen889//pNOnTqxadMmatSocVH7hIQExo8f75X6A8GF19H5Mmm/++dZzq7un/vZllPDOMREZz/A4OCJM/leaFBERMRKfhFyiiM+Pp74+Hj3806dOtGkSRPeeecdnnvuuYvajx07ljFjxrifZ2RkULNmTZ/UWhrld2fyasYRahtpnOvV2XX4FFdPWKxbQoiIiN/xi5BTqVIl7HY7aWlpHtvT0tKIjY0t1HuEhobSunVrtm/fnufrDocDh8Nx2bUGi3Mrr1LTz3D+rJt/O28GTAzghohkqix9lyO5Q4BQd5tzE5N1SwgREbGSX0w8DgsLo23btixatMi9zeVysWjRIo/emoI4nU42btxIXJy+VEtCQSuvDAxMIJqTxBiZZP+elW24gIsnJusO5yIiYgW/6MkBGDNmDIMHD+bKK6+kffv2vPHGG2RmZjJ06FAABg0aRPXq1UlISADg2WefpWPHjjRo0IDjx4/zyiuvsHv3boYPH27laQSUcyuvLryDeWxMOLe3q8nrC+ETOgLQwviNf4VO5I6csewzq7gnJk9cvJ2ZP+3RcJaIiPic34Sc2267jUOHDvH000+TmppKq1atmDdvnnsy8p49e7DZ/uh4OnbsGHfffTepqamUL1+etm3bsmLFCpo2bWrVKQSkvFZeta9bgW9+PuDRLoMyLHW15IBZCYA/GXvZZtbg9YXbLnpPDWeJiIgvGKZpBuXYQVFu1S4XW/nbEQa8uyrP1ypznP85HuCp3Lv41Hltnm0MzvYILX/0Oq3EEhGRQivK97ff9ORI6ZLfxGSAQ8QwOOcxfnbVA6C/fQmnTQdfuzq525wbzlr12xFsNkPLz0VEpMQp5EixnJuYfP/HiRjgEXQMDFa6/rgoY3vbVk79HnJCyaUCGaRRAYARMxI5fjrH3VbzdUREpKT4xeoqKZ3yuyVEbEw4D3Zv6H7+j5z7eCZ3MAC9batZ7hhFFY4BeAQcuPi+WFqZJSIixaWeHLks+U1MBpj50173cJbr9zy92NWaB3Ie4CDlAXg79A3mOjvwjevspQJMzs7XGf/1ZlwueO7bzVqZJSIixaKJx5p47DXzklO4/+NEgIvm7QCEksvzIVOZ52rHEldrmhm7aG7byRfOzuTmk7/PzdY5tzJLt5QQEQkumngsfiG/6+yUiwjl+Okccgjh0dx73Ns72jYxyL6AT3+/V1YnWzIbXfU4QRl3G/X0iIhIYaknRz05Xndhb8v5dzi/kINssggjktMkOu7l+dw7+Mh5PZVIJ5RcUij4jufn9/TkNYymXh4RkdKtKN/fCjkKOT7ndJlcPWFxnsvPzxfHETJxkEFZRtm/YFjIXFpnvYMTO/WMA+w2q+LEftF+BhBTJpTwEDupGerlEREJJEX5/tbqKvG5gu+L9YcUKpJBWQCmOXsxNPthnNix42RO2NPcb/8KgBhOUtdI4dzMHxM4firHI+CAVm6JiAQb9eSoJ8cy85JTLpqvExcTzlN9mvDct7/k29Nj4KKFsYNDZjkOUIm/2RfxbMg0Wma9SyYRtDW2kkYF9pmV89j37BL3p/o0LXA+jyY0i4j4Jw1XFYJCjn/IL0zktzLrwgsPApTlFI2NPaw1GwOwOGwMP7haMj53MDGc5C/2H5ntvJrjRBVYy7kIc881dflqQ4omNIuI+CGFnEJQyPF/xe3pieYkDnI4RHnaG7/wUdhLXJf1T/ZTmTvsC4ghk7ed/X5vfW69VsG0dF1ExD9oCbkEhPwuNGi3GdhsRp63lADc83gA1phNuCLrPbJ//1WPIZNKRjpw9jo9Kx0jeSpnKHNdHahEOuWNE2w3q2FeMF2tqEvXFYJERKynnhz15JRaefX0xEY7OJPrIv1UToErtwDKcIYh9vnMd13Jb2Z1htrn8ljITJpmTcWJnSH2eRwwK/K9qx1/RKm8g8r5PT1Anj1Qmu8jInL5NFxVCAo5gSGvwLBgc2qh5/OcL4pT1DZSSTbP3j19Suir/GLW5vXcW6hlpPF12BMMzn6MJLMBTYzdVDaOs8zV0uP9Y8qE5hmwijLf51IhSCFJRIKZQk4hKOQEtuLO58lPZY7T376UT5zXcZRongr5iC62DXTP/icA74W+wnxXO2Y5uxJNJk2MPSSZ9cki7JLvXZReoPzOS71EIhIsFHIKQSEn8BV15VZRGLgox0mOcfZ358mQj/ifqzlLXK3pbPuZj8Je4uqsN9hnVuEe+9fUMA7zdO5QAK62bWS7qxqp5129ubC9QFOW7VQvkYgENYWcQlDICW759Yjc1DKOKct2AkUb6jpfKLnUMtLYacbhwsbt9sXEGUd5PfcWDFxscQzhhdyBfOjsSQfjF8aFfsid2Y9xhBg62ZIJI5elrlYA2HG6r+psM6Co1yv0ZS+RApKI+IJCTiEo5EhBPT0lOdTlyaQy6WQRSgaRNDF2M9C+kPG5g8khhDdDJ1KOkwzOeQwDF784hjIudwifOq+lmbGL/vYlvJx7O5lEUN/YTy52dpuxBR7RF71ElwpIUHAIUkASkcJSyCkEhRwpSHEvUlgunzBRFKHkkkMIIeTyV/uPrHX9id/M6lxj28DYkBnclP0COYTwTuhrOMhhSM6jGLhYGjaGl3IHMNfVgUbGHm6wr2ZS7k2cwUFtIxUn9jyvAg2X10t0qYB0qV6kgl5TQBKRCynkFIJCjhRXQb0WwGXP9ymsqhwl3MhmtxlLGDk8EDKbec52bDLr0sO2lvGh07km6w1yfw9EYeQw9PdA9KNjNC/m/I3vXB1pZuzkFvsyXs69jdOE09zYgYnBJrMu8Efoyk9BAelSvUj5fUa+CkiXel0BSsT/KOQUgkKOXI6CvvyKO9/nUr1ANgNMs3jhqYZxiDBy2GFWI5RcRobM4XvnlWwy69DVtp7HQ2bQJzuBHEKYEvoqITi5K+cRDFxsd9zJ47nD+dR5LR2MXxgZMpv7c0ZzkjL0sq0hjBy+cl0FQDtjCzvNOA4Tgw0XLgwKc0XpvHg7IF1qmO1S+4J3A5QClkjeFHIKQSFHvKmo830K6gW6cFjowtdLWllO4SCHI8Rgx0k/2/9INBuy04yjjbGNu0LmMTrn/8glhISQd4kyTjMy5+8YuPjNcSeP5w5jpvM6rrf9xKTQN2iXNYmjRDPK/gXljJOMzx0MwCMhM1nkbM06sxGVOUZb268scbUiizAqko6JwVGK99/m5c5DsjpAeXuOk3q3pDRTyCkEhRyxSnF6gS715WdlL9EfTOoYqRwzo0inLDWMg1xj28hnzi7kEsJA+0KiOMVk502Aybywx/h37k185bqKHra1vBv2Gm3OTOYo0bwSMpm6tlRuyX4GgOWOvzM5ty8fO3vQzNjJoyEzeSjnfg5Rjp62NcQax/jA2ROAG2yr+NWswa9mDcpwhlrGQXaYcWQT+vtqNRtgFGseEng/QHl7jlNxf8d8MX9KPWNSGAo5haCQI/6quH+sreolKomAZMNFFKfIoAwmNhoY+yjLGZLMBgAMt3/LGldjfjbr09TYxYiQOTyeM5x0yvJgyCwaGfu4L+dBAJIcdzMp9ybecfalg/ELnzqeo2vWq+wy43g65EM62TbRK3sCAJ+GPcu3zg586OxJHSOFsSGf8FzuHewzq9DFtoGGxj7ec/YBoJdtDfvMSiSb9YjgDE2N3Wwxa5FJBJGcJgQn6b/fN624AepS+17uEF5hVtF5q3eroNe8Hb5Kc8+YwtvFFHIKQSFHApGve4kg/4BUkivOiiKUXAxMsgklktM0NPbzi1mLLMJoZWynqnGM+a52APzd/l/Wmw340dWC+sZ+ngz5mCdz7mI/lbnP/hWdbRsZmPMEAMvCRvG1K55Xcm+nibGbuY6x9M16no1mPR4M+Zz+9qXEZ00EYHbY06x2NeGl3AFU4zDvhb3KIzl3k2zWo6ftJ3rY1/GPnPsAuMs+l1SzPN+5OlKGM/S3L2WBsy37qUxNI436xgGWuloDUNdIIcsM5QCVMHARQyYniSC3kPdavlT48lbvlpXhqzT3jF3Oe1/uta38OXwp5BSCQo4EI2/0EhXmD/ml7iV24ZeVVQGpIAYu7LjIJQQH2dQ0DrLXrEIWYdQzDlDdOMyPrhYA/MW2jBQqstLVjMocZ2TIbN533sAesyp9bKvoaf+Jv+c8AMDE0Df5zazO67m3UI3DLHL8g+E5D/E/1xUMs3/HgyGf0zxrKnC252mfWYmHcv6PcpwgKfxe7s1+kPmudtxqX8q4kA9pnvU+YDAhZAqncDA+dzBh5PBB6ATecvZjhas5bY2tDAxZxKM595BDCH+2/UiI4WSWsytwNngtdzVnm1mTWI7Q1vYrC1xtySaU+sZ+QnGyxawFnA1eR8xoMogkhFwiyOYk4ZjYCv3Zeit8XWrf0hzOvHltK/Bu+LpcCjmFoJAjUjSX8//civsHFbwXkEpmHpJ3OcimLKc5QgwA9YwD5GJnj1kVB9l0tW1gvasBBynPn4y9dLJtYrqzFwC32ZeQbYYw29UZB9lMCJ3Ch7nXk2j+ic62nxkZMoe/ZT+BEzvPhkwjlFzG5t6NgYtkxzCezhnKF65r6G1bzaSwN2lx5l0yiOTN0IlU5jh/y3kSgC2OwSTk/o0PnD3pYtvAB2ET6HBmImlU4PmQ92lo289t2U8DMDfsUf7j7O6eW5UQ+h73Z49mP5UZaF9IE2M3T+YOA+DpkA9Z7WrMfFd74jjC8JDvmJLbhzQqEG/bRH3jAB87ewDQ17aCfWZl1psNKcspOts2stLVlONEUcM4RBWOkWj+CTgbyk6ZDtKogB0nVTjOEaLJJpRQcrHj5AyOQv37WBXOivve/hC+Jt3R5rKDjkJOISjkiPhWcbvGvRGQ4NLDbBf+fP5zbwYofwtfdpxEkMVJIgCDyhwnBCcpv997ra2xlX1mZdKoQGWO0862hUWuNmQRRrxtE+U5wXeujgD8n/1L1rgasdZsTF0jhWH273gt91aOEk1/+xIaGft4LvdOAP4d+gZLXK2Y5exKY2MPE0P/xT05Y9hhVmOkfTbd7evol/08AIvCHmKBqy0v5f6NPxl7+d7xKH/OGs96syF/t/+XgSEL6ZD1bwC+DRvLOtefeDp3KDWMgyx3jOZv2Y+zwtWc4fZv+XvIf2mR9T4AM8OeY6crlrG5d1OeDL53PMJDOfezzNWSvrYVjAyZQ8/slwEYF/IBJ4ng1dz+OMhmSuhr/Dv3ZlabTWhv/MIdIQsZk3M/uYRwh30BBiYfOa8HTB4L+YTvnB342axPfWM/PW1red/ZmyzC6Gz7mQiy+P73IdabbP9jk1mH38zqVCKd1rZfWeZqQRZh1Df2E0EWyWY9AJoauzhkluMQ5QgnizjjKPvMyjiNECLNU4SQ677/XhnOkEPI79fEOv+/AE+XE74MIDYmnOWPXndZQ1cKOYWgkCNSengjIF1Olz14J0AVZt/LGcK7VIDyt4BVNCZgYMNFWU6TSThO7ERzkmjjFPvMKgA0NPZxGgf7zMqEk0V72xZ+dtXjOFHUN/bTyNjrDmU9bWs4QRlWuJpThjMMtc/jW1cHdv1+OYXr7et4KXcAcHZy/Gkc/MfZnXCyeC10Eu/n9mad2YjOtp+51/41g3LG4sLGEyEfY8Pkudw7seFiUdhDvJx7O3NdHehuW8eE0Cl0yXqdk5Th1dB/U4kMBuc8BsBWxyCez72Dj5zXc61tPdPCXqHdmbc5RHleCpnCn2z7+Ev2s4DnJPyOts3MDHueLlmvsduMZVzIB8TbNrsn4a90jOQzZ1dez72FFsZvfOV4it5ZCfxi1mZ0yOf0tK2ld/ZLAHwWNp4fnVfwlvMv1DFSmBr6Cg/k/J1NZh1uty+mj20Vd+Y8nu+/1Cd3dyS+fsV8X78UhZxCUMgRCQ7emnxp1YRTKN4QHhQcoC71ujfnT3kzfJXu4HaxUHJxYeDETii5RHGK45TFhY0KZBBGDqm/97I1MvZwxIzhMDFEk0ljYw8bzPpkEUZDYx8VOMFqswkAPWxr2WNWYatZiwpk0MO+jnnOdqRTliuNLdSzpfCZ81oA7rR/z04zjuWuK6jMcYaFfMdHuT3YT2W62pLoYPuFCb+Hv7y8eXsrbm5VvdifQakNOW+//TavvPIKqamptGzZkrfeeov27dvn237WrFk89dRT7Nq1i4YNGzJhwgRuuOGGQh1LIUdELpdVq1esmjRa0HuD/4WvwuxbWsNZaQ5vQdmT8+mnnzJo0CAmT55Mhw4deOONN5g1axZbt26lSpUqF7VfsWIF11xzDQkJCdx4443MmDGDCRMmkJiYSPPmzS95PIUcESnN/PG6Lv4Yvkpjz9jlvHdhWBW+gnpOTocOHWjXrh0TJ569zoTL5aJmzZo88MADPPbYYxe1v+2228jMzOSbb75xb+vYsSOtWrVi8uTJlzyeQo6ISMnzx/B1qddLYzjz5rWtLvy5sPsW5vWgXF2VnZ1NmTJl+Pzzz+nXr597++DBgzl+/DhffvnlRfvUqlWLMWPGMHr0aPe2cePGMWfOHDZs2HDJYyrkiIjIOaUxnBX0uj+GLyuuk1O4y2R62eHDh3E6nVStWtVje9WqVdmyZUue+6SmpubZPjU1Nc/2WVlZZGVluZ9nZGRcZtUiIhIo7DYj33kiBb12ua976717NY+jR9PYfAPSpV6/nH0v9bov+UXI8YWEhATGjx9vdRkiIiI+4Y/hy9cKf91tL6pUqRJ2u520tDSP7WlpacTGxua5T2xsbJHajx07lvT0dPdj7969JVO8iIiI+CW/CDlhYWG0bduWRYsWube5XC4WLVpEfHx8nvvEx8d7tAdYsGBBvu0dDgfR0dEeDxEREQlcfjNcNWbMGAYPHsyVV15J+/bteeONN8jMzGTo0KEADBo0iOrVq5OQkADAqFGj6NKlC6+++ip9+vRh5syZrF27lilTplh5GiIiIuIn/Cbk3HbbbRw6dIinn36a1NRUWrVqxbx589yTi/fs2YPN9kfHU6dOnZgxYwZPPvkkjz/+OA0bNmTOnDmFukaOiIiIBD6/WEJuBS0hFxERKX2K8v3tF3NyREREREqaQo6IiIgEJIUcERERCUgKOSIiIhKQ/GZ1la+dm2+t2zuIiIiUHue+twuzbipoQ86JEycAqFmzpsWViIiISFGdOHGCmJiYAtsE7RJyl8vFgQMHiIqKwjCKdtOwjIwMatasyd69e4Nm+bnOOTjOGYLzvHXOOudAFYjnbJomJ06coFq1ah7Xz8tL0Pbk2Gw2atSocVnvEYy3h9A5B49gPG+dc3DQOZd+l+rBOUcTj0VERCQgKeSIiIhIQFLIKQaHw8G4ceNwOBxWl+IzOufgEYznrXMODjrn4BO0E49FREQksKknR0RERAKSQo6IiIgEJIUcERERCUgKOSIiIhKQFHKK4e2336ZOnTqEh4fToUMH1qxZY3VJXpOQkEC7du2IioqiSpUq9OvXj61bt1pdlk+99NJLGIbB6NGjrS7Fq/bv388dd9xBxYoViYiI4IorrmDt2rVWl+U1TqeTp556irp16xIREUH9+vV57rnnCnU/nNJk2bJl9O3bl2rVqmEYBnPmzPF43TRNnn76aeLi4oiIiKB79+78+uuv1hRbQgo655ycHB599FGuuOIKIiMjqVatGoMGDeLAgQPWFVwCLvXvfL777rsPwzB44403fFafVRRyiujTTz9lzJgxjBs3jsTERFq2bEnPnj05ePCg1aV5xQ8//MCIESNYtWoVCxYsICcnh+uvv57MzEyrS/OJn376iXfeeYcWLVpYXYpXHTt2jKuuuorQ0FDmzp3L5s2befXVVylfvrzVpXnNhAkTmDRpEhMnTuSXX35hwoQJvPzyy7z11ltWl1aiMjMzadmyJW+//Xaer7/88sv861//YvLkyaxevZrIyEh69uzJmTNnfFxpySnonE+dOkViYiJPPfUUiYmJ/Pe//2Xr1q3cdNNNFlRaci7173zO7NmzWbVqFdWqVfNRZRYzpUjat29vjhgxwv3c6XSa1apVMxMSEiysyncOHjxoAuYPP/xgdSled+LECbNhw4bmggULzC5dupijRo2yuiSvefTRR82rr77a6jJ8qk+fPuZdd93lse0vf/mLOXDgQIsq8j7AnD17tvu5y+UyY2NjzVdeecW97fjx46bD4TA/+eQTCyoseReec17WrFljAubu3bt9U5SX5XfO+/btM6tXr24mJyebtWvXNl9//XWf1+Zr6skpguzsbNatW0f37t3d22w2G927d2flypUWVuY76enpAFSoUMHiSrxvxIgR9OnTx+PfO1B99dVXXHnlldx6661UqVKF1q1b8+6771pdlld16tSJRYsWsW3bNgA2bNjA8uXL6d27t8WV+c7OnTtJTU31+B2PiYmhQ4cOQfM3Dc7+XTMMg3Llylldite4XC7uvPNO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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "import matplotlib.pyplot as plt \n", "ij_pairs = np.triu_indices(len(x_int), k=1)\n", @@ -110,9 +149,37 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "xmin = 1, xmax = 60\n" + ] + }, + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Coulomb potential on a.')" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjkAAAHICAYAAACszftMAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/TGe4hAAAACXBIWXMAAA9hAAAPYQGoP6dpAABSt0lEQVR4nO3dd3hUVf7H8fedVFoCGEgChCpSRFqAGBFRCYIiiqsLsrAUxcKiq2DFAmIhgKuigvDDgrgWFFdRRKNSLaAoEQXpvSaAgQQCaTPn90dkZCBAApncyczn9Tz32cydc+d+c+XJfPacc8+1jDEGERERET/jsLsAEREREW9QyBERERG/pJAjIiIifkkhR0RERPySQo6IiIj4JYUcERER8UsKOSIiIuKXFHJERETELynkiIiIiF9SyBHxM4sWLcKyLBYtWlTm537zzTexLIuff/65zM/ty7Zu3YplWbz55pslPvbYNd26dWup1yXi7xRyRLxk06ZN3HHHHTRs2JDw8HAiIiLo2LEjL774IkePHrW7vIAxduxYZs+eXSbnevfdd5k4cWKZnEtEzizY7gJE/NHcuXP5+9//TlhYGAMGDKBFixbk5eXx3Xff8cADD/D7778zbdo0u8sMCGPHjuWmm26iV69eXj/Xu+++y6pVq7j33ns99terV4+jR48SEhLi9RpE5C8KOSKlbMuWLdx8883Uq1ePBQsWEBsb635v2LBhbNy4kblz59pYoZQ1y7IIDw+3uwyRgKPhKpFSNmHCBA4fPszrr7/uEXCOOf/887nnnnvcrwsKCnjqqado1KgRYWFh1K9fn0ceeYTc3FyP4yzL4oknnjjp8+rXr8+gQYPOWNesWbOIj4+nQoUKREVF0b9/f3bt2uXRZtCgQVSuXJnt27dz7bXXUrlyZWrXrs3kyZMBWLlyJVdeeSWVKlWiXr16vPvuu0We68iRI9xxxx2cd955REREMGDAAA4cOHDGGo+df/PmzXTr1o1KlSpRq1YtnnzySYwxHm2zs7O57777iIuLIywsjCZNmvCf//zHo51lWWRnZzNjxgwsy8KyLI9rtWvXLm655Raio6MJCwvjwgsv5I033vA4z7E5Th988AHPPPMMderUITw8nC5durBx40Z3u8svv5y5c+eybds297nq168PFD0n57fffmPQoEHu4cyYmBhuueUW/vjjjzNep1NZsGABnTp1olKlSlStWpXrr7+eNWvWeLR54oknsCyLjRs3MmjQIKpWrUpkZCSDBw/myJEjZzzHt99+y9///nfq1q1LWFgYcXFxDB8+XEOw4pPUkyNSyubMmUPDhg255JJLitV+yJAhzJgxg5tuuon77ruPH3/8keTkZNasWcPHH39cKjW9+eabDB48mPbt25OcnEx6ejovvvgi33//Pb/88gtVq1Z1t3U6nVx99dVcdtllTJgwgXfeeYe77rqLSpUq8eijj9KvXz/+9re/MXXqVAYMGEBiYiINGjTwON9dd91F1apVeeKJJ1i3bh1Tpkxh27Zt7sBwOk6nk+7du3PxxRczYcIEUlJSGD16NAUFBTz55JMAGGO47rrrWLhwIbfeeiutW7fmyy+/5IEHHmDXrl288MILAPz3v/9lyJAhdOjQgdtvvx2ARo0aAZCens7FF1+MZVncdddd1KhRgy+++IJbb72VrKysk4acxo0bh8Ph4P777yczM5MJEybQr18/fvzxRwAeffRRMjMz2blzp/v8lStXPuXv+fXXX7N582YGDx5MTEyMewjz999/54cffjjjdTrRvHnzuPrqq2nYsCFPPPEER48e5eWXX6Zjx46kpqa6A9cxvXv3pkGDBiQnJ5Oamsprr71GzZo1GT9+/GnPM2vWLI4cOcLQoUM577zzWLZsGS+//DI7d+5k1qxZJapZxOuMiJSazMxMA5jrr7++WO1XrFhhADNkyBCP/ffff78BzIIFC9z7ADN69OiTPqNevXpm4MCB7tcLFy40gFm4cKExxpi8vDxTs2ZN06JFC3P06FF3u88++8wAZtSoUe59AwcONIAZO3ase9+BAwdMhQoVjGVZZubMme79a9euPamm6dOnG8DEx8ebvLw89/4JEyYYwHzyySenvR7Hzn/33Xe797lcLtOjRw8TGhpq9u3bZ4wxZvbs2QYwTz/9tMfxN910k7Esy2zcuNG9r1KlSh7X55hbb73VxMbGmv3793vsv/nmm01kZKQ5cuSIMeav69msWTOTm5vrbvfiiy8awKxcudK9r0ePHqZevXonnWvLli0GMNOnT3fvO/b5x3vvvfcMYL755hv3vmPXdMuWLSe1P17r1q1NzZo1zR9//OHe9+uvvxqHw2EGDBjg3jd69GgDmFtuucXj+BtuuMGcd955pz3HqepOTk42lmWZ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Leq28MOHD5t//OMfpmrVqgZw305eVNudO3eaG264wVStWtVERkaav//972b37t2nvC3/TLeQG2PMvHnzTMeOHU2FChVMRESE6dmzp1m9erVHm2O3kB+7Hb+k51m9erVJSkoylStXNlFRUea2224zv/7660m/37HPO36fSFmzjCmHswZFxC8NGjSIDz/8kMOHD9tdioj4Ac3JEREREb+kkCMiIiJ+SSFHRERE/JLm5IiIiIhfUk+OiIiI+CWFHBEREfFLAbsYoMvlYvfu3VSpUuWcnhskIiIiZccYw6FDh6hVqxYOx+n7agI25OzevZu4uDi7yxAREZGzsGPHDurUqXPaNgEbco491G/Hjh1ERETYXI2IiIgUR1ZWFnFxcR4P5z2VgA05x4aoIiIiFHJERETKmeJMNdHEYxEREfFLCjkiIiLilxRyRERExC8p5IiIiIhfUsgRERERv6SQIyIiIn5JIUdERET8kkKOiIiI+CWFHBEREfFLAbvisbc4XYZlWzLYeyiHmlXC6dCgOkEOPQBURESkrPlET84333xDz549qVWrFpZlMXv27DMes2jRItq2bUtYWBjnn38+b775ptfrPJOUVXu4dPwC+r76A/fMXEHfV3/g0vELSFm1x+7SREREAo5PhJzs7GxatWrF5MmTi9V+y5Yt9OjRgyuuuIIVK1Zw7733MmTIEL788ksvV3pqKav2MPTtVPZk5njsT8vMYejbqQo6IiIiZcwyxhi7izieZVl8/PHH9OrV65RtHnroIebOncuqVavc+26++WYOHjxISkpKsc6TlZVFZGQkmZmZ5/yATqfLcOn4BScFnGMsICYynO8eulJDVyIiIuegJN/fPtGTU1JLly4lKSnJY1+3bt1YunTpKY/Jzc0lKyvLYysty7ZknDLgABhgT2YOy7ZklNo5RURE5PTKZchJS0sjOjraY190dDRZWVkcPXq0yGOSk5OJjIx0b3FxcaVWz95DngHnf6Gj+XfQR2dsJyIiIt5TLkPO2Rg5ciSZmZnubceOHaX22TWrhHu8/sjZiZ/NBWdsJyIiIt5TLm8hj4mJIT093WNfeno6ERERVKhQochjwsLCCAsL80o9HRpUJzYynLTMHAzwjtNzKO3YnJwODap75fwiIiJysnLZk5OYmMj8+fM99n399dckJibaUk+Qw2J0z+ZAYaBpZO2ikbXL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mDBBiwKejssFr14BCXdC6752VyMiIuJ3SvyAzgMHDhAeHs7cuXP57rvv2LZtG0ePHiUqKoo2bdrQrVs3WrRo4a16S41PPKBz3hPQqAs06GTP+UVERMoZrz6FvEaNGjz11FPccccdWOV4PolPhBwREREpEa8+hXzEiBE8+OCDtGnThm+//fasixTg6AHY85vdVYiIiPilEoeckSNHsm7dOtq0acMVV1xB3759dRfV2Up9C6Zfo9vIRUREvOCs1smJjY1l+vTp/Pjjj+zcuZMmTZrw9NNPk5ubW9r1+beLesOtX9ldhYiIiF8668UAAeLj4/n22295/fXXef3112nWrBkff/xxadXm/yJiIbq51soRERHxgnMKOcf06dOHtWvXcuuttzJw4EC6du1aGh/r/1wumP8kbFtqdyUiIiJ+56yfXQWQl5fH2rVrWbVqlXurUKGCHutQXA4HrP8KqjeCeol2VyMiIuJXShxyxowZ4w40mzZtoqCggMjISFq0aEHLli255ppraNmypTdq9U9Dv7O7AhEREb9U4uGqWbNmERwczIABA/joo4/YunUrBw4c4Ntvv2Xy5MnceeedXHLJJWdVzOTJk6lfvz7h4eEkJCSwbNmyYh03c+ZMLMuiV69eZ3VeERER8T8l7slZtWqVN+rg/fffZ8SIEUydOpWEhAQmTpxIt27dWLduHTVr1jzlcVu3buX++++nU6dyumrwT6/Dj1PhrlM/D0xERERKrlQmHpeG559/nttuu43BgwfTvHlzpk6dSsWKFXnjjTdOeYzT6aRfv36MGTOGhg0blmG1pSjmImjdT2vliIiIlDKfCDl5eXksX76cpKQk9z6Hw0FSUhJLl576zqMnn3ySmjVrcuutt57xHLm5uWRlZXlsPiGuA1x6r24jFxERKWU+EXL279+P0+kkOjraY390dDRpaWlFHvPdd9/x+uuv8+qrrxbrHMnJyURGRrq3uLi4c667VLhcsGMZZO60uxIRERG/4hMhp6QOHTrEP//5T1599VWioqKKdczIkSPJzMx0bzt27PBylcVkWfBWL/hdiyiKiIiUpnNaJ6e0REVFERQURHp6usf+9PR0YmJiTmq/adMmtm7dSs+ePd37XC4XAMHBwaxbt45GjRp5HBMWFkZYWJgXqj9HlgV3LIaIWnZXIiIi4ld8oicnNDSU+Ph45s+f797n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3d3m6NGj5l//+pepVq2aqVixornhhhvMnj177CvaZrfccoupV6+eCQ0NNTVq1DBdunRxBxxjdL2K48SQo2vmqU+fPiY2NtaEhoaa2rVrmz59+piNGze639f1OtmcOXNMixYtTFhYmGnatKmZNm2ax/t2/u23jDHG+/1FIiIiImVLc3JERETELynkiIiIiF9SyBERERG/pJAjIiIifkkhR0RERPySQo6IiIj4JYUcERER8UsKOSLiV8aOHUvlypXd27EHB4pI4NFigCLiVzIyMsjIyHC/rl69OtWrV7exIhGxi0KOiIiI+CUNV4mI33jvvfeoUKECe/bsce8bPHgwLVu2JDMz08bKRMQO6skREb9hjKF169ZcdtllvPzyy4wePZo33niDH374gdq1a9tdnoiUsWC7CxARKS2WZfHMM89w0003ERMTw8svv8y3336rgCMSoNSTIyJ+p23btvz+++989dVXdO7c2e5yRMQmmpMjIn4lJSWFtWvX4nQ6iY6OtrscEbGRenJExG+kpqZy+eWX83//93+8+eabREREMGvWLLvLEhGbaE6OiPiFrVu30qNHDx555BH69u1Lw4YNSUxMJDU1lbZt29pdnojYQD05IlLuZWRkcMkll3D55ZczdepU9/4ePXrgdDpJSUmxsToRsYtCjoiIiPglTTwWERERv6SQIyIiIn5JIUdERET8kkKOiIiI+CWFHBEREfFLCjkiIiLilxRyRERExC8p5IiIiIhfUsgRERERv6SQIyIiIn5JIUdERET8kkKOiIiI+CWFHBEREfFL/w/zjzepNWozaAAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "rij_int = np.unique(np.array(np.sqrt((x_int[ij_pairs[0]] - x_int[ij_pairs[1]])**(2)), dtype=int))\n", "Vij_int = 1.0 / rij_int\n", @@ -234,9 +301,30 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "(1e-06, 0.0001)" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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1ignJ0e7d++W0+nU6dOnVVlZqYqKCs8Chc2bN/eEIElKSkrSoUOHJEllZWUqKSlRv379PK9HRkaqb9++crvdkqR9+/apoqJCQ4cOrfHZJ0+eVO/evf3+e14IghAAAAGS0CLGr8f56qKLLqqx/cMPP+imm27S+PHjNWPGDMXHx2vTpk269957dfLkSU8Qatq0aY332Ww2+fJo0uoxRytXrlSbNm1qvBYdHd2QXyVgCEIAAARIv47xSrLHqLisss5xQjZJifYY9esYH5R6tm/fLrfbrZdeekkREVXDhN955x2fzmG329W6dWtt27ZN119/vaSqZ7Ll5+crJSVFkuRwOBQdHa39+/eHVDdYXQhCAAAESGSETVMzHBr/Rr5sUo0wVP1wiKkZDkVGBOcRHl26dNGpU6c0f/58ZWRk1BhE7YuHH35YOTk56tKli7p376758+frl19+8TzyokWLFpo0aZIee+wxud1uXXvttSorK9PmzZsVGxurzMzMOs9bWlqq/fv36+eff5Yk7dmzR5KUmJioxMTEBv7W58asMQAAAmh4zyQtuLuPEu01u78S7TFacHefoK0jJEm9evXSnDlzNGvWLPXs2VNLlixRTk6Oz+d54okndMcdd2jMmDFKS0vTxRdfrGHDhikm5tffcfr06XrmmWeUk5OjHj16aPjw4Vq5cqU6duxY73k/+OAD9e7dWyNHjpQk3X777erdu3eDwpq3bIYvnX4WVD1YuqysTLGxsWaXAwAIosrKShUWFqpjx441bvIN4XIb2lpYqkPllUpoUdUdFqyWoEBzu93q0aOHRo8erenTpwftc8/1/Xh7/6ZrDACAIIiMsAVkirwZfvzxR61evVrp6ek6ceKEcnNzVVhYqDvvvNPs0nxG1xgAAPBJRESEFi9erNTUVA0YMEDffPON1qxZox49ephdms9oEQIAAD5p27atNm/ebHYZfkGLEAAAsCyCUD3y8vLkcDiUmppqdikAACBACEL1yMrK0q5du7Rt2zazSwEAAAFCEAIAAJZFEAIAAJZFEAIAAJZFEAIAAJZFEAIAAKY7deqUnnjiCV155ZW66KKLlJycrDFjxngewBooBCEAAAJpfY60YXbdr22YXfV6EJw8eTIon9NQFRUVys/P1zPPPKP8/Hy999572rNnj26++eaAfi5BCACAQIqIlNbPqB2GNsyu2h8R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xo0aOnSoPvroI23fvl2DBg1SRkaGvv766wBX2rj5ep2rHT16VGPGjNFvfvObAFUWXhpynUePHq21a9fq5Zdf1p49e7R06VJ169YtgFWGB1+v9ebNmzVmzBjde++92rlzp959911t3bpV999/f4Arbbw2bNigrKwsffnll/r000916tQp3XDDDTp+/Hi97/niiy90xx136N5779XXX3+tUaNGadSoUdqxY0fgCjUQcJKM5cuXn/OYyZMnG1dccUWNfbfddpsxbNiwAFYWXry5znVxOBzGtGnT/F9QmPLlOt92223G73//e2Pq1KlGr169AlpXuPHmOn/88ceG3W43jhw5EpyiwpQ31/qFF14wOnXqVGPfvHnzjDZt2gSwsvBy6NAhQ5KxYcOGeo8ZPXq0MXLkyBr7+vfvbzzwwAMBq4sWoRCxZcsWDRkypMa+YcOGacuWLSZVZA1ut1vl5eWKj483u5Sws2jRIn3//feaOnWq2aWErQ8++EBXX321Zs+erTZt2qhr166aNGmS/vOf/5hdWthJS0vTgQMH9NFHH8kwDJWUlGjZsmUaMWKE2aU1GmVlZZJ0zv/fmnEvNH1laVQpLi5W69ata+xr3bq1nE6n/vOf/6hZs2YmVRbeXnzxRR07dkyjR482u5Sw8t133+nJJ5/U559/riZN+N9MoHz//ffatGmTYmJitHz5ch0+fFgPPfSQjhw5okWLFpldXlgZMGCAlixZottuu02VlZU6ffq0MjIyfO4utiq3262JEydqwIAB6tmzZ73H1XcvLC4uDlhttAjBst58801NmzZN77zzjhISEswuJ2y4XC7deeedmjZtmrp27Wp2OWHN7XbLZrNpyZIl6tevn0aMGKE5c+bo1VdfpVXIz3bt2qVHH31Uzz77rLZv365Vq1bphx9+0IMPPmh2aY1CVlaWduzYobfeesvsUmrhr2ohIjExUSUlJTX2lZSUKDY2ltagAHjrrbd033336d13363VDIsLU15erq+++kpff/21JkyYIKnqhm0Yhpo0aaLVq1dr8ODBJlcZHpKSktSmTRvZ7XbPvh49esgwDB08eFCXX365idWFl5ycHA0YMECPP/64JOmqq67SRRddpOuuu07PP/+8kpKSTK4wdE2YMEF///vftXHjRl122WXnPLa+e2FiYmLA6qNFKESkpaVp7dq1NfZ9+umnSktLM6mi8LV06VKNHTtWS5cu1ciRI80uJ+zExsbqm2++UUFBgefnwQcfVLdu3VRQUMBzAv1owIAB+vnnn3Xs2DHPvr179yoiIuK8Nxz4pqKiQhERNW+ZkZGRkiSDJ1XVyTAMTZgwQcuXL9e6devUsWPH877HjHshLUIBcuzYMe3bt8+zXVhYqIKCAsXHx6tdu3aaMmWKfvrpJ7322muSpAcffFC5ubmaPHmyxo0bp3Xr1umdd97RypUrzfoVGgVfr/Obb76pzMxM/fGPf1T//v09/c7NmjWr8bdq1OTLdY6IiKg1BiAhIUExMTHnHBsA3/8833nnnZo+fbrGjh2radOm6fDhw3r88cc1btw4WpLPw9drnZGRofvvv18LFizQsGHDVFRUpIkTJ6pfv35KTk4269cIaVlZWXrzzTf1/vvvq0WLFp7/39rtds+fzzFjxqhNmzbKycmRJD366KNKT0/XSy+9pJEjR+qtt97SV199pf/7v/8LXKEBm49mcevXrzck1frJzMw0DMMwMjMzjfT09FrvSUlJMaKiooxOnToZixYtCnrdjY2v1zk9Pf2cx6NuDfnzfCamz3unIdf522+/NYYMGWI0a9bMuOyyy4zs7GyjoqIi+MU3Mg251vPmzTMcDofRrFkzIykpybjrrruMgwcPBr/4RqKu6yupxr0tPT291v9/33nnHaNr165GVFSUccUVVxgrV64MaJ08fR4AAFgWY4QAAIBlEYQAAIBlEYQAAIBlEYQAAIBlEYQAAIBlEYQAAIBlEYQAAIBlEYQAAIBlEYQAAIBlEYQAAIBlEYQAAIBlEYQAWMrSpUvVrFkzFRUVefaNHTtWV111lcrKykysDIAZeOgqAEsxDEMpKSm6/vrrNX/+fE2dOlWvvPKKvvzyS7Vp08bs8gAEWROzCwCAYLLZbJoxY4Z++9vfKjExUfPnz9fnn39OCAIsihYhAJbUp08f7dy5U6tXr1Z6errZ5QAwCWOEAFjOqlWrtHv3brlcLrVu3drscgCYiBYhAJaSn5+vgQMH6s9//rMWL16s2NhYvfvuu2aXBcAkjBECYBk//PCDRo4cqaeeekp33HGHOnXqpLS0NOXn56tPnz5mlwfABLQIAbCE0tJS/c///I8GDhyohQsXevaPHDlSLpdLq1atMrE6AGYhCAEAAMtisDQAALAsghAAALAsghAAALAsghAAALAsghAAALAsghAAALAsghAAALAsghAAALAsghAAALAsghAAALAsghAAALCs/x/8Z7s0Me/FegAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "from qualtran.bloqs.chemistry.trotter.grid_ham.inverse_sqrt import get_inverse_square_root_poly_coeffs\n", "coeffs_one, coeffs_two = get_inverse_square_root_poly_coeffs()\n", @@ -271,7 +359,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -286,7 +374,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -295,7 +383,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -304,9 +392,30 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "(1e-12, 1e-08)" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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m29Wp06dFBcXp2nTpjleXOstBCQAAMywPUvauazpfTuX1e33gZqaGp98TktVVlYqPz9fv/71r5Wfn6+3335bR48e1Z133unVzyUguYmFIgEAHhEULG1f0jgk7VxW1x4U7JWPHTFihGbPnq25c+eqa9euGjNmjCRp+fLljl6anj176sEHH9T58+cdv7d27VpFRkbqo48+Uv/+/fW9731PY8eOVVFRkeOY2tpaPfzww4qMjFSXLl30+OOPKz09XRMnTnQcY7fblZWVpT59+qhDhw5KSEjQW2+91Wy9ERER+vjjjzVp0iT169dPt956q7Kzs5WXl6cvv/zS8xfovwhIbmKhSACAR6TMk0YubBiS6sPRyIV1+73kpZdeUkhIiPbs2aPVq1dLqnuP2vPPP69Dhw7ppZde0rZt2zRvXsMaKisr9eyzz+qVV17Rrl279OWXX+rRRx917F+6dKlee+01rVmzRnv27FF5ebk2btzY4BxZWVl6+eWXtXr1ah06dEiPPPKIpk6dqp07d7pcf1lZmSwWiyIjI1t8DS7LQIuUlZUZkoyysjKzSwEA+Ni3335rHD582Pj222+v/GQ7lhrGonDD+E3Xuj93LL3yc15CSkqKMXDgwMset2HDBqNLly6O7TVr1hiSjGPHjjnarFarER0d7diOjo42nnnmGcd2bW2t0atXL+PHP/6xYRiGUVVVZXTs2NH429/+1uCz7rvvPuPee+91qf5vv/3WGDRokDFlypRLHtPc34+r929eVgsAgJlS5km7npFsNVJwiFd7juoNHjy4UduWLVuUlZWlI0eOqLy8XLW1taqqqlJlZaU6duwoSerYsaP69u3r+J3Y2FidOnVKUl2vTklJiZKSkhz7g4ODNXjwYNntdknSsWPHVFlZqdGjRzf47JqaGg0cOPCydV+4cEGTJk2SYRhatWqV+1/cDQQkAADMtHPZd+HIVlO37eWQ1KlTpwbbn3/+uX70ox9p1qxZWrJkiaKiorR7927dd999qqmpcQSk9u3bN/g9i8UiwzBc/tz6MU2bNm1Sjx49GuwLDQ295O/Wh6MvvvhC27ZtU3h4uMuf2xIEJAAAzOI85qh+W/JJT1K9vLw82e12PffccwoKqhue/Oabb7p1joiICEVHR2vfvn26/fbbJUk2m035+flKTEyUJMXHxys0NFRffvmlUlJSXD53fTj6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y5UGNnevc72zZ89q2rRp+uEPf+ilylqXllznSZMmaevWrXrhhRd09OhRrVu3Tv369fNila2Du9d6z549mjZtmu677z4dOnRIGzZsUG5urmbOnOnlSgPXzp07lZGRoU8++UQff/yxLly4oDvuuEMVFRXN/s7f/vY33Xvvvbrvvvv06aefauLEiZo4caIOHjzovUINmEaS8c4771zymHnz5hk33XRTg7bJkycbY8aM8WJlrYsr17kp8fHxxuLFiz1fUCvlznWePHmy8atf/cpYtGiRkZCQ4NW6WhtXrvOHH35oREREGGfOnPFNUa2UK9f6mWeeMa699toGbc8//7zRo0cPL1bWupw6dcqQZOzcubPZYyZNmmRMmDChQduwYcOMX/7yl16rix4kP7d3716lpqY2aBszZoz27t1rUkVtg91u17lz5xQVFWV2Ka3OmjVr9J///EeLFi0yu5RW691339WQIUO0bNky9ejRQzfccIMeffRRffvtt2aX1uokJyfr+PHj+uCDD2QYhkpKSvTWW29p/PjxZpcWMMrKyiTpkv+9NeNeaPpK2ri04uJiRUdHN2iLjo5WeXm5vv32W3Xo0MGkylq3Z599VufPn9ekSZPMLqVV+de//qX58+frr3/9q9q14z8/3vKf//xHu3fvVlhYmN555x2dPn1aDz74oM6cOaM1a9aYXV6rMnz4cL322muaPHmyqqqqVFtbq7S0NLcfO7dVdrtdc+fO1fDhwzVgwIBmj2vuXlhcXOy12uhBApy8/vrrWrx4sd588011797d7HJaDZvNpilTpmjx4sW64YYbzC6nVbPb7bJYLHrttdeUlJSk8ePHa/ny5XrppZfoRfKww4cPa86cOXryySeVl5ennJwcff7553rggQfMLi0gZGRk6ODBg3rjjTfMLqUR/i+cn4uJiVFJSUmDtpKSEoWHh9N75AVvvPGG7r//fm3YsKFRdy6uzLlz57R//359+umnmj17tqS6G7lhGGrXrp02b96sUaNGmVxl6xAbG6sePXooIiLC0da/f38ZhqGvvvpK119/vYnVtS5ZWVkaPny4HnvsMUnSLbfcok6dOukHP/iBfvvb3yo2NtbkCv3X7Nmz9f7772vXrl26+uqrL3lsc/fCmJgYr9VHD5KfS05O1tatWxu0ffzxx0pOTjapotZr3bp1mj59utatW6cJEyaYXU6rEx4ergMHDqigoMDx88ADD6hfv34qKCjg/YoeNHz4cJ08eVLnz593tP3zn/9UUFDQZW9EcE9lZaWCghreSoODgyVJBm/yapJhGJo9e7beeecdbdu2TX369Lns75hxL6QHycfOnz+vY8eOObYLCwtVUFCgqKgo9erVSwsWLNCJEyf08ssvS5IeeOABZWdna968eZoxY4a2bdumN998U5s2bTLrKwQEd6/z66+/rvT0dP3+97/XsGHDHM+1O3To0OD/haMhd65zUFBQozEG3bt3V1hY2CXHHsD9f56nTJmip556StOnT9fixYt1+vRpPfbYY5oxYwY9z5fh7rVOS0vTzJkztWrVKo0ZM0ZFRUWaO3eukpKSFBcXZ9bX8GsZGRl6/fXX9Ze//EWdO3d2/Pc2IiLC8c/ntGnT1KNHD2VlZUmS5syZo5SUFD333HOaMGGC3njjDe3fv19//OMfvVeo1+bHoUnbt283JDX6SU9PNwzDMNLT042UlJRGv5OYmGiEhIQY1157rbFmzRqf1x1o3L3OKSkplzweTWvJP88XY5q/a1pynT/77DMjNTXV6NChg3H11VcbmZmZRmVlpe+LDzAtudbPP/+8ER8fb3To0MGIjY01fvrTnxpfffWV74sPEE1dX0kN7m0pKSmN/vv75ptvGjfccIMREhJi3HTTTcamTZu8Wqflv8UCAADgvxiDBAAA4ISABAAA4ISABAAA4ISABAAA4ISABAAA4ISABAAA4ISABAAA4ISABAAA4ISABAAA4ISABAAA4ISABAAA4ISABAD/tW7dOnXo0EFFRUWOtunTp+uWW25RWVmZiZUB8DVeVgsA/2UYhhITE3X77bdr5cqVWrRokV588UV98skn6tGjh9nlAfChdmYXAAD+wmKxaMmSJbrnnnsUExOjlStX6q9//SvhCGiD6EECACeDBg3SoUOHtHnzZqWkpJhdDgATMAYJAC6Sk5OjI0eOyGazKTo62uxyAJiEHiQA+K/8/HyNGDFCf/jDH7R27VqFh4drw4YNZpcFwASMQQIASZ9//rkmTJigJ554Qvfee6+uvfZaJScnKz8/X4MGDTK7PAA+Rg8SgDavtLRU3//+9zVixAitXr3a0T5hwgTZbDbl5OSYWB0AMxCQAAAAnDBIGwAAwAkBCQAAwAkBCQAAwAkBCQAAwAkBCQAAwAkBCQAAwAkBCQAAwAkBCQAAwAkBCQAAwAkBCQAAwAkBCQAAwMn/B43Z6D8yasHtAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "plt.plot(xs_one, np.abs(1.0 / xs_one**0.5 - nr_one), marker='o', lw=0, label=\"range 1\")\n", "plt.plot(xs_two, np.abs(1.0 / xs_two**0.5 - nr_two), marker='x', lw=0, label=\"range 2\")\n", @@ -330,9 +439,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "max error = 5.8675108801198306e-05 vs expected error = 3.0517578125e-05\n", + "max error after NR step = 2.5821014215665627e-09\n" + ] + }, + { + "data": { + "image/png": "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SkRERMpjUFIpDr0REREpz+OC0tKlSxEREQGj0YiYmBjs3LmzVsetXr0akiThtttua9wK1hJ7lIiIiJTnUUFpzZo1mDp1KmbPno3U1FT06tULw4cPx6lTp2o87vjx45g2bRoGDx7spppemmOOksweJSIiIqV4VFBatGgRJkyYgPHjxyMqKgrLly+Hj48P3n//fZfHWK1W3HvvvZgzZw46duzoxtrWjENvREREyvOYoFRWVobdu3cjLi7OsU2j0SAuLg4pKSkuj3vxxRfRpk0bPPjgg5d8j9LSUuTl5Tk9GoujRwkMSkRERErxmKB05swZWK1WBAcHO20PDg5GdnZ2tcds27YN7733HlasWFGr90hISIDJZHI8wsPD611vVxxzlGTOUSIiIlKKxwSlusrPz8f999+PFStWICgoqFbHzJgxA2az2fE4ceJEo9WPQ29ERETK0yldgYYSFBQErVaLnJwcp+05OTkICQmpUv7o0aM4fvw4Ro4c6dhmnzit0+lw6NAhdOrUyekYg8EAg8HQCLWvigtOEhERKc9jepT0ej369euHzZs3O7bJsozNmzcjNja2Svlu3bph7969SEtLczxuvfVWXHvttUhLS2vUYbXa0GpsPUpcHoCIiEg5HtOjBABTp07FuHHj0L9/fwwYMACLFy9GYWEhxo8fDwAYO3YswsLCkJCQAKPRiOjoaKfjAwMDAaDKdiVIkACwR4mIiEhJHhWURo8ejdOnT+P5559HdnY2evfujcTERMcE78zMTGg0TaMTjXOUiIiIlCcJIYTSlWiq8vLyYDKZYDabERAQ0KDn/jXrV0zYMAGdAzvjq1FfNei5iYiImrO6/P5uGt0rzRB7lIiIiJTHoKRSvOqNiIhIeQxKKsWgREREpDwGJZVyrMzN5QGIiIgUw6CkUpyjREREpDwGJZVijxIREZHyGJRUij1KREREymNQUilO5iYiIlIeg5JKMSgREREpj0FJpThHiYiISHkMSirFOUpERETKY1BSKyEBAMqsFqQcPQurzFvyERERuRuDkgolpmfhrrd3AADKLFaMWfErBs3fgsT0LIVrRkRE1LwwKKlMYnoWJn6cilN5ZbYNkq0nKdtcgokfpzIsERERuRGDkopYZYE53+6HLRpJFVttc5TsA29zvt3PYTgiIiI3YVBSkZ0Zucgyl9heCNtHI0kC9pgkAGSZ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yAUGJSIiIiIXGJRUymAwYPbs2TAYDEpXxa2aa7sBtr05tr25thtg25tj25tquzmZm4iIiMgF9igRERERucCgREREROQCgxIRERGRCwxKRERERC4wKKnQ0qVLERERAaPRiJiYGOzcuVPpKtXLCy+8AEmSnB7dunVz7C8pKcGkSZPQqlUr+Pn54f/+7/+Qk5PjdI7MzEzccsst8PHxQZs2bfDUU0/BYrG4uymXtHXrVowcORJt27aFJElYt26d034hBJ5//nmEhobC29sbcXFxOHz4sFOZ3Nxc3HvvvQgICEBgYCAefPBBFBQUOJXZs2cPBg8eDKPRiPDwcCxYsKCxm3ZJl2r7Aw88UOXnID4+3qlMU2x7QkICrrrqKvj7+6NNmza47bbbcOjQIacyDfUznpSUhL59+8JgMKBz585YtWpVYzevRrVp+7Bhw6p87g8//LBTmabW9mXLlqFnz56OhRNjY2Pxww8/OPZ76ucNXLrtnvh5Q5CqrF69Wuj1evH++++Lffv2iQkTJojAwECRk5OjdNUu2+zZs8WVV14psrKyHI/Tp0879j/88MMiPDxcbN68WezatUtcffXV4pprrnHst1gsIjo6WsTFxYnff/9dfP/99yIoKEjMmDFDiebU6PvvvxfPPvus+PLLLwUA8dVXXzntf+WVV4TJZBLr1q0Tf/zxh7j11ltFZGSkKC4udpSJj48XvXr1Er/++qv4+eefRefOncWYMWMc+81mswgODhb33nuvSE9PF5999pnw9vYWb7/9truaWa1LtX3cuHEiPj7e6ecgNzfXqUxTbPvw4cPFypUrRXp6ukhLSxM333yzaN++vSgoKHCUaYif8WPHjgkfHx8xdepUsX//frFkyRKh1WpFYmKiW9tbWW3aPnToUDFhwgSnz91sNjv2N8W2f/PNN2L9+vXizz//FIcOHRIzZ84UXl5eIj09XQjhuZ+3EJduuyd+3gxKKjNgwAAxadIkx2ur1Sratm0rEhISFKxV/cyePVv06tWr2n3nz58XXl5e4osvvnBsO3DggAAgUlJShBC2X8AajUZkZ2c7yixbtkwEBASI0tLSRq17fVwcFmRZFiEhIeLVV191bDt//rwwGAzis88+E0IIsX//fgFA/Pbbb44yP/zwg5AkSfzzzz9CCCHeeust0aJFC6e2T58+XXTt2rWRW1R7roLSqFGj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FxiUiIiIiFxgUCIiIiJygUGJiIiIyAUGJSIiIiIXGJSIiIiIXGBQIiIiInKBQYmIiIjIBQYlIiIiIhcYlIiIanDixAkMGzYMUVFR6NmzJ7744gulq0REbsRbmBAR1SArKws5OTno3bs3srOz0a9fP/z555/w9fVVumpE5AY6pStARKRmoaGhCA0NBQCEhIQgKCgIubm5DEpEzQSH3oiIamn37t2wWq0IDw9XuipE5CYMSkRENbBYLACA3NxcjB07Fu+8847CNSIid2JQIiKqcPz4cUiShM8//xyDBw+GwWDAN998g9LSUtx222145plncM011yhdTSJyI85RIiKq8McffwAAXn31VcybNw+RkZFo3bo1HnjgAVx33XW4//77Fa4hEbkbgxIRUYW0tDT4+vriiy++QEREBABg27ZtWLNmDXr27Il169YBAD766CP06NFDuYoSkdtweQAiogp33HEHjEYjPv30U6WrQkQqwTlKREQV0tLSMGzYMKdtW7ZswcKFCwEAv/zyC15++WUFakZESmGPEhERgLy8PAQGBmLHjh246qqrlK4OEakEe5SIiGCbyK3VaqvMPbr11luxd+9eAMBdd92FXbt2KVE9IlIIgxIREWxBqWvXrjAajU7bDx48iG7dugEA9u3bhyuvvFKJ6hGRQhiUiIgATJ48Genp6U7b8vPzYTQa4eXlhaKiIkiSBG9vb4VqSERKYFAiInKhcg9Seno6e5OImiEGJSIiF/bu3euYs7Rnzx707NlT4RoRkbsxKBERubB3715ER0cDYFAiaq64PAARUS307dsX69evR2hoqNJVISI3Yo8SEVENysrK0LdvX8THxzMkETVD7FEiIiIicoE9SkREREQuMCgRERERucCgREREROQCgxIRERGRCwxKRERERC4wKBERERG5wKBERERE5AKDEhEREZELDEpERERELjAoEREREbnAoERERETkAoMSERERkQsMSkREREQu/H/iEvE6lDsYEQAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "from typing import Union\n", "from numpy.typing import NDArray\n", @@ -441,9 +569,42 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "L = 16384\n", + "l = 00000000000100, range = [ 4, 5], length = 2 3\n", + "l = 00000000000110, range = [ 6, 7], length = 2 3\n", + "l = 00000000001000, range = [ 8, 11], length = 4 4\n", + "l = 00000000001100, range = [ 12, 15], length = 4 4\n", + "l = 00000000010000, range = [ 16, 23], length = 8 5\n", + "l = 00000000011000, range = [ 24, 31], length = 8 5\n", + "l = 00000000100000, range = [ 32, 47], length = 16 6\n", + "l = 00000000110000, range = [ 48, 63], length = 16 6\n", + "l = 00000001000000, range = [ 64, 95], length = 32 7\n", + "l = 00000001100000, range = [ 96, 127], length = 32 7\n", + "l = 00000010000000, range = [ 128, 191], length = 64 8\n", + "l = 00000011000000, range = [ 192, 255], length = 64 8\n", + "l = 00000100000000, range = [ 256, 383], length = 128 9\n", + "l = 00000110000000, range = [ 384, 511], length = 128 9\n", + "l = 00001000000000, range = [ 512, 767], length = 256 10\n", + "l = 00001100000000, range = [ 768, 1023], length = 256 10\n", + "l = 00010000000000, range = [ 1024, 1535], length = 512 11\n", + "l = 00011000000000, range = [ 1536, 2047], length = 512 11\n", + "l = 00100000000000, range = [ 2048, 3071], length = 1024 12\n", + "l = 00110000000000, range = [ 3072, 4095], length = 1024 12\n", + "l = 01000000000000, range = [ 4096, 6143], length = 2048 13\n", + "l = 01100000000000, range = [ 6144, 8191], length = 2048 13\n", + "l = 10000000000000, range = [ 8192, 12287], length = 4096 14\n", + "l = 11000000000000, range = [12288, 16383], length = 4096 14\n", + "number of distinct regions g = 28. Toffoli cost g - 2 = 26\n" + ] + } + ], "source": [ "nbits = 6\n", "nbits_rij_sq = 2 * nbits + 2\n", @@ -504,9 +665,189 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "my_graph\n", + "\n", + "\n", + "\n", + "system_G0\n", + "system[0, 0]\n", + "\n", + "\n", + "\n", + "KineticEnergy\n", + "\n", + "KineticEnergy\n", + "\n", + "system[0, 0]\n", + "\n", + "system[0, 1]\n", + "\n", + "system[0, 2]\n", + "\n", + "system[1, 0]\n", + "\n", + "system[1, 1]\n", + "\n", + "system[1, 2]\n", + "\n", + "\n", + "\n", + "system_G0:e->KineticEnergy:w\n", + "\n", + "\n", + "6\n", + "\n", + "\n", + "\n", + "system_G6\n", + "system[0, 1]\n", + "\n", + "\n", + "\n", + "system_G6:e->KineticEnergy:w\n", + "\n", + "\n", + "6\n", + "\n", + "\n", + "\n", + "system_G2\n", + "system[0, 2]\n", + "\n", + "\n", + "\n", + "system_G2:e->KineticEnergy:w\n", + "\n", + "\n", + "6\n", + "\n", + "\n", + "\n", + "system_G12\n", + "system[1, 0]\n", + "\n", + "\n", + "\n", + "system_G12:e->KineticEnergy:w\n", + "\n", + "\n", + "6\n", + "\n", + "\n", + "\n", + "system_G16\n", + "system[1, 1]\n", + "\n", + "\n", + "\n", + "system_G16:e->KineticEnergy:w\n", + "\n", + "\n", + "6\n", + "\n", + "\n", + "\n", + "system_G3\n", + "system[1, 2]\n", + "\n", + "\n", + "\n", + "system_G3:e->KineticEnergy:w\n", + "\n", + "\n", + "6\n", + "\n", + "\n", + "\n", + "system_G9\n", + "system[0, 0]\n", + "\n", + "\n", + "\n", + "KineticEnergy:e->system_G9:w\n", + "\n", + "\n", + "6\n", + "\n", + "\n", + "\n", + "system_G15\n", + "system[0, 1]\n", + "\n", + "\n", + "\n", + "KineticEnergy:e->system_G15:w\n", + "\n", + "\n", + "6\n", + "\n", + "\n", + "\n", + "system_G13\n", + "system[0, 2]\n", + "\n", + "\n", + "\n", + "KineticEnergy:e->system_G13:w\n", + "\n", + "\n", + "6\n", + "\n", + "\n", + "\n", + "system_G1\n", + "system[1, 0]\n", + "\n", + "\n", + "\n", + "KineticEnergy:e->system_G1:w\n", + "\n", + "\n", + "6\n", + "\n", + "\n", + "\n", + "system_G5\n", + "system[1, 1]\n", + "\n", + "\n", + "\n", + "KineticEnergy:e->system_G5:w\n", + "\n", + "\n", + "6\n", + "\n", + "\n", + "\n", + "system_G11\n", + "system[1, 2]\n", + "\n", + "\n", + "\n", + "KineticEnergy:e->system_G11:w\n", + "\n", + "\n", + "6\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "from qualtran.bloqs.chemistry.trotter.grid_ham import KineticEnergy \n", "from qualtran.drawing import show_bloq\n", @@ -518,9 +859,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "T-count: 808\n", + "Rotations: 28\n", + "Cliffords: 0\n", + "\n" + ] + } + ], "source": [ "print(ke_bloq.t_complexity())" ] @@ -541,9 +893,199 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "my_graph\n", + "\n", + "\n", + "\n", + "system\n", + "system[0, 0]\n", + "\n", + "\n", + "\n", + "PairPotential\n", + "\n", + "PairPotential\n", + "\n", + "system_i[0]\n", + "\n", + "system_i[1]\n", + "\n", + "system_i[2]\n", + "\n", + "system_j[0]\n", + "\n", + "system_j[1]\n", + "\n", + "system_j[2]\n", + "\n", + "\n", + "\n", + "system:e->PairPotential:w\n", + "\n", + "\n", + "6\n", + "\n", + "\n", + "\n", + "system_G6\n", + "system[0, 1]\n", + "\n", + "\n", + "\n", + "system_G6:e->PairPotential:w\n", + "\n", + "\n", + "6\n", + "\n", + "\n", + "\n", + "system_G2\n", + "system[0, 2]\n", + "\n", + "\n", + "\n", + "system_G2:e->PairPotential:w\n", + "\n", + "\n", + "6\n", + "\n", + "\n", + "\n", + "system_G9\n", + "system[1, 0]\n", + "\n", + "\n", + "\n", + "system_G9:e->PairPotential:w\n", + "\n", + "\n", + "6\n", + "\n", + "\n", + "\n", + "system_G13\n", + "system[1, 1]\n", + "\n", + "\n", + "\n", + "system_G13:e->PairPotential:w\n", + "\n", + "\n", + "6\n", + "\n", + "\n", + "\n", + "system_G3\n", + "system[1, 2]\n", + "\n", + "\n", + "\n", + "system_G3:e->PairPotential:w\n", + "\n", + "\n", + "6\n", + "\n", + "\n", + "\n", + "system_G7\n", + "system[0, 0]\n", + "\n", + "\n", + "\n", + "PairPotential:e->system_G7:w\n", + "\n", + "\n", + "6\n", + "\n", + "\n", + "\n", + "system_G15\n", + "system[0, 1]\n", + "\n", + "\n", + "\n", + "PairPotential:e->system_G15:w\n", + "\n", + "\n", + "6\n", + "\n", + "\n", + "\n", + "system_G11\n", + "system[0, 2]\n", + "\n", + "\n", + "\n", + "PairPotential:e->system_G11:w\n", + "\n", + "\n", + "6\n", + "\n", + "\n", + "\n", + "system_G1\n", + "system[1, 0]\n", + "\n", + "\n", + "\n", + "PairPotential:e->system_G1:w\n", + "\n", + "\n", + "6\n", + "\n", + "\n", + "\n", + "system_G5\n", + "system[1, 1]\n", + "\n", + "\n", + "\n", + "PairPotential:e->system_G5:w\n", + "\n", + "\n", + "6\n", + "\n", + "\n", + "\n", + "system_G8\n", + "system[1, 2]\n", + "\n", + "\n", + "\n", + "PairPotential:e->system_G8:w\n", + "\n", + "\n", + "6\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "T-count: 9176\n", + "Rotations: 24\n", + "Cliffords: 2646\n", + "\n" + ] + } + ], "source": [ "from qualtran.bloqs.chemistry.trotter.grid_ham import PotentialEnergy\n", "from qualtran.drawing import show_bloq, show_call_graph\n", @@ -564,9 +1106,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "T-count: 9176\n", + "Rotations: 24\n", + "Cliffords: 2646\n", + "\n" + ] + } + ], "source": [ "from qualtran.bloqs.chemistry.trotter.grid_ham import PairPotential\n", "from qualtran.bloqs.chemistry.trotter.grid_ham.inverse_sqrt import build_qrom_data_for_poly_fit\n", @@ -591,7 +1144,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -599,7 +1152,6 @@ "import cirq\n", "\n", "from qualtran.resource_counting import SympySymbolAllocator\n", - "from qualtran.drawing import GraphvizCounts\n", "from qualtran.bloqs.bookkeeping import Split, Join, Allocate, Free\n", "from qualtran.bloqs.basic_gates.rotation import Rx, Ry, Rz\n", "from qualtran.cirq_interop import CirqGateAsBloq\n", @@ -640,9 +1192,158 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "counts\n", + "\n", + "\n", + "\n", + "b0\n", + "\n", + "y ~ x^{-1/2}\n", + "x_sq_bitsize=14, poly_bitsize=15, out_bitsize=24\n", + "\n", + "\n", + "\n", + "b1\n", + "\n", + "Add\n", + "a_dtype=QInt(bit ..., b_dtype=QInt(bit ...\n", + "\n", + "\n", + "\n", + "b0->b1\n", + "\n", + "\n", + "3\n", + "\n", + "\n", + "\n", + "b2\n", + "\n", + "a*b\n", + "bitsize=15\n", + "\n", + "\n", + "\n", + "b0->b2\n", + "\n", + "\n", + "3\n", + "\n", + "\n", + "\n", + "b3\n", + "\n", + "And†\n", + "cv1=1, cv2=1, uncompute=True\n", + "\n", + "\n", + "\n", + "b1->b3\n", + "\n", + "\n", + "14\n", + "\n", + "\n", + "\n", + "b4\n", + "\n", + "And\n", + "cv1=1, cv2=1, uncompute=False\n", + "\n", + "\n", + "\n", + "b1->b4\n", + "\n", + "\n", + "14\n", + "\n", + "\n", + "\n", + "b5\n", + "\n", + "CNOT\n", + "\n", + "\n", + "\n", + "b1->b5\n", + "\n", + "\n", + "81\n", + "\n", + "\n", + "\n", + "b6\n", + "\n", + "Toffoli\n", + "\n", + "\n", + "\n", + "b2->b6\n", + "\n", + "\n", + "209\n", + "\n", + "\n", + "\n", + "b7\n", + "\n", + "ArbitraryClifford\n", + "n=2\n", + "\n", + "\n", + "\n", + "b3->b7\n", + "\n", + "\n", + "4\n", + "\n", + "\n", + "\n", + "b4->b7\n", + "\n", + "\n", + "9\n", + "\n", + "\n", + "\n", + "b8\n", + "\n", + "T\n", + "is_adjoint=False\n", + "\n", + "\n", + "\n", + "b4->b8\n", + "\n", + "\n", + "4\n", + "\n", + "\n", + "\n", + "b6->b8\n", + "\n", + "\n", + "4\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "from qualtran.bloqs.chemistry.trotter.grid_ham.inverse_sqrt import PolynmomialEvaluationInverseSquareRoot \n", "poly_eval = PolynmomialEvaluationInverseSquareRoot(14, 15, 24)\n", @@ -652,9 +1353,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "qualtran cost = 2676 vs paper_cost = 2880 T gates\n" + ] + } + ], "source": [ "paper_cost = 4 * (3*15**2 + 45)\n", "print(f\"qualtran cost = {poly_eval.t_complexity().t} vs paper_cost = {paper_cost} T gates\")" @@ -662,9 +1371,200 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "counts\n", + "\n", + "\n", + "\n", + "b0\n", + "\n", + "y = x^{-1/2}\n", + "x_sq_bitsize=14, poly_bitsize=15, target_bitsize=24\n", + "\n", + "\n", + "\n", + "b1\n", + "\n", + "a^2\n", + "bitsize=15\n", + "\n", + "\n", + "\n", + "b0->b1\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b2\n", + "\n", + "r*i\n", + "r_bitsize=15, i_bitsize=14\n", + "\n", + "\n", + "\n", + "b0->b2\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b3\n", + "\n", + "Add\n", + "a_dtype=QInt(bit ..., b_dtype=QInt(bit ...\n", + "\n", + "\n", + "\n", + "b0->b3\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b4\n", + "\n", + "a*b\n", + "bitsize=24\n", + "\n", + "\n", + "\n", + "b0->b4\n", + "\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "b8\n", + "\n", + "Toffoli\n", + "\n", + "\n", + "\n", + "b1->b8\n", + "\n", + "\n", + "108\n", + "\n", + "\n", + "\n", + "b2->b8\n", + "\n", + "\n", + "209\n", + "\n", + "\n", + "\n", + "b5\n", + "\n", + "CNOT\n", + "\n", + "\n", + "\n", + "b3->b5\n", + "\n", + "\n", + "135\n", + "\n", + "\n", + "\n", + "b6\n", + "\n", + "And†\n", + "cv1=1, cv2=1, uncompute=True\n", + "\n", + "\n", + "\n", + "b3->b6\n", + "\n", + "\n", + "23\n", + "\n", + "\n", + "\n", + "b7\n", + "\n", + "And\n", + "cv1=1, cv2=1, uncompute=False\n", + "\n", + "\n", + "\n", + "b3->b7\n", + "\n", + "\n", + "23\n", + "\n", + "\n", + "\n", + "b4->b8\n", + "\n", + "\n", + "551\n", + "\n", + "\n", + "\n", + "b9\n", + "\n", + "ArbitraryClifford\n", + "n=2\n", + "\n", + "\n", + "\n", + "b6->b9\n", + "\n", + "\n", + "4\n", + "\n", + "\n", + "\n", + "b7->b9\n", + "\n", + "\n", + "9\n", + "\n", + "\n", + "\n", + "b10\n", + "\n", + "T\n", + "is_adjoint=False\n", + "\n", + "\n", + "\n", + "b7->b10\n", + "\n", + "\n", + "4\n", + "\n", + "\n", + "\n", + "b8->b10\n", + "\n", + "\n", + "4\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "from qualtran.bloqs.chemistry.trotter.grid_ham.inverse_sqrt import NewtonRaphsonApproxInverseSquareRoot\n", "nr = NewtonRaphsonApproxInverseSquareRoot(14, 15, 24)\n", @@ -674,9 +1574,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 19, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "qualtran cost = 5768 vs paper cost = 5664 T gates\n" + ] + } + ], "source": [ "paper_cost = 4 * (2136 - 3*15**2 - 45)\n", "print(f\"qualtran cost = {nr.t_complexity().t} vs paper cost = {paper_cost} T gates\")" @@ -684,9 +1592,103 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 20, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "counts\n", + "\n", + "\n", + "\n", + "b0\n", + "\n", + "U_T(dt)\n", + "num_elec=4, num_grid=21\n", + "\n", + "\n", + "\n", + "b1\n", + "\n", + "e^{i*phi}\n", + "phi_bitsize=14\n", + "\n", + "\n", + "\n", + "b0->b1\n", + "\n", + "\n", + "4\n", + "\n", + "\n", + "\n", + "b2\n", + "\n", + "SOS\n", + "bitsize=6, k=3\n", + "\n", + "\n", + "\n", + "b0->b2\n", + "\n", + "\n", + "4\n", + "\n", + "\n", + "\n", + "b3\n", + "\n", + "Rz\n", + "angle=_phi0, eps=1e-11\n", + "\n", + "\n", + "\n", + "b1->b3\n", + "\n", + "\n", + "14\n", + "\n", + "\n", + "\n", + "b4\n", + "\n", + "Toffoli\n", + "\n", + "\n", + "\n", + "b2->b4\n", + "\n", + "\n", + "101\n", + "\n", + "\n", + "\n", + "b5\n", + "\n", + "T\n", + "is_adjoint=False\n", + "\n", + "\n", + "\n", + "b4->b5\n", + "\n", + "\n", + "4\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "from qualtran.bloqs.chemistry.trotter.grid_ham import KineticEnergy \n", "ke = KineticEnergy(4, 21)\n", @@ -696,9 +1698,500 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 21, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "counts\n", + "\n", + "\n", + "\n", + "b0\n", + "\n", + "PotentialEnergy\n", + "num_elec=4, num_grid=21, poly_bitsize=15, label='V'\n", + "\n", + "\n", + "\n", + "b1\n", + "\n", + "PairPotential\n", + "bitsize=6, qrom_data=((0, 327 ..., poly_bitsize=15, inv_sqrt_bitsize=24, label='V'\n", + "\n", + "\n", + "\n", + "b0->b1\n", + "\n", + "\n", + "6\n", + "\n", + "\n", + "\n", + "b2\n", + "\n", + "Cast\n", + "inp_dtype=BQUInt(b ..., out_dtype=QUInt(bi ..., shape=()\n", + "\n", + "\n", + "\n", + "b1->b2\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b3\n", + "\n", + "c = a + b\n", + "bitsize=6, is_adjoint=False\n", + "\n", + "\n", + "\n", + "b1->b3\n", + "\n", + "\n", + "3\n", + "\n", + "\n", + "\n", + "b4\n", + "\n", + "y = x^{-1/2}\n", + "x_sq_bitsize=16, poly_bitsize=15, target_bitsize=24\n", + "\n", + "\n", + "\n", + "b1->b4\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b5\n", + "\n", + "e^{i*phi}\n", + "phi_bitsize=24\n", + "\n", + "\n", + "\n", + "b1->b5\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b6\n", + "\n", + "y ~ x^{-1/2}\n", + "x_sq_bitsize=16, poly_bitsize=15, out_bitsize=15\n", + "\n", + "\n", + "\n", + "b1->b6\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b7\n", + "\n", + "Cast\n", + "inp_dtype=QUInt(bi ..., out_dtype=BQUInt(b ..., shape=()\n", + "\n", + "\n", + "\n", + "b1->b7\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b8\n", + "\n", + "c = a + b\n", + "bitsize=6, is_adjoint=True\n", + "\n", + "\n", + "\n", + "b1->b8\n", + "\n", + "\n", + "3\n", + "\n", + "\n", + "\n", + "b9\n", + "\n", + "QROM\n", + "data_or_shape=(array([ ..., selection_bitsizes=(16,), target_bitsizes=(15, 15, ..., target_shapes=((), (), ..., num_controls=0\n", + "\n", + "\n", + "\n", + "b1->b9\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b10\n", + "\n", + "SOS\n", + "bitsize=7, k=3\n", + "\n", + "\n", + "\n", + "b1->b10\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b21\n", + "\n", + "And\n", + "cv1=1, cv2=1, uncompute=False\n", + "\n", + "\n", + "\n", + "b3->b21\n", + "\n", + "\n", + "6\n", + "\n", + "\n", + "\n", + "b22\n", + "\n", + "CNOT\n", + "\n", + "\n", + "\n", + "b3->b22\n", + "\n", + "\n", + "30\n", + "\n", + "\n", + "\n", + "b11\n", + "\n", + "a^2\n", + "bitsize=15\n", + "\n", + "\n", + "\n", + "b4->b11\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b12\n", + "\n", + "a*b\n", + "bitsize=24\n", + "\n", + "\n", + "\n", + "b4->b12\n", + "\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "b13\n", + "\n", + "r*i\n", + "r_bitsize=15, i_bitsize=16\n", + "\n", + "\n", + "\n", + "b4->b13\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b14\n", + "\n", + "Add\n", + "a_dtype=QInt(bit ..., b_dtype=QInt(bit ...\n", + "\n", + "\n", + "\n", + "b4->b14\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b15\n", + "\n", + "Rz\n", + "angle=_phi0, eps=1e-11\n", + "\n", + "\n", + "\n", + "b5->b15\n", + "\n", + "\n", + "24\n", + "\n", + "\n", + "\n", + "b16\n", + "\n", + "Add\n", + "a_dtype=QInt(bit ..., b_dtype=QInt(bit ...\n", + "\n", + "\n", + "\n", + "b6->b16\n", + "\n", + "\n", + "3\n", + "\n", + "\n", + "\n", + "b17\n", + "\n", + "a*b\n", + "bitsize=15\n", + "\n", + "\n", + "\n", + "b6->b17\n", + "\n", + "\n", + "3\n", + "\n", + "\n", + "\n", + "b20\n", + "\n", + "And†\n", + "cv1=1, cv2=1, uncompute=True\n", + "\n", + "\n", + "\n", + "b8->b20\n", + "\n", + "\n", + "6\n", + "\n", + "\n", + "\n", + "b8->b22\n", + "\n", + "\n", + "30\n", + "\n", + "\n", + "\n", + "b18\n", + "\n", + "XGate\n", + "\n", + "\n", + "\n", + "b9->b18\n", + "\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "b19\n", + "\n", + "And\n", + "cv1=1, cv2=0, uncompute=False\n", + "\n", + "\n", + "\n", + "b9->b19\n", + "\n", + "\n", + "26\n", + "\n", + "\n", + "\n", + "b9->b20\n", + "\n", + "\n", + "26\n", + "\n", + "\n", + "\n", + "b9->b22\n", + "\n", + "\n", + "617\n", + "\n", + "\n", + "\n", + "b23\n", + "\n", + "Toffoli\n", + "\n", + "\n", + "\n", + "b10->b23\n", + "\n", + "\n", + "139\n", + "\n", + "\n", + "\n", + "b11->b23\n", + "\n", + "\n", + "108\n", + "\n", + "\n", + "\n", + "b12->b23\n", + "\n", + "\n", + "551\n", + "\n", + "\n", + "\n", + "b13->b23\n", + "\n", + "\n", + "209\n", + "\n", + "\n", + "\n", + "b14->b20\n", + "\n", + "\n", + "23\n", + "\n", + "\n", + "\n", + "b14->b21\n", + "\n", + "\n", + "23\n", + "\n", + "\n", + "\n", + "b14->b22\n", + "\n", + "\n", + "135\n", + "\n", + "\n", + "\n", + "b16->b20\n", + "\n", + "\n", + "14\n", + "\n", + "\n", + "\n", + "b16->b21\n", + "\n", + "\n", + "14\n", + "\n", + "\n", + "\n", + "b16->b22\n", + "\n", + "\n", + "81\n", + "\n", + "\n", + "\n", + "b17->b23\n", + "\n", + "\n", + "209\n", + "\n", + "\n", + "\n", + "b24\n", + "\n", + "ArbitraryClifford\n", + "n=2\n", + "\n", + "\n", + "\n", + "b19->b24\n", + "\n", + "\n", + "11\n", + "\n", + "\n", + "\n", + "b25\n", + "\n", + "T\n", + "is_adjoint=False\n", + "\n", + "\n", + "\n", + "b19->b25\n", + "\n", + "\n", + "4\n", + "\n", + "\n", + "\n", + "b20->b24\n", + "\n", + "\n", + "4\n", + "\n", + "\n", + "\n", + "b21->b24\n", + "\n", + "\n", + "9\n", + "\n", + "\n", + "\n", + "b21->b25\n", + "\n", + "\n", + "4\n", + "\n", + "\n", + "\n", + "b23->b25\n", + "\n", + "\n", + "4\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "from qualtran.bloqs.chemistry.trotter.grid_ham import PotentialEnergy \n", "pe = PotentialEnergy(4, 21)\n", @@ -715,9 +2208,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 22, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "qualtran_cost = 7928 vs paper_cost = 9052\n" + ] + } + ], "source": [ "from qualtran.bloqs.chemistry.trotter.grid_ham import PairPotential\n", "nbits = 6\n", @@ -761,7 +2262,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "qualtran", "language": "python", "name": "python3" }, @@ -775,9 +2276,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.5" + "version": "3.11.9" } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/qualtran/bloqs/chemistry/trotter/hubbard/hopping.py b/qualtran/bloqs/chemistry/trotter/hubbard/hopping.py index b5806ed41..7e9a93856 100644 --- a/qualtran/bloqs/chemistry/trotter/hubbard/hopping.py +++ b/qualtran/bloqs/chemistry/trotter/hubbard/hopping.py @@ -174,8 +174,8 @@ class HoppingTileHWP(HoppingTile): system: The system register of size 2 `length`. References: - [Early fault-tolerant simulations of the Hubbard model]( - https://arxiv.org/abs/2012.09238) see Eq. 21 and App E. + [Early fault-tolerant simulations of the Hubbard model](https://arxiv.org/abs/2012.09238). + Eq. 21 and App E. """ def short_name(self) -> str: diff --git a/qualtran/bloqs/chemistry/trotter/hubbard/hopping_test.py b/qualtran/bloqs/chemistry/trotter/hubbard/hopping_test.py index 337ac1adc..652664817 100644 --- a/qualtran/bloqs/chemistry/trotter/hubbard/hopping_test.py +++ b/qualtran/bloqs/chemistry/trotter/hubbard/hopping_test.py @@ -53,6 +53,4 @@ def test_hopping_tile_hwp_t_counts(): _, counts = bloq.call_graph(generalizer=catch_rotations) n_rot_par = bloq.length**2 // 2 assert counts[Rz(PHI)] == 2 * n_rot_par.bit_length() - assert counts[TGate()] == 8 * bloq.length**2 // 2 + 2 * 4 * ( - n_rot_par - n_rot_par.bit_count() - ) + assert counts[TGate()] == 8 * bloq.length**2 // 2 + 2 * 4 * (n_rot_par - n_rot_par.bit_count()) diff --git a/qualtran/bloqs/chemistry/trotter/hubbard/hubbard.ipynb b/qualtran/bloqs/chemistry/trotter/hubbard/hubbard.ipynb index d0f82b9ab..54fab69e6 100644 --- a/qualtran/bloqs/chemistry/trotter/hubbard/hubbard.ipynb +++ b/qualtran/bloqs/chemistry/trotter/hubbard/hubbard.ipynb @@ -463,7 +463,7 @@ " - `system`: The system register of size 2 `length`. \n", "\n", "#### References\n", - " - [Early fault-tolerant simulations of the Hubbard model]( https://arxiv.org/abs/2012.09238) see Eq. 21 and App E.\n" + " - [Early fault-tolerant simulations of the Hubbard model](https://arxiv.org/abs/2012.09238). Eq. 21 and App E.\n" ] }, { @@ -581,7 +581,7 @@ " - `system`: The system register of size 2 `length`. \n", "\n", "#### References\n", - " - [Early fault-tolerant simulations of the Hubbard model]( https://arxiv.org/abs/2012.09238) Eq. page 13 paragraph 1, and page 14 paragraph 3 right column. The apply 2 batches of $L^2/2$ rotations.\n" + " - [Early fault-tolerant simulations of the Hubbard model](https://arxiv.org/abs/2012.09238). Eq. page 13 paragraph 1, and page 14 paragraph 3 right column. They apply 2 batches of $L^2/2$ rotations.\n" ] }, { diff --git a/qualtran/bloqs/chemistry/trotter/hubbard/interaction.py b/qualtran/bloqs/chemistry/trotter/hubbard/interaction.py index 30b515299..9783ee833 100644 --- a/qualtran/bloqs/chemistry/trotter/hubbard/interaction.py +++ b/qualtran/bloqs/chemistry/trotter/hubbard/interaction.py @@ -91,9 +91,9 @@ class InteractionHWP(Bloq): system: The system register of size 2 `length`. References: - [Early fault-tolerant simulations of the Hubbard model]( - https://arxiv.org/abs/2012.09238) Eq. page 13 paragraph 1, and page - 14 paragraph 3 right column. The apply 2 batches of $L^2/2$ rotations. + [Early fault-tolerant simulations of the Hubbard model](https://arxiv.org/abs/2012.09238). + Eq. page 13 paragraph 1, and page 14 paragraph 3 right column. + They apply 2 batches of $L^2/2$ rotations. """ length: SymbolicInt diff --git a/qualtran/bloqs/chemistry/trotter/ising/unitaries.py b/qualtran/bloqs/chemistry/trotter/ising/unitaries.py index b65714305..c9f9b14ce 100644 --- a/qualtran/bloqs/chemistry/trotter/ising/unitaries.py +++ b/qualtran/bloqs/chemistry/trotter/ising/unitaries.py @@ -33,6 +33,7 @@ class IsingXUnitary(Bloq): Registers: system: The system register to apply the unitary to. """ + nsites: int angle: float eps: float = 1e-10 @@ -65,6 +66,7 @@ class IsingZZUnitary(Bloq): Registers: system: The system register to apply the unitary to. """ + nsites: int angle: float eps: float = 1e-10 diff --git a/qualtran/bloqs/chemistry/trotter/trotterized_unitary.ipynb b/qualtran/bloqs/chemistry/trotter/trotterized_unitary.ipynb index 2dbaea6e4..c31c756fd 100644 --- a/qualtran/bloqs/chemistry/trotter/trotterized_unitary.ipynb +++ b/qualtran/bloqs/chemistry/trotter/trotterized_unitary.ipynb @@ -85,8 +85,8 @@ " - `system`: The system register to which to apply the unitary. \n", "\n", "#### References\n", - " - [Theory of Trotter Error with Commutator Scaling]( https://journals.aps.org/prx/abstract/10.1103/PhysRevX.11.011020) Eq. 12 page 7.\n", - " - [Trotter error with commutator scaling for the Fermi-Hubbard model]( https://arxiv.org/abs/2306.10603) see github repo for software to produce splittings.\n" + " - [Theory of Trotter Error with Commutator Scaling](https://journals.aps.org/prx/abstract/10.1103/PhysRevX.11.011020). Eq. 12 page 7.\n", + " - [Trotter error with commutator scaling for the Fermi-Hubbard model](https://arxiv.org/abs/2306.10603). See github repo for software to produce splittings.\n" ] }, { diff --git a/qualtran/bloqs/chemistry/trotter/trotterized_unitary.py b/qualtran/bloqs/chemistry/trotter/trotterized_unitary.py index 91dd0736a..f362ad22e 100644 --- a/qualtran/bloqs/chemistry/trotter/trotterized_unitary.py +++ b/qualtran/bloqs/chemistry/trotter/trotterized_unitary.py @@ -74,11 +74,11 @@ class TrotterizedUnitary(Bloq): system: The system register to which to apply the unitary. References: - [Theory of Trotter Error with Commutator Scaling]( - https://journals.aps.org/prx/abstract/10.1103/PhysRevX.11.011020) Eq. 12 page 7. + [Theory of Trotter Error with Commutator Scaling](https://journals.aps.org/prx/abstract/10.1103/PhysRevX.11.011020) + Eq. 12 page 7. - [Trotter error with commutator scaling for the Fermi-Hubbard model]( - https://arxiv.org/abs/2306.10603) see github repo for software to produce splittings. + [Trotter error with commutator scaling for the Fermi-Hubbard model](https://arxiv.org/abs/2306.10603). + See github repo for software to produce splittings. """ bloqs: Sequence[Bloq] diff --git a/qualtran/bloqs/data_loading/qroam_clean.ipynb b/qualtran/bloqs/data_loading/qroam_clean.ipynb index 2d5652cfc..372bf3407 100644 --- a/qualtran/bloqs/data_loading/qroam_clean.ipynb +++ b/qualtran/bloqs/data_loading/qroam_clean.ipynb @@ -184,7 +184,7 @@ " - `- junk_registers`: $K - 1$ RIGHT registers, each of bitsize $b$ used to load batches of size $K$ \n", "\n", "#### References\n", - " - [Qubitization of Arbitrary Basis Quantum Chemistry Leveraging Sparsity and Low Rank Factorization](https://arxiv.org/abs/1902.02134). Berry et. al. (2019). Appendix A. and B.\n" + " - [Qubitization of Arbitrary Basis Quantum Chemistry Leveraging Sparsity and Low Rank Factorization](https://arxiv.org/abs/1902.02134). Berry et al. (2019). Appendix A. and B.\n" ] }, { diff --git a/qualtran/bloqs/data_loading/qroam_clean.py b/qualtran/bloqs/data_loading/qroam_clean.py index e7dc9c78e..4dedb5be3 100644 --- a/qualtran/bloqs/data_loading/qroam_clean.py +++ b/qualtran/bloqs/data_loading/qroam_clean.py @@ -30,7 +30,7 @@ if TYPE_CHECKING: from qualtran import Bloq, BloqBuilder, SoquetT, QDType from qualtran.simulation.classical_sim import ClassicalValT - from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator + from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator, CostKey from qualtran.bloqs.data_loading.select_swap_qrom import _alloc_anc_for_reg, SelectSwapQROM @@ -108,8 +108,9 @@ class QROAMCleanAdjoint(QROMBase, GateWithRegisters): # type: ignore[misc] References: [Qubitization of Arbitrary Basis Quantum Chemistry Leveraging Sparsity and Low Rank Factorization](https://arxiv.org/abs/1902.02134). - Berry et. al. (2019). Appendix C. + Berry et al. (2019). Appendix C. """ + log_block_sizes: Tuple[SymbolicInt, ...] = attrs.field( converter=lambda x: tuple(x.tolist() if isinstance(x, np.ndarray) else x) ) @@ -179,7 +180,7 @@ def with_log_block_sizes( def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': block_sizes = prod([2**k for k in self.log_block_sizes]) data_size = prod(self.data_shape) - n_toffoli = ceil(data_size / block_sizes) + block_sizes + n_toffoli = ceil(data_size / block_sizes) + block_sizes - 4 + self.num_controls return {Toffoli(): n_toffoli} @cached_property @@ -213,9 +214,9 @@ class QROAMCleanAdjointWrapper(Bloq): qroam_clean: 'QROAMClean' log_block_sizes: Tuple[SymbolicInt, ...] = attrs.field( - converter=lambda x: x - if x is None - else tuple(x.tolist() if isinstance(x, np.ndarray) else x) + converter=lambda x: ( + x if x is None else tuple(x.tolist() if isinstance(x, np.ndarray) else x) + ) ) @cached_property @@ -268,7 +269,7 @@ def build_composite_bloq(self, bb: 'BloqBuilder', **soqs: 'SoquetT') -> Dict[str def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': block_sizes = prod([2**k for k in self.log_block_sizes]) data_size = prod(self.qroam_clean.data_shape) - n_toffoli = ceil(data_size / block_sizes) + block_sizes + n_toffoli = ceil(data_size / block_sizes) + block_sizes - 4 + self.qroam_clean.num_controls return {Toffoli(): n_toffoli} def adjoint(self) -> 'QROAMClean': @@ -299,6 +300,9 @@ def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) - return Circle() raise ValueError(f'Unknown register name {name}') + def __str__(self): + return 'QROAMCleanAdjoint' + @attrs.frozen class QROAMClean(SelectSwapQROM): @@ -338,8 +342,9 @@ class QROAMClean(SelectSwapQROM): References: [Qubitization of Arbitrary Basis Quantum Chemistry Leveraging Sparsity and Low Rank Factorization](https://arxiv.org/abs/1902.02134). - Berry et. al. (2019). Appendix A. and B. + Berry et al. (2019). Appendix A. and B. """ + log_block_sizes: Tuple[SymbolicInt, ...] = attrs.field( converter=lambda x: tuple(x.tolist() if isinstance(x, np.ndarray) else x) ) @@ -450,6 +455,15 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': ret[swz] += 1 return ret + def my_static_costs(self, cost_key: "CostKey"): + from qualtran.resource_counting import get_cost_value, QubitCount + + if isinstance(cost_key, QubitCount): + qrom_costs = get_cost_value(self.qrom_bloq, QubitCount()) + return qrom_costs + sum(self.log_block_sizes) + + return NotImplemented + def _build_composite_bloq_with_swz_clean( self, bb: 'BloqBuilder', diff --git a/qualtran/bloqs/data_loading/qroam_clean_test.py b/qualtran/bloqs/data_loading/qroam_clean_test.py index 96f30af53..1f8cd0f0a 100644 --- a/qualtran/bloqs/data_loading/qroam_clean_test.py +++ b/qualtran/bloqs/data_loading/qroam_clean_test.py @@ -22,7 +22,8 @@ QROAMClean, QROAMCleanAdjointWrapper, ) -from qualtran.symbolics import ceil +from qualtran.resource_counting import get_cost_value, QubitCount +from qualtran.symbolics import ceil, log2 def test_bloq_examples(bloq_autotester): @@ -30,6 +31,20 @@ def test_bloq_examples(bloq_autotester): bloq_autotester(_qroam_clean_multi_dim) +def test_qroam_clean_qubit_counts(): + bloq = _qroam_clean_multi_data.make() + assert get_cost_value(bloq, QubitCount()) == get_cost_value(bloq.decompose_bloq(), QubitCount()) + bloq = _qroam_clean_multi_dim.make() + assert get_cost_value(bloq, QubitCount()) == get_cost_value(bloq.decompose_bloq(), QubitCount()) + # Symbolic + N, b, k = sympy.symbols('N b k', positive=True, integer=True) + bloq = QROAMClean.build_from_bitsize((N,), (b,), log_block_sizes=(k,)) + K = 2**k + # log(N) - k ancilla are required for the nested unary iteration. + expected_qubits = K * b + 2 * ceil(log2(N)) - k - 1 + assert sympy.simplify(get_cost_value(bloq, QubitCount()) - expected_qubits) == 0 + + def test_t_complexity_1d_data_symbolic(): # 1D data, 1 dataset N, b, k = sympy.symbols('N b k') @@ -42,7 +57,7 @@ def test_t_complexity_1d_data_symbolic(): inv_k = sympy.symbols('kinv') inv_K = 2**inv_k bloq_inv = bloq_inv.with_log_block_sizes(log_block_sizes=(inv_k,)) - expected_toffoli_inv = ceil(N / inv_K) + inv_K + expected_toffoli_inv = ceil(N / inv_K) + inv_K - 4 assert bloq_inv.t_complexity().t == 4 * expected_toffoli_inv @@ -58,7 +73,7 @@ def test_t_complexity_2d_data_symbolic(): inv_k1, inv_k2 = sympy.symbols('kinv1, kinv2') inv_K1, inv_K2 = 2**inv_k1, 2**inv_k2 bloq_inv = bloq_inv.with_log_block_sizes(log_block_sizes=(inv_k1, inv_k2)) - expected_toffoli_inv = ceil(N1 * N2 / (inv_K1 * inv_K2)) + inv_K1 * inv_K2 + expected_toffoli_inv = ceil(N1 * N2 / (inv_K1 * inv_K2)) + inv_K1 * inv_K2 - 4 assert bloq_inv.t_complexity().t == 4 * expected_toffoli_inv diff --git a/qualtran/bloqs/data_loading/qrom.ipynb b/qualtran/bloqs/data_loading/qrom.ipynb index ffdfc412a..abf3d05e3 100644 --- a/qualtran/bloqs/data_loading/qrom.ipynb +++ b/qualtran/bloqs/data_loading/qrom.ipynb @@ -175,8 +175,8 @@ "load, the QROM also implements the \"variable-spaced\" QROM optimization described in Ref [2].\n", "\n", "#### References\n", - " - [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). Babbush et. al. (2018). Figure 1.\n", - " - [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization](https://arxiv.org/abs/2007.07391). Babbush et. al. (2020). Figure 3.\n" + " - [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). Babbush et al. 2018. Figure 1.\n", + " - [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization](https://arxiv.org/abs/2007.07391). Babbush et al. 2020. Figure 3.\n" ] }, { diff --git a/qualtran/bloqs/data_loading/qrom.py b/qualtran/bloqs/data_loading/qrom.py index 59ace8544..51b8bcaca 100644 --- a/qualtran/bloqs/data_loading/qrom.py +++ b/qualtran/bloqs/data_loading/qrom.py @@ -33,7 +33,7 @@ from qualtran.symbolics import prod, SymbolicInt if TYPE_CHECKING: - from qualtran.resource_counting import BloqCountDictT, BloqCountT, SympySymbolAllocator + from qualtran.resource_counting import BloqCountDictT, BloqCountT, CostKey, SympySymbolAllocator def _to_tuple(x: Iterable[NDArray]) -> Sequence[NDArray]: @@ -75,10 +75,10 @@ class QROM(QROMBase, UnaryIterationGate): # type: ignore[misc] References: [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). - Babbush et. al. (2018). Figure 1. + Babbush et al. 2018. Figure 1. [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization](https://arxiv.org/abs/2007.07391). - Babbush et. al. (2020). Figure 3. + Babbush et al. 2020. Figure 3. """ @classmethod @@ -184,6 +184,17 @@ def _circuit_diagram_info_(self, args) -> cirq.CircuitDiagramInfo: return _wire_symbol_to_cirq_diagram_info(self, args) + def my_static_costs(self, cost_key: "CostKey"): + from qualtran.resource_counting import QubitCount + + if isinstance(cost_key, QubitCount): + return self.signature.n_qubits() + sum(self.selection_bitsizes) - 1 + self.num_controls + + return NotImplemented + + def __str__(self): + return f'QROM({self.data_shape}, {self.target_shapes}, {self.target_bitsizes})' + def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -> 'WireSymbol': if reg is None: return Text('QROM') diff --git a/qualtran/bloqs/data_loading/qrom_test.py b/qualtran/bloqs/data_loading/qrom_test.py index 271346c34..af990ed80 100644 --- a/qualtran/bloqs/data_loading/qrom_test.py +++ b/qualtran/bloqs/data_loading/qrom_test.py @@ -25,7 +25,8 @@ from qualtran.bloqs.data_loading.qrom import _qrom_multi_data, _qrom_multi_dim, _qrom_small, QROM from qualtran.cirq_interop.t_complexity_protocol import t_complexity from qualtran.cirq_interop.testing import assert_circuit_inp_out_cirqsim, GateHelper -from qualtran.resource_counting import get_cost_value, QECGatesCost +from qualtran.resource_counting import get_cost_value, QECGatesCost, QubitCount +from qualtran.symbolics import ceil, log2 def test_qrom_small(bloq_autotester): @@ -40,6 +41,21 @@ def test_qrom_multi_dim(bloq_autotester): bloq_autotester(_qrom_multi_dim) +def test_qrom_qubit_counts(): + bloq = _qrom_small.make() + assert get_cost_value(bloq, QubitCount()) == get_cost_value(bloq.decompose_bloq(), QubitCount()) + bloq = _qrom_multi_data.make() + assert get_cost_value(bloq, QubitCount()) == get_cost_value(bloq.decompose_bloq(), QubitCount()) + bloq = _qrom_multi_dim.make() + assert get_cost_value(bloq, QubitCount()) == get_cost_value(bloq.decompose_bloq(), QubitCount()) + # Symbolic + N, b, c = sympy.symbols('N b c', positive=True, integer=True) + bloq = QROM.build_from_bitsize((N,), (b,), num_controls=c) + # log(N) ancilla are required for the ancilla used in unary iteration. + expected_qubits = 2 * ceil(log2(N)) + b + 2 * c - 1 + assert sympy.simplify(get_cost_value(bloq, QubitCount()) - expected_qubits) == 0 + + @pytest.mark.slow @pytest.mark.parametrize( "data,num_controls", diff --git a/qualtran/bloqs/data_loading/select_swap_qrom.ipynb b/qualtran/bloqs/data_loading/select_swap_qrom.ipynb index 4bd012a63..0bae09671 100644 --- a/qualtran/bloqs/data_loading/select_swap_qrom.ipynb +++ b/qualtran/bloqs/data_loading/select_swap_qrom.ipynb @@ -179,7 +179,7 @@ "\n", "#### References\n", " - [Trading T-gates for dirty qubits in state preparation and unitary synthesis](https://arxiv.org/abs/1812.00954). Low, Kliuchnikov, Schaeffer. 2018.\n", - " - [Qubitization of Arbitrary Basis Quantum Chemistry Leveraging Sparsity and Low Rank Factorization](https://arxiv.org/abs/1902.02134). Berry et. al. (2019). Appendix A. and B.\n" + " - [Qubitization of Arbitrary Basis Quantum Chemistry Leveraging Sparsity and Low Rank Factorization](https://arxiv.org/abs/1902.02134). Berry et al. 2019. Appendix A. and B.\n" ] }, { @@ -241,7 +241,7 @@ }, "outputs": [], "source": [ - "N, b, k, c = sympy.symbols('N b k c')\n", + "N, b, k, c = sympy.symbols('N b k c', positive=True, integers=True)\n", "qroam_symb_dirty_1d = SelectSwapQROM.build_from_bitsize(\n", " (N,), (b,), log_block_sizes=(k,), num_controls=c\n", ")" @@ -256,7 +256,7 @@ }, "outputs": [], "source": [ - "N, M, b1, b2, k1, k2, c = sympy.symbols('N M b1 b2 k1 k2 c')\n", + "N, M, b1, b2, k1, k2, c = sympy.symbols('N M b1 b2 k1 k2 c', positive=True, integers=True)\n", "log_block_sizes = (k1, k2)\n", "qroam_symb_dirty_2d = SelectSwapQROM.build_from_bitsize(\n", " (N, M), (b1, b2), log_block_sizes=log_block_sizes, num_controls=c\n", @@ -272,7 +272,7 @@ }, "outputs": [], "source": [ - "N, b, k, c = sympy.symbols('N b k c')\n", + "N, b, k, c = sympy.symbols('N b k c', positive=True, integers=True)\n", "qroam_symb_clean_1d = SelectSwapQROM.build_from_bitsize(\n", " (N,), (b,), log_block_sizes=(k,), num_controls=c, use_dirty_ancilla=False\n", ")" @@ -287,7 +287,7 @@ }, "outputs": [], "source": [ - "N, M, b1, b2, k1, k2, c = sympy.symbols('N M b1 b2 k1 k2 c')\n", + "N, M, b1, b2, k1, k2, c = sympy.symbols('N M b1 b2 k1 k2 c', positive=True, integers=True)\n", "log_block_sizes = (k1, k2)\n", "qroam_symb_clean_2d = SelectSwapQROM.build_from_bitsize(\n", " (N, M), (b1, b2), log_block_sizes=log_block_sizes, num_controls=c, use_dirty_ancilla=False\n", diff --git a/qualtran/bloqs/data_loading/select_swap_qrom.py b/qualtran/bloqs/data_loading/select_swap_qrom.py index 74b86c269..47eabc704 100644 --- a/qualtran/bloqs/data_loading/select_swap_qrom.py +++ b/qualtran/bloqs/data_loading/select_swap_qrom.py @@ -22,7 +22,15 @@ import sympy from numpy.typing import ArrayLike -from qualtran import bloq_example, BloqDocSpec, BQUInt, GateWithRegisters, Register, Signature +from qualtran import ( + bloq_example, + BloqDocSpec, + BQUInt, + DecomposeTypeError, + GateWithRegisters, + Register, + Signature, +) from qualtran.bloqs.arithmetic.bitwise import Xor from qualtran.bloqs.bookkeeping import Partition from qualtran.bloqs.data_loading.qrom import QROM @@ -33,7 +41,7 @@ if TYPE_CHECKING: from qualtran import Bloq, BloqBuilder, QDType, SoquetT - from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator + from qualtran.resource_counting import BloqCountDictT, CostKey, SympySymbolAllocator SelSwapQROM_T = TypeVar('SelSwapQROM_T', bound='SelectSwapQROM') @@ -127,13 +135,13 @@ class SelectSwapQROM(QROMBase, GateWithRegisters): # type: ignore[misc] Low, Kliuchnikov, Schaeffer. 2018. [Qubitization of Arbitrary Basis Quantum Chemistry Leveraging Sparsity and Low Rank Factorization](https://arxiv.org/abs/1902.02134). - Berry et. al. (2019). Appendix A. and B. + Berry et al. 2019. Appendix A. and B. """ log_block_sizes: Tuple[SymbolicInt, ...] = attrs.field( - converter=lambda x: tuple(x.tolist() if isinstance(x, np.ndarray) else x) - if x is not None - else x, + converter=lambda x: ( + tuple(x.tolist() if isinstance(x, np.ndarray) else x) if x is not None else x + ), default=None, ) use_dirty_ancilla: bool = True @@ -408,7 +416,7 @@ def build_composite_bloq(self, bb: 'BloqBuilder', **soqs: 'SoquetT') -> Dict[str target = [soqs.pop(reg.name) for reg in self.target_registers] # Allocate intermediate clean/dirty ancilla for the underlying QROM call. if is_symbolic(*self.block_sizes): - raise ValueError( + raise DecomposeTypeError( f"Cannot decompose SelectSwapQROM bloq with symbolic block sizes. Found {self.block_sizes=}" ) block_sizes = cast(Tuple[int, ...], self.block_sizes) @@ -448,6 +456,18 @@ def _circuit_diagram_info_(self, args) -> cirq.CircuitDiagramInfo: return _wire_symbol_to_cirq_diagram_info(self, args) + def my_static_costs(self, cost_key: "CostKey"): + from qualtran.resource_counting import get_cost_value, QubitCount + + if isinstance(cost_key, QubitCount): + return ( + get_cost_value(self.qrom_bloq, QubitCount()) + + sum(self.log_block_sizes) + + sum(self.target_bitsizes) + ) + + return NotImplemented + def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -> 'WireSymbol': if reg is None: return Text('QROAM') @@ -482,7 +502,7 @@ def _qroam_multi_dim() -> SelectSwapQROM: @bloq_example def _qroam_symb_dirty_1d() -> SelectSwapQROM: - N, b, k, c = sympy.symbols('N b k c') + N, b, k, c = sympy.symbols('N b k c', positive=True, integers=True) qroam_symb_dirty_1d = SelectSwapQROM.build_from_bitsize( (N,), (b,), log_block_sizes=(k,), num_controls=c ) @@ -491,7 +511,7 @@ def _qroam_symb_dirty_1d() -> SelectSwapQROM: @bloq_example def _qroam_symb_dirty_2d() -> SelectSwapQROM: - N, M, b1, b2, k1, k2, c = sympy.symbols('N M b1 b2 k1 k2 c') + N, M, b1, b2, k1, k2, c = sympy.symbols('N M b1 b2 k1 k2 c', positive=True, integers=True) log_block_sizes = (k1, k2) qroam_symb_dirty_2d = SelectSwapQROM.build_from_bitsize( (N, M), (b1, b2), log_block_sizes=log_block_sizes, num_controls=c @@ -501,7 +521,7 @@ def _qroam_symb_dirty_2d() -> SelectSwapQROM: @bloq_example def _qroam_symb_clean_1d() -> SelectSwapQROM: - N, b, k, c = sympy.symbols('N b k c') + N, b, k, c = sympy.symbols('N b k c', positive=True, integers=True) qroam_symb_clean_1d = SelectSwapQROM.build_from_bitsize( (N,), (b,), log_block_sizes=(k,), num_controls=c, use_dirty_ancilla=False ) @@ -510,7 +530,7 @@ def _qroam_symb_clean_1d() -> SelectSwapQROM: @bloq_example def _qroam_symb_clean_2d() -> SelectSwapQROM: - N, M, b1, b2, k1, k2, c = sympy.symbols('N M b1 b2 k1 k2 c') + N, M, b1, b2, k1, k2, c = sympy.symbols('N M b1 b2 k1 k2 c', positive=True, integers=True) log_block_sizes = (k1, k2) qroam_symb_clean_2d = SelectSwapQROM.build_from_bitsize( (N, M), (b1, b2), log_block_sizes=log_block_sizes, num_controls=c, use_dirty_ancilla=False diff --git a/qualtran/bloqs/data_loading/select_swap_qrom_test.py b/qualtran/bloqs/data_loading/select_swap_qrom_test.py index 975120bbf..8db1c9836 100644 --- a/qualtran/bloqs/data_loading/select_swap_qrom_test.py +++ b/qualtran/bloqs/data_loading/select_swap_qrom_test.py @@ -15,6 +15,7 @@ import cirq import numpy as np import pytest +import sympy from qualtran._infra.data_types import QUInt from qualtran._infra.gate_with_registers import get_named_qubits, split_qubits @@ -27,7 +28,8 @@ ) from qualtran.cirq_interop.t_complexity_protocol import t_complexity, TComplexity from qualtran.cirq_interop.testing import assert_circuit_inp_out_cirqsim -from qualtran.resource_counting import GateCounts, get_cost_value, QECGatesCost +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 @@ -192,6 +194,20 @@ def test_qroam_t_complexity(): assert qroam.t_complexity() == TComplexity(t=192, clifford=1082) +def test_selswap_qubit_counts(): + bloq = _qroam_multi_data.make() + assert get_cost_value(bloq, QubitCount()) == get_cost_value(bloq.decompose_bloq(), QubitCount()) + bloq = _qroam_multi_dim.make() + assert get_cost_value(bloq, QubitCount()) == get_cost_value(bloq.decompose_bloq(), QubitCount()) + # Symbolic + N, b, k = sympy.symbols('N b k', positive=True, integer=True) + bloq = SelectSwapQROM.build_from_bitsize((N,), (b,), log_block_sizes=(k,)) + K = 2**k + # log(N) - k ancilla are required for the nested unary iteration. + expected_qubits = K * b + b + 2 * ceil(log2(N)) - k - 1 + assert sympy.simplify(get_cost_value(bloq, QubitCount()) - expected_qubits) == 0 + + def test_qroam_many_registers(): # Test > 10 registers which resulted in https://github.com/quantumlib/Qualtran/issues/556 target_bitsizes = (3,) * 10 + (1,) * 2 + (3,) diff --git a/qualtran/bloqs/factoring/__init__.py b/qualtran/bloqs/factoring/__init__.py index 59a92dad8..15780de77 100644 --- a/qualtran/bloqs/factoring/__init__.py +++ b/qualtran/bloqs/factoring/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2023 Google LLC +# 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. @@ -11,5 +11,3 @@ # 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. - -from .mod_exp import ModExp diff --git a/qualtran/bloqs/factoring/ecc/_ecc_shims.py b/qualtran/bloqs/factoring/_factoring_shims.py similarity index 55% rename from qualtran/bloqs/factoring/ecc/_ecc_shims.py rename to qualtran/bloqs/factoring/_factoring_shims.py index 4b602e73f..1bc32a5c6 100644 --- a/qualtran/bloqs/factoring/ecc/_ecc_shims.py +++ b/qualtran/bloqs/factoring/_factoring_shims.py @@ -13,24 +13,54 @@ # limitations under the License. from functools import cached_property -from typing import Optional, Tuple +from typing import Dict, Optional, Tuple +import numpy as np +import sympy from attrs import frozen -from qualtran import Bloq, CompositeBloq, DecomposeTypeError, QBit, Register, Side, Signature +from qualtran import ( + Bloq, + BloqBuilder, + DecomposeTypeError, + QBit, + QUInt, + Register, + Side, + Signature, + Soquet, + SoquetT, +) +from qualtran.bloqs.basic_gates._shims import Measure +from qualtran.bloqs.qft import QFTTextBook from qualtran.drawing import RarrowTextBox, Text, WireSymbol +from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator +from qualtran.symbolics.types import SymbolicInt @frozen class MeasureQFT(Bloq): - n: int + n: 'SymbolicInt' @cached_property def signature(self) -> 'Signature': return Signature([Register('x', QBit(), shape=(self.n,), side=Side.LEFT)]) - def decompose_bloq(self) -> 'CompositeBloq': - raise DecomposeTypeError('MeasureQFT is a placeholder, atomic bloq.') + def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet) -> Dict[str, 'SoquetT']: + if isinstance(self.n, sympy.Expr): + raise DecomposeTypeError("Cannot decompose symbolic `n`.") + + x = bb.join(np.array(x), dtype=QUInt(self.n)) + x = bb.add(QFTTextBook(self.n), q=x) + x = bb.split(x) + + for i in range(self.n): + bb.add(Measure(), q=x[i]) + + return {} + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': + return {QFTTextBook(self.n): 1, Measure(): self.n} def wire_symbol( self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() diff --git a/qualtran/bloqs/factoring/ecc/ec_add.ipynb b/qualtran/bloqs/factoring/ecc/ec_add.ipynb index 543458c8c..cbc279bbe 100644 --- a/qualtran/bloqs/factoring/ecc/ec_add.ipynb +++ b/qualtran/bloqs/factoring/ecc/ec_add.ipynb @@ -41,16 +41,22 @@ "This takes elliptic curve points given by (a, b) and (x, y)\n", "and outputs the sum (x_r, y_r) in the second pair of registers.\n", "\n", + "Because the decomposition of this Bloq is complex, we split it into six separate parts\n", + "corresponding to the parts described in figure 10 of the Litinski paper cited below. We follow\n", + "the signature from figure 5 and break down the further decompositions based on the steps in\n", + "figure 10.\n", + "\n", "#### Parameters\n", " - `n`: The bitsize of the two registers storing the elliptic curve point\n", - " - `mod`: The modulus of the field in which we do the addition. \n", + " - `mod`: The modulus of the field in which we do the addition.\n", + " - `window_size`: The number of bits in the ModMult window. \n", "\n", "#### Registers\n", - " - `a`: The x component of the first input elliptic curve point of bitsize `n`.\n", - " - `b`: The y component of the first input elliptic curve point of bitsize `n`.\n", - " - `x`: The x component of the second input elliptic curve point of bitsize `n`, which will contain the x component of the resultant curve point.\n", - " - `y`: The y component of the second input elliptic curve point of bitsize `n`, which will contain the y component of the resultant curve point.\n", - " - `lam`: The precomputed lambda slope used in the addition operation. \n", + " - `a`: The x component of the first input elliptic curve point of bitsize `n` in montgomery form.\n", + " - `b`: The y component of the first input elliptic curve point of bitsize `n` in montgomery form.\n", + " - `x`: The x component of the second input elliptic curve point of bitsize `n` in montgomery form, which will contain the x component of the resultant curve point.\n", + " - `y`: The y component of the second input elliptic curve point of bitsize `n` in montgomery form, which will contain the y component of the resultant curve point.\n", + " - `lam_r`: The precomputed lambda slope used in the addition operation if (a, b) = (x, y) in montgomery form. \n", "\n", "#### References\n", " - [How to compute a 256-bit elliptic curve private key with only 50 million Toffoli gates](https://arxiv.org/abs/2306.08585). Litinski. 2023. Fig 5.\n" @@ -91,6 +97,18 @@ "ec_add = ECAdd(n, mod=p)" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "170da165", + "metadata": { + "cq.autogen": "ECAdd.ec_add_small" + }, + "outputs": [], + "source": [ + "ec_add_small = ECAdd(5, mod=7)" + ] + }, { "cell_type": "markdown", "id": "39210af4", @@ -111,8 +129,8 @@ "outputs": [], "source": [ "from qualtran.drawing import show_bloqs\n", - "show_bloqs([ec_add],\n", - " ['`ec_add`'])" + "show_bloqs([ec_add, ec_add_small],\n", + " ['`ec_add`', '`ec_add_small`'])" ] }, { @@ -157,7 +175,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.7" + "version": "3.10.13" } }, "nbformat": 4, diff --git a/qualtran/bloqs/factoring/ecc/ec_add.py b/qualtran/bloqs/factoring/ecc/ec_add.py index e0ff64e6a..f764c70cb 100644 --- a/qualtran/bloqs/factoring/ecc/ec_add.py +++ b/qualtran/bloqs/factoring/ecc/ec_add.py @@ -12,15 +12,963 @@ # See the License for the specific language governing permissions and # limitations under the License. from functools import cached_property +from typing import Dict, Union +import numpy as np import sympy from attrs import frozen -from qualtran import Bloq, bloq_example, BloqDocSpec, QUInt, Register, Signature -from qualtran.bloqs.arithmetic._shims import MultiCToffoli -from qualtran.bloqs.mod_arithmetic import CModAdd, CModNeg, CModSub, ModAdd, ModNeg, ModSub -from qualtran.bloqs.mod_arithmetic._shims import ModDbl, ModInv, ModMul +from qualtran import ( + Bloq, + bloq_example, + BloqBuilder, + BloqDocSpec, + DecomposeTypeError, + QBit, + QMontgomeryUInt, + Register, + Side, + Signature, + Soquet, + SoquetT, +) +from qualtran.bloqs.arithmetic.comparison import Equals +from qualtran.bloqs.basic_gates import CNOT, IntState, Toffoli, ZeroState +from qualtran.bloqs.bookkeeping import Free +from qualtran.bloqs.mcmt import MultiAnd, MultiControlX, MultiTargetCNOT +from qualtran.bloqs.mod_arithmetic import ( + CModAdd, + CModNeg, + CModSub, + DirtyOutOfPlaceMontgomeryModMul, + KaliskiModInverse, + ModAdd, + ModDbl, + ModNeg, + ModSub, +) from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator +from qualtran.simulation.classical_sim import ClassicalValT +from qualtran.symbolics.types import HasLength, is_symbolic, SymbolicInt + +from .ec_point import ECPoint + + +@frozen +class _ECAddStepOne(Bloq): + r"""Performs step one of the ECAdd bloq. + + Args: + n: The bitsize of the two registers storing the elliptic curve point + mod: The modulus of the field in which we do the addition. + + Registers: + f1: Flag to set if a = x. + f2: Flag to set if b = -y. + f3: Flag to set if (a, b) = (0, 0). + f4: Flag to set if (x, y) = (0, 0). + ctrl: Flag to set if neither the input points nor the output point are (0, 0). + a: The x component of the first input elliptic curve point of bitsize `n` in montgomery form. + b: The y component of the first input elliptic curve point of bitsize `n` in montgomery form. + x: The x component of the second input elliptic curve point of bitsize `n` in montgomery form, which + will contain the x component of the resultant curve point. + y: The y component of the second input elliptic curve point of bitsize `n` in montgomery form, which + will contain the y component of the resultant curve point. + + References: + [How to compute a 256-bit elliptic curve private key with only 50 million Toffoli gates](https://arxiv.org/abs/2306.08585) + Fig 10. + """ + + n: 'SymbolicInt' + mod: 'SymbolicInt' + + @cached_property + def signature(self) -> 'Signature': + return Signature( + [ + Register('f1', QBit(), side=Side.RIGHT), + Register('f2', QBit(), side=Side.RIGHT), + Register('f3', QBit(), side=Side.RIGHT), + Register('f4', QBit(), side=Side.RIGHT), + Register('ctrl', QBit(), side=Side.RIGHT), + Register('a', QMontgomeryUInt(self.n)), + Register('b', QMontgomeryUInt(self.n)), + Register('x', QMontgomeryUInt(self.n)), + Register('y', QMontgomeryUInt(self.n)), + ] + ) + + def on_classical_vals( + self, a: 'ClassicalValT', b: 'ClassicalValT', x: 'ClassicalValT', y: 'ClassicalValT' + ) -> Dict[str, 'ClassicalValT']: + f1 = int(a == x) + f2 = int(b == (-y % self.mod)) + f3 = int(a == b == 0) + f4 = int(x == y == 0) + ctrl = int(f2 == f3 == f4 == 0) + return { + 'f1': f1, + 'f2': f2, + 'f3': f3, + 'f4': f4, + 'ctrl': ctrl, + 'a': a, + 'b': b, + 'x': x, + 'y': y, + } + + def build_composite_bloq( + self, bb: 'BloqBuilder', a: Soquet, b: Soquet, x: Soquet, y: Soquet + ) -> Dict[str, 'SoquetT']: + if is_symbolic(self.n): + raise DecomposeTypeError(f"Cannot decompose {self} with symbolic `n`.") + + # Initialize control flags to 0. + f1 = bb.add(ZeroState()) + f2 = bb.add(ZeroState()) + f3 = bb.add(ZeroState()) + f4 = bb.add(ZeroState()) + ctrl = bb.add(ZeroState()) + + # Set flag 1 if a = x. + a, x, f1 = bb.add(Equals(QMontgomeryUInt(self.n)), x=a, y=x, target=f1) + + # Set flag 2 if b = -y. + y = bb.add(ModNeg(QMontgomeryUInt(self.n), mod=self.mod), x=y) + b, y, f2 = bb.add(Equals(QMontgomeryUInt(self.n)), x=b, y=y, target=f2) + y = bb.add(ModNeg(QMontgomeryUInt(self.n), mod=self.mod), x=y) + + # Set flag 3 if (a, b) == (0, 0). + ab_arr = np.concatenate([bb.split(a), bb.split(b)]) + ab_arr, f3 = bb.add(MultiControlX(cvs=[0] * 2 * self.n), controls=ab_arr, target=f3) + ab_arr = np.split(ab_arr, 2) + a = bb.join(ab_arr[0], dtype=QMontgomeryUInt(self.n)) + b = bb.join(ab_arr[1], dtype=QMontgomeryUInt(self.n)) + + # Set flag 4 if (x, y) == (0, 0). + xy_arr = np.concatenate([bb.split(x), bb.split(y)]) + xy_arr, f4 = bb.add(MultiControlX(cvs=[0] * 2 * self.n), controls=xy_arr, target=f4) + xy_arr = np.split(xy_arr, 2) + x = bb.join(xy_arr[0], dtype=QMontgomeryUInt(self.n)) + y = bb.join(xy_arr[1], dtype=QMontgomeryUInt(self.n)) + + # Set ctrl flag if f2, f3, f4 are set. + f_ctrls = [f2, f3, f4] + f_ctrls, ctrl = bb.add(MultiControlX(cvs=[0] * 3), controls=f_ctrls, target=ctrl) + f2 = f_ctrls[0] + f3 = f_ctrls[1] + f4 = f_ctrls[2] + + # Return the output registers. + return { + 'f1': f1, + 'f2': f2, + 'f3': f3, + 'f4': f4, + 'ctrl': ctrl, + 'a': a, + 'b': b, + 'x': x, + 'y': y, + } + + def build_call_graph(self, ssa: SympySymbolAllocator) -> BloqCountDictT: + cvs: Union[list[int], HasLength] + if isinstance(self.n, int): + cvs = [0] * 2 * self.n + else: + cvs = HasLength(2 * self.n) + return { + Equals(QMontgomeryUInt(self.n)): 2, + ModNeg(QMontgomeryUInt(self.n), mod=self.mod): 2, + MultiControlX(cvs=cvs): 2, + MultiControlX(cvs=[0] * 3): 1, + } + + +@frozen +class _ECAddStepTwo(Bloq): + r"""Performs step two of the ECAdd bloq. + + Args: + n: The bitsize of the two registers storing the elliptic curve point + mod: The modulus of the field in which we do the addition. + window_size: The number of bits in the ModMult window. + + Registers: + f1: Flag set if a = x. + ctrl: Flag set if neither the input points nor the output point are (0, 0). + a: The x component of the first input elliptic curve point of bitsize `n` in montgomery form. + b: The y component of the first input elliptic curve point of bitsize `n` in montgomery form. + x: The x component of the second input elliptic curve point of bitsize `n` in montgomery form, which + will contain the x component of the resultant curve point. + y: The y component of the second input elliptic curve point of bitsize `n` in montgomery form, which + will contain the y component of the resultant curve point. + lam: The lambda slope used in the addition operation. + lam_r: The precomputed lambda slope used in the addition operation if (a, b) = (x, y) in montgomery form. + + References: + [How to compute a 256-bit elliptic curve private key with only 50 million Toffoli gates](https://arxiv.org/abs/2306.08585) + Fig 10. + """ + + n: 'SymbolicInt' + mod: 'SymbolicInt' + window_size: 'SymbolicInt' = 1 + + @cached_property + def signature(self) -> 'Signature': + return Signature( + [ + Register('f1', QBit()), + Register('ctrl', QBit()), + Register('a', QMontgomeryUInt(self.n)), + Register('b', QMontgomeryUInt(self.n)), + Register('x', QMontgomeryUInt(self.n)), + Register('y', QMontgomeryUInt(self.n)), + Register('lam', QMontgomeryUInt(self.n), side=Side.RIGHT), + Register('lam_r', QMontgomeryUInt(self.n)), + ] + ) + + def on_classical_vals( + self, + f1: 'ClassicalValT', + ctrl: 'ClassicalValT', + a: 'ClassicalValT', + b: 'ClassicalValT', + x: 'ClassicalValT', + y: 'ClassicalValT', + lam_r: 'ClassicalValT', + ) -> Dict[str, 'ClassicalValT']: + x = (x - a) % self.mod + if ctrl == 1: + y = (y - b) % self.mod + if f1 == 1: + lam = lam_r + f1 = 0 + else: + lam = QMontgomeryUInt(self.n).montgomery_product( + int(y), + QMontgomeryUInt(self.n).montgomery_inverse(int(x), int(self.mod)), + int(self.mod), + ) + # TODO(https://github.com/quantumlib/Qualtran/issues/1461): Fix bug in circuit + # which flips f1 when lam and lam_r are equal. + if lam == lam_r: + f1 = (f1 + 1) % 2 + else: + lam = 0 + return {'f1': f1, 'ctrl': ctrl, 'a': a, 'b': b, 'x': x, 'y': y, 'lam': lam, 'lam_r': lam_r} + + def build_composite_bloq( + self, + bb: 'BloqBuilder', + f1: Soquet, + ctrl: Soquet, + a: Soquet, + b: Soquet, + x: Soquet, + y: Soquet, + lam_r: Soquet, + ) -> Dict[str, 'SoquetT']: + if is_symbolic(self.n): + raise DecomposeTypeError(f"Cannot decompose {self} with symbolic `n`.") + + # Initalize lambda to 0. + lam = bb.add(IntState(bitsize=self.n, val=0)) + + # Perform modular subtraction so that x = (x - a) % p. + a, x = bb.add(ModSub(QMontgomeryUInt(self.n), mod=self.mod), x=a, y=x) + + # Perform controlled modular subtraction so that y = (y - b) % p iff ctrl = 1. + ctrl, b, y = bb.add(CModSub(QMontgomeryUInt(self.n), mod=self.mod), ctrl=ctrl, x=b, y=y) + + # Perform modular inversion s.t. x = (x - a)^-1 % p. + x, junk = bb.add(KaliskiModInverse(bitsize=self.n, mod=self.mod), x=x) + + # Perform modular multiplication z4 = (y / x) % p. + x, y, z4, z3, reduced = bb.add( + DirtyOutOfPlaceMontgomeryModMul( + bitsize=self.n, window_size=self.window_size, mod=self.mod + ), + x=x, + y=y, + ) + + # If ctrl = 1 and x != a: lam = (y - b) / (x - a) % p. + z4_split = bb.split(z4) + lam_split = bb.split(lam) + for i in range(int(self.n)): + ctrls = [f1, ctrl, z4_split[i]] + ctrls, lam_split[i] = bb.add( + MultiControlX(cvs=[0, 1, 1]), controls=ctrls, target=lam_split[i] + ) + f1 = ctrls[0] + ctrl = ctrls[1] + z4_split[i] = ctrls[2] + z4 = bb.join(z4_split, dtype=QMontgomeryUInt(self.n)) + + # If ctrl = 1 and x = a: lam = lam_r. + lam_r_split = bb.split(lam_r) + for i in range(int(self.n)): + ctrls = [f1, ctrl, lam_r_split[i]] + ctrls, lam_split[i] = bb.add( + MultiControlX(cvs=[1, 1, 1]), controls=ctrls, target=lam_split[i] + ) + f1 = ctrls[0] + ctrl = ctrls[1] + lam_r_split[i] = ctrls[2] + lam_r = bb.join(lam_r_split, dtype=QMontgomeryUInt(self.n)) + lam = bb.join(lam_split, dtype=QMontgomeryUInt(self.n)) + + # If lam = lam_r: return f1 = 0. (If not we will flip f1 to 0 at the end iff x_r = y_r = 0). + lam, lam_r, f1 = bb.add(Equals(QMontgomeryUInt(self.n)), x=lam, y=lam_r, target=f1) + + # Uncompute the modular multiplication then the modular inversion. + x, y = bb.add( + DirtyOutOfPlaceMontgomeryModMul( + bitsize=self.n, window_size=self.window_size, mod=self.mod + ).adjoint(), + x=x, + y=y, + target=z4, + qrom_indices=z3, + reduced=reduced, + ) + x = bb.add(KaliskiModInverse(bitsize=self.n, mod=self.mod).adjoint(), x=x, junk=junk) + + # Return the output registers. + return {'f1': f1, 'ctrl': ctrl, 'a': a, 'b': b, 'x': x, 'y': y, 'lam': lam, 'lam_r': lam_r} + + def build_call_graph(self, ssa: SympySymbolAllocator) -> BloqCountDictT: + return { + Equals(QMontgomeryUInt(self.n)): 1, + ModSub(QMontgomeryUInt(self.n), mod=self.mod): 1, + CModSub(QMontgomeryUInt(self.n), mod=self.mod): 1, + KaliskiModInverse(bitsize=self.n, mod=self.mod): 1, + DirtyOutOfPlaceMontgomeryModMul( + bitsize=self.n, window_size=self.window_size, mod=self.mod + ): 1, + MultiControlX(cvs=[0, 1, 1]): self.n, + MultiControlX(cvs=[1, 1, 1]): self.n, + DirtyOutOfPlaceMontgomeryModMul( + bitsize=self.n, window_size=self.window_size, mod=self.mod + ).adjoint(): 1, + KaliskiModInverse(bitsize=self.n, mod=self.mod).adjoint(): 1, + } + + +@frozen +class _ECAddStepThree(Bloq): + r"""Performs step three of the ECAdd bloq. + + Args: + n: The bitsize of the two registers storing the elliptic curve point + mod: The modulus of the field in which we do the addition. + window_size: The number of bits in the ModMult window. + + Registers: + ctrl: Flag set if neither the input points nor the output point are (0, 0). + a: The x component of the first input elliptic curve point of bitsize `n` in montgomery form. + b: The y component of the first input elliptic curve point of bitsize `n` in montgomery form. + x: The x component of the second input elliptic curve point of bitsize `n` in montgomery form, which + will contain the x component of the resultant curve point. + y: The y component of the second input elliptic curve point of bitsize `n` in montgomery form, which + will contain the y component of the resultant curve point. + lam: The lambda slope used in the addition operation. + + References: + [How to compute a 256-bit elliptic curve private key with only 50 million Toffoli gates](https://arxiv.org/abs/2306.08585) + Fig 10. + """ + + n: 'SymbolicInt' + mod: 'SymbolicInt' + window_size: 'SymbolicInt' = 1 + + @cached_property + def signature(self) -> 'Signature': + return Signature( + [ + Register('ctrl', QBit()), + Register('a', QMontgomeryUInt(self.n)), + Register('b', QMontgomeryUInt(self.n)), + Register('x', QMontgomeryUInt(self.n)), + Register('y', QMontgomeryUInt(self.n)), + Register('lam', QMontgomeryUInt(self.n)), + ] + ) + + def on_classical_vals( + self, + ctrl: 'ClassicalValT', + a: 'ClassicalValT', + b: 'ClassicalValT', + x: 'ClassicalValT', + y: 'ClassicalValT', + lam: 'ClassicalValT', + ) -> Dict[str, 'ClassicalValT']: + if ctrl == 1: + x = (x + 3 * a) % self.mod + y = 0 + return {'ctrl': ctrl, 'a': a, 'b': b, 'x': x, 'y': y, 'lam': lam} + + def build_composite_bloq( + self, + bb: 'BloqBuilder', + ctrl: Soquet, + a: Soquet, + b: Soquet, + x: Soquet, + y: Soquet, + lam: Soquet, + ) -> Dict[str, 'SoquetT']: + if is_symbolic(self.n): + raise DecomposeTypeError(f"Cannot decompose {self} with symbolic `n`.") + + # Store (x - a) * lam % p in z1 (= (y - b) % p). + x, lam, z1, z2, reduced = bb.add( + DirtyOutOfPlaceMontgomeryModMul( + bitsize=self.n, window_size=self.window_size, mod=self.mod + ), + x=x, + y=lam, + ) + + # If ctrl: subtract z1 from y (= 0). + ctrl, z1, y = bb.add(CModSub(QMontgomeryUInt(self.n), mod=self.mod), ctrl=ctrl, x=z1, y=y) + + # Uncompute original multiplication. + x, lam = bb.add( + DirtyOutOfPlaceMontgomeryModMul( + bitsize=self.n, window_size=self.window_size, mod=self.mod + ).adjoint(), + x=x, + y=lam, + target=z1, + qrom_indices=z2, + reduced=reduced, + ) + + # z1 = a. + z1 = bb.add(IntState(bitsize=self.n, val=0)) + a_split = bb.split(a) + z1_split = bb.split(z1) + for i in range(int(self.n)): + a_split[i], z1_split[i] = bb.add(CNOT(), ctrl=a_split[i], target=z1_split[i]) + a = bb.join(a_split, QMontgomeryUInt(self.n)) + z1 = bb.join(z1_split, QMontgomeryUInt(self.n)) + + # z1 = (3 * a) % p. + z1 = bb.add(ModDbl(QMontgomeryUInt(self.n), mod=self.mod), x=z1) + a, z1 = bb.add(ModAdd(self.n, mod=self.mod), x=a, y=z1) + + # If ctrl: x = (x + 2 * a) % p. + ctrl, z1, x = bb.add(CModAdd(QMontgomeryUInt(self.n), mod=self.mod), ctrl=ctrl, x=z1, y=x) + + # Uncompute z1. + a, z1 = bb.add(ModAdd(self.n, mod=self.mod).adjoint(), x=a, y=z1) + z1 = bb.add(ModDbl(QMontgomeryUInt(self.n), mod=self.mod).adjoint(), x=z1) + a_split = bb.split(a) + z1_split = bb.split(z1) + for i in range(int(self.n)): + a_split[i], z1_split[i] = bb.add(CNOT(), ctrl=a_split[i], target=z1_split[i]) + a = bb.join(a_split, QMontgomeryUInt(self.n)) + z1 = bb.join(z1_split, QMontgomeryUInt(self.n)) + bb.add(Free(QMontgomeryUInt(self.n)), reg=z1) + + # Return the output registers. + return {'ctrl': ctrl, 'a': a, 'b': b, 'x': x, 'y': y, 'lam': lam} + + def build_call_graph(self, ssa: SympySymbolAllocator) -> BloqCountDictT: + return { + CModSub(QMontgomeryUInt(self.n), mod=self.mod): 1, + DirtyOutOfPlaceMontgomeryModMul( + bitsize=self.n, window_size=self.window_size, mod=self.mod + ): 1, + DirtyOutOfPlaceMontgomeryModMul( + bitsize=self.n, window_size=self.window_size, mod=self.mod + ).adjoint(): 1, + CNOT(): 2 * self.n, + ModDbl(QMontgomeryUInt(self.n), mod=self.mod): 1, + ModAdd(self.n, mod=self.mod): 1, + CModAdd(QMontgomeryUInt(self.n), mod=self.mod): 1, + ModAdd(self.n, mod=self.mod).adjoint(): 1, + ModDbl(QMontgomeryUInt(self.n), mod=self.mod).adjoint(): 1, + } + + +@frozen +class _ECAddStepFour(Bloq): + r"""Performs step four of the ECAdd bloq. + + Args: + n: The bitsize of the two registers storing the elliptic curve point + mod: The modulus of the field in which we do the addition. + window_size: The number of bits in the ModMult window. + + Registers: + x: The x component of the second input elliptic curve point of bitsize `n` in montgomery form, which + will contain the x component of the resultant curve point. + y: The y component of the second input elliptic curve point of bitsize `n` in montgomery form, which + will contain the y component of the resultant curve point. + lam: The lambda slope used in the addition operation. + + References: + [How to compute a 256-bit elliptic curve private key with only 50 million Toffoli gates](https://arxiv.org/abs/2306.08585) + Fig 10. + """ + + n: 'SymbolicInt' + mod: 'SymbolicInt' + window_size: 'SymbolicInt' = 1 + + @cached_property + def signature(self) -> 'Signature': + return Signature( + [ + Register('x', QMontgomeryUInt(self.n)), + Register('y', QMontgomeryUInt(self.n)), + Register('lam', QMontgomeryUInt(self.n)), + ] + ) + + def on_classical_vals( + self, x: 'ClassicalValT', y: 'ClassicalValT', lam: 'ClassicalValT' + ) -> Dict[str, 'ClassicalValT']: + x = ( + x - QMontgomeryUInt(self.n).montgomery_product(int(lam), int(lam), int(self.mod)) + ) % self.mod + if lam > 0: + y = QMontgomeryUInt(self.n).montgomery_product(int(x), int(lam), int(self.mod)) + return {'x': x, 'y': y, 'lam': lam} + + def build_composite_bloq( + self, bb: 'BloqBuilder', x: Soquet, y: Soquet, lam: Soquet + ) -> Dict[str, 'SoquetT']: + if is_symbolic(self.n): + raise DecomposeTypeError(f"Cannot decompose {self} with symbolic `n`.") + + # Initialize z4 = lam. + z4 = bb.add(IntState(bitsize=self.n, val=0)) + lam_split = bb.split(lam) + z4_split = bb.split(z4) + for i in range(int(self.n)): + lam_split[i], z4_split[i] = bb.add(CNOT(), ctrl=lam_split[i], target=z4_split[i]) + lam = bb.join(lam_split, QMontgomeryUInt(self.n)) + z4 = bb.join(z4_split, QMontgomeryUInt(self.n)) + + # z3 = lam * lam % p. + z4, lam, z3, z2, reduced = bb.add( + DirtyOutOfPlaceMontgomeryModMul( + bitsize=self.n, window_size=self.window_size, mod=self.mod + ), + x=z4, + y=lam, + ) + + # x = a - x_r % p. + z3, x = bb.add(ModSub(QMontgomeryUInt(self.n), mod=self.mod), x=z3, y=x) + + # Uncompute the multiplication and initialization of z4. + z4, lam = bb.add( + DirtyOutOfPlaceMontgomeryModMul( + bitsize=self.n, window_size=self.window_size, mod=self.mod + ).adjoint(), + x=z4, + y=lam, + target=z3, + qrom_indices=z2, + reduced=reduced, + ) + lam_split = bb.split(lam) + z4_split = bb.split(z4) + for i in range(int(self.n)): + lam_split[i], z4_split[i] = bb.add(CNOT(), ctrl=lam_split[i], target=z4_split[i]) + lam = bb.join(lam_split, QMontgomeryUInt(self.n)) + z4 = bb.join(z4_split, QMontgomeryUInt(self.n)) + bb.add(Free(QMontgomeryUInt(self.n)), reg=z4) + + # z3 = lam * x % p. + x, lam, z3, z4, reduced = bb.add( + DirtyOutOfPlaceMontgomeryModMul( + bitsize=self.n, window_size=self.window_size, mod=self.mod + ), + x=x, + y=lam, + ) + + # y = y_r + b % p. + z3_split = bb.split(z3) + y_split = bb.split(y) + for i in range(int(self.n)): + z3_split[i], y_split[i] = bb.add(CNOT(), ctrl=z3_split[i], target=y_split[i]) + z3 = bb.join(z3_split, QMontgomeryUInt(self.n)) + y = bb.join(y_split, QMontgomeryUInt(self.n)) + + # Uncompute multiplication. + x, lam = bb.add( + DirtyOutOfPlaceMontgomeryModMul( + bitsize=self.n, window_size=self.window_size, mod=self.mod + ).adjoint(), + x=x, + y=lam, + target=z3, + qrom_indices=z4, + reduced=reduced, + ) + + # Return the output registers. + return {'x': x, 'y': y, 'lam': lam} + + def build_call_graph(self, ssa: SympySymbolAllocator) -> BloqCountDictT: + return { + ModSub(QMontgomeryUInt(self.n), mod=self.mod): 1, + DirtyOutOfPlaceMontgomeryModMul( + bitsize=self.n, window_size=self.window_size, mod=self.mod + ): 2, + DirtyOutOfPlaceMontgomeryModMul( + bitsize=self.n, window_size=self.window_size, mod=self.mod + ).adjoint(): 2, + CNOT(): 3 * self.n, + } + + +@frozen +class _ECAddStepFive(Bloq): + r"""Performs step five of the ECAdd bloq. + + Args: + n: The bitsize of the two registers storing the elliptic curve point + mod: The modulus of the field in which we do the addition. + window_size: The number of bits in the ModMult window. + + Registers: + ctrl: Flag set if neither the input points nor the output point are (0, 0). + a: The x component of the first input elliptic curve point of bitsize `n` in montgomery form. + b: The y component of the first input elliptic curve point of bitsize `n` in montgomery form. + x: The x component of the second input elliptic curve point of bitsize `n` in montgomery form, which + will contain the x component of the resultant curve point. + y: The y component of the second input elliptic curve point of bitsize `n` in montgomery form, which + will contain the y component of the resultant curve point. + lam: The lambda slope used in the addition operation. + + References: + [How to compute a 256-bit elliptic curve private key with only 50 million Toffoli gates](https://arxiv.org/abs/2306.08585) + Fig 10. + """ + + n: 'SymbolicInt' + mod: 'SymbolicInt' + window_size: 'SymbolicInt' = 1 + + @cached_property + def signature(self) -> 'Signature': + return Signature( + [ + Register('ctrl', QBit()), + Register('a', QMontgomeryUInt(self.n)), + Register('b', QMontgomeryUInt(self.n)), + Register('x', QMontgomeryUInt(self.n)), + Register('y', QMontgomeryUInt(self.n)), + Register('lam', QMontgomeryUInt(self.n), side=Side.LEFT), + ] + ) + + def on_classical_vals( + self, + ctrl: 'ClassicalValT', + a: 'ClassicalValT', + b: 'ClassicalValT', + x: 'ClassicalValT', + y: 'ClassicalValT', + lam: 'ClassicalValT', + ) -> Dict[str, 'ClassicalValT']: + if ctrl == 1: + x = (a - x) % self.mod + y = (y - b) % self.mod + else: + x = (x + a) % self.mod + return {'ctrl': ctrl, 'a': a, 'b': b, 'x': x, 'y': y} + + def build_composite_bloq( + self, + bb: 'BloqBuilder', + ctrl: Soquet, + a: Soquet, + b: Soquet, + x: Soquet, + y: Soquet, + lam: Soquet, + ) -> Dict[str, 'SoquetT']: + if is_symbolic(self.n): + raise DecomposeTypeError(f"Cannot decompose {self} with symbolic `n`.") + + # x = x ^ -1 % p. + x, junk = bb.add(KaliskiModInverse(bitsize=self.n, mod=self.mod), x=x) + + # z4 = x * y % p. + x, y, z4, z3, reduced = bb.add( + DirtyOutOfPlaceMontgomeryModMul( + bitsize=self.n, window_size=self.window_size, mod=self.mod + ), + x=x, + y=y, + ) + + # If ctrl: lam = 0. + z4_split = bb.split(z4) + lam_split = bb.split(lam) + for i in range(int(self.n)): + ctrls = [ctrl, z4_split[i]] + ctrls, lam_split[i] = bb.add( + MultiControlX(cvs=[1, 1]), controls=ctrls, target=lam_split[i] + ) + ctrl = ctrls[0] + z4_split[i] = ctrls[1] + z4 = bb.join(z4_split, dtype=QMontgomeryUInt(self.n)) + lam = bb.join(lam_split, dtype=QMontgomeryUInt(self.n)) + # TODO(https://github.com/quantumlib/Qualtran/issues/1461): Fix bug in circuit where lambda + # is not set to 0 before being freed. + bb.add(Free(QMontgomeryUInt(self.n), dirty=True), reg=lam) + + # Uncompute multiplication and inverse. + x, y = bb.add( + DirtyOutOfPlaceMontgomeryModMul( + bitsize=self.n, window_size=self.window_size, mod=self.mod + ).adjoint(), + x=x, + y=y, + target=z4, + qrom_indices=z3, + reduced=reduced, + ) + x = bb.add(KaliskiModInverse(bitsize=self.n, mod=self.mod).adjoint(), x=x, junk=junk) + + # If ctrl: x = x_r - a % p. + ctrl, x = bb.add(CModNeg(QMontgomeryUInt(self.n), mod=self.mod), ctrl=ctrl, x=x) + + # Add a to x (x = x_r). + a, x = bb.add(ModAdd(self.n, mod=self.mod), x=a, y=x) + + # If ctrl: subtract b from y (y = y_r). + ctrl, b, y = bb.add(CModSub(QMontgomeryUInt(self.n), mod=self.mod), ctrl=ctrl, x=b, y=y) + + # Return the output registers. + return {'ctrl': ctrl, 'a': a, 'b': b, 'x': x, 'y': y} + + def build_call_graph(self, ssa: SympySymbolAllocator) -> BloqCountDictT: + return { + CModSub(QMontgomeryUInt(self.n), mod=self.mod): 1, + KaliskiModInverse(bitsize=self.n, mod=self.mod): 1, + DirtyOutOfPlaceMontgomeryModMul( + bitsize=self.n, window_size=self.window_size, mod=self.mod + ): 1, + DirtyOutOfPlaceMontgomeryModMul( + bitsize=self.n, window_size=self.window_size, mod=self.mod + ).adjoint(): 1, + KaliskiModInverse(bitsize=self.n, mod=self.mod).adjoint(): 1, + ModAdd(self.n, mod=self.mod): 1, + MultiControlX(cvs=[1, 1]): self.n, + CModNeg(QMontgomeryUInt(self.n), mod=self.mod): 1, + } + + +@frozen +class _ECAddStepSix(Bloq): + r"""Performs step six of the ECAdd bloq. + + Args: + n: The bitsize of the two registers storing the elliptic curve point + mod: The modulus of the field in which we do the addition. + + Registers: + f1: Flag to set if a = x. + f2: Flag to set if b = -y. + f3: Flag to set if (a, b) = (0, 0). + f4: Flag to set if (x, y) = (0, 0). + ctrl: Flag to set if neither the input points nor the output point are (0, 0). + a: The x component of the first input elliptic curve point of bitsize `n` in montgomery form. + b: The y component of the first input elliptic curve point of bitsize `n` in montgomery form. + x: The x component of the second input elliptic curve point of bitsize `n` in montgomery form, which + will contain the x component of the resultant curve point. + y: The y component of the second input elliptic curve point of bitsize `n` in montgomery form, which + will contain the y component of the resultant curve point. + + References: + [How to compute a 256-bit elliptic curve private key with only 50 million Toffoli gates](https://arxiv.org/abs/2306.08585) + Fig 10. + """ + + n: 'SymbolicInt' + mod: 'SymbolicInt' + + @cached_property + def signature(self) -> 'Signature': + return Signature( + [ + Register('f1', QBit(), side=Side.LEFT), + Register('f2', QBit(), side=Side.LEFT), + Register('f3', QBit(), side=Side.LEFT), + Register('f4', QBit(), side=Side.LEFT), + Register('ctrl', QBit(), side=Side.LEFT), + Register('a', QMontgomeryUInt(self.n)), + Register('b', QMontgomeryUInt(self.n)), + Register('x', QMontgomeryUInt(self.n)), + Register('y', QMontgomeryUInt(self.n)), + ] + ) + + def on_classical_vals( + self, + f1: 'ClassicalValT', + f2: 'ClassicalValT', + f3: 'ClassicalValT', + f4: 'ClassicalValT', + ctrl: 'ClassicalValT', + a: 'ClassicalValT', + b: 'ClassicalValT', + x: 'ClassicalValT', + y: 'ClassicalValT', + ) -> Dict[str, 'ClassicalValT']: + if f4 == 1: + x = a + y = b + if f1 and f2: + x = 0 + y = 0 + return {'a': a, 'b': b, 'x': x, 'y': y} + + def build_composite_bloq( + self, + bb: 'BloqBuilder', + f1: Soquet, + f2: Soquet, + f3: Soquet, + f4: Soquet, + ctrl: Soquet, + a: Soquet, + b: Soquet, + x: Soquet, + y: Soquet, + ) -> Dict[str, 'SoquetT']: + if is_symbolic(self.n): + raise DecomposeTypeError(f"Cannot decompose {self} with symbolic `n`.") + + # Unset control if f2, f3, and f4 flags are set. + f_ctrls = [f2, f3, f4] + f_ctrls, ctrl = bb.add(MultiControlX(cvs=[0] * 3), controls=f_ctrls, target=ctrl) + f2 = f_ctrls[0] + f3 = f_ctrls[1] + f4 = f_ctrls[2] + + # Set (x, y) to (a, b) if f4 is set. + a_split = bb.split(a) + x_split = bb.split(x) + for i in range(int(self.n)): + toff_ctrl = [f4, a_split[i]] + toff_ctrl, x_split[i] = bb.add(Toffoli(), ctrl=toff_ctrl, target=x_split[i]) + f4 = toff_ctrl[0] + a_split[i] = toff_ctrl[1] + a = bb.join(a_split, QMontgomeryUInt(self.n)) + x = bb.join(x_split, QMontgomeryUInt(self.n)) + b_split = bb.split(b) + y_split = bb.split(y) + for i in range(int(self.n)): + toff_ctrl = [f4, b_split[i]] + toff_ctrl, y_split[i] = bb.add(Toffoli(), ctrl=toff_ctrl, target=y_split[i]) + f4 = toff_ctrl[0] + b_split[i] = toff_ctrl[1] + b = bb.join(b_split, QMontgomeryUInt(self.n)) + y = bb.join(y_split, QMontgomeryUInt(self.n)) + + # Unset f4 if (x, y) = (a, b). + ab = bb.join(np.concatenate([bb.split(a), bb.split(b)]), dtype=QMontgomeryUInt(2 * self.n)) + xy = bb.join(np.concatenate([bb.split(x), bb.split(y)]), dtype=QMontgomeryUInt(2 * self.n)) + ab, xy, f4 = bb.add(Equals(QMontgomeryUInt(2 * self.n)), x=ab, y=xy, target=f4) + ab_split = bb.split(ab) + a = bb.join(ab_split[: int(self.n)], dtype=QMontgomeryUInt(self.n)) + b = bb.join(ab_split[int(self.n) :], dtype=QMontgomeryUInt(self.n)) + xy_split = bb.split(xy) + x = bb.join(xy_split[: int(self.n)], dtype=QMontgomeryUInt(self.n)) + y = bb.join(xy_split[int(self.n) :], dtype=QMontgomeryUInt(self.n)) + + # Unset f3 if (a, b) = (0, 0). + ab_arr = np.concatenate([bb.split(a), bb.split(b)]) + ab_arr, f3 = bb.add(MultiControlX(cvs=[0] * 2 * self.n), controls=ab_arr, target=f3) + ab_arr = np.split(ab_arr, 2) + a = bb.join(ab_arr[0], dtype=QMontgomeryUInt(self.n)) + b = bb.join(ab_arr[1], dtype=QMontgomeryUInt(self.n)) + + # If f1 and f2 are set, subtract a from x and add b to y. + ancilla = bb.add(ZeroState()) + toff_ctrl = [f1, f2] + toff_ctrl, ancilla = bb.add(Toffoli(), ctrl=toff_ctrl, target=ancilla) + ancilla, a, x = bb.add( + CModSub(QMontgomeryUInt(self.n), mod=self.mod), ctrl=ancilla, x=a, y=x + ) + toff_ctrl, ancilla = bb.add(Toffoli(), ctrl=toff_ctrl, target=ancilla) + f1 = toff_ctrl[0] + f2 = toff_ctrl[1] + bb.add(Free(QBit()), reg=ancilla) + ancilla = bb.add(ZeroState()) + toff_ctrl = [f1, f2] + toff_ctrl, ancilla = bb.add(Toffoli(), ctrl=toff_ctrl, target=ancilla) + ancilla, b, y = bb.add( + CModAdd(QMontgomeryUInt(self.n), mod=self.mod), ctrl=ancilla, x=b, y=y + ) + toff_ctrl, ancilla = bb.add(Toffoli(), ctrl=toff_ctrl, target=ancilla) + f1 = toff_ctrl[0] + f2 = toff_ctrl[1] + bb.add(Free(QBit()), reg=ancilla) + + # Unset f1 and f2 if (x, y) = (0, 0). + xy_arr = np.concatenate([bb.split(x), bb.split(y)]) + xy_arr, junk, out = bb.add(MultiAnd(cvs=[0] * 2 * self.n), ctrl=xy_arr) + targets = bb.join(np.array([f1, f2])) + out, targets = bb.add(MultiTargetCNOT(2), control=out, targets=targets) + targets = bb.split(targets) + f1 = targets[0] + f2 = targets[1] + xy_arr = bb.add( + MultiAnd(cvs=[0] * 2 * self.n).adjoint(), ctrl=xy_arr, junk=junk, target=out + ) + xy_arr = np.split(xy_arr, 2) + x = bb.join(xy_arr[0], dtype=QMontgomeryUInt(self.n)) + y = bb.join(xy_arr[1], dtype=QMontgomeryUInt(self.n)) + + # Free all ancilla qubits in the zero state. + # TODO(https://github.com/quantumlib/Qualtran/issues/1461): Fix bugs in circuit where f1, + # f2, and f4 are freed before being set to 0. + bb.add(Free(QBit(), dirty=True), reg=f1) + bb.add(Free(QBit(), dirty=True), reg=f2) + bb.add(Free(QBit()), reg=f3) + bb.add(Free(QBit(), dirty=True), reg=f4) + bb.add(Free(QBit()), reg=ctrl) + + # Return the output registers. + return {'a': a, 'b': b, 'x': x, 'y': y} + + def build_call_graph(self, ssa: SympySymbolAllocator) -> BloqCountDictT: + cvs: Union[list[int], HasLength] + if isinstance(self.n, int): + cvs = [0] * 2 * self.n + else: + cvs = HasLength(2 * self.n) + return { + MultiControlX(cvs=cvs): 1, + MultiControlX(cvs=[0] * 3): 1, + CModSub(QMontgomeryUInt(self.n), mod=self.mod): 1, + CModAdd(QMontgomeryUInt(self.n), mod=self.mod): 1, + Toffoli(): 2 * self.n + 4, + Equals(QMontgomeryUInt(2 * self.n)): 1, + MultiAnd(cvs=cvs): 1, + MultiTargetCNOT(2): 1, + MultiAnd(cvs=cvs).adjoint(): 1, + } @frozen @@ -30,52 +978,134 @@ class ECAdd(Bloq): This takes elliptic curve points given by (a, b) and (x, y) and outputs the sum (x_r, y_r) in the second pair of registers. + Because the decomposition of this Bloq is complex, we split it into six separate parts + corresponding to the parts described in figure 10 of the Litinski paper cited below. We follow + the signature from figure 5 and break down the further decompositions based on the steps in + figure 10. + Args: n: The bitsize of the two registers storing the elliptic curve point mod: The modulus of the field in which we do the addition. + window_size: The number of bits in the ModMult window. Registers: - a: The x component of the first input elliptic curve point of bitsize `n`. - b: The y component of the first input elliptic curve point of bitsize `n`. - x: The x component of the second input elliptic curve point of bitsize `n`, which + a: The x component of the first input elliptic curve point of bitsize `n` in montgomery form. + b: The y component of the first input elliptic curve point of bitsize `n` in montgomery form. + x: The x component of the second input elliptic curve point of bitsize `n` in montgomery form, which will contain the x component of the resultant curve point. - y: The y component of the second input elliptic curve point of bitsize `n`, which + y: The y component of the second input elliptic curve point of bitsize `n` in montgomery form, which will contain the y component of the resultant curve point. - lam: The precomputed lambda slope used in the addition operation. + lam_r: The precomputed lambda slope used in the addition operation if (a, b) = (x, y) in montgomery form. References: [How to compute a 256-bit elliptic curve private key with only 50 million Toffoli gates](https://arxiv.org/abs/2306.08585). Litinski. 2023. Fig 5. """ - n: int - mod: int + n: 'SymbolicInt' + mod: 'SymbolicInt' + window_size: 'SymbolicInt' = 1 @cached_property def signature(self) -> 'Signature': return Signature( [ - Register('a', QUInt(self.n)), - Register('b', QUInt(self.n)), - Register('x', QUInt(self.n)), - Register('y', QUInt(self.n)), - Register('lam', QUInt(self.n)), + Register('a', QMontgomeryUInt(self.n)), + Register('b', QMontgomeryUInt(self.n)), + Register('x', QMontgomeryUInt(self.n)), + Register('y', QMontgomeryUInt(self.n)), + Register('lam_r', QMontgomeryUInt(self.n)), ] ) + def build_composite_bloq( + self, bb: 'BloqBuilder', a: Soquet, b: Soquet, x: Soquet, y: Soquet, lam_r: Soquet + ) -> Dict[str, 'SoquetT']: + f1, f2, f3, f4, ctrl, a, b, x, y = bb.add( + _ECAddStepOne(n=self.n, mod=self.mod), a=a, b=b, x=x, y=y + ) + f1, ctrl, a, b, x, y, lam, lam_r = bb.add( + _ECAddStepTwo(n=self.n, mod=self.mod, window_size=self.window_size), + f1=f1, + ctrl=ctrl, + a=a, + b=b, + x=x, + y=y, + lam_r=lam_r, + ) + ctrl, a, b, x, y, lam = bb.add( + _ECAddStepThree(n=self.n, mod=self.mod, window_size=self.window_size), + ctrl=ctrl, + a=a, + b=b, + x=x, + y=y, + lam=lam, + ) + x, y, lam = bb.add( + _ECAddStepFour(n=self.n, mod=self.mod, window_size=self.window_size), x=x, y=y, lam=lam + ) + ctrl, a, b, x, y = bb.add( + _ECAddStepFive(n=self.n, mod=self.mod, window_size=self.window_size), + ctrl=ctrl, + a=a, + b=b, + x=x, + y=y, + lam=lam, + ) + a, b, x, y = bb.add( + _ECAddStepSix(n=self.n, mod=self.mod), + f1=f1, + f2=f2, + f3=f3, + f4=f4, + ctrl=ctrl, + a=a, + b=b, + x=x, + y=y, + ) + + return {'a': a, 'b': b, 'x': x, 'y': y, 'lam_r': lam_r} + + def on_classical_vals(self, a, b, x, y, lam_r) -> Dict[str, Union['ClassicalValT', sympy.Expr]]: + curve_a = ( + QMontgomeryUInt(self.n).montgomery_to_uint(lam_r, int(self.mod)) + * 2 + * QMontgomeryUInt(self.n).montgomery_to_uint(b, int(self.mod)) + - (3 * QMontgomeryUInt(self.n).montgomery_to_uint(a, int(self.mod)) ** 2) + ) % self.mod + p1 = ECPoint( + QMontgomeryUInt(self.n).montgomery_to_uint(a, int(self.mod)), + QMontgomeryUInt(self.n).montgomery_to_uint(b, int(self.mod)), + mod=self.mod, + curve_a=curve_a, + ) + p2 = ECPoint( + QMontgomeryUInt(self.n).montgomery_to_uint(x, int(self.mod)), + QMontgomeryUInt(self.n).montgomery_to_uint(y, int(self.mod)), + mod=self.mod, + curve_a=curve_a, + ) + result = p1 + p2 + return { + 'a': a, + 'b': b, + 'x': QMontgomeryUInt(self.n).uint_to_montgomery(result.x, int(self.mod)), + 'y': QMontgomeryUInt(self.n).uint_to_montgomery(result.y, int(self.mod)), + 'lam_r': lam_r, + } + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': - # litinksi return { - MultiCToffoli(n=self.n): 18, - ModAdd(bitsize=self.n, mod=self.mod): 3, - CModAdd(QUInt(self.n), mod=self.mod): 2, - ModSub(QUInt(self.n), mod=self.mod): 2, - CModSub(QUInt(self.n), mod=self.mod): 4, - ModNeg(QUInt(self.n), mod=self.mod): 2, - CModNeg(QUInt(self.n), mod=self.mod): 1, - ModDbl(QUInt(self.n), mod=self.mod): 2, - ModMul(n=self.n, mod=self.mod): 10, - ModInv(n=self.n, mod=self.mod): 4, + _ECAddStepOne(n=self.n, mod=self.mod): 1, + _ECAddStepTwo(n=self.n, mod=self.mod, window_size=self.window_size): 1, + _ECAddStepThree(n=self.n, mod=self.mod, window_size=self.window_size): 1, + _ECAddStepFour(n=self.n, mod=self.mod, window_size=self.window_size): 1, + _ECAddStepFive(n=self.n, mod=self.mod, window_size=self.window_size): 1, + _ECAddStepSix(n=self.n, mod=self.mod): 1, } @@ -86,4 +1116,10 @@ def _ec_add() -> ECAdd: return ec_add -_EC_ADD_DOC = BloqDocSpec(bloq_cls=ECAdd, examples=[_ec_add]) +@bloq_example +def _ec_add_small() -> ECAdd: + ec_add_small = ECAdd(5, mod=7) + return ec_add_small + + +_EC_ADD_DOC = BloqDocSpec(bloq_cls=ECAdd, examples=[_ec_add, _ec_add_small]) diff --git a/qualtran/bloqs/factoring/ecc/ec_add_r.py b/qualtran/bloqs/factoring/ecc/ec_add_r.py index 7596e120d..6bf020659 100644 --- a/qualtran/bloqs/factoring/ecc/ec_add_r.py +++ b/qualtran/bloqs/factoring/ecc/ec_add_r.py @@ -15,13 +15,30 @@ from functools import cached_property from typing import Dict, Optional, Tuple, Union +import numpy as np import sympy from attrs import frozen -from qualtran import Bloq, bloq_example, BloqDocSpec, QBit, QUInt, Register, Signature +from qualtran import ( + Bloq, + bloq_example, + BloqBuilder, + BloqDocSpec, + QBit, + QMontgomeryUInt, + QUInt, + Register, + Signature, + Soquet, + SoquetT, +) +from qualtran.bloqs.data_loading import QROAMClean from qualtran.drawing import Circle, Text, TextBox, WireSymbol +from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator from qualtran.simulation.classical_sim import ClassicalValT +from qualtran.symbolics import is_symbolic, Shaped, SymbolicInt +from .ec_add import ECAdd from .ec_point import ECPoint @@ -51,14 +68,14 @@ class ECAddR(Bloq): Litinski. 2023. Section 1, eq. (3) and (4). [Quantum resource estimates for computing elliptic curve discrete logarithms](https://arxiv.org/abs/1706.06752). - Roetteler et. al. 2017. Algorithm 1 and Figure 10. + Roetteler et al. 2017. Algorithm 1 and Figure 10. [https://github.com/microsoft/QuantumEllipticCurves/blob/dbf4836afaf7a9fab813cbc0970e65af85a6f93a/MicrosoftQuantumCrypto/EllipticCurves.qs#L456](QuantumQuantumCrypto). `DistinctEllipticCurveClassicalPointAddition`. """ - n: int + n: 'SymbolicInt' R: ECPoint @cached_property @@ -113,33 +130,134 @@ class ECWindowAddR(Bloq): Args: n: The bitsize of the two registers storing the elliptic curve point - window_size: The number of bits in the window. - R: The elliptic curve point to add. + R: The elliptic curve point to add (NOT in montgomery form). + add_window_size: The number of bits in the ECAdd window. + mul_window_size: The number of bits in the modular multiplication window. Registers: ctrl: `window_size` control bits. - x: The x component of the input elliptic curve point of bitsize `n`. - y: The y component of the input elliptic curve point of bitsize `n`. + x: The x component of the input elliptic curve point of bitsize `n` in montgomery form. + y: The y component of the input elliptic curve point of bitsize `n` in montgomery form. References: [How to compute a 256-bit elliptic curve private key with only 50 million Toffoli gates](https://arxiv.org/abs/2306.08585). Litinski. 2013. Section 1, eq. (3) and (4). """ - n: int - window_size: int + n: 'SymbolicInt' R: ECPoint + add_window_size: 'SymbolicInt' + mul_window_size: 'SymbolicInt' = 1 @cached_property def signature(self) -> 'Signature': return Signature( [ - Register('ctrl', QBit(), shape=(self.window_size,)), + Register('ctrl', QBit(), shape=(self.add_window_size,)), Register('x', QUInt(self.n)), Register('y', QUInt(self.n)), ] ) + @cached_property + def qrom(self) -> QROAMClean: + if is_symbolic(self.n) or is_symbolic(self.add_window_size): + log_block_sizes = None + if is_symbolic(self.n) and not is_symbolic(self.add_window_size): + # We assume that bitsize is much larger than window_size + log_block_sizes = (0,) + return QROAMClean( + [ + Shaped((2**self.add_window_size,)), + Shaped((2**self.add_window_size,)), + Shaped((2**self.add_window_size,)), + ], + selection_bitsizes=(self.add_window_size,), + target_bitsizes=(self.n, self.n, self.n), + log_block_sizes=log_block_sizes, + ) + + cR = self.R + data_a, data_b, data_lam = [0], [0], [0] + mon_int = QMontgomeryUInt(self.n) + for _ in range(1, 2**self.add_window_size): + data_a.append(mon_int.uint_to_montgomery(int(cR.x), int(self.R.mod))) + data_b.append(mon_int.uint_to_montgomery(int(cR.y), int(self.R.mod))) + lam_num = (3 * cR.x**2 + cR.curve_a) % cR.mod + lam_denom = (2 * cR.y) % cR.mod + if lam_denom != 0: + lam = (lam_num * pow(lam_denom, -1, mod=cR.mod)) % cR.mod + else: + lam = 0 + data_lam.append(mon_int.uint_to_montgomery(int(lam), int(self.R.mod))) + cR = cR + self.R + + return QROAMClean( + [data_a, data_b, data_lam], + selection_bitsizes=(self.add_window_size,), + target_bitsizes=(self.n, self.n, self.n), + ) + + def build_composite_bloq( + self, bb: 'BloqBuilder', ctrl: 'SoquetT', x: 'Soquet', y: 'Soquet' + ) -> Dict[str, 'SoquetT']: + ctrl = bb.join(np.array(ctrl)) + + ctrl, a, b, lam_r, *junk = bb.add(self.qrom, selection=ctrl) + + a, b, x, y, lam_r = bb.add( + # TODO(https://github.com/quantumlib/Qualtran/issues/1476): make ECAdd accept SymbolicInt. + ECAdd(n=self.n, mod=int(self.R.mod), window_size=self.mul_window_size), + a=a, + b=b, + x=x, + y=y, + lam_r=lam_r, + ) + + if junk: + assert len(junk) == 3 + ctrl = bb.add( + self.qrom.adjoint(), + selection=ctrl, + target0_=a, + target1_=b, + target2_=lam_r, + junk_target0_=junk[0], + junk_target1_=junk[1], + junk_target2_=junk[2], + ) + else: + ctrl = bb.add( + self.qrom.adjoint(), selection=ctrl, target0_=a, target1_=b, target2_=lam_r + ) + + return {'ctrl': bb.split(ctrl), 'x': x, 'y': y} + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': + return { + self.qrom: 1, + # TODO(https://github.com/quantumlib/Qualtran/issues/1476): make ECAdd accept SymbolicInt. + ECAdd(self.n, int(self.R.mod), self.mul_window_size): 1, + self.qrom.adjoint(): 1, + } + + def on_classical_vals(self, ctrl, x, y) -> Dict[str, Union['ClassicalValT', sympy.Expr]]: + # TODO(https://github.com/quantumlib/Qualtran/issues/1476): make ECAdd accept SymbolicInt. + A = ECPoint( + QMontgomeryUInt(self.n).montgomery_to_uint(int(x), int(self.R.mod)), + QMontgomeryUInt(self.n).montgomery_to_uint(int(y), int(self.R.mod)), + mod=self.R.mod, + curve_a=self.R.curve_a, + ) + ctrls = QUInt(self.n).from_bits(ctrl) + result: ECPoint = A + (ctrls * self.R) + return { + 'ctrl': ctrl, + 'x': QMontgomeryUInt(self.n).uint_to_montgomery(int(result.x), int(self.R.mod)), + 'y': QMontgomeryUInt(self.n).uint_to_montgomery(int(result.y), int(self.R.mod)), + } + def wire_symbol( self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() ) -> 'WireSymbol': @@ -153,16 +271,13 @@ def wire_symbol( return TextBox(f'$+{self.R.y}$') raise ValueError(f'Unrecognized register name {reg.name}') - def __str__(self): - return f'ECWindowAddR({self.n=})' - @bloq_example -def _ec_window_add() -> ECWindowAddR: - n, p = sympy.symbols('n p') - Rx, Ry = sympy.symbols('Rx Ry') - ec_window_add = ECWindowAddR(n=n, window_size=3, R=ECPoint(Rx, Ry, mod=p)) - return ec_window_add +def _ec_window_add_r_small() -> ECWindowAddR: + n = 16 + P = ECPoint(2, 2, mod=7, curve_a=3) + ec_window_add_r_small = ECWindowAddR(n=n, R=P, add_window_size=4) + return ec_window_add_r_small -_EC_WINDOW_ADD_BLOQ_DOC = BloqDocSpec(bloq_cls=ECWindowAddR, examples=[_ec_window_add]) +_EC_WINDOW_ADD_BLOQ_DOC = BloqDocSpec(bloq_cls=ECWindowAddR, examples=[_ec_window_add_r_small]) diff --git a/qualtran/bloqs/factoring/ecc/ec_add_r_test.py b/qualtran/bloqs/factoring/ecc/ec_add_r_test.py index f36ec4e11..65e437811 100644 --- a/qualtran/bloqs/factoring/ecc/ec_add_r_test.py +++ b/qualtran/bloqs/factoring/ecc/ec_add_r_test.py @@ -12,16 +12,79 @@ # See the License for the specific language governing permissions and # limitations under the License. -from qualtran.bloqs.factoring.ecc.ec_add_r import _ec_add_r, _ec_add_r_small, _ec_window_add +import numpy as np +import pytest +import qualtran.testing as qlt_testing +from qualtran import QMontgomeryUInt, QUInt +from qualtran.bloqs.factoring.ecc.ec_add_r import ( + _ec_add_r, + _ec_add_r_small, + _ec_window_add_r_small, + ECWindowAddR, +) +from qualtran.resource_counting.generalizers import ignore_alloc_free, ignore_split_join -def test_ec_add_r(bloq_autotester): - bloq_autotester(_ec_add_r) +from .ec_add_r import ECWindowAddR +from .ec_point import ECPoint -def test_ec_add_r_small(bloq_autotester): - bloq_autotester(_ec_add_r_small) +@pytest.mark.parametrize('bloq', [_ec_add_r, _ec_add_r_small, _ec_window_add_r_small]) +def test_ec_add_r(bloq_autotester, bloq): + bloq_autotester(bloq) -def test_ec_window_add(bloq_autotester): - bloq_autotester(_ec_window_add) +@pytest.mark.parametrize('a,b', [(15, 13), (0, 0)]) +@pytest.mark.parametrize( + ['n', 'window_size'], + [ + (n, window_size) + for n in range(5, 8) + for window_size in range(1, n + 1) + if n % window_size == 0 + ], +) +def test_ec_window_add_r_bloq_counts(n, window_size, a, b): + p = 17 + R = ECPoint(a, b, mod=p) + bloq = ECWindowAddR(n=n, R=R, add_window_size=window_size) + qlt_testing.assert_equivalent_bloq_counts(bloq, [ignore_alloc_free, ignore_split_join]) + + +@pytest.mark.parametrize( + ['n', 'm'], [(n, m) for n in range(4, 5) for m in range(1, n + 1) if n % m == 0] +) +@pytest.mark.parametrize('a,b', [(15, 13), (0, 0)]) +@pytest.mark.parametrize('x,y', [(15, 13), (5, 8)]) +@pytest.mark.parametrize('ctrl', [0, 1, 5]) +def test_ec_window_add_r_classical(n, m, ctrl, x, y, a, b): + p = 17 + R = ECPoint(a, b, mod=p) + x = QMontgomeryUInt(n).uint_to_montgomery(x, p) + y = QMontgomeryUInt(n).uint_to_montgomery(y, p) + ctrl = np.array(QUInt(m).to_bits(ctrl % (2**m))) + bloq = ECWindowAddR(n=n, R=R, add_window_size=m, mul_window_size=m) + ret1 = bloq.call_classically(ctrl=ctrl, x=x, y=y) + ret2 = bloq.decompose_bloq().call_classically(ctrl=ctrl, x=x, y=y) + for i, ret1_i in enumerate(ret1): + np.testing.assert_array_equal(ret1_i, ret2[i]) + + +@pytest.mark.slow +@pytest.mark.parametrize( + ['n', 'm'], [(n, m) for n in range(7, 9) for m in range(1, n + 1) if n % m == 0] +) +@pytest.mark.parametrize('a,b', [(15, 13), (0, 0)]) +@pytest.mark.parametrize('x,y', [(15, 13), (5, 8)]) +@pytest.mark.parametrize('ctrl', [0, 1, 5, 8]) +def test_ec_window_add_r_classical_slow(n, m, ctrl, x, y, a, b): + p = 17 + R = ECPoint(a, b, mod=p) + x = QMontgomeryUInt(n).uint_to_montgomery(x, p) + y = QMontgomeryUInt(n).uint_to_montgomery(y, p) + ctrl = np.array(QUInt(m).to_bits(ctrl % (2**m))) + bloq = ECWindowAddR(n=n, R=R, add_window_size=m, mul_window_size=m) + ret1 = bloq.call_classically(ctrl=ctrl, x=x, y=y) + ret2 = bloq.decompose_bloq().call_classically(ctrl=ctrl, x=x, y=y) + for i, ret1_i in enumerate(ret1): + np.testing.assert_array_equal(ret1_i, ret2[i]) diff --git a/qualtran/bloqs/factoring/ecc/ec_add_test.py b/qualtran/bloqs/factoring/ecc/ec_add_test.py index 44a8b77e1..7f7439d2d 100644 --- a/qualtran/bloqs/factoring/ecc/ec_add_test.py +++ b/qualtran/bloqs/factoring/ecc/ec_add_test.py @@ -12,13 +12,422 @@ # See the License for the specific language governing permissions and # limitations under the License. +import pytest +import sympy + import qualtran.testing as qlt_testing -from qualtran.bloqs.factoring.ecc.ec_add import _ec_add +from qualtran._infra.data_types import QMontgomeryUInt +from qualtran.bloqs.factoring.ecc.ec_add import ( + _ec_add, + _ec_add_small, + _ECAddStepFive, + _ECAddStepFour, + _ECAddStepOne, + _ECAddStepSix, + _ECAddStepThree, + _ECAddStepTwo, + ECAdd, +) +from qualtran.resource_counting._bloq_counts import QECGatesCost +from qualtran.resource_counting._costing import get_cost_value +from qualtran.resource_counting.generalizers import ignore_alloc_free, ignore_split_join + + +@pytest.mark.parametrize( + ['n', 'm'], [(n, m) for n in range(7, 8) for m in range(1, n + 1) if n % m == 0] +) +@pytest.mark.parametrize('a,b', [(15, 13), (2, 10)]) +@pytest.mark.parametrize('x,y', [(15, 13), (0, 0)]) +def test_ec_add_steps_classical_fast(n, m, a, b, x, y): + p = 17 + lam_num = (3 * a**2) % p + lam_denom = (2 * b) % p + lam_r = 0 if b == 0 else (lam_num * pow(lam_denom, -1, mod=p)) % p + + a = QMontgomeryUInt(n).uint_to_montgomery(a, p) + b = QMontgomeryUInt(n).uint_to_montgomery(b, p) + x = QMontgomeryUInt(n).uint_to_montgomery(x, p) + y = QMontgomeryUInt(n).uint_to_montgomery(y, p) + lam_r = QMontgomeryUInt(n).uint_to_montgomery(lam_r, p) if lam_r != 0 else p + + bloq = _ECAddStepOne(n=n, mod=p) + ret1 = bloq.call_classically(a=a, b=b, x=x, y=y) + ret2 = bloq.decompose_bloq().call_classically(a=a, b=b, x=x, y=y) + assert ret1 == ret2 + + step_1 = _ECAddStepOne(n=n, mod=p).on_classical_vals(a=a, b=b, x=x, y=y) + bloq = _ECAddStepTwo(n=n, mod=p, window_size=m) + ret1 = bloq.call_classically( + f1=step_1['f1'], ctrl=step_1['ctrl'], a=a, b=b, x=x, y=y, lam_r=lam_r + ) + ret2 = bloq.decompose_bloq().call_classically( + f1=step_1['f1'], ctrl=step_1['ctrl'], a=a, b=b, x=x, y=y, lam_r=lam_r + ) + assert ret1 == ret2 + + step_2 = _ECAddStepTwo(n=n, mod=p, window_size=m).on_classical_vals( + f1=step_1['f1'], ctrl=step_1['ctrl'], a=a, b=b, x=x, y=y, lam_r=lam_r + ) + bloq = _ECAddStepThree(n=n, mod=p, window_size=m) + ret1 = bloq.call_classically( + ctrl=step_2['ctrl'], + a=step_2['a'], + b=step_2['b'], + x=step_2['x'], + y=step_2['y'], + lam=step_2['lam'], + ) + ret2 = bloq.decompose_bloq().call_classically( + ctrl=step_2['ctrl'], + a=step_2['a'], + b=step_2['b'], + x=step_2['x'], + y=step_2['y'], + lam=step_2['lam'], + ) + assert ret1 == ret2 + + step_3 = _ECAddStepThree(n=n, mod=p, window_size=m).on_classical_vals( + ctrl=step_2['ctrl'], + a=step_2['a'], + b=step_2['b'], + x=step_2['x'], + y=step_2['y'], + lam=step_2['lam'], + ) + bloq = _ECAddStepFour(n=n, mod=p, window_size=m) + ret1 = bloq.call_classically(x=step_3['x'], y=step_3['y'], lam=step_3['lam']) + ret2 = bloq.decompose_bloq().call_classically(x=step_3['x'], y=step_3['y'], lam=step_3['lam']) + assert ret1 == ret2 + + step_4 = _ECAddStepFour(n=n, mod=p, window_size=m).on_classical_vals( + x=step_3['x'], y=step_3['y'], lam=step_3['lam'] + ) + bloq = _ECAddStepFive(n=n, mod=p, window_size=m) + ret1 = bloq.call_classically( + ctrl=step_3['ctrl'], + a=step_3['a'], + b=step_3['b'], + x=step_4['x'], + y=step_4['y'], + lam=step_4['lam'], + ) + ret2 = bloq.decompose_bloq().call_classically( + ctrl=step_3['ctrl'], + a=step_3['a'], + b=step_3['b'], + x=step_4['x'], + y=step_4['y'], + lam=step_4['lam'], + ) + assert ret1 == ret2 + + step_5 = _ECAddStepFive(n=n, mod=p, window_size=m).on_classical_vals( + ctrl=step_3['ctrl'], + a=step_3['a'], + b=step_3['b'], + x=step_4['x'], + y=step_4['y'], + lam=step_4['lam'], + ) + bloq = _ECAddStepSix(n=n, mod=p) + ret1 = bloq.call_classically( + f1=step_2['f1'], + f2=step_1['f2'], + f3=step_1['f3'], + f4=step_1['f4'], + ctrl=step_5['ctrl'], + a=step_5['a'], + b=step_5['b'], + x=step_5['x'], + y=step_5['y'], + ) + ret2 = bloq.decompose_bloq().call_classically( + f1=step_2['f1'], + f2=step_1['f2'], + f3=step_1['f3'], + f4=step_1['f4'], + ctrl=step_5['ctrl'], + a=step_5['a'], + b=step_5['b'], + x=step_5['x'], + y=step_5['y'], + ) + assert ret1 == ret2 + + +@pytest.mark.slow +@pytest.mark.parametrize( + ['n', 'm'], [(n, m) for n in range(7, 9) for m in range(1, n + 1) if n % m == 0] +) +@pytest.mark.parametrize( + 'a,b', + [ + (15, 13), + (2, 10), + (8, 3), + (12, 1), + (6, 6), + (5, 8), + (10, 15), + (1, 12), + (3, 0), + (1, 5), + (10, 2), + (0, 0), + ], +) +@pytest.mark.parametrize('x,y', [(15, 13), (5, 8), (10, 15), (1, 12), (3, 0), (1, 5), (10, 2)]) +def test_ec_add_steps_classical(n, m, a, b, x, y): + p = 17 + lam_num = (3 * a**2) % p + lam_denom = (2 * b) % p + lam_r = 0 if b == 0 else (lam_num * pow(lam_denom, -1, mod=p)) % p + + a = QMontgomeryUInt(n).uint_to_montgomery(a, p) + b = QMontgomeryUInt(n).uint_to_montgomery(b, p) + x = QMontgomeryUInt(n).uint_to_montgomery(x, p) + y = QMontgomeryUInt(n).uint_to_montgomery(y, p) + lam_r = QMontgomeryUInt(n).uint_to_montgomery(lam_r, p) if lam_r != 0 else p + + bloq = _ECAddStepOne(n=n, mod=p) + ret1 = bloq.call_classically(a=a, b=b, x=x, y=y) + ret2 = bloq.decompose_bloq().call_classically(a=a, b=b, x=x, y=y) + assert ret1 == ret2 + + step_1 = _ECAddStepOne(n=n, mod=p).on_classical_vals(a=a, b=b, x=x, y=y) + bloq = _ECAddStepTwo(n=n, mod=p, window_size=m) + ret1 = bloq.call_classically( + f1=step_1['f1'], ctrl=step_1['ctrl'], a=a, b=b, x=x, y=y, lam_r=lam_r + ) + ret2 = bloq.decompose_bloq().call_classically( + f1=step_1['f1'], ctrl=step_1['ctrl'], a=a, b=b, x=x, y=y, lam_r=lam_r + ) + assert ret1 == ret2 + + step_2 = _ECAddStepTwo(n=n, mod=p, window_size=m).on_classical_vals( + f1=step_1['f1'], ctrl=step_1['ctrl'], a=a, b=b, x=x, y=y, lam_r=lam_r + ) + bloq = _ECAddStepThree(n=n, mod=p, window_size=m) + ret1 = bloq.call_classically( + ctrl=step_2['ctrl'], + a=step_2['a'], + b=step_2['b'], + x=step_2['x'], + y=step_2['y'], + lam=step_2['lam'], + ) + ret2 = bloq.decompose_bloq().call_classically( + ctrl=step_2['ctrl'], + a=step_2['a'], + b=step_2['b'], + x=step_2['x'], + y=step_2['y'], + lam=step_2['lam'], + ) + assert ret1 == ret2 + + step_3 = _ECAddStepThree(n=n, mod=p, window_size=m).on_classical_vals( + ctrl=step_2['ctrl'], + a=step_2['a'], + b=step_2['b'], + x=step_2['x'], + y=step_2['y'], + lam=step_2['lam'], + ) + bloq = _ECAddStepFour(n=n, mod=p, window_size=m) + ret1 = bloq.call_classically(x=step_3['x'], y=step_3['y'], lam=step_3['lam']) + ret2 = bloq.decompose_bloq().call_classically(x=step_3['x'], y=step_3['y'], lam=step_3['lam']) + assert ret1 == ret2 + + step_4 = _ECAddStepFour(n=n, mod=p, window_size=m).on_classical_vals( + x=step_3['x'], y=step_3['y'], lam=step_3['lam'] + ) + bloq = _ECAddStepFive(n=n, mod=p, window_size=m) + ret1 = bloq.call_classically( + ctrl=step_3['ctrl'], + a=step_3['a'], + b=step_3['b'], + x=step_4['x'], + y=step_4['y'], + lam=step_4['lam'], + ) + ret2 = bloq.decompose_bloq().call_classically( + ctrl=step_3['ctrl'], + a=step_3['a'], + b=step_3['b'], + x=step_4['x'], + y=step_4['y'], + lam=step_4['lam'], + ) + assert ret1 == ret2 + + step_5 = _ECAddStepFive(n=n, mod=p, window_size=m).on_classical_vals( + ctrl=step_3['ctrl'], + a=step_3['a'], + b=step_3['b'], + x=step_4['x'], + y=step_4['y'], + lam=step_4['lam'], + ) + bloq = _ECAddStepSix(n=n, mod=p) + ret1 = bloq.call_classically( + f1=step_2['f1'], + f2=step_1['f2'], + f3=step_1['f3'], + f4=step_1['f4'], + ctrl=step_5['ctrl'], + a=step_5['a'], + b=step_5['b'], + x=step_5['x'], + y=step_5['y'], + ) + ret2 = bloq.decompose_bloq().call_classically( + f1=step_2['f1'], + f2=step_1['f2'], + f3=step_1['f3'], + f4=step_1['f4'], + ctrl=step_5['ctrl'], + a=step_5['a'], + b=step_5['b'], + x=step_5['x'], + y=step_5['y'], + ) + assert ret1 == ret2 + + +@pytest.mark.parametrize( + ['n', 'm'], [(n, m) for n in range(7, 8) for m in range(1, n + 1) if n % m == 0] +) +@pytest.mark.parametrize('a,b', [(15, 13), (2, 10)]) +@pytest.mark.parametrize('x,y', [(15, 13), (0, 0)]) +def test_ec_add_classical_fast(n, m, a, b, x, y): + p = 17 + bloq = ECAdd(n=n, mod=p, window_size=m) + lam_num = (3 * a**2) % p + lam_denom = (2 * b) % p + lam_r = p if b == 0 else (lam_num * pow(lam_denom, -1, mod=p)) % p + ret1 = bloq.call_classically( + a=QMontgomeryUInt(n).uint_to_montgomery(a, p), + b=QMontgomeryUInt(n).uint_to_montgomery(b, p), + x=QMontgomeryUInt(n).uint_to_montgomery(x, p), + y=QMontgomeryUInt(n).uint_to_montgomery(y, p), + lam_r=QMontgomeryUInt(n).uint_to_montgomery(lam_r, p), + ) + ret2 = bloq.decompose_bloq().call_classically( + a=QMontgomeryUInt(n).uint_to_montgomery(a, p), + b=QMontgomeryUInt(n).uint_to_montgomery(b, p), + x=QMontgomeryUInt(n).uint_to_montgomery(x, p), + y=QMontgomeryUInt(n).uint_to_montgomery(y, p), + lam_r=QMontgomeryUInt(n).uint_to_montgomery(lam_r, p), + ) + assert ret1 == ret2 + + +@pytest.mark.slow +@pytest.mark.parametrize( + ['n', 'm'], [(n, m) for n in range(7, 9) for m in range(1, n + 1) if n % m == 0] +) +@pytest.mark.parametrize( + 'a,b', + [ + (15, 13), + (2, 10), + (8, 3), + (12, 1), + (6, 6), + (5, 8), + (10, 15), + (1, 12), + (3, 0), + (1, 5), + (10, 2), + (0, 0), + ], +) +@pytest.mark.parametrize('x,y', [(15, 13), (5, 8), (10, 15), (1, 12), (3, 0), (1, 5), (10, 2)]) +def test_ec_add_classical(n, m, a, b, x, y): + p = 17 + bloq = ECAdd(n=n, mod=p, window_size=m) + lam_num = (3 * a**2) % p + lam_denom = (2 * b) % p + lam_r = p if b == 0 else (lam_num * pow(lam_denom, -1, mod=p)) % p + ret1 = bloq.call_classically( + a=QMontgomeryUInt(n).uint_to_montgomery(a, p), + b=QMontgomeryUInt(n).uint_to_montgomery(b, p), + x=QMontgomeryUInt(n).uint_to_montgomery(x, p), + y=QMontgomeryUInt(n).uint_to_montgomery(y, p), + lam_r=QMontgomeryUInt(n).uint_to_montgomery(lam_r, p), + ) + ret2 = bloq.decompose_bloq().call_classically( + a=QMontgomeryUInt(n).uint_to_montgomery(a, p), + b=QMontgomeryUInt(n).uint_to_montgomery(b, p), + x=QMontgomeryUInt(n).uint_to_montgomery(x, p), + y=QMontgomeryUInt(n).uint_to_montgomery(y, p), + lam_r=QMontgomeryUInt(n).uint_to_montgomery(lam_r, p), + ) + assert ret1 == ret2 + + +@pytest.mark.parametrize('p', (7, 9, 11)) +@pytest.mark.parametrize( + ['n', 'window_size'], + [ + (n, window_size) + for n in range(5, 8) + for window_size in range(1, n + 1) + if n % window_size == 0 + ], +) +def test_ec_add_decomposition(n, window_size, p): + b = ECAdd(n=n, window_size=window_size, mod=p) + qlt_testing.assert_valid_bloq_decomposition(b) + + +@pytest.mark.parametrize('p', (7, 9, 11)) +@pytest.mark.parametrize( + ['n', 'window_size'], + [ + (n, window_size) + for n in range(5, 8) + for window_size in range(1, n + 1) + if n % window_size == 0 + ], +) +def test_ec_add_bloq_counts(n, window_size, p): + b = ECAdd(n=n, window_size=window_size, mod=p) + qlt_testing.assert_equivalent_bloq_counts(b, [ignore_alloc_free, ignore_split_join]) + + +def test_ec_add_symbolic_cost(): + n, m, p = sympy.symbols('n m p', integer=True) + + # In Litinski 2023 https://arxiv.org/abs/2306.08585 a window size of 4 is used. + # The cost function generally has floor/ceil division that disappear for bitsize=0 mod 4. + # This is why instead of using bitsize=n directly, we use bitsize=4*m=n. + b = ECAdd(n=4 * m, window_size=4, mod=p) + cost = get_cost_value(b, QECGatesCost()).total_t_and_ccz_count() + # We have some T gates since we use CSwapApprox instead of n CSWAPs in KaliskiModInverse. + total_toff = (cost['n_t'] / 4 + cost['n_ccz']) * sympy.Integer(1) + total_toff = total_toff.subs(m, n / 4).expand() + + # Litinski 2023 https://arxiv.org/abs/2306.08585 + # Based on the counts from Figures 3, 5, and 8 the toffoli count for ECAdd is 126.5n^2 + 189n. + # The following formula is 126.5n^2 + 195.5n - 31. We account for the discrepancy in the + # coefficient of n by a reduction in the toffoli cost of Montgomery ModMult, an increase in the + # toffoli cost for Kaliski Mod Inverse, n extra toffolis in ModNeg, 2n extra toffolis to do n + # 3-controlled toffolis in step 2. The expression is written with rationals because sympy + # comparison fails with floats. + assert total_toff == sympy.Rational(253, 2) * n**2 + sympy.Rational(407, 2) * n - 31 def test_ec_add(bloq_autotester): bloq_autotester(_ec_add) +def test_ec_add_small(bloq_autotester): + bloq_autotester(_ec_add_small) + + def test_notebook(): qlt_testing.execute_notebook('ec_add') diff --git a/qualtran/bloqs/factoring/ecc/ec_phase_estimate_r.py b/qualtran/bloqs/factoring/ecc/ec_phase_estimate_r.py index f4ddb9d19..eb991c19a 100644 --- a/qualtran/bloqs/factoring/ecc/ec_phase_estimate_r.py +++ b/qualtran/bloqs/factoring/ecc/ec_phase_estimate_r.py @@ -12,9 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +import functools from functools import cached_property -from typing import Dict +from typing import Dict, Union +import numpy as np import sympy from attrs import frozen @@ -32,9 +34,10 @@ ) from qualtran.bloqs.basic_gates import PlusState from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator +from qualtran.symbolics.types import SymbolicInt -from ._ecc_shims import MeasureQFT -from .ec_add_r import ECAddR +from .._factoring_shims import MeasureQFT +from .ec_add_r import ECAddR, ECWindowAddR from .ec_point import ECPoint @@ -45,30 +48,65 @@ class ECPhaseEstimateR(Bloq): This is used as a subroutine in `FindECCPrivateKey`. First, we phase-estimate the addition of the base point $P$, then of the public key $Q$. + When the ellptic curve point addition window size is 1 we use the ECAddR bloq which has it's + own bespoke circuit; when it is greater than 1 we use the windowed circuit which uses + pre-computed classical additions loaded into the circuit. + Args: n: The bitsize of the elliptic curve points' x and y registers. point: The elliptic curve point to phase estimate against. + add_window_size: The number of bits in the ECAdd window. + mul_window_size: The number of bits in the modular multiplication window. """ - n: int + n: 'SymbolicInt' point: ECPoint + add_window_size: 'SymbolicInt' = 1 + mul_window_size: 'SymbolicInt' = 1 @cached_property def signature(self) -> 'Signature': return Signature([Register('x', QUInt(self.n)), Register('y', QUInt(self.n))]) + @property + def ec_add(self) -> Union[functools.partial[ECAddR], functools.partial[ECWindowAddR]]: + if self.add_window_size == 1: + return functools.partial(ECAddR, n=self.n) + return functools.partial( + ECWindowAddR, + n=self.n, + add_window_size=self.add_window_size, + mul_window_size=self.mul_window_size, + ) + + @property + def num_windows(self) -> int: + return self.n // self.add_window_size + def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet, y: Soquet) -> Dict[str, 'SoquetT']: if isinstance(self.n, sympy.Expr): raise DecomposeTypeError("Cannot decompose symbolic `n`.") ctrl = [bb.add(PlusState()) for _ in range(self.n)] - for i in range(self.n): - ctrl[i], x, y = bb.add(ECAddR(n=self.n, R=2**i * self.point), ctrl=ctrl[i], x=x, y=y) + + if self.add_window_size == 1: + for i in range(self.n): + ctrl[i], x, y = bb.add(self.ec_add(R=2**i * self.point), ctrl=ctrl[i], x=x, y=y) + else: + ctrls = np.split(np.array(ctrl), self.num_windows) + for i in range(self.num_windows): + ctrls[i], x, y = bb.add( + self.ec_add(R=2 ** (self.add_window_size * i) * self.point), + ctrl=ctrls[i], + x=x, + y=y, + ) + ctrl = np.concatenate(ctrls, axis=None) bb.add(MeasureQFT(n=self.n), x=ctrl) return {'x': x, 'y': y} def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': - return {ECAddR(n=self.n, R=self.point): self.n, MeasureQFT(n=self.n): 1} + return {self.ec_add(R=self.point): self.num_windows, MeasureQFT(n=self.n): 1} def __str__(self) -> str: return f'PE${self.point}$' @@ -76,7 +114,7 @@ def __str__(self) -> str: @bloq_example def _ec_pe() -> ECPhaseEstimateR: - n, p = sympy.symbols('n p ') + n, p = sympy.symbols('n p') Rx, Ry = sympy.symbols('R_x R_y') ec_pe = ECPhaseEstimateR(n=n, point=ECPoint(Rx, Ry, mod=p)) return ec_pe @@ -90,4 +128,4 @@ def _ec_pe_small() -> ECPhaseEstimateR: return ec_pe_small -_EC_PE_BLOQ_DOC = BloqDocSpec(bloq_cls=ECPhaseEstimateR, examples=[_ec_pe]) +_EC_PE_BLOQ_DOC = BloqDocSpec(bloq_cls=ECPhaseEstimateR, examples=[_ec_pe, _ec_pe_small]) diff --git a/qualtran/bloqs/factoring/ecc/ec_point.py b/qualtran/bloqs/factoring/ecc/ec_point.py index 968ebe0b5..eb885c54c 100644 --- a/qualtran/bloqs/factoring/ecc/ec_point.py +++ b/qualtran/bloqs/factoring/ecc/ec_point.py @@ -12,9 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. +import sympy from attrs import frozen -from qualtran.symbolics import SymbolicInt +from qualtran.symbolics import is_symbolic, SymbolicInt + +ec_times_x = sympy.Function("ec_times_x") +ec_times_y = sympy.Function("ec_times_y") +"""Support purely-symbolic ECPoint operations. +https://docs.sympy.org/latest/guides/custom-functions.html#easy-cases-fully-symbolic-or-fully-evaluated +""" @frozen @@ -63,15 +70,26 @@ def __add__(self, other): lam_num = (other.y - self.y) % self.mod lam_denom = (other.x - self.x) % self.mod - lam = (lam_num * pow(lam_denom, -1, mod=self.mod)) % self.mod + lam = (lam_num * pow(int(lam_denom), -1, mod=int(self.mod))) % self.mod xr = (lam**2 - other.x - self.x) % self.mod yr = (lam * (self.x - xr) - self.y) % self.mod return ECPoint(xr, yr, mod=self.mod, curve_a=self.curve_a) - def __mul__(self, other): - assert other > 0, other + def __mul__(self, times): + if times == 0: + return ECPoint.inf(mod=self.mod, curve_a=self.curve_a) + if is_symbolic(self.x, self.y): + # Symbolic case: use sympy.Function to opaquely represent the + # multiplication operation + return ECPoint( + ec_times_x(self.x, times), + ec_times_y(self.y, times), + mod=self.mod, + curve_a=self.curve_a, + ) + # Otherwise, multiplication by an integer is repeated addition x = self - for _ in range(other - 1): + for _ in range(times - 1): x = x + self return x diff --git a/qualtran/bloqs/factoring/ecc/ec_point_test.py b/qualtran/bloqs/factoring/ecc/ec_point_test.py index f65981c80..1ac1b59bd 100644 --- a/qualtran/bloqs/factoring/ecc/ec_point_test.py +++ b/qualtran/bloqs/factoring/ecc/ec_point_test.py @@ -21,6 +21,7 @@ def test_ec_point_overrides(): assert 1 * p == p assert 2 * p == (p + p) assert 3 * p == (p + p + p) + assert 0 * p == ECPoint.inf(mod=17, curve_a=0) def test_ec_point_addition(): diff --git a/qualtran/bloqs/factoring/ecc/ecc.ipynb b/qualtran/bloqs/factoring/ecc/ecc.ipynb index a9196721b..33e82abca 100644 --- a/qualtran/bloqs/factoring/ecc/ecc.ipynb +++ b/qualtran/bloqs/factoring/ecc/ecc.ipynb @@ -89,7 +89,9 @@ "#### Parameters\n", " - `n`: The bitsize of the elliptic curve points' x and y registers.\n", " - `base_point`: The base point $P$ with unknown order $r$ such that $P = [r] P$.\n", - " - `public_key`: The public key $Q$ such that $Q = [k] P$ for private key $k$. \n", + " - `public_key`: The public key $Q$ such that $Q = [k] P$ for private key $k$.\n", + " - `add_window_size`: The number of bits in the ECAdd window.\n", + " - `mul_window_size`: The number of bits in the modular multiplication window. \n", "\n", "#### References\n", " - [How to compute a 256-bit elliptic curve private key with only 50 million Toffoli gates](https://arxiv.org/abs/2306.08585). Litinski. 2023. Figure 4 (a).\n" @@ -215,9 +217,15 @@ "This is used as a subroutine in `FindECCPrivateKey`. First, we phase-estimate the\n", "addition of the base point $P$, then of the public key $Q$.\n", "\n", + "When the ellptic curve point addition window size is 1 we use the ECAddR bloq which has it's\n", + "own bespoke circuit; when it is greater than 1 we use the windowed circuit which uses\n", + "pre-computed classical additions loaded into the circuit.\n", + "\n", "#### Parameters\n", " - `n`: The bitsize of the elliptic curve points' x and y registers.\n", - " - `point`: The elliptic curve point to phase estimate against.\n" + " - `point`: The elliptic curve point to phase estimate against.\n", + " - `add_window_size`: The number of bits in the ECAdd window.\n", + " - `mul_window_size`: The number of bits in the modular multiplication window.\n" ] }, { @@ -251,11 +259,25 @@ }, "outputs": [], "source": [ - "n, p = sympy.symbols('n p ')\n", + "n, p = sympy.symbols('n p')\n", "Rx, Ry = sympy.symbols('R_x R_y')\n", "ec_pe = ECPhaseEstimateR(n=n, point=ECPoint(Rx, Ry, mod=p))" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "4616d33a", + "metadata": { + "cq.autogen": "ECPhaseEstimateR.ec_pe_small" + }, + "outputs": [], + "source": [ + "n = 3\n", + "Rx, Ry, p = sympy.symbols('R_x R_y p')\n", + "ec_pe_small = ECPhaseEstimateR(n=n, point=ECPoint(Rx, Ry, mod=p))" + ] + }, { "cell_type": "markdown", "id": "33824ce4", @@ -276,8 +298,8 @@ "outputs": [], "source": [ "from qualtran.drawing import show_bloqs\n", - "show_bloqs([ec_pe],\n", - " ['`ec_pe`'])" + "show_bloqs([ec_pe, ec_pe_small],\n", + " ['`ec_pe`', '`ec_pe_small`'])" ] }, { @@ -334,7 +356,7 @@ "\n", "#### References\n", " - [How to compute a 256-bit elliptic curve private key with only 50 million Toffoli gates](https://arxiv.org/abs/2306.08585). Litinski. 2023. Section 1, eq. (3) and (4).\n", - " - [Quantum resource estimates for computing elliptic curve discrete logarithms](https://arxiv.org/abs/1706.06752). Roetteler et. al. 2017. Algorithm 1 and Figure 10.\n", + " - [Quantum resource estimates for computing elliptic curve discrete logarithms](https://arxiv.org/abs/1706.06752). Roetteler et al. 2017. Algorithm 1 and Figure 10.\n", " - [https://github.com/microsoft/QuantumEllipticCurves/blob/dbf4836afaf7a9fab813cbc0970e65af85a6f93a/MicrosoftQuantumCrypto/EllipticCurves.qs#L456](QuantumQuantumCrypto). `DistinctEllipticCurveClassicalPointAddition`.\n" ] }, @@ -463,13 +485,14 @@ "\n", "#### Parameters\n", " - `n`: The bitsize of the two registers storing the elliptic curve point\n", - " - `window_size`: The number of bits in the window.\n", - " - `R`: The elliptic curve point to add. \n", + " - `R`: The elliptic curve point to add (NOT in montgomery form).\n", + " - `add_window_size`: The number of bits in the ECAdd window.\n", + " - `mul_window_size`: The number of bits in the modular multiplication window. \n", "\n", "#### Registers\n", " - `ctrl`: `window_size` control bits.\n", - " - `x`: The x component of the input elliptic curve point of bitsize `n`.\n", - " - `y`: The y component of the input elliptic curve point of bitsize `n`. \n", + " - `x`: The x component of the input elliptic curve point of bitsize `n` in montgomery form.\n", + " - `y`: The y component of the input elliptic curve point of bitsize `n` in montgomery form. \n", "\n", "#### References\n", " - [How to compute a 256-bit elliptic curve private key with only 50 million Toffoli gates](https://arxiv.org/abs/2306.08585). Litinski. 2013. Section 1, eq. (3) and (4).\n" @@ -500,15 +523,15 @@ { "cell_type": "code", "execution_count": null, - "id": "e1a3a397", + "id": "2ab0c101", "metadata": { - "cq.autogen": "ECWindowAddR.ec_window_add" + "cq.autogen": "ECWindowAddR.ec_window_add_r_small" }, "outputs": [], "source": [ - "n, p = sympy.symbols('n p')\n", - "Rx, Ry = sympy.symbols('Rx Ry')\n", - "ec_window_add = ECWindowAddR(n=n, window_size=3, R=ECPoint(Rx, Ry, mod=p))" + "n = 16\n", + "P = ECPoint(2, 2, mod=7, curve_a=3)\n", + "ec_window_add_r_small = ECWindowAddR(n=n, R=P, add_window_size=4)" ] }, { @@ -531,8 +554,8 @@ "outputs": [], "source": [ "from qualtran.drawing import show_bloqs\n", - "show_bloqs([ec_window_add],\n", - " ['`ec_window_add`'])" + "show_bloqs([ec_window_add_r_small],\n", + " ['`ec_window_add_r_small`'])" ] }, { @@ -555,9 +578,9 @@ "outputs": [], "source": [ "from qualtran.resource_counting.generalizers import ignore_split_join\n", - "ec_window_add_g, ec_window_add_sigma = ec_window_add.call_graph(max_depth=1, generalizer=ignore_split_join)\n", - "show_call_graph(ec_window_add_g)\n", - "show_counts_sigma(ec_window_add_sigma)" + "ec_window_add_r_small_g, ec_window_add_r_small_sigma = ec_window_add_r_small.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(ec_window_add_r_small_g)\n", + "show_counts_sigma(ec_window_add_r_small_sigma)" ] } ], diff --git a/qualtran/bloqs/factoring/ecc/find_ecc_private_key.py b/qualtran/bloqs/factoring/ecc/find_ecc_private_key.py index efaa42ed3..f6129af80 100644 --- a/qualtran/bloqs/factoring/ecc/find_ecc_private_key.py +++ b/qualtran/bloqs/factoring/ecc/find_ecc_private_key.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import functools from functools import cached_property from typing import Dict @@ -66,15 +67,19 @@ class FindECCPrivateKey(Bloq): n: The bitsize of the elliptic curve points' x and y registers. base_point: The base point $P$ with unknown order $r$ such that $P = [r] P$. public_key: The public key $Q$ such that $Q = [k] P$ for private key $k$. + add_window_size: The number of bits in the ECAdd window. + mul_window_size: The number of bits in the modular multiplication window. References: [How to compute a 256-bit elliptic curve private key with only 50 million Toffoli gates](https://arxiv.org/abs/2306.08585). Litinski. 2023. Figure 4 (a). """ - n: int + n: 'SymbolicInt' base_point: ECPoint public_key: ECPoint + add_window_size: 'SymbolicInt' = 1 + mul_window_size: 'SymbolicInt' = 1 @cached_property def signature(self) -> 'Signature': @@ -92,15 +97,24 @@ def curve_a(self) -> SymbolicInt: raise ValueError("Inconsistent curve parameters in the two points.") return self.base_point.curve_a + @property + def ec_pe_r(self) -> functools.partial[ECPhaseEstimateR]: + return functools.partial( + ECPhaseEstimateR, + n=self.n, + add_window_size=self.add_window_size, + mul_window_size=self.mul_window_size, + ) + def build_composite_bloq(self, bb: 'BloqBuilder') -> Dict[str, 'SoquetT']: x = bb.add(IntState(bitsize=self.n, val=self.base_point.x)) y = bb.add(IntState(bitsize=self.n, val=self.base_point.y)) - x, y = bb.add(ECPhaseEstimateR(n=self.n, point=self.base_point), x=x, y=y) - x, y = bb.add(ECPhaseEstimateR(n=self.n, point=self.public_key), x=x, y=y) + x, y = bb.add(self.ec_pe_r(point=self.base_point), x=x, y=y) + x, y = bb.add(self.ec_pe_r(point=self.public_key), x=x, y=y) - bb.add(Free(QUInt(self.n)), reg=x) - bb.add(Free(QUInt(self.n)), reg=y) + bb.add(Free(QUInt(self.n), dirty=True), reg=x) + bb.add(Free(QUInt(self.n), dirty=True), reg=y) return {} def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': @@ -108,7 +122,7 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': Ry = ssa.new_symbol('Ry') generic_point = ECPoint(Rx, Ry, mod=self.mod, curve_a=self.curve_a) - return {ECPhaseEstimateR(n=self.n, point=generic_point): 2} + return {self.ec_pe_r(point=generic_point): 2} def cost_attrs(self): return [('n', self.n)] diff --git a/qualtran/bloqs/factoring/mod_add_test.py b/qualtran/bloqs/factoring/mod_add_test.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/qualtran/bloqs/factoring/mod_exp.html b/qualtran/bloqs/factoring/mod_exp.html deleted file mode 100644 index 9d015ba4c..000000000 --- a/qualtran/bloqs/factoring/mod_exp.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - shor - - - - - - -
- -
- - - - \ No newline at end of file diff --git a/qualtran/bloqs/factoring/mod_exp.ipynb b/qualtran/bloqs/factoring/mod_exp.ipynb deleted file mode 100644 index 77c87aa15..000000000 --- a/qualtran/bloqs/factoring/mod_exp.ipynb +++ /dev/null @@ -1,209 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "5947b041", - "metadata": { - "cq.autogen": "title_cell" - }, - "source": [ - "# Modular Exponentiation" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ce6b0d51", - "metadata": { - "cq.autogen": "top_imports" - }, - "outputs": [], - "source": [ - "from qualtran import Bloq, CompositeBloq, BloqBuilder, Signature, Register\n", - "from qualtran import QBit, QInt, QUInt, QAny\n", - "from qualtran.drawing import show_bloq, show_call_graph, show_counts_sigma\n", - "from typing import *\n", - "import numpy as np\n", - "import sympy\n", - "import cirq" - ] - }, - { - "cell_type": "markdown", - "id": "2f68374c", - "metadata": { - "cq.autogen": "ModExp.bloq_doc.md" - }, - "source": [ - "## `ModExp`\n", - "Perform $b^e \\mod{m}$ for constant `base` $b$, `mod` $m$, and quantum `exponent` $e$.\n", - "\n", - "Modular exponentiation is the main computational primitive for quantum factoring algorithms.\n", - "We follow [GE2019]'s \"reference implementation\" for factoring. See `ModExp.make_for_shor`\n", - "to set the class attributes for a factoring run.\n", - "\n", - "This bloq decomposes into controlled modular exponentiation for each exponent bit.\n", - "\n", - "#### Parameters\n", - " - `base`: The integer base of the exponentiation\n", - " - `mod`: The integer modulus\n", - " - `exp_bitsize`: The size of the `exponent` thru-register\n", - " - `x_bitsize`: The size of the `x` right-register \n", - "\n", - "#### Registers\n", - " - `exponent`: The exponent\n", - " - `x [right]`: The output register containing the result of the exponentiation \n", - "\n", - "#### References\n", - " - [How to factor 2048 bit RSA integers in 8 hours using 20 million noisy qubits](https://arxiv.org/abs/1905.09749). Gidney and Ekerå. 2019.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4a1b8a2a", - "metadata": { - "cq.autogen": "ModExp.bloq_doc.py" - }, - "outputs": [], - "source": [ - "from qualtran.bloqs.factoring import ModExp" - ] - }, - { - "cell_type": "markdown", - "id": "902ec939", - "metadata": { - "cq.autogen": "ModExp.example_instances.md" - }, - "source": [ - "### Example Instances" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4c5aac47", - "metadata": { - "cq.autogen": "ModExp.modexp_small" - }, - "outputs": [], - "source": [ - "modexp_small = ModExp(base=4, mod=15, exp_bitsize=3, x_bitsize=2048)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "41d94c81", - "metadata": { - "cq.autogen": "ModExp.modexp" - }, - "outputs": [], - "source": [ - "modexp = ModExp.make_for_shor(big_n=13 * 17, g=9)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5c0c6c06", - "metadata": { - "cq.autogen": "ModExp.modexp_symb" - }, - "outputs": [], - "source": [ - "g, N, n_e, n_x = sympy.symbols('g N n_e, n_x')\n", - "modexp_symb = ModExp(base=g, mod=N, exp_bitsize=n_e, x_bitsize=n_x)" - ] - }, - { - "cell_type": "markdown", - "id": "a55a51df", - "metadata": { - "cq.autogen": "ModExp.graphical_signature.md" - }, - "source": [ - "#### Graphical Signature" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ce953a6d", - "metadata": { - "cq.autogen": "ModExp.graphical_signature.py" - }, - "outputs": [], - "source": [ - "from qualtran.drawing import show_bloqs\n", - "show_bloqs([modexp_symb, modexp_small, modexp],\n", - " ['`modexp_symb`', '`modexp_small`', '`modexp`'])" - ] - }, - { - "cell_type": "markdown", - "id": "83ba4b54", - "metadata": {}, - "source": [ - "### Decomposition" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a0e53334", - "metadata": {}, - "outputs": [], - "source": [ - "show_bloq(modexp_small.decompose_bloq())" - ] - }, - { - "cell_type": "markdown", - "id": "8662fa01", - "metadata": { - "cq.autogen": "ModExp.call_graph.md" - }, - "source": [ - "### Call Graph" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9a7d9f5a", - "metadata": { - "cq.autogen": "ModExp.call_graph.py" - }, - "outputs": [], - "source": [ - "from qualtran.resource_counting.generalizers import ignore_split_join\n", - "modexp_symb_g, modexp_symb_sigma = modexp_symb.call_graph(max_depth=1, generalizer=ignore_split_join)\n", - "show_call_graph(modexp_symb_g)\n", - "show_counts_sigma(modexp_symb_sigma)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/qualtran/bloqs/factoring/rsa/__init__.py b/qualtran/bloqs/factoring/rsa/__init__.py new file mode 100644 index 000000000..d58509847 --- /dev/null +++ b/qualtran/bloqs/factoring/rsa/__init__.py @@ -0,0 +1,37 @@ +# 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. +# +# isort:skip_file + +r"""Bloqs for breaking RSA cryptography systems via integer factorization. + +RSA cryptography is a form of public key cryptography based on the difficulty of +factoring the product of two large prime numbers. + +Using RSA, the cryptographic scheme chooses two large prime numbers p, q, their product n, +λ(n) = lcm(p - 1, q - 1) where λ is Carmichael's totient function, an integer e such that +1 < e < λ(n), and finally d as d ≡ e^-1 (mod λ(n)). The public key consists of the modulus n and +the public (or encryption) exponent e. The private key consists of the private (or decryption) +exponent d, which must be kept secret. p, q, and λ(n) must also be kept secret because they can be +used to calculate d. + +Using Shor's algorithm for factoring, we can find p and q (the factors of n) in polynomial time +with a quantum algorithm. + +References: + [RSA (cryptosystem)](https://en.wikipedia.org/wiki/RSA_(cryptosystem)). +""" + +from .rsa_phase_estimate import RSAPhaseEstimate +from .rsa_mod_exp import ModExp diff --git a/qualtran/bloqs/factoring/factoring-via-modexp.ipynb b/qualtran/bloqs/factoring/rsa/factoring-via-modexp.ipynb similarity index 98% rename from qualtran/bloqs/factoring/factoring-via-modexp.ipynb rename to qualtran/bloqs/factoring/rsa/factoring-via-modexp.ipynb index dfea45b9e..08ff940b8 100644 --- a/qualtran/bloqs/factoring/factoring-via-modexp.ipynb +++ b/qualtran/bloqs/factoring/rsa/factoring-via-modexp.ipynb @@ -180,7 +180,7 @@ "metadata": {}, "outputs": [], "source": [ - "from qualtran.bloqs.factoring.mod_exp import ModExp\n", + "from qualtran.bloqs.factoring.rsa.rsa_mod_exp import ModExp\n", "from qualtran.drawing import show_bloq\n", "\n", "mod_exp = ModExp(base=g, mod=N, exp_bitsize=32, x_bitsize=32)\n", @@ -205,6 +205,7 @@ "metadata": {}, "outputs": [], "source": [ + "from qualtran import QUInt\n", "for e in range(20):\n", " ref = (g ** e) % N\n", " _, bloq_eval = mod_exp.call_classically(exponent=e)\n", @@ -231,7 +232,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.9" + "version": "3.10.13" } }, "nbformat": 4, diff --git a/qualtran/bloqs/factoring/rsa/rsa.ipynb b/qualtran/bloqs/factoring/rsa/rsa.ipynb new file mode 100644 index 000000000..5415a4d53 --- /dev/null +++ b/qualtran/bloqs/factoring/rsa/rsa.ipynb @@ -0,0 +1,331 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "48ce60bb", + "metadata": { + "cq.autogen": "title_cell" + }, + "source": [ + "# Factoring RSA\n", + "\n", + "Bloqs for breaking RSA cryptography systems via integer factorization.\n", + "\n", + "RSA cryptography is a form of public key cryptography based on the difficulty of\n", + "factoring the product of two large prime numbers.\n", + "\n", + "Using RSA, the cryptographic scheme chooses two large prime numbers p, q, their product n,\n", + "λ(n) = lcm(p - 1, q - 1) where λ is Carmichael's totient function, an integer e such that\n", + "1 < e < λ(n), and finally d as d ≡ e^-1 (mod λ(n)). The public key consists of the modulus n and\n", + "the public (or encryption) exponent e. The private key consists of the private (or decryption)\n", + "exponent d, which must be kept secret. p, q, and λ(n) must also be kept secret because they can be\n", + "used to calculate d.\n", + "\n", + "Using Shor's algorithm for factoring, we can find p and q (the factors of n) in polynomial time\n", + "with a quantum algorithm.\n", + "\n", + "References:\n", + " [RSA (cryptosystem)](https://en.wikipedia.org/wiki/RSA_(cryptosystem))." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d12766dd", + "metadata": { + "cq.autogen": "top_imports" + }, + "outputs": [], + "source": [ + "from qualtran import Bloq, CompositeBloq, BloqBuilder, Signature, Register\n", + "from qualtran import QBit, QInt, QUInt, QAny\n", + "from qualtran.drawing import show_bloq, show_call_graph, show_counts_sigma\n", + "from typing import *\n", + "import numpy as np\n", + "import sympy\n", + "import cirq" + ] + }, + { + "cell_type": "markdown", + "id": "881834c3", + "metadata": { + "cq.autogen": "ModExp.bloq_doc.md" + }, + "source": [ + "## `ModExp`\n", + "Perform $b^e \\mod{m}$ for constant `base` $b$, `mod` $m$, and quantum `exponent` $e$.\n", + "\n", + "Modular exponentiation is the main computational primitive for quantum factoring algorithms.\n", + "We follow [GE2019]'s \"reference implementation\" for factoring. See `ModExp.make_for_shor`\n", + "to set the class attributes for a factoring run.\n", + "\n", + "This bloq decomposes into controlled modular exponentiation for each exponent bit.\n", + "\n", + "#### Parameters\n", + " - `base`: The integer base of the exponentiation\n", + " - `mod`: The integer modulus\n", + " - `exp_bitsize`: The size of the `exponent` thru-register\n", + " - `x_bitsize`: The size of the `x` right-register \n", + "\n", + "#### Registers\n", + " - `exponent`: The exponent\n", + " - `x [right]`: The output register containing the result of the exponentiation \n", + "\n", + "#### References\n", + " - [How to factor 2048 bit RSA integers in 8 hours using 20 million noisy qubits](https://arxiv.org/abs/1905.09749). Gidney and Ekerå. 2019.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "61062a3d", + "metadata": { + "cq.autogen": "ModExp.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.factoring.rsa import ModExp" + ] + }, + { + "cell_type": "markdown", + "id": "6963ad94", + "metadata": { + "cq.autogen": "ModExp.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "56d35af9", + "metadata": { + "cq.autogen": "ModExp.modexp_symb" + }, + "outputs": [], + "source": [ + "g, N, n_e, n_x = sympy.symbols('g N n_e, n_x')\n", + "modexp_symb = ModExp(base=g, mod=N, exp_bitsize=n_e, x_bitsize=n_x)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7d3d90b4", + "metadata": { + "cq.autogen": "ModExp.modexp_small" + }, + "outputs": [], + "source": [ + "modexp_small = ModExp(base=4, mod=15, exp_bitsize=3, x_bitsize=2048)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e3a004d3", + "metadata": { + "cq.autogen": "ModExp.modexp" + }, + "outputs": [], + "source": [ + "modexp = ModExp.make_for_shor(big_n=13 * 17, g=9)" + ] + }, + { + "cell_type": "markdown", + "id": "39422b45", + "metadata": { + "cq.autogen": "ModExp.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "83a891e2", + "metadata": { + "cq.autogen": "ModExp.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([modexp_small, modexp, modexp_symb],\n", + " ['`modexp_small`', '`modexp`', '`modexp_symb`'])" + ] + }, + { + "cell_type": "markdown", + "id": "9c271392", + "metadata": { + "cq.autogen": "ModExp.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c6d55278", + "metadata": { + "cq.autogen": "ModExp.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "modexp_small_g, modexp_small_sigma = modexp_small.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(modexp_small_g)\n", + "show_counts_sigma(modexp_small_sigma)" + ] + }, + { + "cell_type": "markdown", + "id": "2603abbd", + "metadata": { + "cq.autogen": "RSAPhaseEstimate.bloq_doc.md" + }, + "source": [ + "## `RSAPhaseEstimate`\n", + "Perform a single phase estimation of the decomposition of Modular Exponentiation for the\n", + "given base.\n", + "\n", + "The constructor requires a pre-set base, see the make_for_shor factory method for picking a\n", + "random, valid base\n", + "\n", + "#### Parameters\n", + " - `n`: The bitsize of the modulus N.\n", + " - `mod`: The modulus N; a part of the public key for RSA.\n", + " - `base`: A base for modular exponentiation. \n", + "\n", + "#### References\n", + " - [Circuit for Shor's algorithm using 2n+3 qubits](https://arxiv.org/abs/quant-ph/0205095). Beauregard. 2003. Fig 1.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5b838c20", + "metadata": { + "cq.autogen": "RSAPhaseEstimate.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.factoring.rsa import RSAPhaseEstimate" + ] + }, + { + "cell_type": "markdown", + "id": "20426b03", + "metadata": { + "cq.autogen": "RSAPhaseEstimate.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f696c5fd", + "metadata": { + "cq.autogen": "RSAPhaseEstimate.rsa_pe" + }, + "outputs": [], + "source": [ + "n, p, g = sympy.symbols('n p g')\n", + "rsa_pe = RSAPhaseEstimate(n=n, mod=p, base=g)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b16e84a5", + "metadata": { + "cq.autogen": "RSAPhaseEstimate.rsa_pe_small" + }, + "outputs": [], + "source": [ + "rsa_pe_small = RSAPhaseEstimate.make_for_shor(big_n=13 * 17, g=9)" + ] + }, + { + "cell_type": "markdown", + "id": "0c0078d5", + "metadata": { + "cq.autogen": "RSAPhaseEstimate.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6b493a30", + "metadata": { + "cq.autogen": "RSAPhaseEstimate.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([rsa_pe_small, rsa_pe],\n", + " ['`rsa_pe_small`', '`rsa_pe`'])" + ] + }, + { + "cell_type": "markdown", + "id": "441028fa", + "metadata": { + "cq.autogen": "RSAPhaseEstimate.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7f30cb55", + "metadata": { + "cq.autogen": "RSAPhaseEstimate.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "rsa_pe_small_g, rsa_pe_small_sigma = rsa_pe_small.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(rsa_pe_small_g)\n", + "show_counts_sigma(rsa_pe_small_sigma)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d72bc625", + "metadata": { + "cq.autogen": "RSAPhaseEstimate.rsa_pe_shor" + }, + "outputs": [], + "source": [ + "rsa_pe_shor = RSAPhaseEstimate.make_for_shor(big_n=13 * 17, g=9)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/qualtran/bloqs/factoring/mod_exp.py b/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py similarity index 78% rename from qualtran/bloqs/factoring/mod_exp.py rename to qualtran/bloqs/factoring/rsa/rsa_mod_exp.py index 129711797..fda33f5fc 100644 --- a/qualtran/bloqs/factoring/mod_exp.py +++ b/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py @@ -12,11 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. import math -import random from functools import cached_property from typing import cast, Dict, Optional, Tuple, Union import attrs +import numpy as np import sympy from attrs import frozen @@ -28,17 +28,19 @@ DecomposeTypeError, QUInt, Register, - Side, Signature, Soquet, SoquetT, ) -from qualtran.bloqs.basic_gates import IntState +from qualtran._infra.registers import Side +from qualtran.bloqs.basic_gates.z_basis import IntState from qualtran.bloqs.mod_arithmetic import CModMulK from qualtran.drawing import Text, WireSymbol from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator from qualtran.resource_counting.generalizers import ignore_split_join +from qualtran.simulation.classical_sim import ClassicalValT from qualtran.symbolics import is_symbolic +from qualtran.symbolics.types import SymbolicInt @frozen @@ -66,10 +68,10 @@ class ModExp(Bloq): Gidney and Ekerå. 2019. """ - base: Union[int, sympy.Expr] - mod: Union[int, sympy.Expr] - exp_bitsize: Union[int, sympy.Expr] - x_bitsize: Union[int, sympy.Expr] + base: 'SymbolicInt' + mod: 'SymbolicInt' + exp_bitsize: 'SymbolicInt' + x_bitsize: 'SymbolicInt' def __attrs_post_init__(self): if not is_symbolic(self.base, self.mod): @@ -85,33 +87,47 @@ def signature(self) -> 'Signature': ) @classmethod - def make_for_shor(cls, big_n: int, g: Optional[int] = None): + def make_for_shor( + cls, + big_n: 'SymbolicInt', + g: Optional['SymbolicInt'] = None, + rs: Optional[np.random.RandomState] = None, + ): """Factory method that sets up the modular exponentiation for a factoring run. Args: big_n: The large composite number N. Used to set `mod`. Its bitsize is used to set `x_bitsize` and `exp_bitsize`. g: Optional base of the exponentiation. If `None`, we pick a random base. + rs: Optional random state which can be seeded to make base generation deterministic. """ - if isinstance(big_n, sympy.Expr): + if is_symbolic(big_n): little_n = sympy.ceiling(sympy.log(big_n, 2)) else: little_n = int(math.ceil(math.log2(big_n))) if g is None: - g = random.randint(2, big_n) + if is_symbolic(big_n): + g = sympy.symbols('g') + else: + if rs is None: + rs = np.random.RandomState() + while True: + g = rs.randint(2, int(big_n)) + if math.gcd(g, int(big_n)) == 1: + break return cls(base=g, mod=big_n, exp_bitsize=2 * little_n, x_bitsize=little_n) - def _CtrlModMul(self, k: Union[int, sympy.Expr]): + def _CtrlModMul(self, k: 'SymbolicInt'): """Helper method to return a `CModMulK` with attributes forwarded.""" return CModMulK(QUInt(self.x_bitsize), k=k, mod=self.mod) def build_composite_bloq(self, bb: 'BloqBuilder', exponent: 'Soquet') -> Dict[str, 'SoquetT']: - if isinstance(self.exp_bitsize, sympy.Expr): - raise DecomposeTypeError("`exp_bitsize` must be a concrete value.") + if is_symbolic(self.exp_bitsize): + raise DecomposeTypeError(f"Cannot decompose {self} with symbolic `exp_bitsize`.") + # https://en.wikipedia.org/wiki/Modular_exponentiation#Right-to-left_binary_method x = bb.add(IntState(val=1, bitsize=self.x_bitsize)) exponent = bb.split(exponent) - # https://en.wikipedia.org/wiki/Modular_exponentiation#Right-to-left_binary_method base = self.base % self.mod for j in range(self.exp_bitsize - 1, 0 - 1, -1): exponent[j], x = bb.add(self._CtrlModMul(k=base), ctrl=exponent[j], x=x) @@ -121,9 +137,9 @@ def build_composite_bloq(self, bb: 'BloqBuilder', exponent: 'Soquet') -> Dict[st def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': k = ssa.new_symbol('k') - return {IntState(val=1, bitsize=self.x_bitsize): 1, self._CtrlModMul(k=k): self.exp_bitsize} + return {self._CtrlModMul(k=k): self.exp_bitsize, IntState(val=1, bitsize=self.x_bitsize): 1} - def on_classical_vals(self, exponent: int): + def on_classical_vals(self, exponent) -> Dict[str, Union['ClassicalValT', sympy.Expr]]: return {'exponent': exponent, 'x': (self.base**exponent) % self.mod} def wire_symbol( @@ -163,4 +179,4 @@ def _modexp_symb() -> ModExp: return modexp_symb -_MODEXP_DOC = BloqDocSpec(bloq_cls=ModExp, examples=(_modexp_symb, _modexp_small, _modexp)) +_RSA_MODEXP_DOC = BloqDocSpec(bloq_cls=ModExp, examples=(_modexp_small, _modexp, _modexp_symb)) diff --git a/qualtran/bloqs/factoring/mod_exp_test.py b/qualtran/bloqs/factoring/rsa/rsa_mod_exp_test.py similarity index 83% rename from qualtran/bloqs/factoring/mod_exp_test.py rename to qualtran/bloqs/factoring/rsa/rsa_mod_exp_test.py index d0b48c4af..e4cabd8c4 100644 --- a/qualtran/bloqs/factoring/mod_exp_test.py +++ b/qualtran/bloqs/factoring/rsa/rsa_mod_exp_test.py @@ -21,7 +21,7 @@ from qualtran import Bloq from qualtran.bloqs.bookkeeping import Join, Split -from qualtran.bloqs.factoring.mod_exp import _modexp, _modexp_symb, ModExp +from qualtran.bloqs.factoring.rsa.rsa_mod_exp import _modexp, _modexp_small, _modexp_symb, ModExp from qualtran.bloqs.mod_arithmetic import CModMulK from qualtran.drawing import Text from qualtran.resource_counting import SympySymbolAllocator @@ -40,17 +40,13 @@ def test_mod_exp_consistent_classical(): # Choose an exponent in a range. Set exp_bitsize=ne bit enough to fit. exponent = rs.randint(1, 2**n) - ne = 2 * n - # Choose a base smaller than mod. - base = rs.randint(1, mod) - while np.gcd(base, mod) != 1: - base = rs.randint(1, mod) - - bloq = ModExp(base=base, exp_bitsize=ne, x_bitsize=n, mod=mod) + bloq = ModExp.make_for_shor(big_n=mod, rs=rs) ret1 = bloq.call_classically(exponent=exponent) ret2 = bloq.decompose_bloq().call_classically(exponent=exponent) - assert ret1 == ret2 + assert len(ret1) == len(ret2) + for i in range(len(ret1)): + np.testing.assert_array_equal(ret1[i], ret2[i]) def test_modexp_symb_manual(): @@ -93,19 +89,11 @@ def test_mod_exp_t_complexity(): assert tcomp.t > 0 -def test_modexp(bloq_autotester): - bloq_autotester(_modexp) - - -def test_modexp_symb(bloq_autotester): - bloq_autotester(_modexp_symb) +@pytest.mark.parametrize('bloq', [_modexp, _modexp_symb, _modexp_small]) +def test_modexp(bloq_autotester, bloq): + bloq_autotester(bloq) @pytest.mark.notebook def test_intro_notebook(): execute_notebook('factoring-via-modexp') - - -@pytest.mark.notebook -def test_notebook(): - execute_notebook('mod_exp') diff --git a/qualtran/bloqs/factoring/rsa/rsa_phase_estimate.py b/qualtran/bloqs/factoring/rsa/rsa_phase_estimate.py new file mode 100644 index 000000000..611af9de4 --- /dev/null +++ b/qualtran/bloqs/factoring/rsa/rsa_phase_estimate.py @@ -0,0 +1,150 @@ +# 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 math +from functools import cached_property +from typing import Dict, Optional + +import attrs +import numpy as np +import sympy +from attrs import frozen + +from qualtran import ( + Bloq, + bloq_example, + BloqBuilder, + BloqDocSpec, + DecomposeTypeError, + QUInt, + Signature, + SoquetT, +) +from qualtran.bloqs.basic_gates import IntState, PlusState +from qualtran.bloqs.bookkeeping import Free +from qualtran.bloqs.factoring._factoring_shims import MeasureQFT +from qualtran.bloqs.mod_arithmetic.mod_multiplication import CModMulK +from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator +from qualtran.symbolics import is_symbolic, SymbolicInt + + +@frozen +class RSAPhaseEstimate(Bloq): + """Perform a single phase estimation of the decomposition of Modular Exponentiation for the + given base. + + The constructor requires a pre-set base, see the make_for_shor factory method for picking a + random, valid base + + Args: + n: The bitsize of the modulus N. + mod: The modulus N; a part of the public key for RSA. + base: A base for modular exponentiation. + + References: + [Circuit for Shor's algorithm using 2n+3 qubits](https://arxiv.org/abs/quant-ph/0205095). + Beauregard. 2003. Fig 1. + """ + + n: 'SymbolicInt' + mod: 'SymbolicInt' + base: 'SymbolicInt' + + @cached_property + def signature(self) -> 'Signature': + return Signature([]) + + @classmethod + def make_for_shor( + cls, + big_n: 'SymbolicInt', + g: Optional['SymbolicInt'] = None, + rs: Optional[np.random.RandomState] = None, + ): + """Factory method that sets up the modular exponentiation for a factoring run. + + Args: + big_n: The large composite number N. Used to set `mod`. Its bitsize is used + to set `x_bitsize` and `exp_bitsize`. + g: Optional base of the exponentiation. If `None`, we pick a random base. + rs: Optional random state which can be seeded to make base generation deterministic. + """ + if is_symbolic(big_n): + little_n = sympy.ceiling(sympy.log(big_n, 2)) + else: + little_n = int(math.ceil(math.log2(big_n))) + if g is None: + if is_symbolic(big_n): + g = sympy.symbols('g') + else: + if rs is None: + rs = np.random.RandomState() + while True: + g = rs.randint(2, int(big_n)) + if math.gcd(g, int(big_n)) == 1: + break + return cls(base=g, mod=big_n, n=little_n) + + def __attrs_post_init__(self): + if not is_symbolic(self.n, self.mod): + assert self.n == int(math.ceil(math.log2(self.mod))) + + def _CtrlModMul(self, k: 'SymbolicInt'): + """Helper method to return a `CModMulK` with attributes forwarded.""" + return CModMulK(QUInt(self.n), k=k, mod=self.mod) + + def build_composite_bloq(self, bb: 'BloqBuilder') -> Dict[str, 'SoquetT']: + if is_symbolic(self.n): + raise DecomposeTypeError(f"Cannot decompose {self} with symbolic `n`.") + exponent = [bb.add(PlusState()) for _ in range(2 * self.n)] + x = bb.add(IntState(val=1, bitsize=self.n)) + + base = self.base % self.mod + for j in range((2 * self.n) - 1, 0 - 1, -1): + exponent[j], x = bb.add(self._CtrlModMul(k=base), ctrl=exponent[j], x=x) + base = (base * base) % self.mod + + bb.add(MeasureQFT(n=2 * self.n), x=exponent) + bb.add(Free(QUInt(self.n), dirty=True), reg=x) + return {} + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': + k = ssa.new_symbol('k') + return {MeasureQFT(n=self.n): 1, self._CtrlModMul(k=k): 2 * self.n} + + +_K = sympy.Symbol('k_exp') + + +def _generalize_k(b: Bloq) -> Optional[Bloq]: + if isinstance(b, CModMulK): + return attrs.evolve(b, k=_K) + + return b + + +@bloq_example +def _rsa_pe() -> RSAPhaseEstimate: + n, p, g = sympy.symbols('n p g') + rsa_pe = RSAPhaseEstimate(n=n, mod=p, base=g) + return rsa_pe + + +@bloq_example +def _rsa_pe_small() -> RSAPhaseEstimate: + rsa_pe_small = RSAPhaseEstimate.make_for_shor(big_n=13 * 17, g=9) + return rsa_pe_small + + +_RSA_PE_BLOQ_DOC = BloqDocSpec(bloq_cls=RSAPhaseEstimate, examples=[_rsa_pe_small, _rsa_pe]) diff --git a/qualtran/bloqs/factoring/rsa/rsa_phase_estimate_test.py b/qualtran/bloqs/factoring/rsa/rsa_phase_estimate_test.py new file mode 100644 index 000000000..4ddedf964 --- /dev/null +++ b/qualtran/bloqs/factoring/rsa/rsa_phase_estimate_test.py @@ -0,0 +1,27 @@ +# 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 + +import qualtran.testing as qlt_testing +from qualtran.bloqs.factoring.rsa.rsa_phase_estimate import _rsa_pe, _rsa_pe_small + + +@pytest.mark.parametrize('bloq', [_rsa_pe_small, _rsa_pe]) +def test_rsa_pe(bloq_autotester, bloq): + bloq_autotester(bloq) + + +def test_notebook(): + qlt_testing.execute_notebook('rsa') diff --git a/qualtran/bloqs/for_testing/atom.py b/qualtran/bloqs/for_testing/atom.py index c7e998611..210ede7e8 100644 --- a/qualtran/bloqs/for_testing/atom.py +++ b/qualtran/bloqs/for_testing/atom.py @@ -27,7 +27,6 @@ GateWithRegisters, Signature, ) -from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.resource_counting import CostKey, GateCounts, QECGatesCost if TYPE_CHECKING: @@ -72,9 +71,6 @@ def my_static_costs(self, cost_key: 'CostKey'): return GateCounts(t=100) return NotImplemented - def _t_complexity_(self) -> 'TComplexity': - return TComplexity(100) - def __repr__(self): if self.tag: return f'TestAtom({self.tag!r})' @@ -154,8 +150,10 @@ def _unitary_(self): def adjoint(self) -> 'TestGWRAtom': return attrs.evolve(self, is_adjoint=not self.is_adjoint) - def _t_complexity_(self) -> 'TComplexity': - return TComplexity(100) + def my_static_costs(self, cost_key: 'CostKey'): + if isinstance(cost_key, QECGatesCost): + return GateCounts(t=100) + return NotImplemented def __repr__(self): tag = f'{self.tag!r}' if self.tag else '' diff --git a/qualtran/bloqs/for_testing/random_select_and_prepare.py b/qualtran/bloqs/for_testing/random_select_and_prepare.py index c5eed3569..6e1a607f5 100644 --- a/qualtran/bloqs/for_testing/random_select_and_prepare.py +++ b/qualtran/bloqs/for_testing/random_select_and_prepare.py @@ -14,13 +14,13 @@ from functools import cached_property from typing import Iterator, Optional, Tuple +import attrs import cirq import numpy as np from attrs import frozen from numpy.typing import NDArray -from qualtran import BloqBuilder, BQUInt, QBit, Register, SoquetT -from qualtran._infra.single_qubit_controlled import SpecializedSingleQubitControlledExtension +from qualtran import AddControlledT, Bloq, BloqBuilder, BQUInt, CtrlSpec, QBit, Register, SoquetT from qualtran.bloqs.block_encoding.lcu_block_encoding import SelectBlockEncoding from qualtran.bloqs.for_testing.matrix_gate import MatrixGate from qualtran.bloqs.multiplexers.select_base import SelectOracle @@ -84,7 +84,7 @@ def alphas(self): @frozen -class TestPauliSelectOracle(SpecializedSingleQubitControlledExtension, SelectOracle): # type: ignore[misc] +class TestPauliSelectOracle(SelectOracle): # type: ignore[misc] r"""Paulis acting on $m$ qubits, controlled by an $n$-qubit register. Given $2^n$ multi-qubit-Paulis (acting on $m$ qubits) $U_j$, @@ -149,6 +149,19 @@ def decompose_from_registers( op = op.controlled_by(*quregs['control'], control_values=[self.control_val]) yield op + def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> Tuple['Bloq', 'AddControlledT']: + from qualtran.bloqs.mcmt.specialized_ctrl import get_ctrl_system_1bit_cv + + return get_ctrl_system_1bit_cv( + self, + ctrl_spec=ctrl_spec, + current_ctrl_bit=self.control_val, + get_ctrl_bloq_and_ctrl_reg_name=lambda cv: ( + attrs.evolve(self, control_val=cv), + 'control', + ), + ) + def random_qubitization_walk_operator( select_bitsize: int, target_bitsize: int, *, random_state: np.random.RandomState diff --git a/qualtran/bloqs/gf_arithmetic/__init__.py b/qualtran/bloqs/gf_arithmetic/__init__.py new file mode 100644 index 000000000..de19116b3 --- /dev/null +++ b/qualtran/bloqs/gf_arithmetic/__init__.py @@ -0,0 +1,19 @@ +# 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. + +from qualtran.bloqs.gf_arithmetic.gf2_add_k import GF2AddK +from qualtran.bloqs.gf_arithmetic.gf2_addition import GF2Addition +from qualtran.bloqs.gf_arithmetic.gf2_inverse import GF2Inverse +from qualtran.bloqs.gf_arithmetic.gf2_multiplication import GF2Multiplication +from qualtran.bloqs.gf_arithmetic.gf2_square import GF2Square diff --git a/qualtran/bloqs/gf_arithmetic/gf2_add_k.ipynb b/qualtran/bloqs/gf_arithmetic/gf2_add_k.ipynb new file mode 100644 index 000000000..6e8ab3396 --- /dev/null +++ b/qualtran/bloqs/gf_arithmetic/gf2_add_k.ipynb @@ -0,0 +1,168 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "b02daa35", + "metadata": { + "cq.autogen": "title_cell" + }, + "source": [ + "# GF($2^m$) Add Constant" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c57ce930", + "metadata": { + "cq.autogen": "top_imports" + }, + "outputs": [], + "source": [ + "from qualtran import Bloq, CompositeBloq, BloqBuilder, Signature, Register\n", + "from qualtran import QBit, QInt, QUInt, QAny\n", + "from qualtran.drawing import show_bloq, show_call_graph, show_counts_sigma\n", + "from typing import *\n", + "import numpy as np\n", + "import sympy\n", + "import cirq" + ] + }, + { + "cell_type": "markdown", + "id": "e4620511", + "metadata": { + "cq.autogen": "GF2AddK.bloq_doc.md" + }, + "source": [ + "## `GF2AddK`\n", + "In place addition of a constant $k$ for elements in GF($2^m$).\n", + "\n", + "The bloq implements in place addition of a classical constant $k$ and a quantum register\n", + "$|x\\rangle$ storing elements from GF($2^m$). Addition in GF($2^m$) simply reduces to a component\n", + "wise XOR, which can be implemented via X gates.\n", + "\n", + "$$\n", + "|x\\rangle \\rightarrow |x + k\\rangle\n", + "$$\n", + "\n", + "#### Parameters\n", + " - `bitsize`: The degree $m$ of the galois field GF($2^m$). Also corresponds to the number of qubits in the input register x.\n", + " - `k`: Integer representation of constant over GF($2^m$) that should be added to the input register x. \n", + "\n", + "#### Registers\n", + " - `x`: Input THRU register of size $m$ that stores elements from $GF(2^m)$.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "04dc8f2b", + "metadata": { + "cq.autogen": "GF2AddK.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.gf_arithmetic import GF2AddK" + ] + }, + { + "cell_type": "markdown", + "id": "a0d774d4", + "metadata": { + "cq.autogen": "GF2AddK.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0588ca5b", + "metadata": { + "cq.autogen": "GF2AddK.gf16_add_k" + }, + "outputs": [], + "source": [ + "gf16_add_k = GF2AddK(4, 1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8250af55", + "metadata": { + "cq.autogen": "GF2AddK.gf2_add_k_symbolic" + }, + "outputs": [], + "source": [ + "import sympy\n", + "\n", + "m, k = sympy.symbols('m, k', positive=True, integers=True)\n", + "gf2_add_k_symbolic = GF2AddK(m, k)" + ] + }, + { + "cell_type": "markdown", + "id": "fe4c4550", + "metadata": { + "cq.autogen": "GF2AddK.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2374ad42", + "metadata": { + "cq.autogen": "GF2AddK.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([gf16_add_k, gf2_add_k_symbolic],\n", + " ['`gf16_add_k`', '`gf2_add_k_symbolic`'])" + ] + }, + { + "cell_type": "markdown", + "id": "f64d8798", + "metadata": { + "cq.autogen": "GF2AddK.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "578f0f39", + "metadata": { + "cq.autogen": "GF2AddK.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "gf16_add_k_g, gf16_add_k_sigma = gf16_add_k.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(gf16_add_k_g)\n", + "show_counts_sigma(gf16_add_k_sigma)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/qualtran/bloqs/gf_arithmetic/gf2_add_k.py b/qualtran/bloqs/gf_arithmetic/gf2_add_k.py new file mode 100644 index 000000000..41e380ea8 --- /dev/null +++ b/qualtran/bloqs/gf_arithmetic/gf2_add_k.py @@ -0,0 +1,106 @@ +# 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. +from functools import cached_property +from typing import Dict, Sequence, TYPE_CHECKING + +import attrs + +from qualtran import Bloq, bloq_example, BloqDocSpec, DecomposeTypeError, QGF, Register, Signature +from qualtran.bloqs.basic_gates import XGate +from qualtran.symbolics import is_symbolic, SymbolicInt + +if TYPE_CHECKING: + from qualtran import BloqBuilder, Soquet + from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator + from qualtran.simulation.classical_sim import ClassicalValT + + +@attrs.frozen +class GF2AddK(Bloq): + r"""In place addition of a constant $k$ for elements in GF($2^m$). + + The bloq implements in place addition of a classical constant $k$ and a quantum register + $|x\rangle$ storing elements from GF($2^m$). Addition in GF($2^m$) simply reduces to a component + wise XOR, which can be implemented via X gates. + + $$ + |x\rangle \rightarrow |x + k\rangle + $$ + + Args: + bitsize: The degree $m$ of the galois field GF($2^m$). Also corresponds to the number of + qubits in the input register x. + k: Integer representation of constant over GF($2^m$) that should be added to the input + register x. + + Registers: + x: Input THRU register of size $m$ that stores elements from $GF(2^m)$. + """ + + bitsize: SymbolicInt + k: SymbolicInt + + @cached_property + def signature(self) -> 'Signature': + return Signature([Register('x', dtype=self.qgf)]) + + @cached_property + def qgf(self) -> QGF: + return QGF(characteristic=2, degree=self.bitsize) + + @cached_property + def _bits_k(self) -> Sequence[int]: + return self.qgf.to_bits(self.qgf.gf_type(self.k)) + + def is_symbolic(self): + return is_symbolic(self.k, self.bitsize) + + def build_composite_bloq(self, bb: 'BloqBuilder', *, x: 'Soquet') -> Dict[str, 'Soquet']: + if self.is_symbolic(): + raise DecomposeTypeError(f"Cannot decompose symbolic {self}") + xs = bb.split(x) + + for i, bit in enumerate(self._bits_k): + if bit == 1: + xs[i] = bb.add(XGate(), q=xs[i]) + + x = bb.join(xs, dtype=self.qgf) + + return {'x': x} + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': + num_flips = self.bitsize if self.is_symbolic() else sum(self._bits_k) + return {XGate(): num_flips} + + def on_classical_vals(self, *, x) -> Dict[str, 'ClassicalValT']: + assert isinstance(x, self.qgf.gf_type) + return {'x': x + self.qgf.gf_type(self.k)} + + +@bloq_example +def _gf16_add_k() -> GF2AddK: + gf16_add_k = GF2AddK(4, 1) + return gf16_add_k + + +@bloq_example +def _gf2_add_k_symbolic() -> GF2AddK: + import sympy + + m, k = sympy.symbols('m, k', positive=True, integers=True) + gf2_add_k_symbolic = GF2AddK(m, k) + return gf2_add_k_symbolic + + +_GF2_ADD_K_DOC = BloqDocSpec(bloq_cls=GF2AddK, examples=(_gf16_add_k, _gf2_add_k_symbolic)) diff --git a/qualtran/bloqs/gf_arithmetic/gf2_add_k_test.py b/qualtran/bloqs/gf_arithmetic/gf2_add_k_test.py new file mode 100644 index 000000000..0add69ae4 --- /dev/null +++ b/qualtran/bloqs/gf_arithmetic/gf2_add_k_test.py @@ -0,0 +1,44 @@ +# 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 galois import GF + +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): + bloq_autotester(_gf16_add_k) + + +def test_gf2_add_k_symbolic(bloq_autotester): + bloq_autotester(_gf2_add_k_symbolic) + + +def test_gf2_add_k_classical_sim_quick(): + m = 2 + GFM = GF(2**m) + for k in GFM.elements: + bloq = GF2AddK(m, int(k)) + assert_consistent_classical_action(bloq, x=GFM.elements) + + +@pytest.mark.slow +@pytest.mark.parametrize('m', [3, 4, 5]) +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) diff --git a/qualtran/bloqs/gf_arithmetic/gf2_addition.ipynb b/qualtran/bloqs/gf_arithmetic/gf2_addition.ipynb new file mode 100644 index 000000000..befc2c5fe --- /dev/null +++ b/qualtran/bloqs/gf_arithmetic/gf2_addition.ipynb @@ -0,0 +1,168 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "52204197", + "metadata": { + "cq.autogen": "title_cell" + }, + "source": [ + "# GF($2^m$) Addition" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "74b61b7c", + "metadata": { + "cq.autogen": "top_imports" + }, + "outputs": [], + "source": [ + "from qualtran import Bloq, CompositeBloq, BloqBuilder, Signature, Register\n", + "from qualtran import QBit, QInt, QUInt, QAny\n", + "from qualtran.drawing import show_bloq, show_call_graph, show_counts_sigma\n", + "from typing import *\n", + "import numpy as np\n", + "import sympy\n", + "import cirq" + ] + }, + { + "cell_type": "markdown", + "id": "fab3b162", + "metadata": { + "cq.autogen": "GF2Addition.bloq_doc.md" + }, + "source": [ + "## `GF2Addition`\n", + "In place addition over GF($2^m$).\n", + "\n", + "The bloq implements in place addition of two quantum registers storing elements\n", + "from GF($2^m$). Addition in GF($2^m$) simply reduces to a component wise XOR, which\n", + "can be implemented via CNOT gates. The addition is performed in-place such that\n", + "\n", + "$$\n", + "|x\\rangle |y\\rangle \\rightarrow |x\\rangle |x + y\\rangle\n", + "$$\n", + "\n", + "#### Parameters\n", + " - `bitsize`: The degree $m$ of the galois field $GF(2^m)$. Also corresponds to the number of qubits in each of the two input registers x and y that should be added. \n", + "\n", + "#### Registers\n", + " - `x`: Input THRU register of size $m$ that stores elements from $GF(2^m)$.\n", + " - `y`: Input THRU register of size $m$ that stores elements from $GF(2^m)$.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "507cd9b4", + "metadata": { + "cq.autogen": "GF2Addition.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.gf_arithmetic import GF2Addition" + ] + }, + { + "cell_type": "markdown", + "id": "fae463a5", + "metadata": { + "cq.autogen": "GF2Addition.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "57a0702b", + "metadata": { + "cq.autogen": "GF2Addition.gf16_addition" + }, + "outputs": [], + "source": [ + "gf16_addition = GF2Addition(4)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2c2db2b3", + "metadata": { + "cq.autogen": "GF2Addition.gf2_addition_symbolic" + }, + "outputs": [], + "source": [ + "import sympy\n", + "\n", + "m = sympy.Symbol('m')\n", + "gf2_addition_symbolic = GF2Addition(m)" + ] + }, + { + "cell_type": "markdown", + "id": "3e416779", + "metadata": { + "cq.autogen": "GF2Addition.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0691c5f3", + "metadata": { + "cq.autogen": "GF2Addition.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([gf16_addition, gf2_addition_symbolic],\n", + " ['`gf16_addition`', '`gf2_addition_symbolic`'])" + ] + }, + { + "cell_type": "markdown", + "id": "189469f9", + "metadata": { + "cq.autogen": "GF2Addition.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eae7490a", + "metadata": { + "cq.autogen": "GF2Addition.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "gf16_addition_g, gf16_addition_sigma = gf16_addition.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(gf16_addition_g)\n", + "show_counts_sigma(gf16_addition_sigma)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/qualtran/bloqs/gf_arithmetic/gf2_addition.py b/qualtran/bloqs/gf_arithmetic/gf2_addition.py new file mode 100644 index 000000000..e316e0c58 --- /dev/null +++ b/qualtran/bloqs/gf_arithmetic/gf2_addition.py @@ -0,0 +1,99 @@ +# 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. +from functools import cached_property +from typing import Dict, Set, TYPE_CHECKING, Union + +import attrs + +from qualtran import Bloq, bloq_example, BloqDocSpec, DecomposeTypeError, QGF, Register, Signature +from qualtran.bloqs.basic_gates import CNOT +from qualtran.symbolics import is_symbolic, SymbolicInt + +if TYPE_CHECKING: + from qualtran import BloqBuilder, Soquet + from qualtran.resource_counting import BloqCountDictT, BloqCountT, SympySymbolAllocator + from qualtran.simulation.classical_sim import ClassicalValT + + +@attrs.frozen +class GF2Addition(Bloq): + r"""In place addition over GF($2^m$). + + The bloq implements in place addition of two quantum registers storing elements + from GF($2^m$). Addition in GF($2^m$) simply reduces to a component wise XOR, which + can be implemented via CNOT gates. The addition is performed in-place such that + + $$ + |x\rangle |y\rangle \rightarrow |x\rangle |x + y\rangle + $$ + + Args: + bitsize: The degree $m$ of the galois field $GF(2^m)$. Also corresponds to the number of + qubits in each of the two input registers x and y that should be added. + + Registers: + x: Input THRU register of size $m$ that stores elements from $GF(2^m)$. + y: Input THRU register of size $m$ that stores elements from $GF(2^m)$. + """ + + bitsize: SymbolicInt + + @cached_property + def signature(self) -> 'Signature': + return Signature([Register('x', dtype=self.qgf), Register('y', dtype=self.qgf)]) + + @cached_property + def qgf(self) -> QGF: + return QGF(characteristic=2, degree=self.bitsize) + + def build_composite_bloq( + self, bb: 'BloqBuilder', *, x: 'Soquet', y: 'Soquet' + ) -> Dict[str, 'Soquet']: + if is_symbolic(self.bitsize): + raise DecomposeTypeError(f"Cannot decompose symbolic {self}") + x, y = bb.split(x), bb.split(y) + m = int(self.bitsize) + for i in range(m): + x[i], y[i] = bb.add(CNOT(), ctrl=x[i], target=y[i]) + x, y = (bb.join(x, dtype=self.qgf), bb.join(y, dtype=self.qgf)) + return {'x': x, 'y': y} + + def build_call_graph( + self, ssa: 'SympySymbolAllocator' + ) -> Union['BloqCountDictT', Set['BloqCountT']]: + return {CNOT(): self.bitsize} + + def on_classical_vals(self, *, x, y) -> Dict[str, 'ClassicalValT']: + assert isinstance(x, self.qgf.gf_type) and isinstance(y, self.qgf.gf_type) + return {'x': x, 'y': x + y} + + +@bloq_example +def _gf16_addition() -> GF2Addition: + gf16_addition = GF2Addition(4) + return gf16_addition + + +@bloq_example +def _gf2_addition_symbolic() -> GF2Addition: + import sympy + + m = sympy.Symbol('m') + gf2_addition_symbolic = GF2Addition(m) + return gf2_addition_symbolic + + +_GF2_ADDITION_DOC = BloqDocSpec( + bloq_cls=GF2Addition, examples=(_gf16_addition, _gf2_addition_symbolic) +) diff --git a/qualtran/bloqs/gf_arithmetic/gf2_addition_test.py b/qualtran/bloqs/gf_arithmetic/gf2_addition_test.py new file mode 100644 index 000000000..ed1ed3434 --- /dev/null +++ b/qualtran/bloqs/gf_arithmetic/gf2_addition_test.py @@ -0,0 +1,53 @@ +# 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 galois import GF + +from qualtran.bloqs.gf_arithmetic.gf2_addition import ( + _gf2_addition_symbolic, + _gf16_addition, + GF2Addition, +) +from qualtran.resource_counting import get_cost_value, QECGatesCost +from qualtran.testing import assert_consistent_classical_action + + +def test_gf16_addition(bloq_autotester): + bloq_autotester(_gf16_addition) + + +def test_gf2_addition_symbolic(bloq_autotester): + bloq_autotester(_gf2_addition_symbolic) + + +def test_gf2_addition_classical_sim_quick(): + m = 2 + bloq = GF2Addition(m) + GFM = GF(2**m) + assert_consistent_classical_action(bloq, x=GFM.elements, y=GFM.elements) + + +def test_gf2_addition_resource(): + bloq = _gf2_addition_symbolic.make() + assert get_cost_value(bloq, QECGatesCost()).total_t_count() == 0 + assert get_cost_value(bloq, QECGatesCost()).clifford == bloq.bitsize + + +@pytest.mark.slow +@pytest.mark.parametrize('m', [3, 4, 5]) +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) diff --git a/qualtran/bloqs/gf_arithmetic/gf2_inverse.ipynb b/qualtran/bloqs/gf_arithmetic/gf2_inverse.ipynb new file mode 100644 index 000000000..dec031115 --- /dev/null +++ b/qualtran/bloqs/gf_arithmetic/gf2_inverse.ipynb @@ -0,0 +1,190 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "25463ba5", + "metadata": { + "cq.autogen": "title_cell" + }, + "source": [ + "# GF($2^m$) Inverse" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "38263a0a", + "metadata": { + "cq.autogen": "top_imports" + }, + "outputs": [], + "source": [ + "from qualtran import Bloq, CompositeBloq, BloqBuilder, Signature, Register\n", + "from qualtran import QBit, QInt, QUInt, QAny\n", + "from qualtran.drawing import show_bloq, show_call_graph, show_counts_sigma\n", + "from typing import *\n", + "import numpy as np\n", + "import sympy\n", + "import cirq" + ] + }, + { + "cell_type": "markdown", + "id": "9054cbbb", + "metadata": { + "cq.autogen": "GF2Inverse.bloq_doc.md" + }, + "source": [ + "## `GF2Inverse`\n", + "Out of place inversion for elements in GF($2^m$)\n", + "\n", + "Given a quantum register storing elements from GF($2^m$), this bloq computes the inverse\n", + "of the given element in a new output register, out-of-place. Specifically,\n", + "it implements the transformation\n", + "\n", + "$$\n", + " |a\\rangle |0\\rangle \\rightarrow |a\\rangle |a^{-1}\\rangle\n", + "$$\n", + "\n", + "Inverse is computed by using Fermat's little theorem for Finite Fields, which states that\n", + "for a finite field $\\mathbb{F}$ with $m$ elements, $\\forall a \\in \\mathbb{F}$\n", + "$$\n", + " a^{m} = a\n", + "$$\n", + "\n", + "When the finite field is GF($2^m$), Fermat's little theorem can be used to obtain the relation\n", + "\n", + "$$\n", + " a^{-1} = a^{2^m - 2}\n", + "$$\n", + "\n", + "The exponential $a^{2^m - 2}$ is computed using $\\mathcal{O}(m)$ squaring and\n", + "$\\mathcal{O}(\\log_2(m))$ multiplications via Itoh-Tsujii inversion. The algorithm is described on\n", + "page 4 and 5 of Ref[1] and resembles binary exponentiation. The inverse is computed as $B_{n-1}^2$,\n", + "where $B_1 = x$ and $B_{i+j} = B_i B_j^{2^i}$.\n", + "\n", + "#### Parameters\n", + " - `bitsize`: The degree $m$ of the galois field $GF(2^m)$. Also corresponds to the number of qubits in the input register whose inverse should be calculated. \n", + "\n", + "#### Registers\n", + " - `x`: Input THRU register of size $m$ that stores elements from $GF(2^m)$.\n", + " - `result`: Output RIGHT register of size $m$ that stores $x^{-1}$ from $GF(2^m)$.\n", + " - `junk`: Output RIGHT register of size $m$ and shape ($m - 2$) that stores results from intermediate multiplications. \n", + "\n", + "#### References\n", + " - [Efficient quantum circuits for binary elliptic curve arithmetic: reducing T -gate complexity](https://arxiv.org/abs/1209.6348). Section 2.3\n", + " - [Structure of parallel multipliers for a class of fields GF(2^m)](https://doi.org/10.1016/0890-5401(89)90045-X)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b3a43c81", + "metadata": { + "cq.autogen": "GF2Inverse.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.gf_arithmetic import GF2Inverse" + ] + }, + { + "cell_type": "markdown", + "id": "b0bf14cc", + "metadata": { + "cq.autogen": "GF2Inverse.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f6ce3222", + "metadata": { + "cq.autogen": "GF2Inverse.gf16_inverse" + }, + "outputs": [], + "source": [ + "gf16_inverse = GF2Inverse(4)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6c26f44f", + "metadata": { + "cq.autogen": "GF2Inverse.gf2_inverse_symbolic" + }, + "outputs": [], + "source": [ + "import sympy\n", + "\n", + "m = sympy.Symbol('m')\n", + "gf2_inverse_symbolic = GF2Inverse(m)" + ] + }, + { + "cell_type": "markdown", + "id": "c813ddef", + "metadata": { + "cq.autogen": "GF2Inverse.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "591221ff", + "metadata": { + "cq.autogen": "GF2Inverse.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([gf16_inverse],\n", + " ['`gf16_inverse`'])" + ] + }, + { + "cell_type": "markdown", + "id": "c96fb97d", + "metadata": { + "cq.autogen": "GF2Inverse.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "67aeeb6c", + "metadata": { + "cq.autogen": "GF2Inverse.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "gf16_inverse_g, gf16_inverse_sigma = gf16_inverse.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(gf16_inverse_g)\n", + "show_counts_sigma(gf16_inverse_sigma)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/qualtran/bloqs/gf_arithmetic/gf2_inverse.py b/qualtran/bloqs/gf_arithmetic/gf2_inverse.py new file mode 100644 index 000000000..eb0e80f4f --- /dev/null +++ b/qualtran/bloqs/gf_arithmetic/gf2_inverse.py @@ -0,0 +1,228 @@ +# 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. +from functools import cached_property +from typing import Dict, Set, TYPE_CHECKING, Union + +import attrs +import numpy as np + +from qualtran import ( + Bloq, + bloq_example, + BloqDocSpec, + DecomposeTypeError, + QGF, + Register, + Side, + Signature, +) +from qualtran.bloqs.gf_arithmetic.gf2_addition import GF2Addition +from qualtran.bloqs.gf_arithmetic.gf2_multiplication import GF2Multiplication +from qualtran.bloqs.gf_arithmetic.gf2_square import GF2Square +from qualtran.resource_counting.generalizers import ignore_alloc_free, ignore_split_join +from qualtran.symbolics import bit_length, ceil, is_symbolic, log2, SymbolicInt + +if TYPE_CHECKING: + from qualtran import BloqBuilder, Soquet, SoquetT + from qualtran.resource_counting import BloqCountDictT, BloqCountT, CostKey, SympySymbolAllocator + from qualtran.simulation.classical_sim import ClassicalValT + + +@attrs.frozen +class GF2Inverse(Bloq): + r"""Out of place inversion for elements in GF($2^m$) + + Given a quantum register storing elements from GF($2^m$), this bloq computes the inverse + of the given element in a new output register, out-of-place. Specifically, + it implements the transformation + + $$ + |a\rangle |0\rangle \rightarrow |a\rangle |a^{-1}\rangle + $$ + + Inverse is computed by using Fermat's little theorem for Finite Fields, which states that + for a finite field $\mathbb{F}$ with $m$ elements, $\forall a \in \mathbb{F}$ + $$ + a^{m} = a + $$ + + When the finite field is GF($2^m$), Fermat's little theorem can be used to obtain the relation + + $$ + a^{-1} = a^{2^m - 2} + $$ + + The exponential $a^{2^m - 2}$ is computed using $\mathcal{O}(m)$ squaring and + $\mathcal{O}(\log_2(m))$ multiplications via Itoh-Tsujii inversion. The algorithm is described on + page 4 and 5 of Ref[1] and resembles binary exponentiation. The inverse is computed as $B_{n-1}^2$, + where $B_1 = x$ and $B_{i+j} = B_i B_j^{2^i}$. + + Args: + bitsize: The degree $m$ of the galois field $GF(2^m)$. Also corresponds to the number of + qubits in the input register whose inverse should be calculated. + + Registers: + x: Input THRU register of size $m$ that stores elements from $GF(2^m)$. + result: Output RIGHT register of size $m$ that stores $x^{-1}$ from $GF(2^m)$. + junk: Output RIGHT register of size $m$ and shape ($m - 2$) that stores + results from intermediate multiplications. + + References: + [Efficient quantum circuits for binary elliptic curve arithmetic: reducing T -gate complexity](https://arxiv.org/abs/1209.6348). + Section 2.3 + + [Structure of parallel multipliers for a class of fields GF(2^m)](https://doi.org/10.1016/0890-5401(89)90045-X) + """ + + bitsize: SymbolicInt + + @cached_property + def signature(self) -> 'Signature': + junk_reg = ( + [Register('junk', dtype=self.qgf, shape=(self.n_junk_regs,), side=Side.RIGHT)] + if is_symbolic(self.bitsize) or self.bitsize > 1 + else [] + ) + return Signature( + [ + Register('x', dtype=self.qgf), + Register('result', dtype=self.qgf, side=Side.RIGHT), + *junk_reg, + ] + ) + + @cached_property + def qgf(self) -> QGF: + return QGF(characteristic=2, degree=self.bitsize) + + @cached_property + def n_junk_regs(self) -> SymbolicInt: + return 2 * bit_length(self.bitsize - 1) + self.bitsize_hamming_weight + + @cached_property + def bitsize_hamming_weight(self) -> SymbolicInt: + """Hamming weight of self.bitsize - 1""" + return ( + bit_length(self.bitsize - 1) + if is_symbolic(self.bitsize) + else int(self.bitsize - 1).bit_count() + ) + + def my_static_costs(self, cost_key: 'CostKey'): + from qualtran._infra.gate_with_registers import total_bits + from qualtran.resource_counting import QubitCount + + if isinstance(cost_key, QubitCount): + return total_bits(self.signature.rights()) + + return NotImplemented + + def build_composite_bloq(self, bb: 'BloqBuilder', *, x: 'Soquet') -> Dict[str, 'SoquetT']: + if is_symbolic(self.bitsize): + raise DecomposeTypeError(f"Cannot decompose symbolic {self}") + + result = bb.allocate(dtype=self.qgf) + if self.bitsize == 1: + x, result = bb.add(GF2Addition(self.bitsize), x=x, y=result) + return {'x': x, 'result': result} + + junk = [] + beta = bb.allocate(dtype=self.qgf) + x, beta = bb.add(GF2Addition(self.bitsize), x=x, y=beta) + is_first = True + bitsize_minus_one = int(self.bitsize - 1) + for i in range(bitsize_minus_one.bit_length()): + if (1 << i) & bitsize_minus_one: + if is_first: + beta, result = bb.add(GF2Addition(self.bitsize), x=beta, y=result) + is_first = False + else: + for j in range(2**i): + result = bb.add(GF2Square(self.bitsize), x=result) + beta, result, new_result = bb.add( + GF2Multiplication(self.bitsize), x=beta, y=result + ) + junk.append(result) + result = new_result + beta_squared = bb.allocate(dtype=self.qgf) + beta, beta_squared = bb.add(GF2Addition(self.bitsize), x=beta, y=beta_squared) + for j in range(2**i): + beta_squared = bb.add(GF2Square(self.bitsize), x=beta_squared) + beta, beta_squared, beta_new = bb.add( + GF2Multiplication(self.bitsize), x=beta, y=beta_squared + ) + junk.extend([beta, beta_squared]) + beta = beta_new + junk.append(beta) + result = bb.add(GF2Square(self.bitsize), x=result) + assert len(junk) == self.n_junk_regs, f'{len(junk)=}, {self.n_junk_regs=}' + return {'x': x, 'result': result, 'junk': np.array(junk)} + + def build_call_graph( + self, ssa: 'SympySymbolAllocator' + ) -> Union['BloqCountDictT', Set['BloqCountT']]: + if not is_symbolic(self.bitsize) and self.bitsize == 1: + return {GF2Addition(self.bitsize): 1} + square_count = self.bitsize + 2 ** ceil(log2(self.bitsize)) - 1 + if not is_symbolic(self.bitsize): + n = self.bitsize - 1 + square_count -= n & (-n) + return { + GF2Addition(self.bitsize): 2 + ceil(log2(self.bitsize)), + GF2Square(self.bitsize): square_count, + GF2Multiplication(self.bitsize): ceil(log2(self.bitsize)) + + self.bitsize_hamming_weight + - 1, + } + + def on_classical_vals(self, *, x) -> Dict[str, 'ClassicalValT']: + assert isinstance(x, self.qgf.gf_type) + junk = [] + bitsize_minus_one = int(self.bitsize - 1) + beta = x + result = self.qgf.gf_type(0) + is_first = True + for i in range(bitsize_minus_one.bit_length()): + if (1 << i) & bitsize_minus_one: + if is_first: + is_first = False + result = beta + else: + for j in range(2**i): + result = result**2 + junk.append(result) + result = result * beta + beta_squared = beta ** (2 ** (2**i)) + junk.extend([beta, beta_squared]) + beta = beta * beta_squared + junk.append(beta) + return {'x': x, 'result': x ** (-1), 'junk': np.array(junk)} + + +@bloq_example(generalizer=[ignore_split_join, ignore_alloc_free]) +def _gf16_inverse() -> GF2Inverse: + gf16_inverse = GF2Inverse(4) + return gf16_inverse + + +@bloq_example +def _gf2_inverse_symbolic() -> GF2Inverse: + import sympy + + m = sympy.Symbol('m', positive=True, integer=True) + gf2_inverse_symbolic = GF2Inverse(m) + return gf2_inverse_symbolic + + +_GF2_INVERSE_DOC = BloqDocSpec(bloq_cls=GF2Inverse, examples=(_gf16_inverse,)) diff --git a/qualtran/bloqs/gf_arithmetic/gf2_inverse_test.py b/qualtran/bloqs/gf_arithmetic/gf2_inverse_test.py new file mode 100644 index 000000000..91b793c67 --- /dev/null +++ b/qualtran/bloqs/gf_arithmetic/gf2_inverse_test.py @@ -0,0 +1,65 @@ +# 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 +import sympy +from galois import GF + +from qualtran.bloqs.gf_arithmetic.gf2_inverse import ( + _gf2_inverse_symbolic, + _gf16_inverse, + GF2Inverse, +) +from qualtran.resource_counting import get_cost_value, QECGatesCost, QubitCount +from qualtran.resource_counting.generalizers import ignore_alloc_free, ignore_split_join +from qualtran.symbolics import ceil, log2 +from qualtran.testing import assert_consistent_classical_action, assert_equivalent_bloq_counts + + +def test_gf16_inverse(bloq_autotester): + bloq_autotester(_gf16_inverse) + + +def test_gf2_inverse_symbolic(bloq_autotester): + bloq_autotester(_gf2_inverse_symbolic) + + +def test_gf2_inverse_symbolic_toffoli_complexity(): + bloq = _gf2_inverse_symbolic.make() + m = bloq.bitsize + expected_expr = m**2 * (2 * ceil(log2(m)) - 1) + assert get_cost_value(bloq, QECGatesCost()).total_toffoli_only() - expected_expr == 0 + expected_expr = m * (3 * ceil(log2(m)) + 2) + assert sympy.simplify(get_cost_value(bloq, QubitCount()) - expected_expr) == 0 + + +def test_gf2_inverse_classical_sim_quick(): + m = 1 + bloq = GF2Inverse(m) + GFM = GF(2**m) + assert_consistent_classical_action(bloq, x=GFM.elements[1:]) + + +@pytest.mark.slow +@pytest.mark.parametrize('m', [2, 3, 4, 5]) +def test_gf2_inverse_classical_sim(m): + bloq = GF2Inverse(m) + GFM = GF(2**m) + assert_consistent_classical_action(bloq, x=GFM.elements[1:]) + + +@pytest.mark.parametrize('m', [*range(1, 12)]) +def test_gf2_equivalent_bloq_counts(m): + bloq = GF2Inverse(m) + assert_equivalent_bloq_counts(bloq, generalizer=[ignore_split_join, ignore_alloc_free]) diff --git a/qualtran/bloqs/gf_arithmetic/gf2_multiplication.ipynb b/qualtran/bloqs/gf_arithmetic/gf2_multiplication.ipynb new file mode 100644 index 000000000..b001a27dd --- /dev/null +++ b/qualtran/bloqs/gf_arithmetic/gf2_multiplication.ipynb @@ -0,0 +1,183 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "87c95c4a", + "metadata": { + "cq.autogen": "title_cell" + }, + "source": [ + "# GF($2^m$) Multiplication" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "31c1f087", + "metadata": { + "cq.autogen": "top_imports" + }, + "outputs": [], + "source": [ + "from qualtran import Bloq, CompositeBloq, BloqBuilder, Signature, Register\n", + "from qualtran import QBit, QInt, QUInt, QAny\n", + "from qualtran.drawing import show_bloq, show_call_graph, show_counts_sigma\n", + "from typing import *\n", + "import numpy as np\n", + "import sympy\n", + "import cirq" + ] + }, + { + "cell_type": "markdown", + "id": "307679ec", + "metadata": { + "cq.autogen": "GF2Multiplication.bloq_doc.md" + }, + "source": [ + "## `GF2Multiplication`\n", + "Out of place multiplication over GF($2^m$).\n", + "\n", + "The bloq implements out of place multiplication of two quantum registers storing elements\n", + "from GF($2^m$) using construction described in Ref[1], which extends the classical construction\n", + "of Ref[2].\n", + "\n", + "To multiply two m-bit inputs $a = [a_0, a_1, ..., a_{m-1}]$ and $b = [b_0, b_1, ..., b_{m-1}]$,\n", + "the construction computes the output vector $c$ via the following three steps:\n", + " 1. Compute $e = U.b$ where $U$ is an upper triangular matrix constructed using $a$.\n", + " 2. Compute $Q.e$ where $Q$ is an $m \\times (m - 1)$ reduction matrix that depends upon the\n", + " irreducible polynomial $P(x)$ of the galois field $GF(2^m)$. The i'th column of the\n", + " matrix corresponds to coefficients of the polynomial $x^{m + i} % P(x)$. This matrix $Q$\n", + " is a linear reversible circuit that can be implemented only using CNOT gates.\n", + " 3. Compute $d = L.b$ where $L$ is a lower triangular matrix constructed using $a$.\n", + " 4. Compute $c = d + Q.e$ to obtain the final product.\n", + "\n", + "Steps 1 and 3 are performed using $n^2$ Toffoli gates and step 2 is performed only using CNOT\n", + "gates.\n", + "\n", + "#### Parameters\n", + " - `bitsize`: The degree $m$ of the galois field $GF(2^m)$. Also corresponds to the number of qubits in each of the two input registers $a$ and $b$ that should be multiplied.\n", + " - `plus_equal_prod`: If True, implements the `PlusEqualProduct` version that applies the map $|x\\rangle |y\\rangle |z\\rangle \\rightarrow |x\\rangle |y\\rangle |x + z\\rangle$. \n", + "\n", + "#### Registers\n", + " - `x`: Input THRU register of size $m$ that stores elements from $GF(2^m)$.\n", + " - `y`: Input THRU register of size $m$ that stores elements from $GF(2^m)$.\n", + " - `result`: Register of size $m$ that stores the product $x * y$ in $GF(2^m)$. If plus_equal_prod is True - result is a THRU register and stores $result + x * y$. If plus_equal_prod is False - result is a RIGHT register and stores $x * y$. \n", + "\n", + "#### References\n", + " - [On the Design and Optimization of a Quantum Polynomial-Time Attack on Elliptic Curve Cryptography](https://arxiv.org/abs/0710.1093). \n", + " - [Low complexity bit parallel architectures for polynomial basis multiplication over GF(2m)](https://ieeexplore.ieee.org/abstract/document/1306989). \n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "872a44d1", + "metadata": { + "cq.autogen": "GF2Multiplication.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.gf_arithmetic import GF2Multiplication" + ] + }, + { + "cell_type": "markdown", + "id": "d0f0db7d", + "metadata": { + "cq.autogen": "GF2Multiplication.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "131bc962", + "metadata": { + "cq.autogen": "GF2Multiplication.gf16_multiplication" + }, + "outputs": [], + "source": [ + "gf16_multiplication = GF2Multiplication(4, plus_equal_prod=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "69f564d8", + "metadata": { + "cq.autogen": "GF2Multiplication.gf2_multiplication_symbolic" + }, + "outputs": [], + "source": [ + "import sympy\n", + "\n", + "m = sympy.Symbol('m')\n", + "gf2_multiplication_symbolic = GF2Multiplication(m, plus_equal_prod=False)" + ] + }, + { + "cell_type": "markdown", + "id": "2a62c2b8", + "metadata": { + "cq.autogen": "GF2Multiplication.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cf003e98", + "metadata": { + "cq.autogen": "GF2Multiplication.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([gf16_multiplication, gf2_multiplication_symbolic],\n", + " ['`gf16_multiplication`', '`gf2_multiplication_symbolic`'])" + ] + }, + { + "cell_type": "markdown", + "id": "f14ef0c5", + "metadata": { + "cq.autogen": "GF2Multiplication.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f4b7bf2c", + "metadata": { + "cq.autogen": "GF2Multiplication.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "gf16_multiplication_g, gf16_multiplication_sigma = gf16_multiplication.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(gf16_multiplication_g)\n", + "show_counts_sigma(gf16_multiplication_sigma)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/qualtran/bloqs/gf_arithmetic/gf2_multiplication.py b/qualtran/bloqs/gf_arithmetic/gf2_multiplication.py new file mode 100644 index 000000000..48e3a7f1e --- /dev/null +++ b/qualtran/bloqs/gf_arithmetic/gf2_multiplication.py @@ -0,0 +1,249 @@ +# 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. +from functools import cached_property +from typing import Dict, Set, TYPE_CHECKING, Union + +import attrs +import numpy as np +from galois import GF, Poly + +from qualtran import ( + Bloq, + bloq_example, + BloqDocSpec, + DecomposeTypeError, + QBit, + QGF, + Register, + Side, + Signature, +) +from qualtran.bloqs.basic_gates import CNOT, Toffoli +from qualtran.symbolics import ceil, is_symbolic, log2, Shaped, SymbolicInt + +if TYPE_CHECKING: + from qualtran import BloqBuilder, Soquet + from qualtran.resource_counting import BloqCountDictT, BloqCountT, SympySymbolAllocator + from qualtran.simulation.classical_sim import ClassicalValT + + +def _data_or_shape_to_tuple(data_or_shape: Union[np.ndarray, Shaped]) -> tuple: + return ( + tuple(data_or_shape.flatten()) + if isinstance(data_or_shape, np.ndarray) + else (data_or_shape,) + ) + + +@attrs.frozen +class SynthesizeLRCircuit(Bloq): + """Synthesize linear reversible circuit using CNOT gates. + + Args: + matrix: An n x n matrix describing the linear transformation. + + References: + [Efficient Synthesis of Linear Reversible Circuits](https://arxiv.org/abs/quant-ph/0302002) + """ + + matrix: Union[Shaped, np.ndarray] = attrs.field(eq=_data_or_shape_to_tuple) + is_adjoint: bool = False + + def __attrs_post_init__(self): + assert len(self.matrix.shape) == 2 + n, m = self.matrix.shape + assert is_symbolic(n, m) or n == m + + @cached_property + def signature(self) -> 'Signature': + n, _ = self.matrix.shape + return Signature([Register('q', QBit(), shape=(n,))]) + + def on_classical_vals(self, *, q: 'ClassicalValT') -> Dict[str, 'ClassicalValT']: + matrix = self.matrix + assert isinstance(matrix, np.ndarray) + if self.is_adjoint: + matrix = np.linalg.inv(matrix) + assert np.allclose(matrix, matrix.astype(int)) + matrix = matrix.astype(int) + _, m = matrix.shape + assert isinstance(q, np.ndarray) + return {'q': (matrix @ q) % 2} + + def build_call_graph( + self, ssa: 'SympySymbolAllocator' + ) -> Union['BloqCountDictT', Set['BloqCountT']]: + n = self.matrix.shape[0] + return {CNOT(): ceil(n**2 / log2(n))} + + def adjoint(self) -> 'SynthesizeLRCircuit': + return attrs.evolve(self, is_adjoint=not self.is_adjoint) + + +@attrs.frozen +class GF2Multiplication(Bloq): + r"""Out of place multiplication over GF($2^m$). + + The bloq implements out of place multiplication of two quantum registers storing elements + from GF($2^m$) using construction described in Ref[1], which extends the classical construction + of Ref[2]. + + To multiply two m-bit inputs $a = [a_0, a_1, ..., a_{m-1}]$ and $b = [b_0, b_1, ..., b_{m-1}]$, + the construction computes the output vector $c$ via the following three steps: + 1. Compute $e = U.b$ where $U$ is an upper triangular matrix constructed using $a$. + 2. Compute $Q.e$ where $Q$ is an $m \times (m - 1)$ reduction matrix that depends upon the + irreducible polynomial $P(x)$ of the galois field $GF(2^m)$. The i'th column of the + matrix corresponds to coefficients of the polynomial $x^{m + i} % P(x)$. This matrix $Q$ + is a linear reversible circuit that can be implemented only using CNOT gates. + 3. Compute $d = L.b$ where $L$ is a lower triangular matrix constructed using $a$. + 4. Compute $c = d + Q.e$ to obtain the final product. + + Steps 1 and 3 are performed using $n^2$ Toffoli gates and step 2 is performed only using CNOT + gates. + + Args: + bitsize: The degree $m$ of the galois field $GF(2^m)$. Also corresponds to the number of + qubits in each of the two input registers $a$ and $b$ that should be multiplied. + plus_equal_prod: If True, implements the `PlusEqualProduct` version that applies the + map $|x\rangle |y\rangle |z\rangle \rightarrow |x\rangle |y\rangle |x + z\rangle$. + + Registers: + x: Input THRU register of size $m$ that stores elements from $GF(2^m)$. + y: Input THRU register of size $m$ that stores elements from $GF(2^m)$. + result: Register of size $m$ that stores the product $x * y$ in $GF(2^m)$. + If plus_equal_prod is True - result is a THRU register and stores $result + x * y$. + If plus_equal_prod is False - result is a RIGHT register and stores $x * y$. + + + References: + [On the Design and Optimization of a Quantum Polynomial-Time Attack on + Elliptic Curve Cryptography](https://arxiv.org/abs/0710.1093) + + [Low complexity bit parallel architectures for polynomial basis multiplication + over GF(2m)](https://ieeexplore.ieee.org/abstract/document/1306989) + """ + + bitsize: SymbolicInt + plus_equal_prod: bool = False + + @cached_property + def signature(self) -> 'Signature': + result_side = Side.THRU if self.plus_equal_prod else Side.RIGHT + return Signature( + [ + Register('x', dtype=self.qgf), + Register('y', dtype=self.qgf), + Register('result', dtype=self.qgf, side=result_side), + ] + ) + + @cached_property + def qgf(self) -> QGF: + return QGF(characteristic=2, degree=self.bitsize) + + @cached_property + def reduction_matrix_q(self) -> np.ndarray: + m = int(self.bitsize) + f = self.qgf.gf_type.irreducible_poly + M = np.zeros((m, m)) + alpha = [1] + [0] * m + for i in range(m - 1): + # x ** (m + i) % f + coeffs = (Poly(alpha, GF(2)) % f).coeffs.tolist()[::-1] + coeffs = coeffs + [0] * (m - len(coeffs)) + M[i] = coeffs + alpha += [0] + M[m - 1][m - 1] = 1 + return np.transpose(M) + + @cached_property + def synthesize_reduction_matrix_q(self) -> SynthesizeLRCircuit: + m = self.bitsize + return ( + SynthesizeLRCircuit(Shaped((m, m - 1))) + if is_symbolic(m) + else SynthesizeLRCircuit(self.reduction_matrix_q) + ) + + def build_composite_bloq(self, bb: 'BloqBuilder', **soqs: 'Soquet') -> Dict[str, 'Soquet']: + if is_symbolic(self.bitsize): + raise DecomposeTypeError(f"Cannot decompose symbolic {self}") + x, y = soqs['x'], soqs['y'] + result = soqs['result'] if self.plus_equal_prod else bb.allocate(dtype=self.qgf) + x, y, result = bb.split(x)[::-1], bb.split(y)[::-1], bb.split(result)[::-1] + m = int(self.bitsize) + + # Step-0: PlusEqualProduct special case. + if self.plus_equal_prod: + result = bb.add(self.synthesize_reduction_matrix_q.adjoint(), q=result) + + # Step-1: Multiply Monomials. + for i in range(m): + for j in range(i + 1, m): + ctrl = np.array([x[m - j + i], y[j]]) + ctrl, result[i] = bb.add(Toffoli(), ctrl=ctrl, target=result[i]) + x[m - j + i], y[j] = ctrl[0], ctrl[1] + + # Step-2: Reduce polynomial + result = bb.add(self.synthesize_reduction_matrix_q, q=result) + + # Step-3: Multiply Monomials + for i in range(m): + for j in range(i + 1): + ctrl = np.array([x[j], y[i - j]]) + ctrl, result[i] = bb.add(Toffoli(), ctrl=ctrl, target=result[i]) + x[j], y[i - j] = ctrl[0], ctrl[1] + + # Done :) + x, y, result = ( + bb.join(x[::-1], dtype=self.qgf), + bb.join(y[::-1], dtype=self.qgf), + bb.join(result[::-1], dtype=self.qgf), + ) + return {'x': x, 'y': y, 'result': result} + + def build_call_graph( + self, ssa: 'SympySymbolAllocator' + ) -> Union['BloqCountDictT', Set['BloqCountT']]: + m = self.bitsize + plus_equal_prod = ( + {self.synthesize_reduction_matrix_q.adjoint(): 1} if self.plus_equal_prod else {} + ) + return {Toffoli(): m**2, self.synthesize_reduction_matrix_q: 1} | plus_equal_prod + + def on_classical_vals(self, **vals) -> Dict[str, 'ClassicalValT']: + assert all(isinstance(val, self.qgf.gf_type) for val in vals.values()) + x, y = vals['x'], vals['y'] + result = vals['result'] if self.plus_equal_prod else self.qgf.gf_type(0) + return {'x': x, 'y': y, 'result': result + x * y} + + +@bloq_example +def _gf16_multiplication() -> GF2Multiplication: + gf16_multiplication = GF2Multiplication(4, plus_equal_prod=True) + return gf16_multiplication + + +@bloq_example +def _gf2_multiplication_symbolic() -> GF2Multiplication: + import sympy + + m = sympy.Symbol('m') + gf2_multiplication_symbolic = GF2Multiplication(m, plus_equal_prod=False) + return gf2_multiplication_symbolic + + +_GF2_MULTIPLICATION_DOC = BloqDocSpec( + bloq_cls=GF2Multiplication, examples=(_gf16_multiplication, _gf2_multiplication_symbolic) +) diff --git a/qualtran/bloqs/gf_arithmetic/gf2_multiplication_test.py b/qualtran/bloqs/gf_arithmetic/gf2_multiplication_test.py new file mode 100644 index 000000000..a37b0cddf --- /dev/null +++ b/qualtran/bloqs/gf_arithmetic/gf2_multiplication_test.py @@ -0,0 +1,91 @@ +# 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 numpy as np +import pytest +from galois import GF + +from qualtran import QGF +from qualtran.bloqs.gf_arithmetic.gf2_multiplication import ( + _gf2_multiplication_symbolic, + _gf16_multiplication, + GF2Multiplication, + SynthesizeLRCircuit, +) +from qualtran.testing import assert_consistent_classical_action + + +def test_gf16_multiplication(bloq_autotester): + bloq_autotester(_gf16_multiplication) + + +def test_gf2_multiplication_symbolic(bloq_autotester): + bloq_autotester(_gf2_multiplication_symbolic) + + +def test_synthesize_lr_circuit(): + m = 2 + matrix = GF2Multiplication(m).reduction_matrix_q + bloq = SynthesizeLRCircuit(matrix) + bloq_adj = bloq.adjoint() + QGFM, GFM = QGF(2, m), GF(2**m) + for i in GFM.elements: + bloq_out = bloq.call_classically(q=np.array(QGFM.to_bits(i)))[0] + bloq_adj_out = bloq_adj.call_classically(q=bloq_out)[0] + assert isinstance(bloq_adj_out, np.ndarray) + assert i == QGFM.from_bits([*bloq_adj_out]) + + +@pytest.mark.slow +@pytest.mark.parametrize('m', [3, 4, 5]) +def test_synthesize_lr_circuit_slow(m): + matrix = GF2Multiplication(m).reduction_matrix_q + bloq = SynthesizeLRCircuit(matrix) + bloq_adj = bloq.adjoint() + QGFM, GFM = QGF(2, m), GF(2**m) + for i in GFM.elements: + bloq_out = bloq.call_classically(q=np.array(QGFM.to_bits(i)))[0] + bloq_adj_out = bloq_adj.call_classically(q=bloq_out)[0] + assert isinstance(bloq_adj_out, np.ndarray) + assert i == QGFM.from_bits([*bloq_adj_out]) + + +def test_gf2_plus_equal_prod_classical_sim_quick(): + m = 2 + bloq = GF2Multiplication(m, plus_equal_prod=True) + GFM = GF(2**m) + assert_consistent_classical_action(bloq, x=GFM.elements, y=GFM.elements, result=GFM.elements) + + +@pytest.mark.slow +def test_gf2_plus_equal_prod_classical_sim(): + m = 3 + bloq = GF2Multiplication(m, plus_equal_prod=True) + GFM = GF(2**m) + assert_consistent_classical_action(bloq, x=GFM.elements, y=GFM.elements, result=GFM.elements) + + +def test_gf2_multiplication_classical_sim_quick(): + m = 2 + bloq = GF2Multiplication(m, plus_equal_prod=False) + GFM = GF(2**m) + assert_consistent_classical_action(bloq, x=GFM.elements, y=GFM.elements) + + +@pytest.mark.slow +@pytest.mark.parametrize('m', [3, 4, 5]) +def test_gf2_multiplication_classical_sim(m): + bloq = GF2Multiplication(m, plus_equal_prod=False) + GFM = GF(2**m) + assert_consistent_classical_action(bloq, x=GFM.elements, y=GFM.elements) diff --git a/qualtran/bloqs/gf_arithmetic/gf2_square.ipynb b/qualtran/bloqs/gf_arithmetic/gf2_square.ipynb new file mode 100644 index 000000000..4ddf5e6dd --- /dev/null +++ b/qualtran/bloqs/gf_arithmetic/gf2_square.ipynb @@ -0,0 +1,173 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "cb46f029", + "metadata": { + "cq.autogen": "title_cell" + }, + "source": [ + "# GF($2^m$) Square" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6054ebf1", + "metadata": { + "cq.autogen": "top_imports" + }, + "outputs": [], + "source": [ + "from qualtran import Bloq, CompositeBloq, BloqBuilder, Signature, Register\n", + "from qualtran import QBit, QInt, QUInt, QAny\n", + "from qualtran.drawing import show_bloq, show_call_graph, show_counts_sigma\n", + "from typing import *\n", + "import numpy as np\n", + "import sympy\n", + "import cirq" + ] + }, + { + "cell_type": "markdown", + "id": "abc9f3d5", + "metadata": { + "cq.autogen": "GF2Square.bloq_doc.md" + }, + "source": [ + "## `GF2Square`\n", + "In place squaring for elements in GF($2^m$)\n", + "\n", + "The bloq implements in-place squaring of a quantum registers storing elements\n", + "from GF($2^m$). Specifically, it implements the transformation\n", + "\n", + "$$\n", + " |a\\rangle \\rightarrow |a^2\\rangle\n", + "$$\n", + "\n", + "The key insight is that for elements in GF($2^m$),\n", + "$$\n", + " a^2 =a_0 + a_1 x^2 + a_2 x^4 + ... + a_{n-1} x^{2(n - 1)}\n", + "$$\n", + "\n", + "Thus, squaring can be implemented via a linear reversible circuit using only CNOT gates.\n", + "\n", + "#### Parameters\n", + " - `bitsize`: The degree $m$ of the galois field $GF(2^m)$. Also corresponds to the number of qubits in the input register to be squared. \n", + "\n", + "#### Registers\n", + " - `x`: Input THRU register of size $m$ that stores elements from $GF(2^m)$.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c78c541d", + "metadata": { + "cq.autogen": "GF2Square.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.gf_arithmetic import GF2Square" + ] + }, + { + "cell_type": "markdown", + "id": "3867aabe", + "metadata": { + "cq.autogen": "GF2Square.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10c374a4", + "metadata": { + "cq.autogen": "GF2Square.gf16_square" + }, + "outputs": [], + "source": [ + "gf16_square = GF2Square(4)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "34d1aa8e", + "metadata": { + "cq.autogen": "GF2Square.gf2_square_symbolic" + }, + "outputs": [], + "source": [ + "import sympy\n", + "\n", + "m = sympy.Symbol('m')\n", + "gf2_square_symbolic = GF2Square(m)" + ] + }, + { + "cell_type": "markdown", + "id": "40f24bac", + "metadata": { + "cq.autogen": "GF2Square.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "218453ac", + "metadata": { + "cq.autogen": "GF2Square.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([gf16_square, gf2_square_symbolic],\n", + " ['`gf16_square`', '`gf2_square_symbolic`'])" + ] + }, + { + "cell_type": "markdown", + "id": "ffc34750", + "metadata": { + "cq.autogen": "GF2Square.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "05bdb33f", + "metadata": { + "cq.autogen": "GF2Square.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "gf16_square_g, gf16_square_sigma = gf16_square.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(gf16_square_g)\n", + "show_counts_sigma(gf16_square_sigma)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/qualtran/bloqs/gf_arithmetic/gf2_square.py b/qualtran/bloqs/gf_arithmetic/gf2_square.py new file mode 100644 index 000000000..140895e92 --- /dev/null +++ b/qualtran/bloqs/gf_arithmetic/gf2_square.py @@ -0,0 +1,125 @@ +# 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. +from functools import cached_property +from typing import Dict, Set, TYPE_CHECKING, Union + +import attrs +import numpy as np +from galois import GF, Poly + +from qualtran import Bloq, bloq_example, BloqDocSpec, DecomposeTypeError, QGF, Register, Signature +from qualtran.bloqs.gf_arithmetic.gf2_multiplication import SynthesizeLRCircuit +from qualtran.symbolics import is_symbolic, Shaped, SymbolicInt + +if TYPE_CHECKING: + from qualtran import BloqBuilder, Soquet + from qualtran.resource_counting import BloqCountDictT, BloqCountT, SympySymbolAllocator + from qualtran.simulation.classical_sim import ClassicalValT + + +@attrs.frozen +class GF2Square(Bloq): + r"""In place squaring for elements in GF($2^m$) + + The bloq implements in-place squaring of a quantum registers storing elements + from GF($2^m$). Specifically, it implements the transformation + + $$ + |a\rangle \rightarrow |a^2\rangle + $$ + + The key insight is that for elements in GF($2^m$), + $$ + a^2 =a_0 + a_1 x^2 + a_2 x^4 + ... + a_{n-1} x^{2(n - 1)} + $$ + + Thus, squaring can be implemented via a linear reversible circuit using only CNOT gates. + + Args: + bitsize: The degree $m$ of the galois field $GF(2^m)$. Also corresponds to the number of + qubits in the input register to be squared. + + Registers: + x: Input THRU register of size $m$ that stores elements from $GF(2^m)$. + """ + + bitsize: SymbolicInt + + @cached_property + def signature(self) -> 'Signature': + return Signature([Register('x', dtype=self.qgf)]) + + @cached_property + def qgf(self) -> QGF: + return QGF(characteristic=2, degree=self.bitsize) + + @cached_property + def squaring_matrix(self) -> np.ndarray: + r"""$m \times m$ matrix that maps the input $x^{i}$ to $x^{2 * i} % P(x)$""" + m = int(self.bitsize) + f = self.qgf.gf_type.irreducible_poly + M = np.zeros((m, m)) + alpha = [0] * m + for i in range(m): + # x ** (2 * i) % f + alpha[-i - 1] = 1 + coeffs = ((Poly(alpha, GF(2)) * Poly(alpha, GF(2))) % f).coeffs.tolist()[::-1] + coeffs = coeffs + [0] * (m - len(coeffs)) + M[i] = coeffs + alpha[-i - 1] = 0 + return np.transpose(M) + + @cached_property + def synthesize_squaring_matrix(self) -> SynthesizeLRCircuit: + m = self.bitsize + return ( + SynthesizeLRCircuit(Shaped((m, m))) + if is_symbolic(m) + else SynthesizeLRCircuit(self.squaring_matrix) + ) + + def build_composite_bloq(self, bb: 'BloqBuilder', *, x: 'Soquet') -> Dict[str, 'Soquet']: + if is_symbolic(self.bitsize): + raise DecomposeTypeError(f"Cannot decompose symbolic {self}") + x = bb.split(x)[::-1] + x = bb.add(self.synthesize_squaring_matrix, q=x) + x = bb.join(x[::-1], dtype=self.qgf) + return {'x': x} + + def build_call_graph( + self, ssa: 'SympySymbolAllocator' + ) -> Union['BloqCountDictT', Set['BloqCountT']]: + return {self.synthesize_squaring_matrix: 1} + + def on_classical_vals(self, *, x) -> Dict[str, 'ClassicalValT']: + assert isinstance(x, self.qgf.gf_type) + return {'x': x**2} + + +@bloq_example +def _gf16_square() -> GF2Square: + gf16_square = GF2Square(4) + return gf16_square + + +@bloq_example +def _gf2_square_symbolic() -> GF2Square: + import sympy + + m = sympy.Symbol('m') + gf2_square_symbolic = GF2Square(m) + return gf2_square_symbolic + + +_GF2_SQUARE_DOC = BloqDocSpec(bloq_cls=GF2Square, examples=(_gf16_square, _gf2_square_symbolic)) diff --git a/qualtran/bloqs/gf_arithmetic/gf2_square_test.py b/qualtran/bloqs/gf_arithmetic/gf2_square_test.py new file mode 100644 index 000000000..a6d2ab970 --- /dev/null +++ b/qualtran/bloqs/gf_arithmetic/gf2_square_test.py @@ -0,0 +1,52 @@ +# 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 +import sympy +from galois import GF + +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.symbolics import ceil, log2 +from qualtran.testing import assert_consistent_classical_action + + +def test_gf16_square(bloq_autotester): + bloq_autotester(_gf16_square) + + +def test_gf2_square_symbolic(bloq_autotester): + bloq_autotester(_gf2_square_symbolic) + + +def test_gf2_square_classical_sim_quick(): + m = 2 + bloq = GF2Square(m) + GFM = GF(2**m) + assert_consistent_classical_action(bloq, x=GFM.elements) + + +def test_gf2_square_resource(): + bloq = _gf2_square_symbolic.make() + m = bloq.bitsize + assert get_cost_value(bloq, QECGatesCost()).total_t_count() == 0 + assert sympy.simplify(get_cost_value(bloq, QECGatesCost()).clifford - ceil(m**2 / log2(m))) == 0 + + +@pytest.mark.slow +@pytest.mark.parametrize('m', [3, 4, 5]) +def test_gf2_square_classical_sim(m): + bloq = GF2Square(m) + GFM = GF(2**m) + assert_consistent_classical_action(bloq, x=GFM.elements) diff --git a/qualtran/bloqs/mcmt/and_bloq.ipynb b/qualtran/bloqs/mcmt/and_bloq.ipynb index 8208ef807..54a99b452 100644 --- a/qualtran/bloqs/mcmt/and_bloq.ipynb +++ b/qualtran/bloqs/mcmt/and_bloq.ipynb @@ -56,7 +56,7 @@ " - `target [right]`: The output bit. \n", "\n", "#### References\n", - " - [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). Babbush et. al. 2018. Section III.A. and Fig. 4.\n", + " - [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). Babbush et al. 2018. Section III.A. and Fig. 4.\n", " - [Verifying Measurement Based Uncomputation](https://algassert.com/post/1903). Gidney, C. 2019.\n" ] }, diff --git a/qualtran/bloqs/mcmt/and_bloq.py b/qualtran/bloqs/mcmt/and_bloq.py index 29f8d4188..e1da7fff5 100644 --- a/qualtran/bloqs/mcmt/and_bloq.py +++ b/qualtran/bloqs/mcmt/and_bloq.py @@ -48,7 +48,6 @@ from qualtran.bloqs.basic_gates import TGate, XGate from qualtran.bloqs.bookkeeping import ArbitraryClifford from qualtran.cirq_interop import decompose_from_cirq_style_method -from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.drawing import Circle, directional_text_box, Text, WireSymbol from qualtran.resource_counting import ( big_O, @@ -87,7 +86,7 @@ class And(GateWithRegisters): References: [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). - Babbush et. al. 2018. Section III.A. and Fig. 4. + Babbush et al. 2018. Section III.A. and Fig. 4. [Verifying Measurement Based Uncomputation](https://algassert.com/post/1903). Gidney, C. 2019. """ @@ -126,19 +125,6 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': return {ArbitraryClifford(n=2): 9 + 2 * pre_post_cliffords, TGate(): 4} - def _t_complexity_(self) -> 'TComplexity': - if not FLAG_AND_AS_LEAF: - return NotImplemented - - if isinstance(self.cv1, sympy.Expr) or isinstance(self.cv2, sympy.Expr): - pre_post_cliffords: Union[sympy.Order, int] = 0 - else: - pre_post_cliffords = 2 - self.cv1 - self.cv2 - if self.uncompute: - return TComplexity(clifford=4 + 2 * pre_post_cliffords) - - return TComplexity(t=4, clifford=9 + 2 * pre_post_cliffords) - def on_classical_vals( self, *, ctrl: NDArray[np.uint8], target: Optional[int] = None ) -> Dict[str, ClassicalValT]: diff --git a/qualtran/bloqs/mcmt/controlled_via_and.ipynb b/qualtran/bloqs/mcmt/controlled_via_and.ipynb index 1785c5512..825c66f74 100644 --- a/qualtran/bloqs/mcmt/controlled_via_and.ipynb +++ b/qualtran/bloqs/mcmt/controlled_via_and.ipynb @@ -153,6 +153,36 @@ "show_call_graph(controlled_via_and_ints_g)\n", "show_counts_sigma(controlled_via_and_ints_sigma)" ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "## Nested Controls\n", + "Calling `controlled` on a `ControlledViaAnd` returns another `ControlledViaAnd` by combining the existing and new controls into a single control specification." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "nested_ctrl_bloq = controlled_via_and_qbits.controlled(CtrlSpec(cvs=[1, 1]))\n", + "show_bloqs([nested_ctrl_bloq])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "13", + "metadata": {}, + "outputs": [], + "source": [ + "show_call_graph(nested_ctrl_bloq)" + ] } ], "metadata": { diff --git a/qualtran/bloqs/mcmt/controlled_via_and.py b/qualtran/bloqs/mcmt/controlled_via_and.py index a6778483d..a0ae58dee 100644 --- a/qualtran/bloqs/mcmt/controlled_via_and.py +++ b/qualtran/bloqs/mcmt/controlled_via_and.py @@ -13,7 +13,7 @@ # limitations under the License. from collections import Counter from functools import cached_property -from typing import TYPE_CHECKING +from typing import Iterable, Sequence, TYPE_CHECKING import numpy as np from attrs import frozen @@ -23,7 +23,7 @@ from qualtran.bloqs.mcmt.ctrl_spec_and import CtrlSpecAnd if TYPE_CHECKING: - from qualtran import BloqBuilder, SoquetT + from qualtran import AddControlledT, BloqBuilder, SoquetT from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator @@ -50,8 +50,7 @@ def _is_single_bit_control(self) -> bool: @cached_property def _single_control_value(self) -> int: - assert self._is_single_bit_control() - return self.ctrl_spec._cvs_tuple[0] + return self.ctrl_spec.get_single_ctrl_bit() def adjoint(self) -> 'ControlledViaAnd': return ControlledViaAnd(self.subbloq.adjoint(), self.ctrl_spec) @@ -126,6 +125,28 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': return counts + def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> tuple['Bloq', 'AddControlledT']: + ctrl_spec_combined = CtrlSpec( + qdtypes=ctrl_spec.qdtypes + self.ctrl_spec.qdtypes, + cvs=ctrl_spec.cvs + self.ctrl_spec.cvs, + ) + ctrl_bloq = ControlledViaAnd(subbloq=self.subbloq, ctrl_spec=ctrl_spec_combined) + + def _adder( + bb: 'BloqBuilder', ctrl_soqs: Sequence['SoquetT'], in_soqs: dict[str, 'SoquetT'] + ) -> tuple[Iterable['SoquetT'], Iterable['SoquetT']]: + rhs_ctrl_soqs_t = tuple(in_soqs.pop(name) for name in self.ctrl_reg_names) + all_ctrl_soqs_t = tuple([*ctrl_soqs, *rhs_ctrl_soqs_t]) + + all_ctrl_soqs_d = dict(zip(ctrl_bloq.ctrl_reg_names, all_ctrl_soqs_t)) + all_soqs = all_ctrl_soqs_d | in_soqs + all_soqs = bb.add_t(ctrl_bloq, **all_soqs) + + n_ctrl_lhs = ctrl_spec.num_ctrl_reg + return all_soqs[:n_ctrl_lhs], all_soqs[n_ctrl_lhs:] + + return ctrl_bloq, _adder + @bloq_example def _controlled_via_and_qbits() -> ControlledViaAnd: diff --git a/qualtran/bloqs/mcmt/controlled_via_and_test.py b/qualtran/bloqs/mcmt/controlled_via_and_test.py index 981d7396f..9e60e3ecc 100644 --- a/qualtran/bloqs/mcmt/controlled_via_and_test.py +++ b/qualtran/bloqs/mcmt/controlled_via_and_test.py @@ -15,12 +15,14 @@ import pytest from qualtran import Controlled, CtrlSpec, QInt, QUInt +from qualtran.bloqs.basic_gates import XGate from qualtran.bloqs.for_testing.matrix_gate import MatrixGate from qualtran.bloqs.mcmt.controlled_via_and import ( _controlled_via_and_ints, _controlled_via_and_qbits, ControlledViaAnd, ) +from qualtran.resource_counting import GateCounts, get_cost_value, QECGatesCost def test_examples(bloq_autotester): @@ -40,10 +42,39 @@ def test_tensor_against_naive_controlled(ctrl_spec: CtrlSpec): rs = np.random.RandomState(42) subbloq = MatrixGate.random(2, random_state=rs) - cbloq = ControlledViaAnd(subbloq, ctrl_spec) - naive_cbloq = Controlled(subbloq, ctrl_spec) + ctrl_bloq = ControlledViaAnd(subbloq, ctrl_spec) + naive_ctrl_bloq = Controlled(subbloq, ctrl_spec) - expected_tensor = naive_cbloq.tensor_contract() - actual_tensor = cbloq.tensor_contract() + expected_tensor = naive_ctrl_bloq.tensor_contract() + actual_tensor = ctrl_bloq.tensor_contract() np.testing.assert_allclose(expected_tensor, actual_tensor) + + +def test_nested_controls(): + spec1 = CtrlSpec(QUInt(4), [2, 3]) + spec2 = CtrlSpec(QInt(4), [1, 2]) + spec = CtrlSpec((QInt(4), QUInt(4)), ([1, 2], [2, 3])) + + rs = np.random.RandomState(42) + bloq = MatrixGate.random(2, random_state=rs) + + ctrl_bloq = ControlledViaAnd(bloq, spec1).controlled(ctrl_spec=spec2) + assert ctrl_bloq == ControlledViaAnd(bloq, spec) + + +def test_nested_controlled_x(): + bloq = XGate() + + ctrl_bloq = ControlledViaAnd(bloq, CtrlSpec(cvs=[1, 1])).controlled( + ctrl_spec=CtrlSpec(cvs=[1, 1]) + ) + cost = get_cost_value(ctrl_bloq, QECGatesCost()) + + n_ands = 3 + assert cost == GateCounts(and_bloq=n_ands, clifford=n_ands + 1, measurement=n_ands) + + np.testing.assert_allclose( + ctrl_bloq.tensor_contract(), + XGate().controlled(CtrlSpec(cvs=[1, 1, 1, 1])).tensor_contract(), + ) diff --git a/qualtran/bloqs/mcmt/ctrl_spec_and.ipynb b/qualtran/bloqs/mcmt/ctrl_spec_and.ipynb index c266e0511..e96d6fa66 100644 --- a/qualtran/bloqs/mcmt/ctrl_spec_and.ipynb +++ b/qualtran/bloqs/mcmt/ctrl_spec_and.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "0beec492", + "id": "209f4989", "metadata": { "cq.autogen": "title_cell" }, @@ -13,7 +13,7 @@ { "cell_type": "code", "execution_count": null, - "id": "4d31094e", + "id": "373927c3", "metadata": { "cq.autogen": "top_imports" }, @@ -30,7 +30,7 @@ }, { "cell_type": "markdown", - "id": "12aba876", + "id": "d7dd05fa", "metadata": { "cq.autogen": "CtrlSpecAnd.bloq_doc.md" }, @@ -60,13 +60,13 @@ " - `target [right]`: The output bit storing the result of the `ctrl_spec`. \n", "\n", "#### References\n", - " - [Unqomp: synthesizing uncomputation in Quantum circuits](https://dl.acm.org/doi/10.1145/3453483.3454040). Paradis et. al. 2021.\n" + " - [Unqomp: synthesizing uncomputation in Quantum circuits](https://dl.acm.org/doi/10.1145/3453483.3454040). Paradis et al. 2021.\n" ] }, { "cell_type": "code", "execution_count": null, - "id": "842307af", + "id": "837cc8f9", "metadata": { "cq.autogen": "CtrlSpecAnd.bloq_doc.py" }, @@ -77,7 +77,7 @@ }, { "cell_type": "markdown", - "id": "76f2965f", + "id": "760fb1b0", "metadata": { "cq.autogen": "CtrlSpecAnd.example_instances.md" }, @@ -88,7 +88,7 @@ { "cell_type": "code", "execution_count": null, - "id": "68a43214", + "id": "8486e383", "metadata": { "cq.autogen": "CtrlSpecAnd.ctrl_on_int" }, @@ -102,7 +102,7 @@ { "cell_type": "code", "execution_count": null, - "id": "945fa4a4", + "id": "1510fd98", "metadata": { "cq.autogen": "CtrlSpecAnd.ctrl_on_bits" }, @@ -116,7 +116,7 @@ { "cell_type": "code", "execution_count": null, - "id": "5e6374b2", + "id": "489cf199", "metadata": { "cq.autogen": "CtrlSpecAnd.ctrl_on_nd_bits" }, @@ -132,7 +132,7 @@ { "cell_type": "code", "execution_count": null, - "id": "2047fccf", + "id": "bc189810", "metadata": { "cq.autogen": "CtrlSpecAnd.ctrl_on_multiple_values" }, @@ -145,9 +145,39 @@ ")" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "5fa2c166", + "metadata": { + "cq.autogen": "CtrlSpecAnd.ctrl_on_symbolic_cv" + }, + "outputs": [], + "source": [ + "from qualtran import CtrlSpec\n", + "from qualtran.symbolics import Shaped\n", + "\n", + "ctrl_on_symbolic_cv = CtrlSpecAnd(CtrlSpec(cvs=Shaped((2,))))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d0d78907", + "metadata": { + "cq.autogen": "CtrlSpecAnd.ctrl_on_symbolic_cv_multi" + }, + "outputs": [], + "source": [ + "from qualtran import CtrlSpec\n", + "from qualtran.symbolics import Shaped\n", + "\n", + "ctrl_on_symbolic_cv_multi = CtrlSpecAnd(CtrlSpec(cvs=Shaped((3,))))" + ] + }, { "cell_type": "markdown", - "id": "55581cc0", + "id": "60a9fcc0", "metadata": { "cq.autogen": "CtrlSpecAnd.graphical_signature.md" }, @@ -158,20 +188,20 @@ { "cell_type": "code", "execution_count": null, - "id": "5ea9e6e5", + "id": "f1b9274c", "metadata": { "cq.autogen": "CtrlSpecAnd.graphical_signature.py" }, "outputs": [], "source": [ "from qualtran.drawing import show_bloqs\n", - "show_bloqs([ctrl_on_int, ctrl_on_bits, ctrl_on_nd_bits, ctrl_on_multiple_values],\n", - " ['`ctrl_on_int`', '`ctrl_on_bits`', '`ctrl_on_nd_bits`', '`ctrl_on_multiple_values`'])" + "show_bloqs([ctrl_on_int, ctrl_on_bits, ctrl_on_nd_bits, ctrl_on_multiple_values, ctrl_on_symbolic_cv, ctrl_on_symbolic_cv_multi],\n", + " ['`ctrl_on_int`', '`ctrl_on_bits`', '`ctrl_on_nd_bits`', '`ctrl_on_multiple_values`', '`ctrl_on_symbolic_cv`', '`ctrl_on_symbolic_cv_multi`'])" ] }, { "cell_type": "markdown", - "id": "3f5bb7d6", + "id": "704a2863", "metadata": { "cq.autogen": "CtrlSpecAnd.call_graph.md" }, @@ -182,7 +212,7 @@ { "cell_type": "code", "execution_count": null, - "id": "f83e1715", + "id": "1c2ab375", "metadata": { "cq.autogen": "CtrlSpecAnd.call_graph.py" }, diff --git a/qualtran/bloqs/mcmt/ctrl_spec_and.py b/qualtran/bloqs/mcmt/ctrl_spec_and.py index d22b47f4e..713a94047 100644 --- a/qualtran/bloqs/mcmt/ctrl_spec_and.py +++ b/qualtran/bloqs/mcmt/ctrl_spec_and.py @@ -14,6 +14,8 @@ from functools import cached_property from typing import Optional, TYPE_CHECKING, Union +import numpy as np +import sympy from attrs import frozen from qualtran import ( @@ -71,7 +73,7 @@ class CtrlSpecAnd(Bloq): References: [Unqomp: synthesizing uncomputation in Quantum circuits](https://dl.acm.org/doi/10.1145/3453483.3454040) - Paradis et. al. 2021. + Paradis et al. 2021. """ ctrl_spec: CtrlSpec @@ -80,7 +82,9 @@ def __attrs_post_init__(self): if not is_symbolic(self.n_ctrl_qubits) and self.n_ctrl_qubits <= 1: raise ValueError(f"Expected at least 2 controls, got {self.n_ctrl_qubits}") for qdtype in self.ctrl_spec.qdtypes: - if not isinstance(qdtype, (QBit, QInt, QUInt, BQUInt, QIntOnesComp, QMontgomeryUInt)): + if not isinstance( + qdtype, (QBit, QAny, QInt, QUInt, BQUInt, QIntOnesComp, QMontgomeryUInt) + ): raise NotImplementedError("CtrlSpecAnd currently only supports integer types") @cached_property @@ -88,7 +92,7 @@ def signature(self) -> Signature: return Signature( [ *self.control_registers, - *self.junk_registers(), + *self.junk_registers, Register('target', QBit(), side=Side.RIGHT), ] ) @@ -100,6 +104,7 @@ def control_registers(self) -> tuple[Register, ...]: for i, (dtype, shape) in enumerate(self.ctrl_spec.activation_function_dtypes()) ) + @cached_property def junk_registers(self) -> tuple[Register, ...]: if not is_symbolic(self.n_ctrl_qubits) and self.n_ctrl_qubits == 2: return () @@ -121,6 +126,7 @@ def _flat_cvs(self) -> Union[tuple[int, ...], HasLength]: flat_cvs: list[int] = [] for reg, cv in zip(self.control_registers, self.ctrl_spec.cvs): + assert isinstance(cv, np.ndarray) flat_cvs.extend(reg.dtype.to_bits_array(cv.ravel()).ravel()) return tuple(flat_cvs) @@ -136,8 +142,10 @@ def build_composite_bloq(self, bb: 'BloqBuilder', **soqs: 'SoquetT') -> dict[str # Compute the single control qubit `target` if self.n_ctrl_qubits == 2: - assert isinstance(self._flat_cvs, tuple) - cv1, cv2 = self._flat_cvs + if isinstance(self._flat_cvs, tuple): + cv1, cv2 = self._flat_cvs + else: + cv1, cv2 = sympy.symbols("cv1, cv2") ctrl_qubits, target = bb.add(And(cv1, cv2), ctrl=ctrl_qubits) junk = None else: @@ -173,8 +181,10 @@ def wire_symbol(self, reg: Optional[Register], idx: tuple[int, ...] = tuple()) - def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': if not is_symbolic(self.n_ctrl_qubits) and self.n_ctrl_qubits == 2: - assert isinstance(self._flat_cvs, tuple) - cv1, cv2 = self._flat_cvs + if isinstance(self._flat_cvs, tuple): + cv1, cv2 = self._flat_cvs + else: + cv1, cv2 = sympy.symbols("cv1, cv2") return {And(cv1, cv2): 1} return {MultiAnd(self._flat_cvs): 1} @@ -216,7 +226,42 @@ def _ctrl_on_multiple_values() -> CtrlSpecAnd: return ctrl_on_multiple_values +@bloq_example(generalizer=ignore_split_join) +def _ctrl_on_symbolic_cv() -> CtrlSpecAnd: + from qualtran import CtrlSpec + from qualtran.symbolics import Shaped + + ctrl_on_symbolic_cv = CtrlSpecAnd(CtrlSpec(cvs=Shaped((2,)))) + return ctrl_on_symbolic_cv + + +@bloq_example(generalizer=ignore_split_join) +def _ctrl_on_symbolic_cv_multi() -> CtrlSpecAnd: + from qualtran import CtrlSpec + from qualtran.symbolics import Shaped + + ctrl_on_symbolic_cv_multi = CtrlSpecAnd(CtrlSpec(cvs=Shaped((3,)))) + return ctrl_on_symbolic_cv_multi + + +@bloq_example(generalizer=ignore_split_join) +def _ctrl_on_symbolic_n_ctrls() -> CtrlSpecAnd: + from qualtran import CtrlSpec + from qualtran.symbolics import Shaped + + n = sympy.Symbol("n") + ctrl_on_symbolic_cv_multi = CtrlSpecAnd(CtrlSpec(cvs=Shaped((n,)))) + return ctrl_on_symbolic_cv_multi + + _CTRLSPEC_AND_DOC = BloqDocSpec( bloq_cls=CtrlSpecAnd, - examples=(_ctrl_on_int, _ctrl_on_bits, _ctrl_on_nd_bits, _ctrl_on_multiple_values), + examples=( + _ctrl_on_int, + _ctrl_on_bits, + _ctrl_on_nd_bits, + _ctrl_on_multiple_values, + _ctrl_on_symbolic_cv, + _ctrl_on_symbolic_cv_multi, + ), ) diff --git a/qualtran/bloqs/mcmt/ctrl_spec_and_test.py b/qualtran/bloqs/mcmt/ctrl_spec_and_test.py index eb3ce38d8..f0f8638db 100644 --- a/qualtran/bloqs/mcmt/ctrl_spec_and_test.py +++ b/qualtran/bloqs/mcmt/ctrl_spec_and_test.py @@ -14,19 +14,33 @@ import numpy as np import pytest +import qualtran.testing as qlt_testing from qualtran import CtrlSpec, QUInt from qualtran.bloqs.mcmt.ctrl_spec_and import ( _ctrl_on_bits, _ctrl_on_int, _ctrl_on_multiple_values, _ctrl_on_nd_bits, + _ctrl_on_symbolic_cv, + _ctrl_on_symbolic_cv_multi, + _ctrl_on_symbolic_n_ctrls, CtrlSpecAnd, ) from qualtran.simulation.classical_sim import get_classical_truth_table @pytest.mark.parametrize( - "example", [_ctrl_on_bits, _ctrl_on_nd_bits, _ctrl_on_int, _ctrl_on_multiple_values] + "example", + [ + _ctrl_on_bits, + _ctrl_on_nd_bits, + _ctrl_on_int, + _ctrl_on_multiple_values, + _ctrl_on_symbolic_cv, + _ctrl_on_symbolic_cv_multi, + _ctrl_on_symbolic_n_ctrls, + ], + ids=lambda ex: ex.name, ) def test_examples(bloq_autotester, example): bloq_autotester(example) @@ -51,3 +65,8 @@ def test_truth_table_using_classical_sim(ctrl_spec: CtrlSpec): # check: target bit (last output value) matches `is_active` assert out_vals[-1] == ctrl_spec.is_active(*in_vals) + + +@pytest.mark.notebook +def test_notebook(): + qlt_testing.execute_notebook('ctrl_spec_and') diff --git a/qualtran/bloqs/mcmt/multi_control_pauli.py b/qualtran/bloqs/mcmt/multi_control_pauli.py index 6ea86c686..27300e739 100644 --- a/qualtran/bloqs/mcmt/multi_control_pauli.py +++ b/qualtran/bloqs/mcmt/multi_control_pauli.py @@ -185,6 +185,7 @@ class MultiControlX(MultiControlPauli): See :class:`MultiControlPauli` for implementation and costs. """ + target_gate: cirq.Pauli = field(init=False) @target_gate.default @@ -198,6 +199,7 @@ class MultiControlZ(MultiControlPauli): See :class:`MultiControlPauli` for implementation and costs. """ + target_gate: cirq.Pauli = field(init=False) @target_gate.default diff --git a/qualtran/bloqs/mcmt/specialized_ctrl.ipynb b/qualtran/bloqs/mcmt/specialized_ctrl.ipynb new file mode 100644 index 000000000..67c4664b3 --- /dev/null +++ b/qualtran/bloqs/mcmt/specialized_ctrl.ipynb @@ -0,0 +1,201 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "## Bloqs with specialized controlled implementations\n", + "\n", + "In some cases, a bloq may have a specialized singly-controlled version (e.g. `LCUBlockEncoding`).\n", + "Qualtran provides a convenience methods `get_ctrl_system_1bit_cv` and `get_ctrl_system_1bit_cv_from_bloqs` to override the `get_ctrl_system`. These methods ensure that multiply-controlled bloqs are correctly reduced to the provided singly-controlled variants.\n", + "\n", + "- `get_ctrl_system_1bit_cv_from_bloqs` - Override when a specialized controlled-by-1 implementation is available.\n", + "- `get_ctrl_system_1bit_cv` - Override when both specialized controlled-by-1 and controlled-by-0 implementations are available.\n", + "\n", + "The following demonstrates an example for a bloq implementing $T^\\dagger X T$, where the controlled version only needs to control the $X$." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1", + "metadata": {}, + "outputs": [], + "source": [ + "import attrs\n", + "from qualtran import Bloq, BloqBuilder, Soquet, SoquetT, Signature, CtrlSpec, AddControlledT\n", + "from qualtran.bloqs.basic_gates import TGate, XGate, CNOT\n", + "\n", + "\n", + "@attrs.frozen\n", + "class BloqWithSpecializedCtrl(Bloq):\n", + " \"\"\"Bloq implementing $T^\\dagger X T$\"\"\"\n", + " is_controlled: bool = False\n", + "\n", + " @property\n", + " def signature(self) -> 'Signature':\n", + " n_ctrls = 1 if self.is_controlled else 0\n", + " return Signature.build(ctrl=n_ctrls, q=1)\n", + " \n", + " def build_composite_bloq(self, bb: 'BloqBuilder', q: 'Soquet', **soqs) -> dict[str, 'SoquetT']:\n", + " ctrl = soqs.pop('ctrl', None)\n", + " \n", + " q = bb.add(TGate(), q=q)\n", + " if self.is_controlled:\n", + " ctrl, q = bb.add(CNOT(), ctrl=ctrl, target=q)\n", + " else:\n", + " ctrl, q = bb.add(XGate(), ctrl=ctrl, target=q)\n", + " q = bb.add(TGate().adjoint(), q=q)\n", + " \n", + " out_soqs = {'q': q}\n", + " if ctrl:\n", + " out_soqs |= {'ctrl': ctrl}\n", + " return out_soqs\n", + " \n", + " def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> tuple['Bloq', 'AddControlledT']:\n", + " from qualtran.bloqs.mcmt.specialized_ctrl import get_ctrl_system_1bit_cv_from_bloqs\n", + "\n", + " return get_ctrl_system_1bit_cv_from_bloqs(\n", + " self,\n", + " ctrl_spec,\n", + " current_ctrl_bit=1 if self.is_controlled else None,\n", + " bloq_with_ctrl=attrs.evolve(self, is_controlled=True),\n", + " ctrl_reg_name='ctrl',\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloq, show_call_graph\n", + "\n", + "bloq = BloqWithSpecializedCtrl().controlled().controlled()\n", + "show_bloq(bloq.decompose_bloq().flatten())\n", + "show_call_graph(bloq)" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Propagating the Adjoint\n", + "\n", + "In the above bloq, calling controlled on the adjoint does not push the controls into the bloq, and therefore does not use the specialized implementation provided." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "BloqWithSpecializedCtrl().adjoint().controlled()" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "This can be fixed by overriding the adjoint using a special wrapper for this case - `AdjointWithSpecializedCtrl`. This is a subclass of the default `Adjoint` metabloq, and ensures that single-qubit controls are pushed into the underlying bloq." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "@attrs.frozen\n", + "class BloqWithSpecializedCtrlWithAdjoint(Bloq):\n", + " \"\"\"Bloq implementing $T^\\dagger X T$\"\"\"\n", + " is_controlled: bool = False\n", + "\n", + " @property\n", + " def signature(self) -> 'Signature':\n", + " n_ctrls = 1 if self.is_controlled else 0\n", + " return Signature.build(ctrl=n_ctrls, q=1)\n", + " \n", + " def build_composite_bloq(self, bb: 'BloqBuilder', q: 'Soquet', **soqs) -> dict[str, 'SoquetT']:\n", + " ctrl = soqs.pop('ctrl', None)\n", + " \n", + " q = bb.add(TGate(), q=q)\n", + " if self.is_controlled:\n", + " ctrl, q = bb.add(CNOT(), ctrl=ctrl, target=q)\n", + " else:\n", + " ctrl, q = bb.add(XGate(), ctrl=ctrl, target=q)\n", + " q = bb.add(TGate().adjoint(), q=q)\n", + " \n", + " out_soqs = {'q': q}\n", + " if ctrl:\n", + " out_soqs |= {'ctrl': ctrl}\n", + " return out_soqs\n", + " \n", + " def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> tuple['Bloq', 'AddControlledT']:\n", + " from qualtran.bloqs.mcmt.specialized_ctrl import get_ctrl_system_1bit_cv_from_bloqs\n", + "\n", + " return get_ctrl_system_1bit_cv_from_bloqs(\n", + " self,\n", + " ctrl_spec,\n", + " current_ctrl_bit=1 if self.is_controlled else None,\n", + " bloq_with_ctrl=attrs.evolve(self, is_controlled=True),\n", + " ctrl_reg_name='ctrl',\n", + " )\n", + "\n", + " def adjoint(self):\n", + " from qualtran.bloqs.mcmt.specialized_ctrl import AdjointWithSpecializedCtrl, SpecializeOnCtrlBit\n", + " \n", + " return AdjointWithSpecializedCtrl(self, SpecializeOnCtrlBit.ONE)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "BloqWithSpecializedCtrlWithAdjoint().adjoint().controlled()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "assert BloqWithSpecializedCtrlWithAdjoint().adjoint().controlled() == BloqWithSpecializedCtrlWithAdjoint(is_controlled=True).adjoint()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/qualtran/bloqs/mcmt/specialized_ctrl.py b/qualtran/bloqs/mcmt/specialized_ctrl.py new file mode 100644 index 000000000..ce1bd3362 --- /dev/null +++ b/qualtran/bloqs/mcmt/specialized_ctrl.py @@ -0,0 +1,379 @@ +# 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 enum +from functools import cached_property +from typing import Callable, cast, Iterable, Optional, Sequence, TYPE_CHECKING + +import attrs +import numpy as np + +from qualtran import Adjoint, Bloq, BloqBuilder, CompositeBloq, QBit, Register, Signature +from qualtran.bloqs.bookkeeping import AutoPartition + +if TYPE_CHECKING: + from qualtran import AddControlledT, CtrlSpec, SoquetT + from qualtran._infra.controlled import ControlBit + from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator + + +@attrs.frozen +class _MultiControlledFromSinglyControlled(Bloq): + """Helper bloq implementing a multi-controlled-U given access to controlled-U. + + This is for internal use only. For reducing multiple controls to a single control, + see :class:`qualtran.bloqs.mcmt.ControlledViaAnd` and + :meth:`qualtran.bloqs.mcmt.specialized_ctrl.get_ctrl_system_1bit_cv`. + + This bloq is used as an intermediate bloq by `get_ctrl_system_1bit_cv` in the + controlled-controlled-bloq case. To cleanly support further controlling this bloq, + the `cvs` attribute accepts a tuple (of at least two controls), and defers to + `ControlledViaAnd` whenever possible, and only extends the `cvs` in the edge cases. + """ + + cvs: tuple[int, ...] + ctrl_bloq: Bloq + ctrl_reg_name: str + + def __attrs_post_init__(self): + assert len(self.cvs) >= 2, f"{self} must have at least 2 controls, got {self.cvs=}" + + @cached_property + def signature(self) -> 'Signature': + return Signature( + [Register(self.ctrl_reg_name, dtype=QBit(), shape=(len(self.cvs),))] + + [reg for reg in self.ctrl_bloq.signature if reg.name != self.ctrl_reg_name] + ) + + @cached_property + def _and_bloq(self) -> Bloq: + from qualtran.bloqs.mcmt import And, MultiAnd + + if len(self.cvs) == 2: + return And(*self.cvs) + else: + return MultiAnd(self.cvs) + + def build_composite_bloq(self, bb: 'BloqBuilder', **soqs: 'SoquetT') -> dict[str, 'SoquetT']: + and_soqs = bb.add_d(self._and_bloq, ctrl=soqs.pop(self.ctrl_reg_name)) + + soqs |= {self.ctrl_reg_name: and_soqs.pop('target')} + soqs = bb.add_d(self.ctrl_bloq, **soqs) + and_soqs |= {'target': soqs.pop(self.ctrl_reg_name)} + + soqs |= {self.ctrl_reg_name: bb.add(self._and_bloq.adjoint(), **and_soqs)} + + return soqs + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': + return {self._and_bloq: 1, self.ctrl_bloq: 1, self._and_bloq.adjoint(): 1} + + def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> tuple['Bloq', 'AddControlledT']: + if ctrl_spec.num_qubits != 1: + return super().get_ctrl_system(ctrl_spec=ctrl_spec) + + ctrl_bloq = attrs.evolve(self, cvs=(ctrl_spec.get_single_ctrl_bit(),) + self.cvs) + + def _adder(bb, ctrl_soqs, in_soqs): + in_soqs[self.ctrl_reg_name] = np.concatenate(ctrl_soqs, in_soqs[self.ctrl_reg_name]) + ctrls, *out_soqs = bb.add_t(ctrl_bloq, **in_soqs) + return ctrls[:1], [*ctrls[1:], *out_soqs] + + return ctrl_bloq, _adder + + def __str__(self): + return f'C[{len(self.cvs)-1}][{self.ctrl_bloq}]' + + +def _get_ctrl_system_1bit_cv( + bloq: 'Bloq', + ctrl_spec: 'CtrlSpec', + *, + current_ctrl_bit: Optional['ControlBit'], + get_ctrl_bloq_and_ctrl_reg_name: Callable[['ControlBit'], Optional[tuple['Bloq', str]]], +) -> tuple['Bloq', 'AddControlledT']: + """Internal method to build the control system for a bloq using single-qubit controlled variants. + + Uses the provided specialized implementation when a singly-controlled variant of the bloq is + requested. When controlled by multiple qubits, the controls are reduced to a single qubit + and the singly-controlled bloq is used. + + The user can provide specializations for the bloq controlled by `1` and (optionally) by `0`. + The specialization for control bit `1` must be provided. + In case a specialization for a control bit `0` is not provided, the default fallback is used + instead, which wraps the bloq using the `Controlled` metabloq. + + Args: + bloq: The current bloq. + ctrl_spec: The control specification + current_ctrl_bit: The control bit of the current bloq, one of `0, 1, None`. + get_ctrl_bloq_and_ctrl_reg_name: A callable that accepts a control bit (`0` or `1`), + and returns the controlled variant of this bloq and the name of the control register. + If the callable returns `None`, then the default fallback is used. + """ + from qualtran import Soquet + from qualtran.bloqs.mcmt import ControlledViaAnd + + def _get_default_fallback(): + return ControlledViaAnd.make_ctrl_system(bloq=bloq, ctrl_spec=ctrl_spec) + + if ctrl_spec.num_qubits != 1: + return _get_default_fallback() + + ctrl_bit = ctrl_spec.get_single_ctrl_bit() + + if current_ctrl_bit is None: + # the easy case: use the controlled bloq + ctrl_bloq_and_ctrl_reg_name = get_ctrl_bloq_and_ctrl_reg_name(ctrl_bit) + if ctrl_bloq_and_ctrl_reg_name is None: + assert ctrl_bit != 1, "invalid usage: controlled-by-1 variant must be provided" + return _get_default_fallback() + + ctrl_bloq, ctrl_reg_name = ctrl_bloq_and_ctrl_reg_name + + def _adder( + bb: 'BloqBuilder', ctrl_soqs: Sequence['SoquetT'], in_soqs: dict[str, 'SoquetT'] + ) -> tuple[Iterable['SoquetT'], Iterable['SoquetT']]: + (ctrl,) = ctrl_soqs + in_soqs |= {ctrl_reg_name: ctrl} + + out_soqs = bb.add_d(ctrl_bloq, **in_soqs) + + ctrl = out_soqs.pop(ctrl_reg_name) + return [ctrl], out_soqs.values() + + else: + # the difficult case: must combine the two controls into one + ctrl_1_bloq_and_reg_name = get_ctrl_bloq_and_ctrl_reg_name(1) + assert ( + ctrl_1_bloq_and_reg_name is not None + ), "invalid usage: controlled-by-1 variant must be provided" + ctrl_1_bloq, ctrl_reg_name = ctrl_1_bloq_and_reg_name + + ctrl_bloq = _MultiControlledFromSinglyControlled( + cvs=(ctrl_bit, current_ctrl_bit), ctrl_bloq=ctrl_1_bloq, ctrl_reg_name=ctrl_reg_name + ) + + def _adder( + bb: 'BloqBuilder', ctrl_soqs: Sequence['SoquetT'], in_soqs: dict[str, 'SoquetT'] + ) -> tuple[Iterable['SoquetT'], Iterable['SoquetT']]: + # extract the two control bits + (ctrl0,) = ctrl_soqs + ctrl1 = in_soqs.pop(ctrl_reg_name) + + ctrl0 = cast(Soquet, ctrl0) + ctrl1 = cast(Soquet, ctrl1) + + # add the singly controlled bloq + in_soqs |= {ctrl_reg_name: np.array([ctrl0, ctrl1])} + ctrls, *out_soqs = bb.add_t(ctrl_bloq, **in_soqs) + assert isinstance(ctrls, np.ndarray) + ctrl0, ctrl1 = ctrls + + return [ctrl0], [ctrl1, *out_soqs] + + def _unwrap(b): + if isinstance(b, AutoPartition): + return _unwrap(b.bloq) + return b + + return _unwrap(ctrl_bloq), _adder + + +def get_ctrl_system_1bit_cv( + bloq: 'Bloq', + ctrl_spec: 'CtrlSpec', + *, + current_ctrl_bit: Optional['ControlBit'], + get_ctrl_bloq_and_ctrl_reg_name: Callable[['ControlBit'], tuple['Bloq', str]], +) -> tuple['Bloq', 'AddControlledT']: + """Build the control system for a bloq with specialized single-qubit controlled variants. + + Uses the provided specialized implementation when a singly-controlled variant of the bloq is + requested. When controlled by multiple qubits, the controls are reduced to a single qubit + and the singly-controlled bloq is used. + + The user must provide two specializations for the bloq: controlled by `1` and by `0`. + + When only one specialization (controlled by `1`) is known, use + :meth:`get_ctrl_system_1bit_cv_from_bloqs` instead. + + Args: + bloq: The current bloq. + ctrl_spec: The control specification + current_ctrl_bit: The control bit of the current bloq, one of `0, 1, None`. + get_ctrl_bloq_and_ctrl_reg_name: A callable that accepts a control bit (`0` or `1`), + and returns the controlled variant of this bloq and the name of the control register. + """ + return _get_ctrl_system_1bit_cv( + bloq, + ctrl_spec, + current_ctrl_bit=current_ctrl_bit, + get_ctrl_bloq_and_ctrl_reg_name=get_ctrl_bloq_and_ctrl_reg_name, + ) + + +def get_ctrl_system_1bit_cv_from_bloqs( + bloq: 'Bloq', + ctrl_spec: 'CtrlSpec', + *, + current_ctrl_bit: Optional['ControlBit'], + bloq_with_ctrl: 'Bloq', + ctrl_reg_name: 'str', +) -> tuple['Bloq', 'AddControlledT']: + """Helper to construct the control system given a singly-controlled variant of a bloq. + + Uses the provided specialized implementation when a singly-controlled (by `1`) variant of + the bloq is requested. When controlled by multiple qubits, the controls are reduced to a + single qubit and the singly-controlled bloq is used. + + When specializations for both cases - controlled by `1` and by `0` - are known, use + :meth:`get_ctrl_system_1bit_cv` instead. + + Args: + bloq: The current bloq. + ctrl_spec: The control specification + current_ctrl_bit: The control bit of the current bloq, one of `0, 1, None`. + bloq_with_ctrl: The variant of this bloq controlled by a single qubit in the `1` basis state. + ctrl_reg_name: The name of the control register for the controlled bloq variant(s). + """ + + def get_ctrl_bloq_and_ctrl_reg_name(cv: 'ControlBit') -> Optional[tuple['Bloq', str]]: + if cv == 1: + return bloq_with_ctrl, ctrl_reg_name + else: + return None + + return _get_ctrl_system_1bit_cv( + bloq, + ctrl_spec, + current_ctrl_bit=current_ctrl_bit, + get_ctrl_bloq_and_ctrl_reg_name=get_ctrl_bloq_and_ctrl_reg_name, + ) + + +class SpecializeOnCtrlBit(enum.Flag): + """Control-specs to propagate to the subbloq. + + See `AdjointWithSpecializedCtrl` for usage. + + Currently only allows pushing a single-qubit-control. + """ + + NONE = enum.auto() + ZERO = enum.auto() + ONE = enum.auto() + BOTH = ZERO | ONE + + +@attrs.frozen() +class AdjointWithSpecializedCtrl(Adjoint): + """Adjoint of a bloq with a specialized control implementation. + + If the subbloq has a specialized control implementation, then calling + `Adjoint(subbloq).controlled()` propagates the controls to the subbloq. + This only propagates single-qubit `CtrlSpec`s, all others use the default: + reduced to single-qubit control using the `ControlledViaAnd` bloq. + + By default in Qualtran, `Controlled(bloq).adjoint()` returns `Controlled(bloq.adjoint())`. + But `Adjoint(bloq).controlled()` does not propagate the controls, therefore returns + `Controlled(Adjoint(bloq))`. + This bloq helps override that behaviour for single-qubit controlled versions. + + For example, if a bloq has a specialized implementation for the controlled-by-1 case: + + ```py + class BloqWithSpecializedCtrl(Bloq): + ... + + def adjoint(self): + return AdjointWithSpecializedCtrl(self, SpecializeOnCtrlBit.ONE) + ``` + + See `get_ctrl_system_1bit_cv` on one way to provide specialized controlled implementations + for bloqs. If a bloq uses the above and does not have a trivial `adjoint` implementation, + it is recommended to override the `adjoint` method as show above. + + Caution: + Use this bloq _only_ when a specialized control implementation is guaranteed, + i.e. `subbloq.controlled()` should not return `Controlled(...)`. + Otherwise, it could lead to an infinite recursion. + + Args: + subbloq: The bloq to wrap. + specialize_on_ctrl: Values of the control bit to propagate the control into the subbloq. + Can be `SpecializeOnCtrlBit.ONE` for `1` only, `SpecializeOnCtrlBit.ZERO` for `0` only, + or `SpecializeOnCtrlBit.BOTH` for both `0` and `1`. + """ + + specialize_on_ctrl: SpecializeOnCtrlBit = SpecializeOnCtrlBit.NONE + + def _specialize_control(self, ctrl_spec: 'CtrlSpec') -> bool: + """if True, push the control to the subbloq""" + if ctrl_spec.num_qubits != 1: + return False + + cv = ctrl_spec.get_single_ctrl_bit() + cv_flag = SpecializeOnCtrlBit.ONE if cv == 1 else SpecializeOnCtrlBit.ZERO + return cv_flag in self.specialize_on_ctrl + + def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> tuple['Bloq', 'AddControlledT']: + from qualtran._infra.controlled import _get_nice_ctrl_reg_names + + if not self._specialize_control(ctrl_spec): + # no specialized controlled version available, fallback to default + return super().get_ctrl_system(ctrl_spec) + + # get the builder for the controlled version of subbloq + ctrl_subbloq, ctrl_subbloq_adder = self.subbloq.get_ctrl_system(ctrl_spec) + ctrl_bloq = attrs.evolve(self, subbloq=ctrl_subbloq) + (ctrl_reg_name,) = _get_nice_ctrl_reg_names([reg.name for reg in self.subbloq.signature], 1) + + # build a composite bloq using the control-adder + def _get_adj_cbloq() -> 'CompositeBloq': + bb, initial_soqs = BloqBuilder.from_signature( + self.subbloq.signature, add_registers_allowed=True + ) + ctrl = bb.add_register(ctrl_reg_name, 1) + bb.add_register_allowed = False + + (ctrl,), out_soqs_t = ctrl_subbloq_adder(bb, [ctrl], initial_soqs) + + out_soqs = dict(zip([reg.name for reg in self.subbloq.signature.rights()], out_soqs_t)) + out_soqs |= {ctrl_reg_name: ctrl} + + cbloq = bb.finalize(**out_soqs) + return cbloq.adjoint() + + adj_cbloq = _get_adj_cbloq() + + def _adder( + bb: 'BloqBuilder', ctrl_soqs: Sequence['SoquetT'], in_soqs: dict[str, 'SoquetT'] + ) -> tuple[Iterable['SoquetT'], Iterable['SoquetT']]: + (ctrl,) = ctrl_soqs + in_soqs |= {ctrl_reg_name: ctrl} + soqs = bb.add_from(adj_cbloq, **in_soqs) + + # locate the correct control soquet + soqs = list(soqs) + ctrl_soq = None + for soq, reg in zip(soqs, adj_cbloq.signature.rights()): + if reg.name == ctrl_reg_name: + ctrl_soq = soq + soqs.remove(soq) + break + assert ctrl_soq is not None, "ctrl_soq must be present in output soqs" + + return [ctrl_soq], soqs + + return ctrl_bloq, _adder diff --git a/qualtran/bloqs/mcmt/specialized_ctrl_test.py b/qualtran/bloqs/mcmt/specialized_ctrl_test.py new file mode 100644 index 000000000..c42e95da7 --- /dev/null +++ b/qualtran/bloqs/mcmt/specialized_ctrl_test.py @@ -0,0 +1,242 @@ +# 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 itertools +from typing import Optional, Sequence, Tuple +from unittest.mock import ANY + +import attrs +import pytest + +import qualtran.testing as qlt_testing +from qualtran import ( + AddControlledT, + Bloq, + BloqBuilder, + CtrlSpec, + QAny, + QBit, + Register, + Signature, + SoquetT, +) +from qualtran.bloqs.mcmt import And +from qualtran.bloqs.mcmt.specialized_ctrl import ( + AdjointWithSpecializedCtrl, + get_ctrl_system_1bit_cv, + get_ctrl_system_1bit_cv_from_bloqs, + SpecializeOnCtrlBit, +) +from qualtran.resource_counting import CostKey, GateCounts, get_cost_value, QECGatesCost + + +def _keep_and(b): + # TODO remove this after https://github.com/quantumlib/Qualtran/issues/1346 is resolved. + return isinstance(b, And) + + +@attrs.frozen +class AtomWithSpecializedControl(Bloq): + cv: Optional[int] = None + ctrl_reg_name: str = 'ctrl' + target_reg_name: str = 'q' + + @property + def signature(self) -> 'Signature': + n_ctrl = 1 if self.cv is not None else 0 + reg_name_map = {self.ctrl_reg_name: n_ctrl, self.target_reg_name: 2} + return Signature.build(**reg_name_map) + + def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> Tuple['Bloq', 'AddControlledT']: + return get_ctrl_system_1bit_cv( + self, + ctrl_spec, + current_ctrl_bit=self.cv, + get_ctrl_bloq_and_ctrl_reg_name=lambda cv: ( + attrs.evolve(self, cv=cv), + self.ctrl_reg_name, + ), + ) + + @staticmethod + def cost_expr_for_cv(cv: Optional[int]): + import sympy + + c_unctrl = sympy.Symbol("_c_target_") + c_ctrl = sympy.Symbol("_c_ctrl_") + + if cv is None: + return c_unctrl + return c_unctrl + c_ctrl + + def my_static_costs(self, cost_key: 'CostKey'): + if cost_key == QECGatesCost(): + r = self.cost_expr_for_cv(self.cv) + return GateCounts(rotation=r) + + return NotImplemented + + def adjoint(self) -> 'AdjointWithSpecializedCtrl': + return AdjointWithSpecializedCtrl(self, specialize_on_ctrl=SpecializeOnCtrlBit.BOTH) + + +def ON(n: int = 1) -> CtrlSpec: + return CtrlSpec(cvs=[1] * n) + + +def OFF(n: int = 1) -> CtrlSpec: + return CtrlSpec(cvs=[0] * n) + + +@pytest.mark.parametrize( + 'ctrl_specs', + [ + [ON()], + [OFF()], + [OFF(), OFF()], + [OFF(4)], + [OFF(2), OFF(2)], + [ON(), OFF(5)], + [ON(), ON(), ON()], + [OFF(4), ON(3), OFF(5)], + ], +) +@pytest.mark.parametrize('ctrl_reg_name', ['ctrl', 'control']) +def test_custom_controlled(ctrl_specs: Sequence[CtrlSpec], ctrl_reg_name: str): + bloq: Bloq = AtomWithSpecializedControl(ctrl_reg_name=ctrl_reg_name) + for ctrl_spec in ctrl_specs: + bloq = bloq.controlled(ctrl_spec) + n_ctrls = sum(ctrl_spec.num_qubits for ctrl_spec in ctrl_specs) + + gc = get_cost_value(bloq, QECGatesCost()) + assert gc == GateCounts( + and_bloq=n_ctrls - 1, + rotation=AtomWithSpecializedControl.cost_expr_for_cv(1), + clifford=ANY, + measurement=ANY, + ) + + +@attrs.frozen +class TestAtom(Bloq): + tag: str + + @property + def signature(self) -> 'Signature': + return Signature.build(q=2) + + def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> Tuple['Bloq', 'AddControlledT']: + return get_ctrl_system_1bit_cv_from_bloqs( + self, + ctrl_spec, + current_ctrl_bit=None, + bloq_with_ctrl=CTestAtom(self.tag), + ctrl_reg_name='ctrl', + ) + + def adjoint(self) -> 'AdjointWithSpecializedCtrl': + return AdjointWithSpecializedCtrl(self, specialize_on_ctrl=SpecializeOnCtrlBit.ONE) + + +@attrs.frozen +class CTestAtom(Bloq): + tag: str + + @property + def signature(self) -> 'Signature': + return Signature.build(ctrl=1, q=2) + + def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> Tuple['Bloq', 'AddControlledT']: + return get_ctrl_system_1bit_cv_from_bloqs( + self, ctrl_spec, current_ctrl_bit=1, bloq_with_ctrl=self, ctrl_reg_name='ctrl' + ) + + def adjoint(self) -> 'AdjointWithSpecializedCtrl': + return AdjointWithSpecializedCtrl(self, specialize_on_ctrl=SpecializeOnCtrlBit.ONE) + + +def test_bloq_with_controlled_bloq(): + assert TestAtom('g').controlled() == CTestAtom('g') + + ctrl_bloq = CTestAtom('g').controlled() + _, sigma = ctrl_bloq.call_graph(keep=_keep_and) + assert sigma == {And(): 1, CTestAtom('g'): 1, And().adjoint(): 1} + + ctrl_bloq = CTestAtom('n').controlled(CtrlSpec(cvs=0)) + _, sigma = ctrl_bloq.call_graph(keep=_keep_and) + assert sigma == {And(0, 1): 1, CTestAtom('n'): 1, And(0, 1).adjoint(): 1} + + ctrl_bloq = TestAtom('nn').controlled(CtrlSpec(cvs=[0, 0])) + _, sigma = ctrl_bloq.call_graph(keep=_keep_and) + assert sigma == {And(0, 0): 1, CTestAtom('nn'): 1, And(0, 0).adjoint(): 1} + + +def test_ctrl_adjoint(): + assert TestAtom('a').adjoint().controlled() == CTestAtom('a').adjoint() + + _, sigma = ( + TestAtom('g') + .adjoint() + .controlled(ctrl_spec=CtrlSpec(cvs=[1, 1])) + .call_graph(keep=_keep_and) + ) + assert sigma == {And(): 1, And().adjoint(): 1, CTestAtom('g').adjoint(): 1} + + _, sigma = CTestAtom('c').adjoint().controlled().call_graph(keep=_keep_and) + assert sigma == {And(): 1, And().adjoint(): 1, CTestAtom('c').adjoint(): 1} + + for cv in [0, 1]: + assert ( + AtomWithSpecializedControl().adjoint().controlled(ctrl_spec=CtrlSpec(cvs=cv)) + == AtomWithSpecializedControl(cv=cv).adjoint() + ) + + +@attrs.frozen +class TestBloqWithDecompose(Bloq): + ctrl_reg_name: str + target_reg_name: str + + @property + def signature(self) -> 'Signature': + return Signature( + [Register(self.ctrl_reg_name, QBit()), Register(self.target_reg_name, QAny(2))] + ) + + def build_composite_bloq(self, bb: 'BloqBuilder', **soqs: 'SoquetT') -> dict[str, 'SoquetT']: + for _ in range(2): + soqs = bb.add_d( + AtomWithSpecializedControl( + cv=1, ctrl_reg_name=self.ctrl_reg_name, target_reg_name=self.target_reg_name + ), + **soqs, + ) + return soqs + + +@pytest.mark.parametrize( + ('ctrl_reg_name', 'target_reg_name'), + [ + (ctrl, targ) + for (ctrl, targ) in itertools.product(['ctrl', 'control', 'a', 'b'], repeat=2) + if ctrl != targ + ], +) +def test_get_ctrl_system(ctrl_reg_name: str, target_reg_name: str): + bloq = TestBloqWithDecompose(ctrl_reg_name, target_reg_name).controlled() + _ = bloq.decompose_bloq().flatten() + + +@pytest.mark.notebook +def test_notebook(): + qlt_testing.execute_notebook('specialized_ctrl') diff --git a/qualtran/bloqs/mean_estimation/mean_estimation_operator.py b/qualtran/bloqs/mean_estimation/mean_estimation_operator.py index b145b5cba..1e5b5d7db 100644 --- a/qualtran/bloqs/mean_estimation/mean_estimation_operator.py +++ b/qualtran/bloqs/mean_estimation/mean_estimation_operator.py @@ -13,20 +13,20 @@ # limitations under the License. from functools import cached_property -from typing import Iterator, Optional, Tuple +from typing import Iterator, Tuple, TYPE_CHECKING import attrs -import cirq from numpy.typing import NDArray -from qualtran import CtrlSpec, Register, Signature -from qualtran._infra.gate_with_registers import GateWithRegisters, total_bits -from qualtran._infra.single_qubit_controlled import SpecializedSingleQubitControlledExtension +from qualtran import GateWithRegisters, Register, Signature from qualtran.bloqs.mean_estimation.complex_phase_oracle import ComplexPhaseOracle from qualtran.bloqs.multiplexers.select_base import SelectOracle from qualtran.bloqs.reflections.reflection_using_prepare import ReflectionUsingPrepare from qualtran.bloqs.state_preparation.prepare_base import PrepareOracle +if TYPE_CHECKING: + import cirq + @attrs.frozen class CodeForRandomVariable: @@ -65,7 +65,7 @@ def __attrs_post_init__(self): @attrs.frozen -class MeanEstimationOperator(GateWithRegisters, SpecializedSingleQubitControlledExtension): # type: ignore[misc] +class MeanEstimationOperator(GateWithRegisters): r"""Mean estimation operator $U=REFL_{p} ROT_{y}$ as per Sec 3.1 of arxiv.org:2208.07544. The MeanEstimationOperator (aka KO Operator) expects `CodeForRandomVariable` to specify the @@ -84,51 +84,39 @@ class MeanEstimationOperator(GateWithRegisters, SpecializedSingleQubitControlled """ code: CodeForRandomVariable - control_val: Optional[int] = None arctan_bitsize: int = 32 @cached_property def reflect(self) -> ReflectionUsingPrepare: - return ReflectionUsingPrepare( - self.code.synthesizer, global_phase=-1, control_val=self.control_val - ) + return ReflectionUsingPrepare(self.code.synthesizer, global_phase=-1) @cached_property def select(self) -> ComplexPhaseOracle: return ComplexPhaseOracle(self.code.encoder, self.arctan_bitsize) - @cached_property - def control_registers(self) -> Tuple[Register, ...]: - return self.code.encoder.control_registers - @cached_property def selection_registers(self) -> Tuple[Register, ...]: return self.code.encoder.selection_registers @cached_property def signature(self) -> Signature: - return Signature([*self.control_registers, *self.selection_registers]) + return Signature([*self.selection_registers]) def decompose_from_registers( self, *, - context: cirq.DecompositionContext, - **quregs: NDArray[cirq.Qid], # type:ignore[type-var] - ) -> Iterator[cirq.OP_TREE]: + context: 'cirq.DecompositionContext', + **quregs: NDArray['cirq.Qid'], # type:ignore[type-var] + ) -> Iterator['cirq.OP_TREE']: select_reg = {reg.name: quregs[reg.name] for reg in self.select.signature} reflect_reg = {reg.name: quregs[reg.name] for reg in self.reflect.signature} yield self.select.on_registers(**select_reg) yield self.reflect.on_registers(**reflect_reg) - def get_single_qubit_controlled_bloq(self, control_val: int) -> 'MeanEstimationOperator': - c_encoder = self.code.encoder.controlled(ctrl_spec=CtrlSpec(cvs=control_val)) - assert isinstance(c_encoder, SelectOracle) - c_code = attrs.evolve(self.code, encoder=c_encoder) - return attrs.evolve(self, code=c_code, control_val=control_val) - - def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo: - wire_symbols = [] - if self.control_val is not None: - wire_symbols.append("@" if self.control_val == 1 else "(0)") - wire_symbols += ['U_ko'] * (total_bits(self.signature) - total_bits(self.control_registers)) + def _circuit_diagram_info_( + self, args: 'cirq.CircuitDiagramInfoArgs' + ) -> 'cirq.CircuitDiagramInfo': + import cirq + + wire_symbols = ['U_ko'] * self.signature.n_qubits() return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) diff --git a/qualtran/bloqs/mean_estimation/mean_estimation_operator_test.py b/qualtran/bloqs/mean_estimation/mean_estimation_operator_test.py index cc62f7f4a..bbc6d9ff3 100644 --- a/qualtran/bloqs/mean_estimation/mean_estimation_operator_test.py +++ b/qualtran/bloqs/mean_estimation/mean_estimation_operator_test.py @@ -20,9 +20,8 @@ import pytest from attrs import frozen -from qualtran import BQUInt, QAny, QBit, QUInt, Register +from qualtran import BQUInt, QAny, QUInt, Register from qualtran._infra.gate_with_registers import get_named_qubits, total_bits -from qualtran._infra.single_qubit_controlled import SpecializedSingleQubitControlledExtension from qualtran.bloqs.mean_estimation.mean_estimation_operator import ( CodeForRandomVariable, MeanEstimationOperator, @@ -52,7 +51,7 @@ def decompose_from_registers( # type:ignore[override] @frozen -class BernoulliEncoder(SelectOracle, SpecializedSingleQubitControlledExtension): # type: ignore[misc] +class BernoulliEncoder(SelectOracle): r"""Encodes Bernoulli random variable y0/y1 as $Enc|ii..i>|0> = |ii..i>|y_{i}>$ where i=0/1.""" p: float @@ -63,7 +62,7 @@ class BernoulliEncoder(SelectOracle, SpecializedSingleQubitControlledExtension): @cached_property def control_registers(self) -> Tuple[Register, ...]: - return () if self.control_val is None else (Register('control', QBit()),) + return () @cached_property def selection_registers(self) -> Tuple[Register, ...]: diff --git a/qualtran/bloqs/mod_arithmetic/__init__.py b/qualtran/bloqs/mod_arithmetic/__init__.py index deba42bb0..774aa44d6 100644 --- a/qualtran/bloqs/mod_arithmetic/__init__.py +++ b/qualtran/bloqs/mod_arithmetic/__init__.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from ._shims import ModInv, ModMul from .mod_addition import CModAdd, CModAddK, CtrlScaleModAdd, ModAdd, ModAddK -from .mod_multiplication import CModMulK, ModDbl +from .mod_division import KaliskiModInverse +from .mod_multiplication import CModMulK, DirtyOutOfPlaceMontgomeryModMul, ModDbl from .mod_subtraction import CModNeg, CModSub, ModNeg, ModSub diff --git a/qualtran/bloqs/mod_arithmetic/_shims.py b/qualtran/bloqs/mod_arithmetic/_shims.py deleted file mode 100644 index c6e630235..000000000 --- a/qualtran/bloqs/mod_arithmetic/_shims.py +++ /dev/null @@ -1,150 +0,0 @@ -# Copyright 2023 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. -"""This module has a selection of minimally-implemented modular arithmetic primitives. - -These bloqs serve as the callees in the call graphs of the algorithms found -in `qualtran.bloq.factoring`. They are place-holders, so we don't have undefined symbols -and can still merge the high-level algorithms. These shims will be fleshed out -and moved to their final organizational location soon (written: 2024-05-06). -""" - - -from collections import defaultdict -from functools import cached_property -from typing import Dict, Optional, Tuple, TYPE_CHECKING - -from attrs import frozen - -from qualtran import Bloq, QUInt, Register, Signature -from qualtran.bloqs.arithmetic import Add, AddK, Negate, Subtract -from qualtran.bloqs.arithmetic._shims import CHalf, Lt, MultiCToffoli -from qualtran.bloqs.basic_gates import CNOT, CSwap, Swap, Toffoli -from qualtran.bloqs.mod_arithmetic.mod_multiplication import ModDbl -from qualtran.drawing import Text, TextBox, WireSymbol -from qualtran.symbolics import ceil, log2 - -if TYPE_CHECKING: - from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator - - -@frozen -class _ModInvInner(Bloq): - n: int - mod: int - - @cached_property - def signature(self) -> 'Signature': - return Signature([Register('x', QUInt(self.n)), Register('out', QUInt(self.n))]) - - def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': - # This listing is based off of Haner 2023, fig 15. The order of operations - # matches the order in the figure - listing = [ - (MultiCToffoli(self.n + 1), 1), - (CNOT(), 1), - (Toffoli(), 1), - (MultiCToffoli(n=3), 1), - (CNOT(), 2), - (Lt(self.n), 1), - (CSwap(self.n), 2), - (Subtract(QUInt(self.n)), 1), - (Add(QUInt(self.n)), 1), - (CNOT(), 1), - (ModDbl(QUInt(self.n), self.mod), 1), - (CHalf(self.n), 1), - (CSwap(self.n), 2), - (CNOT(), 1), - ] - # Since the listing is time-ordered and the call graph protocol expects - # unique bloq keys, we group counts by bloqs. - summer: Dict[Bloq, int] = defaultdict(lambda: 0) - for bloq, n in listing: - summer[bloq] += n - return summer - - def wire_symbol( - self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() - ) -> 'WireSymbol': - if reg is None: - return Text("") - if reg.name == 'x': - return TextBox('x') - elif reg.name == 'out': - return TextBox('$x^{-1}$') - raise ValueError(f'Unrecognized register name {reg.name}') - - -@frozen -class ModInv(Bloq): - n: int - mod: int - - @cached_property - def signature(self) -> 'Signature': - return Signature([Register('x', QUInt(self.n)), Register('out', QUInt(self.n))]) - - def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': - # Roetteler - # return {(Toffoli(), 32 * self.n**2 * log2(self.n))} - return { - _ModInvInner(n=self.n, mod=self.mod): 2 * self.n, - Negate(QUInt(self.n)): 1, - AddK(self.n, k=self.mod): 1, - Swap(self.n): 1, - } - - def wire_symbol( - self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() - ) -> 'WireSymbol': - if reg is None: - return Text("") - if reg.name == 'x': - return TextBox('x') - elif reg.name == 'out': - return TextBox('$x^{-1}$') - raise ValueError(f'Unrecognized register name {reg.name}') - - -@frozen -class ModMul(Bloq): - n: int - mod: int - - @cached_property - def signature(self) -> 'Signature': - return Signature( - [ - Register('x', QUInt(self.n)), - Register('y', QUInt(self.n)), - Register('out', QUInt(self.n)), - ] - ) - - def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': - # Roetteler montgomery - return {Toffoli(): ceil(16 * self.n**2 * log2(self.n) - 26.3 * self.n**2)} - - def wire_symbol( - self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() - ) -> 'WireSymbol': - if reg is None: - return Text("") - if reg.name in ['x', 'y']: - return TextBox(reg.name) - elif reg.name == 'out': - return TextBox('x*y') - raise ValueError(f'Unrecognized register name {reg.name}') - - def __str__(self): - return self.__class__.__name__ diff --git a/qualtran/bloqs/mod_arithmetic/mod_addition.ipynb b/qualtran/bloqs/mod_arithmetic/mod_addition.ipynb index dab6f57c5..b081513d2 100644 --- a/qualtran/bloqs/mod_arithmetic/mod_addition.ipynb +++ b/qualtran/bloqs/mod_arithmetic/mod_addition.ipynb @@ -394,6 +394,246 @@ "show_call_graph(cmodadd_example_g)\n", "show_counts_sigma(cmodadd_example_sigma)" ] + }, + { + "cell_type": "markdown", + "id": "0523961e", + "metadata": { + "cq.autogen": "CModAddK.bloq_doc.md" + }, + "source": [ + "## `CModAddK`\n", + "Perform x += k mod m for constant k, m and quantum x.\n", + "\n", + "#### Parameters\n", + " - `k`: The integer to add to `x`.\n", + " - `mod`: The modulus for the addition.\n", + " - `bitsize`: The bitsize of the `x` register. \n", + "\n", + "#### Registers\n", + " - `ctrl`: The control bit\n", + " - `x`: The register to perform the in-place modular addition. \n", + "\n", + "#### References\n", + " - [How to factor 2048 bit RSA integers in 8 hours using 20 million noisy qubits](https://arxiv.org/abs/1905.09749). Gidney and Ekerå 2019. The reference implementation in section 2.2 uses CModAddK, but the circuit that it points to is just ModAdd (not ModAddK). This ModAdd is less efficient than the circuit later introduced in the Litinski paper so we choose to use that since it is more efficient and already implemented in Qualtran.\n", + " - [How to compute a 256-bit elliptic curve private key with only 50 million Toffoli gates](https://arxiv.org/abs/2306.08585). Litinski et al. 2023. This CModAdd circuit uses 2 fewer additions than the implementation referenced in the paper above. Because of this we choose to use this CModAdd bloq instead.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "341211fa", + "metadata": { + "cq.autogen": "CModAddK.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.mod_arithmetic import CModAddK" + ] + }, + { + "cell_type": "markdown", + "id": "6b00aef7", + "metadata": { + "cq.autogen": "CModAddK.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b8877f17", + "metadata": { + "cq.autogen": "CModAddK.cmod_add_k" + }, + "outputs": [], + "source": [ + "n, m, k = sympy.symbols('n m k')\n", + "cmod_add_k = CModAddK(bitsize=n, mod=m, k=k)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "87fe8b8f", + "metadata": { + "cq.autogen": "CModAddK.cmod_add_k_small" + }, + "outputs": [], + "source": [ + "cmod_add_k_small = CModAddK(bitsize=4, mod=7, k=1)" + ] + }, + { + "cell_type": "markdown", + "id": "f44f0268", + "metadata": { + "cq.autogen": "CModAddK.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b7a02d6e", + "metadata": { + "cq.autogen": "CModAddK.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([cmod_add_k, cmod_add_k_small],\n", + " ['`cmod_add_k`', '`cmod_add_k_small`'])" + ] + }, + { + "cell_type": "markdown", + "id": "48b87ff2", + "metadata": { + "cq.autogen": "CModAddK.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f6e6770b", + "metadata": { + "cq.autogen": "CModAddK.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "cmod_add_k_g, cmod_add_k_sigma = cmod_add_k.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(cmod_add_k_g)\n", + "show_counts_sigma(cmod_add_k_sigma)" + ] + }, + { + "cell_type": "markdown", + "id": "21f93349", + "metadata": { + "cq.autogen": "CtrlScaleModAdd.bloq_doc.md" + }, + "source": [ + "## `CtrlScaleModAdd`\n", + "Perform y += x*k mod m for constant k, m and quantum x, y.\n", + "\n", + "#### Parameters\n", + " - `k`: The constant integer to scale `x` before adding into `y`.\n", + " - `mod`: The modulus of the addition\n", + " - `bitsize`: The size of the two registers. \n", + "\n", + "#### Registers\n", + " - `ctrl`: The control bit\n", + " - `x`: The 'source' quantum register containing the integer to be scaled and added to `y`.\n", + " - `y`: The 'destination' quantum register to which the addition will apply. \n", + "\n", + "#### References\n", + " - [How to factor 2048 bit RSA integers in 8 hours using 20 million noisy qubits](https://arxiv.org/abs/1905.09749). Construction based on description in section 2.2 paragraph 4. We add n And/And† bloqs because the bloq is controlled, but the construction also involves modular addition controlled on the qubits comprising register x.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4ac170e5", + "metadata": { + "cq.autogen": "CtrlScaleModAdd.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.mod_arithmetic import CtrlScaleModAdd" + ] + }, + { + "cell_type": "markdown", + "id": "58ee7de2", + "metadata": { + "cq.autogen": "CtrlScaleModAdd.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "73c2c6f7", + "metadata": { + "cq.autogen": "CtrlScaleModAdd.ctrl_scale_mod_add" + }, + "outputs": [], + "source": [ + "n, m, k = sympy.symbols('n m k')\n", + "ctrl_scale_mod_add = CtrlScaleModAdd(bitsize=n, mod=m, k=k)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e822d5eb", + "metadata": { + "cq.autogen": "CtrlScaleModAdd.ctrl_scale_mod_add_small" + }, + "outputs": [], + "source": [ + "ctrl_scale_mod_add_small = CtrlScaleModAdd(bitsize=4, mod=7, k=1)" + ] + }, + { + "cell_type": "markdown", + "id": "fe4c8957", + "metadata": { + "cq.autogen": "CtrlScaleModAdd.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e4dc8923", + "metadata": { + "cq.autogen": "CtrlScaleModAdd.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([ctrl_scale_mod_add, ctrl_scale_mod_add_small],\n", + " ['`ctrl_scale_mod_add`', '`ctrl_scale_mod_add_small`'])" + ] + }, + { + "cell_type": "markdown", + "id": "97d6888d", + "metadata": { + "cq.autogen": "CtrlScaleModAdd.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bd734f6b", + "metadata": { + "cq.autogen": "CtrlScaleModAdd.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "ctrl_scale_mod_add_g, ctrl_scale_mod_add_sigma = ctrl_scale_mod_add.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(ctrl_scale_mod_add_g)\n", + "show_counts_sigma(ctrl_scale_mod_add_sigma)" + ] } ], "metadata": { diff --git a/qualtran/bloqs/mod_arithmetic/mod_addition.py b/qualtran/bloqs/mod_arithmetic/mod_addition.py index 31f66a847..cb7ab6396 100644 --- a/qualtran/bloqs/mod_arithmetic/mod_addition.py +++ b/qualtran/bloqs/mod_arithmetic/mod_addition.py @@ -23,6 +23,7 @@ Bloq, bloq_example, BloqDocSpec, + DecomposeTypeError, GateWithRegisters, QBit, QMontgomeryUInt, @@ -35,8 +36,9 @@ from qualtran.bloqs.arithmetic.addition import Add, AddK from qualtran.bloqs.arithmetic.comparison import CLinearDepthGreaterThan, LinearDepthGreaterThan from qualtran.bloqs.arithmetic.controlled_addition import CAdd -from qualtran.bloqs.basic_gates import XGate +from qualtran.bloqs.basic_gates import IntEffect, IntState, XGate from qualtran.bloqs.bookkeeping import Cast +from qualtran.bloqs.mcmt.and_bloq import And from qualtran.drawing import Circle, Text, TextBox, WireSymbol from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator from qualtran.resource_counting.generalizers import ignore_split_join @@ -89,7 +91,7 @@ def on_classical_vals( def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet, y: Soquet) -> Dict[str, 'SoquetT']: if is_symbolic(self.bitsize): - raise NotImplementedError(f'symbolic decomposition is not supported for {self}') + raise DecomposeTypeError(f"Cannot decompose {self} with symbolic `bitsize`.") # Allocate ancilla bits for use in addition. junk_bit = bb.allocate(n=1) sign = bb.allocate(n=1) @@ -115,7 +117,7 @@ def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet, y: Soquet) -> Dict[ x = bb.join(x_split[1:], dtype=QMontgomeryUInt(bitsize=self.bitsize)) # Add constant -p to the y register. - y = bb.add(AddK(bitsize=self.bitsize + 1, k=-1 * self.mod, signed=True, cvs=()), x=y) + y = bb.add(AddK(QMontgomeryUInt(self.bitsize + 1), k=-1 * self.mod), x=y) # Controlled addition of classical constant p if the sign of y after the last addition is # negative. @@ -123,11 +125,9 @@ def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet, y: Soquet) -> Dict[ sign = y_split[0] y = bb.join(y_split[1:], dtype=QMontgomeryUInt(bitsize=self.bitsize)) - sign_split = bb.split(sign) - sign_split, y = bb.add( - AddK(bitsize=self.bitsize, k=self.mod, signed=True, cvs=(1,)), x=y, ctrls=sign_split + sign, y = bb.add( + AddK(QMontgomeryUInt(self.bitsize), k=self.mod).controlled(), ctrl=sign, x=y ) - sign = bb.join(sign_split) # Check if y < x; if yes flip the bit of the signed ancilla bit. Then bitflip the sign bit # again before freeing. @@ -146,8 +146,8 @@ def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet, y: Soquet) -> Dict[ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': return { Add(QUInt(self.bitsize + 1)): 1, - AddK(self.bitsize + 1, k=-self.mod): 1, - AddK(self.bitsize, k=self.mod).controlled(): 1, + AddK(QUInt(self.bitsize + 1), k=-self.mod): 1, + AddK(QUInt(self.bitsize), k=self.mod).controlled(): 1, LinearDepthGreaterThan(self.bitsize): 1, XGate(): 1, } @@ -269,6 +269,19 @@ class CModAddK(Bloq): Registers: ctrl: The control bit x: The register to perform the in-place modular addition. + + References: + [How to factor 2048 bit RSA integers in 8 hours using 20 million noisy qubits](https://arxiv.org/abs/1905.09749). + Gidney and Ekerå 2019. + The reference implementation in section 2.2 uses CModAddK, but the circuit that it points + to is just ModAdd (not ModAddK). This ModAdd is less efficient than the circuit later + introduced in the Litinski paper so we choose to use that since it is more efficient and + already implemented in Qualtran. + + [How to compute a 256-bit elliptic curve private key with only 50 million Toffoli gates](https://arxiv.org/abs/2306.08585). + Litinski et al. 2023. + This CModAdd circuit uses 2 fewer additions than the implementation referenced in the paper + above. Because of this we choose to use this CModAdd bloq instead. """ k: Union[int, sympy.Expr] @@ -279,12 +292,53 @@ class CModAddK(Bloq): def signature(self) -> 'Signature': return Signature([Register('ctrl', QBit()), Register('x', QUInt(self.bitsize))]) + def build_composite_bloq( + self, bb: 'BloqBuilder', ctrl: 'Soquet', x: 'Soquet' + ) -> Dict[str, 'SoquetT']: + k = bb.add(IntState(bitsize=self.bitsize, val=self.k)) + ctrl, k, x = bb.add(CModAdd(QUInt(self.bitsize), mod=self.mod), ctrl=ctrl, x=k, y=x) + bb.add(IntEffect(bitsize=self.bitsize, val=self.k), val=k) + return {'ctrl': ctrl, 'x': x} + + def on_classical_vals( + self, ctrl: 'ClassicalValT', x: 'ClassicalValT' + ) -> Dict[str, 'ClassicalValT']: + if ctrl == 0: + return {'ctrl': 0, 'x': x} + + assert ctrl == 1, 'Bad ctrl value.' + x = (x + self.k) % self.mod + return {'ctrl': ctrl, 'x': x} + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': - k = ssa.new_symbol('k') - return {AddK(k=k, bitsize=self.bitsize).controlled(): 5} + return {CModAdd(QUInt(self.bitsize), mod=self.mod): 1} - def short_name(self) -> str: - return f'x += {self.k} % {self.mod}' + def wire_symbol( + self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() + ) -> 'WireSymbol': + if reg is None: + return Text(f"mod {self.mod}") + if reg.name == 'ctrl': + return Circle() + if reg.name == 'x': + return TextBox(f'x += {self.k}') + raise ValueError(f"Unknown register {reg}") + + +@bloq_example(generalizer=ignore_split_join) +def _cmod_add_k() -> CModAddK: + n, m, k = sympy.symbols('n m k') + cmod_add_k = CModAddK(bitsize=n, mod=m, k=k) + return cmod_add_k + + +@bloq_example +def _cmod_add_k_small() -> CModAddK: + cmod_add_k_small = CModAddK(bitsize=4, mod=7, k=1) + return cmod_add_k_small + + +_C_MOD_ADD_K_DOC = BloqDocSpec(bloq_cls=CModAddK, examples=[_cmod_add_k, _cmod_add_k_small]) @frozen @@ -300,6 +354,12 @@ class CtrlScaleModAdd(Bloq): ctrl: The control bit x: The 'source' quantum register containing the integer to be scaled and added to `y`. y: The 'destination' quantum register to which the addition will apply. + + References: + [How to factor 2048 bit RSA integers in 8 hours using 20 million noisy qubits](https://arxiv.org/abs/1905.09749). + Construction based on description in section 2.2 paragraph 4. We add n And/And† bloqs + because the bloq is controlled, but the construction also involves modular addition + controlled on the qubits comprising register x. """ k: Union[int, sympy.Expr] @@ -316,9 +376,38 @@ def signature(self) -> 'Signature': ] ) + def build_composite_bloq( + self, bb: 'BloqBuilder', ctrl: 'Soquet', x: 'Soquet', y: 'Soquet' + ) -> Dict[str, 'SoquetT']: + if is_symbolic(self.bitsize): + raise DecomposeTypeError(f"Cannot decompose {self} with symbolic `bitsize`.") + x_split = bb.split(x) + for i in range(int(self.bitsize)): + and_ctrl = [ctrl, x_split[i]] + and_ctrl, ancilla = bb.add(And(), ctrl=and_ctrl) + ancilla, y = bb.add( + CModAddK( + k=((self.k * 2 ** (self.bitsize - 1 - i)) % self.mod), + bitsize=self.bitsize, + mod=self.mod, + ), + ctrl=ancilla, + x=y, + ) + and_ctrl = bb.add(And().adjoint(), ctrl=and_ctrl, target=ancilla) + ctrl = and_ctrl[0] + x_split[i] = and_ctrl[1] + x = bb.join(x_split, dtype=QUInt(self.bitsize)) + + return {'ctrl': ctrl, 'x': x, 'y': y} + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': k = ssa.new_symbol('k') - return {CModAddK(k=k, bitsize=self.bitsize, mod=self.mod): self.bitsize} + return { + CModAddK(k=k, bitsize=self.bitsize, mod=self.mod): self.bitsize, + And(): self.bitsize, + And().adjoint(): self.bitsize, + } def on_classical_vals( self, ctrl: 'ClassicalValT', x: 'ClassicalValT', y: 'ClassicalValT' @@ -344,6 +433,24 @@ def wire_symbol( raise ValueError(f"Unknown register {reg}") +@bloq_example(generalizer=ignore_split_join) +def _ctrl_scale_mod_add() -> CtrlScaleModAdd: + n, m, k = sympy.symbols('n m k') + ctrl_scale_mod_add = CtrlScaleModAdd(bitsize=n, mod=m, k=k) + return ctrl_scale_mod_add + + +@bloq_example +def _ctrl_scale_mod_add_small() -> CtrlScaleModAdd: + ctrl_scale_mod_add_small = CtrlScaleModAdd(bitsize=4, mod=7, k=1) + return ctrl_scale_mod_add_small + + +_CTRL_SCALE_MOD_ADD_DOC = BloqDocSpec( + bloq_cls=CtrlScaleModAdd, examples=[_ctrl_scale_mod_add, _ctrl_scale_mod_add_small] +) + + @frozen class CModAdd(Bloq): r"""Controlled Modular Addition. @@ -390,6 +497,8 @@ def on_classical_vals( def build_composite_bloq( self, bb: 'BloqBuilder', ctrl, x: Soquet, y: Soquet ) -> Dict[str, 'SoquetT']: + if is_symbolic(self.dtype.bitsize): + raise DecomposeTypeError(f'symbolic decomposition is not supported for {self}') y_arr = bb.split(y) ancilla = bb.allocate(1) x = bb.add(Cast(self.dtype, QUInt(self.dtype.bitsize)), reg=x) @@ -401,12 +510,12 @@ def build_composite_bloq( a=x, b=y, ) - y = bb.add(AddK(self.dtype.bitsize + 1, -self.mod, signed=False), x=y) + y = bb.add(AddK(QUInt(self.dtype.bitsize + 1), -self.mod), x=y) y_arr = bb.split(y) ancilla, y_arr = y_arr[0], y_arr[1:] y = bb.join(y_arr) - (ancilla,), y = bb.add( - AddK(self.dtype.bitsize, self.mod, signed=False, cvs=(1,)), ctrls=(ancilla,), x=y + ancilla, y = bb.add( + AddK(QUInt(self.dtype.bitsize), self.mod).controlled(), ctrl=ancilla, x=y ) ctrl, x, y, ancilla = bb.add( @@ -427,8 +536,8 @@ def build_composite_bloq( def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': return { CAdd(QUInt(self.dtype.bitsize), QUInt(self.dtype.bitsize + 1), cv=self.cv): 1, - AddK(self.dtype.bitsize + 1, -self.mod, signed=False): 1, - AddK(self.dtype.bitsize, self.mod, cvs=(1,), signed=False): 1, + AddK(QUInt(self.dtype.bitsize + 1), -self.mod): 1, + AddK(QUInt(self.dtype.bitsize), self.mod).controlled(): 1, CLinearDepthGreaterThan(QUInt(self.dtype.bitsize), cv=self.cv): 1, XGate(): 1, } diff --git a/qualtran/bloqs/mod_arithmetic/mod_addition_test.py b/qualtran/bloqs/mod_arithmetic/mod_addition_test.py index 948001b38..455670bb5 100644 --- a/qualtran/bloqs/mod_arithmetic/mod_addition_test.py +++ b/qualtran/bloqs/mod_arithmetic/mod_addition_test.py @@ -18,20 +18,55 @@ import pytest import sympy +import qualtran.testing as qlt_testing from qualtran import QMontgomeryUInt, QUInt from qualtran.bloqs.arithmetic import Add from qualtran.bloqs.mod_arithmetic import CModAdd, CModAddK, CtrlScaleModAdd, ModAdd, ModAddK -from qualtran.bloqs.mod_arithmetic.mod_addition import _cmodadd_example +from qualtran.bloqs.mod_arithmetic.mod_addition import ( + _cmod_add_k, + _cmod_add_k_small, + _cmodadd_example, + _ctrl_scale_mod_add, + _ctrl_scale_mod_add_small, + _mod_add, + _mod_add_k, + _mod_add_k_large, + _mod_add_k_small, +) from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.resource_counting import GateCounts, get_cost_value, QECGatesCost from qualtran.resource_counting.generalizers import ignore_alloc_free, ignore_split_join from qualtran.testing import ( + assert_consistent_classical_action, assert_equivalent_bloq_counts, assert_valid_bloq_decomposition, execute_notebook, ) +@pytest.mark.parametrize( + "bloq", + [ + _mod_add, + _mod_add_k, + _mod_add_k_small, + _mod_add_k_large, + _cmod_add_k, + _cmod_add_k_small, + _ctrl_scale_mod_add, + _ctrl_scale_mod_add_small, + _cmodadd_example, + ], +) +def test_examples(bloq_autotester, bloq): + bloq_autotester(bloq) + + +@pytest.mark.notebook +def test_notebook(): + execute_notebook('mod_addition') + + def identity_map(n: int): """Returns a dict of size `2**n` mapping each integer in range [0, 2**n) to itself.""" return {i: i for i in range(2**n)} @@ -60,22 +95,6 @@ def test_add_mod_n_gate_counts(bitsize): assert bloq.t_complexity() == add_constant_mod_n_ref_t_complexity_(bloq) -def test_ctrl_scale_mod_add(): - bloq = CtrlScaleModAdd(k=123, mod=13 * 17, bitsize=8) - - counts = bloq.bloq_counts() - ((bloq, n),) = counts.items() - assert n == 8 - - -def test_ctrl_mod_add_k(): - bloq = CModAddK(k=123, mod=13 * 17, bitsize=8) - - counts = bloq.bloq_counts() - ((bloq, n),) = counts.items() - assert n == 5 - - @pytest.mark.parametrize('bitsize,p', [(1, 1), (2, 3), (5, 8)]) def test_mod_add_valid_decomp(bitsize, p): bloq = ModAdd(bitsize=bitsize, mod=p) @@ -130,6 +149,26 @@ def test_classical_action_cmodadd_fast(control, bitsize): assert b.call_classically(ctrl=c, x=x, y=y) == cb.call_classically(ctrl=c, x=x, y=y) +@pytest.mark.slow +@pytest.mark.parametrize( + ['prime', 'bitsize', 'k'], + [(p, n, k) for p in (13, 17, 23) for n in range(p.bit_length(), 8) for k in range(1, p)], +) +def test_cscalemodadd_classical_action(bitsize, prime, k): + b = CtrlScaleModAdd(bitsize=bitsize, mod=prime, k=k) + qlt_testing.assert_consistent_classical_action(b, ctrl=(0, 1), x=range(prime), y=range(prime)) + + +@pytest.mark.slow +@pytest.mark.parametrize( + ['prime', 'bitsize', 'k'], + [(p, n, k) for p in (13, 17, 23) for n in range(p.bit_length(), 8) for k in range(1, p)], +) +def test_cmodaddk_classical_action(bitsize, prime, k): + b = CModAddK(bitsize=bitsize, mod=prime, k=k) + qlt_testing.assert_consistent_classical_action(b, ctrl=(0, 1), x=range(prime)) + + @pytest.mark.parametrize('control', range(2)) @pytest.mark.parametrize( ['prime', 'bitsize'], @@ -153,15 +192,6 @@ def test_cmodadd_cost(control, dtype): assert cost.total_t_count() == 4 * n_toffolis -def test_cmodadd_example(bloq_autotester): - bloq_autotester(_cmodadd_example) - - -@pytest.mark.notebook -def test_notebook(): - execute_notebook('mod_addition') - - def test_cmod_add_complexity_vs_ref(): n, k = sympy.symbols('n k', integer=True, positive=True) bloq = CModAdd(QUInt(n), mod=k) @@ -172,3 +202,9 @@ def test_cmod_add_complexity_vs_ref(): # Figure/Table 8. Lists n-qubit controlled modular addition as 5n toffoli. # Note: We have an extra toffoli due to how our OutOfPlaceAdder works. assert counts['n_ccz'] == 5 * n + 1 + + +@pytest.mark.parametrize(['prime', 'bitsize'], [(p, bitsize) for p in [5, 7] for bitsize in (5, 6)]) +def test_mod_add_classical_action(bitsize, prime): + b = ModAdd(bitsize, prime) + assert_consistent_classical_action(b, x=range(prime), y=range(prime)) diff --git a/qualtran/bloqs/mod_arithmetic/mod_division.ipynb b/qualtran/bloqs/mod_arithmetic/mod_division.ipynb new file mode 100644 index 000000000..fd34e136d --- /dev/null +++ b/qualtran/bloqs/mod_arithmetic/mod_division.ipynb @@ -0,0 +1,170 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "1c5f2b28", + "metadata": { + "cq.autogen": "title_cell" + }, + "source": [ + "# Modular Divison" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8751aa36", + "metadata": { + "cq.autogen": "top_imports" + }, + "outputs": [], + "source": [ + "from qualtran import Bloq, CompositeBloq, BloqBuilder, Signature, Register\n", + "from qualtran import QBit, QInt, QUInt, QAny\n", + "from qualtran.drawing import show_bloq, show_call_graph, show_counts_sigma\n", + "from typing import *\n", + "import numpy as np\n", + "import sympy\n", + "import cirq" + ] + }, + { + "cell_type": "markdown", + "id": "d680443c", + "metadata": { + "cq.autogen": "KaliskiModInverse.bloq_doc.md" + }, + "source": [ + "## `KaliskiModInverse`\n", + "Compute modular multiplicative inverse -inplace- of numbers in montgomery form.\n", + "\n", + "Applies the transformation\n", + "$$\n", + " \\ket{x} \\ket{0} \\rightarrow \\ket{x^{-1} 2^{2n} \\mod p} \\ket{\\mathrm{garbage}}\n", + "$$\n", + "\n", + "#### Parameters\n", + " - `bitsize`: size of the number.\n", + " - `mod`: The integer modulus.\n", + " - `uncompute`: whether to compute or uncompute. \n", + "\n", + "#### Registers\n", + " - `x`: The register for which we compute the multiplicative inverse.\n", + " - `m`: A 2*bitsize register of intermediate values needed for uncomputation. \n", + "\n", + "#### References\n", + " - [Performance Analysis of a Repetition Cat Code Architecture: Computing 256-bit Elliptic Curve Logarithm in 9 Hours with 126 133 Cat Qubits](https://arxiv.org/abs/2302.06639). Appendix C5.\n", + " - [Improved quantum circuits for elliptic curve discrete logarithms](https://arxiv.org/abs/2001.09580). Fig 7(b)\n", + " - [How to compute a 256-bit elliptic curve private key with only 50 million Toffoli gates](https://arxiv.org/abs/2306.08585). page 8.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f5917d72", + "metadata": { + "cq.autogen": "KaliskiModInverse.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.mod_arithmetic import KaliskiModInverse" + ] + }, + { + "cell_type": "markdown", + "id": "d44329eb", + "metadata": { + "cq.autogen": "KaliskiModInverse.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "31a37cf6", + "metadata": { + "cq.autogen": "KaliskiModInverse.kaliskimodinverse_example" + }, + "outputs": [], + "source": [ + "kaliskimodinverse_example = KaliskiModInverse(32, 10**9 + 7)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "58c697e6", + "metadata": { + "cq.autogen": "KaliskiModInverse.kaliskimodinverse_symbolic" + }, + "outputs": [], + "source": [ + "n, p = sympy.symbols('n p')\n", + "kaliskimodinverse_symbolic = KaliskiModInverse(n, p)" + ] + }, + { + "cell_type": "markdown", + "id": "9bf1e17c", + "metadata": { + "cq.autogen": "KaliskiModInverse.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eca3a706", + "metadata": { + "cq.autogen": "KaliskiModInverse.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([kaliskimodinverse_example, kaliskimodinverse_symbolic],\n", + " ['`kaliskimodinverse_example`', '`kaliskimodinverse_symbolic`'])" + ] + }, + { + "cell_type": "markdown", + "id": "69fd8906", + "metadata": { + "cq.autogen": "KaliskiModInverse.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "15c6fabe", + "metadata": { + "cq.autogen": "KaliskiModInverse.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "kaliskimodinverse_example_g, kaliskimodinverse_example_sigma = kaliskimodinverse_example.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(kaliskimodinverse_example_g)\n", + "show_counts_sigma(kaliskimodinverse_example_sigma)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/qualtran/bloqs/mod_arithmetic/mod_division.py b/qualtran/bloqs/mod_arithmetic/mod_division.py new file mode 100644 index 000000000..d3472103b --- /dev/null +++ b/qualtran/bloqs/mod_arithmetic/mod_division.py @@ -0,0 +1,772 @@ +# 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. + +from functools import cached_property +from typing import cast, Dict, List, Optional, Tuple, TYPE_CHECKING, Union + +import numpy as np +import sympy +from attrs import evolve, field, frozen + +from qualtran import ( + Bloq, + bloq_example, + BloqBuilder, + BloqDocSpec, + DecomposeTypeError, + QAny, + QBit, + QMontgomeryUInt, + Register, + Side, + Signature, + Soquet, + SoquetT, +) +from qualtran.bloqs.arithmetic.addition import AddK +from qualtran.bloqs.arithmetic.bitwise import BitwiseNot, XorK +from qualtran.bloqs.arithmetic.comparison import LinearDepthHalfGreaterThan +from qualtran.bloqs.arithmetic.controlled_addition import CAdd +from qualtran.bloqs.basic_gates import CNOT, TwoBitCSwap, XGate +from qualtran.bloqs.mcmt import And, MultiAnd +from qualtran.bloqs.mod_arithmetic.mod_multiplication import ModDbl +from qualtran.bloqs.swap_network import CSwapApprox +from qualtran.resource_counting import BloqCountDictT +from qualtran.resource_counting._call_graph import SympySymbolAllocator +from qualtran.symbolics import HasLength, is_symbolic + +if TYPE_CHECKING: + from qualtran.resource_counting import BloqCountDictT + from qualtran.simulation.classical_sim import ClassicalValT + from qualtran.symbolics import SymbolicInt + + +@frozen +class _KaliskiIterationStep1(Bloq): + """The first layer of operations in figure 15 of https://arxiv.org/pdf/2302.06639.""" + + bitsize: 'SymbolicInt' + + @cached_property + def signature(self) -> 'Signature': + return Signature( + [ + Register('v', QMontgomeryUInt(self.bitsize)), + Register('m', QBit()), + Register('f', QBit()), + Register('is_terminal', QBit()), + ] + ) + + def on_classical_vals( + self, v: int, m: int, f: int, is_terminal: int + ) -> Dict[str, 'ClassicalValT']: + m ^= f & (v == 0) + assert is_terminal == 0 + is_terminal ^= m + f ^= m + return {'v': v, 'm': m, 'f': f, 'is_terminal': is_terminal} + + def build_composite_bloq( + self, bb: 'BloqBuilder', v: Soquet, m: Soquet, f: Soquet, is_terminal: Soquet + ) -> Dict[str, 'SoquetT']: + if is_symbolic(self.bitsize): + raise DecomposeTypeError(f'symbolic decomposition is not supported for {self}') + v_arr = bb.split(v) + ctrls = np.concatenate([v_arr, [f]]) + ctrls, junk, target = bb.add(MultiAnd(cvs=[0] * self.bitsize + [1]), ctrl=ctrls) + target, m = bb.add(CNOT(), ctrl=target, target=m) + ctrls = bb.add( + MultiAnd(cvs=[0] * self.bitsize + [1]).adjoint(), ctrl=ctrls, junk=junk, target=target + ) + v_arr = ctrls[:-1] + f = ctrls[-1] + v = bb.join(v_arr) + m, f = bb.add(CNOT(), ctrl=m, target=f) + m, is_terminal = bb.add(CNOT(), ctrl=m, target=is_terminal) + return {'v': v, 'm': m, 'f': f, 'is_terminal': is_terminal} + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': + if is_symbolic(self.bitsize): + cvs: Union[HasLength, List[int]] = HasLength(self.bitsize + 1) + else: + cvs = [0] * int(self.bitsize) + [1] + return {MultiAnd(cvs=cvs): 1, MultiAnd(cvs=cvs).adjoint(): 1, CNOT(): 3} + + +@frozen +class _KaliskiIterationStep2(Bloq): + """The second layer of operations in figure 15 of https://arxiv.org/pdf/2302.06639.""" + + bitsize: 'SymbolicInt' + + @cached_property + def signature(self) -> 'Signature': + return Signature( + [ + Register('u', QMontgomeryUInt(self.bitsize)), + Register('v', QMontgomeryUInt(self.bitsize)), + Register('b', QBit()), + Register('a', QBit()), + Register('m', QBit()), + Register('f', QBit()), + ] + ) + + def on_classical_vals( + self, u: int, v: int, b: int, a: int, m: int, f: int + ) -> Dict[str, 'ClassicalValT']: + a ^= ((u & 1) == 0) & f + m ^= ((v & 1) == 0) & (a == 0) & f + b ^= a + b ^= m + return {'u': u, 'v': v, 'b': b, 'a': a, 'm': m, 'f': f} + + def build_composite_bloq( + self, bb: 'BloqBuilder', u: Soquet, v: Soquet, b: Soquet, a: Soquet, m: Soquet, f: Soquet + ) -> Dict[str, 'SoquetT']: + if is_symbolic(self.bitsize): + raise DecomposeTypeError(f"Cannot decompose {self} with symbolic `bitsize`.") + + u_arr = bb.split(u) + v_arr = bb.split(v) + + (f, u_arr[-1]), c = bb.add(And(1, 0), ctrl=(f, u_arr[-1])) + c, a = bb.add(CNOT(), ctrl=c, target=a) + f, u_arr[-1] = bb.add(And(1, 0).adjoint(), ctrl=(f, u_arr[-1]), target=c) + + (f, v_arr[-1], a), junk, c = bb.add(MultiAnd(cvs=(1, 0, 0)), ctrl=(f, v_arr[-1], a)) + c, m = bb.add(CNOT(), ctrl=c, target=m) + f, v_arr[-1], a = bb.add( + MultiAnd(cvs=(1, 0, 0)).adjoint(), ctrl=(f, v_arr[-1], a), junk=junk, target=c + ) + + a, b = bb.add(CNOT(), ctrl=a, target=b) + m, b = bb.add(CNOT(), ctrl=m, target=b) + u = bb.join(u_arr) + v = bb.join(v_arr) + return {'u': u, 'v': v, 'b': b, 'a': a, 'm': m, 'f': f} + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': + return { + And(1, 0): 1, + And(1, 0).adjoint(): 1, + CNOT(): 4, + MultiAnd((1, 0, 0)): 1, + MultiAnd((1, 0, 0)).adjoint(): 1, + } + + +@frozen +class _KaliskiIterationStep3(Bloq): + """The third layer of operations in figure 15 of https://arxiv.org/pdf/2302.06639.""" + + bitsize: 'SymbolicInt' + + @cached_property + def signature(self) -> 'Signature': + return Signature( + [ + Register('u', QMontgomeryUInt(self.bitsize)), + Register('v', QMontgomeryUInt(self.bitsize)), + Register('b', QBit()), + Register('a', QBit()), + Register('m', QBit()), + Register('f', QBit()), + ] + ) + + def on_classical_vals( + self, u: int, v: int, b: int, a: int, m: int, f: int + ) -> Dict[str, 'ClassicalValT']: + c = (u > v) & (b == 0) & f + a ^= c + m ^= c + return {'u': u, 'v': v, 'b': b, 'a': a, 'm': m, 'f': f} + + def build_composite_bloq( + self, bb: 'BloqBuilder', u: Soquet, v: Soquet, b: Soquet, a: Soquet, m: Soquet, f: Soquet + ) -> Dict[str, 'SoquetT']: + u, v, junk_c, greater_than = bb.add( + LinearDepthHalfGreaterThan(QMontgomeryUInt(self.bitsize)), a=u, b=v + ) + + (greater_than, f, b), junk_m, ctrl = bb.add( + MultiAnd(cvs=(1, 1, 0)), ctrl=(greater_than, f, b) + ) + + ctrl, a = bb.add(CNOT(), ctrl=ctrl, target=a) + ctrl, m = bb.add(CNOT(), ctrl=ctrl, target=m) + + greater_than, f, b = bb.add( + MultiAnd(cvs=(1, 1, 0)).adjoint(), ctrl=(greater_than, f, b), junk=junk_m, target=ctrl + ) + u, v = bb.add( + LinearDepthHalfGreaterThan(QMontgomeryUInt(self.bitsize)).adjoint(), + a=u, + b=v, + c=junk_c, + target=greater_than, + ) + return {'u': u, 'v': v, 'b': b, 'a': a, 'm': m, 'f': f} + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': + return { + LinearDepthHalfGreaterThan(QMontgomeryUInt(self.bitsize)): 1, + LinearDepthHalfGreaterThan(QMontgomeryUInt(self.bitsize)).adjoint(): 1, + MultiAnd((1, 1, 0)): 1, + MultiAnd((1, 1, 0)).adjoint(): 1, + CNOT(): 2, + } + + +@frozen +class _KaliskiIterationStep4(Bloq): + """The fourth layer of operations in figure 15 of https://arxiv.org/pdf/2302.06639.""" + + bitsize: 'SymbolicInt' + + @cached_property + def signature(self) -> 'Signature': + return Signature( + [ + Register('u', QMontgomeryUInt(self.bitsize)), + Register('v', QMontgomeryUInt(self.bitsize)), + Register('r', QMontgomeryUInt(self.bitsize)), + Register('s', QMontgomeryUInt(self.bitsize)), + Register('a', QBit()), + ] + ) + + def on_classical_vals( + self, u: int, v: int, r: int, s: int, a: int + ) -> Dict[str, 'ClassicalValT']: + if a: + u, v = v, u + r, s = s, r + return {'u': u, 'v': v, 'r': r, 's': s, 'a': a} + + def build_composite_bloq( + self, bb: 'BloqBuilder', u: Soquet, v: Soquet, r: Soquet, s: Soquet, a: Soquet + ) -> Dict[str, 'SoquetT']: + # CSwapApprox is a CSWAP with a phase flip. + # Since we are doing two SWAPs the overal phase is correct. + a, u, v = bb.add(CSwapApprox(self.bitsize), ctrl=a, x=u, y=v) + a, r, s = bb.add(CSwapApprox(self.bitsize), ctrl=a, x=r, y=s) + return {'u': u, 'v': v, 'r': r, 's': s, 'a': a} + + def build_call_graph(self, ssa: SympySymbolAllocator) -> 'BloqCountDictT': + return {CSwapApprox(self.bitsize): 2} + + +@frozen +class _KaliskiIterationStep5(Bloq): + """The fifth layer of operations in figure 15 of https://arxiv.org/pdf/2302.06639.""" + + bitsize: 'SymbolicInt' + + @cached_property + def signature(self) -> 'Signature': + return Signature( + [ + Register('u', QMontgomeryUInt(self.bitsize)), + Register('v', QMontgomeryUInt(self.bitsize)), + Register('r', QMontgomeryUInt(self.bitsize)), + Register('s', QMontgomeryUInt(self.bitsize)), + Register('b', QBit()), + Register('f', QBit()), + ] + ) + + def on_classical_vals( + self, u: int, v: int, r: int, s: int, b: int, f: int + ) -> Dict[str, 'ClassicalValT']: + if f and b == 0: + v -= u + s += r + return {'u': u, 'v': v, 'r': r, 's': s, 'b': b, 'f': f} + + def build_composite_bloq( + self, bb: 'BloqBuilder', u: Soquet, v: Soquet, r: Soquet, s: Soquet, b: Soquet, f: Soquet + ) -> Dict[str, 'SoquetT']: + (f, b), c = bb.add(And(1, 0), ctrl=(f, b)) + v = bb.add(BitwiseNot(QMontgomeryUInt(self.bitsize)), x=v) + c, u, v = bb.add(CAdd(QMontgomeryUInt(self.bitsize)), ctrl=c, a=u, b=v) + v = bb.add(BitwiseNot(QMontgomeryUInt(self.bitsize)), x=v) + c, r, s = bb.add(CAdd(QMontgomeryUInt(self.bitsize)), ctrl=c, a=r, b=s) + f, b = bb.add(And(1, 0).adjoint(), ctrl=(f, b), target=c) + return {'u': u, 'v': v, 'r': r, 's': s, 'b': b, 'f': f} + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': + return { + And(1, 0): 1, + And(1, 0).adjoint(): 1, + BitwiseNot(QMontgomeryUInt(self.bitsize)): 2, + CAdd(QMontgomeryUInt(self.bitsize)): 2, + } + + +@frozen +class _KaliskiIterationStep6(Bloq): + """The sixth layer of operations in figure 15 of https://arxiv.org/pdf/2302.06639.""" + + bitsize: 'SymbolicInt' + mod: 'SymbolicInt' + + @cached_property + def signature(self) -> 'Signature': + return Signature( + [ + Register('u', QMontgomeryUInt(self.bitsize)), + Register('v', QMontgomeryUInt(self.bitsize)), + Register('r', QMontgomeryUInt(self.bitsize)), + Register('s', QMontgomeryUInt(self.bitsize)), + Register('b', QBit()), + Register('a', QBit()), + Register('m', QBit()), + Register('f', QBit()), + ] + ) + + def on_classical_vals( + self, u: int, v: int, r: int, s: int, b: int, a: int, m: int, f: int + ) -> Dict[str, 'ClassicalValT']: + b ^= m + b ^= a + if f: + v >>= 1 + r = (2 * r) % self.mod + if a: + r, s = s, r + u, v = v, u + if s % 2 == 0: + a ^= 1 + return {'u': u, 'v': v, 'r': r, 's': s, 'b': b, 'a': a, 'm': m, 'f': f} + + def build_composite_bloq( + self, + bb: 'BloqBuilder', + u: Soquet, + v: Soquet, + r: Soquet, + s: Soquet, + b: Soquet, + a: Soquet, + m: Soquet, + f: Soquet, + ) -> Dict[str, 'SoquetT']: + if is_symbolic(self.bitsize, self.mod): + raise DecomposeTypeError(f'symbolic decomposition is not supported for {self}') + m, b = bb.add(CNOT(), ctrl=m, target=b) + a, b = bb.add(CNOT(), ctrl=a, target=b) + + # Controlled Divison by 2. The control bit is set only iff the number is even so the divison becomes equivalent to a cyclic right shift. + v_arr = bb.split(v) + for i in reversed(range(self.bitsize - 1)): + f, v_arr[i], v_arr[i + 1] = bb.add(TwoBitCSwap(), ctrl=f, x=v_arr[i], y=v_arr[i + 1]) + v = bb.join(v_arr) + + r = bb.add(ModDbl(QMontgomeryUInt(self.bitsize), self.mod), x=r) + + a, u, v = bb.add(CSwapApprox(self.bitsize), ctrl=a, x=u, y=v) + a, r, s = bb.add(CSwapApprox(self.bitsize), ctrl=a, x=r, y=s) + + s_arr = bb.split(s) + s_arr[-1] = bb.add(XGate(), q=s_arr[-1]) + s_arr[-1], a = bb.add(CNOT(), ctrl=s_arr[-1], target=a) + s_arr[-1] = bb.add(XGate(), q=s_arr[-1]) + s = bb.join(s_arr) + + return {'u': u, 'v': v, 'r': r, 's': s, 'b': b, 'a': a, 'm': m, 'f': f} + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': + return { + CNOT(): 3, + XGate(): 2, + ModDbl(QMontgomeryUInt(self.bitsize), self.mod): 1, + CSwapApprox(self.bitsize): 2, + TwoBitCSwap(): self.bitsize - 1, + } + + +@frozen +class _KaliskiIteration(Bloq): + """The single full iteration of Kaliski. see figure 15 of https://arxiv.org/pdf/2302.06639.""" + + bitsize: 'SymbolicInt' + mod: 'SymbolicInt' + + @cached_property + def signature(self) -> 'Signature': + return Signature( + [ + Register('u', QMontgomeryUInt(self.bitsize)), + Register('v', QMontgomeryUInt(self.bitsize)), + Register('r', QMontgomeryUInt(self.bitsize)), + Register('s', QMontgomeryUInt(self.bitsize)), + Register('m', QBit()), + Register('f', QBit()), + Register('is_terminal', QBit()), + ] + ) + + def build_composite_bloq( + self, + bb: 'BloqBuilder', + u: Soquet, + v: Soquet, + r: Soquet, + s: Soquet, + m: Soquet, + f: Soquet, + is_terminal: Soquet, + ) -> Dict[str, 'SoquetT']: + a = bb.allocate(1) + b = bb.allocate(1) + + v, m, f, is_terminal = bb.add( + _KaliskiIterationStep1(self.bitsize), v=v, m=m, f=f, is_terminal=is_terminal + ) + u, v, b, a, m, f = bb.add( + _KaliskiIterationStep2(self.bitsize), u=u, v=v, b=b, a=a, m=m, f=f + ) + u, v, b, a, m, f = bb.add( + _KaliskiIterationStep3(self.bitsize), u=u, v=v, b=b, a=a, m=m, f=f + ) + u, v, r, s, a = bb.add(_KaliskiIterationStep4(self.bitsize), u=u, v=v, r=r, s=s, a=a) + u, v, r, s, b, f = bb.add( + _KaliskiIterationStep5(self.bitsize), u=u, v=v, r=r, s=s, b=b, f=f + ) + u, v, r, s, b, a, m, f = bb.add( + _KaliskiIterationStep6(self.bitsize, self.mod), u=u, v=v, r=r, s=s, b=b, a=a, m=m, f=f + ) + + bb.free(a) + bb.free(b) + return {'u': u, 'v': v, 'r': r, 's': s, 'm': m, 'f': f, 'is_terminal': is_terminal} + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': + return { + _KaliskiIterationStep1(self.bitsize): 1, + _KaliskiIterationStep2(self.bitsize): 1, + _KaliskiIterationStep3(self.bitsize): 1, + _KaliskiIterationStep4(self.bitsize): 1, + _KaliskiIterationStep5(self.bitsize): 1, + _KaliskiIterationStep6(self.bitsize, self.mod): 1, + } + + def on_classical_vals( + self, u: int, v: int, r: int, s: int, m: int, f: int, is_terminal: int + ) -> Dict[str, 'ClassicalValT']: + """This is the Kaliski algorithm as described in Fig7 of https://arxiv.org/pdf/2001.09580. + + The following implementation merges together the pseudocode from Fig7 of https://arxiv.org/pdf/2001.09580 + and the circuit in figure 15 of https://arxiv.org/pdf/2302.06639; This is in order to compute the values + of `f` and `m`. + """ + assert m == 0 + is_terminal = int(f == 1 and v == 0) + if f == 0: + # When `f = 0` this means that the algorithm is nearly over and that we just need to + # double the value of `r`. + r = (r << 1) % self.mod + elif v == 0: + # `v = 0` is the termination condition of the algorithm and it means that the only + # remaining step is multiplying `r` by 2 raised to the number of remaining iterations. + # Classically this translates into a `r = (r * pow(2, k, p))%p` where k is the number + # of iterations left followed by a break statement. + m = u & 1 + f = 0 + r = (r << 1) % self.mod + else: + m = ((u % 2 == 1) & (v % 2 == 0)) or (u % 2 == 1 and v % 2 == 1 and u > v) + m = int(m) + # Kaliski iteration as described in Fig7 of https://arxiv.org/pdf/2001.09580. + swap = (u % 2 == 0 and v % 2 == 1) or (u % 2 == 1 and v % 2 == 1 and u > v) + if swap: + u, v = v, u + r, s = s, r + if u % 2 == 1 and v % 2 == 1: + v -= u + s += r + assert v % 2 == 0, f'{u=} {v=} {swap=}' + v >>= 1 + r = (r << 1) % self.mod + if swap: + u, v = v, u + r, s = s, r + return {'u': u, 'v': v, 'r': r, 's': s, 'm': m, 'f': f, 'is_terminal': is_terminal} + + +@frozen +class _KaliskiModInverseImpl(Bloq): + """The full KaliskiIteration algorithm. see C5 https://arxiv.org/pdf/2302.06639""" + + bitsize: 'SymbolicInt' + mod: 'SymbolicInt' + + @cached_property + def signature(self) -> 'Signature': + return Signature( + [ + Register('u', QMontgomeryUInt(self.bitsize)), + Register('v', QMontgomeryUInt(self.bitsize)), + Register('r', QMontgomeryUInt(self.bitsize)), + Register('s', QMontgomeryUInt(self.bitsize)), + Register('m', QAny(2 * self.bitsize)), + Register('f', QBit()), + Register('terminal_condition', QAny(2 * self.bitsize)), + ] + ) + + @cached_property + def _kaliski_iteration(self): + return _KaliskiIteration(self.bitsize, self.mod) + + def build_composite_bloq( + self, + bb: 'BloqBuilder', + u: Soquet, + v: Soquet, + r: Soquet, + s: Soquet, + m: Soquet, + f: Soquet, + terminal_condition: Soquet, + ) -> Dict[str, 'SoquetT']: + if is_symbolic(self.bitsize): + raise DecomposeTypeError(f"Cannot decompose {self} with symbolic `bitsize`.") + + f = bb.add(XGate(), q=f) + u = bb.add(XorK(QMontgomeryUInt(self.bitsize), self.mod), x=u) + s = bb.add(XorK(QMontgomeryUInt(self.bitsize), 1), x=s) + + m_arr = bb.split(m) + terminal_condition_arr = bb.split(terminal_condition) + + for i in range(2 * self.bitsize): + u, v, r, s, m_arr[i], f, terminal_condition_arr[i] = bb.add( + self._kaliski_iteration, + u=u, + v=v, + r=r, + s=s, + m=m_arr[i], + f=f, + is_terminal=terminal_condition_arr[i], + ) + + r = bb.add(BitwiseNot(QMontgomeryUInt(self.bitsize)), x=r) + r = bb.add(AddK(QMontgomeryUInt(self.bitsize), self.mod + 1), x=r) + + u = bb.add(XorK(QMontgomeryUInt(self.bitsize), 1), x=u) + s = bb.add(XorK(QMontgomeryUInt(self.bitsize), self.mod), x=s) + + # This is an extra step not present in the original Kaliski algorithm in order to + # handle the case of x=0. The invariant of the Kaliski algorithm is that that end of the + # algorithm u=1, s=0, r=mod inverse. This happens for all cases where the modular inverse + # exists (i.e. gcd(x, mod) = 1). + # The case where the input is zero is important. Although mathematically the inverse + # doesn't exist. For the bloq to be unitary it needs to map zero to itself. + # When the input is zero, the terminal values of the registers are r=mod, u=v=mod^1=mod-1 + # (assuming odd modulus). + # So we clean those registers conditioned on the first terminal qubit which is set + # if and only if the input is zero. + terminal_condition_arr[0], r = bb.add( + XorK(QMontgomeryUInt(self.bitsize), self.mod).controlled(), + ctrl=terminal_condition_arr[0], + x=r, + ) + terminal_condition_arr[0], u = bb.add( + XorK(QMontgomeryUInt(self.bitsize), self.mod - 1).controlled(), + ctrl=terminal_condition_arr[0], + x=u, + ) + terminal_condition_arr[0], s = bb.add( + XorK(QMontgomeryUInt(self.bitsize), self.mod - 1).controlled(), + ctrl=terminal_condition_arr[0], + x=s, + ) + + m = bb.join(m_arr) + terminal_condition = bb.join(terminal_condition_arr) + return { + 'u': u, + 'v': v, + 'r': r, + 's': s, + 'm': m, + 'f': f, + 'terminal_condition': terminal_condition, + } + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': + return { + self._kaliski_iteration: 2 * self.bitsize, + BitwiseNot(QMontgomeryUInt(self.bitsize)): 1, + AddK(QMontgomeryUInt(self.bitsize), self.mod + 1): 1, + XGate(): 1, + XorK(QMontgomeryUInt(self.bitsize), self.mod): 2, + XorK(QMontgomeryUInt(self.bitsize), 1): 2, + XorK(QMontgomeryUInt(self.bitsize), self.mod).controlled(): 1, + XorK(QMontgomeryUInt(self.bitsize), self.mod - 1).controlled(): 2, + } + + +@frozen +class KaliskiModInverse(Bloq): + r"""Compute modular multiplicative inverse -inplace- of numbers in montgomery form. + + Applies the transformation + $$ + \ket{x} \ket{0} \rightarrow \ket{x^{-1} 2^{2n} \mod p} \ket{\mathrm{garbage}} + $$ + + Args: + bitsize: size of the number. + mod: The integer modulus. + uncompute: whether to compute or uncompute. + + Registers: + x: The register for which we compute the multiplicative inverse. + m: A 2*bitsize register of intermediate values needed for uncomputation. + + References: + [Performance Analysis of a Repetition Cat Code Architecture: Computing 256-bit Elliptic Curve Logarithm in 9 Hours with 126 133 Cat Qubits](https://arxiv.org/abs/2302.06639) + Appendix C5. + + [Improved quantum circuits for elliptic curve discrete logarithms](https://arxiv.org/abs/2001.09580) + Fig 7(b) + + [How to compute a 256-bit elliptic curve private key with only 50 million Toffoli gates](https://arxiv.org/abs/2306.08585) + page 8. + """ + + bitsize: 'SymbolicInt' + mod: 'SymbolicInt' = field(validator=lambda _, __, v: is_symbolic(v) or v % 2 == 1) + uncompute: bool = False + + @cached_property + def signature(self) -> 'Signature': + side = Side.LEFT if self.uncompute else Side.RIGHT + return Signature( + [ + Register('x', QMontgomeryUInt(self.bitsize)), + Register('junk', QAny(4 * self.bitsize), side=side), + ] + ) + + def build_composite_bloq( + self, bb: 'BloqBuilder', x: Soquet, junk: Optional[Soquet] = None + ) -> Dict[str, 'SoquetT']: + if is_symbolic(self.bitsize): + raise DecomposeTypeError(f"Cannot decompose {self} with symbolic `bitsize`.") + + u = bb.allocate(self.bitsize, QMontgomeryUInt(self.bitsize)) + r = bb.allocate(self.bitsize, QMontgomeryUInt(self.bitsize)) + s = bb.allocate(self.bitsize, QMontgomeryUInt(self.bitsize)) + f = bb.allocate(1) + + if self.uncompute: + assert junk is not None + junk_arr = bb.split(junk) + m = bb.join(junk_arr[: 2 * self.bitsize]) + terminal_condition = bb.join(junk_arr[2 * self.bitsize :]) + u, x, r, s, m, f, terminal_condition = cast( + Tuple[Soquet, Soquet, Soquet, Soquet, Soquet, Soquet, Soquet], + bb.add_from( + _KaliskiModInverseImpl(self.bitsize, self.mod).adjoint(), + u=u, + v=r, + r=x, + s=s, + m=m, + f=f, + terminal_condition=terminal_condition, + ), + ) + bb.free(u) + bb.free(r) + bb.free(s) + bb.free(m) + bb.free(f) + bb.free(terminal_condition) + return {'x': x} + + m = bb.allocate(2 * self.bitsize) + terminal_condition = bb.allocate(2 * self.bitsize) + u, v, x, s, m, f, terminal_condition = cast( + Tuple[Soquet, Soquet, Soquet, Soquet, Soquet, Soquet, Soquet], + bb.add_from( + _KaliskiModInverseImpl(self.bitsize, self.mod), + u=u, + v=x, + r=r, + s=s, + m=m, + f=f, + terminal_condition=terminal_condition, + ), + ) + + bb.free(u) + bb.free(v) + bb.free(s) + bb.free(f) + junk = bb.join(np.concatenate([bb.split(m), bb.split(terminal_condition)])) + return {'x': x, 'junk': junk} + + def adjoint(self) -> 'KaliskiModInverse': + return evolve(self, uncompute=not self.uncompute) + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': + return _KaliskiModInverseImpl(self.bitsize, self.mod).build_call_graph(ssa) + + def on_classical_vals(self, x: int, junk: int = 0) -> Dict[str, 'ClassicalValT']: + mod = int(self.mod) + u, v, r, s, f = mod, x, 0, 1, 1 + terminal_condition = m = 0 + iteration = _KaliskiModInverseImpl(self.bitsize, self.mod)._kaliski_iteration + for _ in range(2 * int(self.bitsize)): + u, v, r, s, m_i, f, is_terminal = iteration.call_classically( + u=u, v=v, r=r, s=s, m=0, f=f, is_terminal=0 + ) + m = (m << 1) | m_i + terminal_condition = (terminal_condition << 1) | is_terminal + assert u == 1 or (x == 0 and u == mod) + assert s == self.mod or (x == 0 and s == 1) + assert f == 0 + assert v == 0 + return { + 'x': (self.mod - r) if r else 0, + 'junk': m * 2 ** (2 * self.bitsize) + terminal_condition, + } + + +@bloq_example +def _kaliskimodinverse_example() -> KaliskiModInverse: + kaliskimodinverse_example = KaliskiModInverse(32, 10**9 + 7) + return kaliskimodinverse_example + + +@bloq_example +def _kaliskimodinverse_symbolic() -> KaliskiModInverse: + n, p = sympy.symbols('n p') + kaliskimodinverse_symbolic = KaliskiModInverse(n, p) + return kaliskimodinverse_symbolic + + +_KALISKI_MOD_INVERSE_DOC = BloqDocSpec( + bloq_cls=KaliskiModInverse, examples=[_kaliskimodinverse_example, _kaliskimodinverse_symbolic] +) diff --git a/qualtran/bloqs/mod_arithmetic/mod_division_test.py b/qualtran/bloqs/mod_arithmetic/mod_division_test.py new file mode 100644 index 000000000..934a26967 --- /dev/null +++ b/qualtran/bloqs/mod_arithmetic/mod_division_test.py @@ -0,0 +1,177 @@ +# 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 math + +import pytest +import sympy + +import qualtran.testing as qlt_testing +from qualtran import QMontgomeryUInt +from qualtran.bloqs.mod_arithmetic import mod_division +from qualtran.bloqs.mod_arithmetic.mod_division import _kaliskimodinverse_example, KaliskiModInverse +from qualtran.resource_counting import get_cost_value, QECGatesCost +from qualtran.resource_counting.generalizers import ignore_alloc_free, ignore_split_join + + +@pytest.mark.parametrize('bitsize', [5, 6]) +@pytest.mark.parametrize('mod', [3, 5, 7, 11, 13, 15]) +def test_kaliski_mod_inverse_classical_action(bitsize, mod): + blq = KaliskiModInverse(bitsize, mod) + cblq = blq.decompose_bloq() + dtype = QMontgomeryUInt(bitsize) + R = pow(2, bitsize, mod) + for x in range(1, mod): + if math.gcd(x, mod) != 1: + continue + x_montgomery = dtype.uint_to_montgomery(x, mod) + res = blq.call_classically(x=x_montgomery) + + assert res == cblq.call_classically(x=x_montgomery) + assert len(res) == 2 + assert res[0] == dtype.montgomery_inverse(x_montgomery, mod) + assert dtype.montgomery_product(int(res[0]), x_montgomery, mod) == R + assert blq.adjoint().call_classically(x=res[0], junk=res[1]) == (x_montgomery,) + + +@pytest.mark.parametrize('bitsize', [5, 6]) +@pytest.mark.parametrize('mod', [3, 5, 7, 11, 13, 15]) +def test_kaliski_mod_inverse_classical_action_zero(bitsize, mod): + blq = KaliskiModInverse(bitsize, mod) + cblq = blq.decompose_bloq() + # When x = 0 the terminal condition is achieved at the first iteration, this corresponds to + # m_0 = is_terminal_0 = 1 and all other bits = 0. + junk = 2 ** (4 * bitsize - 1) + 2 ** (2 * bitsize - 1) + assert blq.call_classically(x=0) == cblq.call_classically(x=0) == (0, junk) + assert blq.adjoint().call_classically(x=0, junk=junk) == (0,) + + +@pytest.mark.parametrize('bitsize', [5, 6]) +@pytest.mark.parametrize('mod', [3, 5, 7, 11, 13, 15]) +def test_kaliski_mod_inverse_decomposition(bitsize, mod): + b = KaliskiModInverse(bitsize, mod) + qlt_testing.assert_valid_bloq_decomposition(b) + + +@pytest.mark.parametrize('bitsize', [5, 6]) +@pytest.mark.parametrize('mod', [3, 5, 7, 11, 13, 15]) +def test_kaliski_mod_bloq_counts(bitsize, mod): + b = KaliskiModInverse(bitsize, mod) + qlt_testing.assert_equivalent_bloq_counts(b, [ignore_alloc_free, ignore_split_join]) + + +def test_kaliski_symbolic_cost(): + n, p = sympy.symbols('n p') + b = KaliskiModInverse(n, p) + cost = get_cost_value(b, QECGatesCost()).total_t_and_ccz_count() + # We have some T gates since we use CSwapApprox instead of n CSWAPs. + total_toff = (cost['n_t'] / 4 + cost['n_ccz']) * sympy.Integer(1) + total_toff = total_toff.expand() + + # The toffoli cost from Litinski https://arxiv.org/abs/2306.08585 is 26n^2 + 2n. + # The cost of Kaliski is 2*n*(cost of an iteration) + (cost of computing $p - x$) + # + # - The cost of of computing $p-x$ in Litinski is 2n (Neg -> Add(p)). In our + # construction this is just $n-1$ (BitwiseNot -> Add(p+1)). + # - The cost of an iteration in Litinski $13n$ since they ignore constants. + # Our construction is exactly the same but we also count the constants + # which amout to $3$. for a total cost of $13n + 4$. + # For example the cost of ModDbl is 2n+1. In their figure 8, they report + # it as just $2n$. ModDbl gets executed within the 2n loop so its contribution + # to the overal cost should be 4n^2 + 2n instead of just 4n^2. + assert total_toff == 26 * n**2 + 9 * n - 1 + + +def test_kaliskimodinverse_example(bloq_autotester): + bloq_autotester(_kaliskimodinverse_example) + + +@pytest.mark.notebook +def test_notebook(): + qlt_testing.execute_notebook('mod_division') + + +def test_kaliski_iteration_decomposition(): + mod = 7 + bitsize = 5 + b = mod_division._KaliskiIteration(bitsize, mod) + cb = b.decompose_bloq() + for x in range(mod): + u = mod + v = x + r = 0 + s = 1 + f = 1 + + for _ in range(2 * bitsize): + inputs = {'u': u, 'v': v, 'r': r, 's': s, 'm': 0, 'f': f, 'is_terminal': 0} + res = b.call_classically(**inputs) + assert res == cb.call_classically(**inputs), f'{inputs=}' + u, v, r, s, _, f, _ = res # type: ignore + + qlt_testing.assert_valid_bloq_decomposition(b) + qlt_testing.assert_equivalent_bloq_counts(b, generalizer=(ignore_alloc_free, ignore_split_join)) + + +def test_kaliski_steps(): + bitsize = 5 + mod = 7 + steps = [ + mod_division._KaliskiIterationStep1(bitsize), + mod_division._KaliskiIterationStep2(bitsize), + mod_division._KaliskiIterationStep3(bitsize), + mod_division._KaliskiIterationStep4(bitsize), + mod_division._KaliskiIterationStep5(bitsize), + mod_division._KaliskiIterationStep6(bitsize, mod), + ] + csteps = [b.decompose_bloq() for b in steps] + + # check decomposition is valid. + for step in steps: + qlt_testing.assert_valid_bloq_decomposition(step) + qlt_testing.assert_equivalent_bloq_counts( + step, generalizer=(ignore_alloc_free, ignore_split_join) + ) + + # check that for all inputs all 2n iteration work when excuted directly on the 6 steps + # and their decompositions. + for x in range(mod): + u, v, r, s, f = mod, x, 0, 1, 1 + + for _ in range(2 * bitsize): + a = b = m = is_terminal = 0 + + res = steps[0].call_classically(v=v, m=m, f=f, is_terminal=is_terminal) + assert res == csteps[0].call_classically(v=v, m=m, f=f, is_terminal=is_terminal) + v, m, f, is_terminal = res # type: ignore + + res = steps[1].call_classically(u=u, v=v, b=b, a=a, m=m, f=f) + assert res == csteps[1].call_classically(u=u, v=v, b=b, a=a, m=m, f=f) + u, v, b, a, m, f = res # type: ignore + + res = steps[2].call_classically(u=u, v=v, b=b, a=a, m=m, f=f) + assert res == csteps[2].call_classically(u=u, v=v, b=b, a=a, m=m, f=f) + u, v, b, a, m, f = res # type: ignore + + res = steps[3].call_classically(u=u, v=v, r=r, s=s, a=a) + assert res == csteps[3].call_classically(u=u, v=v, r=r, s=s, a=a) + u, v, r, s, a = res # type: ignore + + res = steps[4].call_classically(u=u, v=v, r=r, s=s, b=b, f=f) + assert res == csteps[4].call_classically(u=u, v=v, r=r, s=s, b=b, f=f) + u, v, r, s, b, f = res # type: ignore + + res = steps[5].call_classically(u=u, v=v, r=r, s=s, b=b, a=a, m=m, f=f) + assert res == csteps[5].call_classically(u=u, v=v, r=r, s=s, b=b, a=a, m=m, f=f) + u, v, r, s, b, a, m, f = res # type: ignore diff --git a/qualtran/bloqs/mod_arithmetic/mod_multiplication.ipynb b/qualtran/bloqs/mod_arithmetic/mod_multiplication.ipynb index dc4c62bdf..a7f5a4aac 100644 --- a/qualtran/bloqs/mod_arithmetic/mod_multiplication.ipynb +++ b/qualtran/bloqs/mod_arithmetic/mod_multiplication.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "6b63cfe5", + "id": "b23ed079", "metadata": { "cq.autogen": "title_cell" }, @@ -13,7 +13,7 @@ { "cell_type": "code", "execution_count": null, - "id": "d72f6711", + "id": "5cde4184", "metadata": { "cq.autogen": "top_imports" }, @@ -30,7 +30,7 @@ }, { "cell_type": "markdown", - "id": "d3899162", + "id": "33013eac", "metadata": { "cq.autogen": "ModDbl.bloq_doc.md" }, @@ -54,7 +54,7 @@ { "cell_type": "code", "execution_count": null, - "id": "53515719", + "id": "934adfa2", "metadata": { "cq.autogen": "ModDbl.bloq_doc.py" }, @@ -65,7 +65,7 @@ }, { "cell_type": "markdown", - "id": "b5e0c374", + "id": "854a2f34", "metadata": { "cq.autogen": "ModDbl.example_instances.md" }, @@ -76,7 +76,7 @@ { "cell_type": "code", "execution_count": null, - "id": "550f264b", + "id": "a376c520", "metadata": { "cq.autogen": "ModDbl.moddbl_small" }, @@ -88,7 +88,7 @@ { "cell_type": "code", "execution_count": null, - "id": "89df68f0", + "id": "559f3f97", "metadata": { "cq.autogen": "ModDbl.moddbl_large" }, @@ -100,7 +100,7 @@ }, { "cell_type": "markdown", - "id": "acd85b81", + "id": "e90cf054", "metadata": { "cq.autogen": "ModDbl.graphical_signature.md" }, @@ -111,7 +111,7 @@ { "cell_type": "code", "execution_count": null, - "id": "c588ee92", + "id": "75c4294c", "metadata": { "cq.autogen": "ModDbl.graphical_signature.py" }, @@ -124,7 +124,7 @@ }, { "cell_type": "markdown", - "id": "3cfc35a0", + "id": "ef3bfee0", "metadata": { "cq.autogen": "ModDbl.call_graph.md" }, @@ -135,7 +135,7 @@ { "cell_type": "code", "execution_count": null, - "id": "c5211901", + "id": "bd1dfb09", "metadata": { "cq.autogen": "ModDbl.call_graph.py" }, @@ -149,7 +149,7 @@ }, { "cell_type": "markdown", - "id": "e21338a3", + "id": "03dac121", "metadata": { "cq.autogen": "CModMulK.bloq_doc.md" }, @@ -172,7 +172,7 @@ { "cell_type": "code", "execution_count": null, - "id": "08cc01f5", + "id": "b735fef0", "metadata": { "cq.autogen": "CModMulK.bloq_doc.py" }, @@ -183,7 +183,7 @@ }, { "cell_type": "markdown", - "id": "4a8585a7", + "id": "0d8c1a4b", "metadata": { "cq.autogen": "CModMulK.example_instances.md" }, @@ -194,7 +194,7 @@ { "cell_type": "code", "execution_count": null, - "id": "7d72393a", + "id": "1986bbf9", "metadata": { "cq.autogen": "CModMulK.modmul_symb" }, @@ -209,7 +209,7 @@ { "cell_type": "code", "execution_count": null, - "id": "521a0b51", + "id": "ecdbe3f4", "metadata": { "cq.autogen": "CModMulK.modmul" }, @@ -220,7 +220,7 @@ }, { "cell_type": "markdown", - "id": "b51e0ac8", + "id": "52e944f4", "metadata": { "cq.autogen": "CModMulK.graphical_signature.md" }, @@ -231,7 +231,7 @@ { "cell_type": "code", "execution_count": null, - "id": "686db91d", + "id": "1e28aa1f", "metadata": { "cq.autogen": "CModMulK.graphical_signature.py" }, @@ -244,7 +244,7 @@ }, { "cell_type": "markdown", - "id": "0749b88f", + "id": "8e34d67f", "metadata": { "cq.autogen": "CModMulK.call_graph.md" }, @@ -255,7 +255,7 @@ { "cell_type": "code", "execution_count": null, - "id": "29decc82", + "id": "f8645e0c", "metadata": { "cq.autogen": "CModMulK.call_graph.py" }, @@ -266,6 +266,143 @@ "show_call_graph(modmul_symb_g)\n", "show_counts_sigma(modmul_symb_sigma)" ] + }, + { + "cell_type": "markdown", + "id": "849371cb", + "metadata": { + "cq.autogen": "DirtyOutOfPlaceMontgomeryModMul.bloq_doc.md" + }, + "source": [ + "## `DirtyOutOfPlaceMontgomeryModMul`\n", + "Perform windowed montgomery modular multiplication.\n", + "\n", + "Applies the trasformation\n", + "$$\n", + " \\ket{x}\\ket{y}\\ket{0}\\ket{0}\\ket{0} \\rightarrow \\ket{x}\\ket{y}\\ket{xy2^{-n}}\\ket{h}\\ket{c}\n", + "$$\n", + "\n", + "Where:\n", + "\n", + "- $n$ is the bitsize.\n", + "- $x, y$ are in montgomery form\n", + "- $h$ is an ancilla register that represents intermidate values.\n", + "- $c$ is whether a final modular reduction was applied or not.\n", + "\n", + "#### Parameters\n", + " - `bitsize`: size of the numbers.\n", + " - `window_size`: size of the window.\n", + " - `mod`: The integer modulus.\n", + " - `uncompute`: whether to compute or uncompute. \n", + "\n", + "#### Registers\n", + " - `x`: The first integer\n", + " - `y`: The second integer\n", + " - `target`: product in montgomery form $xy 2^{-n}$\n", + " - `qrom_indices`: concatination of the indicies used to query QROM.\n", + " - `reduced`: whether a final modular reduction was applied. \n", + "\n", + "#### References\n", + " - [Performance Analysis of a Repetition Cat Code Architecture: Computing 256-bit Elliptic Curve Logarithm in 9 Hours with 126 133 Cat Qubits](https://arxiv.org/abs/2302.06639). Appendix C4.\n", + " - [How to compute a 256-bit elliptic curve private key with only 50 million Toffoli gates](https://arxiv.org/abs/2306.08585). page 8.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d08d7f3a", + "metadata": { + "cq.autogen": "DirtyOutOfPlaceMontgomeryModMul.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.mod_arithmetic import DirtyOutOfPlaceMontgomeryModMul" + ] + }, + { + "cell_type": "markdown", + "id": "56c6466e", + "metadata": { + "cq.autogen": "DirtyOutOfPlaceMontgomeryModMul.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b573e60f", + "metadata": { + "cq.autogen": "DirtyOutOfPlaceMontgomeryModMul.dirtyoutofplacemontgomerymodmul_small" + }, + "outputs": [], + "source": [ + "dirtyoutofplacemontgomerymodmul_small = DirtyOutOfPlaceMontgomeryModMul(6, 2, 7)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f339fb21", + "metadata": { + "cq.autogen": "DirtyOutOfPlaceMontgomeryModMul.dirtyoutofplacemontgomerymodmul_medium" + }, + "outputs": [], + "source": [ + "dirtyoutofplacemontgomerymodmul_medium = DirtyOutOfPlaceMontgomeryModMul(\n", + " bitsize=16, window_size=4, mod=2**15 - 1\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "c832c4a9", + "metadata": { + "cq.autogen": "DirtyOutOfPlaceMontgomeryModMul.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "99ee86e1", + "metadata": { + "cq.autogen": "DirtyOutOfPlaceMontgomeryModMul.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([dirtyoutofplacemontgomerymodmul_small, dirtyoutofplacemontgomerymodmul_medium],\n", + " ['`dirtyoutofplacemontgomerymodmul_small`', '`dirtyoutofplacemontgomerymodmul_medium`'])" + ] + }, + { + "cell_type": "markdown", + "id": "1de095e7", + "metadata": { + "cq.autogen": "DirtyOutOfPlaceMontgomeryModMul.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bde436f4", + "metadata": { + "cq.autogen": "DirtyOutOfPlaceMontgomeryModMul.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "dirtyoutofplacemontgomerymodmul_small_g, dirtyoutofplacemontgomerymodmul_small_sigma = dirtyoutofplacemontgomerymodmul_small.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(dirtyoutofplacemontgomerymodmul_small_g)\n", + "show_counts_sigma(dirtyoutofplacemontgomerymodmul_small_sigma)" + ] } ], "metadata": { diff --git a/qualtran/bloqs/mod_arithmetic/mod_multiplication.py b/qualtran/bloqs/mod_arithmetic/mod_multiplication.py index 536cfae6b..0e8f3ca0b 100644 --- a/qualtran/bloqs/mod_arithmetic/mod_multiplication.py +++ b/qualtran/bloqs/mod_arithmetic/mod_multiplication.py @@ -15,34 +15,44 @@ import math import numbers from functools import cached_property -from typing import cast, Dict, Optional, Tuple, Union +from typing import cast, Dict, Optional, Sequence, Set, Tuple, TYPE_CHECKING, Union import attrs import numpy as np import sympy from attrs import frozen +from numpy.typing import NDArray from qualtran import ( Bloq, bloq_example, BloqBuilder, BloqDocSpec, + DecomposeNotImplementedError, + DecomposeTypeError, QBit, + QInt, QMontgomeryUInt, QUInt, Register, + Side, Signature, Soquet, SoquetT, ) -from qualtran.bloqs.arithmetic.addition import AddK +from qualtran.bloqs.arithmetic import Add, AddK, CAdd, Xor +from qualtran.bloqs.arithmetic.comparison import LessThanConstant from qualtran.bloqs.basic_gates import CNOT, CSwap, XGate +from qualtran.bloqs.data_loading.qroam_clean import QROAMClean from qualtran.bloqs.mod_arithmetic.mod_addition import CtrlScaleModAdd from qualtran.drawing import Circle, directional_text_box, Text, WireSymbol -from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator +from qualtran.resource_counting import BloqCountDictT, BloqCountT, SympySymbolAllocator from qualtran.resource_counting.generalizers import ignore_alloc_free, ignore_split_join from qualtran.simulation.classical_sim import ClassicalValT -from qualtran.symbolics import is_symbolic +from qualtran.symbolics import is_symbolic, Shaped + +if TYPE_CHECKING: + from qualtran.symbolics import SymbolicInt @frozen @@ -64,7 +74,7 @@ class ModDbl(Bloq): """ dtype: Union[QUInt, QMontgomeryUInt] - mod: int = attrs.field() + mod: 'SymbolicInt' = attrs.field() @mod.validator def _validate_mod(self, attribute, value): @@ -82,6 +92,9 @@ def on_classical_vals(self, x: 'ClassicalValT') -> Dict[str, 'ClassicalValT']: return {'x': x} def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet) -> Dict[str, 'SoquetT']: + if is_symbolic(self.dtype.bitsize): + raise DecomposeTypeError(f'symbolic decomposition is not supported for {self}') + # Allocate ancilla bits for sign and double. lower_bit = bb.allocate(n=1) sign = bb.allocate(n=1) @@ -95,7 +108,7 @@ def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet) -> Dict[str, 'Soque ) # Add constant -p to the x register. - x = bb.add(AddK(bitsize=self.dtype.bitsize + 2, k=-self.mod, signed=False), x=x) + x = bb.add(AddK(QInt(self.dtype.bitsize + 2), k=-self.mod), x=x) # Split the three bit pieces again so that we can use the sign to control our constant # addition circuit. @@ -104,10 +117,8 @@ def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet) -> Dict[str, 'Soque x = bb.join(x_split[1:], dtype=attrs.evolve(self.dtype, bitsize=self.dtype.bitsize + 1)) # Add constant p to the x register if the result of the last modular reduction is negative. - (sign,), x = bb.add( - AddK(bitsize=self.dtype.bitsize + 1, k=self.mod, signed=False, cvs=(1,)), - ctrls=(sign,), - x=x, + sign, x = bb.add( + AddK(QUInt(self.dtype.bitsize + 1), k=self.mod).controlled(), ctrl=sign, x=x ) # Split the lower bit ancilla from the x register for use in resetting the other ancilla bit @@ -137,8 +148,8 @@ def wire_symbol( def build_call_graph(self, ssa: SympySymbolAllocator) -> BloqCountDictT: return { - AddK(self.dtype.bitsize + 2, -self.mod, signed=False): 1, - AddK(self.dtype.bitsize + 1, self.mod, cvs=(1,), signed=False): 1, + AddK(QInt(self.dtype.bitsize + 2), -self.mod): 1, + AddK(QUInt(self.dtype.bitsize + 1), self.mod).controlled(): 1, CNOT(): 1, XGate(): 2, } @@ -265,3 +276,478 @@ def _modmul_symb() -> CModMulK: _C_MOD_MUL_K_DOC = BloqDocSpec(bloq_cls=CModMulK, examples=(_modmul_symb, _modmul)) + + +@frozen +class SingleWindowModMul(Bloq): + r"""Performs modular multiplication on a single windowed. + + This bloq is used as a subroutine in DirtyOutOfPlaceMontgomeryModMul. + + Applies + $$ + \ket{x}\key{y}\key{t}\ket{0} \rightarrow \ket{x}\key{y}\ket{t+xy \mod p} \ket{xy \mod 2^w} + $$ + + Where: + + - $w$ is the window size. + - $p$ is the modulus. + + Args: + window_size: size of the window (=size of the first register). + bitsize: size of the second register. + mod: The integer modulus. + + Registers: + x: The first integer as an array of bits (`window_size` bits). + y: The second integer (`bitsize` bits) + target: product accumulation array of bits. + qrom_index: contains the value $xy \mod 2^w$ (starts at 0). + """ + + window_size: 'SymbolicInt' + bitsize: 'SymbolicInt' + mod: 'SymbolicInt' + + def __attrs_post_init__(self): + if not is_symbolic(self.bitsize, self.window_size): + assert self.bitsize % self.window_size == 0 + + @property + def signature(self) -> 'Signature': + return Signature( + [ + Register('x', QBit(), shape=(self.window_size,)), + Register('y', QMontgomeryUInt(self.bitsize)), + Register('target', QBit(), shape=(self.window_size + self.bitsize,)), + Register('qrom_index', QMontgomeryUInt(self.window_size)), + ] + ) + + @cached_property + def qrom(self) -> QROAMClean: + if is_symbolic(self.bitsize) or is_symbolic(self.window_size) or is_symbolic(self.mod): + log_block_sizes = None + if is_symbolic(self.bitsize) and not is_symbolic(self.window_size): + # We assume that bitsize is much larger than window_size + log_block_sizes = (0,) + return QROAMClean( + [Shaped((2**self.window_size,))], + selection_bitsizes=(self.window_size,), + target_bitsizes=(self.window_size + self.bitsize,), + log_block_sizes=log_block_sizes, + ) + inv_mod = pow(self.mod, 2 ** (self.window_size - 1) - 1, 2**self.window_size) + N = 2**self.window_size + data = (-np.arange(N) * inv_mod) % N + data *= self.mod + return QROAMClean( + [data], + selection_bitsizes=(self.window_size,), + target_bitsizes=(self.window_size + self.bitsize,), + ) + + def on_classical_vals(self, x: Sequence[int], y: int, target: Sequence[int], qrom_index: int): + if is_symbolic(self.bitsize) or is_symbolic(self.window_size): + raise ValueError(f'classical action is not supported for {self}') + dtype = QMontgomeryUInt(self.window_size + self.bitsize) + target_val = QMontgomeryUInt.from_bits(dtype, target) + for i in range(self.window_size): + if x[i]: + target_val += y << i + qrom_index = target_val & (2**self.window_size - 1) + Tm = self.qrom.data[0][qrom_index] + target_val = (target_val + Tm) >> self.window_size + target = QMontgomeryUInt.to_bits(dtype, target_val) + return {'target': target, 'qrom_index': qrom_index, 'x': x, 'y': y} + + def build_composite_bloq( + self, bb: 'BloqBuilder', x: NDArray[Soquet], y: Soquet, target: NDArray[Soquet], qrom_index: Soquet # type: ignore[type-var] + ): + if is_symbolic(self.window_size): + raise DecomposeNotImplementedError(f'symbolic decomposition not supported for {self}') + for i in range(self.window_size): + z = bb.join(target[-self.bitsize - 1 - i : len(target) - i]) + x[i], y, z = bb.add( + CAdd(QMontgomeryUInt(self.bitsize), QMontgomeryUInt(self.bitsize + 1)), + ctrl=x[i], + a=y, + b=z, + ) + z_arr = bb.split(z) + target[-self.bitsize - 1 - i : len(target) - i] = z_arr + + m = bb.join(target[-self.window_size :], QMontgomeryUInt(self.window_size)) + m, qrom_index = bb.add(Xor(QMontgomeryUInt(self.window_size)), x=m, y=qrom_index) + target[-self.window_size :] = bb.split(m) + + qrom_index, qrom_target, *junk = bb.add(self.qrom, selection=qrom_index) + z = bb.join(target) + qrom_target, z = bb.add( + Add(QMontgomeryUInt(self.bitsize + self.window_size)), a=qrom_target, b=z + ) + if junk: + assert len(junk) == 1 + qrom_index = bb.add( + self.qrom.adjoint(), + selection=qrom_index, + target0_=qrom_target, + junk_target0_=junk[0], + ) + else: + qrom_index = bb.add(self.qrom.adjoint(), selection=qrom_index, target0_=qrom_target) + target_arr = bb.split(z) + target_arr = np.roll(target_arr, self.window_size) + + return {'x': x, 'y': y, 'target': target_arr, 'qrom_index': qrom_index} + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> BloqCountDictT: + return { + CAdd( + QMontgomeryUInt(self.bitsize), QMontgomeryUInt(self.bitsize + 1) + ): self.window_size, + Xor(QMontgomeryUInt(self.window_size)): 1, + Add(QMontgomeryUInt(self.bitsize + self.window_size)): 1, + self.qrom: 1, + self.qrom.adjoint(): 1, + } + + +@frozen +class _DirtyOutOfPlaceMontgomeryModMulImpl(Bloq): + r"""Perform windowed montgomery modular multiplication. + + Applies the trasformation + $$ + \ket{x}\ket{y}\ket{0}\ket{0}\ket{0} \rightarrow \ket{x}\ket{y}\ket{xy2^{-n}}\ket{h}\ket{c} + $$ + + Where: + + - $n$ is the bitsize. + - $x, y$ are in montgomery form + - $h$ is an ancilla register that represents intermediate values. + - $c$ is whether a final modular reduction was applied or not. + + Note: this is an internal implementation class that assumes the target registers (see above) are clean. + + Args: + bitsize: size of the numbers. + window_size: size of the window. + mod: The integer modulus. + uncompute: whether to compute or uncompute. + + Registers: + x: The first integer + y: The second integer + target: product in montgomery form $xy 2^{-n}$ + qrom_indices: concatination of the indicies used to query QROM. + reduced: whether a final modular reduction was applied. + + References: + [Performance Analysis of a Repetition Cat Code Architecture: Computing 256-bit Elliptic Curve Logarithm in 9 Hours with 126 133 Cat Qubits](https://arxiv.org/abs/2302.06639) + Appendix C4. + + [How to compute a 256-bit elliptic curve private key with only 50 million Toffoli gates](https://arxiv.org/abs/2306.08585) + page 8. + """ + + bitsize: 'SymbolicInt' + window_size: 'SymbolicInt' + mod: 'SymbolicInt' + + def __attrs_post_init__(self): + if isinstance(self.mod, int): + assert self.mod > 1 and self.mod % 2 == 1 # Must be an odd integer greater than 1. + + if isinstance(self.mod, int) and isinstance(self.bitsize, int): + assert 2 * self.mod - 1 < 2**self.bitsize, f'bitsize={self.bitsize} is too small' + + if isinstance(self.window_size, int) and isinstance(self.bitsize, int): + assert self.window_size <= self.bitsize + + @cached_property + def signature(self) -> 'Signature': + num_windows = ( + self.bitsize + self.window_size - 1 + ) // self.window_size # = ceil(self.bitsize/self.window_size) + return Signature( + [ + Register('x', QMontgomeryUInt(self.bitsize)), + Register('y', QMontgomeryUInt(self.bitsize)), + Register('target', QMontgomeryUInt(self.bitsize)), + Register('qrom_indices', QMontgomeryUInt(num_windows * self.window_size)), + Register('reduced', QBit()), + ] + ) + + @cached_property + def _window(self): + return SingleWindowModMul(self.window_size, self.bitsize, self.mod) + + def build_composite_bloq( + self, + bb: 'BloqBuilder', + x: Soquet, + y: Soquet, + target: Soquet, + qrom_indices: Soquet, + reduced: Soquet, + ) -> Dict[str, 'SoquetT']: + if is_symbolic(self.window_size) or is_symbolic(self.bitsize) or is_symbolic(self.mod): + raise DecomposeNotImplementedError(f'symbolic decomposition not supported for {self}') + x_arr = bb.split(x) + x_arr = np.flip(x_arr) + + target_arr = np.concatenate([bb.split(bb.allocate(self.window_size)), bb.split(target)]) + qrom_indices_arr = bb.split(qrom_indices) + + for i in range(0, self.bitsize, self.window_size): + (x_arr[i : i + self.window_size], y, target_arr, qrom_index) = bb.add( + self._window, + x=x_arr[i : i + self.window_size], + y=y, + target=target_arr, + qrom_index=bb.join(qrom_indices_arr[i : i + self.window_size]), + ) + qrom_indices_arr[i : i + self.window_size] = bb.split(qrom_index) + + # Free ancillas and join + bb.free(bb.join(target_arr[: -self.bitsize])) + x_arr = np.flip(x_arr[: self.bitsize]) + x = bb.join(x_arr) + qrom_indices = bb.join(qrom_indices_arr) + + # Modular reduction + target = bb.join(target_arr[-self.bitsize :]) + reduced = bb.add(XGate(), q=reduced) + target, reduced = bb.add(LessThanConstant(self.bitsize, self.mod), x=target, target=reduced) + reduced, target = bb.add( + AddK(QUInt(self.bitsize), self.mod).controlled(), ctrl=reduced, x=target + ) + + return {'x': x, 'y': y, 'target': target, 'qrom_indices': qrom_indices, 'reduced': reduced} + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> BloqCountDictT: + num_windows = (self.bitsize + self.window_size - 1) // self.window_size + return { + AddK(QUInt(self.bitsize), self.mod).controlled(): 1, + LessThanConstant(bitsize=self.bitsize, less_than_val=self.mod): 1, + XGate(): 1, + self._window: num_windows, + } + + +@frozen +class DirtyOutOfPlaceMontgomeryModMul(Bloq): + r"""Perform windowed montgomery modular multiplication. + + Applies the trasformation + $$ + \ket{x}\ket{y}\ket{0}\ket{0}\ket{0} \rightarrow \ket{x}\ket{y}\ket{xy2^{-n}}\ket{h}\ket{c} + $$ + + Where: + + - $n$ is the bitsize. + - $x, y$ are in montgomery form + - $h$ is an ancilla register that represents intermidate values. + - $c$ is whether a final modular reduction was applied or not. + + Args: + bitsize: size of the numbers. + window_size: size of the window. + mod: The integer modulus. + uncompute: whether to compute or uncompute. + + Registers: + x: The first integer + y: The second integer + target: product in montgomery form $xy 2^{-n}$ + qrom_indices: concatination of the indicies used to query QROM. + reduced: whether a final modular reduction was applied. + + References: + [Performance Analysis of a Repetition Cat Code Architecture: Computing 256-bit Elliptic Curve Logarithm in 9 Hours with 126 133 Cat Qubits](https://arxiv.org/abs/2302.06639) + Appendix C4. + + [How to compute a 256-bit elliptic curve private key with only 50 million Toffoli gates](https://arxiv.org/abs/2306.08585) + page 8. + """ + + bitsize: 'SymbolicInt' + window_size: 'SymbolicInt' + mod: 'SymbolicInt' + uncompute: bool = False + + def __attrs_post_init__(self): + if isinstance(self.mod, int): + assert self.mod > 1 and self.mod % 2 == 1 # Must be an odd integer greater than 1. + + if isinstance(self.mod, int) and isinstance(self.bitsize, int): + assert 2 * self.mod - 1 < 2**self.bitsize, f'bitsize={self.bitsize} is too small' + + if isinstance(self.window_size, int) and isinstance(self.bitsize, int): + assert self.bitsize % self.window_size == 0 + + @cached_property + def signature(self) -> 'Signature': + num_windows = ( + self.bitsize + self.window_size - 1 + ) // self.window_size # = ceil(self.bitsize/self.window_size) + side = Side.LEFT if self.uncompute else Side.RIGHT + return Signature( + [ + Register('x', QMontgomeryUInt(self.bitsize)), + Register('y', QMontgomeryUInt(self.bitsize)), + Register('target', QMontgomeryUInt(self.bitsize), side=side), + Register( + 'qrom_indices', QMontgomeryUInt(num_windows * self.window_size), side=side + ), + Register('reduced', QBit(), side=side), + ] + ) + + def adjoint(self) -> 'DirtyOutOfPlaceMontgomeryModMul': + return attrs.evolve(self, uncompute=self.uncompute ^ True) + + @cached_property + def _inversion_data(self) -> np.typing.NDArray: + inv_mod = pow(self.mod, 2 ** (self.window_size - 1) - 1, 2**self.window_size) + N = 2**self.window_size + data = (-np.arange(N) * inv_mod) % N + data *= self.mod + return data + + def _classical_action_window( + self, + x: 'ClassicalValT', + y: 'ClassicalValT', + target: 'ClassicalValT', + qrom_indices: 'ClassicalValT', + ): + # This method implements same logic as SingleWindowModMul.on_classical_vals except that it works on integers rather than bit arrays. + # Calls to this function are equivalent to calls to self._window.call_classically given the appropiate conversion int <-> bitarray. + if is_symbolic(self.bitsize) or is_symbolic(self.window_size) or is_symbolic(self.mod): + raise ValueError(f'classical action is not supported for {self}') + for i in range(self.window_size): + if (x >> i) & 1: + target += y << i + m = target & (2**self.window_size - 1) + Tm = self._inversion_data[m] + target += Tm + target >>= self.window_size + qrom_indices = (qrom_indices << self.window_size) | m + return target, qrom_indices + + def on_classical_vals( + self, + x: 'ClassicalValT', + y: 'ClassicalValT', + target: Optional['ClassicalValT'] = None, + qrom_indices: Optional['ClassicalValT'] = None, + reduced: Optional['ClassicalValT'] = None, + ) -> Dict[str, ClassicalValT]: + if is_symbolic(self.bitsize) or is_symbolic(self.window_size) or is_symbolic(self.mod): + raise ValueError(f'classical action is not supported for {self}') + if self.uncompute: + assert ( + target is not None + and target == (x * y * pow(2, self.bitsize * (self.mod - 2), self.mod)) % self.mod + ) + assert qrom_indices is not None + assert reduced is not None + return {'x': x, 'y': y} + assert target is None + assert qrom_indices is None + assert reduced is None + + if not (0 < x < self.mod and 0 < y < self.mod): + return {'x': x, 'y': y, 'target': 0, 'qrom_indices': 0, 'reduced': 0} + + target = 0 + qrom_indices = 0 + reduced = 0 + for i in range(0, self.bitsize, self.window_size): + target, qrom_indices = self._classical_action_window(x >> i, y, target, qrom_indices) + + if target >= self.mod: + target -= self.mod + reduced = 1 + + montgomery_prod = (x * y * pow(2, self.bitsize * (self.mod - 2), self.mod)) % self.mod + assert target == montgomery_prod + return {'x': x, 'y': y, 'target': target, 'qrom_indices': qrom_indices, 'reduced': reduced} + + @cached_property + def _mod_mul_impl(self) -> Bloq: + b: Bloq = _DirtyOutOfPlaceMontgomeryModMulImpl(self.bitsize, self.window_size, self.mod) + if self.uncompute: + b = b.adjoint() + return b + + def build_composite_bloq( + self, + bb: 'BloqBuilder', + x: Soquet, + y: Soquet, + target: Optional[Soquet] = None, + qrom_indices: Optional[Soquet] = None, + reduced: Optional[Soquet] = None, + ) -> Dict[str, 'SoquetT']: + if self.uncompute: + assert target is not None + assert qrom_indices is not None + assert reduced is not None + + x, y, target, qrom_indices, reduced = bb.add_from( # type: ignore + self._mod_mul_impl, + x=x, + y=y, + target=target, + qrom_indices=qrom_indices, + reduced=reduced, + ) + + bb.free(reduced) + bb.free(qrom_indices) + bb.free(target) + return {'x': x, 'y': y} + + target = bb.allocate(self.bitsize, QMontgomeryUInt(self.bitsize)) + num_windows = (self.bitsize + self.window_size - 1) // self.window_size + qrom_indices = bb.allocate( + num_windows * self.window_size, QMontgomeryUInt(num_windows * self.window_size) + ) + reduced = bb.allocate(1) + + x, y, target, qrom_indices, reduced = bb.add_from( + self._mod_mul_impl, x=x, y=y, target=target, qrom_indices=qrom_indices, reduced=reduced + ) + return {'x': x, 'y': y, 'target': target, 'qrom_indices': qrom_indices, 'reduced': reduced} + + def build_call_graph( + self, ssa: 'SympySymbolAllocator' + ) -> Union[Set['BloqCountT'], BloqCountDictT]: + return self._mod_mul_impl.build_call_graph(ssa) + + +@bloq_example(generalizer=[ignore_alloc_free, ignore_split_join]) +def _dirtyoutofplacemontgomerymodmul_small() -> DirtyOutOfPlaceMontgomeryModMul: + dirtyoutofplacemontgomerymodmul_small = DirtyOutOfPlaceMontgomeryModMul(6, 2, 7) + return dirtyoutofplacemontgomerymodmul_small + + +@bloq_example(generalizer=[ignore_alloc_free, ignore_split_join]) +def _dirtyoutofplacemontgomerymodmul_medium() -> DirtyOutOfPlaceMontgomeryModMul: + dirtyoutofplacemontgomerymodmul_medium = DirtyOutOfPlaceMontgomeryModMul( + bitsize=16, window_size=4, mod=2**15 - 1 + ) + return dirtyoutofplacemontgomerymodmul_medium + + +_DIRTY_OUT_OF_PLACE_MONTGOMERY_MOD_MUL_DOC = BloqDocSpec( + bloq_cls=DirtyOutOfPlaceMontgomeryModMul, + examples=(_dirtyoutofplacemontgomerymodmul_small, _dirtyoutofplacemontgomerymodmul_medium), +) diff --git a/qualtran/bloqs/mod_arithmetic/mod_multiplication_test.py b/qualtran/bloqs/mod_arithmetic/mod_multiplication_test.py index c5e16682b..d6a1caa62 100644 --- a/qualtran/bloqs/mod_arithmetic/mod_multiplication_test.py +++ b/qualtran/bloqs/mod_arithmetic/mod_multiplication_test.py @@ -13,6 +13,7 @@ # limitations under the License. import attrs +import numpy as np import pytest import sympy @@ -20,12 +21,16 @@ from qualtran import QMontgomeryUInt, QUInt from qualtran.bloqs.mod_arithmetic.mod_addition import CtrlScaleModAdd from qualtran.bloqs.mod_arithmetic.mod_multiplication import ( + _dirtyoutofplacemontgomerymodmul_medium, + _dirtyoutofplacemontgomerymodmul_small, _moddbl_large, _moddbl_small, _modmul, _modmul_symb, CModMulK, + DirtyOutOfPlaceMontgomeryModMul, ModDbl, + SingleWindowModMul, ) from qualtran.resource_counting import get_cost_value, QECGatesCost, SympySymbolAllocator from qualtran.resource_counting.generalizers import ignore_alloc_free, ignore_split_join @@ -132,6 +137,138 @@ def test_examples_modmul(bloq_autotester): bloq_autotester(_modmul) +def test_examples_dirtyoutofplacemontgomerymodmul_small(bloq_autotester): + bloq_autotester(_dirtyoutofplacemontgomerymodmul_small) + + +def test_examples_dirtyoutofplacemontgomerymodmul_medium(bloq_autotester): + bloq_autotester(_dirtyoutofplacemontgomerymodmul_medium) + + @pytest.mark.notebook def test_notebook(): qlt_testing.execute_notebook('mod_multiplication') + + +@pytest.mark.parametrize('p', (7, 9, 11)) +@pytest.mark.parametrize('uncompute', [True, False]) +@pytest.mark.parametrize( + ['n', 'm'], [(n, m) for n in range(6, 10) for m in range(1, n + 1) if n % m == 0] +) +def test_dirtyoutofplacemontgomerymodmul_decomposition(n, m, p, uncompute): + b = DirtyOutOfPlaceMontgomeryModMul(n, m, p, uncompute) + qlt_testing.assert_valid_bloq_decomposition(b) + + +@pytest.mark.parametrize('p', (7, 9, 11)) +@pytest.mark.parametrize('uncompute', [True, False]) +@pytest.mark.parametrize( + ['n', 'm'], [(n, m) for n in range(6, 10) for m in range(1, n + 1) if n % m == 0] +) +def test_dirtyoutofplacemontgomerymodmul_bloq_counts(n, m, p, uncompute): + b = DirtyOutOfPlaceMontgomeryModMul(n, m, p, uncompute) + qlt_testing.assert_equivalent_bloq_counts(b, [ignore_alloc_free, ignore_split_join]) + + +@pytest.mark.parametrize('uncompute', [True, False]) +def test_dirtyoutofplacemontgomerymodmul_symbolic_cost(uncompute): + n, m, p = sympy.symbols('n m p', integer=True) + + # In Litinski 2023 https://arxiv.org/abs/2306.08585 a window size of 4 is used. + # The cost function generally has floor/ceil division that disappear for bitsize=0 mod 4. + # This is why instead of using bitsize=n directly, we use bitsize=4*m=n. + b = DirtyOutOfPlaceMontgomeryModMul(4 * m, 4, p, uncompute) + cost = get_cost_value(b, QECGatesCost()).total_t_and_ccz_count() + assert cost['n_t'] == 0 + + # Litinski 2023 https://arxiv.org/abs/2306.08585 + # Figure/Table 8. Lists modular multiplication as 2.25n^2+9n toffoli. + # The following formula is 2.25n^2+7.25n-1 written with rationals because sympy comparison fails with floats. + assert isinstance(cost['n_ccz'], sympy.Expr) + assert ( + cost['n_ccz'].subs(m, n / 4).expand() + == sympy.Rational(9, 4) * n**2 + sympy.Rational(29, 4) * n - 1 + ) + + +@pytest.mark.parametrize('p', (3, 5, 7)) +@pytest.mark.parametrize( + ['n', 'm'], [(n, m) for n in range(5, 8) for m in range(1, n + 1) if n % m == 0] +) +def test_dirtyoutofplacemontgomerymodmul_classical_action(n, m, p): + b = DirtyOutOfPlaceMontgomeryModMul(n, m, p, False) + qlt_testing.assert_consistent_classical_action(b, x=range(1, p), y=range(1, p)) + + +@pytest.mark.parametrize('p', (3, 5, 7)) +@pytest.mark.parametrize( + ['n', 'm'], [(n, m) for n in range(5, 8) for m in range(1, n + 1) if n % m == 0] +) +def test_singlewindowmodmul_decomposition(n, m, p): + b = SingleWindowModMul(window_size=m, bitsize=n, mod=p) + qlt_testing.assert_valid_bloq_decomposition(b) + + +@pytest.mark.parametrize('p', (3, 5, 7)) +@pytest.mark.parametrize( + ['n', 'm'], [(n, m) for n in range(5, 8) for m in range(1, n + 1) if n % m == 0] +) +def test_singlewindowmodmul_bloq_counts(n, m, p): + b = SingleWindowModMul(window_size=m, bitsize=n, mod=p) + qlt_testing.assert_equivalent_bloq_counts(b, [ignore_alloc_free, ignore_split_join]) + + +@pytest.mark.slow +@pytest.mark.parametrize('p', (3, 5, 7)) +@pytest.mark.parametrize( + ['n', 'm'], [(n, m) for n in range(5, 8) for m in range(1, n + 1) if n % m == 0] +) +def test_singlewindowmodmul_classical_action(n, m, p): + b = SingleWindowModMul(window_size=m, bitsize=n, mod=p) + cb = b.decompose_bloq() + for x in range(1, min(p, 2**m)): + for y in range(1, p): + for target in range(2**n): + bloq_res = b.call_classically( + x=np.array(QUInt(m).to_bits(x)), + y=y, + target=np.array(QUInt(n + m).to_bits(target)), + qrom_index=0, + ) + decomposed_res = cb.call_classically( + x=np.array(QUInt(m).to_bits(x)), + y=y, + target=np.array(QUInt(n + m).to_bits(target)), + qrom_index=0, + ) + np.testing.assert_equal(bloq_res[0], decomposed_res[0]) # x + assert bloq_res[1] == decomposed_res[1] # y + np.testing.assert_equal(bloq_res[2], decomposed_res[2]) # target + assert bloq_res[3] == decomposed_res[3] # qrom_index + + +def test_singlewindowmodmul_classical_action_fast(): + n = 4 + m = 2 + p = 5 + b = SingleWindowModMul(window_size=m, bitsize=n, mod=p) + cb = b.decompose_bloq() + for x in range(1, min(p, 2**m)): + for y in range(1, p): + for target in range(2**n): + bloq_res = b.call_classically( + x=np.array(QUInt(m).to_bits(x)), + y=y, + target=np.array(QUInt(n + m).to_bits(target)), + qrom_index=0, + ) + decomposed_res = cb.call_classically( + x=np.array(QUInt(m).to_bits(x)), + y=y, + target=np.array(QUInt(n + m).to_bits(target)), + qrom_index=0, + ) + np.testing.assert_equal(bloq_res[0], decomposed_res[0]) # x + assert bloq_res[1] == decomposed_res[1] # y + np.testing.assert_equal(bloq_res[2], decomposed_res[2]) # target + assert bloq_res[3] == decomposed_res[3] # qrom_index diff --git a/qualtran/bloqs/mod_arithmetic/mod_subtraction.py b/qualtran/bloqs/mod_arithmetic/mod_subtraction.py index 01863dc04..c4da9e861 100644 --- a/qualtran/bloqs/mod_arithmetic/mod_subtraction.py +++ b/qualtran/bloqs/mod_arithmetic/mod_subtraction.py @@ -22,6 +22,7 @@ bloq_example, BloqBuilder, BloqDocSpec, + CtrlSpec, DecomposeTypeError, QBit, QMontgomeryUInt, @@ -79,38 +80,28 @@ def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet) -> Dict[str, 'Soque ancilla = bb.allocate(1) ancilla = bb.add(XGate(), q=ancilla) - x_arr = bb.split(x) - x_arr, ancilla = bb.add( - MultiControlX(cvs=[0] * self.dtype.bitsize), controls=x_arr, target=ancilla + x, ancilla = bb.add( + XGate().controlled(CtrlSpec(qdtypes=self.dtype, cvs=0)), ctrl=x, q=ancilla ) - x = bb.join(x_arr) ancilla, x = bb.add(MultiTargetCNOT(self.dtype.bitsize), control=ancilla, targets=x) - (ancilla,), x = bb.add( - AddK(self.dtype.bitsize, self.mod + 1, cvs=(1,), signed=False), ctrls=(ancilla,), x=x + ancilla, x = bb.add( + AddK(QUInt(self.dtype.bitsize), self.mod + 1).controlled(), ctrl=ancilla, x=x ) - x_arr = bb.split(x) - x_arr, ancilla = bb.add( - MultiControlX(cvs=[0] * self.dtype.bitsize).adjoint(), controls=x_arr, target=ancilla + x, ancilla = bb.add( + XGate().controlled(CtrlSpec(qdtypes=self.dtype, cvs=0)), ctrl=x, q=ancilla ) - x = bb.join(x_arr) ancilla = bb.add(XGate(), q=ancilla) bb.free(ancilla) return {'x': x} def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': - cvs: Union[list[int], HasLength] - if isinstance(self.dtype.bitsize, int): - cvs = [0] * self.dtype.bitsize - else: - cvs = HasLength(self.dtype.bitsize) return { - MultiControlX(cvs): 1, - MultiControlX(cvs).adjoint(): 1, + XGate().controlled(CtrlSpec(qdtypes=self.dtype, cvs=0)): 2, MultiTargetCNOT(self.dtype.bitsize): 1, - AddK(self.dtype.bitsize, k=self.mod + 1, cvs=(1), signed=False): 1, + AddK(QUInt(self.dtype.bitsize), k=self.mod + 1).controlled(): 1, XGate(): 2, } @@ -178,9 +169,7 @@ def build_composite_bloq( (ctrl, ancilla), apply_op = bb.add(And(self.cv, 1), ctrl=(ctrl, ancilla)) apply_op, x = bb.add(MultiTargetCNOT(self.dtype.bitsize), control=apply_op, targets=x) - (apply_op,), x = bb.add( - AddK(self.dtype.bitsize, self.mod + 1, cvs=(1,), signed=False), ctrls=(apply_op,), x=x - ) + apply_op, x = bb.add(AddK(self.dtype, self.mod + 1).controlled(), ctrl=apply_op, x=x) ctrl, ancilla = bb.add(And(self.cv, 1).adjoint(), ctrl=(ctrl, ancilla), target=apply_op) @@ -206,7 +195,7 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': And(self.cv, 1): 1, And(self.cv, 1).adjoint(): 1, MultiTargetCNOT(self.dtype.bitsize): 1, - AddK(self.dtype.bitsize, k=self.mod + 1, cvs=(1,), signed=False): 1, + AddK(self.dtype, k=self.mod + 1).controlled(): 1, XGate(): 2, } @@ -279,17 +268,17 @@ def signature(self) -> 'Signature': def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet, y: Soquet) -> Dict[str, 'SoquetT']: x = bb.add(BitwiseNot(self.dtype), x=x) - x = bb.add(AddK(self.dtype.bitsize, self.mod + 1, signed=False), x=x) + x = bb.add(AddK(self.dtype, self.mod + 1), x=x) x, y = bb.add(ModAdd(self.dtype.bitsize, self.mod), x=x, y=y) - x = bb.add(AddK(self.dtype.bitsize, self.mod + 1, signed=False).adjoint(), x=x) + x = bb.add(AddK(self.dtype, self.mod + 1).adjoint(), x=x) x = bb.add(BitwiseNot(self.dtype), x=x) return {'x': x, 'y': y} def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': return { BitwiseNot(self.dtype): 2, - AddK(self.dtype.bitsize, self.mod + 1, signed=False): 1, - AddK(self.dtype.bitsize, self.mod + 1, signed=False).adjoint(): 1, + AddK(self.dtype, self.mod + 1): 1, + AddK(self.dtype, self.mod + 1).adjoint(): 1, ModAdd(self.dtype.bitsize, self.mod): 1, } @@ -359,17 +348,17 @@ def build_composite_bloq( self, bb: 'BloqBuilder', ctrl: Soquet, x: Soquet, y: Soquet ) -> Dict[str, 'SoquetT']: x = bb.add(BitwiseNot(self.dtype), x=x) - x = bb.add(AddK(self.dtype.bitsize, self.mod + 1, signed=False), x=x) + x = bb.add(AddK(self.dtype, self.mod + 1), x=x) ctrl, x, y = bb.add(CModAdd(self.dtype, self.mod, self.cv), ctrl=ctrl, x=x, y=y) - x = bb.add(AddK(self.dtype.bitsize, self.mod + 1, signed=False).adjoint(), x=x) + x = bb.add(AddK(self.dtype, self.mod + 1).adjoint(), x=x) x = bb.add(BitwiseNot(self.dtype), x=x) return {'ctrl': ctrl, 'x': x, 'y': y} def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': return { BitwiseNot(self.dtype): 2, - AddK(self.dtype.bitsize, self.mod + 1, signed=False): 1, - AddK(self.dtype.bitsize, self.mod + 1, signed=False).adjoint(): 1, + AddK(self.dtype, self.mod + 1): 1, + AddK(self.dtype, self.mod + 1).adjoint(): 1, CModAdd(self.dtype, self.mod, self.cv): 1, } diff --git a/qualtran/bloqs/multiplexers/apply_gate_to_lth_target.ipynb b/qualtran/bloqs/multiplexers/apply_gate_to_lth_target.ipynb index a4785f12d..7b733722e 100644 --- a/qualtran/bloqs/multiplexers/apply_gate_to_lth_target.ipynb +++ b/qualtran/bloqs/multiplexers/apply_gate_to_lth_target.ipynb @@ -75,7 +75,7 @@ " - `control_regs`: Control signature for constructing a controlled version of the gate. \n", "\n", "#### References\n", - " - [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). Babbush et. al. (2018). Section III.A. and Figure 7.\n" + " - [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). Babbush et al. (2018). Section III.A. and Figure 7.\n" ] }, { diff --git a/qualtran/bloqs/multiplexers/apply_gate_to_lth_target.py b/qualtran/bloqs/multiplexers/apply_gate_to_lth_target.py index 8238ebbcd..77f70157c 100644 --- a/qualtran/bloqs/multiplexers/apply_gate_to_lth_target.py +++ b/qualtran/bloqs/multiplexers/apply_gate_to_lth_target.py @@ -47,8 +47,9 @@ class ApplyGateToLthQubit(UnaryIterationGate): References: [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). - Babbush et. al. (2018). Section III.A. and Figure 7. + Babbush et al. (2018). Section III.A. and Figure 7. """ + selection_regs: Tuple[Register, ...] = attrs.field( converter=lambda v: (v,) if isinstance(v, Register) else tuple(v) ) diff --git a/qualtran/bloqs/multiplexers/apply_lth_bloq.ipynb b/qualtran/bloqs/multiplexers/apply_lth_bloq.ipynb index 35a038bea..ff8d6499f 100644 --- a/qualtran/bloqs/multiplexers/apply_lth_bloq.ipynb +++ b/qualtran/bloqs/multiplexers/apply_lth_bloq.ipynb @@ -56,7 +56,7 @@ " - `[user_spec]`: The output registers of the bloqs in `ops`. \n", "\n", "#### References\n", - " - [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity]( https://arxiv.org/abs/1805.03662). Babbush et. al. (2018). Section III.A. and Figure 7.\n" + " - [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). Babbush et al. (2018). Section III.A. and Figure 7.\n" ] }, { diff --git a/qualtran/bloqs/multiplexers/apply_lth_bloq.py b/qualtran/bloqs/multiplexers/apply_lth_bloq.py index 003080af1..5ff1e6c74 100644 --- a/qualtran/bloqs/multiplexers/apply_lth_bloq.py +++ b/qualtran/bloqs/multiplexers/apply_lth_bloq.py @@ -17,12 +17,21 @@ import cirq import numpy as np -from attrs import field, frozen +from attrs import evolve, field, frozen from numpy.typing import NDArray -from qualtran import Bloq, bloq_example, BloqDocSpec, BQUInt, QBit, Register, Side +from qualtran import ( + AddControlledT, + Bloq, + bloq_example, + BloqDocSpec, + BQUInt, + CtrlSpec, + QBit, + Register, + Side, +) from qualtran._infra.gate_with_registers import merge_qubits -from qualtran._infra.single_qubit_controlled import SpecializedSingleQubitControlledExtension from qualtran.bloqs.multiplexers.select_base import SelectOracle from qualtran.bloqs.multiplexers.unary_iteration_bloq import UnaryIterationGate from qualtran.resource_counting import BloqCountT @@ -30,7 +39,7 @@ @frozen -class ApplyLthBloq(UnaryIterationGate, SpecializedSingleQubitControlledExtension, SelectOracle): # type: ignore[misc] +class ApplyLthBloq(UnaryIterationGate, SelectOracle): # type: ignore[misc] r"""A SELECT operation that executes one of a list of bloqs $U_l$ based on a quantum index: $$ @@ -51,8 +60,8 @@ class ApplyLthBloq(UnaryIterationGate, SpecializedSingleQubitControlledExtension [user_spec]: The output registers of the bloqs in `ops`. References: - [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity]( - https://arxiv.org/abs/1805.03662). Babbush et. al. (2018). Section III.A. and Figure 7. + [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). + Babbush et al. (2018). Section III.A. and Figure 7. """ # type ignore needed here for Bloq as NDArray parameter @@ -108,6 +117,16 @@ def nth_operation( target_qubits = merge_qubits(bloq.signature, **targets) return bloq.controlled().on(control, *target_qubits) + def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> Tuple['Bloq', 'AddControlledT']: + from qualtran.bloqs.mcmt.specialized_ctrl import get_ctrl_system_1bit_cv + + return get_ctrl_system_1bit_cv( + self, + ctrl_spec=ctrl_spec, + current_ctrl_bit=self.control_val, + get_ctrl_bloq_and_ctrl_reg_name=lambda cv: (evolve(self, control_val=cv), 'control'), + ) + @bloq_example def _apply_lth_bloq() -> ApplyLthBloq: diff --git a/qualtran/bloqs/multiplexers/select_base.py b/qualtran/bloqs/multiplexers/select_base.py index 26ad6d3d6..dc72bbc1a 100644 --- a/qualtran/bloqs/multiplexers/select_base.py +++ b/qualtran/bloqs/multiplexers/select_base.py @@ -39,18 +39,15 @@ class SelectOracle(GateWithRegisters): @property @abc.abstractmethod - def control_registers(self) -> Tuple[Register, ...]: - ... + def control_registers(self) -> Tuple[Register, ...]: ... @property @abc.abstractmethod - def selection_registers(self) -> Tuple[Register, ...]: - ... + def selection_registers(self) -> Tuple[Register, ...]: ... @property @abc.abstractmethod - def target_registers(self) -> Tuple[Register, ...]: - ... + def target_registers(self) -> Tuple[Register, ...]: ... @cached_property def signature(self) -> Signature: diff --git a/qualtran/bloqs/multiplexers/select_pauli_lcu.py b/qualtran/bloqs/multiplexers/select_pauli_lcu.py index 4cbedb324..41e2f8ddb 100644 --- a/qualtran/bloqs/multiplexers/select_pauli_lcu.py +++ b/qualtran/bloqs/multiplexers/select_pauli_lcu.py @@ -22,8 +22,17 @@ import numpy as np from numpy.typing import NDArray -from qualtran import bloq_example, BloqDocSpec, BQUInt, QAny, QBit, Register -from qualtran._infra.single_qubit_controlled import SpecializedSingleQubitControlledExtension +from qualtran import ( + AddControlledT, + Bloq, + bloq_example, + BloqDocSpec, + BQUInt, + CtrlSpec, + QAny, + QBit, + Register, +) from qualtran.bloqs.multiplexers.select_base import SelectOracle from qualtran.bloqs.multiplexers.unary_iteration_bloq import UnaryIterationGate from qualtran.resource_counting.generalizers import ( @@ -39,7 +48,7 @@ def _to_tuple(x: Iterable[cirq.DensePauliString]) -> Sequence[cirq.DensePauliStr @attrs.frozen -class SelectPauliLCU(SelectOracle, UnaryIterationGate, SpecializedSingleQubitControlledExtension): # type: ignore[misc] +class SelectPauliLCU(SelectOracle, UnaryIterationGate): # type: ignore[misc] r"""A SELECT bloq for selecting and applying operators from an array of `PauliString`s. $$ @@ -59,6 +68,7 @@ class SelectPauliLCU(SelectOracle, UnaryIterationGate, SpecializedSingleQubitCon dense pauli string must contain `target_bitsize` terms. control_val: Optional control value. If specified, a singly controlled gate is constructed. """ + selection_bitsize: int target_bitsize: int select_unitaries: Tuple[cirq.DensePauliString, ...] = attrs.field(converter=_to_tuple) @@ -116,6 +126,25 @@ def nth_operation( # type: ignore[override] ps = self.select_unitaries[selection].on(*target) return ps.with_coefficient(np.sign(complex(ps.coefficient).real)).controlled_by(control) + def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> Tuple['Bloq', 'AddControlledT']: + from qualtran.bloqs.mcmt.specialized_ctrl import get_ctrl_system_1bit_cv + + return get_ctrl_system_1bit_cv( + self, + ctrl_spec=ctrl_spec, + current_ctrl_bit=self.control_val, + get_ctrl_bloq_and_ctrl_reg_name=lambda cv: ( + attrs.evolve(self, control_val=cv), + 'control', + ), + ) + + def adjoint(self) -> 'Bloq': + return self + + def _has_unitary_(self): + return True + @bloq_example(generalizer=[cirq_to_bloqs, ignore_split_join, ignore_cliffords]) def _select_pauli_lcu() -> SelectPauliLCU: diff --git a/qualtran/bloqs/multiplexers/unary_iteration_bloq.py b/qualtran/bloqs/multiplexers/unary_iteration_bloq.py index f5b5308b6..1c75868a0 100644 --- a/qualtran/bloqs/multiplexers/unary_iteration_bloq.py +++ b/qualtran/bloqs/multiplexers/unary_iteration_bloq.py @@ -404,7 +404,7 @@ class UnaryIterationGate(GateWithRegisters): References: [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). - Babbush et. al. (2018). Section III.A. + Babbush et al. (2018). Section III.A. """ @cached_property diff --git a/qualtran/bloqs/phase_estimation/kaiser_window_state.ipynb b/qualtran/bloqs/phase_estimation/kaiser_window_state.ipynb index 029c9f8f3..c108729f0 100644 --- a/qualtran/bloqs/phase_estimation/kaiser_window_state.ipynb +++ b/qualtran/bloqs/phase_estimation/kaiser_window_state.ipynb @@ -55,7 +55,7 @@ " - `alpha`: Shape parameter, determines trade-off between main-lobe width and side lobe level. \n", "\n", "#### References\n", - " - [Analyzing Prospects for Quantum Advantage in Topological Data Analysis](https://arxiv.org/abs/2209.13581). Berry et. al. (2022). Appendix D\n" + " - [Analyzing Prospects for Quantum Advantage in Topological Data Analysis](https://arxiv.org/abs/2209.13581). Berry et al. (2022). Appendix D\n" ] }, { diff --git a/qualtran/bloqs/phase_estimation/kaiser_window_state.py b/qualtran/bloqs/phase_estimation/kaiser_window_state.py index b82a2493f..38e851f81 100644 --- a/qualtran/bloqs/phase_estimation/kaiser_window_state.py +++ b/qualtran/bloqs/phase_estimation/kaiser_window_state.py @@ -61,7 +61,7 @@ class KaiserWindowState(QPEWindowStateBase): References: [Analyzing Prospects for Quantum Advantage in Topological Data Analysis](https://arxiv.org/abs/2209.13581). - Berry et. al. (2022). Appendix D + Berry et al. (2022). Appendix D """ bitsize: SymbolicInt diff --git a/qualtran/bloqs/phase_estimation/lp_resource_state.py b/qualtran/bloqs/phase_estimation/lp_resource_state.py index d17866a49..53cadba44 100644 --- a/qualtran/bloqs/phase_estimation/lp_resource_state.py +++ b/qualtran/bloqs/phase_estimation/lp_resource_state.py @@ -106,11 +106,10 @@ class LPResourceState(QPEWindowStateBase): References: - [Optimum phase-shift estimation and the quantum description of the phase - difference](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.54.4564) + [Optimum phase-shift estimation and the quantum description of the phase difference](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.54.4564). - [Encoding Electronic Spectra in Quantum Circuits with Linear T - Complexity](https://arxiv.org/abs/1805.03662) Section II-B + [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). + Section II-B """ bitsize: SymbolicInt 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 0268db0a8..6937510fa 100644 --- a/qualtran/bloqs/phase_estimation/phase_estimation_of_quantum_walk.ipynb +++ b/qualtran/bloqs/phase_estimation/phase_estimation_of_quantum_walk.ipynb @@ -92,7 +92,7 @@ " state_prep = cirq.StatePreparationChannel(get_resource_state(m), name='chi_m')\n", "\n", " yield state_prep.on(*m_qubits)\n", - " yield walk_controlled.on_registers(**walk_regs, control=m_qubits[0])\n", + " yield walk_controlled.on_registers(**walk_regs, ctrl=m_qubits[0])\n", " for i in range(1, m):\n", " yield reflect_controlled.on_registers(control=m_qubits[i], **reflect_regs)\n", " walk = walk ** 2\n", diff --git a/qualtran/bloqs/phase_estimation/qpe_window_state.py b/qualtran/bloqs/phase_estimation/qpe_window_state.py index 8b92a6495..5d6f0c373 100644 --- a/qualtran/bloqs/phase_estimation/qpe_window_state.py +++ b/qualtran/bloqs/phase_estimation/qpe_window_state.py @@ -35,8 +35,7 @@ def m_register(self) -> 'Register': @property @abc.abstractmethod - def m_bits(self) -> SymbolicInt: - ... + def m_bits(self) -> SymbolicInt: ... @attrs.frozen diff --git a/qualtran/bloqs/phase_estimation/qubitization_qpe.ipynb b/qualtran/bloqs/phase_estimation/qubitization_qpe.ipynb index 92ab548b9..b99e4b7c6 100644 --- a/qualtran/bloqs/phase_estimation/qubitization_qpe.ipynb +++ b/qualtran/bloqs/phase_estimation/qubitization_qpe.ipynb @@ -61,7 +61,6 @@ "\n", "#### Parameters\n", " - `walk`: Bloq representing the Qubitization walk operator to run the phase estimation protocol on.\n", - " - `m_bits`: Bitsize of the phase register to be used during phase estimation.\n", " - `ctrl_state_prep`: Bloq to prepare the control state on the phase register. Defaults to `OnEach(self.m_bits, Hadamard())`.\n", " - `qft_inv`: Bloq to apply inverse QFT on the phase register. Defaults to `QFTTextBook(self.m_bits).adjoint()` \n", "\n", @@ -193,6 +192,22 @@ ")" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "49743657", + "metadata": { + "cq.autogen": "QubitizationQPE.qubitization_qpe_ising" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.chemistry.ising.walk_operator import get_walk_operator_for_1d_ising_model\n", + "from qualtran.bloqs.phase_estimation import RectangularWindowState\n", + "\n", + "walk, _ = get_walk_operator_for_1d_ising_model(4, 0.1)\n", + "qubitization_qpe_ising = QubitizationQPE(walk, RectangularWindowState(4))" + ] + }, { "cell_type": "markdown", "id": "b19f9365", @@ -213,8 +228,8 @@ "outputs": [], "source": [ "from qualtran.drawing import show_bloqs\n", - "show_bloqs([qubitization_qpe_hubbard_model_small, qubitization_qpe_sparse_chem, qubitization_qpe_chem_thc],\n", - " ['`qubitization_qpe_hubbard_model_small`', '`qubitization_qpe_sparse_chem`', '`qubitization_qpe_chem_thc`'])" + "show_bloqs([qubitization_qpe_hubbard_model_small, qubitization_qpe_sparse_chem, qubitization_qpe_chem_thc, qubitization_qpe_ising],\n", + " ['`qubitization_qpe_hubbard_model_small`', '`qubitization_qpe_sparse_chem`', '`qubitization_qpe_chem_thc`', '`qubitization_qpe_ising`'])" ] }, { diff --git a/qualtran/bloqs/phase_estimation/qubitization_qpe.py b/qualtran/bloqs/phase_estimation/qubitization_qpe.py index 9cc1b3a8b..39b608ea5 100644 --- a/qualtran/bloqs/phase_estimation/qubitization_qpe.py +++ b/qualtran/bloqs/phase_estimation/qubitization_qpe.py @@ -64,7 +64,6 @@ class QubitizationQPE(GateWithRegisters): Args: walk: Bloq representing the Qubitization walk operator to run the phase estimation protocol on. - m_bits: Bitsize of the phase register to be used during phase estimation. ctrl_state_prep: Bloq to prepare the control state on the phase register. Defaults to `OnEach(self.m_bits, Hadamard())`. qft_inv: Bloq to apply inverse QFT on the phase register. Defaults to @@ -119,7 +118,7 @@ def decompose_from_registers( qpre_reg = quregs['qpe_reg'] yield self.ctrl_state_prep.on(*qpre_reg) - yield walk_controlled.on_registers(**walk_regs, control=qpre_reg[-1]) + yield walk_controlled.on_registers(**walk_regs, ctrl=qpre_reg[-1]) walk = self.walk**2 for i in range(self.m_bits - 2, -1, -1): yield reflect_controlled.on_registers(control=qpre_reg[i], **reflect_regs) @@ -143,6 +142,16 @@ def __str__(self) -> str: return f'QubitizationQPE[{self.m_bits}]' +@bloq_example +def _qubitization_qpe_ising() -> QubitizationQPE: + from qualtran.bloqs.chemistry.ising.walk_operator import get_walk_operator_for_1d_ising_model + from qualtran.bloqs.phase_estimation import RectangularWindowState + + walk, _ = get_walk_operator_for_1d_ising_model(4, 0.1) + qubitization_qpe_ising = QubitizationQPE(walk, RectangularWindowState(4)) + return qubitization_qpe_ising + + @bloq_example def _qubitization_qpe_hubbard_model_small() -> QubitizationQPE: import numpy as np @@ -252,5 +261,6 @@ def _qubitization_qpe_sparse_chem() -> QubitizationQPE: _qubitization_qpe_hubbard_model_small, _qubitization_qpe_sparse_chem, _qubitization_qpe_chem_thc, + _qubitization_qpe_ising, ), ) diff --git a/qualtran/bloqs/phase_estimation/qubitization_qpe_test.py b/qualtran/bloqs/phase_estimation/qubitization_qpe_test.py index 2888ef7da..1db8ff3a9 100644 --- a/qualtran/bloqs/phase_estimation/qubitization_qpe_test.py +++ b/qualtran/bloqs/phase_estimation/qubitization_qpe_test.py @@ -15,12 +15,14 @@ import numpy as np import pytest +from qualtran._infra.gate_with_registers import get_named_qubits from qualtran.bloqs.for_testing.qubitization_walk_test import get_uniform_pauli_qubitized_walk from qualtran.bloqs.phase_estimation.lp_resource_state import LPResourceState from qualtran.bloqs.phase_estimation.qpe_window_state import RectangularWindowState from qualtran.bloqs.phase_estimation.qubitization_qpe import ( _qubitization_qpe_chem_thc, _qubitization_qpe_hubbard_model_small, + _qubitization_qpe_ising, _qubitization_qpe_sparse_chem, QubitizationQPE, ) @@ -29,6 +31,10 @@ from qualtran.testing import execute_notebook +def test_ising_example(bloq_autotester): + bloq_autotester(_qubitization_qpe_ising) + + @pytest.mark.slow def test_qubitization_qpe_bloq_autotester(bloq_autotester): bloq_autotester(_qubitization_qpe_hubbard_model_small) @@ -44,8 +50,9 @@ def test_qubitization_qpe_sparse_chem_bloq_autotester(bloq_autotester): bloq_autotester(_qubitization_qpe_sparse_chem) -@pytest.mark.slow -@pytest.mark.parametrize('num_terms', [2, 3, 4]) +@pytest.mark.parametrize( + 'num_terms', [pytest.param(n, marks=() if n <= 2 else pytest.mark.slow) for n in [2, 3, 4]] +) @pytest.mark.parametrize('use_resource_state', [True, False]) def test_qubitization_phase_estimation_of_walk(num_terms: int, use_resource_state: bool): precision, eps = 5, 0.05 @@ -65,21 +72,45 @@ def test_qubitization_phase_estimation_of_walk(num_terms: int, use_resource_stat state_prep = ( LPResourceState(precision) if use_resource_state else RectangularWindowState(precision) ) - gh = GateHelper(QubitizationQPE(walk, ctrl_state_prep=state_prep)) - qpe_reg, selection, target = (gh.quregs['qpe_reg'], gh.quregs['selection'], gh.quregs['target']) + qpe_bloq = QubitizationQPE(walk, state_prep) + + # TODO cirq simulation seems to fail for controlled `QubitizationWalkOperator`. + # the following code decomposes a few levels till it gets only simulable bloqs. + # https://github.com/quantumlib/Qualtran/issues/1495 + def should_decompose(binst): + from qualtran import Adjoint, Controlled + from qualtran.bloqs.basic_gates import Power + from qualtran.bloqs.qubitization import QubitizationWalkOperator + + bloqs_to_decompose = (QubitizationQPE, QubitizationWalkOperator, Power) + + if binst.bloq_is(bloqs_to_decompose): + return True + + if binst.bloq_is(Controlled) or binst.bloq_is(Adjoint): + return isinstance(binst.bloq.subbloq, bloqs_to_decompose) + + return False + + cbloq = qpe_bloq.as_composite_bloq().flatten(pred=should_decompose) + quregs = get_named_qubits(cbloq.signature.lefts()) + qpe_circuit, quregs = cbloq.to_cirq_circuit_and_quregs(None, **quregs) + for eig_idx, eig_val in enumerate(eigen_values): # Apply QPE to determine eigenvalue for walk operator W on initial state |L>|k> # 2. State preparation for initial eigenstate. L_K = np.kron(L_state, eigen_vectors[:, eig_idx].flatten()) L_K /= abs(np.linalg.norm(L_K)) - prep_L_K = cirq.Circuit(cirq.StatePreparationChannel(L_K).on(*selection, *target)) + prep_L_K = cirq.Circuit( + cirq.StatePreparationChannel(L_K).on(*quregs['selection'], *quregs['target']) + ) # 3. QPE circuit with state prep - qpe_with_init = prep_L_K + gh.circuit + qpe_with_init = prep_L_K + qpe_circuit assert len(qpe_with_init.all_qubits()) < 23 # 4. Estimate theta - theta = simulate_theta_estimate(qpe_with_init, qpe_reg) + theta = simulate_theta_estimate(qpe_with_init, quregs['qpe_reg']) assert 0 <= theta <= 1 # 5. Verify that the estimated phase is correct. @@ -103,3 +134,8 @@ def test_qubitization_phase_estimation_of_walk(num_terms: int, use_resource_stat @pytest.mark.notebook def test_phase_estimation_of_qubitized_hubbard_model(): execute_notebook('phase_estimation_of_quantum_walk') + + +@pytest.mark.notebook +def test_notebook(): + execute_notebook('qubitization_qpe') diff --git a/qualtran/bloqs/phase_estimation/text_book_qpe_test.py b/qualtran/bloqs/phase_estimation/text_book_qpe_test.py index 9a7d4fb72..79159e76e 100644 --- a/qualtran/bloqs/phase_estimation/text_book_qpe_test.py +++ b/qualtran/bloqs/phase_estimation/text_book_qpe_test.py @@ -15,6 +15,8 @@ import numpy as np import pytest +from qualtran import Signature +from qualtran._infra.gate_with_registers import get_named_qubits from qualtran.bloqs.basic_gates import ZPowGate from qualtran.bloqs.for_testing.qubitization_walk_test import get_uniform_pauli_qubitized_walk from qualtran.bloqs.phase_estimation.lp_resource_state import LPResourceState @@ -43,8 +45,9 @@ def test_textbook_phase_estimation_zpow_theta(theta): assert abs(simulate_theta_estimate(circuit, precision_register) - theta) < error_bound -@pytest.mark.slow -@pytest.mark.parametrize('num_terms', [2, 3, 4]) +@pytest.mark.parametrize( + 'num_terms', [pytest.param(n, marks=() if n <= 2 else pytest.mark.slow) for n in [2, 3, 4]] +) @pytest.mark.parametrize('use_resource_state', [True, False]) def test_textbook_phase_estimation_qubitized_walk(num_terms: int, use_resource_state: bool): precision, eps = 5, 0.05 @@ -52,8 +55,8 @@ def test_textbook_phase_estimation_qubitized_walk(num_terms: int, use_resource_s ham_coeff = [abs(ps.coefficient.real) for ps in ham] qubitization_lambda = np.sum(ham_coeff) - g = GateHelper(walk) - L_state = np.zeros(2 ** len(g.quregs['selection'])) + n_select_bits = Signature(walk.selection_registers).n_qubits() + L_state = np.zeros(2**n_select_bits) L_state[: len(ham_coeff)] = np.sqrt(ham_coeff / qubitization_lambda) eigen_values, eigen_vectors = np.linalg.eigh(ham.matrix()) @@ -61,22 +64,46 @@ def test_textbook_phase_estimation_qubitized_walk(num_terms: int, use_resource_s state_prep = ( LPResourceState(precision) if use_resource_state else RectangularWindowState(precision) ) - gh = GateHelper(TextbookQPE(walk, ctrl_state_prep=state_prep)) + # 1. Construct QPE bloq - qpe_reg, selection, target = (gh.quregs['qpe_reg'], gh.quregs['selection'], gh.quregs['target']) + qpe_bloq = TextbookQPE(walk, ctrl_state_prep=state_prep) + + # TODO cirq simulation seems to fail for controlled `QubitizationWalkOperator`. + # the following code decomposes a few levels till it gets only simulable bloqs. + # https://github.com/quantumlib/Qualtran/issues/1495 + def should_decompose(binst): + from qualtran import Adjoint, Controlled + from qualtran.bloqs.basic_gates import Power + from qualtran.bloqs.qubitization import QubitizationWalkOperator + + bloqs_to_decompose = (TextbookQPE, QubitizationWalkOperator, Power) + + if binst.bloq_is(bloqs_to_decompose): + return True + + if binst.bloq_is(Controlled) or binst.bloq_is(Adjoint): + return isinstance(binst.bloq.subbloq, bloqs_to_decompose) + + return False + + cbloq = qpe_bloq.as_composite_bloq().flatten(pred=should_decompose) + quregs = get_named_qubits(cbloq.signature.lefts()) + qpe_circuit, quregs = cbloq.to_cirq_circuit_and_quregs(None, **quregs) for eig_idx, eig_val in enumerate(eigen_values): # Apply QPE to determine eigenvalue for walk operator W on initial state |L>|k> # 2. State preparation for initial eigenstate. L_K = np.kron(L_state, eigen_vectors[:, eig_idx].flatten()) L_K /= abs(np.linalg.norm(L_K)) - prep_L_K = cirq.Circuit(cirq.StatePreparationChannel(L_K).on(*selection, *target)) + prep_L_K = cirq.Circuit( + cirq.StatePreparationChannel(L_K).on(*quregs['selection'], *quregs['target']) + ) # 3. QPE circuit with state prep - qpe_with_init = prep_L_K + gh.circuit + qpe_with_init = prep_L_K + qpe_circuit assert len(qpe_with_init.all_qubits()) < 23 # 4. Estimate theta - theta = simulate_theta_estimate(qpe_with_init, qpe_reg) + theta = simulate_theta_estimate(qpe_with_init, quregs['qpe_reg']) assert 0 <= theta <= 1 # 5. Verify that the estimated phase is correct. diff --git a/qualtran/bloqs/qsp/generalized_qsp_test.py b/qualtran/bloqs/qsp/generalized_qsp_test.py index c64899b19..c925da2a8 100644 --- a/qualtran/bloqs/qsp/generalized_qsp_test.py +++ b/qualtran/bloqs/qsp/generalized_qsp_test.py @@ -201,7 +201,7 @@ def catch_rotations(bloq: Bloq) -> Bloq: expected_sigma: dict[Bloq, int] = {arbitrary_rotation: degree + 1} if degree > negative_power: - expected_sigma[Controlled(U, CtrlSpec(cvs=0))] = degree - negative_power + expected_sigma[U.controlled(ctrl_spec=CtrlSpec(cvs=0))] = degree - negative_power if negative_power > 0: expected_sigma[Controlled(U.adjoint(), CtrlSpec())] = min(degree, negative_power) if negative_power > degree: diff --git a/qualtran/bloqs/qubitization/qubitization_walk_operator.ipynb b/qualtran/bloqs/qubitization/qubitization_walk_operator.ipynb index afeb920f3..a31dafeb4 100644 --- a/qualtran/bloqs/qubitization/qubitization_walk_operator.ipynb +++ b/qualtran/bloqs/qubitization/qubitization_walk_operator.ipynb @@ -143,11 +143,10 @@ "\n", "#### Parameters\n", " - `select`: The SELECT lcu gate implementing $\\mathrm{SELECT}=\\sum_{l}|l\\rangle\\langle l|H_{l}$.\n", - " - `prepare`: Then PREPARE lcu gate implementing $\\mathrm{PREPARE}|0\\dots 0\\rangle = \\sum_l \\sqrt{\\frac{w_{l}}{\\lambda}} |l\\rangle = |L\\rangle$\n", - " - `control_val`: If 0/1, a controlled version of the walk operator is constructed. Defaults to None, in which case the resulting walk operator is not controlled. \n", + " - `prepare`: Then PREPARE lcu gate implementing $\\mathrm{PREPARE}|0\\dots 0\\rangle = \\sum_l \\sqrt{\\frac{w_{l}}{\\lambda}} |l\\rangle = |L\\rangle$ \n", "\n", "#### References\n", - " - [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). Babbush et. al. (2018). Figure 1.\n" + " - [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). Babbush et al. (2018). Figure 1.\n" ] }, { diff --git a/qualtran/bloqs/qubitization/qubitization_walk_operator.py b/qualtran/bloqs/qubitization/qubitization_walk_operator.py index 2d49b9408..9e270d8ec 100644 --- a/qualtran/bloqs/qubitization/qubitization_walk_operator.py +++ b/qualtran/bloqs/qubitization/qubitization_walk_operator.py @@ -27,16 +27,21 @@ """ from functools import cached_property -from typing import Iterator, Optional, Tuple, Union +from typing import Tuple, Union import attrs import cirq import numpy as np -from numpy.typing import NDArray -from qualtran import bloq_example, BloqDocSpec, CtrlSpec, Register, Signature -from qualtran._infra.gate_with_registers import GateWithRegisters, total_bits -from qualtran._infra.single_qubit_controlled import SpecializedSingleQubitControlledExtension +from qualtran import ( + bloq_example, + BloqBuilder, + BloqDocSpec, + GateWithRegisters, + Register, + Signature, + SoquetT, +) from qualtran.bloqs.block_encoding.lcu_block_encoding import ( BlackBoxPrepare, LCUBlockEncoding, @@ -53,7 +58,7 @@ @attrs.frozen(cache_hash=True) -class QubitizationWalkOperator(GateWithRegisters, SpecializedSingleQubitControlledExtension): # type: ignore[misc] +class QubitizationWalkOperator(GateWithRegisters): r"""Construct a Szegedy Quantum Walk operator using LCU oracles SELECT and PREPARE. For a Hamiltonian $H = \sum_l w_l H_l$ (where coefficients $w_l > 0$ and $H_l$ are unitaries), @@ -79,21 +84,13 @@ class QubitizationWalkOperator(GateWithRegisters, SpecializedSingleQubitControll prepare: Then PREPARE lcu gate implementing $\mathrm{PREPARE}|0\dots 0\rangle = \sum_l \sqrt{\frac{w_{l}}{\lambda}} |l\rangle = |L\rangle$ - control_val: If 0/1, a controlled version of the walk operator is constructed. Defaults to - None, in which case the resulting walk operator is not controlled. References: [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). - Babbush et. al. (2018). Figure 1. + Babbush et al. (2018). Figure 1. """ block_encoding: Union[SelectBlockEncoding, LCUBlockEncoding] - control_val: Optional[int] = None - uncompute: bool = False - - @cached_property - def control_registers(self) -> Tuple[Register, ...]: - return self.block_encoding.control_registers @cached_property def selection_registers(self) -> Tuple[Register, ...]: @@ -119,58 +116,30 @@ def junk_registers(self) -> Tuple[Register, ...]: @cached_property def signature(self) -> Signature: - return Signature( - [ - *self.control_registers, - *self.selection_registers, - *self.target_registers, - *self.junk_registers, - ] - ) + return Signature([*self.selection_registers, *self.target_registers, *self.junk_registers]) @cached_property def reflect(self) -> ReflectionUsingPrepare: - return ReflectionUsingPrepare( - self.block_encoding.signal_state, control_val=self.control_val, global_phase=-1 - ) + return ReflectionUsingPrepare(self.block_encoding.signal_state, global_phase=-1) @cached_property def sum_of_lcu_coefficients(self) -> SymbolicFloat: r"""value of $\lambda$, i.e. sum of absolute values of coefficients $w_l$.""" return self.block_encoding.alpha - def decompose_from_registers( - self, - context: cirq.DecompositionContext, - **quregs: NDArray[cirq.Qid], # type:ignore[type-var] - ) -> Iterator[cirq.OP_TREE]: - select_reg = {reg.name: quregs[reg.name] for reg in self.block_encoding.signature} - - reflect_reg = {reg.name: quregs[reg.name] for reg in self.reflect.signature} - if self.uncompute: - yield self.reflect.adjoint().on_registers(**reflect_reg) - yield self.block_encoding.adjoint().on_registers(**select_reg) - else: - yield self.block_encoding.on_registers(**select_reg) - yield self.reflect.on_registers(**reflect_reg) - - def get_single_qubit_controlled_bloq(self, control_val: int) -> 'QubitizationWalkOperator': - assert self.control_val is None - c_block = self.block_encoding.controlled(ctrl_spec=CtrlSpec(cvs=control_val)) - if not isinstance(c_block, (SelectBlockEncoding, LCUBlockEncoding)): - raise TypeError( - f"controlled version of {self.block_encoding} = {c_block} must also be a SelectOracle" - ) - return attrs.evolve(self, block_encoding=c_block, control_val=control_val) + def build_composite_bloq(self, bb: 'BloqBuilder', **soqs: 'SoquetT') -> dict[str, 'SoquetT']: + be_soqs = {reg.name: soqs.pop(reg.name) for reg in self.block_encoding.signature} + soqs |= bb.add_d(self.block_encoding, **be_soqs) + + reflect_soqs = {reg.name: soqs.pop(reg.name) for reg in self.reflect.signature} + soqs |= bb.add_d(self.reflect, **reflect_soqs) + + return soqs def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo: - wire_symbols = ['@' if self.control_val else '@(0)'] * total_bits(self.control_registers) - wire_symbols += ['W'] * (total_bits(self.signature) - total_bits(self.control_registers)) + wire_symbols = ['W'] * self.signature.n_qubits() return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) - def adjoint(self) -> 'QubitizationWalkOperator': - return attrs.evolve(self, uncompute=not self.uncompute) - @cached_property def prepare(self) -> Union[PrepareOracle, BlackBoxPrepare]: """Get the Prepare bloq if appropriate from the block encoding.""" diff --git a/qualtran/bloqs/qubitization/qubitization_walk_operator_test.py b/qualtran/bloqs/qubitization/qubitization_walk_operator_test.py index 37019cf30..69b8da1d5 100644 --- a/qualtran/bloqs/qubitization/qubitization_walk_operator_test.py +++ b/qualtran/bloqs/qubitization/qubitization_walk_operator_test.py @@ -17,7 +17,7 @@ import pytest from qualtran import Adjoint -from qualtran._infra.gate_with_registers import get_named_qubits +from qualtran.bloqs.basic_gates import Power, XGate from qualtran.bloqs.chemistry.ising.walk_operator import get_walk_operator_for_1d_ising_model from qualtran.bloqs.mcmt import MultiControlPauli from qualtran.bloqs.multiplexers.select_pauli_lcu import SelectPauliLCU @@ -107,7 +107,7 @@ def test_qubitization_walk_operator_diagrams(): num_sites, eps = 4, 1e-1 walk, _ = get_walk_operator_for_1d_ising_model(num_sites, eps) # 1. Diagram for $W = SELECT.R_{L}$ - g, qubit_order, walk_circuit = construct_gate_helper_and_qubit_order(walk, decompose_once=True) + walk_circuit = walk.decompose_bloq().to_cirq_circuit() cirq.testing.assert_has_diagram( walk_circuit, ''' @@ -128,14 +128,7 @@ def test_qubitization_walk_operator_diagrams(): ) # 2. Diagram for $W^{2} = B[H].R_{L}.B[H].R_{L}$ - def decompose_twice(op): - ops = [] - for sub_op in cirq.decompose_once(op): - ops += cirq.decompose_once(sub_op) - return ops - - walk_squared_op = (walk**2).on_registers(**g.quregs) - circuit = cirq.Circuit(decompose_twice(walk_squared_op)) + circuit = Power(walk, 2).decompose_bloq().flatten_once().to_cirq_circuit() cirq.testing.assert_has_diagram( circuit, ''' @@ -154,13 +147,14 @@ def decompose_twice(op): target3: ──────B[H]─────────B[H]───────── ''', ) + # 3. Diagram for $Ctrl-W = Ctrl-B[H].Ctrl-R_{L}$ - controlled_walk_op = walk.controlled().on_registers(**g.quregs, control=cirq.q('control')) - circuit = cirq.Circuit(cirq.decompose_once(controlled_walk_op)) + controlled_walk_op = walk.controlled().decompose_bloq() + circuit = controlled_walk_op.to_cirq_circuit() cirq.testing.assert_has_diagram( circuit, ''' -control: ──────@──────@───── +ctrl: ─────────@──────@───── │ │ selection0: ───B[H]───R_L─── │ │ @@ -177,22 +171,17 @@ def decompose_twice(op): target3: ──────B[H]───────── ''', ) - # 4. Diagram for $Ctrl-W = Ctrl-SELECT.Ctrl-R_{L}$ in terms of $Ctrl-SELECT$ and $PREPARE$. - gateset_to_keep = cirq.Gateset( - SelectPauliLCU, StatePreparationAliasSampling, MultiControlPauli, cirq.X - ) - def keep(op): - ret = op in gateset_to_keep - if op.gate is not None and isinstance(op.gate, Adjoint): - ret |= op.gate.subbloq in gateset_to_keep - return ret + # 4. Diagram for $Ctrl-W = Ctrl-SELECT.Ctrl-R_{L}$ in terms of $Ctrl-SELECT$ and $PREPARE$. + def pred(binst): + bloqs_to_keep = (SelectPauliLCU, StatePreparationAliasSampling, MultiControlPauli, XGate) + keep = binst.bloq_is(bloqs_to_keep) + if binst.bloq_is(Adjoint): + keep |= isinstance(binst.bloq.subbloq, bloqs_to_keep) + return not keep greedy_mm = cirq.GreedyQubitManager(prefix="ancilla", maximize_reuse=True) - context = cirq.DecompositionContext(greedy_mm) - circuit = cirq.Circuit( - cirq.decompose(controlled_walk_op, keep=keep, on_stuck_raise=None, context=context) - ) + circuit = controlled_walk_op.flatten(pred=pred).to_cirq_circuit(qubit_manager=greedy_mm) # pylint: disable=line-too-long cirq.testing.assert_has_diagram( circuit, @@ -226,7 +215,7 @@ def keep(op): │ │ ancilla_13: ────────────────────less_than_equal──────────────────────────less_than_equal────────────────── │ │ -control: ──────@────────────────┼───────────────────────────────Z───────Z┼──────────────────────────────── +ctrl: ─────────@────────────────┼───────────────────────────────Z───────Z┼──────────────────────────────── │ │ │ │ selection0: ───In───────────────StatePreparationAliasSampling───@(0)─────StatePreparationAliasSampling──── │ │ │ │ @@ -247,24 +236,6 @@ def keep(op): # pylint: enable=line-too-long -def test_qubitization_walk_operator_consistent_protocols_and_controlled(): - gate, _ = get_walk_operator_for_1d_ising_model(4, 1e-1) - op = gate.on_registers(**get_named_qubits(gate.signature)) - # Build controlled gate - equals_tester = cirq.testing.EqualsTester() - equals_tester.add_equality_group( - gate.controlled(), - gate.controlled(num_controls=1), - gate.controlled(control_values=(1,)), - op.controlled_by(cirq.q("control")).gate, - ) - equals_tester.add_equality_group( - gate.controlled(control_values=(0,)), - gate.controlled(num_controls=1, control_values=(0,)), - op.controlled_by(cirq.q("control"), control_values=(0,)).gate, - ) - - @pytest.mark.notebook def test_notebook(): execute_notebook('qubitization_walk_operator') diff --git a/qualtran/bloqs/reflections/reflection_using_prepare.py b/qualtran/bloqs/reflections/reflection_using_prepare.py index ac2ec1337..a3cd4c2b3 100644 --- a/qualtran/bloqs/reflections/reflection_using_prepare.py +++ b/qualtran/bloqs/reflections/reflection_using_prepare.py @@ -20,9 +20,17 @@ import numpy as np from numpy.typing import NDArray -from qualtran import Bloq, bloq_example, BloqDocSpec, CtrlSpec, QBit, Register, Signature +from qualtran import ( + AddControlledT, + Bloq, + bloq_example, + BloqDocSpec, + CtrlSpec, + QBit, + Register, + Signature, +) from qualtran._infra.gate_with_registers import GateWithRegisters, merge_qubits, total_bits -from qualtran._infra.single_qubit_controlled import SpecializedSingleQubitControlledExtension from qualtran.bloqs.basic_gates.global_phase import GlobalPhase from qualtran.bloqs.basic_gates.x_basis import XGate from qualtran.bloqs.mcmt import MultiControlZ @@ -41,7 +49,7 @@ @attrs.frozen(cache_hash=True) -class ReflectionUsingPrepare(GateWithRegisters, SpecializedSingleQubitControlledExtension): # type: ignore[misc] +class ReflectionUsingPrepare(GateWithRegisters): r"""Applies reflection around a state prepared by `prepare_gate` Applies $R_{s, g=1} = g (I - 2|s\rangle\langle s|)$ using $R_{s} = @@ -76,7 +84,7 @@ class ReflectionUsingPrepare(GateWithRegisters, SpecializedSingleQubitControlled References: [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). - Babbush et. al. (2018). Figure 1. + Babbush et al. 2018. Figure 1. """ prepare_gate: Union['PrepareOracle', 'BlackBoxPrepare'] @@ -186,6 +194,19 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': def adjoint(self) -> 'ReflectionUsingPrepare': return self + def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> Tuple['Bloq', 'AddControlledT']: + from qualtran.bloqs.mcmt.specialized_ctrl import get_ctrl_system_1bit_cv + + return get_ctrl_system_1bit_cv( + self, + ctrl_spec=ctrl_spec, + current_ctrl_bit=self.control_val, + get_ctrl_bloq_and_ctrl_reg_name=lambda cv: ( + attrs.evolve(self, control_val=cv), + 'control', + ), + ) + @bloq_example(generalizer=ignore_split_join) def _refl_using_prep() -> ReflectionUsingPrepare: diff --git a/qualtran/bloqs/reflections/reflections.ipynb b/qualtran/bloqs/reflections/reflections.ipynb index 552e9d7a8..e4d3316f9 100644 --- a/qualtran/bloqs/reflections/reflections.ipynb +++ b/qualtran/bloqs/reflections/reflections.ipynb @@ -66,7 +66,7 @@ " - `eps`: precision for implementation of rotation. Only relevant if global_phase is arbitrary angle and gate is not controlled. \n", "\n", "#### References\n", - " - [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). Babbush et. al. (2018). Figure 1.\n" + " - [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). Babbush et al. 2018. Figure 1.\n" ] }, { diff --git a/qualtran/bloqs/rotations/hamming_weight_phasing.ipynb b/qualtran/bloqs/rotations/hamming_weight_phasing.ipynb index 6d718e094..3279a78ef 100644 --- a/qualtran/bloqs/rotations/hamming_weight_phasing.ipynb +++ b/qualtran/bloqs/rotations/hamming_weight_phasing.ipynb @@ -199,7 +199,7 @@ " - `phase_grad`: Phase gradient THRU register of size `O(log2(1/eps))`, to be used to apply the phasing via addition. \n", "\n", "#### References\n", - " - 1. [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization] (https://arxiv.org/abs/2007.07391), Appendix A: Addition for controlled rotations\n" + " - [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization](https://arxiv.org/abs/2007.07391). Appendix A: Addition for controlled rotations\n" ] }, { diff --git a/qualtran/bloqs/rotations/hamming_weight_phasing.py b/qualtran/bloqs/rotations/hamming_weight_phasing.py index 94239138e..2c55d84b5 100644 --- a/qualtran/bloqs/rotations/hamming_weight_phasing.py +++ b/qualtran/bloqs/rotations/hamming_weight_phasing.py @@ -85,8 +85,7 @@ def build_composite_bloq(self, bb: 'BloqBuilder', **soqs: 'SoquetT') -> Dict[str out = bb.split(out) for i in range(len(out)): out[-(i + 1)] = bb.add( - ZPowGate(exponent=(2**i) * self.exponent, eps=self.eps / len(out)), - q=out[-(i + 1)], + ZPowGate(exponent=(2**i) * self.exponent, eps=self.eps / len(out)), q=out[-(i + 1)] ) out = bb.join(out, dtype=QUInt(self.bitsize.bit_length())) soqs['x'] = bb.add( @@ -155,8 +154,8 @@ class HammingWeightPhasingViaPhaseGradient(GateWithRegisters): apply the phasing via addition. References: - 1. [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization] - (https://arxiv.org/abs/2007.07391), Appendix A: Addition for controlled rotations + [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization](https://arxiv.org/abs/2007.07391). + Appendix A: Addition for controlled rotations """ bitsize: int diff --git a/qualtran/bloqs/rotations/phase_gradient.ipynb b/qualtran/bloqs/rotations/phase_gradient.ipynb index 0245fccf8..ffe411603 100644 --- a/qualtran/bloqs/rotations/phase_gradient.ipynb +++ b/qualtran/bloqs/rotations/phase_gradient.ipynb @@ -70,7 +70,7 @@ "\n", "#### References\n", " - [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization](https://arxiv.org/abs/2007.07391). Appendix A: Addition for controlled rotations\n", - " - [Halving the cost of quantum addition](https://quantum-journal.org/papers/q-2018-06-18-74/pdf/). \n" + " - [Halving the cost of quantum addition](https://arxiv.org/abs/1709.06648). Gidney (2017).\n" ] }, { diff --git a/qualtran/bloqs/rotations/phase_gradient.py b/qualtran/bloqs/rotations/phase_gradient.py index 5a1d481fb..0a6dded65 100644 --- a/qualtran/bloqs/rotations/phase_gradient.py +++ b/qualtran/bloqs/rotations/phase_gradient.py @@ -88,7 +88,8 @@ class PhaseGradientUnitary(GateWithRegisters): [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization](https://arxiv.org/abs/2007.07391) Appendix A: Addition for controlled rotations - [Halving the cost of quantum addition](https://quantum-journal.org/papers/q-2018-06-18-74/pdf/) + [Halving the cost of quantum addition](https://arxiv.org/abs/1709.06648). + Gidney (2017). """ bitsize: 'SymbolicInt' diff --git a/qualtran/bloqs/rotations/phasing_via_cost_function.py b/qualtran/bloqs/rotations/phasing_via_cost_function.py index e82b31a7a..7e4204a0d 100644 --- a/qualtran/bloqs/rotations/phasing_via_cost_function.py +++ b/qualtran/bloqs/rotations/phasing_via_cost_function.py @@ -68,6 +68,7 @@ class PhasingViaCostFunction(Bloq): [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization](https://arxiv.org/abs/2007.07391). Appendix C: Oracles for phasing by cost function """ + cost_eval_oracle: Bloq phase_oracle: QvrInterface diff --git a/qualtran/bloqs/rotations/phasing_via_cost_function_test.py b/qualtran/bloqs/rotations/phasing_via_cost_function_test.py index ebb8bdc18..a554f98fa 100644 --- a/qualtran/bloqs/rotations/phasing_via_cost_function_test.py +++ b/qualtran/bloqs/rotations/phasing_via_cost_function_test.py @@ -180,10 +180,7 @@ def test_square_phasing_via_phase_gradient( initial_state = np.array([1 / np.sqrt(2**n)] * 2**n) normalization_factor = 1 if normalize_cost_function else 4**n phases = np.array( - [ - np.exp(1j * 2 * np.pi * gamma * x**2 * normalization_factor / 4**n) - for x in range(2**n) - ] + [np.exp(1j * 2 * np.pi * gamma * x**2 * normalization_factor / 4**n) for x in range(2**n)] ) expected_final_state = np.multiply(initial_state, phases) test_bloq_one = TestSquarePhasing( diff --git a/qualtran/bloqs/rotations/programmable_ancilla_rotation.py b/qualtran/bloqs/rotations/programmable_ancilla_rotation.py index 07b6097e9..e50445fd1 100644 --- a/qualtran/bloqs/rotations/programmable_ancilla_rotation.py +++ b/qualtran/bloqs/rotations/programmable_ancilla_rotation.py @@ -36,6 +36,7 @@ class ZPowProgrammedAncilla(Bloq): Signature: q: the ancilla qubit prepared in the above state. """ + exponent: SymbolicFloat eps: SymbolicFloat = 1e-11 @@ -98,7 +99,7 @@ class ZPowUsingProgrammedAncilla(Bloq): References: [Simulating chemistry efficiently on fault-tolerant quantum computers](https://arxiv.org/abs/1204.0567) - Jones et. al. 2012. Fig 4. + Jones et al. 2012. Fig 4. """ exponent: SymbolicFloat @@ -164,7 +165,7 @@ def _zpow_using_programmed_ancilla_symb() -> ZPowUsingProgrammedAncilla: References: [Simulating chemistry efficiently on fault-tolerant quantum computers](https://arxiv.org/abs/1204.0567) - Jones et. al. 2012. Fig 4. + Jones et al. 2012. Fig 4. """ phi, eps = sympy.symbols(r"\phi \epsilon") zpow_using_programmed_ancilla_symb = ZPowUsingProgrammedAncilla( @@ -179,7 +180,7 @@ def _zpow_using_programmed_ancilla_symb_rounds() -> ZPowUsingProgrammedAncilla: References: [Simulating chemistry efficiently on fault-tolerant quantum computers](https://arxiv.org/abs/1204.0567) - Jones et. al. 2012. Fig 4. + Jones et al. 2012. Fig 4. """ phi, n = sympy.symbols(r"\phi n") zpow_using_programmed_ancilla_symb_rounds = ZPowUsingProgrammedAncilla( diff --git a/qualtran/bloqs/rotations/programmable_rotation_gate_array.py b/qualtran/bloqs/rotations/programmable_rotation_gate_array.py index 8b59c6bc6..c7c896793 100644 --- a/qualtran/bloqs/rotations/programmable_rotation_gate_array.py +++ b/qualtran/bloqs/rotations/programmable_rotation_gate_array.py @@ -63,7 +63,7 @@ class ProgrammableRotationGateArrayBase(GateWithRegisters): References: [Quantum computing enhanced computational catalysis](https://arxiv.org/abs/2007.14460). - Burg, Low et. al. 2021. Page 45; Section VII.B.1 + Burg, Low, et al. 2021. Page 45; Section VII.B.1 """ def __init__(self, *angles: Sequence[int], kappa: int, rotation_gate: cirq.Gate): diff --git a/qualtran/bloqs/rotations/quantum_variable_rotation.py b/qualtran/bloqs/rotations/quantum_variable_rotation.py index 869882f96..5a254a4b4 100644 --- a/qualtran/bloqs/rotations/quantum_variable_rotation.py +++ b/qualtran/bloqs/rotations/quantum_variable_rotation.py @@ -98,13 +98,11 @@ class QvrInterface(GateWithRegisters, metaclass=abc.ABCMeta): @property @abc.abstractmethod - def cost_registers(self) -> Sequence[Register]: - ... + def cost_registers(self) -> Sequence[Register]: ... @property @abc.abstractmethod - def extra_registers(self) -> Sequence[Register]: - ... + def extra_registers(self) -> Sequence[Register]: ... @cached_property def signature(self) -> Signature: diff --git a/qualtran/bloqs/rotations/rz_via_phase_gradient_test.py b/qualtran/bloqs/rotations/rz_via_phase_gradient_test.py index 0e905a1e5..110583b25 100644 --- a/qualtran/bloqs/rotations/rz_via_phase_gradient_test.py +++ b/qualtran/bloqs/rotations/rz_via_phase_gradient_test.py @@ -17,13 +17,13 @@ from attrs import frozen from qualtran import Bloq, BloqBuilder, QBit, QFxp, QUInt, Signature, Soquet, SoquetT -from qualtran.bloqs.basic_gates import IntState, Rz, TGate +from qualtran.bloqs.basic_gates import IntState, Rz from qualtran.bloqs.rotations.phase_gradient import PhaseGradientState from qualtran.bloqs.rotations.rz_via_phase_gradient import ( _rz_via_phase_gradient, RzViaPhaseGradient, ) -from qualtran.resource_counting import BloqCount, get_cost_value +from qualtran.resource_counting import GateCounts, get_cost_value, QECGatesCost def test_examples(bloq_autotester): @@ -34,8 +34,10 @@ def test_costs(): n = sympy.Symbol("n") dtype = QUInt(n) bloq = RzViaPhaseGradient(angle_dtype=dtype, phasegrad_dtype=dtype) - # TODO need to improve this to `4 * n - 8` (i.e. Toffoli cost of `n - 2`) - assert get_cost_value(bloq, BloqCount.for_gateset('t')) == {TGate(): 4 * n - 4} + # TODO need to improve this to `n - 2` Ands. + assert get_cost_value(bloq, QECGatesCost()) == GateCounts( + and_bloq=n - 1, clifford=9 * n - 8, measurement=n - 1 + ) @frozen diff --git a/qualtran/bloqs/rotations/zpow_via_phase_gradient.py b/qualtran/bloqs/rotations/zpow_via_phase_gradient.py index 5e4e9c412..c1b5ff1d2 100644 --- a/qualtran/bloqs/rotations/zpow_via_phase_gradient.py +++ b/qualtran/bloqs/rotations/zpow_via_phase_gradient.py @@ -62,8 +62,9 @@ class ZPowConstViaPhaseGradient(Bloq): References: [Improved quantum circuits for elliptic curve discrete logarithms](https://arxiv.org/abs/2001.09580). - Haner et. al. 2020. Section 3: Components. "Integer addition" and Fig 2a. + Haner et al. 2020. Section 3: Components. "Integer addition" and Fig 2a. """ + exponent: SymbolicFloat phase_grad_bitsize: SymbolicInt diff --git a/qualtran/bloqs/state_preparation/prepare_base.py b/qualtran/bloqs/state_preparation/prepare_base.py index d918d43d0..6870d7752 100644 --- a/qualtran/bloqs/state_preparation/prepare_base.py +++ b/qualtran/bloqs/state_preparation/prepare_base.py @@ -38,8 +38,7 @@ class PrepareOracle(GateWithRegisters): @property @abc.abstractmethod - def selection_registers(self) -> Tuple[Register, ...]: - ... + def selection_registers(self) -> Tuple[Register, ...]: ... @cached_property def junk_registers(self) -> Tuple[Register, ...]: diff --git a/qualtran/bloqs/state_preparation/sparse_state_preparation_via_rotations.py b/qualtran/bloqs/state_preparation/sparse_state_preparation_via_rotations.py index 37939becf..e5d631263 100644 --- a/qualtran/bloqs/state_preparation/sparse_state_preparation_via_rotations.py +++ b/qualtran/bloqs/state_preparation/sparse_state_preparation_via_rotations.py @@ -13,6 +13,7 @@ # limitations under the License. from typing import Sequence, TYPE_CHECKING, Union +import attrs import numpy as np import sympy from attrs import field, frozen @@ -25,6 +26,7 @@ _to_tuple_or_has_length, StatePreparationViaRotations, ) +from qualtran.resource_counting.generalizers import ignore_split_join from qualtran.symbolics import bit_length, HasLength, is_symbolic, slen, SymbolicInt if TYPE_CHECKING: @@ -51,18 +53,22 @@ class SparseStatePreparationViaRotations(Bloq): References: [A simple quantum algorithm to efficiently prepare sparse states](https://arxiv.org/abs/2310.19309) - Ramacciotti et. al. Section 4 "Permutation Grover-Rudolph". + Ramacciotti et al. Section 4 "Permutation Grover-Rudolph". """ + sparse_indices: Union[tuple[int, ...], HasLength] = field(converter=_to_tuple_or_has_length) nonzero_coeffs: Union[tuple[complex, ...], HasLength] = field(converter=_to_tuple_or_has_length) N: SymbolicInt phase_bitsize: SymbolicInt + target_bitsize: SymbolicInt = field() def __attrs_post_init__(self): n_idx = slen(self.sparse_indices) n_coeff = slen(self.nonzero_coeffs) if not is_symbolic(n_idx, n_coeff) and n_idx != n_coeff: raise ValueError(f"Number of indices {n_idx} must equal number of coeffs {n_coeff}") + if not is_symbolic(self.target_bitsize, self.N): + assert 2**self.target_bitsize >= self.N @property def signature(self) -> Signature: @@ -70,8 +76,8 @@ def signature(self) -> Signature: target_state=QUInt(self.target_bitsize), phase_gradient=QAny(self.phase_bitsize) ) - @property - def target_bitsize(self) -> SymbolicInt: + @target_bitsize.default + def _default_target_bitsize(self) -> SymbolicInt: return bit_length(self.N - 1) @property @@ -159,6 +165,7 @@ def _dense_stateprep_bloq(self) -> StatePreparationViaRotations: dense_coeffs_padded = np.pad( list(self.nonzero_coeffs), (0, 2**self.dense_bitsize - len(self.nonzero_coeffs)) ) + dense_coeffs_padded = dense_coeffs_padded / np.linalg.norm(dense_coeffs_padded) return StatePreparationViaRotations(tuple(dense_coeffs_padded.tolist()), self.phase_bitsize) @property @@ -169,9 +176,10 @@ def _basis_permutation_bloq(self) -> Permutation: assert isinstance(self.sparse_indices, tuple) - return Permutation.from_partial_permutation_map( + permute_bloq = Permutation.from_partial_permutation_map( self.N, dict(enumerate(self.sparse_indices)) ) + return attrs.evolve(permute_bloq, bitsize=self.target_bitsize) def build_composite_bloq( self, bb: 'BloqBuilder', target_state: 'SoquetT', phase_gradient: 'SoquetT' @@ -197,10 +205,24 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': return {self._dense_stateprep_bloq: 1, self._basis_permutation_bloq: 1} -@bloq_example +@bloq_example(generalizer=ignore_split_join) def _sparse_state_prep_via_rotations() -> SparseStatePreparationViaRotations: sparse_state_prep_via_rotations = SparseStatePreparationViaRotations.from_sparse_array( [0.70914953, 0, 0, 0, 0.46943701, 0, 0.2297245, 0, 0, 0.32960471, 0, 0, 0.33959273, 0, 0], phase_bitsize=2, ) return sparse_state_prep_via_rotations + + +@bloq_example(generalizer=ignore_split_join) +def _sparse_state_prep_via_rotations_with_large_target_bitsize() -> ( + SparseStatePreparationViaRotations +): + sparse_state_prep_via_rotations = SparseStatePreparationViaRotations.from_sparse_array( + [0.70914953, 0, 0, 0, 0.46943701, 0, 0.2297245, 0, 0, 0.32960471, 0, 0, 0.33959273, 0, 0], + phase_bitsize=2, + ) + sparse_state_prep_via_rotations_with_large_target_bitsize = attrs.evolve( + sparse_state_prep_via_rotations, target_bitsize=6 + ) + return sparse_state_prep_via_rotations_with_large_target_bitsize diff --git a/qualtran/bloqs/state_preparation/sparse_state_preparation_via_rotations_test.py b/qualtran/bloqs/state_preparation/sparse_state_preparation_via_rotations_test.py index 121d3c781..ea6a6748f 100644 --- a/qualtran/bloqs/state_preparation/sparse_state_preparation_via_rotations_test.py +++ b/qualtran/bloqs/state_preparation/sparse_state_preparation_via_rotations_test.py @@ -11,6 +11,9 @@ # 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. +from typing import Optional + +import attrs import numpy as np import pytest from numpy.typing import NDArray @@ -20,12 +23,17 @@ from qualtran.bloqs.rotations import PhaseGradientState from qualtran.bloqs.state_preparation.sparse_state_preparation_via_rotations import ( _sparse_state_prep_via_rotations, + _sparse_state_prep_via_rotations_with_large_target_bitsize, SparseStatePreparationViaRotations, ) -def test_examples(bloq_autotester): - bloq_autotester(_sparse_state_prep_via_rotations) +@pytest.mark.parametrize( + "bloq_ex", + [_sparse_state_prep_via_rotations, _sparse_state_prep_via_rotations_with_large_target_bitsize], +) +def test_examples(bloq_autotester, bloq_ex): + bloq_autotester(bloq_ex) def get_prepared_state_vector(bloq: SparseStatePreparationViaRotations) -> NDArray[np.complex128]: @@ -40,7 +48,8 @@ def get_prepared_state_vector(bloq: SparseStatePreparationViaRotations) -> NDArr @pytest.mark.slow -def test_prepared_state(): +@pytest.mark.parametrize("target_bitsize", [None, 4, 6]) +def test_prepared_state(target_bitsize: Optional[int]): expected_state = np.array( [ (-0.42677669529663675 - 0.1767766952966366j), @@ -63,6 +72,9 @@ def test_prepared_state(): N = len(expected_state) bloq = SparseStatePreparationViaRotations.from_sparse_array(expected_state, phase_bitsize=3) + if target_bitsize is not None: + bloq = attrs.evolve(bloq, target_bitsize=target_bitsize) + actual_state = get_prepared_state_vector(bloq) np.testing.assert_allclose(np.linalg.norm(actual_state), 1) np.testing.assert_allclose(actual_state[:N], expected_state) diff --git a/qualtran/bloqs/state_preparation/state_preparation_alias_sampling.ipynb b/qualtran/bloqs/state_preparation/state_preparation_alias_sampling.ipynb index 3b06aa946..081add39d 100644 --- a/qualtran/bloqs/state_preparation/state_preparation_alias_sampling.ipynb +++ b/qualtran/bloqs/state_preparation/state_preparation_alias_sampling.ipynb @@ -88,7 +88,7 @@ " - `sum_of_unnormalized_probabilities`: The total of the input unnormalized probabilities, i.e., $\\lambda$. This is used as the `PrepareOracle.l1_norm_of_coeffs` property. \n", "\n", "#### References\n", - " - [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). Babbush et. al. (2018). Section III.D. and Figure 11.\n" + " - [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). Babbush et al. (2018). Section III.D. and Figure 11.\n" ] }, { @@ -241,7 +241,8 @@ "Total space will be $(2 \\log(L) + \\log(d) + 2 \\mu + 1)$ work qubits + $log(L)$ ancillas for QROM.\n", "\n", "#### References\n", - " - [1] [Qubitization of Arbitrary Basis Quantum Chemistry Leveraging Sparsity and Low Rank Factorization](https://arxiv.org/pdf/1902.02134#page=15.30) Berry et al. (2019). Section 5, Eqs. 43, 44. [2] [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). Babbush et al. (2018). Section III.D. and Figure 11.\n" + " - [Qubitization of Arbitrary Basis Quantum Chemistry Leveraging Sparsity and Low Rank Factorization](https://arxiv.org/abs/1902.02134). Berry et al. (2019). Section 5, Eqs. 43, 44.\n", + " - [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). Babbush et al. (2018). Section III.D. and Figure 11.\n" ] }, { diff --git a/qualtran/bloqs/state_preparation/state_preparation_alias_sampling.py b/qualtran/bloqs/state_preparation/state_preparation_alias_sampling.py index 99bc134f0..4850ef95d 100644 --- a/qualtran/bloqs/state_preparation/state_preparation_alias_sampling.py +++ b/qualtran/bloqs/state_preparation/state_preparation_alias_sampling.py @@ -110,7 +110,7 @@ class StatePreparationAliasSampling(PrepareOracle): References: [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). - Babbush et. al. (2018). Section III.D. and Figure 11. + Babbush et al. (2018). Section III.D. and Figure 11. """ selection_registers: Tuple[Register, ...] = attrs.field( @@ -348,11 +348,13 @@ class SparseStatePreparationAliasSampling(PrepareOracle): Total space will be $(2 \log(L) + \log(d) + 2 \mu + 1)$ work qubits + $log(L)$ ancillas for QROM. References: - [1] [Qubitization of Arbitrary Basis Quantum Chemistry Leveraging Sparsity and Low Rank Factorization](https://arxiv.org/pdf/1902.02134#page=15.30) + [Qubitization of Arbitrary Basis Quantum Chemistry Leveraging Sparsity and Low Rank Factorization](https://arxiv.org/abs/1902.02134). Berry et al. (2019). Section 5, Eqs. 43, 44. - [2] [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). + + [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). Babbush et al. (2018). Section III.D. and Figure 11. """ + selection_registers: Tuple[Register, ...] = attrs.field( converter=lambda v: (v,) if isinstance(v, Register) else tuple(v) ) diff --git a/qualtran/bloqs/swap_network/cswap_approx.py b/qualtran/bloqs/swap_network/cswap_approx.py index 5c123202d..7058ff684 100644 --- a/qualtran/bloqs/swap_network/cswap_approx.py +++ b/qualtran/bloqs/swap_network/cswap_approx.py @@ -57,7 +57,7 @@ class CSwapApprox(GateWithRegisters): References: [Trading T-gates for dirty qubits in state preparation and unitary synthesis](https://arxiv.org/abs/1812.00954). - Low et. al. 2018. See Appendix B.2.c. + Low et al. 2018. See Appendix B.2.c. """ bitsize: SymbolicInt diff --git a/qualtran/bloqs/swap_network/multiplexed_cswap.py b/qualtran/bloqs/swap_network/multiplexed_cswap.py index 7872f45fa..d41e2bb20 100644 --- a/qualtran/bloqs/swap_network/multiplexed_cswap.py +++ b/qualtran/bloqs/swap_network/multiplexed_cswap.py @@ -57,6 +57,7 @@ class MultiplexedCSwap(UnaryIterationGate): [Fault-Tolerant Quantum Simulations of Chemistry in First Quantization](https://arxiv.org/abs/2105.12767) page 20 paragraph 2. """ + selection_regs: Tuple[Register, ...] = field( converter=lambda v: (v,) if isinstance(v, Register) else tuple(v) ) diff --git a/qualtran/bloqs/swap_network/swap_network.ipynb b/qualtran/bloqs/swap_network/swap_network.ipynb index 32a164e47..49c22e5c4 100644 --- a/qualtran/bloqs/swap_network/swap_network.ipynb +++ b/qualtran/bloqs/swap_network/swap_network.ipynb @@ -55,7 +55,7 @@ " - `y`: the second register \n", "\n", "#### References\n", - " - [Trading T-gates for dirty qubits in state preparation and unitary synthesis](https://arxiv.org/abs/1812.00954). Low et. al. 2018. See Appendix B.2.c.\n" + " - [Trading T-gates for dirty qubits in state preparation and unitary synthesis](https://arxiv.org/abs/1812.00954). Low et al. 2018. See Appendix B.2.c.\n" ] }, { diff --git a/qualtran/cirq_interop/__init__.py b/qualtran/cirq_interop/__init__.py index f292f268c..7dc4c6069 100644 --- a/qualtran/cirq_interop/__init__.py +++ b/qualtran/cirq_interop/__init__.py @@ -11,11 +11,9 @@ # 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. +# isort:skip_file -"""Bi-directional interop between Qualtran & Cirq using Cirq-FT. - -isort:skip_file -""" +"""Bi-directional interop between Qualtran & Cirq.""" from ._cirq_to_bloq import ( CirqQuregT, diff --git a/qualtran/cirq_interop/_bloq_to_cirq.py b/qualtran/cirq_interop/_bloq_to_cirq.py index 61475b8c8..70f6d122c 100644 --- a/qualtran/cirq_interop/_bloq_to_cirq.py +++ b/qualtran/cirq_interop/_bloq_to_cirq.py @@ -122,10 +122,6 @@ def bloq_on( ) return BloqAsCirqGate(bloq=bloq).on_registers(**all_quregs), out_quregs - def _t_complexity_(self): - """Delegate to the bloq's t complexity.""" - return self._bloq.t_complexity() - def _num_qubits_(self) -> int: return total_bits(self.signature) diff --git a/qualtran/cirq_interop/_bloq_to_cirq_test.py b/qualtran/cirq_interop/_bloq_to_cirq_test.py index 8ecc6a59b..87a76c014 100644 --- a/qualtran/cirq_interop/_bloq_to_cirq_test.py +++ b/qualtran/cirq_interop/_bloq_to_cirq_test.py @@ -21,11 +21,10 @@ from qualtran import Bloq, BloqBuilder, ConnectionT, Signature, Soquet, SoquetT from qualtran._infra.gate_with_registers import get_named_qubits from qualtran.bloqs.basic_gates import Toffoli, XGate, YGate -from qualtran.bloqs.factoring import ModExp +from qualtran.bloqs.factoring.rsa import ModExp from qualtran.bloqs.mcmt.and_bloq import And, MultiAnd from qualtran.bloqs.state_preparation import PrepareUniformSuperposition from qualtran.cirq_interop._bloq_to_cirq import BloqAsCirqGate, CirqQuregT -from qualtran.cirq_interop.t_complexity_protocol import t_complexity_compat from qualtran.testing import execute_notebook if TYPE_CHECKING: @@ -235,7 +234,6 @@ def test_bloq_as_cirq_gate_for_mod_exp(): # newly allocated RIGHT registers in the decomposition to the one's specified by the user # when constructing the original operation (in this case, register `x`). circuit = cirq.Circuit(op, cirq.decompose_once(op)) - assert t_complexity_compat(circuit) == 2 * mod_exp.t_complexity() cirq.testing.assert_has_diagram( circuit, ''' @@ -260,7 +258,6 @@ def test_bloq_as_cirq_gate_for_mod_exp(): # Whereas when directly applying a cirq gate on qubits to get an operations, we need to # specify both input and output registers. circuit = cirq.Circuit(gate.on_registers(**out_regs), decomposed_circuit) - assert t_complexity_compat(circuit) == 2 * mod_exp.t_complexity() # Notice the newly allocated qubits _C(0) and _C(1) for output register x. cirq.testing.assert_has_diagram( circuit, diff --git a/qualtran/cirq_interop/_cirq_to_bloq.py b/qualtran/cirq_interop/_cirq_to_bloq.py index 447227a6b..f71810df9 100644 --- a/qualtran/cirq_interop/_cirq_to_bloq.py +++ b/qualtran/cirq_interop/_cirq_to_bloq.py @@ -47,7 +47,7 @@ split_qubits, ) from qualtran.cirq_interop._interop_qubit_manager import InteropQubitManager -from qualtran.cirq_interop.t_complexity_protocol import _from_directly_countable_cirq, TComplexity +from qualtran.cirq_interop.t_complexity_protocol import _from_directly_countable_cirq from qualtran.resource_counting import CostKey, GateCounts, QECGatesCost if TYPE_CHECKING: @@ -75,8 +75,7 @@ class CirqGateAsBloqBase(GateWithRegisters, metaclass=abc.ABCMeta): @property @abc.abstractmethod - def cirq_gate(self) -> cirq.Gate: - ... + def cirq_gate(self) -> cirq.Gate: ... @cached_property def signature(self) -> 'Signature': @@ -148,12 +147,6 @@ def __str__(self) -> str: def cirq_gate(self) -> cirq.Gate: return self.gate - def _t_complexity_(self) -> 'TComplexity': - t_count = _from_directly_countable_cirq(self.cirq_gate) - if t_count is None: - raise ValueError(f"Cirq gate must be directly countable, not {self.cirq_gate}") - return t_count - def my_static_costs(self, cost_key: 'CostKey'): if isinstance(cost_key, QECGatesCost): t_count = _from_directly_countable_cirq(self.cirq_gate) @@ -264,9 +257,9 @@ def _ensure_in_reg_exists( qubits_to_allocate: List[cirq.Qid] = [q for q in in_reg.qubits if q not in all_mapped_qubits] if qubits_to_allocate: n_alloc = len(qubits_to_allocate) - qreg_to_qvar[ - _QReg(qubits_to_allocate, dtype=QBit() if n_alloc == 1 else QAny(n_alloc)) - ] = bb.allocate(n_alloc) + qreg_to_qvar[_QReg(qubits_to_allocate, dtype=QBit() if n_alloc == 1 else QAny(n_alloc))] = ( + bb.allocate(n_alloc) + ) if in_reg in qreg_to_qvar: # This is the easy case when no split / joins are needed. diff --git a/qualtran/cirq_interop/cirq_interop.ipynb b/qualtran/cirq_interop/cirq_interop.ipynb index 8a2854df8..991f95bd8 100644 --- a/qualtran/cirq_interop/cirq_interop.ipynb +++ b/qualtran/cirq_interop/cirq_interop.ipynb @@ -471,7 +471,7 @@ "metadata": {}, "outputs": [], "source": [ - "from qualtran.bloqs.factoring.mod_exp import ModExp\n", + "from qualtran.bloqs.factoring.rsa import ModExp\n", "from qualtran.drawing import show_bloq\n", "from qualtran.drawing import get_musical_score_data, draw_musical_score\n", "N = 13*17\n", diff --git a/qualtran/cirq_interop/t_complexity_protocol.py b/qualtran/cirq_interop/t_complexity_protocol.py index 00114bf17..64e3da56f 100644 --- a/qualtran/cirq_interop/t_complexity_protocol.py +++ b/qualtran/cirq_interop/t_complexity_protocol.py @@ -11,15 +11,13 @@ # 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 warnings -from typing import Any, Callable, Iterable, Optional, Protocol, Union +from typing import Any, Callable, Iterable, Optional, Union import attrs import cachetools import cirq -from qualtran import Bloq, DecomposeNotImplementedError, DecomposeTypeError -from qualtran.resource_counting import BloqCountT, SympySymbolAllocator +from qualtran import Bloq from qualtran.symbolics import ceil, log2, SymbolicFloat, SymbolicInt from .decompose_protocol import _decompose_once_considering_known_decomposition @@ -72,17 +70,6 @@ def __str__(self) -> str: ) -class SupportsTComplexity(Protocol): - """An object whose TComplexity can be computed. - - An object whose TComplexity can be computed either implements the `_t_complexity_` function - or is of a type that SupportsDecompose. - """ - - def _t_complexity_(self) -> TComplexity: - """Returns the TComplexity.""" - - def _from_explicit_annotation(stc: Any) -> Optional[TComplexity]: """Returns TComplexity of stc by calling `stc._t_complexity_()` method, if it exists.""" estimator = getattr(stc, '_t_complexity_', None) @@ -95,24 +82,6 @@ def _from_explicit_annotation(stc: Any) -> Optional[TComplexity]: return None -def _from_directly_countable_bloqs(bloq: Bloq) -> Optional[TComplexity]: - """Directly count a clifford, T or Rotation (if it is one).""" - from qualtran.bloqs.basic_gates import TGate - from qualtran.resource_counting.classify_bloqs import bloq_is_clifford, bloq_is_rotation - - if isinstance(bloq, TGate): - return TComplexity(t=1) - - if bloq_is_clifford(bloq): - return TComplexity(clifford=1) - - if bloq_is_rotation(bloq): - return TComplexity(rotations=1) - - # Else - return None - - def _from_directly_countable_cirq(stc: Any) -> Optional[TComplexity]: """Directly count a clifford, T or Rotation (if it is one).""" if not isinstance(stc, (cirq.Gate, cirq.Operation)): @@ -156,28 +125,6 @@ def _from_iterable(it: Any) -> Optional[TComplexity]: return t -def _from_bloq_build_call_graph(bloq: Bloq) -> Optional[TComplexity]: - # Uses the depth 1 call graph of Bloq `stc` to recursively compute the complexity. - try: - callee_counts = bloq.build_call_graph(ssa=SympySymbolAllocator()) - except (DecomposeNotImplementedError, DecomposeTypeError): - # We must explicitly catch these exceptions rather than using `get_bloq_callee_counts` - # to distinguish between no decomposition and an empty list of callees. - return None - - ret = TComplexity() - if isinstance(callee_counts, set): - callee_iterator: Iterable[BloqCountT] = callee_counts - else: - callee_iterator = callee_counts.items() - for callee, n in callee_iterator: - r = t_complexity(callee) - if r is None: - return None - ret += n * r - return ret - - def _from_cirq_decomposition(stc: Any) -> Optional[TComplexity]: # Decompose the object and recursively compute the complexity. decomposition = _decompose_once_considering_known_decomposition(stc) @@ -233,19 +180,6 @@ def _t_complexity_for_gate_or_op( return _t_complexity_from_strategies(gate_or_op, strategies) -@cachetools.cached(cachetools.LRUCache(128), key=_get_hash, info=True) -def _t_complexity_for_bloq(bloq: Bloq) -> Optional[TComplexity]: - strategies = [ - _from_explicit_annotation, - _from_bloq_build_call_graph, - _from_directly_countable_bloqs, - ] - return _t_complexity_from_strategies(bloq, strategies) - - -USE_NEW_GATE_COUNTING_FLAG = True - - def t_complexity(bloq: Bloq) -> TComplexity: """Returns the TComplexity of a bloq. @@ -258,17 +192,9 @@ def t_complexity(bloq: Bloq) -> TComplexity: Raises: TypeError: if none of the strategies can derive the t complexity. """ - if USE_NEW_GATE_COUNTING_FLAG: - from qualtran.resource_counting import get_cost_value, QECGatesCost + from qualtran.resource_counting import get_cost_value, QECGatesCost - return get_cost_value(bloq, QECGatesCost(legacy_shims=True)).to_legacy_t_complexity() - - ret = _t_complexity_for_bloq(bloq) - if ret is None: - raise TypeError( - "couldn't compute TComplexity of:\n" f"type: {type(bloq)}\n" f"value: {bloq}" - ) - return ret + return get_cost_value(bloq, QECGatesCost(legacy_shims=True)).to_legacy_t_complexity() def t_complexity_compat(stc: Any) -> TComplexity: @@ -287,18 +213,7 @@ def t_complexity_compat(stc: Any) -> TComplexity: Raises: TypeError: if the methods fails to compute TComplexity. """ - from qualtran.cirq_interop import BloqAsCirqGate - - if isinstance(stc, Bloq): - warnings.warn("Please use `t_complexity`, not the _compat version.") - ret = _t_complexity_for_bloq(stc) - elif isinstance(stc, BloqAsCirqGate): - ret = _t_complexity_for_bloq(stc.bloq) - elif isinstance(stc, cirq.Operation) and isinstance(stc.gate, Bloq): - ret = _t_complexity_for_bloq(stc.gate) - elif isinstance(stc, cirq.Operation) and isinstance(stc.gate, BloqAsCirqGate): - ret = _t_complexity_for_bloq(stc.gate.bloq) - elif isinstance(stc, (cirq.AbstractCircuit, cirq.Moment, list)): + if isinstance(stc, (cirq.AbstractCircuit, cirq.Moment, list)): ret = _from_iterable(stc) elif isinstance(stc, (cirq.Gate, cirq.Operation)): ret = _t_complexity_for_gate_or_op(stc) @@ -308,8 +223,3 @@ def t_complexity_compat(stc: Any) -> TComplexity: if ret is None: raise TypeError("couldn't compute TComplexity of:\n" f"type: {type(stc)}\n" f"value: {stc}") return ret - - -t_complexity.cache_clear = _t_complexity_for_bloq.cache_clear # type: ignore[attr-defined] -t_complexity.cache_info = _t_complexity_for_bloq.cache_info # type: ignore[attr-defined] -t_complexity.cache = _t_complexity_for_bloq.cache # type: ignore[attr-defined] diff --git a/qualtran/cirq_interop/t_complexity_protocol_test.py b/qualtran/cirq_interop/t_complexity_protocol_test.py index 2572b85ec..99496f4d7 100644 --- a/qualtran/cirq_interop/t_complexity_protocol_test.py +++ b/qualtran/cirq_interop/t_complexity_protocol_test.py @@ -11,7 +11,6 @@ # 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. -from typing import Set, TYPE_CHECKING import cirq import pytest @@ -30,12 +29,8 @@ from qualtran.cirq_interop.testing import GateHelper from qualtran.testing import execute_notebook -if TYPE_CHECKING: - from qualtran.resource_counting import BloqCountT, SympySymbolAllocator - -class DoesNotSupportTComplexity: - ... +class DoesNotSupportTComplexity: ... @frozen @@ -67,21 +62,9 @@ def signature(self) -> 'Signature': return Signature.build(q=1) -@frozen -class SupportsTComplexityBloqViaBuildCallGraph(Bloq): - @property - def signature(self) -> 'Signature': - return Signature.build(q=1) - - def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: - return {(SupportsTComplexityGateWithRegisters(), 5)} - - -def test_t_complexity_for_bloq_via_build_call_graph(): - bloq = SupportsTComplexityBloqViaBuildCallGraph() - _, sigma = bloq.call_graph(max_depth=1) - assert sigma != {} - assert t_complexity(bloq) == TComplexity(t=5, clifford=10) +def test_legacy_t_complexity_annotation(): + # Test the deprecated `getattr(.., '_t_complexity_') + assert SupportsTComplexityGateWithRegisters().t_complexity() == TComplexity(t=1, clifford=2) def test_t_complexity_for_bloq_does_not_support(): @@ -101,7 +84,7 @@ def test_t_complexity_compat(): g = GateHelper(SupportsTComplexityGateWithRegisters()) assert g.gate._decompose_with_context_(g.operation.qubits) is NotImplemented # type: ignore[attr-defined] - assert t_complexity(g.gate) == TComplexity(t=1, clifford=2) + assert t_complexity_compat(g.gate) == TComplexity(t=1, clifford=2) assert t_complexity_compat(g.operation) == TComplexity(t=1, clifford=2) assert t_complexity_compat([cirq.T, cirq.X]) == TComplexity(t=1, clifford=1) diff --git a/qualtran/cirq_interop/testing.py b/qualtran/cirq_interop/testing.py index 34428b838..203207f75 100644 --- a/qualtran/cirq_interop/testing.py +++ b/qualtran/cirq_interop/testing.py @@ -20,7 +20,7 @@ import numpy as np from numpy.typing import NDArray -from qualtran import Bloq, DecomposeNotImplementedError, DecomposeTypeError, Signature +from qualtran import Bloq, Signature from qualtran._infra.gate_with_registers import get_named_qubits, merge_qubits from qualtran.cirq_interop import t_complexity_protocol from qualtran.cirq_interop.decompose_protocol import _decompose_once_considering_known_decomposition @@ -126,21 +126,19 @@ def get_circuit_inp_out_cirqsim( def assert_decompose_is_consistent_with_t_complexity(val: Any): + if isinstance(val, Bloq): + raise ValueError( + f"Use `qlt_testing.assert_equivalent_bloq_counts` for testing bloqs: {val}." + ) + t_complexity_method = getattr(val, '_t_complexity_', None) expected = NotImplemented if t_complexity_method is None else t_complexity_method() if expected is NotImplemented or expected is None: raise AssertionError("No consistent t_complexity: no _t_complexity_.") - if isinstance(val, Bloq): - try: - cbloq = val.decompose_bloq() - except (DecomposeNotImplementedError, DecomposeTypeError) as e: - raise AssertionError("No consistent t_complexity: no decomposition.") from e - from_decomposition = t_complexity_protocol._from_bloq_build_call_graph(cbloq) - else: - decomposition = _decompose_once_considering_known_decomposition(val) - if decomposition is None: - raise AssertionError("No consistent t_complexity: no decomposition.") - from_decomposition = t_complexity_protocol._from_iterable(decomposition) + decomposition = _decompose_once_considering_known_decomposition(val) + if decomposition is None: + raise AssertionError("No consistent t_complexity: no decomposition.") + from_decomposition = t_complexity_protocol._from_iterable(decomposition) assert expected == from_decomposition, f'{expected} != {from_decomposition}' diff --git a/qualtran/cirq_interop/testing_test.py b/qualtran/cirq_interop/testing_test.py index 72c75e75c..cc35f145e 100644 --- a/qualtran/cirq_interop/testing_test.py +++ b/qualtran/cirq_interop/testing_test.py @@ -11,14 +11,13 @@ # 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. -from typing import Dict, Iterator +from typing import Iterator import cirq import numpy as np import pytest -from qualtran import Bloq, BloqBuilder, QBit, Register, Side, Signature, SoquetT -from qualtran.bloqs.basic_gates import XGate +from qualtran import QBit, Register, Side, Signature from qualtran.bloqs.mcmt.and_bloq import MultiAnd from qualtran.cirq_interop import testing from qualtran.cirq_interop.t_complexity_protocol import TComplexity @@ -59,9 +58,6 @@ def test_gate_helper(): class DoesNotDecompose(cirq.Operation): - def _t_complexity_(self) -> TComplexity: - return TComplexity(t=1, clifford=2, rotations=3) - @property def qubits(self): return [] @@ -85,23 +81,5 @@ def with_qubits(self, _): pass -class InconsistentDecompostion(Bloq): - @property - def signature(self) -> 'Signature': - return Signature.build(q=1) - - def _t_complexity_(self) -> TComplexity: - return TComplexity(rotations=1) - - def build_composite_bloq(self, bb: 'BloqBuilder', q: 'SoquetT') -> Dict[str, 'SoquetT']: - q = bb.add(XGate(), q=q) - return {'q': q} - - def test_assert_decompose_is_consistent_with_t_complexity(): testing.assert_decompose_is_consistent_with_t_complexity(ConsistentDecompostion()) - - -def test_assert_decompose_is_consistent_with_t_complexity_raises(): - with pytest.raises(AssertionError): - testing.assert_decompose_is_consistent_with_t_complexity(InconsistentDecompostion()) diff --git a/qualtran/conftest.py b/qualtran/conftest.py index 81b05b8c7..28d79a89f 100644 --- a/qualtran/conftest.py +++ b/qualtran/conftest.py @@ -88,8 +88,19 @@ def assert_bloq_example_serializes_for_pytest(bloq_ex: BloqExample): 'qubitization_qpe_chem_thc', # too slow 'walk_op_chem_sparse', 'qubitization_qpe_sparse_chem', # too slow + 'qubitization_qpe_ising', 'trott_unitary', 'symbolic_hamsim_by_gqsp', + 'gf16_addition', # cannot serialize QGF + 'gf2_addition_symbolic', # cannot serialize QGF + 'gf16_add_k', # cannot serialize QGF + 'gf2_add_k_symbolic', # cannot serialize QGF + 'gf16_multiplication', # cannot serialize QGF + 'gf2_multiplication_symbolic', # cannot serialize QGF + 'gf16_square', # cannot serialize QGF + 'gf2_square_symbolic', # cannot serialize QGF + 'gf16_inverse', # cannot serialize QGF + 'gf2_inverse_symbolic', # cannot serialize QGF 'gqsp_1d_ising', 'auto_partition', 'unitary_block_encoding', @@ -106,6 +117,8 @@ def assert_bloq_example_serializes_for_pytest(bloq_ex: BloqExample): 'state_prep_alias_symb', # cannot serialize Shaped 'sparse_matrix_block_encoding', 'sparse_matrix_symb_block_encoding', + 'sparse_matrix_hermitian_block_encoding', + 'sparse_matrix_symb_hermitian_block_encoding', 'sparse_state_prep_alias_symb', # cannot serialize Shaped 'sparse_permutation', # contains nested tuple of inhomogeneous shape 'permutation_cycle_symb', # cannot serialize Shaped @@ -116,6 +129,7 @@ def assert_bloq_example_serializes_for_pytest(bloq_ex: BloqExample): 'state_prep_via_rotation_symb', # cannot serialize HasLength 'state_prep_via_rotation_symb_phasegrad', # cannot serialize Shaped 'sparse_state_prep_via_rotations', # cannot serialize Permutation + 'sparse_state_prep_via_rotations_with_large_target_bitsize', # setting an array element with a sequence. 'explicit_matrix_block_encoding', # cannot serialize AutoPartition 'symmetric_banded_matrix_block_encoding', # cannot serialize AutoPartition 'chebyshev_poly_even', @@ -124,6 +138,9 @@ def assert_bloq_example_serializes_for_pytest(bloq_ex: BloqExample): 'black_box_select', # cannot serialize AutoPartition 'black_box_prepare', # cannot serialize AutoPartition 'kaiser_window_state_symbolic', # Split cannot have a symbolic data type. + 'ctrl_on_symbolic_cv', # cannot serialize Shaped + 'ctrl_on_symbolic_cv_multi', # cannot serialize Shaped + 'ctrl_on_symbolic_n_ctrls', # cannot serialize Shaped ]: pytest.xfail("Skipping serialization test for bloq examples that cannot yet be serialized.") diff --git a/qualtran/drawing/__init__.py b/qualtran/drawing/__init__.py index 6129be710..58dd93470 100644 --- a/qualtran/drawing/__init__.py +++ b/qualtran/drawing/__init__.py @@ -12,10 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Draw and visualize bloqs +# isort:skip_file -isort:skip_file -""" +"""Draw and visualize bloqs.""" from .graphviz import GraphDrawer, PrettyGraphDrawer from .musical_score import ( @@ -39,12 +38,7 @@ from .classical_sim_graph import ClassicalSimGraphDrawer -from .bloq_counts_graph import ( - GraphvizCounts, - GraphvizCallGraph, - format_counts_sigma, - format_counts_graph_markdown, -) +from .bloq_counts_graph import GraphvizCallGraph, format_counts_sigma, format_counts_graph_markdown from ._show_funcs import ( show_bloq, diff --git a/qualtran/drawing/_show_funcs.py b/qualtran/drawing/_show_funcs.py index 5a1657ace..932a78ad6 100644 --- a/qualtran/drawing/_show_funcs.py +++ b/qualtran/drawing/_show_funcs.py @@ -22,7 +22,7 @@ from qualtran import Bloq -from .bloq_counts_graph import format_counts_sigma, GraphvizCallGraph, GraphvizCounts +from .bloq_counts_graph import format_counts_sigma, GraphvizCallGraph from .flame_graph import get_flame_graph_svg_data from .graphviz import PrettyGraphDrawer, TypedGraphDrawer from .musical_score import draw_musical_score, get_musical_score_data @@ -81,15 +81,13 @@ def show_bloqs(bloqs: Sequence['Bloq'], labels: Optional[Sequence[Optional[str]] @overload def show_call_graph( item: 'Bloq', /, *, max_depth: Optional[int] = None, agg_gate_counts: Optional[str] = None -) -> None: - ... +) -> None: ... @overload def show_call_graph( item: 'nx.Graph', /, *, max_depth: Optional[int] = None, agg_gate_counts: Optional[str] = None -) -> None: - ... +) -> None: ... def show_call_graph( @@ -122,7 +120,7 @@ def show_call_graph( ).get_svg() ) else: - IPython.display.display(GraphvizCounts(item).get_svg()) + IPython.display.display(GraphvizCallGraph(item).get_svg()) def show_counts_sigma(sigma: Dict['Bloq', Union[int, 'sympy.Expr']]): diff --git a/qualtran/drawing/bloq_counts_graph.py b/qualtran/drawing/bloq_counts_graph.py index 6240cba47..b6c786c30 100644 --- a/qualtran/drawing/bloq_counts_graph.py +++ b/qualtran/drawing/bloq_counts_graph.py @@ -16,15 +16,14 @@ import abc import html import warnings -from typing import Any, cast, Dict, Iterable, Mapping, Optional, Tuple, TYPE_CHECKING, Union +from typing import Any, cast, Dict, Mapping, Optional, TYPE_CHECKING, Union -import attrs import IPython.display import networkx as nx import pydot import sympy -from qualtran import Bloq, CompositeBloq +from qualtran import Bloq from qualtran.symbolics import SymbolicInt if TYPE_CHECKING: @@ -107,74 +106,6 @@ def get_svg(self) -> IPython.display.SVG: return IPython.display.SVG(self.get_svg_bytes()) -class GraphvizCounts(_CallGraphDrawerBase): - """Draw a bloq call graphs using Graphviz. - - Each node is a bloq with a string label and an automatically-determined - "details" string based on the bloqs attributes. For non-attrs classes, classes with - a large number of fields, or classes where the fields' string representations are long; - the details string will be abbreviated. - - Each edge is labeled with the number of times the "caller" (predecessor) bloq calls the - "callee" (successor) bloq. - - Args: - g: The call graph, from e.g. `Bloq.call_graph()`. - - See Also: - `qualtran.drawing.show_call_graph`, which uses this class under-the-hood. - """ - - def __init__(self, g: nx.DiGraph): - super().__init__(g=g) - - self.max_detail_fields = 5 - self.max_field_val_len = 12 - self.max_detail_len = 200 - - def get_node_title(self, b: Bloq): - return str(b) - - @staticmethod - def abbreviate_field_list( - name_vals: Iterable[Tuple[str, Any]], max_field_val_len: int = 12, max_detail_fields=5 - ): - """Helper function for abbreviating a list of key=value representations. - - This is used by the default `get_node_details`. - """ - - def abbrev(x: str): - # Each field value gets cut off if it's too long. - if len(x) > max_field_val_len: - return x[: max_field_val_len - 4] + ' ...' - return x - - details = [f'{name}={abbrev(repr(val))}' for name, val in name_vals] - if len(details) > max_detail_fields: - # Too many fields gets cut off. - n = len(details) - max_detail_fields + 1 - details = details[: max_detail_fields - 1] + [f'[{n} addtl fields].'] - - return ', '.join(details) - - def get_node_details(self, b: Bloq): - # Special case for composite bloqs. - if isinstance(b, CompositeBloq): - return f'{len(b.bloq_instances)} bloqs...'[: self.max_detail_len] - - # Clumsy truncation if it's not an attrs class, since we can't easily inspect the fields. - if not attrs.has(b.__class__): - return repr(b)[: self.max_detail_len] - - # Otherwise, use the abbreviation function. - return self.abbreviate_field_list( - ((field.name, getattr(b, field.name)) for field in attrs.fields(b.__class__)), - max_field_val_len=self.max_field_val_len, - max_detail_fields=self.max_detail_fields, - )[: self.max_detail_len] - - class GraphvizCallGraph(_CallGraphDrawerBase): """Draw a bloq call graph using Graphviz with additional data. @@ -189,9 +120,6 @@ class GraphvizCallGraph(_CallGraphDrawerBase): This class uses a bloq's `__str__` string to title the bloq. Arbitrary additional tabular data can be provided with `bloq_data`. - This graph drawer is the successor to the `GraphvizCounts` existing drawer, - and will replace `GraphvizCounts` when all bloqs have been migrated to use `__str__()`. - Args: g: The call graph, from e.g. `Bloq.call_graph()`. bloq_data: A mapping from a bloq to a set of key, value pairs to include in a table diff --git a/qualtran/drawing/bloq_counts_graph_test.py b/qualtran/drawing/bloq_counts_graph_test.py index 2b23ceed2..29f9ad98c 100644 --- a/qualtran/drawing/bloq_counts_graph_test.py +++ b/qualtran/drawing/bloq_counts_graph_test.py @@ -12,19 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. import random -import re from typing import List import networkx as nx from qualtran.bloqs.for_testing import TestBloqWithCallGraph from qualtran.bloqs.mcmt.and_bloq import MultiAnd -from qualtran.drawing import ( - format_counts_graph_markdown, - format_counts_sigma, - GraphvizCallGraph, - GraphvizCounts, -) +from qualtran.drawing import format_counts_graph_markdown, format_counts_sigma, GraphvizCallGraph from qualtran.drawing.bloq_counts_graph import _CallGraphDrawerBase from qualtran.resource_counting import get_bloq_call_graph @@ -56,34 +50,6 @@ def test_format_counts_graph_markdown(): ) -def test_graphviz_counts(): - graph, sigma = get_bloq_call_graph(MultiAnd(cvs=(1,) * 6)) - gvc = GraphvizCounts(graph) - - # The main test is in the drawing notebook, so please spot check that. - # Here: we make sure the edge labels are 5, 9 or 4 (see above) - dot_lines = gvc.get_graph().to_string().splitlines() - edge_lines = [line for line in dot_lines if '->' in line] - for line in edge_lines: - ma = re.search(r'label=(\w+)', line) - assert ma is not None, line - i = int(ma.group(1)) - assert i in [5, 9, 4] - - -def test_abbreviate_details(): - namevals = [('x', 5), ('y', 100), ('s', 'a' * 100), ('x1', 1.2), ('x2', 1.3), ('x3', 1.4)] - - assert ( - GraphvizCounts.abbreviate_field_list(namevals) - == "x=5, y=100, s='aaaaaaa ..., x1=1.2, [2 addtl fields]." - ) - assert ( - GraphvizCounts.abbreviate_field_list(namevals[:5]) - == "x=5, y=100, s='aaaaaaa ..., x1=1.2, x2=1.3" - ) - - def _get_node_labels_from_pydot_graph(drawer: _CallGraphDrawerBase) -> List[str]: graph = drawer.get_graph() node_labels = [node.get_label() for node in graph.get_node_list()] diff --git a/qualtran/drawing/drawing_call_graph.ipynb b/qualtran/drawing/drawing_call_graph.ipynb index 25683f2c0..8e8c368bb 100644 --- a/qualtran/drawing/drawing_call_graph.ipynb +++ b/qualtran/drawing/drawing_call_graph.ipynb @@ -30,9 +30,9 @@ "id": "d93b7d58-70e9-4628-99c2-59eff84367b2", "metadata": {}, "source": [ - "## `GraphvizCounts`\n", + "## `GraphvizCallGraph`\n", "\n", - "Under the hood, the above function uses the `GraphvizCounts` class, which describes how to format the display of each bloq and interfaces with Graphviz through `pydot`. " + "Under the hood, the above function uses the `GraphvizCallGraph` class, which describes how to format the display of each bloq and interfaces with Graphviz through `pydot`. " ] }, { @@ -42,38 +42,15 @@ "metadata": {}, "outputs": [], "source": [ - "from qualtran.drawing import GraphvizCounts\n", + "from qualtran.drawing import GraphvizCallGraph\n", "\n", - "drawer = GraphvizCounts(call_graph)\n", + "drawer = GraphvizCallGraph(call_graph)\n", "\n", "# Additional methods are provided like `get_svg_bytes` which can\n", "# be written to a file to save the diagram.\n", "print(drawer.get_svg_bytes()[:400], '...')" ] }, - { - "cell_type": "markdown", - "id": "7ed9f005-4286-4c72-834d-08bb97abc5c0", - "metadata": {}, - "source": [ - "## `GraphvizCallGraph`\n", - "\n", - "In the future, the default drawing style will switch to using `GraphvizCallGraph` to provide standardized control over how bloqs are displayed, and to annotate additional information about each bloq. At present, you must manually invoke this drawing class" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bdfdfb9a-abaf-4b25-95fd-1100a6a4c2c7", - "metadata": {}, - "outputs": [], - "source": [ - "from qualtran.drawing import GraphvizCallGraph\n", - "\n", - "drawer2 = GraphvizCallGraph(call_graph)\n", - "drawer2.get_svg()" - ] - }, { "cell_type": "markdown", "id": "ab093837-1973-4229-a4fa-49b3ac973066", diff --git a/qualtran/linalg/__init__.py b/qualtran/linalg/__init__.py index ce76a8d10..d21658bfb 100644 --- a/qualtran/linalg/__init__.py +++ b/qualtran/linalg/__init__.py @@ -11,3 +11,5 @@ # 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. + +"""Linear algebra routines for building bloqs.""" diff --git a/qualtran/linalg/lcu_util.py b/qualtran/linalg/lcu_util.py index aba500df7..fea17c03d 100644 --- a/qualtran/linalg/lcu_util.py +++ b/qualtran/linalg/lcu_util.py @@ -150,15 +150,13 @@ def _preprocess_for_efficient_roulette_selection( @overload -def sub_bit_prec_from_epsilon(number_of_coefficients: int, precision: float) -> int: - ... +def sub_bit_prec_from_epsilon(number_of_coefficients: int, precision: float) -> int: ... @overload def sub_bit_prec_from_epsilon( number_of_coefficients: SymbolicInt, precision: SymbolicFloat -) -> SymbolicInt: - ... +) -> SymbolicInt: ... def sub_bit_prec_from_epsilon( diff --git a/qualtran/linalg/matrix.py b/qualtran/linalg/matrix.py index cc800bbec..d87eb7e1e 100644 --- a/qualtran/linalg/matrix.py +++ b/qualtran/linalg/matrix.py @@ -13,12 +13,13 @@ # limitations under the License. import numpy as np -from cirq.testing.lin_alg_utils import random_orthogonal from numpy.typing import NDArray def random_hermitian_matrix(dim: int, random_state: np.random.RandomState) -> NDArray: """Return a random Hermitian matrix of given dimension and norm <= 1.""" + from cirq.testing.lin_alg_utils import random_orthogonal + a = random_orthogonal(dim, random_state=random_state) return (a + a.conj().T) / 2 diff --git a/qualtran/protos/bloq.proto b/qualtran/protos/bloq.proto index c5e0bc30a..40e467b56 100644 --- a/qualtran/protos/bloq.proto +++ b/qualtran/protos/bloq.proto @@ -18,6 +18,7 @@ syntax = "proto3"; import "qualtran/protos/annotations.proto"; import "qualtran/protos/args.proto"; +import "qualtran/protos/ec_point.proto"; import "qualtran/protos/registers.proto"; import "qualtran/protos/data_types.proto"; import "qualtran/protos/ctrl_spec.proto"; @@ -49,6 +50,8 @@ message BloqArg { // Ctrl Spec for controlled bloqs CtrlSpec ctrl_spec = 12; Complex complex_val = 13; + // An elliptical curve point for ECC bloqs + ECPoint ec_point = 14; } } diff --git a/qualtran/protos/bloq_pb2.py b/qualtran/protos/bloq_pb2.py index 5397b0d0a..383b347f0 100644 --- a/qualtran/protos/bloq_pb2.py +++ b/qualtran/protos/bloq_pb2.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: qualtran/protos/bloq.proto -# Protobuf Python Version: 5.26.1 +# Protobuf Python Version: 4.25.3 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -14,35 +14,36 @@ from qualtran.protos import annotations_pb2 as qualtran_dot_protos_dot_annotations__pb2 from qualtran.protos import args_pb2 as qualtran_dot_protos_dot_args__pb2 +from qualtran.protos import ec_point_pb2 as qualtran_dot_protos_dot_ec__point__pb2 from qualtran.protos import registers_pb2 as qualtran_dot_protos_dot_registers__pb2 from qualtran.protos import data_types_pb2 as qualtran_dot_protos_dot_data__types__pb2 from qualtran.protos import ctrl_spec_pb2 as qualtran_dot_protos_dot_ctrl__spec__pb2 from qualtran.protos import sympy_pb2 as qualtran_dot_protos_dot_sympy__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1aqualtran/protos/bloq.proto\x12\x08qualtran\x1a!qualtran/protos/annotations.proto\x1a\x1aqualtran/protos/args.proto\x1a\x1fqualtran/protos/registers.proto\x1a qualtran/protos/data_types.proto\x1a\x1fqualtran/protos/ctrl_spec.proto\x1a\x1bqualtran/protos/sympy.proto\"\xa5\x03\n\x07\x42loqArg\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x11\n\x07int_val\x18\x02 \x01(\x03H\x00\x12\x13\n\tfloat_val\x18\x03 \x01(\x01H\x00\x12\x14\n\nstring_val\x18\x04 \x01(\tH\x00\x12$\n\nsympy_expr\x18\x05 \x01(\x0b\x32\x0e.qualtran.TermH\x00\x12$\n\x07ndarray\x18\x06 \x01(\x0b\x32\x11.qualtran.NDArrayH\x00\x12\x11\n\x07subbloq\x18\x07 \x01(\x05H\x00\x12\x18\n\x0e\x63irq_json_gzip\x18\x08 \x01(\x0cH\x00\x12)\n\nqdata_type\x18\t \x01(\x0b\x32\x13.qualtran.QDataTypeH\x00\x12&\n\x08register\x18\n \x01(\x0b\x32\x12.qualtran.RegisterH\x00\x12(\n\tregisters\x18\x0b \x01(\x0b\x32\x13.qualtran.RegistersH\x00\x12\'\n\tctrl_spec\x18\x0c \x01(\x0b\x32\x12.qualtran.CtrlSpecH\x00\x12(\n\x0b\x63omplex_val\x18\r \x01(\x0b\x32\x11.qualtran.ComplexH\x00\x42\x05\n\x03val\"\xe8\x02\n\x0b\x42loqLibrary\x12\x0c\n\x04name\x18\x01 \x01(\t\x12:\n\x05table\x18\x02 \x03(\x0b\x32+.qualtran.BloqLibrary.BloqWithDecomposition\x1a\x8e\x02\n\x15\x42loqWithDecomposition\x12\x0f\n\x07\x62loq_id\x18\x01 \x01(\x05\x12+\n\rdecomposition\x18\x02 \x03(\x0b\x32\x14.qualtran.Connection\x12P\n\x0b\x62loq_counts\x18\x03 \x03(\x0b\x32;.qualtran.BloqLibrary.BloqWithDecomposition.BloqCountsEntry\x12\x1c\n\x04\x62loq\x18\x04 \x01(\x0b\x32\x0e.qualtran.Bloq\x1aG\n\x0f\x42loqCountsEntry\x12\x0b\n\x03key\x18\x01 \x01(\x05\x12#\n\x05value\x18\x02 \x01(\x0b\x32\x14.qualtran.IntOrSympy:\x02\x38\x01\"\x8a\x01\n\x04\x42loq\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x1f\n\x04\x61rgs\x18\x02 \x03(\x0b\x32\x11.qualtran.BloqArg\x12&\n\tregisters\x18\x03 \x01(\x0b\x32\x13.qualtran.Registers\x12+\n\x0ct_complexity\x18\x04 \x01(\x0b\x32\x15.qualtran.TComplexity\"4\n\x0c\x42loqInstance\x12\x13\n\x0binstance_id\x18\x01 \x01(\x05\x12\x0f\n\x07\x62loq_id\x18\x02 \x01(\x05\"\x8d\x01\n\x06Soquet\x12/\n\rbloq_instance\x18\x01 \x01(\x0b\x32\x16.qualtran.BloqInstanceH\x00\x12\x14\n\ndangling_t\x18\x02 \x01(\tH\x00\x12$\n\x08register\x18\x03 \x01(\x0b\x32\x12.qualtran.Register\x12\r\n\x05index\x18\x04 \x03(\x05\x42\x07\n\x05\x62inst\"M\n\nConnection\x12\x1e\n\x04left\x18\x01 \x01(\x0b\x32\x10.qualtran.Soquet\x12\x1f\n\x05right\x18\x02 \x01(\x0b\x32\x10.qualtran.Soquetb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1aqualtran/protos/bloq.proto\x12\x08qualtran\x1a!qualtran/protos/annotations.proto\x1a\x1aqualtran/protos/args.proto\x1a\x1equaltran/protos/ec_point.proto\x1a\x1fqualtran/protos/registers.proto\x1a qualtran/protos/data_types.proto\x1a\x1fqualtran/protos/ctrl_spec.proto\x1a\x1bqualtran/protos/sympy.proto\"\xcc\x03\n\x07\x42loqArg\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x11\n\x07int_val\x18\x02 \x01(\x03H\x00\x12\x13\n\tfloat_val\x18\x03 \x01(\x01H\x00\x12\x14\n\nstring_val\x18\x04 \x01(\tH\x00\x12$\n\nsympy_expr\x18\x05 \x01(\x0b\x32\x0e.qualtran.TermH\x00\x12$\n\x07ndarray\x18\x06 \x01(\x0b\x32\x11.qualtran.NDArrayH\x00\x12\x11\n\x07subbloq\x18\x07 \x01(\x05H\x00\x12\x18\n\x0e\x63irq_json_gzip\x18\x08 \x01(\x0cH\x00\x12)\n\nqdata_type\x18\t \x01(\x0b\x32\x13.qualtran.QDataTypeH\x00\x12&\n\x08register\x18\n \x01(\x0b\x32\x12.qualtran.RegisterH\x00\x12(\n\tregisters\x18\x0b \x01(\x0b\x32\x13.qualtran.RegistersH\x00\x12\'\n\tctrl_spec\x18\x0c \x01(\x0b\x32\x12.qualtran.CtrlSpecH\x00\x12(\n\x0b\x63omplex_val\x18\r \x01(\x0b\x32\x11.qualtran.ComplexH\x00\x12%\n\x08\x65\x63_point\x18\x0e \x01(\x0b\x32\x11.qualtran.ECPointH\x00\x42\x05\n\x03val\"\xe8\x02\n\x0b\x42loqLibrary\x12\x0c\n\x04name\x18\x01 \x01(\t\x12:\n\x05table\x18\x02 \x03(\x0b\x32+.qualtran.BloqLibrary.BloqWithDecomposition\x1a\x8e\x02\n\x15\x42loqWithDecomposition\x12\x0f\n\x07\x62loq_id\x18\x01 \x01(\x05\x12+\n\rdecomposition\x18\x02 \x03(\x0b\x32\x14.qualtran.Connection\x12P\n\x0b\x62loq_counts\x18\x03 \x03(\x0b\x32;.qualtran.BloqLibrary.BloqWithDecomposition.BloqCountsEntry\x12\x1c\n\x04\x62loq\x18\x04 \x01(\x0b\x32\x0e.qualtran.Bloq\x1aG\n\x0f\x42loqCountsEntry\x12\x0b\n\x03key\x18\x01 \x01(\x05\x12#\n\x05value\x18\x02 \x01(\x0b\x32\x14.qualtran.IntOrSympy:\x02\x38\x01\"\x8a\x01\n\x04\x42loq\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x1f\n\x04\x61rgs\x18\x02 \x03(\x0b\x32\x11.qualtran.BloqArg\x12&\n\tregisters\x18\x03 \x01(\x0b\x32\x13.qualtran.Registers\x12+\n\x0ct_complexity\x18\x04 \x01(\x0b\x32\x15.qualtran.TComplexity\"4\n\x0c\x42loqInstance\x12\x13\n\x0binstance_id\x18\x01 \x01(\x05\x12\x0f\n\x07\x62loq_id\x18\x02 \x01(\x05\"\x8d\x01\n\x06Soquet\x12/\n\rbloq_instance\x18\x01 \x01(\x0b\x32\x16.qualtran.BloqInstanceH\x00\x12\x14\n\ndangling_t\x18\x02 \x01(\tH\x00\x12$\n\x08register\x18\x03 \x01(\x0b\x32\x12.qualtran.Register\x12\r\n\x05index\x18\x04 \x03(\x05\x42\x07\n\x05\x62inst\"M\n\nConnection\x12\x1e\n\x04left\x18\x01 \x01(\x0b\x32\x10.qualtran.Soquet\x12\x1f\n\x05right\x18\x02 \x01(\x0b\x32\x10.qualtran.Soquetb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'qualtran.protos.bloq_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - DESCRIPTOR._loaded_options = None - _globals['_BLOQLIBRARY_BLOQWITHDECOMPOSITION_BLOQCOUNTSENTRY']._loaded_options = None +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_BLOQLIBRARY_BLOQWITHDECOMPOSITION_BLOQCOUNTSENTRY']._options = None _globals['_BLOQLIBRARY_BLOQWITHDECOMPOSITION_BLOQCOUNTSENTRY']._serialized_options = b'8\001' - _globals['_BLOQARG']._serialized_start=233 - _globals['_BLOQARG']._serialized_end=654 - _globals['_BLOQLIBRARY']._serialized_start=657 - _globals['_BLOQLIBRARY']._serialized_end=1017 - _globals['_BLOQLIBRARY_BLOQWITHDECOMPOSITION']._serialized_start=747 - _globals['_BLOQLIBRARY_BLOQWITHDECOMPOSITION']._serialized_end=1017 - _globals['_BLOQLIBRARY_BLOQWITHDECOMPOSITION_BLOQCOUNTSENTRY']._serialized_start=946 - _globals['_BLOQLIBRARY_BLOQWITHDECOMPOSITION_BLOQCOUNTSENTRY']._serialized_end=1017 - _globals['_BLOQ']._serialized_start=1020 - _globals['_BLOQ']._serialized_end=1158 - _globals['_BLOQINSTANCE']._serialized_start=1160 - _globals['_BLOQINSTANCE']._serialized_end=1212 - _globals['_SOQUET']._serialized_start=1215 - _globals['_SOQUET']._serialized_end=1356 - _globals['_CONNECTION']._serialized_start=1358 - _globals['_CONNECTION']._serialized_end=1435 + _globals['_BLOQARG']._serialized_start=265 + _globals['_BLOQARG']._serialized_end=725 + _globals['_BLOQLIBRARY']._serialized_start=728 + _globals['_BLOQLIBRARY']._serialized_end=1088 + _globals['_BLOQLIBRARY_BLOQWITHDECOMPOSITION']._serialized_start=818 + _globals['_BLOQLIBRARY_BLOQWITHDECOMPOSITION']._serialized_end=1088 + _globals['_BLOQLIBRARY_BLOQWITHDECOMPOSITION_BLOQCOUNTSENTRY']._serialized_start=1017 + _globals['_BLOQLIBRARY_BLOQWITHDECOMPOSITION_BLOQCOUNTSENTRY']._serialized_end=1088 + _globals['_BLOQ']._serialized_start=1091 + _globals['_BLOQ']._serialized_end=1229 + _globals['_BLOQINSTANCE']._serialized_start=1231 + _globals['_BLOQINSTANCE']._serialized_end=1283 + _globals['_SOQUET']._serialized_start=1286 + _globals['_SOQUET']._serialized_end=1427 + _globals['_CONNECTION']._serialized_start=1429 + _globals['_CONNECTION']._serialized_end=1506 # @@protoc_insertion_point(module_scope) diff --git a/qualtran/protos/bloq_pb2.pyi b/qualtran/protos/bloq_pb2.pyi index f337ce8da..b3481a7aa 100644 --- a/qualtran/protos/bloq_pb2.pyi +++ b/qualtran/protos/bloq_pb2.pyi @@ -16,7 +16,6 @@ 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 builtins import collections.abc import google.protobuf.descriptor @@ -26,13 +25,19 @@ import qualtran.protos.annotations_pb2 import qualtran.protos.args_pb2 import qualtran.protos.ctrl_spec_pb2 import qualtran.protos.data_types_pb2 +import qualtran.protos.ec_point_pb2 import qualtran.protos.registers_pb2 import qualtran.protos.sympy_pb2 -import typing +import sys + +if sys.version_info >= (3, 8): + import typing as typing_extensions +else: + import typing_extensions DESCRIPTOR: google.protobuf.descriptor.FileDescriptor -@typing.final +@typing_extensions.final class BloqArg(google.protobuf.message.Message): DESCRIPTOR: google.protobuf.descriptor.Descriptor @@ -49,40 +54,38 @@ class BloqArg(google.protobuf.message.Message): REGISTERS_FIELD_NUMBER: builtins.int CTRL_SPEC_FIELD_NUMBER: builtins.int COMPLEX_VAL_FIELD_NUMBER: builtins.int + EC_POINT_FIELD_NUMBER: builtins.int name: builtins.str int_val: builtins.int float_val: builtins.float string_val: builtins.str - subbloq: builtins.int - """Integer reference of a subbloq. Assumes access to a BloqLibrary.""" - cirq_json_gzip: builtins.bytes - """Gzipped JSON corresponding to a Cirq object.""" @property def sympy_expr(self) -> qualtran.protos.sympy_pb2.Term: """Sympy expression generated using str(expr).""" - @property def ndarray(self) -> qualtran.protos.args_pb2.NDArray: """N-dimensional numpy array stored as bytes.""" - + subbloq: builtins.int + """Integer reference of a subbloq. Assumes access to a BloqLibrary.""" + cirq_json_gzip: builtins.bytes + """Gzipped JSON corresponding to a Cirq object.""" @property def qdata_type(self) -> qualtran.protos.data_types_pb2.QDataType: """data type""" - @property def register(self) -> qualtran.protos.registers_pb2.Register: """A Register object, accepted as an argument.""" - @property def registers(self) -> qualtran.protos.registers_pb2.Registers: """A repeated list of one or more registers, accepted as arguments.""" - @property def ctrl_spec(self) -> qualtran.protos.ctrl_spec_pb2.CtrlSpec: """Ctrl Spec for controlled bloqs""" - @property def complex_val(self) -> qualtran.protos.args_pb2.Complex: ... + @property + def ec_point(self) -> qualtran.protos.ec_point_pb2.ECPoint: + """An elliptical curve point for ECC bloqs""" def __init__( self, *, @@ -99,14 +102,15 @@ class BloqArg(google.protobuf.message.Message): registers: qualtran.protos.registers_pb2.Registers | None = ..., ctrl_spec: qualtran.protos.ctrl_spec_pb2.CtrlSpec | None = ..., complex_val: qualtran.protos.args_pb2.Complex | None = ..., + ec_point: qualtran.protos.ec_point_pb2.ECPoint | None = ..., ) -> None: ... - def HasField(self, field_name: typing.Literal["cirq_json_gzip", b"cirq_json_gzip", "complex_val", b"complex_val", "ctrl_spec", b"ctrl_spec", "float_val", b"float_val", "int_val", b"int_val", "ndarray", b"ndarray", "qdata_type", b"qdata_type", "register", b"register", "registers", b"registers", "string_val", b"string_val", "subbloq", b"subbloq", "sympy_expr", b"sympy_expr", "val", b"val"]) -> builtins.bool: ... - def ClearField(self, field_name: typing.Literal["cirq_json_gzip", b"cirq_json_gzip", "complex_val", b"complex_val", "ctrl_spec", b"ctrl_spec", "float_val", b"float_val", "int_val", b"int_val", "name", b"name", "ndarray", b"ndarray", "qdata_type", b"qdata_type", "register", b"register", "registers", b"registers", "string_val", b"string_val", "subbloq", b"subbloq", "sympy_expr", b"sympy_expr", "val", b"val"]) -> None: ... - def WhichOneof(self, oneof_group: typing.Literal["val", b"val"]) -> typing.Literal["int_val", "float_val", "string_val", "sympy_expr", "ndarray", "subbloq", "cirq_json_gzip", "qdata_type", "register", "registers", "ctrl_spec", "complex_val"] | None: ... + def HasField(self, field_name: typing_extensions.Literal["cirq_json_gzip", b"cirq_json_gzip", "complex_val", b"complex_val", "ctrl_spec", b"ctrl_spec", "ec_point", b"ec_point", "float_val", b"float_val", "int_val", b"int_val", "ndarray", b"ndarray", "qdata_type", b"qdata_type", "register", b"register", "registers", b"registers", "string_val", b"string_val", "subbloq", b"subbloq", "sympy_expr", b"sympy_expr", "val", b"val"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["cirq_json_gzip", b"cirq_json_gzip", "complex_val", b"complex_val", "ctrl_spec", b"ctrl_spec", "ec_point", b"ec_point", "float_val", b"float_val", "int_val", b"int_val", "name", b"name", "ndarray", b"ndarray", "qdata_type", b"qdata_type", "register", b"register", "registers", b"registers", "string_val", b"string_val", "subbloq", b"subbloq", "sympy_expr", b"sympy_expr", "val", b"val"]) -> None: ... + def WhichOneof(self, oneof_group: typing_extensions.Literal["val", b"val"]) -> typing_extensions.Literal["int_val", "float_val", "string_val", "sympy_expr", "ndarray", "subbloq", "cirq_json_gzip", "qdata_type", "register", "registers", "ctrl_spec", "complex_val", "ec_point"] | None: ... global___BloqArg = BloqArg -@typing.final +@typing_extensions.final class BloqLibrary(google.protobuf.message.Message): """A library of Bloqs. BloqLibrary should be used to represent both primitive Bloqs and composite Bloqs; i.e. Bloqs consisting of other subbloqs, like `CompositeBloq`, @@ -115,13 +119,13 @@ class BloqLibrary(google.protobuf.message.Message): DESCRIPTOR: google.protobuf.descriptor.Descriptor - @typing.final + @typing_extensions.final class BloqWithDecomposition(google.protobuf.message.Message): """Decompositions are specified using integer IDs referencing other Bloqs within this library.""" DESCRIPTOR: google.protobuf.descriptor.Descriptor - @typing.final + @typing_extensions.final class BloqCountsEntry(google.protobuf.message.Message): DESCRIPTOR: google.protobuf.descriptor.Descriptor @@ -136,8 +140,8 @@ class BloqLibrary(google.protobuf.message.Message): key: builtins.int = ..., value: qualtran.protos.args_pb2.IntOrSympy | None = ..., ) -> None: ... - def HasField(self, field_name: typing.Literal["value", b"value"]) -> builtins.bool: ... - def ClearField(self, field_name: typing.Literal["key", b"key", "value", b"value"]) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["value", b"value"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... BLOQ_ID_FIELD_NUMBER: builtins.int DECOMPOSITION_FIELD_NUMBER: builtins.int @@ -148,15 +152,12 @@ class BloqLibrary(google.protobuf.message.Message): @property def decomposition(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___Connection]: """Decomposition of the Bloq as an edge-list.""" - @property def bloq_counts(self) -> google.protobuf.internal.containers.MessageMap[builtins.int, qualtran.protos.args_pb2.IntOrSympy]: """Rough decomposition of the Bloq as bloq-counts.""" - @property def bloq(self) -> global___Bloq: """The Bloq itself.""" - def __init__( self, *, @@ -165,8 +166,8 @@ class BloqLibrary(google.protobuf.message.Message): bloq_counts: collections.abc.Mapping[builtins.int, qualtran.protos.args_pb2.IntOrSympy] | None = ..., bloq: global___Bloq | None = ..., ) -> None: ... - def HasField(self, field_name: typing.Literal["bloq", b"bloq"]) -> builtins.bool: ... - def ClearField(self, field_name: typing.Literal["bloq", b"bloq", "bloq_counts", b"bloq_counts", "bloq_id", b"bloq_id", "decomposition", b"decomposition"]) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["bloq", b"bloq"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["bloq", b"bloq", "bloq_counts", b"bloq_counts", "bloq_id", b"bloq_id", "decomposition", b"decomposition"]) -> None: ... NAME_FIELD_NUMBER: builtins.int TABLE_FIELD_NUMBER: builtins.int @@ -180,11 +181,11 @@ class BloqLibrary(google.protobuf.message.Message): name: builtins.str = ..., table: collections.abc.Iterable[global___BloqLibrary.BloqWithDecomposition] | None = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["name", b"name", "table", b"table"]) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["name", b"name", "table", b"table"]) -> None: ... global___BloqLibrary = BloqLibrary -@typing.final +@typing_extensions.final class Bloq(google.protobuf.message.Message): """Messages to enable efficient description of a BloqLibrary, including Bloq decompositions in terms of other simpler bloqs. @@ -203,15 +204,12 @@ class Bloq(google.protobuf.message.Message): @property def args(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___BloqArg]: """`Args` are used to construct the Bloq.""" - @property def registers(self) -> qualtran.protos.registers_pb2.Registers: """`Registers` specify the signature of the Bloq and are often derived using `args`.""" - @property def t_complexity(self) -> qualtran.protos.annotations_pb2.TComplexity: """Other useful annotations.""" - def __init__( self, *, @@ -220,12 +218,12 @@ class Bloq(google.protobuf.message.Message): registers: qualtran.protos.registers_pb2.Registers | None = ..., t_complexity: qualtran.protos.annotations_pb2.TComplexity | None = ..., ) -> None: ... - def HasField(self, field_name: typing.Literal["registers", b"registers", "t_complexity", b"t_complexity"]) -> builtins.bool: ... - def ClearField(self, field_name: typing.Literal["args", b"args", "name", b"name", "registers", b"registers", "t_complexity", b"t_complexity"]) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["registers", b"registers", "t_complexity", b"t_complexity"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["args", b"args", "name", b"name", "registers", b"registers", "t_complexity", b"t_complexity"]) -> None: ... global___Bloq = Bloq -@typing.final +@typing_extensions.final class BloqInstance(google.protobuf.message.Message): """Specific instance of a Bloq.""" @@ -241,11 +239,11 @@ class BloqInstance(google.protobuf.message.Message): instance_id: builtins.int = ..., bloq_id: builtins.int = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["bloq_id", b"bloq_id", "instance_id", b"instance_id"]) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["bloq_id", b"bloq_id", "instance_id", b"instance_id"]) -> None: ... global___BloqInstance = BloqInstance -@typing.final +@typing_extensions.final class Soquet(google.protobuf.message.Message): """One half of a connection.""" @@ -255,9 +253,9 @@ class Soquet(google.protobuf.message.Message): DANGLING_T_FIELD_NUMBER: builtins.int REGISTER_FIELD_NUMBER: builtins.int INDEX_FIELD_NUMBER: builtins.int - dangling_t: builtins.str @property def bloq_instance(self) -> global___BloqInstance: ... + dangling_t: builtins.str @property def register(self) -> qualtran.protos.registers_pb2.Register: ... @property @@ -270,13 +268,13 @@ class Soquet(google.protobuf.message.Message): register: qualtran.protos.registers_pb2.Register | None = ..., index: collections.abc.Iterable[builtins.int] | None = ..., ) -> None: ... - def HasField(self, field_name: typing.Literal["binst", b"binst", "bloq_instance", b"bloq_instance", "dangling_t", b"dangling_t", "register", b"register"]) -> builtins.bool: ... - def ClearField(self, field_name: typing.Literal["binst", b"binst", "bloq_instance", b"bloq_instance", "dangling_t", b"dangling_t", "index", b"index", "register", b"register"]) -> None: ... - def WhichOneof(self, oneof_group: typing.Literal["binst", b"binst"]) -> typing.Literal["bloq_instance", "dangling_t"] | None: ... + def HasField(self, field_name: typing_extensions.Literal["binst", b"binst", "bloq_instance", b"bloq_instance", "dangling_t", b"dangling_t", "register", b"register"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["binst", b"binst", "bloq_instance", b"bloq_instance", "dangling_t", b"dangling_t", "index", b"index", "register", b"register"]) -> None: ... + def WhichOneof(self, oneof_group: typing_extensions.Literal["binst", b"binst"]) -> typing_extensions.Literal["bloq_instance", "dangling_t"] | None: ... global___Soquet = Soquet -@typing.final +@typing_extensions.final class Connection(google.protobuf.message.Message): """A connection between two Soquets. Quantum compute graph can be represented as a list of connections. @@ -296,7 +294,7 @@ class Connection(google.protobuf.message.Message): left: global___Soquet | None = ..., right: global___Soquet | None = ..., ) -> None: ... - def HasField(self, field_name: typing.Literal["left", b"left", "right", b"right"]) -> builtins.bool: ... - def ClearField(self, field_name: typing.Literal["left", b"left", "right", b"right"]) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["left", b"left", "right", b"right"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["left", b"left", "right", b"right"]) -> None: ... global___Connection = Connection diff --git a/qualtran/protos/ec_point.proto b/qualtran/protos/ec_point.proto new file mode 100644 index 000000000..c57fa1b53 --- /dev/null +++ b/qualtran/protos/ec_point.proto @@ -0,0 +1,25 @@ +/* + 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. +*/ + +syntax = "proto3"; + +import "qualtran/protos/args.proto"; + +package qualtran; + +message ECPoint { + IntOrSympy x = 1; + IntOrSympy y = 2; + IntOrSympy mod = 3; + optional IntOrSympy curve_a = 4; +} diff --git a/qualtran/protos/ec_point_pb2.py b/qualtran/protos/ec_point_pb2.py new file mode 100644 index 000000000..f28f2c110 --- /dev/null +++ b/qualtran/protos/ec_point_pb2.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: qualtran/protos/ec_point.proto +# Protobuf Python Version: 4.25.3 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from qualtran.protos import args_pb2 as qualtran_dot_protos_dot_args__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1equaltran/protos/ec_point.proto\x12\x08qualtran\x1a\x1aqualtran/protos/args.proto\"\xa6\x01\n\x07\x45\x43Point\x12\x1f\n\x01x\x18\x01 \x01(\x0b\x32\x14.qualtran.IntOrSympy\x12\x1f\n\x01y\x18\x02 \x01(\x0b\x32\x14.qualtran.IntOrSympy\x12!\n\x03mod\x18\x03 \x01(\x0b\x32\x14.qualtran.IntOrSympy\x12*\n\x07\x63urve_a\x18\x04 \x01(\x0b\x32\x14.qualtran.IntOrSympyH\x00\x88\x01\x01\x42\n\n\x08_curve_ab\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'qualtran.protos.ec_point_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_ECPOINT']._serialized_start=73 + _globals['_ECPOINT']._serialized_end=239 +# @@protoc_insertion_point(module_scope) diff --git a/qualtran/protos/ec_point_pb2.pyi b/qualtran/protos/ec_point_pb2.pyi new file mode 100644 index 000000000..ac82158cd --- /dev/null +++ b/qualtran/protos/ec_point_pb2.pyi @@ -0,0 +1,57 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file + +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 builtins +import google.protobuf.descriptor +import google.protobuf.message +import qualtran.protos.args_pb2 +import sys + +if sys.version_info >= (3, 8): + import typing as typing_extensions +else: + import typing_extensions + +DESCRIPTOR: google.protobuf.descriptor.FileDescriptor + +@typing_extensions.final +class ECPoint(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + X_FIELD_NUMBER: builtins.int + Y_FIELD_NUMBER: builtins.int + MOD_FIELD_NUMBER: builtins.int + CURVE_A_FIELD_NUMBER: builtins.int + @property + def x(self) -> qualtran.protos.args_pb2.IntOrSympy: ... + @property + def y(self) -> qualtran.protos.args_pb2.IntOrSympy: ... + @property + def mod(self) -> qualtran.protos.args_pb2.IntOrSympy: ... + @property + def curve_a(self) -> qualtran.protos.args_pb2.IntOrSympy: ... + def __init__( + self, + *, + x: qualtran.protos.args_pb2.IntOrSympy | None = ..., + y: qualtran.protos.args_pb2.IntOrSympy | None = ..., + mod: qualtran.protos.args_pb2.IntOrSympy | None = ..., + curve_a: qualtran.protos.args_pb2.IntOrSympy | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["_curve_a", b"_curve_a", "curve_a", b"curve_a", "mod", b"mod", "x", b"x", "y", b"y"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["_curve_a", b"_curve_a", "curve_a", b"curve_a", "mod", b"mod", "x", b"x", "y", b"y"]) -> None: ... + def WhichOneof(self, oneof_group: typing_extensions.Literal["_curve_a", b"_curve_a"]) -> typing_extensions.Literal["curve_a"] | None: ... + +global___ECPoint = ECPoint diff --git a/qualtran/qref_interop/bartiq_demo.ipynb b/qualtran/qref_interop/bartiq_demo.ipynb index 9b11826d9..60e8055c7 100644 --- a/qualtran/qref_interop/bartiq_demo.ipynb +++ b/qualtran/qref_interop/bartiq_demo.ipynb @@ -148,15 +148,13 @@ "metadata": {}, "outputs": [], "source": [ - "from bartiq.integrations.qref import qref_to_bartiq\n", "from bartiq import compile_routine\n", "\n", "\n", "# Clear the resources from the top-level\n", "qref_definition.program.resources = [] \n", "\n", - "bartiq_routine = qref_to_bartiq(qref_definition)\n", - "compiled_routine = compile_routine(bartiq_routine)\n", + "compiled_routine = compile_routine(qref_definition).routine\n", "\n", "print(\"Qualtran Bloq T Complexity: \")\n", "pprint(bloq.t_complexity())\n", @@ -181,14 +179,24 @@ "source": [ "### Numeric Implementation\n", "\n", - "The example above is purely numeric. If symbolic expressions are used, we are able to produce resources and decompose the \"top-level\" bloq, `StatePreparationAliasSampling` (see snippet below). However, subsequent bloqs, like `PrepareUniformSuperposition`, cannot be further decomposed using `decompose_bloq()` This is most likely temporary and as Qualtran improves, this might cease being a problem.\n", - "\n", - "```\n", + "The example above is purely numeric. If symbolic expressions are used, we are able to produce resources and decompose the \"top-level\" bloq, `StatePreparationAliasSampling` (see snippet below). However, subsequent bloqs, like `PrepareUniformSuperposition`, cannot be further decomposed using `decompose_bloq()` This is most likely temporary and as Qualtran improves, this might cease being a problem." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4495c810-fdfd-43d0-a70a-bf40dfdff061", + "metadata": {}, + "outputs": [], + "source": [ "import sympy\n", "N, M, eps = sympy.symbols(\"N M eps\")\n", "bloq = StatePreparationAliasSampling.from_n_coeff(n_coeff=N, sum_of_unnormalized_probabilites=M, precision=eps)\n", "pprint(bloq.t_complexity())\n", - "```" + "children = bloq.decompose_bloq().bloq_instances\n", + "# Doesn't curently work:\n", + "# decomposed_children = [child.decompose_bloq() for child in children]\n", + "# decomposed_children = [child.t_complexity() for child in children]" ] }, { @@ -311,13 +319,11 @@ "metadata": {}, "outputs": [], "source": [ - "usp_1_bartiq = qref_to_bartiq(usp_1_qref)\n", "print(\"Qualtran USP Bloq T Complexity - T Gates\")\n", - "pprint(compile_routine(usp_1_bartiq).resources[\"t\"])\n", + "pprint(compile_routine(usp_1_qref).routine.resources[\"t\"])\n", "\n", - "usp_2_bartiq = qref_to_bartiq(usp_2_qref)\n", "print(\"\\nQREF USP Custom Implementation - T Gates\")\n", - "pprint(compile_routine(usp_2_bartiq).resources[\"t\"])" + "pprint(compile_routine(usp_2_qref).routine.resources[\"t\"])" ] }, { @@ -368,13 +374,13 @@ "qref_as_2.program.children.by_name[\"PrepareUniformSuperposition_0\"] = usp_2_qref.program\n", "\n", "\n", - "bartiq_as_1 = qref_to_bartiq(qref_as_1)\n", "print(\"Unchanged Alias Sampling T Complexity - T Gates: \")\n", - "pprint(compile_routine(bartiq_as_1).resources[\"t\"])\n", + "compiled_as_1 = compile_routine(qref_as_1).routine\n", + "pprint(compiled_as_1.resources[\"t\"])\n", "\n", - "bartiq_as_2 = qref_to_bartiq(qref_as_2)\n", "print(\"\\nUpdated Alias Sampling T Complexity - T Gates: \")\n", - "pprint(compile_routine(bartiq_as_2).resources[\"t\"])\n" + "compiled_as_2 = compile_routine(qref_as_2).routine\n", + "pprint(compiled_as_2.resources[\"t\"])\n" ] }, { @@ -382,30 +388,7 @@ "id": "44cc58e6-6155-4919-9156-556b5e42fdf4", "metadata": {}, "source": [ - "The unchanged Alias Sampling results shows the same 307 T gates that we saw earlier in the notebook. However, for the updated Alias Sampling, we see a much different result. While the T-gates resource is printed, a warning is also seen. Since the \"top-level\" routine does not have knowledge of its child's parameter, the parameter `N` from the USP routine is namespaced resulting in `PrepareUniformSuperposition_0.N` in the expression. To address the warning, at a \"top-level\" we will need to include `N` as an input paramter and link that to `PrepareUniformSuperposition_0.N`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "275437ee-aab0-4f38-93dc-603ba1a4a5f2", - "metadata": {}, - "outputs": [], - "source": [ - "qref_as_2.program.input_params = [\"N\"]\n", - "qref_as_2.program.linked_params = [{\"source\": \"N\", \"targets\": [\"PrepareUniformSuperposition_0.N\"]}]\n", - "\n", - "bartiq_as_2 = qref_to_bartiq(qref_as_2)\n", - "compiled_as_2 = compile_routine(bartiq_as_2)\n", - "compiled_as_2.resources[\"t\"]" - ] - }, - { - "cell_type": "markdown", - "id": "e864d60d-66cc-4bc7-92dc-708c2a77b614", - "metadata": {}, - "source": [ - "With the `N` parameter correctly setup, we no longer see the warning and correctly compile and print the symbolic expression for the number of T gates. Now we can generate numerical values for the resources given a specific value of `N`. To do this, we can use Bartiq's `evaluate` method." + "The unchanged Alias Sampling results shows the same 307 T gates that we saw earlier in the notebook. However, for the updated Alias Sampling, we see a much different result. Now we can generate numerical values for the resources given a specific value of `N`. To do this, we can use Bartiq's `evaluate` method." ] }, { @@ -417,8 +400,8 @@ "source": [ "from bartiq import evaluate\n", "N_value = len(probs_list)\n", - "assignments = {f\"N={N_value}\"}\n", - "evaluate(compiled_as_2, assignments).resources[\"t\"]" + "assignments = {\"N\": N_value}\n", + "evaluate(compiled_as_2, assignments).routine.resources[\"t\"]" ] }, { @@ -456,8 +439,7 @@ "usp_3_qref = copy.copy(usp_2_qref)\n", "usp_3_qref.program.resources = [{\"name\": \"t\", \"type\": \"additive\", \"value\": \"2*foo(N)\"}]\n", "\n", - "usp_3_bartiq = qref_to_bartiq(usp_3_qref)\n", - "compiled_usp_3 = compile_routine(usp_3_bartiq)\n", + "compiled_usp_3 = compile_routine(usp_3_qref).routine\n", "compiled_usp_3.resources" ] }, @@ -476,9 +458,9 @@ "metadata": {}, "outputs": [], "source": [ - "assignments = {\"N=101\"}\n", + "assignments = {\"N\": 101}\n", "functions_map = {\"foo\": foo}\n", - "evaluate(compiled_usp_3, assignments, functions_map=functions_map).resources" + "evaluate(compiled_usp_3, assignments, functions_map=functions_map).routine.resources" ] }, { @@ -524,7 +506,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.9" + "version": "3.10.14" } }, "nbformat": 4, diff --git a/qualtran/resource_counting/__init__.py b/qualtran/resource_counting/__init__.py index 6823999c1..22368f539 100644 --- a/qualtran/resource_counting/__init__.py +++ b/qualtran/resource_counting/__init__.py @@ -12,10 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Counting resource usage (bloqs, qubits) - -isort:skip_file -""" +# isort:skip_file +"""Analysis routines for computing costs and resource counts.""" from ._generalization import GeneralizerT diff --git a/qualtran/resource_counting/_bloq_counts.py b/qualtran/resource_counting/_bloq_counts.py index 3cc0a64d5..02ad3c404 100644 --- a/qualtran/resource_counting/_bloq_counts.py +++ b/qualtran/resource_counting/_bloq_counts.py @@ -252,7 +252,7 @@ def to_legacy_t_complexity( ) def total_beverland_count(self) -> Dict[str, SymbolicInt]: - r"""Counts used by Beverland. et. al. using notation from the reference. + r"""Counts used by Beverland et al. using notation from the reference. - $M_\mathrm{meas}$ is the number of measurements. - $M_R$ is the number of rotations. @@ -297,7 +297,10 @@ def compute(self, bloq: 'Bloq', get_callee_cost: Callable[['Bloq'], GateCounts]) from qualtran.bloqs.mcmt import And, MultiTargetCNOT if self.legacy_shims: - legacy_val = bloq._t_complexity_() + if hasattr(bloq, '_t_complexity_'): + legacy_val = bloq._t_complexity_() + else: + legacy_val = NotImplemented if legacy_val is not NotImplemented: warnings.warn( "Please migrate explicit cost annotations to the general " diff --git a/qualtran/resource_counting/classify_bloqs.py b/qualtran/resource_counting/classify_bloqs.py index eae56e52e..bb2a57323 100644 --- a/qualtran/resource_counting/classify_bloqs.py +++ b/qualtran/resource_counting/classify_bloqs.py @@ -229,7 +229,7 @@ def bloq_is_rotation(b: Bloq) -> bool: ) if isinstance(b, Controlled): - if b.ctrl_spec.num_qubits > 1: + if b.ctrl_spec.num_qubits != 1: return False # TODO https://github.com/quantumlib/Qualtran/issues/878 diff --git a/qualtran/resource_counting/t_counts_from_sigma.py b/qualtran/resource_counting/t_counts_from_sigma.py index 3eaf867cf..02c6673c6 100644 --- a/qualtran/resource_counting/t_counts_from_sigma.py +++ b/qualtran/resource_counting/t_counts_from_sigma.py @@ -14,8 +14,6 @@ import warnings from typing import Mapping -import cirq - from qualtran import Bloq, Controlled from qualtran.symbolics import ceil, SymbolicInt @@ -23,6 +21,9 @@ def t_counts_from_sigma(sigma: Mapping['Bloq', SymbolicInt]) -> SymbolicInt: """Aggregates T-counts from a sigma dictionary by summing T-costs for all rotation bloqs.""" warnings.warn("This function is deprecated. Use `get_cost_value`.", DeprecationWarning) + # TODO: remove dependence on cirq.has_stabilizer_effect. + import cirq + from qualtran.bloqs.basic_gates import TGate, Toffoli, TwoBitCSwap from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.resource_counting.classify_bloqs import bloq_is_rotation diff --git a/qualtran/serialization/__init__.py b/qualtran/serialization/__init__.py index ce76a8d10..a79cdd57a 100644 --- a/qualtran/serialization/__init__.py +++ b/qualtran/serialization/__init__.py @@ -11,3 +11,5 @@ # 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. + +"""Functions for protobuf serialization of bloqs.""" diff --git a/qualtran/serialization/bloq.py b/qualtran/serialization/bloq.py index 6498984f6..99dce0415 100644 --- a/qualtran/serialization/bloq.py +++ b/qualtran/serialization/bloq.py @@ -37,12 +37,14 @@ Signature, Soquet, ) +from qualtran.bloqs.factoring.ecc import ECPoint from qualtran.protos import bloq_pb2 from qualtran.serialization import ( annotations, args, ctrl_spec, data_types, + ec_point, registers, resolver_dict, ) @@ -75,6 +77,8 @@ def arg_to_proto(*, name: str, val: Any) -> bloq_pb2.BloqArg: return bloq_pb2.BloqArg(name=name, ndarray=args.ndarray_to_proto(np.asarray(val))) if np.iscomplexobj(val): return bloq_pb2.BloqArg(name=name, complex_val=args.complex_to_proto(val)) + if isinstance(val, ECPoint): + return bloq_pb2.BloqArg(name=name, ec_point=ec_point.ec_point_to_proto(val)) raise ValueError(f"Cannot serialize {val} of unknown type {type(val)}") @@ -101,6 +105,8 @@ def arg_from_proto(arg: bloq_pb2.BloqArg) -> Dict[str, Any]: return {arg.name: args.ndarray_from_proto(arg.ndarray)} if arg.HasField("complex_val"): return {arg.name: args.complex_from_proto(arg.complex_val)} + if arg.HasField("ec_point"): + return {arg.name: ec_point.ec_point_from_proto(arg.ec_point)} raise ValueError(f"Cannot deserialize {arg=}") diff --git a/qualtran/serialization/bloq_test.py b/qualtran/serialization/bloq_test.py index 5964908ee..d411f09e3 100644 --- a/qualtran/serialization/bloq_test.py +++ b/qualtran/serialization/bloq_test.py @@ -23,10 +23,9 @@ from qualtran import Bloq, Signature from qualtran._infra.composite_bloq_test import TestTwoCNOT -from qualtran.bloqs.factoring.mod_exp import ModExp +from qualtran.bloqs.factoring.rsa.rsa_mod_exp import ModExp from qualtran.cirq_interop import CirqGateAsBloq from qualtran.cirq_interop._cirq_to_bloq_test import TestCNOT as TestCNOTCirq -from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.protos import registers_pb2 from qualtran.resource_counting import CostKey, GateCounts, QECGatesCost from qualtran.serialization import bloq as bloq_serialization @@ -96,9 +95,6 @@ class TestCSwap(Bloq): def signature(self) -> 'Signature': return Signature.build(ctrl=1, x=self.bitsize, y=self.bitsize) - def _t_complexity_(self) -> TComplexity: - return TComplexity(t=7 * self.bitsize, clifford=10 * self.bitsize) - def my_static_costs(self, cost_key: 'CostKey'): if isinstance(cost_key, QECGatesCost): return GateCounts(t=7 * self.bitsize, clifford=10 * self.bitsize) diff --git a/qualtran/serialization/ctrl_spec.py b/qualtran/serialization/ctrl_spec.py index 301faf2fc..30895b51e 100644 --- a/qualtran/serialization/ctrl_spec.py +++ b/qualtran/serialization/ctrl_spec.py @@ -11,10 +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. - from qualtran import CtrlSpec from qualtran.protos import ctrl_spec_pb2 from qualtran.serialization import args, data_types +from qualtran.symbolics import Shaped def ctrl_spec_from_proto(spec: ctrl_spec_pb2.CtrlSpec) -> CtrlSpec: @@ -25,7 +25,12 @@ def ctrl_spec_from_proto(spec: ctrl_spec_pb2.CtrlSpec) -> CtrlSpec: def ctrl_spec_to_proto(spec: CtrlSpec) -> ctrl_spec_pb2.CtrlSpec: + def cvs_to_proto(cvs): + if isinstance(cvs, Shaped): + raise ValueError("cannot serialize Shaped") + return args.ndarray_to_proto(cvs) + return ctrl_spec_pb2.CtrlSpec( qdtypes=[data_types.data_type_to_proto(dtype) for dtype in spec.qdtypes], - cvs=[args.ndarray_to_proto(cvs) for cvs in spec.cvs], + cvs=[cvs_to_proto(cvs) for cvs in spec.cvs], ) diff --git a/qualtran/serialization/ec_point.py b/qualtran/serialization/ec_point.py new file mode 100644 index 000000000..48a4367ce --- /dev/null +++ b/qualtran/serialization/ec_point.py @@ -0,0 +1,35 @@ +# 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. + +from qualtran.bloqs.factoring.ecc import ECPoint +from qualtran.protos import ec_point_pb2 +from qualtran.serialization.args import int_or_sympy_from_proto, int_or_sympy_to_proto + + +def ec_point_from_proto(point: ec_point_pb2.ECPoint) -> ECPoint: + return ECPoint( + x=int_or_sympy_from_proto(point.x), + y=int_or_sympy_from_proto(point.y), + mod=int_or_sympy_from_proto(point.mod), + curve_a=int_or_sympy_from_proto(point.curve_a), + ) + + +def ec_point_to_proto(point: ECPoint) -> ec_point_pb2.ECPoint: + return ec_point_pb2.ECPoint( + x=int_or_sympy_to_proto(point.x), + y=int_or_sympy_to_proto(point.y), + mod=int_or_sympy_to_proto(point.mod), + curve_a=int_or_sympy_to_proto(point.curve_a), + ) diff --git a/qualtran/serialization/ec_point_test.py b/qualtran/serialization/ec_point_test.py new file mode 100644 index 000000000..c9f299bd2 --- /dev/null +++ b/qualtran/serialization/ec_point_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.bloqs.factoring.ecc import ECPoint +from qualtran.serialization.ec_point import ec_point_from_proto, ec_point_to_proto + + +@pytest.mark.parametrize( + "ec_point", + [ + ECPoint(x=15, y=13, mod=17, curve_a=0), + ECPoint(x=0, y=2, mod=7, curve_a=3), + ECPoint(x=0, y=2, mod=7), + ], +) +def test_ec_point_to_proto_roundtrip(ec_point: ECPoint): + ec_point_proto = ec_point_to_proto(ec_point) + ec_point_roundtrip = ec_point_from_proto(ec_point_proto) + assert ec_point == ec_point_roundtrip diff --git a/qualtran/serialization/resolver_dict.py b/qualtran/serialization/resolver_dict.py index ee8025a4e..cd01026fb 100644 --- a/qualtran/serialization/resolver_dict.py +++ b/qualtran/serialization/resolver_dict.py @@ -97,7 +97,10 @@ import qualtran.bloqs.data_loading.qroam_clean import qualtran.bloqs.data_loading.qrom import qualtran.bloqs.data_loading.select_swap_qrom -import qualtran.bloqs.factoring.mod_exp +import qualtran.bloqs.factoring._factoring_shims +import qualtran.bloqs.factoring.ecc +import qualtran.bloqs.factoring.ecc.ec_add +import qualtran.bloqs.factoring.rsa import qualtran.bloqs.for_testing.atom import qualtran.bloqs.for_testing.casting import qualtran.bloqs.for_testing.interior_alloc @@ -114,6 +117,7 @@ import qualtran.bloqs.mean_estimation.complex_phase_oracle import qualtran.bloqs.mean_estimation.mean_estimation_operator import qualtran.bloqs.mod_arithmetic +import qualtran.bloqs.mod_arithmetic.mod_division import qualtran.bloqs.mod_arithmetic.mod_multiplication import qualtran.bloqs.mod_arithmetic.mod_subtraction import qualtran.bloqs.multiplexers.apply_gate_to_lth_target @@ -167,6 +171,7 @@ "qualtran.bloqs.arithmetic.bitwise.Xor": qualtran.bloqs.arithmetic.bitwise.Xor, "qualtran.bloqs.arithmetic.bitwise.XorK": qualtran.bloqs.arithmetic.bitwise.XorK, "qualtran.bloqs.arithmetic.comparison.BiQubitsMixer": qualtran.bloqs.arithmetic.comparison.BiQubitsMixer, + "qualtran.bloqs.arithmetic.comparison.Equals": qualtran.bloqs.arithmetic.comparison.Equals, "qualtran.bloqs.arithmetic.comparison.EqualsAConstant": qualtran.bloqs.arithmetic.comparison.EqualsAConstant, "qualtran.bloqs.arithmetic.comparison.GreaterThan": qualtran.bloqs.arithmetic.comparison.GreaterThan, "qualtran.bloqs.arithmetic.comparison.GreaterThanConstant": qualtran.bloqs.arithmetic.comparison.GreaterThanConstant, @@ -175,6 +180,10 @@ "qualtran.bloqs.arithmetic.comparison.LinearDepthGreaterThan": qualtran.bloqs.arithmetic.comparison.LinearDepthGreaterThan, "qualtran.bloqs.arithmetic.comparison.SingleQubitCompare": qualtran.bloqs.arithmetic.comparison.SingleQubitCompare, "qualtran.bloqs.arithmetic.comparison.CLinearDepthGreaterThan": qualtran.bloqs.arithmetic.comparison.CLinearDepthGreaterThan, + "qualtran.bloqs.arithmetic.comparison.LinearDepthHalfGreaterThan": qualtran.bloqs.arithmetic.comparison.LinearDepthHalfGreaterThan, + "qualtran.bloqs.arithmetic.comparison.LinearDepthHalfLessThan": qualtran.bloqs.arithmetic.comparison.LinearDepthHalfLessThan, + "qualtran.bloqs.arithmetic.comparison.LinearDepthHalfGreaterThanEqual": qualtran.bloqs.arithmetic.comparison.LinearDepthHalfGreaterThanEqual, + "qualtran.bloqs.arithmetic.comparison.LinearDepthHalfLessThanEqual": qualtran.bloqs.arithmetic.comparison.LinearDepthHalfLessThanEqual, "qualtran.bloqs.arithmetic.controlled_add_or_subtract.ControlledAddOrSubtract": qualtran.bloqs.arithmetic.controlled_add_or_subtract.ControlledAddOrSubtract, "qualtran.bloqs.arithmetic.conversions.contiguous_index.ToContiguousIndex": qualtran.bloqs.arithmetic.conversions.contiguous_index.ToContiguousIndex, "qualtran.bloqs.arithmetic.conversions.ones_complement_to_twos_complement.SignedIntegerToTwosComplement": qualtran.bloqs.arithmetic.conversions.ones_complement_to_twos_complement.SignedIntegerToTwosComplement, @@ -325,10 +334,13 @@ "qualtran.bloqs.data_loading.qrom.QROM": qualtran.bloqs.data_loading.qrom.QROM, "qualtran.bloqs.data_loading.qroam_clean.QROAMClean": qualtran.bloqs.data_loading.qroam_clean.QROAMClean, "qualtran.bloqs.data_loading.qroam_clean.QROAMCleanAdjoint": qualtran.bloqs.data_loading.qroam_clean.QROAMCleanAdjoint, + "qualtran.bloqs.data_loading.qroam_clean.QROAMCleanAdjointWrapper": qualtran.bloqs.data_loading.qroam_clean.QROAMCleanAdjointWrapper, "qualtran.bloqs.data_loading.select_swap_qrom.SelectSwapQROM": qualtran.bloqs.data_loading.select_swap_qrom.SelectSwapQROM, "qualtran.bloqs.mod_arithmetic.CModAddK": qualtran.bloqs.mod_arithmetic.CModAddK, - "qualtran.bloqs.mod_arithmetic.mod_addition.CModAdd": qualtran.bloqs.mod_arithmetic.CModAdd, + "qualtran.bloqs.mod_arithmetic.mod_addition.ModAdd": qualtran.bloqs.mod_arithmetic.mod_addition.ModAdd, + "qualtran.bloqs.mod_arithmetic.mod_addition.CModAdd": qualtran.bloqs.mod_arithmetic.mod_addition.CModAdd, "qualtran.bloqs.mod_arithmetic.mod_addition.ModAddK": qualtran.bloqs.mod_arithmetic.mod_addition.ModAddK, + "qualtran.bloqs.mod_arithmetic.mod_addition.CModAddK": qualtran.bloqs.mod_arithmetic.mod_addition.CModAddK, "qualtran.bloqs.mod_arithmetic.mod_addition.CtrlScaleModAdd": qualtran.bloqs.mod_arithmetic.CtrlScaleModAdd, "qualtran.bloqs.mod_arithmetic.ModAdd": qualtran.bloqs.mod_arithmetic.ModAdd, "qualtran.bloqs.mod_arithmetic.ModSub": qualtran.bloqs.mod_arithmetic.ModSub, @@ -337,7 +349,21 @@ "qualtran.bloqs.mod_arithmetic.mod_subtraction.CModNeg": qualtran.bloqs.mod_arithmetic.mod_subtraction.CModNeg, "qualtran.bloqs.mod_arithmetic.mod_multiplication.ModDbl": qualtran.bloqs.mod_arithmetic.mod_multiplication.ModDbl, "qualtran.bloqs.mod_arithmetic.mod_multiplication.CModMulK": qualtran.bloqs.mod_arithmetic.mod_multiplication.CModMulK, - "qualtran.bloqs.factoring.mod_exp.ModExp": qualtran.bloqs.factoring.mod_exp.ModExp, + "qualtran.bloqs.mod_arithmetic.mod_multiplication.DirtyOutOfPlaceMontgomeryModMul": qualtran.bloqs.mod_arithmetic.mod_multiplication.DirtyOutOfPlaceMontgomeryModMul, + "qualtran.bloqs.mod_arithmetic.mod_multiplication.SingleWindowModMul": qualtran.bloqs.mod_arithmetic.mod_multiplication.SingleWindowModMul, + "qualtran.bloqs.mod_arithmetic.mod_division.KaliskiModInverse": qualtran.bloqs.mod_arithmetic.mod_division.KaliskiModInverse, + "qualtran.bloqs.mod_arithmetic.mod_division._KaliskiIteration": qualtran.bloqs.mod_arithmetic.mod_division._KaliskiIteration, + "qualtran.bloqs.factoring._factoring_shims.MeasureQFT": qualtran.bloqs.factoring._factoring_shims.MeasureQFT, + "qualtran.bloqs.factoring.ecc.ec_add_r.ECWindowAddR": qualtran.bloqs.factoring.ecc.ec_add_r.ECWindowAddR, + "qualtran.bloqs.factoring.ecc.ec_add._ECAddStepOne": qualtran.bloqs.factoring.ecc.ec_add._ECAddStepOne, + "qualtran.bloqs.factoring.ecc.ec_add._ECAddStepTwo": qualtran.bloqs.factoring.ecc.ec_add._ECAddStepTwo, + "qualtran.bloqs.factoring.ecc.ec_add._ECAddStepThree": qualtran.bloqs.factoring.ecc.ec_add._ECAddStepThree, + "qualtran.bloqs.factoring.ecc.ec_add._ECAddStepFour": qualtran.bloqs.factoring.ecc.ec_add._ECAddStepFour, + "qualtran.bloqs.factoring.ecc.ec_add._ECAddStepFive": qualtran.bloqs.factoring.ecc.ec_add._ECAddStepFive, + "qualtran.bloqs.factoring.ecc.ec_add._ECAddStepSix": qualtran.bloqs.factoring.ecc.ec_add._ECAddStepSix, + "qualtran.bloqs.factoring.ecc.ec_add.ECAdd": qualtran.bloqs.factoring.ecc.ec_add.ECAdd, + "qualtran.bloqs.factoring.rsa.rsa_phase_estimate.RSAPhaseEstimate": qualtran.bloqs.factoring.rsa.rsa_phase_estimate.RSAPhaseEstimate, + "qualtran.bloqs.factoring.rsa.rsa_mod_exp.ModExp": qualtran.bloqs.factoring.rsa.rsa_mod_exp.ModExp, "qualtran.bloqs.for_testing.atom.TestAtom": qualtran.bloqs.for_testing.atom.TestAtom, "qualtran.bloqs.for_testing.atom.TestGWRAtom": qualtran.bloqs.for_testing.atom.TestGWRAtom, "qualtran.bloqs.for_testing.atom.TestTwoBitOp": qualtran.bloqs.for_testing.atom.TestTwoBitOp, diff --git a/qualtran/simulation/__init__.py b/qualtran/simulation/__init__.py index ce76a8d10..3c74702ce 100644 --- a/qualtran/simulation/__init__.py +++ b/qualtran/simulation/__init__.py @@ -11,3 +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. + +"""Simulators for quantum programs. + +This module includes `qualtran.simulation.classical_sim` for basis state simulation of +classical-reversible bloqs and `qualtran.simulation.tensor` for Quimb-based tensor network +contraction. +""" diff --git a/qualtran/simulation/classical_sim.py b/qualtran/simulation/classical_sim.py index 74eb5319f..8af0af038 100644 --- a/qualtran/simulation/classical_sim.py +++ b/qualtran/simulation/classical_sim.py @@ -14,7 +14,19 @@ """Functionality for the `Bloq.call_classically(...)` protocol.""" import itertools -from typing import Any, Dict, Iterable, List, Mapping, Optional, Sequence, Tuple, Type, Union +from typing import ( + Any, + Dict, + Iterable, + List, + Mapping, + Optional, + Sequence, + Tuple, + Type, + TYPE_CHECKING, + Union, +) import networkx as nx import numpy as np @@ -34,9 +46,50 @@ ) from qualtran._infra.composite_bloq import _binst_to_cxns +if TYPE_CHECKING: + from qualtran import QDType + ClassicalValT = Union[int, np.integer, NDArray[np.integer]] +def _numpy_dtype_from_qdtype(dtype: 'QDType') -> Type: + from qualtran._infra.data_types import QBit, QInt, QUInt + + if isinstance(dtype, QUInt): + if dtype.bitsize <= 8: + return np.uint8 + elif dtype.bitsize <= 16: + return np.uint16 + elif dtype.bitsize <= 32: + return np.uint32 + elif dtype.bitsize <= 64: + return np.uint64 + + if isinstance(dtype, QInt): + if dtype.bitsize <= 8: + return np.int8 + elif dtype.bitsize <= 16: + return np.int16 + elif dtype.bitsize <= 32: + return np.int32 + elif dtype.bitsize <= 64: + return np.int64 + + if isinstance(dtype, QBit): + return np.uint8 + + return object + + +def _empty_ndarray_from_reg(reg: Register) -> np.ndarray: + from qualtran._infra.data_types import QGF + + if isinstance(reg.dtype, QGF): + return reg.dtype.gf_type.Zeros(reg.shape) + + return np.empty(reg.shape, dtype=_numpy_dtype_from_qdtype(reg.dtype)) + + def _get_in_vals( binst: Union[DanglingT, BloqInstance], reg: Register, soq_assign: Dict[Soquet, ClassicalValT] ) -> ClassicalValT: @@ -44,21 +97,7 @@ def _get_in_vals( if not reg.shape: return soq_assign[Soquet(binst, reg)] - if reg.bitsize <= 8: - dtype: Type = np.uint8 - elif reg.bitsize <= 16: - dtype = np.uint16 - elif reg.bitsize <= 32: - dtype = np.uint32 - elif reg.bitsize <= 64: - dtype = np.uint64 - else: - raise NotImplementedError( - "We currently only support up to 64-bit " - "multi-dimensional registers in classical simulation." - ) - - arg = np.empty(reg.shape, dtype=dtype) + arg = _empty_ndarray_from_reg(reg) for idx in reg.all_idxs(): soq = Soquet(binst, reg, idx=idx) arg[idx] = soq_assign[soq] @@ -89,7 +128,7 @@ def _update_assign_from_vals( if reg.shape: # `val` is an array - val = np.asarray(val) + val = np.asanyarray(val) if val.shape != reg.shape: raise ValueError( f"Incorrect shape {val.shape} received for {debug_str}. " f"Want {reg.shape}." diff --git a/qualtran/simulation/classical_sim_test.py b/qualtran/simulation/classical_sim_test.py index fafcd3b0d..6073a50e4 100644 --- a/qualtran/simulation/classical_sim_test.py +++ b/qualtran/simulation/classical_sim_test.py @@ -20,7 +20,23 @@ from attrs import frozen from numpy.typing import NDArray -from qualtran import Bloq, BloqBuilder, QAny, QBit, Register, Side, Signature, Soquet +from qualtran import ( + Bloq, + BloqBuilder, + BQUInt, + QAny, + QBit, + QDType, + QFxp, + QGF, + QInt, + QIntOnesComp, + QUInt, + Register, + Side, + Signature, + Soquet, +) from qualtran.bloqs.basic_gates import CNOT from qualtran.simulation.classical_sim import ( _update_assign_from_vals, @@ -148,3 +164,48 @@ def test_add_ints_signed(n_bits: int): @pytest.mark.notebook def test_notebook(): execute_notebook('classical_sim') + + +@frozen +class TestMultiDimensionalReg(Bloq): + dtype: QDType + n: int + dtypes_to_assert: tuple[type, ...] = (int, np.integer) + + @property + def signature(self): + return Signature( + [ + Register('x', self.dtype, shape=(self.n,), side=Side.LEFT), + Register('y', self.dtype, shape=(self.n,), side=Side.RIGHT), + ] + ) + + def on_classical_vals(self, x): + assert all(isinstance(y, self.dtypes_to_assert) for y in x.reshape(-1)) + return {'y': x} + + +@pytest.mark.parametrize( + 'dtype', [QBit(), QInt(5), QUInt(5), QIntOnesComp(5), BQUInt(5, 20), QFxp(5, 3, signed=True)] +) +def test_multidimensional_classical_sim_for_dtypes(dtype: QDType): + x = [*dtype.get_classical_domain()] + bloq = TestMultiDimensionalReg(dtype, len(x)) + np.testing.assert_equal(bloq.call_classically(x=np.array(x))[0], x) + + +def test_multidimensional_classical_sim_for_large_int(): + dtype = QInt(100) + x = [2**88 - 1, 2**12 - 1, 2**54 - 1, 1 - 2**72, 1 - 2**62] + bloq = TestMultiDimensionalReg(dtype, len(x)) + np.testing.assert_equal(bloq.call_classically(x=np.array(x))[0], x) + + +def test_multidimensional_classical_sim_for_gqf(): + dtype = QGF(2, 2) + x = dtype.gf_type.elements + bloq = TestMultiDimensionalReg(dtype, len(x), (dtype.gf_type,)) + y = bloq.call_classically(x=x)[0] + assert isinstance(y, dtype.gf_type) + np.testing.assert_equal(y, x) diff --git a/qualtran/simulation/tensor/__init__.py b/qualtran/simulation/tensor/__init__.py index 33e340000..9a2efeb04 100644 --- a/qualtran/simulation/tensor/__init__.py +++ b/qualtran/simulation/tensor/__init__.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Functionality for the `Bloq.tensor_contract()` protocol.""" from ._dense import bloq_to_dense, get_right_and_left_inds, quimb_to_dense from ._flattening import bloq_has_custom_tensors, flatten_for_tensor_contraction diff --git a/qualtran/simulation/tensor/_dense_test.py b/qualtran/simulation/tensor/_dense_test.py index 991d68df9..45f1e4223 100644 --- a/qualtran/simulation/tensor/_dense_test.py +++ b/qualtran/simulation/tensor/_dense_test.py @@ -15,8 +15,8 @@ from functools import cached_property from typing import Dict, List -import cirq import numpy as np +import pytest import quimb.tensor as qtn from attrs import frozen @@ -121,6 +121,7 @@ def build_composite_bloq(self, bb: 'BloqBuilder', s: 'SoquetT') -> Dict[str, 'So def test_nest(): + cirq = pytest.importorskip('cirq') x = XNest() should_be = cirq.unitary(cirq.X) np.testing.assert_allclose(should_be, x.tensor_contract()) @@ -128,6 +129,7 @@ def test_nest(): def test_double_nest(): + cirq = pytest.importorskip('cirq') xx = XDoubleNest() should_be = cirq.unitary(cirq.X) np.testing.assert_allclose(should_be, xx.tensor_contract()) @@ -150,6 +152,7 @@ def build_composite_bloq( def test_bloq_with_non_trivial_inds(): + cirq = pytest.importorskip('cirq') bloq = BloqWithNonTrivialInds() assert_valid_bloq_decomposition(bloq) cirq_qubits = cirq.LineQubit.range(2) diff --git a/qualtran/simulation/tensor/_tensor_data_manipulation.py b/qualtran/simulation/tensor/_tensor_data_manipulation.py index 97f8852e2..5029ae22e 100644 --- a/qualtran/simulation/tensor/_tensor_data_manipulation.py +++ b/qualtran/simulation/tensor/_tensor_data_manipulation.py @@ -68,11 +68,15 @@ def active_space_for_ctrl_spec( Returns a tuple of indices/slices that can be used to address into the ndarray, representing tensor data of shape `tensor_shape_from_signature(signature)`, and access the active subspace. """ + if ctrl_spec.is_symbolic(): + raise ValueError(f"cannot compute active space for symbolic {ctrl_spec=}") + out_ind, inp_ind = tensor_out_inp_shape_from_signature(signature) data_shape = out_ind + inp_ind active_idx: List[Union[int, slice]] = [slice(x) for x in data_shape] ctrl_idx = 0 for cv in ctrl_spec.cvs: + assert isinstance(cv, np.ndarray) for idx in itertools.product(*[range(sh) for sh in cv.shape]): active_idx[ctrl_idx] = int(cv[idx]) active_idx[ctrl_idx + len(out_ind)] = int(cv[idx]) diff --git a/qualtran/surface_code/__init__.py b/qualtran/surface_code/__init__.py index d54ac32e7..9f43f1b6a 100644 --- a/qualtran/surface_code/__init__.py +++ b/qualtran/surface_code/__init__.py @@ -14,6 +14,8 @@ # isort:skip_file +"""Physical cost models for surface code architectures.""" + from .algorithm_summary import AlgorithmSummary from .physical_cost_summary import PhysicalCostsSummary from .physical_parameters import PhysicalParameters diff --git a/qualtran/surface_code/beverland_et_al_model.ipynb b/qualtran/surface_code/beverland_et_al_model.ipynb index a3c6f95f5..1dca78647 100644 --- a/qualtran/surface_code/beverland_et_al_model.ipynb +++ b/qualtran/surface_code/beverland_et_al_model.ipynb @@ -5,7 +5,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Beverland et. al. Model\n", + "# Beverland et al. Model\n", "In this notebook, we reproduce the physical resource estimates in \"Assessing requirements to scale to practical quantum advantage\" by [Beverland et al](https://arxiv.org/abs/2211.07629), Appendix F.\n", "\n", "The paper describes the formulas used for estimating cost in the various appendices. The final estimation procedure is put together in Appendix E and we reproduce the values found in Appendix F." diff --git a/qualtran/surface_code/ccz2t_factory_test.py b/qualtran/surface_code/ccz2t_factory_test.py index 4bf7e9dab..5fd35ce68 100644 --- a/qualtran/surface_code/ccz2t_factory_test.py +++ b/qualtran/surface_code/ccz2t_factory_test.py @@ -19,9 +19,7 @@ def test_ccz_2t_factory(): factory = CCZ2TFactory() worse_factory = CCZ2TFactory(distillation_l1_d=7, distillation_l2_d=15) - alg = AlgorithmSummary( - n_logical_gates=GateCounts(t=10**8, toffoli=10**8), n_algo_qubits=100 - ) + alg = AlgorithmSummary(n_logical_gates=GateCounts(t=10**8, toffoli=10**8), n_algo_qubits=100) lem = LogicalErrorModel(qec_scheme=QECScheme.make_gidney_fowler(), physical_error=1e-3) err1 = factory.factory_error(n_logical_gates=alg.n_logical_gates, logical_error_model=lem) diff --git a/qualtran/surface_code/msft_resource_estimator_interop.ipynb b/qualtran/surface_code/msft_resource_estimator_interop.ipynb index af6337271..2af07a495 100644 --- a/qualtran/surface_code/msft_resource_estimator_interop.ipynb +++ b/qualtran/surface_code/msft_resource_estimator_interop.ipynb @@ -27,7 +27,7 @@ }, "outputs": [], "source": [ - "from qualtran.bloqs.factoring.mod_exp import ModExp\n", + "from qualtran.bloqs.factoring.rsa import ModExp\n", "from qualtran.drawing import show_bloq\n", "\n", "N = 13*17 # integer to factor\n", diff --git a/qualtran/surface_code/physical_cost_model.ipynb b/qualtran/surface_code/physical_cost_model.ipynb index 2dbfcbe5d..5cb5070d0 100644 --- a/qualtran/surface_code/physical_cost_model.ipynb +++ b/qualtran/surface_code/physical_cost_model.ipynb @@ -244,7 +244,7 @@ "outputs": [], "source": [ "print(\"Gidney Fowler \", QECScheme.make_gidney_fowler())\n", - "print(\"Beverland et. al.\", QECScheme.make_beverland_et_al())" + "print(\"Beverland et al. \", QECScheme.make_beverland_et_al())" ] }, { diff --git a/qualtran/surface_code/physical_parameters.py b/qualtran/surface_code/physical_parameters.py index f61ab7955..08d00e221 100644 --- a/qualtran/surface_code/physical_parameters.py +++ b/qualtran/surface_code/physical_parameters.py @@ -32,7 +32,7 @@ class PhysicalParameters: def make_beverland_et_al( cls, qubit_modality: str = 'superconducting', optimistic_err_rate: bool = False ): - """The physical parameters considered in the Beverland et. al. reference. + """The physical parameters considered in the Beverland et al. reference. Args: qubit_modality: One of "superconducting", "ion", or "majorana". This sets the @@ -43,7 +43,7 @@ def make_beverland_et_al( References: [Assessing requirements to scale to practical quantum advantage](https://arxiv.org/abs/2211.07629). - Beverland et. al. (2022). + Beverland et al. (2022). """ if optimistic_err_rate: phys_err_rate = 1e-4 diff --git a/qualtran/surface_code/qec_scheme.py b/qualtran/surface_code/qec_scheme.py index 721485814..00e2bd5ac 100644 --- a/qualtran/surface_code/qec_scheme.py +++ b/qualtran/surface_code/qec_scheme.py @@ -62,7 +62,7 @@ def logical_error_rate(self, code_distance: int, physical_error: float) -> float See section XV for introduction of this formula, with citation to below. Surface code quantum error correction incorporating accurate error propagation. - Fowler et. al. (2010). https://arxiv.org/abs/1004.0255. + Fowler et al. (2010). https://arxiv.org/abs/1004.0255. Note: this doesn't actually contain the formula from the above reference. """ return self.error_rate_scaler * math.pow( @@ -99,11 +99,11 @@ def make_gidney_fowler(cls): @classmethod def make_beverland_et_al(cls): - """The qec scheme parameters considered in Beverland et. al. reference. + """The qec scheme parameters considered in Beverland et al. reference. References: [https://arxiv.org/abs/2211.07629](Assessing requirements to scale to practical quantum advantage). - Beverland et. al. (2022). + Beverland et al. (2022). """ return cls(error_rate_scaler=0.03, error_rate_threshold=0.01) diff --git a/qualtran/surface_code/rotation_cost_model.py b/qualtran/surface_code/rotation_cost_model.py index 0944fe4bc..b747ce18a 100644 --- a/qualtran/surface_code/rotation_cost_model.py +++ b/qualtran/surface_code/rotation_cost_model.py @@ -48,10 +48,10 @@ class RotationLogarithmicModel(RotationCostModel): References: [https://arxiv.org/abs/2211.07629](Assessing requirements to scale to practical quantum advantage). - Beverland et. al. (2022). + Beverland et al. (2022). [https://arxiv.org/abs/2203.10064](Shorter quantum circuits via single-qubit gate approximation). - Kliuchnikov. et. al. (2022). Used for the approximation protocol. + Kliuchnikov et al. (2022). Used for the approximation protocol. """ slope: float @@ -82,7 +82,7 @@ class ConstantWithOverheadRotationCost(RotationCostModel): References: [https://doi.org/10.1103/PRXQuantum.1.020312](Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization). - Sanders et. al. (2020). + Sanders et al. (2020). """ bitsize: int diff --git a/qualtran/symbolics/__init__.py b/qualtran/symbolics/__init__.py index b09ebba27..95cf224f0 100644 --- a/qualtran/symbolics/__init__.py +++ b/qualtran/symbolics/__init__.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Utilities for simultaneous support for Sympy symbolic objects and concrete values.""" from qualtran.symbolics.math_funcs import ( acos, @@ -27,8 +28,6 @@ sarg, sconj, sexp, - shape, - slen, smax, smin, ssqrt, @@ -37,7 +36,9 @@ from qualtran.symbolics.types import ( HasLength, is_symbolic, + shape, Shaped, + slen, SymbolicComplex, SymbolicFloat, SymbolicInt, diff --git a/qualtran/symbolics/math_funcs.py b/qualtran/symbolics/math_funcs.py index 7ac2e496d..5768042de 100644 --- a/qualtran/symbolics/math_funcs.py +++ b/qualtran/symbolics/math_funcs.py @@ -11,19 +11,12 @@ # 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. -from typing import cast, Iterable, overload, Sized, Tuple, TypeVar, Union +from typing import cast, Iterable, overload, TypeVar import numpy as np import sympy -from qualtran.symbolics.types import ( - HasLength, - is_symbolic, - Shaped, - SymbolicComplex, - SymbolicFloat, - SymbolicInt, -) +from qualtran.symbolics.types import is_symbolic, SymbolicComplex, SymbolicFloat, SymbolicInt def pi(*args) -> SymbolicFloat: @@ -31,13 +24,11 @@ def pi(*args) -> SymbolicFloat: @overload -def log2(x: float) -> float: - ... +def log2(x: float) -> float: ... @overload -def log2(x: sympy.Expr) -> sympy.Expr: - ... +def log2(x: sympy.Expr) -> sympy.Expr: ... def log2(x: SymbolicFloat) -> SymbolicFloat: @@ -49,13 +40,11 @@ def log2(x: SymbolicFloat) -> SymbolicFloat: @overload -def ln(x: float) -> float: - ... +def ln(x: float) -> float: ... @overload -def ln(x: sympy.Expr) -> sympy.Expr: - ... +def ln(x: sympy.Expr) -> sympy.Expr: ... def ln(x: SymbolicFloat) -> SymbolicFloat: @@ -67,13 +56,11 @@ def ln(x: SymbolicFloat) -> SymbolicFloat: @overload -def sexp(x: complex) -> complex: - ... +def sexp(x: complex) -> complex: ... @overload -def sexp(x: sympy.Expr) -> sympy.Expr: - ... +def sexp(x: sympy.Expr) -> sympy.Expr: ... def sexp(x: SymbolicComplex) -> SymbolicComplex: @@ -83,13 +70,11 @@ def sexp(x: SymbolicComplex) -> SymbolicComplex: @overload -def sarg(x: complex) -> float: - ... +def sarg(x: complex) -> float: ... @overload -def sarg(x: sympy.Expr) -> sympy.Expr: - ... +def sarg(x: sympy.Expr) -> sympy.Expr: ... def sarg(x: SymbolicComplex) -> SymbolicFloat: @@ -100,13 +85,11 @@ def sarg(x: SymbolicComplex) -> SymbolicFloat: @overload -def sabs(x: float) -> float: - ... +def sabs(x: float) -> float: ... @overload -def sabs(x: sympy.Expr) -> sympy.Expr: - ... +def sabs(x: sympy.Expr) -> sympy.Expr: ... def sabs(x: SymbolicFloat) -> SymbolicFloat: @@ -114,13 +97,11 @@ def sabs(x: SymbolicFloat) -> SymbolicFloat: @overload -def ssqrt(x: float) -> float: - ... +def ssqrt(x: float) -> float: ... @overload -def ssqrt(x: sympy.Expr) -> sympy.Expr: - ... +def ssqrt(x: sympy.Expr) -> sympy.Expr: ... def ssqrt(x: SymbolicFloat) -> SymbolicFloat: @@ -130,13 +111,11 @@ def ssqrt(x: SymbolicFloat) -> SymbolicFloat: @overload -def ceil(x: float) -> int: - ... +def ceil(x: float) -> int: ... @overload -def ceil(x: sympy.Expr) -> sympy.Expr: - ... +def ceil(x: sympy.Expr) -> sympy.Expr: ... def ceil(x: SymbolicFloat) -> SymbolicInt: @@ -146,13 +125,11 @@ def ceil(x: SymbolicFloat) -> SymbolicInt: @overload -def floor(x: float) -> int: - ... +def floor(x: float) -> int: ... @overload -def floor(x: sympy.Expr) -> sympy.Expr: - ... +def floor(x: sympy.Expr) -> sympy.Expr: ... def floor(x: SymbolicFloat) -> SymbolicInt: @@ -162,13 +139,11 @@ def floor(x: SymbolicFloat) -> SymbolicInt: @overload -def bit_length(x: float) -> int: - ... +def bit_length(x: float) -> int: ... @overload -def bit_length(x: sympy.Expr) -> sympy.Expr: - ... +def bit_length(x: sympy.Expr) -> sympy.Expr: ... def bit_length(x: SymbolicFloat) -> SymbolicInt: @@ -253,13 +228,11 @@ def ssum(args: Iterable[SymbolicT]) -> SymbolicT: @overload -def acos(x: float) -> float: - ... +def acos(x: float) -> float: ... @overload -def acos(x: sympy.Expr) -> sympy.Expr: - ... +def acos(x: sympy.Expr) -> sympy.Expr: ... def acos(x: SymbolicFloat) -> SymbolicFloat: @@ -269,13 +242,11 @@ def acos(x: SymbolicFloat) -> SymbolicFloat: @overload -def sconj(x: complex) -> complex: - ... +def sconj(x: complex) -> complex: ... @overload -def sconj(x: sympy.Expr) -> sympy.Expr: - ... +def sconj(x: sympy.Expr) -> sympy.Expr: ... def sconj(x: SymbolicComplex) -> SymbolicComplex: @@ -283,38 +254,6 @@ def sconj(x: SymbolicComplex) -> SymbolicComplex: return sympy.conjugate(x) if is_symbolic(x) else np.conjugate(x) -@overload -def slen(x: Sized) -> int: - ... - - -@overload -def slen(x: Union[Shaped, HasLength]) -> sympy.Expr: - ... - - -def slen(x: Union[Sized, Shaped, HasLength]) -> SymbolicInt: - if isinstance(x, Shaped): - return x.shape[0] - if isinstance(x, HasLength): - return x.n - return len(x) - - -@overload -def shape(x: np.ndarray) -> Tuple[int, ...]: - ... - - -@overload -def shape(x: Shaped) -> Tuple[SymbolicInt, ...]: - ... - - -def shape(x: Union[np.ndarray, Shaped]): - return x.shape - - def is_zero(x: SymbolicInt) -> bool: """check if a symbolic integer is zero diff --git a/qualtran/symbolics/math_funcs_test.py b/qualtran/symbolics/math_funcs_test.py index 993006d83..78be4eac0 100644 --- a/qualtran/symbolics/math_funcs_test.py +++ b/qualtran/symbolics/math_funcs_test.py @@ -18,19 +18,7 @@ import sympy from sympy.codegen.cfunctions import log2 as sympy_log2 -from qualtran.symbolics import ( - bit_length, - ceil, - is_symbolic, - is_zero, - log2, - sarg, - sexp, - Shaped, - slen, - smax, - smin, -) +from qualtran.symbolics import bit_length, ceil, is_zero, log2, sarg, sexp, smax, smin def test_log2(): @@ -130,16 +118,6 @@ def test_bit_length_symbolic_simplify(): assert b.subs({N: 2**n}) == n -@pytest.mark.parametrize( - "shape", - [(4,), (1, 2), (1, 2, 3), (sympy.Symbol('n'),), (sympy.Symbol('n'), sympy.Symbol('m'), 100)], -) -def test_shaped(shape: tuple[int, ...]): - shaped = Shaped(shape=shape) - assert is_symbolic(shaped) - assert slen(shaped) == shape[0] - - def test_is_zero(): assert is_zero(0) assert not is_zero(1) diff --git a/qualtran/symbolics/types.py b/qualtran/symbolics/types.py index 3bc42e229..e971db857 100644 --- a/qualtran/symbolics/types.py +++ b/qualtran/symbolics/types.py @@ -11,34 +11,41 @@ # 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. -from typing import overload, TypeVar, Union +from typing import overload, Sized, TypeVar, Union +import numpy as np import sympy from attrs import field, frozen, validators -from cirq._doc import document from typing_extensions import TypeIs SymbolicFloat = Union[float, sympy.Expr] -document(SymbolicFloat, """A floating point value or a sympy expression.""") +"""A floating point value or a sympy expression.""" SymbolicInt = Union[int, sympy.Expr] -document(SymbolicFloat, """A floating point value or a sympy expression.""") +"""An integer value or a sympy expression.""" SymbolicComplex = Union[complex, sympy.Expr] -document(SymbolicComplex, """A complex value or a sympy expression.""") +"""A complex value or a sympy expression.""" @frozen class Shaped: """Symbolic value for an object that has a shape. - A Shaped object can be used as a symbolic replacement for any object that has an attribute `shape`, - for example numpy NDArrays. - Each dimension can be either an positive integer value or a sympy expression. + A Shaped object can be used as a symbolic replacement for any object that has an + attribute `shape`, for example numpy `NDArrays`. Each dimension can be either + a positive integer value or a sympy expression. - This is useful to do symbolic analysis of Bloqs whose call graph only depends on the shape of the input, - but not on the actual values. - For example, T-cost of the `QROM` Bloq depends only on the iteration length (shape) and not on actual data values. + For the symbolic variant of a tuple or sequence of values, see `HasLength`. + + This is useful to do symbolic analysis of Bloqs whose call graph only depends on the shape + of the input, but not on the actual values. For example, T-cost of the `QROM` Bloq depends + only on the iteration length (shape) and not on actual data values. In this case, for the + bloq attribute `data`, we can use the type: + + ```py + data: Union[NDArray, Shaped] + ``` """ shape: tuple[SymbolicInt, ...] = field(validator=validators.instance_of(tuple)) @@ -51,6 +58,15 @@ def is_symbolic(self): class HasLength: """Symbolic value for an object that has a length. + This is used as a "symbolic" tuple. The length can either be a positive integer + or a sympy expression. For example, if a bloq attribute is a tuple of ints, + we can use the type: + + ```py + values: Union[tuple, HasLength] + ``` + + For the symbolic variant of a NDArray, see `Shaped`. Note that we cannot override __len__ and return a sympy symbol because Python has special treatment for __len__ and expects you to return a non-negative integers. @@ -64,19 +80,45 @@ def is_symbolic(self): return True +@overload +def slen(x: Sized) -> int: ... + + +@overload +def slen(x: Union[Shaped, HasLength]) -> sympy.Expr: ... + + +def slen(x: Union[Sized, Shaped, HasLength]) -> SymbolicInt: + if isinstance(x, Shaped): + return x.shape[0] + if isinstance(x, HasLength): + return x.n + return len(x) + + +@overload +def shape(x: np.ndarray) -> tuple[int, ...]: ... + + +@overload +def shape(x: Shaped) -> tuple[SymbolicInt, ...]: ... + + +def shape(x: Union[np.ndarray, Shaped]): + return x.shape + + T = TypeVar('T') @overload def is_symbolic( arg: Union[T, sympy.Expr, Shaped, HasLength], / -) -> TypeIs[Union[sympy.Expr, Shaped, HasLength]]: - ... +) -> TypeIs[Union[sympy.Expr, Shaped, HasLength]]: ... @overload -def is_symbolic(*args) -> bool: - ... +def is_symbolic(*args) -> bool: ... def is_symbolic(*args) -> Union[TypeIs[Union[sympy.Expr, Shaped, HasLength]], bool]: diff --git a/qualtran/symbolics/types_test.py b/qualtran/symbolics/types_test.py new file mode 100644 index 000000000..8e6656119 --- /dev/null +++ b/qualtran/symbolics/types_test.py @@ -0,0 +1,28 @@ +# 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 +import sympy + +from qualtran.symbolics import is_symbolic, Shaped, slen + + +@pytest.mark.parametrize( + "shape", + [(4,), (1, 2), (1, 2, 3), (sympy.Symbol('n'),), (sympy.Symbol('n'), sympy.Symbol('m'), 100)], +) +def test_shaped(shape: tuple[int, ...]): + shaped = Shaped(shape=shape) + assert is_symbolic(shaped) + assert slen(shaped) == shape[0] diff --git a/qualtran/testing.py b/qualtran/testing.py index 31080e993..6427f32e8 100644 --- a/qualtran/testing.py +++ b/qualtran/testing.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Functions for testing bloqs.""" + import itertools import traceback from enum import Enum @@ -20,6 +22,7 @@ import numpy as np import sympy +from numpy.typing import NDArray from qualtran import ( Bloq, @@ -692,7 +695,9 @@ def check_bloq_example_qtyping(bloq_ex: BloqExample) -> Tuple[BloqCheckResult, s return BloqCheckResult.PASS, '' -def assert_consistent_classical_action(bloq: Bloq, **parameter_ranges: Sequence[int]): +def assert_consistent_classical_action( + bloq: Bloq, **parameter_ranges: Union[NDArray, Sequence[int]] +): """Check that the bloq has a classical action consistent with its decomposition. Args: @@ -705,4 +710,6 @@ def assert_consistent_classical_action(bloq: Bloq, **parameter_ranges: Sequence[ call_with = {p: v for p, v in zip(parameter_names, vals)} bloq_res = bloq.call_classically(**call_with) decomposed_res = cb.call_classically(**call_with) - assert bloq_res == decomposed_res, f'{bloq=} {call_with=} {bloq_res=} {decomposed_res=}' + np.testing.assert_equal( + bloq_res, decomposed_res, err_msg=f'{bloq=} {call_with=} {bloq_res=} {decomposed_res=}' + )