From 47a36c8179488a615704198a71f9f2b2afcf977d Mon Sep 17 00:00:00 2001 From: Fionn Malone Date: Thu, 12 Dec 2024 10:00:52 -0800 Subject: [PATCH] First pass at fixing chemistry costs to use QECGatesCost (#1505) --- .../qualtran_dev_tools/notebook_specs.py | 2 + .../first_quantization.ipynb | 113 ++++++++++++++++++ .../pbc/first_quantization/prepare_t_test.py | 15 +-- .../pbc/first_quantization/prepare_zeta.py | 21 +++- .../first_quantization/prepare_zeta_test.py | 6 +- .../projectile/prepare_t_test.py | 8 +- .../projectile/select_t_test.py | 6 +- .../pbc/first_quantization/select_t_test.py | 6 +- .../bloqs/chemistry/resource_estimation.ipynb | 17 ++- .../trotter/grid_ham/inverse_sqrt_test.py | 1 - .../chemistry/trotter/hubbard/hopping_test.py | 30 ++--- .../chemistry/trotter/hubbard/hubbard.ipynb | 2 +- .../chemistry/trotter/hubbard/interaction.py | 2 +- .../trotter/hubbard/interaction_test.py | 15 ++- .../hubbard/qpe_cost_optimization.ipynb | 72 +++++------ .../trotter/hubbard/trotter_step_test.py | 23 +--- .../bloqs/chemistry/writing_algorithms.ipynb | 37 +++--- 17 files changed, 236 insertions(+), 140 deletions(-) diff --git a/dev_tools/qualtran_dev_tools/notebook_specs.py b/dev_tools/qualtran_dev_tools/notebook_specs.py index f0b156b58..292f56869 100644 --- a/dev_tools/qualtran_dev_tools/notebook_specs.py +++ b/dev_tools/qualtran_dev_tools/notebook_specs.py @@ -66,6 +66,7 @@ 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.prepare_zeta 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 @@ -311,6 +312,7 @@ 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, + qualtran.bloqs.chemistry.pbc.first_quantization.prepare_zeta._PREPARE_ZETA, ], directory=f'{SOURCE_DIR}/bloqs/chemistry/pbc/first_quantization', ), diff --git a/qualtran/bloqs/chemistry/pbc/first_quantization/first_quantization.ipynb b/qualtran/bloqs/chemistry/pbc/first_quantization/first_quantization.ipynb index 69a0ea74a..b1de1dbf2 100644 --- a/qualtran/bloqs/chemistry/pbc/first_quantization/first_quantization.ipynb +++ b/qualtran/bloqs/chemistry/pbc/first_quantization/first_quantization.ipynb @@ -822,6 +822,119 @@ "show_call_graph(select_uv_g)\n", "show_counts_sigma(select_uv_sigma)" ] + }, + { + "cell_type": "markdown", + "id": "69c98b9f", + "metadata": { + "cq.autogen": "PrepareZetaState.bloq_doc.md" + }, + "source": [ + "## `PrepareZetaState`\n", + "PREPARE the superpostion over $l$ weighted by $\\zeta_l$.\n", + "\n", + "See https://github.com/quantumlib/Qualtran/issues/473.\n", + "#### Parameters\n", + " - `num_bits_p`: The number of bits to represent each dimension of the momentum register.\n", + " - `eta`: The number of electrons.\n", + " - `m_param`: $\\mathcal{M}$ in the reference.\n", + " - `lambda_zeta`: sum of nuclear charges. \n", + "\n", + "#### Registers\n", + " - `l`: the register indexing the atomic number. \n", + "\n", + "#### References\n", + " - [Fault-Tolerant Quantum Simulations of Chemistry in First Quantization](https://arxiv.org/abs/2105.12767). page 23-24, last 3 paragraphs.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "074bc1b8", + "metadata": { + "cq.autogen": "PrepareZetaState.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.chemistry.pbc.first_quantization.prepare_zeta import PrepareZetaState" + ] + }, + { + "cell_type": "markdown", + "id": "fd3071f7", + "metadata": { + "cq.autogen": "PrepareZetaState.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "31ee49a4", + "metadata": { + "cq.autogen": "PrepareZetaState.prepare_zeta" + }, + "outputs": [], + "source": [ + "num_atoms = 10\n", + "lambda_zeta = 10\n", + "num_bits_nuc_pos = 8\n", + "\n", + "prepare_zeta = PrepareZetaState(\n", + " num_atoms=num_atoms, lambda_zeta=lambda_zeta, num_bits_nuc_pos=num_bits_nuc_pos\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "4fb19c71", + "metadata": { + "cq.autogen": "PrepareZetaState.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6498eee0", + "metadata": { + "cq.autogen": "PrepareZetaState.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([prepare_zeta],\n", + " ['`prepare_zeta`'])" + ] + }, + { + "cell_type": "markdown", + "id": "1e644753", + "metadata": { + "cq.autogen": "PrepareZetaState.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fbef8e6e", + "metadata": { + "cq.autogen": "PrepareZetaState.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "prepare_zeta_g, prepare_zeta_sigma = prepare_zeta.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(prepare_zeta_g)\n", + "show_counts_sigma(prepare_zeta_sigma)" + ] } ], "metadata": { diff --git a/qualtran/bloqs/chemistry/pbc/first_quantization/prepare_t_test.py b/qualtran/bloqs/chemistry/pbc/first_quantization/prepare_t_test.py index f495ff791..afa581df1 100644 --- a/qualtran/bloqs/chemistry/pbc/first_quantization/prepare_t_test.py +++ b/qualtran/bloqs/chemistry/pbc/first_quantization/prepare_t_test.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from qualtran.bloqs.basic_gates import Toffoli from qualtran.bloqs.chemistry.pbc.first_quantization.prepare import ( UniformSuperpostionIJFirstQuantization, ) @@ -20,6 +19,7 @@ _prepare_t, PrepareTFirstQuantization, ) +from qualtran.resource_counting import get_cost_value, QECGatesCost def test_prepare_t(bloq_autotester): @@ -33,15 +33,12 @@ def test_prepare_kinetic_t_counts(): n_eta = (eta - 1).bit_length() expected_cost = (14 * n_eta + 8 * b_r - 36) + 2 * (2 * num_bits_p + 9) uni = UniformSuperpostionIJFirstQuantization(eta, num_bits_rot_aa=b_r) - _, counts = uni.call_graph() - qual_cost = counts[Toffoli()] + + qual_cost = get_cost_value(uni, QECGatesCost()).total_toffoli_only() uni = UniformSuperpostionIJFirstQuantization(eta, num_bits_rot_aa=b_r).adjoint() - _, counts = uni.call_graph() - qual_cost += counts[Toffoli()] + qual_cost += get_cost_value(uni, QECGatesCost()).total_toffoli_only() prep = PrepareTFirstQuantization(num_bits_p, eta, num_bits_rot_aa=b_r) - _, counts = prep.call_graph() - qual_cost += counts[Toffoli()] + qual_cost += get_cost_value(prep, QECGatesCost()).total_toffoli_only() prep = PrepareTFirstQuantization(num_bits_p, eta, num_bits_rot_aa=b_r).adjoint() - _, counts = prep.call_graph() - qual_cost += counts[Toffoli()] + qual_cost += get_cost_value(prep, QECGatesCost()).total_toffoli_only() assert qual_cost == expected_cost diff --git a/qualtran/bloqs/chemistry/pbc/first_quantization/prepare_zeta.py b/qualtran/bloqs/chemistry/pbc/first_quantization/prepare_zeta.py index de328009e..dc76e1ccf 100644 --- a/qualtran/bloqs/chemistry/pbc/first_quantization/prepare_zeta.py +++ b/qualtran/bloqs/chemistry/pbc/first_quantization/prepare_zeta.py @@ -19,7 +19,7 @@ import numpy as np from attrs import evolve, frozen -from qualtran import Bloq, QAny, Register, Signature +from qualtran import Bloq, bloq_example, BloqDocSpec, QAny, Register, Signature from qualtran.bloqs.basic_gates import Toffoli if TYPE_CHECKING: @@ -65,3 +65,22 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': else: return {Toffoli(): self.lambda_zeta} + + +@bloq_example +def _prepare_zeta() -> PrepareZetaState: + num_atoms = 10 + lambda_zeta = 10 + num_bits_nuc_pos = 8 + + prepare_zeta = PrepareZetaState( + num_atoms=num_atoms, lambda_zeta=lambda_zeta, num_bits_nuc_pos=num_bits_nuc_pos + ) + return prepare_zeta + + +_PREPARE_ZETA = BloqDocSpec( + bloq_cls=PrepareZetaState, + import_line='from qualtran.bloqs.chemistry.pbc.first_quantization.prepare_zeta import PrepareZetaState', + examples=(_prepare_zeta,), +) diff --git a/qualtran/bloqs/chemistry/pbc/first_quantization/prepare_zeta_test.py b/qualtran/bloqs/chemistry/pbc/first_quantization/prepare_zeta_test.py index 212f1adba..55cb3afc0 100644 --- a/qualtran/bloqs/chemistry/pbc/first_quantization/prepare_zeta_test.py +++ b/qualtran/bloqs/chemistry/pbc/first_quantization/prepare_zeta_test.py @@ -11,8 +11,8 @@ # 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.chemistry.pbc.first_quantization.prepare_zeta import PrepareZetaState +from qualtran.bloqs.chemistry.pbc.first_quantization.prepare_zeta import _prepare_zeta -def test_uniform_superposition_ij(): - prep = PrepareZetaState(num_atoms=10, lambda_zeta=20, num_bits_nuc_pos=8) +def test_prepare_zeta(bloq_autotester): + bloq_autotester(_prepare_zeta) diff --git a/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/prepare_t_test.py b/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/prepare_t_test.py index ba3c715a7..a250da50e 100644 --- a/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/prepare_t_test.py +++ b/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/prepare_t_test.py @@ -14,13 +14,13 @@ import pytest -from qualtran.bloqs.basic_gates import Toffoli from qualtran.bloqs.chemistry.pbc.first_quantization.projectile.prepare_t import ( _prep_power_two_proj, _prep_t_proj, PreparePowerTwoStateWithProj, PrepareTFirstQuantizationWithProj, ) +from qualtran.resource_counting import get_cost_value, QECGatesCost def test_prep_t_proj(bloq_autotester): @@ -39,13 +39,11 @@ def test_prepare_kinetic_t_proj_counts(): expected_cost = 2 * (2 * num_bits_n + 9) + 2 * (num_bits_n - num_bits_p) + 20 qual_cost = 0 prep = PrepareTFirstQuantizationWithProj(num_bits_p, num_bits_n, eta, num_bits_rot_aa=b_r) - _, counts = prep.call_graph() - qual_cost += counts[Toffoli()] + qual_cost += get_cost_value(prep, QECGatesCost()).total_toffoli_only() prep = PrepareTFirstQuantizationWithProj( num_bits_p, num_bits_n, eta, num_bits_rot_aa=b_r ).adjoint() - _, counts = prep.call_graph() - qual_cost += counts[Toffoli()] + qual_cost += get_cost_value(prep, QECGatesCost()).total_toffoli_only() assert qual_cost == expected_cost diff --git a/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/select_t_test.py b/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/select_t_test.py index b654c30ac..f0752fda4 100644 --- a/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/select_t_test.py +++ b/qualtran/bloqs/chemistry/pbc/first_quantization/projectile/select_t_test.py @@ -11,11 +11,11 @@ # 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.basic_gates import Toffoli from qualtran.bloqs.chemistry.pbc.first_quantization.projectile.select_t import ( _sel_t_proj, SelectTFirstQuantizationWithProj, ) +from qualtran.resource_counting import get_cost_value, QECGatesCost def test_sel_t_proj(bloq_autotester): @@ -25,5 +25,5 @@ def test_sel_t_proj(bloq_autotester): def test_select_kinetic_t_counts(): num_bits_n = 6 sel = SelectTFirstQuantizationWithProj(num_bits_n, 10) - _, counts = sel.call_graph() - assert counts[Toffoli()] == 5 * (num_bits_n - 1) + 2 + 1 + toffolis = get_cost_value(sel, QECGatesCost()).total_toffoli_only() + assert toffolis == 5 * (num_bits_n - 1) + 2 + 1 diff --git a/qualtran/bloqs/chemistry/pbc/first_quantization/select_t_test.py b/qualtran/bloqs/chemistry/pbc/first_quantization/select_t_test.py index 46f1cd2a4..58dec6f7e 100644 --- a/qualtran/bloqs/chemistry/pbc/first_quantization/select_t_test.py +++ b/qualtran/bloqs/chemistry/pbc/first_quantization/select_t_test.py @@ -11,11 +11,11 @@ # 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.basic_gates import Toffoli from qualtran.bloqs.chemistry.pbc.first_quantization.select_t import ( _select_t, SelectTFirstQuantization, ) +from qualtran.resource_counting import get_cost_value, QECGatesCost def test_select_t(bloq_autotester): @@ -25,5 +25,5 @@ def test_select_t(bloq_autotester): def test_select_kinetic_t_counts(): num_bits_p = 6 sel = SelectTFirstQuantization(num_bits_p, 10) - _, counts = sel.call_graph() - assert counts[Toffoli()] == 5 * (num_bits_p - 1) + 2 + toffolis = get_cost_value(sel, QECGatesCost()).total_toffoli_only() + assert toffolis == 5 * (num_bits_p - 1) + 2 diff --git a/qualtran/bloqs/chemistry/resource_estimation.ipynb b/qualtran/bloqs/chemistry/resource_estimation.ipynb index 9218086ee..39585e49e 100644 --- a/qualtran/bloqs/chemistry/resource_estimation.ipynb +++ b/qualtran/bloqs/chemistry/resource_estimation.ipynb @@ -84,7 +84,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "2fb17f7f", "metadata": {}, "outputs": [], @@ -113,7 +113,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "b9991178", "metadata": {}, "outputs": [], @@ -202,7 +202,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "5efd66ae", "metadata": {}, "outputs": [], @@ -305,7 +305,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "id": "614bfb21", "metadata": {}, "outputs": [], @@ -346,9 +346,8 @@ "metadata": {}, "outputs": [], "source": [ - "_, sigma = df_bloq.call_graph()\n", "refl_cost_and_qpe = (df_bloq.num_aux -1).bit_length() + (num_spin_orb // 2 - 1).bit_length() + num_bits_state_prep + 1 + 2\n", - "print(f'qualtran cost = {sigma[TGate()] // 4} vs paper = {21753 - refl_cost_and_qpe}')" + "print(f'qualtran cost = {get_toffoli_counts(df_bloq)} vs paper = {21753 - refl_cost_and_qpe}')" ] }, { @@ -363,7 +362,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "id": "349b70ab", "metadata": {}, "outputs": [], @@ -384,7 +383,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "id": "2ef506ab", "metadata": {}, "outputs": [], @@ -411,7 +410,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "id": "e34dcb1c", "metadata": {}, "outputs": [], 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 a1af126a1..43ecf9c31 100644 --- a/qualtran/bloqs/chemistry/trotter/grid_ham/inverse_sqrt_test.py +++ b/qualtran/bloqs/chemistry/trotter/grid_ham/inverse_sqrt_test.py @@ -41,7 +41,6 @@ def test_newton_raphson_inverse_sqrt_bloq_counts(): poly_bitsize = 15 target_bitsize = 22 bloq = NewtonRaphsonApproxInverseSquareRoot(int_bitsize, poly_bitsize, target_bitsize) - _, counts = bloq.call_graph() cost_square = poly_bitsize**2 // 2 - 4 cost_scale = poly_bitsize * (2 * int_bitsize - 1) - int_bitsize**2 cost_mult = 2 * (target_bitsize**2 - target_bitsize - 1) diff --git a/qualtran/bloqs/chemistry/trotter/hubbard/hopping_test.py b/qualtran/bloqs/chemistry/trotter/hubbard/hopping_test.py index 652664817..006c1c6c3 100644 --- a/qualtran/bloqs/chemistry/trotter/hubbard/hopping_test.py +++ b/qualtran/bloqs/chemistry/trotter/hubbard/hopping_test.py @@ -11,15 +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 qualtran import Bloq -from qualtran.bloqs.basic_gates import Rz, TGate, ZPowGate -from qualtran.bloqs.bookkeeping import ArbitraryClifford from qualtran.bloqs.chemistry.trotter.hubbard.hopping import ( _hopping_tile, _hopping_tile_hwp, _plaquette, ) -from qualtran.resource_counting.generalizers import PHI +from qualtran.resource_counting import get_cost_value, QECGatesCost def test_hopping_tile(bloq_autotester): @@ -30,27 +27,18 @@ def test_hopping_plaquette(bloq_autotester): bloq_autotester(_plaquette) -def catch_rotations(bloq) -> Bloq: - if isinstance(bloq, (Rz, ZPowGate)): - if isinstance(bloq, ZPowGate): - return Rz(angle=PHI) - elif abs(float(bloq.angle)) < 1e-12: - return ArbitraryClifford(1) - else: - return Rz(angle=PHI) - return bloq - - def test_hopping_tile_t_counts(): bloq = _hopping_tile() - _, counts = bloq.call_graph(generalizer=catch_rotations) - assert counts[TGate()] == 8 * bloq.length**2 // 2 - assert counts[Rz(PHI)] == 2 * bloq.length**2 // 2 + costs = get_cost_value(bloq, QECGatesCost()) + assert costs.t == 8 * bloq.length**2 // 2 + assert costs.rotation == 2 * bloq.length**2 // 2 def test_hopping_tile_hwp_t_counts(): bloq = _hopping_tile_hwp() - _, counts = bloq.call_graph(generalizer=catch_rotations) + costs = get_cost_value(bloq, QECGatesCost()) 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 costs.rotation == 2 * n_rot_par.bit_length() + assert costs.total_t_count(ts_per_rotation=0) == 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 54fab69e6..0d46f45fd 100644 --- a/qualtran/bloqs/chemistry/trotter/hubbard/hubbard.ipynb +++ b/qualtran/bloqs/chemistry/trotter/hubbard/hubbard.ipynb @@ -616,7 +616,7 @@ "outputs": [], "source": [ "length = 8\n", - "angle = 0.5\n", + "angle = 0.52728\n", "hubb_u = 4.0\n", "interaction_hwp = InteractionHWP(length, angle, hubb_u)" ] diff --git a/qualtran/bloqs/chemistry/trotter/hubbard/interaction.py b/qualtran/bloqs/chemistry/trotter/hubbard/interaction.py index 9783ee833..5a1d23cd0 100644 --- a/qualtran/bloqs/chemistry/trotter/hubbard/interaction.py +++ b/qualtran/bloqs/chemistry/trotter/hubbard/interaction.py @@ -130,7 +130,7 @@ def _interaction() -> Interaction: @bloq_example def _interaction_hwp() -> InteractionHWP: length = 8 - angle = 0.5 + angle = 0.52728 hubb_u = 4.0 interaction_hwp = InteractionHWP(length, angle, hubb_u) return interaction_hwp diff --git a/qualtran/bloqs/chemistry/trotter/hubbard/interaction_test.py b/qualtran/bloqs/chemistry/trotter/hubbard/interaction_test.py index 43b0479b5..deeccb2a5 100644 --- a/qualtran/bloqs/chemistry/trotter/hubbard/interaction_test.py +++ b/qualtran/bloqs/chemistry/trotter/hubbard/interaction_test.py @@ -11,10 +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 qualtran.bloqs.basic_gates import Rz, TGate -from qualtran.bloqs.chemistry.trotter.hubbard.hopping_test import catch_rotations + from qualtran.bloqs.chemistry.trotter.hubbard.interaction import _interaction, _interaction_hwp -from qualtran.resource_counting.generalizers import PHI +from qualtran.resource_counting import get_cost_value, QECGatesCost def test_hopping_tile(bloq_autotester): @@ -27,14 +26,14 @@ def test_interaction_hwp(bloq_autotester): def test_interaction_hwp_bloq_counts(): bloq = _interaction_hwp() - _, counts = bloq.call_graph(generalizer=catch_rotations) + costs = get_cost_value(bloq, QECGatesCost()) n_rot_par = bloq.length**2 // 2 - assert counts[Rz(PHI)] == 2 * n_rot_par.bit_length() - assert counts[TGate()] == 2 * 4 * (n_rot_par - n_rot_par.bit_count()) + assert costs.rotation == 2 * n_rot_par.bit_length() + assert costs.total_t_count(ts_per_rotation=0) == 2 * 4 * (n_rot_par - n_rot_par.bit_count()) def test_interaction_bloq_counts(): bloq = _interaction() - _, counts = bloq.call_graph(generalizer=catch_rotations) + costs = get_cost_value(bloq, QECGatesCost()) n_rot = bloq.length**2 - assert counts[Rz(PHI)] == n_rot + assert costs.rotation == n_rot diff --git a/qualtran/bloqs/chemistry/trotter/hubbard/qpe_cost_optimization.ipynb b/qualtran/bloqs/chemistry/trotter/hubbard/qpe_cost_optimization.ipynb index 6f4e10cdd..ef5a26f31 100644 --- a/qualtran/bloqs/chemistry/trotter/hubbard/qpe_cost_optimization.ipynb +++ b/qualtran/bloqs/chemistry/trotter/hubbard/qpe_cost_optimization.ipynb @@ -106,36 +106,21 @@ "metadata": {}, "outputs": [], "source": [ - "from typing import Dict, Union, Tuple\n", + "from typing import Tuple\n", "\n", "import numpy as np\n", "import sympy\n", + "import attrs\n", "\n", - "from qualtran.resource_counting.classify_bloqs import bloq_is_rotation\n", - "from qualtran.resource_counting.generalizers import PHI\n", "from qualtran.cirq_interop.t_complexity_protocol import TComplexity\n", - "from qualtran import Bloq\n", - "from qualtran.bloqs.basic_gates import TGate, Rz\n", - "from qualtran.bloqs.bookkeeping import ArbitraryClifford\n", - "\n", - "\n", - "def catch_rotations(bloq) -> Bloq:\n", - " \"\"\"Generalizer to catch rotations.\"\"\"\n", - " if isinstance(bloq, Rz):\n", - " if isinstance(bloq.angle, float) and abs(bloq.angle) < 1e-12:\n", - " return ArbitraryClifford(1)\n", - " else:\n", - " return Rz(angle=PHI, eps=bloq.eps)\n", - " return bloq\n", + "from qualtran.resource_counting import get_cost_value, QECGatesCost\n", "\n", "\n", - "def t_and_rot_counts_from_sigma(sigma: Dict['Bloq', Union[int, 'sympy.Expr']]) -> Tuple[int, int]:\n", - " ret = sigma.get(TGate(), 0)\n", - " n_rot = 0\n", - " for bloq, counts in sigma.items():\n", - " if bloq_is_rotation(bloq):\n", - " n_rot += counts\n", - " return ret, n_rot\n", + "def t_and_rot_counts_from_bloq(bloq) -> Tuple[int, int]:\n", + " costs = get_cost_value(bloq, QECGatesCost())\n", + " n_rot = costs.rotation\n", + " n_t = costs.total_t_count(ts_per_rotation=0)\n", + " return n_t, n_rot\n", "\n", "\n", "def timestep_from_params(delta_ts: float, xi: float, prod_ord: int) -> float:\n", @@ -265,7 +250,7 @@ "from qualtran.bloqs.chemistry.trotter.hubbard.trotter_step import build_plaq_unitary_second_order_suzuki\n", "\n", "trotter_step = build_plaq_unitary_second_order_suzuki(length, hubb_u, timestep, eps=1e-10)\n", - "n_t, n_rot = t_and_rot_counts_from_sigma(trotter_step.call_graph(generalizer=catch_rotations)[1])\n", + "n_t, n_rot = t_and_rot_counts_from_bloq(trotter_step)\n", "print(f\"N_T = {n_t} vs {(3*length**2 // 2)*8}\")\n", "print(f\"N_rot = {n_rot} vs {(3 * length**2 + 2*length**2)}\")" ] @@ -283,8 +268,8 @@ "metadata": {}, "outputs": [], "source": [ - "import attrs\n", "from qualtran.drawing import show_call_graph\n", + "from qualtran.resource_counting.generalizers import generalize_rotation_angle \n", "# get appropriate epsilon given our input parameters now we know the number of rotations\n", "eps_single_rot = get_single_rot_eps(n_rot, delta_ht, timestep)\n", "print(f\"Adjusted eps_single_rot: {eps_single_rot}\")\n", @@ -294,7 +279,7 @@ "# But let's show the call graph anyway to check the parameters all all what we expect.\n", "updated_eps_bloqs = tuple(attrs.evolve(b, eps=eps_single_rot) for b in trotter_step.bloqs)\n", "trotter_step = attrs.evolve(trotter_step, bloqs=updated_eps_bloqs)\n", - "trotter_step_g, _ = trotter_step.call_graph(generalizer=catch_rotations)\n", + "trotter_step_g, _ = trotter_step.call_graph(generalizer=generalize_rotation_angle)\n", "show_call_graph(trotter_step_g)" ] }, @@ -418,7 +403,7 @@ "metadata": {}, "outputs": [], "source": [ - "from scipy.optimize import minimize, bisect, newton\n", + "from scipy.optimize import minimize\n", "def objective(delta_ts, delta_ht, n_rot, n_t, xi_bound, prod_ord):\n", " t_counts = qpe_t_count(epsilon - delta_ts - delta_ht, delta_ts, delta_ht, n_rot, n_t, xi_bound, prod_ord)\n", " return t_counts\n", @@ -467,7 +452,7 @@ "source": [ "from qualtran.bloqs.chemistry.trotter.hubbard.trotter_step import build_plaq_hwp_unitary_second_order_suzuki\n", "trotter_step_hwp = build_plaq_hwp_unitary_second_order_suzuki(length, hubb_u, timestep, eps=1e-10)\n", - "n_t_hwp, n_rot_hwp = t_and_rot_counts_from_sigma(trotter_step_hwp.call_graph(generalizer=catch_rotations)[1])\n", + "n_t_hwp, n_rot_hwp = t_and_rot_counts_from_bloq(trotter_step_hwp)\n", "print(f\"N_T(HWP) = {n_t_hwp} vs {(3*length**2 // 2)*8}\")\n", "print(f\"N_rot(HWP) = {n_rot_hwp} vs {(3 * length**2 + 2*length**2)}\")\n", "delta_ht_opt, delta_ts_opt, delta_pe_opt, t_opt = minimize_linesearch(n_rot_hwp, n_t_hwp, xi_bound, prod_ord)\n", @@ -490,7 +475,7 @@ "outputs": [], "source": [ "trotter_step_hwp = build_plaq_hwp_unitary_second_order_suzuki(length, hubb_u, timestep, eps=1e-10, strip_layer=True)\n", - "n_t_hwp, n_rot_hwp = t_and_rot_counts_from_sigma(trotter_step_hwp.call_graph(generalizer=catch_rotations)[1])\n", + "n_t_hwp, n_rot_hwp = t_and_rot_counts_from_bloq(trotter_step_hwp)\n", "print(f\"N_T(HWP) = {n_t_hwp}\")\n", "print(f\"N_rot(HWP) = {n_rot_hwp}\")\n", "delta_ht_opt, delta_ts_opt, delta_pe_opt, t_opt = minimize_linesearch(n_rot_hwp, n_t_hwp, xi_bound, prod_ord)\n", @@ -513,14 +498,18 @@ "metadata": {}, "outputs": [], "source": [ - "s_eps_r, s_length, s_hubb_u, s_timestep, s_tau = sympy.symbols(r'\\epsilon_{R}, L, u, t, \\tau')" + "s_eps_r, s_length, s_hubb_u, s_timestep, s_tau = sympy.symbols(r'\\epsilon_{R}, L, u, t, \\tau')\n", + "s_delta_ht, s_delta_ts, s_delta_pe, s_p, s_xi = sympy.symbols(\n", + " '\\Delta_{HT}, \\Delta_{TS}, \\Delta_{PE}, p, xi'\n", + ")\n", + "s_n_rot, s_n_t, s_n_pe = sympy.symbols('N_R, N_T, N_PE')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "First let's check the Bloq counts look correct for the Trotter step, there are two sources rotations from the interaction and hopping bloq and some direct T gates from the `TwoBitFFFT` gate." + "First let's check the Bloq counts look correct for the Trotter step, there are two sources: rotations from the interaction and hopping bloq, and some direct T gates from the `TwoBitFFFT` gate." ] }, { @@ -534,8 +523,8 @@ "s_trotter_step = build_plaq_unitary_second_order_suzuki(\n", " s_length, s_hubb_u, s_timestep, eps=s_eps_r, hubb_t=s_tau\n", ")\n", - "t_counts = t_counts_from_sigma(s_trotter_step.call_graph(generalizer=catch_rotations)[1])\n", - "t_counts" + "t_counts, n_rot = t_and_rot_counts_from_bloq(s_trotter_step)\n", + "t_counts + rotation_cost(n_rot, s_delta_ht, s_timestep)" ] }, { @@ -545,7 +534,7 @@ "outputs": [], "source": [ "# check the symbolic counts match the expected counts\n", - "t_counts_orig = t_counts_from_sigma(trotter_step.call_graph(generalizer=catch_rotations)[1])\n", + "t_counts_orig, n_rot = t_and_rot_counts_from_bloq(trotter_step)\n", "# for some reason substituting both at once leads to a precision error\n", "t_counts = t_counts.evalf(subs={s_eps_r: eps_single_rot})\n", "t_counts_symb = t_counts.evalf(subs={s_length: length})\n", @@ -565,10 +554,6 @@ "metadata": {}, "outputs": [], "source": [ - "s_delta_ht, s_delta_ts, s_delta_pe, s_p, s_xi = sympy.symbols(\n", - " '\\Delta_{HT}, \\Delta_{TS}, \\Delta_{PE}, p, xi'\n", - ")\n", - "s_n_rot, s_n_t, s_n_pe = sympy.symbols('N_R, N_T, N_PE')\n", "s_timestep = (s_delta_ts / s_xi) ** (1 / s_p)\n", "s_eps_r = (s_delta_ht * s_timestep) / s_n_rot\n", "s_n_pe = 0.76 * sympy.pi / (s_delta_pe * s_timestep)\n", @@ -577,8 +562,9 @@ ")\n", "# just use this cost in lieu of a QPE bloq\n", "# See: https://github.com/quantumlib/Qualtran/issues/932 this should be replaced by a real bloq.\n", - "t_counts = s_n_pe * t_counts_from_sigma(s_trotter_step.call_graph(generalizer=catch_rotations)[1])\n", - "t_counts" + "s_t_counts, s_n_rot = t_and_rot_counts_from_bloq(s_trotter_step)\n", + "qpe_cost_symb = s_n_pe * (s_t_counts + rotation_cost(s_n_rot, s_delta_ht, s_timestep))\n", + "qpe_cost_symb" ] }, { @@ -587,7 +573,7 @@ "metadata": {}, "outputs": [], "source": [ - "symb_t_count = t_counts.evalf(\n", + "symb_t_count = qpe_cost_symb.evalf(\n", " subs={\n", " s_length: length,\n", " s_delta_ht: delta_ht,\n", @@ -597,7 +583,7 @@ " s_n_rot: n_rot,\n", " }\n", ")\n", - "symb_t_count = symb_t_count.evalf(subs={s_p: prod_ord})\n", + "symb_t_count = symb_t_count.evalf(subs={s_p: prod_ord}, maxn=500)\n", "tot_t_count = qpe_t_count(delta_pe, delta_ts, delta_ht, n_rot, n_t, xi_bound, prod_ord)\n", "assert int(symb_t_count) == int(tot_t_count)" ] @@ -619,7 +605,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.7" + "version": "3.10.4" } }, "nbformat": 4, diff --git a/qualtran/bloqs/chemistry/trotter/hubbard/trotter_step_test.py b/qualtran/bloqs/chemistry/trotter/hubbard/trotter_step_test.py index 773235f2b..0bcdb6e32 100644 --- a/qualtran/bloqs/chemistry/trotter/hubbard/trotter_step_test.py +++ b/qualtran/bloqs/chemistry/trotter/hubbard/trotter_step_test.py @@ -13,36 +13,23 @@ # limitations under the License. import pytest -from qualtran import Bloq -from qualtran.bloqs.basic_gates import Rz -from qualtran.bloqs.basic_gates.t_gate import TGate -from qualtran.bloqs.bookkeeping import ArbitraryClifford from qualtran.bloqs.chemistry.trotter.hubbard.trotter_step import ( build_plaq_unitary_second_order_suzuki, ) -from qualtran.resource_counting.generalizers import PHI +from qualtran.resource_counting import get_cost_value, QECGatesCost from qualtran.testing import execute_notebook -def catch_rotations(bloq) -> Bloq: - if isinstance(bloq, Rz): - if isinstance(bloq.angle, float) and abs(bloq.angle) < 1e-12: - return ArbitraryClifford(1) - else: - return Rz(angle=PHI) - return bloq - - def test_second_order_suzuki_costs(): length = 8 u = 4 - dt = 0.1 + dt = 0.1234 unitary = build_plaq_unitary_second_order_suzuki(length, u, dt) - _, sigma = unitary.call_graph(generalizer=catch_rotations) + costs = get_cost_value(unitary, QECGatesCost()) # there are 3 hopping unitaries contributing 8 Ts from from the F gate - assert sigma[TGate()] == (3 * length**2 // 2) * 8 + assert costs.total_t_count(ts_per_rotation=0) == (3 * length**2 // 2) * 8 # 3 hopping unitaries and 2 interaction unitaries - assert sigma[Rz(PHI)] == (3 * length**2 + 2 * length**2) + assert costs.rotation == (3 * length**2 + 2 * length**2) @pytest.mark.notebook diff --git a/qualtran/bloqs/chemistry/writing_algorithms.ipynb b/qualtran/bloqs/chemistry/writing_algorithms.ipynb index f020eef15..262f4e1c4 100644 --- a/qualtran/bloqs/chemistry/writing_algorithms.ipynb +++ b/qualtran/bloqs/chemistry/writing_algorithms.ipynb @@ -180,16 +180,26 @@ "outputs": [], "source": [ "import numpy as np\n", - "from qualtran.bloqs.basic_gates import TGate\n", - "from qualtran.bloqs.chemistry.chem_tutorials import plot_linear_log_log\n", "import matplotlib.pyplot as plt\n", "\n", + "from qualtran import Bloq\n", + "from qualtran.bloqs.chemistry.chem_tutorials import plot_linear_log_log\n", + "from qualtran.resource_counting import get_cost_value, QECGatesCost\n", + "from qualtran.resource_counting.generalizers import generalize_cswap_approx\n", + "\n", + "def toffoli_count(bloq: Bloq) -> int:\n", + " # TODO: The rotations here should be in terms of Toffoli via phase gradient rotation.\n", + " cost = get_cost_value(bloq, QECGatesCost(), generalizer=generalize_cswap_approx).total_t_and_ccz_count(ts_per_rotation=0)\n", + " n_t = cost['n_t']\n", + " assert n_t == 0, \"Found raw T gates, should be only Toffolis\"\n", + " return cost['n_ccz']\n", + "\n", "fig, ax = plt.subplots()\n", "basis_vals = np.linspace(10, 100, 10, dtype=int)\n", - "tcounts = [PrepareSecondQuantization(int(n), qroam_block_size=1).call_graph()[1][TGate()] for n in basis_vals]\n", - "plot_linear_log_log(ax, basis_vals, np.array(tcounts))\n", + "toff_counts = [toffoli_count(PrepareSecondQuantization(int(n), qroam_block_size=1)) for n in basis_vals]\n", + "plot_linear_log_log(ax, basis_vals, np.array(toff_counts))\n", "ax.set_xlabel(\"$N$\")\n", - "ax.set_ylabel(\"$T$ count\")" + "ax.set_ylabel(\"Toffoli count\")" ] }, { @@ -208,12 +218,12 @@ "fig, ax = plt.subplots()\n", "basis_vals = np.linspace(50, 200, 10, dtype=int)\n", "for ib, block_size in enumerate([1, 2, 8, 32, None]):\n", - " tcounts = [PrepareSecondQuantization(int(n), qroam_block_size=block_size).call_graph()[1][TGate()] for n in basis_vals]\n", + " toff_counts = [toffoli_count(PrepareSecondQuantization(int(n), qroam_block_size=block_size)) for n in basis_vals]\n", " if block_size is None:\n", " block_size = 'opt'\n", - " plot_linear_log_log(ax, basis_vals, np.array(tcounts), label=f'block size = {block_size}: ', color=f'C{ib}')\n", + " plot_linear_log_log(ax, basis_vals, np.array(toff_counts), label=f'block size = {block_size}: ', color=f'C{ib}')\n", "ax.set_xlabel(\"$N$\")\n", - "ax.set_ylabel(\"$T$ count\")" + "ax.set_ylabel(\"Tofflis count\")" ] }, { @@ -302,9 +312,8 @@ "from typing import Dict\n", "from qualtran import SoquetT, BloqBuilder, Register\n", "\n", - "from qualtran.bloqs.state_preparation import PrepareUniformSuperposition\n", "from qualtran.bloqs.data_loading.select_swap_qrom import SelectSwapQROM, find_optimal_log_block_size\n", - "from qualtran.bloqs.basic_gates import CSwap, Hadamard, OnEach\n", + "from qualtran.bloqs.basic_gates import Hadamard, OnEach\n", "\n", "\n", "@frozen\n", @@ -519,8 +528,8 @@ "metadata": {}, "outputs": [], "source": [ - "prep_basic = PrepareSecondQuantization(num_spin_orb).call_graph()[1].get(TGate())\n", - "prep_decom = prep.decompose_bloq().call_graph()[1].get(TGate())\n", + "prep_basic = toffoli_count(PrepareSecondQuantization(num_spin_orb))\n", + "prep_decom = toffoli_count(prep.decompose_bloq())\n", "print(f\"call graph = {prep_basic}, decomposition = {prep_decom}\")" ] }, @@ -567,7 +576,7 @@ "alt_pqrs, keep = build_alt_keep_vals(tpq, eris, num_spin_orb, num_bits_state_prep)\n", "prep = PrepareSecondQuantizationDetailed(num_spin_orb, tuple(alt_pqrs), tuple(keep), num_bits_state_prep=num_bits_state_prep)\n", "prep_sparse = PrepareSparse.from_hamiltonian_coeffs(num_spin_orb, tpq, eris, num_bits_state_prep).decompose_bloq()\n", - "print(f\"naive = {prep.call_graph()[1].get(TGate())}, sparse = {prep_sparse.call_graph()[1].get(TGate())}\")" + "print(f\"naive = {toffoli_count(prep)}, sparse = {toffoli_count(prep_sparse)}\")" ] } ], @@ -587,7 +596,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.6" + "version": "3.11.9" } }, "nbformat": 4,