diff --git a/demonstrations_v2/re_how_to_use_pennylane_for_resource_estimation/demo.py b/demonstrations_v2/re_how_to_use_pennylane_for_resource_estimation/demo.py new file mode 100644 index 0000000000..dceb8df580 --- /dev/null +++ b/demonstrations_v2/re_how_to_use_pennylane_for_resource_estimation/demo.py @@ -0,0 +1,661 @@ +r""" +How to use PennyLane for Resource Estimation +============================================ +""" + +###################################################################### +# Fault tolerant quantum computers are on their way. +# But how do we ensure that useful algorithms can actually run on them? +# An algorithm is hardly helpful when it cannot be executed; +# but only truly helpful when it can. +# +# This is a major challenge in quantum algorithm development, +# especially since we are often working at scales where simulation is no longer feasible. +# We therefore need to analyze our algorithms to perform **resource estimation** +# to get an idea of how many logical qubits and gates an algorithm requires. +# In turn, this gives us an indication of how long the algorithm will take to execute +# on a given quantum hardware architecture, +# or if it will even fit in memory to begin with. +# +# PennyLane is here to make that process easy, with our new resource estimation module: +# :mod:`estimator `. +# +# In this demo, we will show you how to perform resource estimation +# for a simple Hamiltonian simulation workflow. +# +# Let’s import our quantum resource :mod:`estimator `. +# + +import pennylane as qml +import pennylane.estimator as qre + +###################################################################### +# +# PennyLane's :mod:`estimator ` module: +# +# - Makes reasoning about quantum algorithms *quick* and painless - no complicated inputs, just tell +# :mod:`estimator ` what you know. +# - Keeps you up to *speed* - :mod:`estimator ` leverages the latest results from the literature to make +# sure you’re as efficient as can be. +# - Gets you moving *even faster* - in the blink of an eye :mod:`estimator ` +# provides you with resource estimates, and enables effortless customization to enhance your research. +# + +###################################################################### +# Making it Easy +# ~~~~~~~~~~~~~~ +# + +###################################################################### +# We will be using the Kitaev model as an example to explore resource estimation. For more information +# about the Kitaev Hamiltonian, check out :func:`our documentation `. +# +# In this demo we will estimate the quantum resources necessary to evolve the quantum state of a 100 x +# 100 unit honeycomb lattice of spins under the Kitaev Hamiltonian. +# +# **Thats 20,000 spins!** +# +# Generating such Hamiltonians quickly becomes a bottleneck. +# However, thanks to :mod:`estimator `, +# we don’t need a detailed description of our Hamiltonian to estimate its resources! +# +# The geometry of the honeycomb lattice and the structure of the Hamiltonian allows us to calculate +# some important quantities directly: +# +# .. math:: +# n_{q} &= 2 n^{2}, \\ +# n_{YY} &= n_{ZZ} = n * (n - 1), \\ +# n_{XX} &= n^{2}, \\ +# +# :mod:`estimator ` provides +# `classes `__ +# which allow us to investigate the resources of Hamiltonian simulation without needing to generate +# them. +# In this case, we can capture the key information of our Hamiltonian in a compact representation +# using the +# :class:`qre.PauliHamiltonian ` +# class. +# + +n_cell = 100 +n_q = 2 * n_cell**2 +n_xx = n_cell**2 +n_yy = n_cell * (n_cell - 1) +n_zz = n_yy + +pauli_word_distribution = {"XX": n_xx, "YY": n_yy, "ZZ": n_zz} + +kitaev_H = qre.PauliHamiltonian( + num_qubits=n_q, + pauli_dist=pauli_word_distribution, +) + +###################################################################### +# We can then use existing resource +# `operators `__ and +# `templates `__ +# from the :mod:`estimator ` module to express our circuit. +# These +# :class:`ResourceOperator ` +# classes are designed to require minimal information +# while still providing trustworthy estimates. +# + +order = 2 +num_steps = 10 + +def circuit(hamiltonian): + qre.UniformStatePrep(num_states=2**n_q) # uniform superposition over all basis states + qre.TrotterPauli(hamiltonian, num_steps, order) + +###################################################################### +# The cost of an algorithm is typically quantified by the number of logical qubits required and the +# number of gates used. Different hardware will natively support different gatesets. +# The default gateset used by :mod:`estimator ` is: +# ``{'Hadamard', 'S', 'CNOT', 'T', 'Toffoli', 'X', 'Y', 'Z'}``. +# +# So, how do we figure out our quantum resources? +# +# It’s simple: just call :func:`qre.estimate `! +# + +import time + +t1 = time.time() +res = qre.estimate(circuit)(kitaev_H) +t2 = time.time() + +print(f"Processing time: {t2 - t1:.3g} seconds\n") +print(res) + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# Processing time: 0.000349 sec +# +# --- Resources: --- +# Total wires: 2.000E+4 +# algorithmic wires: 20000 +# allocated wires: 0 +# zero state: 0 +# any state: 0 +# Total gates : 2.862E+7 +# 'T': 2.622E+7, +# 'CNOT': 1.192E+6, +# 'Z': 3.960E+5, +# 'S': 7.920E+5, +# 'Hadamard': 2.000E+4 + +###################################################################### +# Our resource estimate was generated in the blink of an eye. +# +# We can also analyze the resources of an individual +# :class:`ResourceOperator `. +# This can be helpful in determining which operators in a workflow demand the most resources. + +resources_without_grouping = qre.estimate(qre.TrotterPauli(kitaev_H, num_steps, order)) + +###################################################################### +# Providing additional information can help to produce more accurate resource estimates. +# In the case of our +# :class:`qre.PauliHamiltonian `, +# we can split the terms into groups of commuting terms: + +commuting_groups = [{"XX": n_xx}, {"YY": n_yy}, {"ZZ": n_zz}] + +kitaev_H_with_grouping = qre.PauliHamiltonian( + num_qubits=n_q, + commuting_groups=commuting_groups, +) + +resources_with_grouping = qre.estimate( + qre.TrotterPauli(kitaev_H_with_grouping, num_steps, order) +) + +###################################################################### +# Let’s see how the cost of ``qre.TrotterPauli`` differs in these two cases! + +# Just compare T gates: +t_count_1 = resources_without_grouping.gate_counts["T"] +t_count_2 = resources_with_grouping.gate_counts["T"] +reduction = abs((t_count_2 - t_count_1) / t_count_1) +print("--- With grouping ---", f"\n T gate count: {t_count_1:.3E}\n") +print("--- Without grouping ---", f"\n T gate count: {t_count_2:.3E}\n") +print(f"Difference: {100*reduction:.1f}% reduction") + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# --- With grouping --- +# T gate count: 2.622E+07 +# +# --- Without grouping --- +# T gate count: 1.791E+07 +# +# Difference: 31.7% reduction + +###################################################################### +# By splitting our terms into groups, we’ve managed to reduce the ``T`` gate count of our +# Trotterization by over 30 percent! +# + +###################################################################### +# Gatesets & Configurations +# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# + +###################################################################### +# Here are the resources for our circuit using our updated Hamiltonian: + +res = qre.estimate(circuit)(kitaev_H_with_grouping) +print(f"\n{res}") + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# --- Resources: --- +# Total wires: 2.000E+4 +# algorithmic wires: 20000 +# allocated wires: 0 +# zero state: 0 +# any state: 0 +# Total gates : 1.993E+7 +# 'T': 1.791E+7, +# 'CNOT': 8.140E+5, +# 'Z': 3.960E+5, +# 'S': 7.920E+5, +# 'Hadamard': 2.000E+4 + +###################################################################### +# We can configure the gateset to obtain resource estimates at various levels of abstraction. +# Here, we configure a high-level gateset which adds gate types such as rotations, and a low +# level-gateset limited to just ``Hadamard``, ``CNOT``, ``S``, and ``T`` gates. +# +# We can see how the resources manifest at these different levels. +# + +highlvl_gateset = { + "RX","RY","RZ", + "Toffoli", + "X","Y","Z", + "Adjoint(S)","Adjoint(T)", + "Hadamard","S","CNOT","T", +} + +highlvl_res = qre.estimate(circuit, gate_set=highlvl_gateset)(kitaev_H_with_grouping) +print(f"High-level resources:\n{highlvl_res}\n") + + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# High-level resources: +# --- Resources: --- +# Total wires: 2.000E+4 +# algorithmic wires: 20000 +# allocated wires: 0 +# zero state: 0 +# any state: 0 +# Total gates : 2.033E+6 +# 'RX': 1.100E+5, +# 'RY': 1.980E+5, +# 'Adjoint(S)': 3.960E+5, +# 'RZ': 9.900E+4, +# 'CNOT': 8.140E+5, +# 'S': 3.960E+5, +# 'Hadamard': 2.000E+4 + +lowlvl_gateset = {"Hadamard", "S", "CNOT", "T"} + +lowlvl_res = qre.estimate(circuit, gate_set=lowlvl_gateset)(kitaev_H_with_grouping) +print(f"Low-level resources:\n{lowlvl_res}") + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# Low-level resources: +# --- Resources: --- +# Total wires: 2.000E+4 +# algorithmic wires: 20000 +# allocated wires: 0 +# zero state: 0 +# any state: 0 +# Total gates : 2.033E+7 +# 'T': 1.791E+7, +# 'CNOT': 8.140E+5, +# 'S': 1.584E+6, +# 'Hadamard': 2.000E+4 + +###################################################################### +# When decomposing our algorithms to a particular gateset, it is often the case that we only have some +# approximate decomposition of a building-block into the target gateset. For example, approximate +# state loading to some precision, or rotation synthesis within some precision of the rotation angle. +# +# These approximate decompositions are accurate within some error threshold; tuning this error +# threshold determines the resource cost of the algorithm. We can set and tune these errors using a +# resource configuration: :class:`ResourceConfig `. +# +# Notice that a more precise estimate requires more ``T`` gates! +# + +custom_rc = qre.ResourceConfig() # generate a resource configuration + +rz_precisions = custom_rc.resource_op_precisions[qre.RZ] +print(f"Default setting: {rz_precisions}\n") + +custom_rc.set_precision(qre.RZ, 1e-15) # customize precision + +res = qre.estimate( + circuit, + gate_set=lowlvl_gateset, + config=custom_rc, # provide our custom configuration +)(kitaev_H_with_grouping) + +# Just compare T gates: +print("--- Lower precision (1e-9) ---", f"\n T counts: {lowlvl_res.gate_counts["T"]:.3E}\n") +print("--- Higher precision (1e-15) ---", f"\n T counts: {res.gate_counts["T"]:.3E}") + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# Default setting: {'precision': 1e-09} +# +# --- Lower precision (1e-9) --- +# T counts: 1.791E+07 +# +# --- Higher precision (1e-15) --- +# T counts: 2.009E+07 + +###################################################################### +# Swapping Decompositions +# ~~~~~~~~~~~~~~~~~~~~~~~ +# +# There are many ways to decompose a quantum gate into our target gateset. +# Selecting an alternate decomposition is a great way to compare the costs of different techniques, +# and thereby optimize your quantum workflow. +# This can be done easily with the +# :class:`ResourceConfig ` class. +# +# Let’s explore decompositions for the ``RZ`` gate: +# Current decomposition for ``RZ``, or single qubit rotation synthesis in general is +# defined in Bocharov et al. [#bocharov]_. +# + +default_cost_RZ = qre.estimate(qre.RZ()) +print(default_cost_RZ) + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# --- Resources: --- +# Total wires: 1 +# algorithmic wires: 1 +# allocated wires: 0 +# zero state: 0 +# any state: 0 +# Total gates : 44 +# 'T': 44 + +###################################################################### +# These are other state of the art methods we could use instead, such as that +# defined in Ross & Selinger [#ross]_. +# +# In order to define a resource decomposition, we first need to know the ``resource_keys`` for the +# operator whose decomposition we want to add: +# + +print(qre.RZ.resource_keys) # these are the required arguments + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# {'precision'} + +###################################################################### +# Now that we know which arguments we need, we can define our resource decomposition. +# +# Each :class:`ResourceOperator ` +# has a ``resource_rep``, serving as an even more compact representation +# to be used when defining resource decompositions. +# + +def gridsynth_decomp(precision): + t_resource_rep = qre.resource_rep(qre.T) + t_counts = round(3 * qml.math.log2(1/precision)) # as per Ross & Selinger + + t_gate_counts = qre.GateCount( + t_resource_rep, t_counts + ) # GateCounts tell us how many of this type of gate are used + + return [ + t_gate_counts + ] # We return a list of GateCounts for all relevant gate types + + +###################################################################### +# Finally, we set the new decomposition in our +# :class:`ResourceConfig `. +# + +gridsynth_rc = qre.ResourceConfig() +gridsynth_rc.set_decomp(qre.RZ, gridsynth_decomp) +gridsynth_cost_RZ = qre.estimate(qre.RZ(), config=gridsynth_rc) + +print("GridSynth decomposition -", f"\t\tT count: {gridsynth_cost_RZ.gate_counts["T"]}") +print("Default decomposition (RUS) -", f"\tT count: {default_cost_RZ.gate_counts["T"]}") + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# GridSynth decomposition - T count: 90 +# Default decomposition (RUS) - T count: 44 + +###################################################################### +# Putting it All Together +# ~~~~~~~~~~~~~~~~~~~~~~~ +# +# We can combine the features we have seen so far to optimize the cost of Trotterized time +# evolution of the Kitaev hamiltonian: +# + +t1 = time.time() + +kitaev_hamiltonian = kitaev_H_with_grouping # use compact hamiltonian with grouping + +custom_gateset = lowlvl_gateset # use the low-level gateset + +custom_config = qre.ResourceConfig() +custom_config.set_precision(qre.RZ, precision=1e-12) # set higher precision + +resources = qre.estimate( + circuit, + gate_set = custom_gateset, + config = custom_config +)(kitaev_hamiltonian) + +t2 = time.time() +print(f"Processing time: {t2 - t1:.3g} seconds\n") +print(resources) + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# Processing time: 0.000747 seconds +# +# --- Resources: --- +# Total wires: 2.000E+4 +# algorithmic wires: 20000 +# allocated wires: 0 +# zero state: 0 +# any state: 0 +# Total gates : 2.142E+7 +# 'T': 1.900E+7, +# 'CNOT': 8.140E+5, +# 'S': 1.584E+6, +# 'Hadamard': 2.000E+4 + +###################################################################### +# Estimating the Resources of your PennyLane Circuits +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# If you’ve already written your workflow for execution, we can invoke +# :func:`qre.estimate ` +# on it directly. +# No need to write it again! +# +# Let's continue with the same example, but with a 25 x 25 unit honeycomb lattice of spins. +# Here, we generate the Hamiltonian ourselves: +# + +import numpy as np + +n_cell = 25 +n_cells = [n_cell, n_cell] +kx, ky, kz = (0.5, 0.6, 0.7) + +t1 = time.time() +flat_hamiltonian = qml.spin.kitaev(n_cells, coupling=np.array([kx, ky, kz])) +flat_hamiltonian.compute_grouping() # compute the qubitize commuting groups! + +groups = [] +for group_indices in flat_hamiltonian.grouping_indices: + grouped_term = qml.sum(*(flat_hamiltonian.operands[index] for index in group_indices)) + groups.append(grouped_term) + +grouped_hamiltonian = qml.sum(*groups) +t2 = time.time() + +###################################################################### +# We'll use :mod:`estimator ` in parallel to make sure everything matches. +# It's a lot easier to prepare for resource estimation than for execution! +# + +t3 = time.time() +n_q = 2 * n_cell**2 +n_xx = n_cell**2 +n_yy = n_cell*(n_cell-1) +n_zz = n_yy + +commuting_groups = [{"XX": n_xx}, {"YY": n_yy}, {"ZZ": n_zz}] + +compact_hamiltonian = qre.PauliHamiltonian( + num_qubits = n_q, + commuting_groups = commuting_groups, +) +t4 = time.time() + +###################################################################### +# The resulting data can be easily compared for a sanity check. +# + +print(f"Processing time for Hamiltonian generation: {(t2 - t1):.3g} seconds") +print("Total number of terms:", len(flat_hamiltonian.operands)) +print("Total number of qubits:", len(flat_hamiltonian.wires), "\n") + +print(f"Processing time for Hamiltonian estimation: {(t4 - t3):.3g} seconds") +print("Total number of terms:", compact_hamiltonian.num_pauli_words) +print("Total number of qubits:", compact_hamiltonian.num_qubits) + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# Processing time for Hamiltonian generation: 4.56 seconds +# Total number of terms: 1825 +# Total number of qubits: 1250 +# +# Processing time for Hamiltonian estimation: 0.000112 seconds +# Total number of terms: 1825 +# Total number of qubits: 1250 + +###################################################################### +# Now we can define our circuit for Hamiltonian simulation. +# + +order = 2 +num_trotter_steps = 1 + +@qml.qnode(qml.device("default.qubit")) +def executable_circuit(hamiltonian): + for wire in hamiltonian.wires: # uniform superposition over all basis states + qml.Hadamard(wire) + qml.TrotterProduct(hamiltonian, time=1.0, n=num_trotter_steps, order=order) + return qml.state() + +def estimation_circuit(hamiltonian): + qre.UniformStatePrep(num_states = 2**n_q) + qre.TrotterPauli(hamiltonian, num_trotter_steps, order) + return + +###################################################################### +# As usual, just call :func:`qre.estimate ` +# to generate a state-of-the-art resource estimate for your PennyLane circuit! +# + +t5 = time.time() +resources_exec = qre.estimate(executable_circuit)(grouped_hamiltonian) +t6 = time.time() + +print(f"Processing time: {(t6 - t5):.3g} seconds") +print(resources_exec) + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# Processing time: 3.22 seconds +# --- Resources: --- +# Total wires: 1250 +# algorithmic wires: 1250 +# allocated wires: 0 +# zero state: 0 +# any state: 0 +# Total gates : 1.488E+5 +# 'T': 1.342E+5, +# 'CNOT': 6.100E+3, +# 'Z': 2.400E+3, +# 'S': 4.800E+3, +# 'Hadamard': 1.250E+3 + +###################################################################### +# Let's validate the results by comparing with our resource *estimation* circuit. +# + +t5 = time.time() +resources_compact = qre.estimate(estimation_circuit)(compact_hamiltonian) +t6 = time.time() + +print(f"Processing time: {(t6 - t5):.3g} seconds") +print(resources_compact) + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# Processing time: 0.000365 seconds +# --- Resources: --- +# Total wires: 1250 +# algorithmic wires: 1250 +# allocated wires: 0 +# zero state: 0 +# any state: 0 +# Total gates : 1.488E+5 +# 'T': 1.342E+5, +# 'CNOT': 6.100E+3, +# 'Z': 2.400E+3, +# 'S': 4.800E+3, +# 'Hadamard': 1.250E+3 + +###################################################################### +# The numbers check out! +# + +###################################################################### +# Your turn! +# ~~~~~~~~~~ +# +# Now that you’ve seen how powerful PennyLane’s +# quantum resource :mod:`estimator ` is, +# go try it out yourself! +# +# Use PennyLane to eason about the costs of your quantum algorithm +# without any of the headaches. +# +# References +# ---------- +# +# .. [#bocharov] +# +# Bocharov, Alex, Martin Roetteler, and Krysta M. Svore. +# "Efficient synthesis of universal repeat-until-success quantum circuits." +# Physical review letters 114.8 (2015): 080502. `arXiv `__. +# +# .. [#ross] +# +# Ross, Neil J., and Peter Selinger. +# "Optimal ancilla-free Clifford+T approximation of z-rotations." +# Quantum information and computation 16.11–12 (2016): 901–953. `arXiv `__. +# \ No newline at end of file diff --git a/demonstrations_v2/re_how_to_use_pennylane_for_resource_estimation/metadata.json b/demonstrations_v2/re_how_to_use_pennylane_for_resource_estimation/metadata.json new file mode 100644 index 0000000000..177821b857 --- /dev/null +++ b/demonstrations_v2/re_how_to_use_pennylane_for_resource_estimation/metadata.json @@ -0,0 +1,54 @@ +{ + "title": "How to use PennyLane for Resource Estimation", + "authors": [ + { + "username": "Jay" + }, + { + "username": "ANT0N" + } + ], + "executable_stable": false, + "executable_latest": false, + "dateOfPublication": "2025-12-01T10:00:00+00:00", + "dateOfLastModification": "2025-12-02T10:00:00+00:01", + "categories": [ + "Algorithms" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_resource_estimation_QChem.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_iqp_circuit_optimization_jax.png" + } + ], + "seoDescription": "Learn how to perform logical resource estimation in PennyLane with our new Estimator!", + "doi": "", + "references": [ + { + "id": "bocharov", + "type": "article", + "title": "Efficient Synthesis of Universal Repeat-Until-Success Quantum Circuits", + "authors": "Bocharov, Alex and Roetteler, Martin and Svore, Krysta M.", + "year": "2015", + "publisher": "American Physical Society", + "url": "https://link.aps.org/doi/10.1103/PhysRevLett.114.080502" + }, + { + "id": "ross", + "type": "article", + "title": "Optimal ancilla-free Clifford+T approximation of z-rotations", + "authors": "Ross, Neil J. and Selinger, Peter", + "year": "2016", + "publisher": "Rinton Press, Incorporated", + "url": "https://dl.acm.org/doi/abs/10.5555/3179330.3179331" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [] +} \ No newline at end of file