From 9689b01eac7319c8303adc32a2a8334d3bf804e4 Mon Sep 17 00:00:00 2001 From: Damian Pope Date: Fri, 4 Jul 2025 07:52:23 -0400 Subject: [PATCH 1/4] Added new tutorial on quantum phase transitions. --- .pylintrc | 4 +- _static/authors/Damian_Pope.png | Bin 0 -> 69001 bytes _static/authors/Tirth_Shah.jpg | Bin 0 -> 180784 bytes _static/authors/damian_pope.txt | 4 + _static/authors/tirth_shah.txt | 4 + _static/css/light-slider.css | 186 +- ..._model_using_JAX_and_JAXopt_2024-01-16.png | Bin 111175 -> 0 bytes ...L_model_using_JAX_and_Optax_2024-01-16.png | Bin 96621 -> 0 bytes .../barren_gadgets/barren_gadgets.py | 260 +-- .../barren_gadgets/layered_ansatz.py | 108 +- .../Fig_1_Ising_chain.png | Bin 0 -> 12045 bytes .../Fig_2_transverse_Ising.png | Bin 0 -> 18196 bytes .../Fig_3_ground_state_J_large.png | Bin 0 -> 32872 bytes .../Fig_4_ground_state_h_large.png | Bin 0 -> 36408 bytes .../Fig_5_2D_Ising_model.png | Bin 0 -> 24708 bytes .../spsa/spsa_thumbnail.png | Bin _static/thumbs/NOON.png | Bin 82085 -> 0 bytes _static/thumbs/bloch.png | Bin 14494 -> 0 bytes _static/thumbs/classifier_output_59_0.png | Bin 72226 -> 0 bytes _static/thumbs/gauss-circuit.png | Bin 13467 -> 0 bytes _static/thumbs/isingspins.png | Bin 93231 -> 0 bytes _static/thumbs/photon_redirection.png | Bin 7304 -> 0 bytes _static/thumbs/qaoa_maxcut_partition.png | Bin 42223 -> 0 bytes _static/thumbs/qgan3.png | Bin 19379 -> 0 bytes _static/thumbs/qng_optimization.png | Bin 42026 -> 0 bytes _static/thumbs/qnn_output_28_0.png | Bin 21546 -> 0 bytes _static/thumbs/rotoselect_structure.png | Bin 7147 -> 0 bytes _static/thumbs/spsa_mntn.png | Bin 38903 -> 0 bytes _static/thumbs/surface.png | Bin 61458 -> 0 bytes _static/thumbs/universal_dnn.png | Bin 40857 -> 0 bytes _static/thumbs/vqls_zoom.png | Bin 12068 -> 0 bytes _templates/filters.html | 230 +-- _templates/page.html | 60 +- .../barren_gadgets/barren_gadgets.py | 260 +-- .../barren_gadgets/layered_ansatz.py | 108 +- demonstrations/ensemble_multi_qpu.py | 1162 ++++++------ demonstrations/gbs.py | 976 +++++----- demonstrations/tutorial_adaptive_circuits.py | 852 ++++----- ...rial_adversarial_attacks_QML.metadata.json | 158 +- demonstrations/tutorial_backprop.py | 912 ++++----- demonstrations/tutorial_barren_gadgets.py | 778 ++++---- demonstrations/tutorial_differentiable_HF.py | 786 ++++---- demonstrations/tutorial_doubly_stochastic.py | 812 ++++---- .../tutorial_fermionic_operators.py | 462 ++--- demonstrations/tutorial_givens_rotations.py | 1012 +++++----- demonstrations/tutorial_haar_measure.py | 1622 ++++++++-------- demonstrations/tutorial_here_comes_the_sun.py | 1152 +++++------ .../tutorial_initial_state_preparation.py | 728 +++---- .../tutorial_jax_transformations.py | 622 +++--- demonstrations/tutorial_mapping.py | 696 +++---- .../tutorial_measurement_optimize.py | 1688 ++++++++--------- demonstrations/tutorial_pasqal.py | 722 +++---- demonstrations/tutorial_qchem_external.py | 460 ++--- demonstrations/tutorial_qft_arithmetics.py | 866 ++++----- demonstrations/tutorial_quantum_chemistry.py | 662 +++---- demonstrations/tutorial_quantum_dropout.py | 1404 +++++++------- .../tutorial_quantum_natural_gradient.py | 996 +++++----- ...al_quantum_phase_transitions.metadata.json | 138 ++ .../tutorial_quantum_phase_transitions.py | 1156 +++++++++++ .../tutorial_quantum_transfer_learning.py | 1222 ++++++------ demonstrations/tutorial_qubit_tapering.py | 660 +++---- .../tutorial_qutrits_bernstein_vazirani.py | 818 ++++---- .../tutorial_resource_estimation.py | 642 +++---- demonstrations/tutorial_rosalin.py | 1330 ++++++------- demonstrations/vqe_parallel.py | 786 ++++---- demonstrations_v2/ensemble_multi_qpu/demo.py | 1162 ++++++------ demonstrations_v2/gbs/demo.py | 976 +++++----- .../tutorial_adaptive_circuits/demo.py | 852 ++++----- .../metadata.json | 158 +- demonstrations_v2/tutorial_backprop/demo.py | 912 ++++----- .../barren_gadgets/barren_gadgets.py | 260 +-- .../barren_gadgets/layered_ansatz.py | 108 +- .../tutorial_barren_gadgets/demo.py | 778 ++++---- .../tutorial_differentiable_HF/demo.py | 786 ++++---- .../tutorial_doubly_stochastic/demo.py | 812 ++++---- .../tutorial_fermionic_operators/demo.py | 462 ++--- .../tutorial_givens_rotations/demo.py | 1012 +++++----- .../tutorial_haar_measure/demo.py | 1622 ++++++++-------- .../tutorial_here_comes_the_sun/demo.py | 1152 +++++------ .../demo.py | 728 +++---- .../tutorial_jax_transformations/demo.py | 622 +++--- demonstrations_v2/tutorial_mapping/demo.py | 696 +++---- .../tutorial_measurement_optimize/demo.py | 1688 ++++++++--------- demonstrations_v2/tutorial_pasqal/demo.py | 722 +++---- .../tutorial_qchem_external/demo.py | 460 ++--- .../tutorial_qft_arithmetics/demo.py | 866 ++++----- .../tutorial_quantum_chemistry/demo.py | 662 +++---- .../tutorial_quantum_dropout/demo.py | 1404 +++++++------- .../tutorial_quantum_natural_gradient/demo.py | 996 +++++----- .../demo.py | 1240 ++++++------ .../tutorial_qubit_tapering/demo.py | 660 +++---- .../demo.py | 818 ++++---- .../tutorial_resource_estimation/demo.py | 642 +++---- demonstrations_v2/tutorial_rosalin/demo.py | 1330 ++++++------- demonstrations_v2/vqe_parallel/demo.py | 786 ++++---- notebook_converter/notebook_to_demo.py | 0 sg_execution_times.rst | 568 ++++++ test-results/sphinx-gallery/junit.xml | 1 + 98 files changed, 27658 insertions(+), 25787 deletions(-) create mode 100644 _static/authors/Damian_Pope.png create mode 100644 _static/authors/Tirth_Shah.jpg create mode 100644 _static/authors/damian_pope.txt create mode 100644 _static/authors/tirth_shah.txt delete mode 100644 _static/demonstration_assets/How_to_optimize_QML_model_using_JAX_and_JAXopt/socialthumbnail_large_How_to_optimize_QML_model_using_JAX_and_JAXopt_2024-01-16.png delete mode 100644 _static/demonstration_assets/How_to_optimize_QML_model_using_JAX_and_Optax/socialsthumbnail_large_How_to_optimize_QML_model_using_JAX_and_Optax_2024-01-16.png create mode 100644 _static/demonstration_assets/quantum_phase_transitions/Fig_1_Ising_chain.png create mode 100644 _static/demonstration_assets/quantum_phase_transitions/Fig_2_transverse_Ising.png create mode 100644 _static/demonstration_assets/quantum_phase_transitions/Fig_3_ground_state_J_large.png create mode 100644 _static/demonstration_assets/quantum_phase_transitions/Fig_4_ground_state_h_large.png create mode 100644 _static/demonstration_assets/quantum_phase_transitions/Fig_5_2D_Ising_model.png mode change 100755 => 100644 _static/demonstration_assets/spsa/spsa_thumbnail.png delete mode 100644 _static/thumbs/NOON.png delete mode 100644 _static/thumbs/bloch.png delete mode 100644 _static/thumbs/classifier_output_59_0.png delete mode 100644 _static/thumbs/gauss-circuit.png delete mode 100644 _static/thumbs/isingspins.png delete mode 100644 _static/thumbs/photon_redirection.png delete mode 100644 _static/thumbs/qaoa_maxcut_partition.png delete mode 100644 _static/thumbs/qgan3.png delete mode 100644 _static/thumbs/qng_optimization.png delete mode 100644 _static/thumbs/qnn_output_28_0.png delete mode 100644 _static/thumbs/rotoselect_structure.png delete mode 100644 _static/thumbs/spsa_mntn.png delete mode 100644 _static/thumbs/surface.png delete mode 100644 _static/thumbs/universal_dnn.png delete mode 100644 _static/thumbs/vqls_zoom.png create mode 100644 demonstrations/tutorial_quantum_phase_transitions.metadata.json create mode 100644 demonstrations/tutorial_quantum_phase_transitions.py mode change 100755 => 100644 notebook_converter/notebook_to_demo.py create mode 100644 sg_execution_times.rst create mode 100644 test-results/sphinx-gallery/junit.xml diff --git a/.pylintrc b/.pylintrc index be17d653e2..8d0c91f1ce 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,2 +1,2 @@ -[MESSAGES CONTROL] -disable=invalid-name,missing-function-docstring,unused-argument +[MESSAGES CONTROL] +disable=invalid-name,missing-function-docstring,unused-argument diff --git a/_static/authors/Damian_Pope.png b/_static/authors/Damian_Pope.png new file mode 100644 index 0000000000000000000000000000000000000000..7b9e395e1e9471c4836bc65e44dc548966c8deb1 GIT binary patch literal 69001 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

*vV9&vLsh_IZvSmR3R&RPn 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

PUCV$3P1T~E59wr#L`*kQlh%m1N<<$LspoA!YH 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

3I@dXdTXRHcyY8rxGErD0Lvf81enN$d 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 z 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 LP9?&0Eu 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 DDz6v= literal 0 HcmV?d00001 diff --git a/_static/authors/damian_pope.txt b/_static/authors/damian_pope.txt new file mode 100644 index 0000000000..793dc4ee30 --- /dev/null +++ b/_static/authors/damian_pope.txt @@ -0,0 +1,4 @@ +.. bio:: Damian Pope + :photo: ../_static/authors/Damian_Pope.png + + Damian has a PhD in theoretical quantum information. He works at Perimeter Institute for Theoretical Physics, Canada in Educational Outreach. He’s interested in quantum computing education, quantum error correction, and using quantum computers in condensed matter physics, high-energy physics and cosmology. \ No newline at end of file diff --git a/_static/authors/tirth_shah.txt b/_static/authors/tirth_shah.txt new file mode 100644 index 0000000000..3139b4d7a1 --- /dev/null +++ b/_static/authors/tirth_shah.txt @@ -0,0 +1,4 @@ +.. bio:: Tirth Shah + :photo: ../_static/authors/Tirth_Shah.jpg + + Tirth is an undergraduate student at the University of Alberta studying Physics and Math. His interests include quantum computing, black hole physics, and the application of machine learning to space science. \ No newline at end of file diff --git a/_static/css/light-slider.css b/_static/css/light-slider.css index 0dac390d5f..7826c4c0e0 100644 --- a/_static/css/light-slider.css +++ b/_static/css/light-slider.css @@ -1,93 +1,93 @@ -.light-slider { - height: 100%!important; -} - -.light-slider li { - margin-bottom: 20px; - margin-top: 20px; -} - -.light-slider .card h4 { - margin-top: 10px; - font-weight: 300; - color: #535353; -} - -.light-slider .card { - margin-left: 30px; - margin-right: 30px; - overflow: hidden; - min-height: 430px; -} - -.light-slider .card .card-body { - z-index: 2; - /*border-radius: 30% 70% 75% 25% / 30% 52% 48% 70% ;*/ - box-sizing: content-box; - padding-top: 20px; - position: relative; - background-image: linear-gradient(to right,#fff6ea 0,#ffefe9 100%); -} - -.lSSlideOuter { - margin-left: calc(100% - 980px); - width: 150%; -} - -.light-slider .card .card-body:after { - content: ""; - position: absolute; - left: 0; - top: 0; - width: 0; - height: 0; - border-top: 20px solid #fff; - border-left: 200px solid transparent; - border-right: 150px solid transparent; -} - -.light-slider .card:hover { - box-shadow: 0 2px 5px 0 rgba(0,0,0,.05),0 2px 25px 2px rgba(0,0,0,.05)!important; - border: 0; - transition-duration: 0.4s; - border-radius: 10px!important; -} - - -.light-slider .card img { - transform: scale(1); - transition-duration: 1s; -} - -.light-slider p { - font-weight: 300; - color: #535353; -} - -.light-slider .card:hover img { - transform: scale(1.10); - transition-duration: 1s; -} - -.lSPrev { - left: -1px !important; -} - -.lSNext { - left: calc(100% - 19px); -} - -@media screen and (max-width: 768px) { - .lSSlideOuter { - margin-left: 0px !important; - width: 100% !important; - } -} - - -@media screen and (max-width: 1400px) and (min-width: 768px){ - .lSSlideOuter { - margin-left: 0px !important; - width: 100% !important; - } -} +.light-slider { + height: 100%!important; +} + +.light-slider li { + margin-bottom: 20px; + margin-top: 20px; +} + +.light-slider .card h4 { + margin-top: 10px; + font-weight: 300; + color: #535353; +} + +.light-slider .card { + margin-left: 30px; + margin-right: 30px; + overflow: hidden; + min-height: 430px; +} + +.light-slider .card .card-body { + z-index: 2; + /*border-radius: 30% 70% 75% 25% / 30% 52% 48% 70% ;*/ + box-sizing: content-box; + padding-top: 20px; + position: relative; + background-image: linear-gradient(to right,#fff6ea 0,#ffefe9 100%); +} + +.lSSlideOuter { + margin-left: calc(100% - 980px); + width: 150%; +} + +.light-slider .card .card-body:after { + content: ""; + position: absolute; + left: 0; + top: 0; + width: 0; + height: 0; + border-top: 20px solid #fff; + border-left: 200px solid transparent; + border-right: 150px solid transparent; +} + +.light-slider .card:hover { + box-shadow: 0 2px 5px 0 rgba(0,0,0,.05),0 2px 25px 2px rgba(0,0,0,.05)!important; + border: 0; + transition-duration: 0.4s; + border-radius: 10px!important; +} + + +.light-slider .card img { + transform: scale(1); + transition-duration: 1s; +} + +.light-slider p { + font-weight: 300; + color: #535353; +} + +.light-slider .card:hover img { + transform: scale(1.10); + transition-duration: 1s; +} + +.lSPrev { + left: -1px !important; +} + +.lSNext { + left: calc(100% - 19px); +} + +@media screen and (max-width: 768px) { + .lSSlideOuter { + margin-left: 0px !important; + width: 100% !important; + } +} + + +@media screen and (max-width: 1400px) and (min-width: 768px){ + .lSSlideOuter { + margin-left: 0px !important; + width: 100% !important; + } +} diff --git a/_static/demonstration_assets/How_to_optimize_QML_model_using_JAX_and_JAXopt/socialthumbnail_large_How_to_optimize_QML_model_using_JAX_and_JAXopt_2024-01-16.png b/_static/demonstration_assets/How_to_optimize_QML_model_using_JAX_and_JAXopt/socialthumbnail_large_How_to_optimize_QML_model_using_JAX_and_JAXopt_2024-01-16.png deleted file mode 100644 index 54f3038d81f6ce76ae875e65c2861a68f39fc2be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 111175 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 G0sjZ)807o_ diff --git a/_static/demonstration_assets/How_to_optimize_QML_model_using_JAX_and_Optax/socialsthumbnail_large_How_to_optimize_QML_model_using_JAX_and_Optax_2024-01-16.png b/_static/demonstration_assets/How_to_optimize_QML_model_using_JAX_and_Optax/socialsthumbnail_large_How_to_optimize_QML_model_using_JAX_and_Optax_2024-01-16.png deleted file mode 100644 index 7f03b489853d8dd431ce0a6aa2701c097e4a9a4e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 96621 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 T`O1Bq=qTP*yHkA2IOzWZnWC6n diff --git a/_static/demonstration_assets/barren_gadgets/barren_gadgets.py b/_static/demonstration_assets/barren_gadgets/barren_gadgets.py index 0e2929dc41..09d3c89df7 100644 --- a/_static/demonstration_assets/barren_gadgets/barren_gadgets.py +++ b/_static/demonstration_assets/barren_gadgets/barren_gadgets.py @@ -1,130 +1,130 @@ -import pennylane as qml -from pennylane import numpy as np - -def non_identity_obs(obs): - return [o for o in obs if not isinstance(o, qml.Identity)] - -class PerturbativeGadgets: - """ Class to generate the gadget Hamiltonian corresponding to a given - computational hamiltonian according to the gadget construction derived - by Faehrmann & Cichy - - Args: - perturbation_factor (float) : parameter controlling the magnitude of the - perturbation (aa pre-factor to \lambda_max) - """ - def __init__(self, perturbation_factor=1): - self.perturbation_factor = perturbation_factor - - def gadgetize(self, Hamiltonian, target_locality=3): - """Generation of the perturbative gadget equivalent of the given - Hamiltonian according to the proceedure in Cichy, Fährmann et al. - Args: - Hamiltonian (qml.Hamiltonian) : target Hamiltonian to decompose - into more local terms - target_locality (int > 2) : desired locality of the resulting - gadget Hamiltonian - Returns: - Hgad (qml.Hamiltonian) : gadget Hamiltonian - """ - # checking for unaccounted for situations - self.run_checks(Hamiltonian, target_locality) - computational_qubits, computational_locality, computational_terms = self.get_params(Hamiltonian) - Hamiltonian_coeffs, Hamiltonian_ops = Hamiltonian.terms() - - # total qubit count, updated progressively when adding ancillaries - total_qubits = computational_qubits - #TODO: check proper convergence guarantee - gap = 1 - perturbation_norm = np.sum(np.abs(Hamiltonian_coeffs)) \ - + computational_terms * (computational_locality - 1) - lambda_max = gap / (4 * perturbation_norm) - l = self.perturbation_factor * lambda_max - sign_correction = (-1)**(computational_locality % 2 + 1) - # creating the gadget Hamiltonian - coeffs_anc = [] - coeffs_pert = [] - obs_anc = [] - obs_pert = [] - ancillary_register_size = int(computational_locality / (target_locality - 2)) - for str_count, string in enumerate(Hamiltonian_ops): - previous_total = total_qubits - total_qubits += ancillary_register_size - # Generating the ancillary part - for anc_q in range(previous_total, total_qubits): - coeffs_anc += [0.5, -0.5] - obs_anc += [qml.Identity(anc_q), qml.PauliZ(anc_q)] - # Generating the perturbative part - for anc_q in range(ancillary_register_size): - term = qml.PauliX(previous_total+anc_q) @ qml.PauliX(previous_total+(anc_q+1)%ancillary_register_size) - term = qml.prod(term, *non_identity_obs(string.operands)[ - (target_locality-2)*anc_q:(target_locality-2)*(anc_q+1)]) - obs_pert.append(term) - coeffs_pert += [l * sign_correction * Hamiltonian_coeffs[str_count]] \ - + [l] * (ancillary_register_size - 1) - coeffs = coeffs_anc + coeffs_pert - obs = obs_anc + obs_pert - Hgad = qml.Hamiltonian(coeffs, obs) - return Hgad - - def get_params(self, Hamiltonian): - """ retrieving the parameters n, k and r from the given Hamiltonian - Args: - Hamiltonian (qml.Hamiltonian) : Hamiltonian from which to get the - relevant parameters - Returns: - computational_qubits (int) : total number of qubits acted upon by - the Hamiltonian - computational_locality (int) : maximum number of qubits acted upon - by a single term of the Hamiltonian - computational_terms (int) : number of terms in the sum - composing the Hamiltonian - """ - _, Hamiltonian_ops = Hamiltonian.terms() - # checking how many qubits the Hamiltonian acts on - computational_qubits = len(Hamiltonian.wires) - # getting the number of terms in the Hamiltonian - computational_terms = len(Hamiltonian_ops) - # getting the locality, assuming all terms have the same - computational_locality = max([len(non_identity_obs(Hamiltonian_ops[s])) - for s in range(computational_terms)]) - return computational_qubits, computational_locality, computational_terms - - def run_checks(self, Hamiltonian, target_locality): - """ method to check a few conditions for the correct application of - the methods - Args: - Hamiltonian (qml.Hamiltonian) : Hamiltonian of interest - target_locality (int > 2) : desired locality of the resulting - gadget Hamiltonian - Returns: - None - """ - _, Hamiltonian_ops = Hamiltonian.terms() - computational_qubits, computational_locality, _ = self.get_params(Hamiltonian) - computational_qubits = len(Hamiltonian.wires) - if computational_qubits != Hamiltonian.wires[-1] + 1: - raise Exception('The studied computational Hamiltonian is not acting on ' + - 'the first {} qubits. '.format(computational_qubits) + - 'Decomposition not implemented for this case') - # Check for same string lengths - localities=[] - for string in Hamiltonian_ops: - localities.append(len(non_identity_obs(string))) - if len(np.unique(localities)) > 1: - raise Exception('The given Hamiltonian has terms with different locality.' + - ' Gadgetization not implemented for this case') - # validity of the target locality given the computational locality - if target_locality < 3: - raise Exception('The target locality can not be smaller than 3') - ancillary_register_size = computational_locality / (target_locality - 2) - if int(ancillary_register_size) != ancillary_register_size: - raise Exception('The locality of the Hamiltonian and the target' + - ' locality are not compatible. The gadgetization' + - ' with "unfull" ancillary registers is not' + - ' supported yet. Please choose such that the' + - ' computational locality is divisible by the' + - ' target locality - 2') - - - +import pennylane as qml +from pennylane import numpy as np + +def non_identity_obs(obs): + return [o for o in obs if not isinstance(o, qml.Identity)] + +class PerturbativeGadgets: + """ Class to generate the gadget Hamiltonian corresponding to a given + computational hamiltonian according to the gadget construction derived + by Faehrmann & Cichy + + Args: + perturbation_factor (float) : parameter controlling the magnitude of the + perturbation (aa pre-factor to \lambda_max) + """ + def __init__(self, perturbation_factor=1): + self.perturbation_factor = perturbation_factor + + def gadgetize(self, Hamiltonian, target_locality=3): + """Generation of the perturbative gadget equivalent of the given + Hamiltonian according to the proceedure in Cichy, Fährmann et al. + Args: + Hamiltonian (qml.Hamiltonian) : target Hamiltonian to decompose + into more local terms + target_locality (int > 2) : desired locality of the resulting + gadget Hamiltonian + Returns: + Hgad (qml.Hamiltonian) : gadget Hamiltonian + """ + # checking for unaccounted for situations + self.run_checks(Hamiltonian, target_locality) + computational_qubits, computational_locality, computational_terms = self.get_params(Hamiltonian) + Hamiltonian_coeffs, Hamiltonian_ops = Hamiltonian.terms() + + # total qubit count, updated progressively when adding ancillaries + total_qubits = computational_qubits + #TODO: check proper convergence guarantee + gap = 1 + perturbation_norm = np.sum(np.abs(Hamiltonian_coeffs)) \ + + computational_terms * (computational_locality - 1) + lambda_max = gap / (4 * perturbation_norm) + l = self.perturbation_factor * lambda_max + sign_correction = (-1)**(computational_locality % 2 + 1) + # creating the gadget Hamiltonian + coeffs_anc = [] + coeffs_pert = [] + obs_anc = [] + obs_pert = [] + ancillary_register_size = int(computational_locality / (target_locality - 2)) + for str_count, string in enumerate(Hamiltonian_ops): + previous_total = total_qubits + total_qubits += ancillary_register_size + # Generating the ancillary part + for anc_q in range(previous_total, total_qubits): + coeffs_anc += [0.5, -0.5] + obs_anc += [qml.Identity(anc_q), qml.PauliZ(anc_q)] + # Generating the perturbative part + for anc_q in range(ancillary_register_size): + term = qml.PauliX(previous_total+anc_q) @ qml.PauliX(previous_total+(anc_q+1)%ancillary_register_size) + term = qml.prod(term, *non_identity_obs(string.operands)[ + (target_locality-2)*anc_q:(target_locality-2)*(anc_q+1)]) + obs_pert.append(term) + coeffs_pert += [l * sign_correction * Hamiltonian_coeffs[str_count]] \ + + [l] * (ancillary_register_size - 1) + coeffs = coeffs_anc + coeffs_pert + obs = obs_anc + obs_pert + Hgad = qml.Hamiltonian(coeffs, obs) + return Hgad + + def get_params(self, Hamiltonian): + """ retrieving the parameters n, k and r from the given Hamiltonian + Args: + Hamiltonian (qml.Hamiltonian) : Hamiltonian from which to get the + relevant parameters + Returns: + computational_qubits (int) : total number of qubits acted upon by + the Hamiltonian + computational_locality (int) : maximum number of qubits acted upon + by a single term of the Hamiltonian + computational_terms (int) : number of terms in the sum + composing the Hamiltonian + """ + _, Hamiltonian_ops = Hamiltonian.terms() + # checking how many qubits the Hamiltonian acts on + computational_qubits = len(Hamiltonian.wires) + # getting the number of terms in the Hamiltonian + computational_terms = len(Hamiltonian_ops) + # getting the locality, assuming all terms have the same + computational_locality = max([len(non_identity_obs(Hamiltonian_ops[s])) + for s in range(computational_terms)]) + return computational_qubits, computational_locality, computational_terms + + def run_checks(self, Hamiltonian, target_locality): + """ method to check a few conditions for the correct application of + the methods + Args: + Hamiltonian (qml.Hamiltonian) : Hamiltonian of interest + target_locality (int > 2) : desired locality of the resulting + gadget Hamiltonian + Returns: + None + """ + _, Hamiltonian_ops = Hamiltonian.terms() + computational_qubits, computational_locality, _ = self.get_params(Hamiltonian) + computational_qubits = len(Hamiltonian.wires) + if computational_qubits != Hamiltonian.wires[-1] + 1: + raise Exception('The studied computational Hamiltonian is not acting on ' + + 'the first {} qubits. '.format(computational_qubits) + + 'Decomposition not implemented for this case') + # Check for same string lengths + localities=[] + for string in Hamiltonian_ops: + localities.append(len(non_identity_obs(string))) + if len(np.unique(localities)) > 1: + raise Exception('The given Hamiltonian has terms with different locality.' + + ' Gadgetization not implemented for this case') + # validity of the target locality given the computational locality + if target_locality < 3: + raise Exception('The target locality can not be smaller than 3') + ancillary_register_size = computational_locality / (target_locality - 2) + if int(ancillary_register_size) != ancillary_register_size: + raise Exception('The locality of the Hamiltonian and the target' + + ' locality are not compatible. The gadgetization' + + ' with "unfull" ancillary registers is not' + + ' supported yet. Please choose such that the' + + ' computational locality is divisible by the' + + ' target locality - 2') + + + diff --git a/_static/demonstration_assets/barren_gadgets/layered_ansatz.py b/_static/demonstration_assets/barren_gadgets/layered_ansatz.py index 7a26d9e059..22f324835e 100644 --- a/_static/demonstration_assets/barren_gadgets/layered_ansatz.py +++ b/_static/demonstration_assets/barren_gadgets/layered_ansatz.py @@ -1,54 +1,54 @@ -import pennylane as qml -from pennylane import numpy as np - -""" Based on the SimplifiedTwoDesign template from pennylane -https://docs.pennylane.ai/en/latest/code/api/pennylane.SimplifiedTwoDesign.html -as proposed in `Cerezo et al. (2021) `_. -but changin the Y-rotations for a random choice of {X, Y, Z}-rotations. -""" - -def build_ansatz(initial_layer_weights, weights, wires, gate_sequence=None): - - n_layers = qml.math.shape(weights)[0] - op_list = [] - - # initial rotations - for i in range(len(wires)): - op_list.append(qml.RY(initial_layer_weights[i], wires=wires[i])) - - # generating the rotation sequence - if gate_sequence is None: - gate_sequence = generate_random_gate_sequence(qml.math.shape(weights)) - - # repeated layers - for layer in range(n_layers): - - # even layer of entanglers - even_wires = [wires[i : i + 2] for i in range(0, len(wires) - 1, 2)] - for i, wire_pair in enumerate(even_wires): - op_list.append(qml.CZ(wires=wire_pair)) - op_list.append(gate_sequence[layer, i, 0].item()(weights[layer, i, 0], wires=wire_pair[0])) - op_list.append(gate_sequence[layer, i, 1].item()(weights[layer, i, 1], wires=wire_pair[1])) - # op_list.append(qml.RX(weights[layer, i, 0], wires=wire_pair[0])) - # op_list.append(qml.RX(weights[layer, i, 1], wires=wire_pair[1])) - - # odd layer of entanglers - odd_wires = [wires[i : i + 2] for i in range(1, len(wires) - 1, 2)] - for i, wire_pair in enumerate(odd_wires): - op_list.append(qml.CZ(wires=wire_pair)) - op_list.append(gate_sequence[layer, len(wires) // 2 + i, 0].item()(weights[layer, len(wires) // 2 + i, 0], wires=wire_pair[0])) - op_list.append(gate_sequence[layer, len(wires) // 2 + i, 1].item()(weights[layer, len(wires) // 2 + i, 1], wires=wire_pair[1])) - # op_list.append(qml.RX(weights[layer, len(wires) // 2 + i, 0], wires=wire_pair[0])) - # op_list.append(qml.RX(weights[layer, len(wires) // 2 + i, 1], wires=wire_pair[1])) - - return op_list - -def generate_random_gate_sequence(shape): - gate_set = [qml.RX, qml.RY, qml.RZ] - return np.random.choice(gate_set, size=shape) - -def get_parameter_shape(n_layers, n_wires): - if n_wires == 1: - return [(n_wires,), (n_layers,)] - return [(n_wires,), (n_layers, n_wires - 1, 2)] - +import pennylane as qml +from pennylane import numpy as np + +""" Based on the SimplifiedTwoDesign template from pennylane +https://docs.pennylane.ai/en/latest/code/api/pennylane.SimplifiedTwoDesign.html +as proposed in `Cerezo et al. (2021) `_. +but changin the Y-rotations for a random choice of {X, Y, Z}-rotations. +""" + +def build_ansatz(initial_layer_weights, weights, wires, gate_sequence=None): + + n_layers = qml.math.shape(weights)[0] + op_list = [] + + # initial rotations + for i in range(len(wires)): + op_list.append(qml.RY(initial_layer_weights[i], wires=wires[i])) + + # generating the rotation sequence + if gate_sequence is None: + gate_sequence = generate_random_gate_sequence(qml.math.shape(weights)) + + # repeated layers + for layer in range(n_layers): + + # even layer of entanglers + even_wires = [wires[i : i + 2] for i in range(0, len(wires) - 1, 2)] + for i, wire_pair in enumerate(even_wires): + op_list.append(qml.CZ(wires=wire_pair)) + op_list.append(gate_sequence[layer, i, 0].item()(weights[layer, i, 0], wires=wire_pair[0])) + op_list.append(gate_sequence[layer, i, 1].item()(weights[layer, i, 1], wires=wire_pair[1])) + # op_list.append(qml.RX(weights[layer, i, 0], wires=wire_pair[0])) + # op_list.append(qml.RX(weights[layer, i, 1], wires=wire_pair[1])) + + # odd layer of entanglers + odd_wires = [wires[i : i + 2] for i in range(1, len(wires) - 1, 2)] + for i, wire_pair in enumerate(odd_wires): + op_list.append(qml.CZ(wires=wire_pair)) + op_list.append(gate_sequence[layer, len(wires) // 2 + i, 0].item()(weights[layer, len(wires) // 2 + i, 0], wires=wire_pair[0])) + op_list.append(gate_sequence[layer, len(wires) // 2 + i, 1].item()(weights[layer, len(wires) // 2 + i, 1], wires=wire_pair[1])) + # op_list.append(qml.RX(weights[layer, len(wires) // 2 + i, 0], wires=wire_pair[0])) + # op_list.append(qml.RX(weights[layer, len(wires) // 2 + i, 1], wires=wire_pair[1])) + + return op_list + +def generate_random_gate_sequence(shape): + gate_set = [qml.RX, qml.RY, qml.RZ] + return np.random.choice(gate_set, size=shape) + +def get_parameter_shape(n_layers, n_wires): + if n_wires == 1: + return [(n_wires,), (n_layers,)] + return [(n_wires,), (n_layers, n_wires - 1, 2)] + diff --git a/_static/demonstration_assets/quantum_phase_transitions/Fig_1_Ising_chain.png b/_static/demonstration_assets/quantum_phase_transitions/Fig_1_Ising_chain.png new file mode 100644 index 0000000000000000000000000000000000000000..39abd30b8f827992d0af3f90b35bea799c563fb7 GIT binary patch literal 12045 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 Un|&n(Z2$lO07*qoM6N<$f?KTii~s-t diff --git a/_static/thumbs/bloch.png b/_static/thumbs/bloch.png deleted file mode 100644 index 85d5844fb4e3e78771d1731a499934803497487f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14494 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

l}Cm72zSa3@axZgqrhCLTn1wod``3xcPwlF*B&> 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 V|0lrSpooZI`r5`?^^ct6{tsp8Wyk;k diff --git a/_static/thumbs/gauss-circuit.png b/_static/thumbs/gauss-circuit.png deleted file mode 100644 index d61703b4f5dd2948461cdafaf2132c7efb04b8a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13467 zcmeIZ=TlQ})HMtupi~uv&_xuKUZgjXCejoX2pvKZLJPeIMM0%0p;whAEl95+^w4_| z2m}bdh2BHn{64*NKOdfX{($?;oH;YenRCuu*WPRIwf5d$U+ZboQn6BzkdV-7y;L(G zAtC)vJOQ^Th`)H6{|q1@d1kAnrt;Q5V`t7UgK=dMwLc^w(KyAX>3-v*0r?xoj);#4 z&2l~s5bn1|&WK6QoAO7$yW&kkowwh#{&^rJ_(Yxj>8Q#KDJ47FH8pkZ6h%7E;g>*~ z8-i&*Ic}SqPMxlwJ0CPenSk%!y*q6(I5|&Byed6?uBP_r9{HCqU(|k++`aqbCF6sK z5AS`wp{Azx@J-m4FE1Za-MM@Bs~YLUhYx=|`QMxWcQyWh+aMav|9M<(_D{}#aE+;+ zJa@bH15!5=I9XkH5Yw-io;Lxhy*vuS8M*+3seifEld1GT%uqG~?BbR_@dpBGc+m@h z&CgsutWcW&?-5apr}UgbZVr0eaRK``w#^%>{oWUg0`67z;`uO*mp;t6)i9xKZpS&< zvvKdXdPQl6^2Nabbn6`%y*hKok+7FsM@41sln&gJD*fwSbCY99ZsRsHhSN| zsOLk6DPoRY6;Qg++(I#m(9eefGqo{v@8@!NIB6^Q{YFV3(W*{zKhB2eE7Mp|EoC0; z=yyJM?6 zARJ_14J~zUkH)%P;o8;Dfdd0{mfMwGV%o{Z3Fr`?vOalf=PFw;ZYcHzsR4CV=S(kZ z@I5%xtuGcTX`XC#=@pkIB~+;%j>hgboPFnwou-oC0|Sqz-9j#~d6-~BPoAG7(>g?_ zl`kAM=24>~mbpDsPyw9=O>;W(I) zSp({;c|~;c*^*QV$Ag7%de31M%KbBJ9H@VAWC4kIEh2b#+Q9_8ACi5SM`LePTV#LI z%)Q)W@db&V{7zMAlb->M2pTmkd3kvm(ChQ2^VM|lJLCI|j4pYI_=Z1J+Qk2kHz%+S zTW}Boe`>WQN#0$f`QIB8vJ`{F?Yi#_Dif}pht}5C-18LU8(hly1;M4%%PzhAVToNS znf4j5FC|Mdbi}V+UeAeh*hSYzvq(hr_xG0~%naI-T*zc^`a3bqbCICPtX#9^rb7WJ z?yS40*7#TXU*s-7Ld=&~%5^P=^x9=QK#=J2vyA~r6HU1ZJ4)9SP;h=n~ zGE^x$^y2Bt$_m=EPiT2{)oo`kp0&mZ1rIfd`h+LUgrT-Q@+iju| zUG9#L(wx8gk>zOBHwHz91;b|Ca9^#XZO)fA`psWg42ndwax)D*ME)d=VU^--zpYh( zZ@_@qZS}tS5xfMTL3pw>Z>7&jU&TM3{jIz-=3XEc!SwT|ehQbY>aG`%(}B~Ga+&aP zy|?#(uT2Mk+us+o`eCQyBrCvg@Mx6O=6%ned4+@`E2D#9Mz$8jhVE-cr@0St8~xjl zk9OyR1_GnSIQX zd8R{vbnu<<`HQd;jY|*wiu7>4X!v-30#ivR-(V}>T=!=$$Ahy-u7ia!eGik32)i=PmAy4cY-kog;mEmrIk&E*Nt6A42sbt_Xo_z} z;&eSf-aV*ObD z!TExpzZet&mMwn@|LYJ7%IoR6u%yR85XK~X0(s+*rDgZ~w)OZBMnA1IkBt2e!& zgd?#Jn`MvnESuIsPcUT+sBNIK!4p?%c2qm*3z5h<->p|R`ISc8+cI>gUj^j%Z8_ahQRIK9gpFlgrF?wbwKm=1$aL6(p66xf24cGKFd@aDscVm{T+m_ z*jpxH{#oUZZ^oNz5O4amh)90O<|0(8Q4#?WF4ilw$S#Txka~#Xe!?XK=R*N@2H{Qutd0A~xcGva# zr1w-oVej!t(CVuL-MomXPqPIPlDQa=)J^)}AS-pEv%=V8KzO`8DOifM3 zh17WdS>;1Fvp9wbC}AaijRn+88XDB|SRh7Be=el8=Nd88e17zAXkKwGjjuVF z@g>=I<&e_Vsx)%(d@XzJS0v{WG3fKj%Ll>yg3Xp1gtOM}7@b2pun@Tuxi5+*TLX)c zxV}W@`GHUM*!ttmAg9wwGHc_Ew#T^`r z^0Fia6wemw_uTMa;Z7xEMcP(FV%!XWe$$PHVlhG-Z@n0O&+l6&wcvLWh`;^n95`CJmBI9?@*fh zXxIDE-#nSVbN8in7~Vc*I&aPcbdh0aP`%v#J) z{+*%zT)dl5ci%u8;P2vQiwX3(T&#A5ExBqoB1&qG?zd`L&h1gHXfeiRd&87$-G`|T zgG~l*=TpYpmn+YN6(RaF&*mI1r(7yTn$JG6xz&B}R~STbL3M_*6s7wDk>{&y7pFLV zKm@!0!iS^H>Zg*1qhw9dYQj;Jr!UaXoDoY;{&--e!g>yDjOm z0-8K``y=ayjkyBMb~!S}Y;wKPj3o_p9)Pm6Q8 z2~d9w`#?r%TbgHFX1QIC%C8DCuAQ7rN5l4sx9 zDKsPG)_^L^aW>;+iW70q%sel>Z6KJnH=@*%W~*_!J2$2CNy=m4xlzf#uWni{Lig`0 zDe7$xja*8R-H1ROavvt0%fz4jF*wn5 z(R!Mq!BM}r$`)|e(^$W`lw%@wK_+}XHxMl4Ns}F-Nb4cHdIWm^X8oNnftIY>slz7Z zw38%9`8vVrun$s;lUt#+{%iBHuBfQnXRcSkt_47e3?{XTySNv25_FQMo@KUE7tXz! z%W{3OgFZN=WRWgN5LVQ8$hI&O--v=9#u1uYf7{V%W_ywu6L#sJc_$r=u=yQ!%yQkU z+vk5L#Dod--VMA(D`7Le?ap#JHdN>1ONQ|ck@KZXR=&C$)h86>~B(6^!LkH$W{Fe&AC7yE~Z5zhI>i4Jhx~LjRQLt{MUPQkUdCK&5-FIi*$~S&%Dd z<~yDyDcAFM1ojvevVvsbbxXt?n+hP|KvRZnHgf;VVYUvcOdqb*mnLD-2Ml$P+t)SQkDJ$Dv)T)Xs7kLRUj*q!77mY!~BZr3lvKCR~_WrvfC~2A(tX11B za9XNXtNyy|&_^+~787gqY>H9oS)3|}3w3XFDbT*!wIkvg>e!^gLy8M=CVe7%VV+it ziE6R(OPt>v)f&_I?7dM(Ekcvtp;{LEP07fSjo~KgDF&*%;hh$Wzkqby)f_kPU>~W0 zkafF2dLP%W-l&5|S{%5lO!*F^OM++f&K)r#LPa?P9VG0bXN&B?d##UPTEtjbx4Oh% zbeyFUjTMJnHFep%7yV^Sl`vK9VpE(%%|T}W^{>C4)3K9IgZD;7KI6f#D*YQHqZ%#w z=Eo*4Ua-0Ri1;A-tjYzGCS%>lEbeGY9t-j?j|{-_RXiZi4;7HB7JT^UKvOa{(n|cZ z-?cPr82%!=?SlpD4-=q@_>bJChq}o;`YYeO=&i4$tt6nSd7@n8w0$ULN8x#!=!09%L1(!z`{?s+Sx*1W+pj{pohGB26&q~vy+tZ0h=~ID z-eWClS)uo{bpUYj)kaJ>o8TgXTclEkHb#zP02ikmp%cuXBxHBdB>Ox%mgAJSkN1gw zg!eXtvDAw;bb~%v$&D)3Fz=I1h>o7&jHLT5ll;MySK_WYaa3oCF=&h9mgx80}0>4O|=&JuTA>?f}*(32-b zt2S>#_IgffSzOu1YhsAuO>)XSe5-+mQz1oP>D&%>5`c(Vhj0Bw^UdCn>WBaI{Px3C zHubXr>F8H)4WN@cuR3tT61iQ`=YPJEOeDdAWlaZEYnguY7gAhT$7N90!+wb&_RRpx z;lDQR&iBWsr>9l$7sq2Z0Qflz6BAQDk>3{YfFKZv91l;9={VE<`+4S57KaSP49X@a zaKJz$CMdUh?%o_su;b9P=Z1kz{6V5qi?vQm|AyE8hbxB|r|-!YrE)F)bW!>Gju#o? zqV?O;u6|3oz_DgGRRbwlC?BWRVS%#!VDI-Ni__eI8bd_Pq-jM(l+%3E9fw3w#~Jp~ zE!ltoICEfD?VIoosVe@QRnOFES+)uF5qL(QSDaPnY-+wnW+OLEJdrze_RA_`$z+e^ zAsgz_qs^hb*F_{^yw>v{>mLc(jf2%7dX~r7UH=4;l>R?GhNQ`eK=v2yE|CvzY4I^6 zBqx&rmseInu3xGnwwsu$-e;in4U;5XdvC@^`k?+cV1AH$zMrfU6HR?gz506h(yog} z(u|fr-J>S6c#}MD1%1A4%*ePh_SNzgqJbnR92Ugo5MuPPfnqP9SdG-kUyr5v z!cQ6{*Xrr{{+dNSq?@noyDXWqRvcVejz?z1<}A7(rsTX zJHnDP490)XuN%3}ge;uhFXfG8n_Dc<(byn)` zSA0!Nm?oLcOxbpt1G~b|FVBAOkDZVENS~A#zceyp2Qz@@S6Ta()TvD@>FidC)~2Wv z1Jh2?Gb~dzZm8~c96Zb0uF+((b$BOTH^DV%YP&CUyva=RI6=rI>z1>Evb^YH`_@(9bCE0#xLf@zm z7cb`ULx~9j6{6I&Lp-SdDpw4vBNOtflT zpg+$EcA;>z`ChJCIA3K^ZiXhhJS1-Go-4@gAJL5ga^6qMO`^f@`ustL1`wL$#XR)z z{4mrF+;Xo0l3UvUS6dQL&ah_#Lgs5~NN3f*gHcrUJDfM0h#u6)4-o zpJEP|qC`AL)R{}chk6fxq&Sx1Cn4-pre>12qqiC+PA~uvJF4&`11l3N)>XQn0gi?^ zH%&=&ar5l1+(-|ZlmgL2ITCcppO<{0pp_7ye7$IBX?dPG*x%dcRXG1{-X#9(LGPRA zS=t&J!KL>;JnraoQEQa?EE-Bq%eMMv>rq#hwu6yLsvV(j zP0Q>V<5UKr#}Mg=XJ6l8U6%F*72=#xa{%p!n;e3~rJ>NCF0Yq>jJ;sQr#%9@8|y#I!-s5iOm~X=EPUHT90rKRF|M>*LSO&ewp5 z54cO4iM5jIW{XC^y763;zXB)Iea1&@lHe5%uYEGz&8z1v#@ID6mm?QBKbLm_V)eIo zXk)SQCgst;2+iShLF^ilS(;1kOJTjDG*0tXDPkCrwewTxa+={O+zY6j*sE~)7k05z z(I*^bUR&{@_cTNvJXxYPv2t*5(8jBq_Lh1pf|f0!v$GSNRXLf)BxHJ)nwpx(tdix( zD&iXY_INwMWoy z%>;Mx#3_hB;*6u$;)Mx=rvOtK>?o3VX5Y7qhSn$jg}Y{UlsQ{g;xZwHot9=(bn4iJ znft+)D{&?dDxKJT-Q%?ZrbTP&J3gaS40TIY6-L|lm0FIdmn>w>h`FF7NPI%+#&!EF-D~fxps{I>Xz4aIf>X2YR9vLT5 z=O_|8n-h?k?VTCjxY_P%BEkc1mt?rEcVnOWWqMvxA!Ac?g_-#%>TS@YW_jmRz`JZK z3)i=ZD6mQCP0k3L6?Lt|=ll3-Foj_uBlW9SEgf1!fqI$-OUW%loBmSejna8NaAIav zDyvmSm}lNUL6l~iu-s>STsL!6BsT?^*&h8)h{@0*sLqTvsq9Qa?fa7NhbV1_8{aa9 zdxa2X{i~}(5n1a}`__--{#nM=jJkqM+VZgsH@YC~1%9}B!cF1Xe|RVkaXMKQLIV)B z4wtKa_I@mAdBD+|b$?BwSQ)dQI~m2MY$D^nv=s1}y7%hB2cgUqC1%}1LNvmzNUqRs zB9O^k?a?-d6+7d9fP{8VcSPHRuQ|Skb%_Z5CNbF(v5s!96I0%~XWH$3R{ue}v~*cJ zS=jzyxre06Y0M#<_oa580RZF-4w_E*g|C*oyyjtYkG-4ru6`e&%^T9X5XX08cqJ@k zI!`A3fzIO6k3?VqP5f7?E0Uu8Z`E`_*>gKqlOH;7jB4)bgcLE|h}n9h6m%npSBO+F z>Qxd5EH8AE`>WbYr;&mHwL6FM{1y6RV&}jCGBk9d7<%9uSQ%|mhx`1`7SLyPrm1eIDdT)tC&p?GRu=TiLLvi^p*a&H3A z#R)H5-goy1^P<8lyuYp3_q8~wm117-t16`mI&O>_kQ_v@!gfqu65aohj`s8xE@k-h zLWtPP{FjM}>Y#VlZ~Af;c6z{R@=mnV6wD-Oa;I!&GWk_rf7@s3;%k2wATZU~)YSA? z+Ex42D_a4h5)+2WSS(Q=OB{AE|qq=&$cxOo7XV*Z3@b<-8Y^hXz0gO3L zVx#yp^=u+2!~0mdyIm8x7V-Dt6H>BZ4<@@ z#5a5?7~61m+!m$DB;t;<=A$1k-%iRJSLDZ6UNmmY(0!dyhR6I3)X}(e^FNHKp8r}d zH6V|Z8i1gy2~WsrU$Bdi-}%%k2m7O+t;8O%{G*9ja8lT7A!GOK=4Mme3<>WQJWlsM zYkt6~Eu7Z)bJ{TuiJ?j{^GX$^u|>?*&hvN%va#W&S2}y} z`SWMC>UOjE)>avZN?%=Zx9B?LC+HjjfDw85!U3eGwBvDLo^5AcZui<8d0^`W-0-~D z^Njae8=(n!fsfFA(jBSZmm~pNHNP>|Fe7Cv*5^oI>snS%RPN4p6fy})y9at%V2~gT zzp7kjVulSglo764{60Q-@YgJ~BalCFM)0=1Dwl}6@u1R!eG5uG)BM5%Mw`Yfxi%&v31j z#C)b|>v;riq2;0*u)#%)q`WC`xqi?TrOX+3Zt1&NBJb9S+YuUx<&5C&OEwOrOcDg% zL!ON2hsOP~bnT<#l8B7%r>Ur1N;mRZ2^6#tO^>r`&JKx{jmzI@SaMe@E2f=^0KI>W z47-b6^*mYR_==i@JNFAbG54N6_QNS3gs(BfL*2IjmFMT?=Fa%xs>byCy8HXh56uyQ z$2vAPHX4&fo0xCks64&A<^qp5JHvk-Y(5qc=&N#>_S~3tMP8PGg-s5e>MPjBVECv0-y3ut;>JAWrETW>6|%y1n#OsR0P zSW5%lx_h!UnW;KEVI|oPe_SOT!zRT`_clQ|w!5s@2ginXnP{9mK3TBb6{&dykVvhQ18B$%;x2q|FR>iOGtk2_U zu41+!)!ZeY+uGP2bh7(~qKt+cdS(nW0U(#DkV|;oRs#lJNw-DZvx|3KfI4^|F^MRj zjEBKgD;)+xV+M>Q?0Xr5F?>8c;S;3wVp8kQvp(r1>j=$A=t`GhP6YBOQh<})Q*DBR z8O$eLcep`l^y>P3KcG~8M|r6i$xgT}dE*g{s$1iZ)T>vo)<^TGjjckCHE`QC-TAQo zEV)E2hJ4Y0rMJBL8NIn&eB#a>$qLdQ4;|y_vWu7{VzCBN1isL;^cx53<9X!1lqvmXzD(KXv5M!fn^`t-e4$%Y#y;C6uyYzQo^ATY~% zwe7y0L#NgS=~HF&x~AMMF}LV9Tut3*CkB~?s;%1|T6sC8?wN7k&lNa9TEO^e>MbRL zh=4`a08OZPm&aCV?Pybg7jbO?G9C+M4S1)qe8}mXA43E;v!y>bio*}BQ3eWs^}q(; zc^MU+jkhtH3dMz<<8SNF2M!ho9Vr~esGeXBBReHROORl!C2G*Lt_}nl(>v6c-5O;( zk>W*cX03ypPG-PO*B4tv3n6M1T5m>7YuTpZB}(TM)Vz@P4ae6$M&CsDo(f#%(W~e| zBw`g$72q5m{JNer8?0nkn>#ZxioOF5*SYG!inij8J64Dh>BNspYnE-YJiG_}1m3+R z4(Wq=!+|G*KIZ|Og8UU}o`(|9MYNu_|M3S@s!Wi0PS>C{F30!t&x(8V$JV{cm;(ut-oJjS_4N5i z>8$1qztPA27ELs0b2_C(XJLgJF1PPRM$;>G!rJ+|w!I$>JfhZ{%rtGgFw3$vwFw#h z0O)y$eno}&zH_1T>>W5()N}b|Rv?arlkEs_$ua7NJ^V(t`tax({RaSp_!VzpfXPOzz!Aftznw2f}+oq zVYDwQzNLE&bI+YykFG_tU)=Q)kx%FnE`hiH{S^np#HVKmCI}BT_s@oFiaO2QFv>sp zha{wCTt`-}PZbMUI&(*I{)@@uSvzP?x&4UFJqp~iwD9N^ksd$d9^MI?-=2Av{NNvN zW5-mFX?HjOPa6o2i>0bKIxYNGvGyhR++{}JZBW<2i2fnV;RX~iRZKa@y0%2gy}#YH zJr=?hp@$FSLeYTcdo)KLo%BH{wP)xzJX_AuQx)>A5f0tnl?-OyZClSl81GLZvmZ)|sw?*bLig6pxVr_=vKX?bBf+r3F=oy zptt6q6qJ`fvhQHnY%31b9`?2u1yNjLfh_FJhG7Fcid<$7Z95d`Uu1hX#G0)J(!n&ETbak0e!M{^V(FFc zNTcDSK{*Iw*BWst1ZY^FeQ9diDSNvI-kPrDV>b67B{`iB5-}{$o3ou|CH6MtXvXFL z`SXRC=!L*`C9Uwa;#)a(4}biX%FOi!PV-uEKE8Jw-nc8*+uOVT-xDnT7xUqsv*qxh zH*W)6{uRBrQTC;>tp1Cyx-e)y_`_6${^KlQQ1Q4qcIT{}^EKPS zK+I%c&)9CA#lxRbi1Q~G8nxT~3e^860I#!!1+~dd&I}nVukUY14k09~R@Qiw7UtQ8 z0B?`0S>yCes|i(;@JgzF2)pmBtMs?1sCxX#?36TaLQhDizzL1ulD2?~moLBIY+kVm zHBXt(y?o9l9bqAt`JwhI6DYtQi`;b9LnFxx$74f|8^zils4ogrdW<+Ze!nZY*vU+B zfc<^5d;n6$h`mD$XQwk{!BU{xLZ=Eb7>;Sm$}Fe8T1O(iHxJ(L+(w+v|93SD@~aVu zVUxBr>?Hixu{bD?dmB=JKsRaQ&+`|Veu67SFx%?^zXZum?$V!Fy|XHx{LlUk3=G;h zi3AQ9*e3TJCrOW*D+pPG0}-dQk=rHFgAl z5`i}?P_a7B_%u0QN)yx0dtYzTD_`wV(0_wj9pNz9pg2V)%xV2%1Y3$SG;|_)r!mJt z@218TuD;Q^6f2$|N9gfWjvV@0Tf$USlbiyT<-Z3Osc)OO z(|0_6etV%LXO1U#_xnq-ydNiS^7ic{7=h)F$a(EC&URqNpxwp+6|23Y`;k8>h^A+y z@5yGcXze^yAw*B+qed$ChR`z^*G31jQQl;R!RGYelUx;#SaMwK$zv)HC$s9rX*@jB zJT~149lyT;GR+vrLyZ+cw+!raCCogp1E983dLh%5z|a^i4nGBp25+0ag;qNUw~cut zdETMJx}BO?fSBz~qVnf6n9HsXh+t%9_BvaP^dgobT$6k>Xus3$v-}m`@ap2>MJ*Ccas_)ZZu4(Mn{27|7O~bRumCdYhLW($6Jo zGP+^6lT+sU1qSTr*1RG`g=N1QxBUH=dvXATR2}T{q4|*g5D^|p`PQ=TiGp)=>%=XK zM&JKx&sFZ2W@pMzKA8g2pMq9X9Y?Fd#^1hu`+fAO)MwUA@VU-P@Y3wOD`hR26wS*7 zV;707X`l()cOAEu{R-|vS|FXaT;iXQP3P(91VbR$(6|~}9kxD_qd71Y_2Ca$ZLqb} zaE{m7ppOvD?j1IW!x`VEN&DS}zd&v3ap{-t3*8$hxw}6SloCk|jN5;Z=BrQt72~xj zMUd1L$Eph^l2*WfgGy&l!qWWQFyDX2|Eh=qU9$EHKCHN3(!kLfX9l^9`p}skgvb(G z#Sb71i1PybJ8E-w>S}gg2LH2>_R6H&apLPp?YxYNhe(TuQkrAnA3iIUz-+(8F zYCq(kgF9pMXW6dSO8GTEy&8S0@2oEG2Wpi=I-AtJZny0m=4+?-LfBs=B{1fNznzor z`iGvq$(vAOM--$zcy^D=dBnQ@KFl1{&Tmhd{y_hMysc%afvRsO^F}U}_RDmQoh-c5 z#j<;s9(#B#jFXcqyQIWrtm@B7bj!6I14)fj{OL@;{e9S~@LWBgw1;lKlc}=bV1wI4 zqbv(3^f=Ik%)>s6d$c0voDN)rrFv|HqLVUoP*9QzWkV3t|KJ{|;yT@AS$4#ozuP2Y2EL alptM2&n&uYZ{pAqiI%#a8vKRz$Nvuo`kB|5VT0j23`;Uq?N+#PkIlt{^gW?AJ6E6{or`=9e$BuUjF`mi4z{5el5LEQBM7OnWwt)Ugf8$H%rntt(7c1KCOH^lj_01 zPt8JkeF|Wjd#ygw^D)19UdxsBJa2ZP%f{`YQ~ zT5i^Tcs+6nmf=;Pyas4pl)NafH;n)9LjQNJ5#_G_e_o$9>1HnkxYdVkQ`ObcxqtA$ zf&Oa0wd6LQ>K9C zY+iTfxhJPR7v|?{dV711iHctR)D(B{U-sz~AE%bYLnrq0^UrUNu{0xk=~Ngpv$B$} z|NZJ2ot)hJx~r?}_N7ag;+~sV8vdD>F#YrE*X706ia%Yl3JY217Z$9)y>u&3RZ)pr zURgQu=)}#%mF_&F8zfT7)V~$4jeET5lVsw4-+54-ZvK%KZO)AP=qI}_bG=PtLmCLbMe?kS{V*()DHCjWQXe06=Y|M=p^ zl;h8h@{QZqZU%fDpyp@Wu_NwaP*BiQ{Tx^9MAD;27Oq1LjHP8|mkyqJq-Dx!Ku|#LURZNWuA=?EAAB>M2FV#Y}&C zihkx&(a`sdjZM#esGqwTL=N6{$h6d?(5kj6h-S;lqcSpzUrR2Z*@6d261S$#%*?Dw zK6*K6dfIig{x-{MXlQ7_&dVP)d09F7-}qh7H#8KS?5`~MTA3Z`=8t*$uC-O?*yWE8 zi%$5=zpN40iH?iw3@&r(`@DmJc3|!3oIUGCqUEcA%{Ql?TfAm2ckI=-zIZW%Vf%K$ z9S2Y6k9R&{866qX;*zo_C~hq8ck}SLs%KzOT2Nf?+VukYl7l@7XQczA31U+{=tL1+-J`|#UX-Y;+NND*KNA&u-?;PjROJn8E{jc&7a}$?8J*pk7y_L87kg33)qnDVa?Ar(` zDk>ZoFa8vyr>B1+DJfaTrs9mpS={)ywxsAk_36_bw>_fdb%n>T(Q2<5>RZ9Vti+ig z^|zBxpFSNM8_PDoxcF{j!a?lFk>{o5<)5M=BI;ZIJM#0s^ws9kPpO+v@tLar%Rjbs zPWkaDCPQ3gZ>`G2Od9uTpd!hZaFOTV#d_iSn8o- zz{V3?HR)rjA`eoT<;s;SoQsQ#hD}XP@9yy^(#y!mFfcGYeR}Rm)Jxp{-G?bDKNfy} zaUE;pD=aP+`LAV!Q9?rE#--MzA8)+pM(-%DzVq_ha9~v#{O~}gpvwFv_|p^AnXP+@8+md@aeZ3#YK-RmYnQvXe^uThzw;Pv zb=#xpzy9Hk-`XyO0ShAGG=R+EWWdJ5MLeb>DIfdqys-GUFYlDL_8n?!>XZ9zUQ|}z zT%ZJSUT$tKkD~t`qqAqvrX(c^(FIBmtiAEydi=)Zo-&^W2X7@M0)Wq^^7>zKIXSt! z^z>k#n>R1Ixk*HENi$(HE$)zuYig9%*H@12lXWE=JpE8exuIC&-o78-U*B{?z^4kH ze)!Vu?VX(;LhAow44M%fkHnH*VY*d3|%~WCBrNzk4f9y(!fH!jB>#wW(>(^J#Ts1DTd26hysw#hT z(eVyhN>Va*-$`FtkIjvL`N_$gY9!OMXO(4~zZtW!vkTA>?%%(!O^gBF8SUA(&%(jZ zPVLMig>bQ!p%BI$h03?+$5Z4z<>kC*haUxmN-T@cJy2glY)3{!oa+ADSFYIC(<7ix zA_<_93dFq?SxZ%`deBVnR5A%_0hL8fD*Y76~3eg z56T}Lz5MYb67l`@KRq+^T{-9W9x;1K@c8wy;HCS>9g)L_x7%F2X#Fmd({*M@O-xLz z;r@Lt!1)2Qm#$Ru*Z+{QcReqz8Zm6$dh5$`OV!st3xg>6FOiVT^W!^udV3i+nfkS- z2C8>&%y*v2!p3R-X-iYJ@$vB)ir9UG>GL!52lI$Z5;ymQ2GB~%pZw1S7oKEiXA7QA z66c$tFo^c{_A4VJBPNWDjFf+;9@M=*UlFjm!Fc%a;hnn=nL|3tzgkV0z@h$9J-#~N3k^oDzkoibRvRGet(bWJqqSAW5emkt zBxs+170*@eh@Yi%Vfy89j(&WK`6TE-<^BD6RGJ^iN%P# zd-sFZ&6{h_0zz*G-u`7N5wNlT>K6UB`yIf8LQ46&?&g+k>*Q1r86K{c*fKKm=FOXt zqN1V^WYTkvlfEwn#fy=-}RtI5+Wi@KoaZE`6e$` zM}cpY^`P0Zo%Zi3_>fJHN9#_kOH-5*brF%BnfW!B1{-~p? zLhJ${tiW5{Mw%1G$;SWIS3Ih2Ozk^t`DV`p85fHvaxVbe3q;U&d76(pP&-Xez(HYa zLnZR;^DdX2@s+luV5@HX2@ZdrxP;IlTI$%?V`x6S zh~=ABy1;KQU%vbX5>u_#z)Zr|W-KiCC|t!K!L<@euD}LpRd_P1Yh1EEW{5C{%g|LcWfm zs1ixRbAtU%z~(;BYuBbxqC?4zt7~gN5&4C{l>|WbtJ$CT6t|E376=eWRB%^b8xID$ zsgN4}{BfM0pKrw-uwS@v;o|x8JDW$`t8Oki+ku%lN9;O8xgMF!!OE(HY9EKBxr>sm zOlrUe3?lkspIcU+esIj`4w(!-GWO#~%o2DU)2UOZfLEkMyBXx?ORL)8bTEmxfMvHI zS*QNnkbKZEv%X2k&*bFjxOyf@{Ku=;ul-h1j^7ZMl+?fE?EEMpIy#VLpF&M=Fg1zf z*q*M|G1Bs%gsdz_MtV9c(e1;1X<<-|^Y7oj?S2*jd49wJmD=0TaM$F|>`r9i{ zQGWh6HTTB_b)nLuqoXkh zpGTCeow|9i$@1>NaL1YZQsYWk&C!X82}6{~7*IqaKB5>2$Q>3MI_|%*&S_?5))-8; zH50_(93gO}uiT~mUg0N`dDN?Ebb!bJLiM=VWG_%25`efSWX zbM8qjqRt53MnrmgI`a-Twzl40PSn_fhet0*m4dE*MufZy4-fa=#(j(#s-d+T_nBQR zwzh?Z$nvRLotITfT|Mj#Z$mH0-R{Pi{i58`4qaX~fmEz} z_AmnrZ1(QiqpyB%U;it_5QQm|BO_@=Me~N+Gc;sYP$)?9^72SR0=DPyp5xc^Pitv~ zkDz$|`1<0i>&mR%I3h6uwC-Cb7* ztQs-UQYT5;HEZE>!UY8dyCst&d%k|9fWXMl_xCw{4Mn_k5l$LNie- ztD^%zmd%^q`u@GfNhr%mGP&lk`KwG|xTRTm_jC(8%Z~DhWk8(2D7b**Ca6^YZ{XiMVT5*b$4Wn)&%_ zdyYAB;Oz&qGBXDYE|d>nD0B2S0SlHNxasS^jl>RoRsc7Po5eyDLzpxp?pp^26c?~h zk_p-exKTZ!TeKD-rPpg}YP3o5NKU2ezds)`fqzqR`?dxZe={Ci;KCc<3Y|1P&Hn!W zl8A^1J1s3O$%6+En&|4@6_b*ZT7wE^BgNn4kWg8~bDEf%n#SC_cZQR{85%iT)_s^^ zb$R)5N{adDOShljSYrn4)|Zsv8TtMGTtFd^^V8Ex3EH91}^1S4OvzrVk%=Y#=!#9ITAghXbZlk1*f7%n=gCsW{B4%~&RIg%`C zr`?W%;#wO_#|gkXL`4`ulT>-H$1wV=3vVD&pfBI~5f@m?Qd&8%Pa_4$0 zZ@6TEcKNR_pOr%{h9D5hR7xi2&Se+~u{WdYj-2#cEoCLd^YQU%lMI{(-n^cGgOCIH z+{_cOezvJNzXQ=;;kULJa+h011SMFg7eJ*zkXn9|G55RS{rd<dI1{@Z2VA$opzR_M_mXbCwOcVI|Hq^N?M`7kt0WrN=r*;V6QpFS}rw@ z%-#X8+2&ikmap&Wk=>71Y7cOK~Bw2eoI{%U7@N zdrp4ijMqK@3nDl?oY4_FCJGcrB!NkQjdA`@?+#QFaXvn(P!+z$5%-=d@1r^?@;imI zdUDT;Mr#VNa&Ry}LvifgdlyhdU17EzOQHAV^j;1*cY#-xmHV^UGxdcdq3A(a3tztE zK(*#Qa3E_4j)>OWuTKHTP?lcc@h)`coI40)%h+Um7qyogi{1P6DKD%H%POy!X3fKM>+qG9(3#&C z_4HVMpn%lX=|$M@Odx_T?__7Em?`3rHpG~Z59N+Zh>Np!c62xxrJzWj^u|YuN04eXF3V8X`D=Uu+9yqY`c4+7i6h7F7ZzFb( z{rY8e&fMJG4fbwif~e6OS9f>QD&%I%)QQ6Z2mH^adKnrf>ItyzgnTaE*w|<*v{voF zdfb6IQv?CMIypHxI5wsUR(Uo&$2VsOMz$o2P7aL|B! z6zNn2MP_mObX3i!Pb{V{9Mt5mjp@ONJpiytL8d#uOK0&Q8*#xS-%~FU5ss zU}Rj$cprNEmtrt$I(6V0F1@V}rC1x>A`}LICk-`qTTKlC1hwYvTRN0o7C=sRcXv0L z+y<9f)_+}A0oTV5?iHB6NC%#8xp?s+#g_vZ#ovGMpc58C9gOKg2rAdbKPDoq4C0Vr zAPCzaIbBh*gv7*1X?hI%)guE@2J+xVx(rTEZoIxdYRR^H_d)wF&r)jI+6sl>?HqIZ z%5E#kIgh7bhcFKYt(aa~x{YjBMfr57s;Y8mZfWuS*57Z9^)I~me!Cj@R;->5+KA4Mu&&RAwVK;-J*%&kVsG0eQ<093~=hc)D`z* zva+&rLPBFVSz0!Ck)I;vo@j=uoHYfW+1l9XvvYEq?14S;AT>2Lh^+Sa>kCnL4-Zj) z@BjxLomLH!m_@m>c{}bpLSJ8h7Cv)2YmuyiLRDi|SEZI3GCw3_)RMQ115fDF}ftQHd!qXw3%hK7p;39$9^xb%v~mt9?E+<#G?FhL+JF)ZH%>ND4aUg5*rc{ zQb$SaKmCN4JmWzzx~#5T3CDJIXWNb;_Ss-!g@Xc<$rRrn zL8kM@?Pr*_r6muMXeY@DE3j-DJlc-A-bzP@nV%m-K!d{TTBG(J|Lk12Jkc%6%)p>& zQSEp3SL{3|d_t_PtznA6 zk8(%tQ@eIe3haIdkqDTx4T-*kEFMCh7iMSE6Nw*MTkoJNa1XIngJR0ZKNQJ%R9iig zgHKmWs|m%>vboBCeW)5)zOb%v%Mpu3P zt<{y4TA*oKdO9_VmpzF@dtM}=5Vn0`QBe@qjS>tO!4lCWpxVAimH@|uMngm6nx|(L z*syheemuvC_ZHi6VjWMH6c);^!Y?lez1+O(hW1fEU&vENJ|m4tj+rV*%B$P= zDaigrT~rfExN}HINT|Qug-5}6nKg`sXFKFQA09-+e{S^d1SC)}P@o>{qY-{qo}Zr| z9iTW1LHHiDt{w)Pfod2t(Xzr_%?(-e@c50xrS|RA6!iitQ41`CnL;BeJoHQfFEYgn zhQRJ@>^jzIi|oxsvDPFB8o@JuLVAOwC^=C6BZranKNftBi;^< zjHsdf2=mvCet1y(?j8N&)W8|gI}LmQ1DXqgFq>;YRw;nyP-JXYdTqReK#<+R#l`jM zN!{kudf(xE_z80eqzPz>dNe1kQ9IGPq=CsMd!@JdfP|D3ExLh)g@u9OmvIn-F(Dze zUWhsNQueG5L{}Ic}|Y- z?9x&|o?+f5HzoLQ2ayM-rczI$GgJtonVfY0{;^GH-}@|kWZU!$4Etc1eU*0XX>&%3 zd_c4I($3wxzg&Ykd!6z+cI?=PjEoHKhK7b7+2hA$AfPH!koD$zdU_X8$>s5$4_HUL z%W&7r5ki#+TvJ@4Pd7!r`{U=&pUnj0{^eZwcDXgFy|K17h@_!W4_Z>&)Wm37?z{~( zkR%vK1cB~BKu9_Euu`fae1`C6&kobl(gs#k$U%$T0wuZ)qONLe%t6`guVqfB%*@TT ze0>%6obX}KK9jl~V#P2;2G*3G2ELBK+r;lfKJ^4WB<7 z>YJL1psyA)4=8LO7^sGkdS(_1E8Mgo5n#nP0I<)3u3$scEu6$3vq!-O!02x2tmlk!w1@hi3 zz24-)jdN8ybc@VDDGH!)B^nqQXs-YJcL4r>6DrJ38T&RR%CZOx2}M(uR&);0Cx~(0yq$WxoCA*vhLcI=wfG=unGSp7<~o9?HuBdjZtHvf)wB( z{-XnT621bHO%1SB3uZTao0&6qpvrr$4$j_rtn+Itc=0Q{cI{dr=(Qp4CRXNt?MCZu z^b07rqJTi=V_at^Jify|v%`!#8E7x?^Ybq!JXQ#}LgFX3;s^CGs{7C;p~3_XCBM-? zv&Q34fPz4<|5_@H#b9}6UP+JI9fC%89X7PKzke^9x@jgQmnc1Nq~>irtg49#r>wlZ z3N$n?>}xJ*2UiWiJxxE9%rR ze#2TvC$fxXAHI52WnE8yKQ;9p8XJwpV&EmCc($i1{4s-AL<1nEar0Ds)11*Is33C~ zXa?;N;`2*O8i{u8eSO^Um14`B`|so#7QiSjEEGpC@c~VK6rG`^)oRzEf_?Mv>eylMGqcdQo8)U>(z;r5ub#9w*8oU2mu!i<%)V;fAhkHzBDY*v*qtUr>j4@V=|-;-Ps9X zDgtI~Y>a$+`(AC0hKcBCR&<%`0T)Bje5%bviQ|HT^Z@J`j6%UT@Yu;O2n*ptN^eAR;9+I)^AJOEe#2h@(X^5g85SbEkBM)ue+~(i` ziYB(EDpt>;?Vv?A9v>Ywl*7t4W6ouk()xrW(u^LAAx3hlBo!5_C1JWA!{4+-QIIkd zP7V%ZPQ(}2&YcON@<27p-?({J5*SSMXz3Qq53?KaT{uI z9=N$@qj`Noo)vl?pKu8{F#_v$C-Cqf?15mcT=KW-fNEbDq35xOZF`Sj7ekwY3|Xa# ztK`9Hzhw(fu{=9+%D(L})p;RyJFx1vfDvn?iWT0|P+y-RD>Y6Zm5QgyFHBtCjC@TgA6!_%HlWWj%=}3Ve7-F)9m&MA)mhRAz8NLcG zriLvH13;^yHd9K7J4*8%ESNI7(pCUeL>7&krlw{NV52i#ZL6z?$1R}XIQ-IXvg`xa zy62qBua!wKe%-B`VuOpYDKFy)OKXJE9IF%jLMMKHt?fnhVb;Zu0RbI>CnBx^*k1oew+Xh-yPCV9>_iUWn460gXD# zA0x1ZmUa+Hho&o%=L~E6*v)(ItJ9&3_CabsqcmkZ9@bjDvsr?ZwsI ziShAPptikWqqhIwzzE_kT}Lp^v8OOihd;&&qgnVBG=}<%E^5>w*CbFF31}`;A8{f9ATjRFS)2mon@vblbyf^sg4>NLYaD1E*9UgI;28OZzN*SOR6$%ya zv17X^>IL(M>`Zj%4e%ET=-=N`T#BbZm3Y|v>twa02&TXrL4!W9 zy4}8Y>sBsiP|jmVa^Lv>do>Jm;(uSH#~Vs8pTpdTuL%~U8?`GJoHY;6Xe4c2M|%aL zas*J6hrr5(1h;qrI}X2l@9O0D*B2HL$hj1b?YFUBF#&vxZEI^2L4C}fot+iIiscHQ z%hp7w|bm?Fi)MKP$w(3VQ1ZaYi9Id-m?-!*&C}VQ4ZA4h>NQ z!fV7My9Nd<7hs#p`Yww@=_$cwpmZ^Slo#>FAjDoBkc0B8ly#Dm!_ZS^LwMk(rcn%Jl=M604@5bZo#iwTAiH&BAPDFuN|c9N76sPs$y95KTj15j2f--U^t)C7!FNp2-lMk!!mWjSIp za10XW0QzvNl%W~?^qE-&8^yH2vt!m*!F7;;u?IdvAy@?+nGD*ojPA582mJ{J1t#KI z%3zi6{1|Z&dJ25VIEXw1hkTEuZR3xr6M~x_A)%qkl%7Nhrido6D@9mOxN;bd0zdA> zXscYDpsqHW-0$iUrYwKEayq9`pU8kx^GXX!*h`%_al#wj#pQYy#jah=5Oi@a{jbyz z5Vy!=3ZWYrN%${5-T))fw;&FL;+ha$w;|hy@N^oET~A{iFlPPp&C<*1>Gh*95KPfP zl2b%}`uY0mi6q!S0(ru@EK&4Z4S|A7N&wFyqsgcRo%bFh9pg0^1V)Qh!VXhP6PcL; zz97_??Lb~WRg2g)0@(tJ`u7_)D3vlAX^U0;k1}70;R(E-%SnCILq+{gZ0ghky1IpYdH_UqbPRNxT`enFHp6`{(!Isx);0gaGU zV%x-Khh;f7+Z!GovJR*t~UiCL6f(jA~QOr-s#XMX+uef0(QY8pea_hH~asI06!s*)Cs z${gA8_HEYFn@f`p6=oYxA=k?x?M#Eo!I+SLSO6-2ijq2i|NI$*croCQi3itsYT@cC zj!6_1D8NSK!Bzln0X(Ocr`-;ef%$QK1m1mwgu9eMmt7hC@}8G&88+~%*tog5TQFBk zf%Q(Lgm@10+{mWak+`HeYsF-l&hDHeXLCM?=8arpO8%;(oTEt~IFQ&GIYVP&G}E6v zzu@E`CgOKKv$^ktO&;ywNjk!Mz54xMp9oY*4S%XCJ{P*2`t8F+C-FQ{`$K|z?(<7I z&+eSxN=tg&L1cJwh~6VO5K&ko$*aVmvGk)xiOnR%R%5%*_!xN8^; zksl2#bc+cMZ{O}jZ8!SV*htPdeIeoude8zZX?|vXWzG;y6Z6m2 z!5Dy9WsUT{s)Jc-B>}de*RM}dw2r)_WXBkuIZ``|8lGmylQTS8nwsQk@ceY3{1}=W zlYi0MRlp>&oAl=TxhY$#(9n+;zqlQ9c_mSdup@XbIsM^o_fLbN_VD_a%@PE{;5 z1YU@D=e4!lHK*rlm)y9wyb_@Qem$3J9LwF0>8X zZ_m@#`n}+Mwmf4y-yu;EyM5xmd6GxlTh38yG3Yk_GZ@gUww-L-UQxd${qgeVlkD{X9x9q? z5yAJPHZ@P^uY`7gj{3`8Cc`T3ly_iuHS4_lxr*3sX=yg%Q9pAjc1C5R#>aX}N|qWm z+VS~;#SGfo%3?;!Sq5}=Y@8eucZL%M)0vK5y!DW&LgcW(F_|n?kBH)|i#~QYu3XWV zbXoi(tYY4O4xN?uxw*Nc5NG?B*VgL6n(}bENd#@=@5YFLsHp9T+B<$2vjPNZ{{~!+ z^_3rqK=pBGSDyCNyq8b@9m3RX&x#-En z#PoF^C+9fGhPeTvF&i>T+_sUm2lKLKK0d3aI6ic6z*~DG2oX9D4!JM^C));l@40{f z{`0Um4tscd>YmZm^!fsU7y($;UNbF^+#^?f5qd!dQ{3C$-{sa&zsq&t=D*b!Hs~Q- zLWYEb#nz*u+rtkZha2>peb=r!7R7Y}#m2x*$HK@pEp_TM6vv4Oip7l#ej>7ezv^WS zc+jJM^2a>4WV|m`W`6zeyT1<(PF7zTT@vZcRoj2Ha?~?YzA>{o+fLfOg{kOU4ariV zRH{SmqWRvg-Hg$E%wrScn$eH%>(}wqg~qbw=x;kOxl=elhELhRW{}D`AK~@#a^4FJQsmfr>g@G~!B8>qTg1zVfSAdGvbab#m3(=re z)p!B+Y%SDQYij6;P}ST0yhlY(b(Ctw6_@-s+4JV+KU%IEDYX7e&!74%a3y}p_ui76 zyl;QO<=i6$7c&n^%dj>rs8R2v=A~AC%s^nDPHo-J&qo_5POFrwH*TPv;B&wxR9QKz ztB{l{#mdC_g7jh6+u``jGU7*1dyaMRIxe;>FBRS_p1r~8{b*ntHKF8L*!Z@*3*{O* zHp-i&=@X+V_l>1wJ`;^|x*LXzeN z0e9}bFZ=to7CE06wtcUuw9{9qK1|ovz(XFyB0Hi*Isv9{n)T#~D*OX@IW@3X_2El= z^MX;spR7Yq9YjDg(ES6toDK8`XWRowa&9 z;1)R(ELia_s`hWdF?bP6G5&7Bo_@e|8b#PX&dZB{0(x7$K6|PkT(lNdCJkL;am+&4 zr0LmrR|f>V>*?XZxhobK7qjPP<<12k3;J#_^Q^4YzswP}Y8jOdJ@z+DZE^B*|E_q4 z{Xql+toqvpoo$(h@Az|)-)Ppd*7?Wmz3Z~iIw?dPB^se>z%)jFqUs*kgT!1iUX#L=@{ zG_I_P-`5=|N&5W7Yq0OJRq&g4IE7(7hAk17m*;{F!H5Cm=$IHP%8{tcpZ1v* zT2X-(?JyA8iB1nSEj87yojcR2Qx!=8I-v^}uGc4=`nBNyZRx4A1?Sc#x2CpNZh}=? zE1zF{XUKFzmuJT*VYQtRN-+ll2_j6>314Q<=-eK?od3>ClAuC$_5$bc=85Eq^Vb~g znW*cO?&=quJ8*t%sr-ebp>am(X1t!PXV#;fo^_Swh#SdSXTIG1%YOW+MrYloSA(~| z48Kh=_oL~^-(6*C;T~$?=|(D^Y05GeJXY-2!n!Z{q>EfUVO z8M-R7;e00U~eWDztR|}HU9+@@pw(tkvQ43ZI z;-`{cJ2SsEE0CbXK`4m&F?{<%aK?V#T={IDVxMfEofu@`<&$Pgcxsy6#cg}FU!>af z>%v9f_J`6LtA=B`-o*ImBmUlYo}XCY$8EYRY%s4^j$Xdt;W3v4z2Cnhob(^M-vgVU z{pR6F_8Fz=(E>ZZSfLpx3=w57%*tSeQ(~?#dN`!)_1rOI)Q@dQ+PeF&lBO%~QoJsMaS(B{`w4nL%=-%;6yKJq3f`W@it>(#s z3ECXahZ%qAvMD`&{BK|&&5b>f;83QioZBlx_oF3&iog`Y_mIsxP<*F;mcOrmsOK|d zXEU*L?4w_V(kn{~uLm4zy;Gs+J)7!Y^m&u(_>C||ZqKr@y)zOnu@ym2J!p3PlV8wX z5INp!oM_B3t}UcS+a>5t5Y}%?V)$@j^9CoM5`mG;d@s>&_u8|MF&-N97K%RR96Q%6 zeCn6pe5|AEyQZS@Iw#HdA7RpYC`&|NpaWko@Cpdn?%*39xFiot%?h|0g)>seF><*I z7w7OHQPJZ|$C>|4X@jV1h$J-Ufp5Id&B*ZRME`E1e4`Jjse>t6795!qNL1_GN+VR=C;z~WFq+bP2PP9lRpOK^XdQYg> z*>?vU1(w^41@c{=37_#k^q@SpP~<=wL5XnoK?y%WDXBVOjDfdBS1^q>kV+{y#C4pB zK&!$C@YIZ`Ht+2KAC(6XD0)jD^HDh*;oy8E`@7<@RkX2T@lLs zI?t|bOQ2&|{$xwbLD0|Rh$cjTRZ6h;WvXKhCcltWVW0{lGJN@C(wBDQzwdSNkp>KY zJDIXq<)>GhbQ%1(2#Qm(bdwg7ujHCm>JF_vudV;R#=Aap)oZ@-zf0LqGK|H-fME5| z9(=sK1Zc4$wEM2X+O%?TV8UUzg4OXSsVBeW8BGnjNB|Epp$f-46}_ZtiwgG<70SuJ z;$p=C`t6vn^?sC`EZ5rJK64HXbwk2(LG)XdcjuSx?uAJ(P4!a(u^ni(oQKcJ#m%j1 zW5bUVB^Bvczc{n**s%=?l&(*Gn`Y|w*JG_CmAu2_7d+n#aU6GYgas`4TA#brKv zgOSD|n*Cvsb+`Rl+PfOquQo^*tSxaE+d9~!+sI`RLhQ4`TB2##tQcwoT|J-Rg7UfP z?OuaRgpZZxA^G2Q!nJcG*P>bD3NP1xmyt}?DG`eG_g=2>bJ_UeaaLRI!(+G8(#q-u z<&2Go3Q443a^$-mc7z~aHF}0$VN8fhc7a`AjQtN66rC2yYid9G8EvHxlkbMVmlZ3z zJ8lN>@t^GDAM_r5*uk|Td;HWbpS@hB?4<=Rrp>HY-SG}x-cFIqRE@i(?*-DbD$fn> zU}9jt6>wxf;agLXWkv!q^F@2(Zcb(1ck1u{m`_zYrD~jPO{clxw?0Y|z z&(h>JxQJ)2dwMzz{rYtfr#&(;D4&Y5c$y-w*;;0)Ccj`7qTI>Apo?;R{Jn=y zb8%uh@oJuZo~xI}wC_cjTmAd9F;RyrTmn z9{X$CF7uYoX0=Ovb$n;E8cJxFe@d|Y&GGjpvo#^ej+Q|B5G;POI`_1GXpdBp&O@{3 zkH$Syc8P5E-=g?HbRz3~OGG@gg+irs z(r!M|j7HSVrU==-bfTfNNnYetW>8L$mSUy6+_qL`5jtk;^I}`c4741H;(_t@*-5O= zbYt~-L(d*_F+Qo9KQ0c%}~m2#UN#R?MGXoh3d+$;b-T0u9&mih`ySl0%;Cy;f-%=2w=nLa#$r_;-H(yJ{~XHlzp-@{#Hm)O z@Ev5Q;<#I)5=5=aG3@yEc_Yi*d6D=Hr#lbrOv0==2xXtG?O09d1F0T2@)DFYsp%OQ z=$M}x7#w_}??`-f-r|YQTR}482|uT}xpNCQ9C!SymkCrR1wr@>fNG@#EL#)T3w?Kx zs(h*MP8>UbI7wFR@<354IW+Wu@rBPp=|d{LrHq}lsj+ON-OlEL+ggqV-u`rAA-}0) zWa0NQaW2IhVh@jB+xP6HWzdhPkLOiaw@H}4bmrrj;J)--)_XskkfM0~!?{)rl3zcP zzp~Vu3;pX5PSdia(idPC=F?=L)8?=WZBs~1e#~vh3W~S!T6sFN+;AbGj zjIr{XBwV>HRBKzpi-rk2GHz4ZHsOfyw2* zZi!sIJYvi#T|Q-%AgW9Tb_Qi?rRrxM_WH(}L_G#({l|f%xVGF6<{pyN@kjYz@DMf) zPda!Q6JA(k2ECy&FTHYBv5&s&`mqJ4VgIxZ+r@@t+osQ1&1;txlGf9Gf82V{V$~X5 z)#{v(3=!6+&u#lcN4u{1_d;L(5p(XaC*)aQ<2hit5dP= zne>Gp-VI0Xd-D@)BIjh=gW}f0(}IXn4)!$OD=W=8Kc9#TC+F0~{dcHBJFgZb-+$jFAzD3|aa=~|YgQQYg5=IJbzB6%1*phxmlppyz&OIiI zQX9f5O_i=5EH-3xWY8jn&!LW0n$?PtRa$Q{*ZbaDd7uMf@~W<46j$Kc{j7-uYhl)1 zL}S`IYSvxR#Y)lVi{rm0jh)xA@8@;1DUsi%89LNlyr1vpCtAO!a~k&+EcXsXd-d&f zR{q6w>g}(C*9#>ceSdw$%tJK8J&Q}2&6bLBtERDbU>1WIU&sLlF+yPo?Th`uG^tGP4I-%p)$(U~_SqcW;ynRr+!sD_-DQPa>vOFz%Wps}=v zE-|aEz{6;)uY`xdzx;ik#ysY*ltT|gc4R7bvK*)D&*5!jHUn8rZ!hfT<{sx3zPdo~ z-@dy#`(m`Rw5yXbo|vC^X)Q2c{e2oHvh^k*Y9VgQXPyx^LaV7{L_gmjfT?mRe!Y5 zlnQ*}@-R_$k<+Gdsb}A8$I?#nHBzB%y-n}8D|57p`Q}2e+~-APw{g+uUC%f4{M)AH zJ-&FLe&fVi{Tc3nVD2RsC0~8tgeax=QbXpn-%j4-Uferi^vdchb@j);@zo=*$#Ws(F(EEb$4(m2{cmQqGc?s;iU?WXCl1h zV&n^p?w1Aj9xkc{qUxVa#5s@&l;2ZVp8R>sQt4si6KechO2P& z!;irtU41|0yFG47pIoI6oWeA?(fVzc4a+@YtoB`n-|t?3{qEzR6OP;O`V2*th2^Jb z<{QWyJ+f8d@b}w2_jm@EM=mn`QvR5}_x6ow*8wA|r!Itu&zp}Q9=F?9V*gL&hj)2| zxWkyHaf!z^r!ToFt7X5O&OUd3Z58!X5<35G+h$}aWRfDK$?(48; z)$poz8JOo?d>JK1PY|xpy1=L!(bw}6&cXOYV%YISvq3A#I#pjA=p4^S4_x#<` zs{3JND)@UimGUnkb?Lln610X@#2rUy1FEt99n!-Oa*{TeLHETEosUD$i_Cs?y@Eu=k5#r7@RL4E6b@H!?E942%Jud|*o zYy3)tBHW&-C5^JNY*vw9b!YyO5Lcn_zjM&y9~Ob!4NY$T@?FL$I}4-cqwH;x zEKQANr>~!B@;>&R!|gNfu=UWE8dj|~S~`0fdH(ttjCCFducKc}Ph2`Kyz=Bmk2 zGKk1fL_!cWo&uM%L_h{v%IFY=NR?{c(9F`N2~zpn&h6@MTQa(H-XAsoe+3@DOm6=8 zWyZ1Q7DlZ{+1n%<=n51p1q4Kj1SrGJ0@XCzb1S)sCL-b9pbRhw2wWthd7p5_bZzTC z_Z4bp>g%rkua^)~?S%rvFq-eb$cRWskA6z|)bPsPE0g;7?62RhW!s7m39;tBLB8O5 z;UNUQ2nLl>C9Y>(Pj}tEbvoCMTm!MB2BPAkRF1s{DVOb7VK{62Oc>Z>Aozwpg5I5a z*4*&EX)KjV;n2E6;2q)x)#B=k*U^!c-JKUo66EydPCo zRvhlzyuRX9@Kt@ORNU^D-A7^6%#n&&ljrE#HEs?m87VMz+%(vJc&l6@lj^Npuo9lV z3WG6DV<0LvN_ph08*}aWb>Z{Z&!Jtbc2KWQJ)E>)lKRN5BRW;pRiILNuRP>gO- zKY5U)N&Doak{c_IKkNKQjQ?MOi(WAU{a&Qs@6*KEW{N|TIDK7#LanB?#sdH-q9y_) z$P#TNp}8VKmO-XAuUs2x0AaOxXCz#-K^RU37{ag&e2GqLe=hi286iaTGp&pW7?u#k zL#GdQCk%ng>PqqGIb-Ce#-=s8y1L>MS5JzCj8HOp>10_}UY4X${YH{&_peH7#5Ixw zXAerRKfEq)-Mp3bRn$vqM0AAobroib`l z+QfEEQ~R46^z;u-zqajKIDZDt-=QO9VB7idMN+Zi)c6h!EBo77W-XbuIJvT_vTXnH zePCr_g@$?t;5^L<#1b)FymJAHN{g_yT`OF&aWRCv2!Yp8uOToj5IeW;g0~*rf{lAO zf? zM1X4KbOk3rIz){?)JO=6nrN8_2#b`{DOw$aS{*~a=p3%b3ba=5Xmku9QU;J^G&)Ji zvS6vNTRo+9gS1vc;W%H-a7b850R^8xLcAa=gT;uanWoq>nIv%R(~WWnN81b!GO3x$9FDN|{gygnTZmu?{P& zVq`+$=Cf-ZpMQtx`qP1kIy=sVZ%2h{hD)c$Db_|Ju^Isl^$k=WejfFgZC=(V`E{yV ztyJp0eDf0Q+uFnUdE?>qzSFY)z5DC=J@F&0o41CYZablgZ4<@m1E-nCPaf+Cg+hq_ z7!B^;?ldMeM)f}Sy>4noDp*-s!K~%8;F-tN#OzxWcN6g3XYCv|Md%n1)M!9WbC)?yEmWCC7uNVjr z*3iH}wfpB?osnWoTN_*bt%tUf!q`HntgeJB?pMU!I(8F|n=?)?^g$@}>e>rVUp=jA z)2fYZ$@(QGQzuM?Lt78Q1HT7QURhrL>g_9wnD`io4U2{7kI~>C>|bHiprPr??W?LD z-+6qceXI6mN~N6nhc~e)VAA{~wIV))Qe1 z6$dnpqoFD3odf0>TpflCI)BFyvBKoZL_($bC(SCZz?n21J{}VhBSirN9tP zaUFnr4=5laWf%rEsfNEzUvX$s67+AL36Y*2{qXUF*^VPS<~6ZtV!3L;D(GV01$vL} zUDl*g6U)JU9Kp!Y7$%RL1p7}Ns5yJ{jPAMp=R^s~39xv^Vu(+QSM{GfKz{F}yYYvF z4={bqG_ba?hNc6XSFN45*3>{xAFR!-Wq~h(iq_0q9bL!VTBTH}goGla!vM*tmYe%$ zmRnWml69BY4h{UTOwykeMAY4MBJ7wU!wjF&waF(#5hGW#z@;u3!isc6?5Z6sr?pE0s!y z(a6(3^y>VYjseYfCjUgo0HuW1>JQDrW$vmmASeLj3=2}0GaQslnFFFglS*d@03!k< z@g?F0kF(0yrfusk+BCV_$KQKSewP#$0f231o-dF~)Eiy9H2&PkOs`U*WC1wC!6E^! z%@rtBsgyXwWyYuQci~kX+IFb9cJx|huhG3K!Xm;duHU;ZHqbYyescdw`A)Z;lrf>=s`st~Y_zHX(9U7dVmnR@@Sqff^D zub;}i`NY%ttcu<0+NJSlTXTadxk62V5EcBi1^*JH2l zj=dc%-^ILx5AiY3X-FrSFnm1jS-%^-gM1(=_8qt`odQYeiRgFL595x$@2>jiE5Aqw_jF8cXw^WXQmIfY zBMKIfyykwbX86G2l{Zh`tmr+ucSUG;X!*_i zH>(6fLFLm2Ps{cl-&c0v_<^#3rvYWrAEPThuXvWq#tDu2nnBt*f_M^>*lO zn30oVHEP%>tA0KESvw7OvOc{1km1wtXO?rP%r&)XU}Jy&`guEZQ%hN`!O-Np83DsG z0)}M>_tLHhAp(t2VThU`45wIts6itf01QijuqeRVS_lxJ_$4>VUAbMDC^Fpd6Cji@ zgv(WOxiOX?P!$C<)6)@8Y-U}kCsFVF;aKR1-(x=hjO;iSHj^rG_oPmZk{g)nRjXNw zgmB|PxNawwU~P0kH8qY}Bf{l^HAaMd>8{cm4s9Jo^S&ApYv-3>ZT(7(z^H*RED%QX z03-LnekCIs1H>BW8OXyU!<#CU^4|C=G z+O=*A!v_sVGgC8CQB?ugf4K%t8#RL={e}{U4h|Uc{uNw2Z~;2E>kI>X4I)E&4~F>E zco^2tN!s1MTkhb#j(Kvm95lHO&GQ0O$s$uzvtZip9}~?0FK>wJ+Zo4vDKxcO|rF# zo>;BsQVW_zH57nb=%s0|q=XPwrjWt-;p5ZGE6bAyP8SRg=IO3_qTC?40;s=!dJ zJotlKQ2*a&YX2M%0QmXp>xojCYL`n#o5VUsdJ>fyfe@gGNVUe6+bR1?+oN_4YK!8U z$O9Ot_3|ia+I6)KfM&5ELpV=QBZHcUSUcBWd5Z*DLKrn6oP20yMB^!{bVNEj>wj7| zutr=X`({vH?prkypaVw^RILnJ}F68 zsH@`I!Zi&Tn)Xei*}BC{Uq?Kuk!7(36&ze~`g!l~J+J>Q5K*xG?2B!N3^T^HOXE)j zfu&UQENLQDLI?|#lP9@M4zniBN_zG7RrbskGhtBgK~Pvy2wso9;N6FJuxjTj5C{Zd zWo`wJ!yZGx(*U@5`y%)T`NE*SgW%PhS9sU^E{4AehjqKw!MNdL;ZxElxa;i>u?ev- zXT=<7V%ym8QK0YOnwlC$AP}m)mL0A9Fam~?H>{0BxJ(Bk=e!{23?UFTL%4_pfv^nG z=pIev&1kY8ppgkg!V;vcW&tAwmMaY)0Tw|}n*|{NEJKhbh*Zh4(9F!BWK=^7$VwD+g7QwBhQ1q$3lqA@H8NGZ}UkqN_jeYFM$ zzpM{o1gK_!rJUhsG#;T)4FuIliJDNNMoL(%=Z9a(hpCCFV$YG?-J3RUW;e!pOy#Z9 zH)TTy4wJPXU@x<@w3H3*>!?~dW1;lXHD77(v3;Zo$)9E8oX091IyuPFU#7}7AKW5y zJAXv(G|)-eqG=0N#Dkaevg&gAxJBdT9b0!$bnoaOuWMPiYSf_7DP22sE|DwcKoBTJ zLMagK{c|#cKvk?7to3zkoa&lNMN;MdqXD0reDAsaqhSDG`-K;CS($3~v@W&@IzZH# z9i^zoaiALKf@2y8MtDazOWercBg{XRT?IB&b_6O6+V^AnF`)Fy&?436OhQHWh$l8Y}4M&f~b%v$jZrt^?NpyE}XIO+=w9~a>SA< zApiiSU!8_UHCY1yAevoxDWwb$E+7O-8N$s7160#-rRl~($`!4kwiAoHB8vj}YCQ;5 zvjPC_cafq1DKZEspcDm!0)s3H5Ky3lKoYBDmQO2cN`?$-w{T3ihX3>cp}z_bpC{CR z@+$kDbBlU<4q6%E>KRn)X=}DP{30Wot5!xd4_<8-mbR$Qa0{>~5TFboMFa$fpd0rA#Q)rSIRrt8?M@rFC0YZ84hi!<5oJ8+Kb88yN$` zFyQPs99(~JEwQ$+ww~!S1Jbk7V9c;FaL3EN>f-GSdRKnFqWd=H4Gicp5F|2*a`fCW z^6NieHOVc=1)F+}pmWx5QTXa6v{$D+8N-8ysLlT?tQyhT37;QsWiVh-{9x7KSNYd6eOf1z~_|DiWbdVFjE&z z)wzG^J~XV?00P5;ut(P(%BAa<=#Fw413xZY0ltC0(4c+;nQwrvetcp)_}%jZUw>aX zdGTc7&UHJFw{6|FQmt0}jWhmFbqobUH2WZNV*F2a3|A=E7S34^AVC5E455Tkv#fw1 zFhDeVrB=^yx`k2{0N3y%Km-9nWRQ~|0Rl2Q2qe6ys!;-ELw{K@X!E~1Rra^?O$G$B zQUAxgAy+!rHSRFHZG&W)ngwmDLF0XY{ou1&D_E-_G<5=jP@t};s1U5*y?*N4X>-jc zEtph!_uO4!R9uu!bxk!4cN`9z_ia{<96D0jsDX{a=j6}Ou627zO!t0fyUsfNPmUBn(BhfFNNgiwq&au#^=55(X&> zxXvqLBatS_tjt99?R_rv;*?QNJ-K2|sXK2@|9V8?rRCH?BQ9mOm zOV2C73l42Q2!2ofXE9H6E1E^il>V2tIB)z;Q~ZbG6ZE0!ljx!cquSJ0wySSi zs8k^kZf`P{XgWJJyAjkzAw)oGYmR#Qda8t^1fw(8&$x8y*hO#Kfo&3>>pnW(LEa!B z0&pJe3}a@FQQkUpi;4XdE2yfj0%u1j*nV&a9n^P_x<#`VI`K*I(6(h;$jHvX;HSaL zgF6n20-gkbnW-7rx3!0vOJ*{Y#!h;B=iHt96_usJ+70)$I!4=xMSuvnpAf3)vSJtj zss;uKCI3vvfM(G$WkEn#&UpX;f|Lj-su_ZWq4-6AAW}|Fv`&b|U?4>SvM6AXFf395 zAPR&)1U}iNjWZcIetyl6OaGVd&u>a$ZF>~HqOP7!FUJ=3)8uMO7-YC!eQiJfmpp%M z-oMtP{3`EFDMi}`wo->K4$q&yc*;D!B~KcQp3C?yYN}Z)kLU|1smJ z4^hVDm1V}E4?{smsMF@)se_$N42%>EK{YVIaxaDjWLPA?G6*ao49f_GEFoNI3FJ%4 z2?W}1A1+VHrBpN_B++=o0?k4~Lb#9;kfnrD!tJC*QNUs#3s=_D7xfz4qHda!MPQJrU9-Uu=1VOIG!2bf z89`1)Y9m&L)VA+3grS6R=_;nSu>mL};J(%t%~8A1kN^veur@V@lwrTBcc>Ze@2DAq zYNW_A6j>uZBgKX57do_S-L6~DZav_2R0QnZ^b;5v8H4-zyU?v;R|tL)0y8Gg#18E` zz>4`RVAG0?@bqOUG-=cXW>1;}o!fPSyQlBMr<6}nTv`k}SM5MUeIxL?;0Yp;F7*oX zs%mT3I&;*}G1=9UY6c0V0)}XcqyiQQsg)5x!tHlJ2=_ffRH_mB*E7v(uB+p`_RNbZ z%wJGOehoy_efqKFV3+!4I`(zVij*u1+;aw$FhpBprIb4lMk^a!)&sbxR3?{!eOvo- z+eVFF7nc<4-a36t*UZ?|;NaE+hOgeeGGtlS@ae-)eM197gTuQH8`X$wjNU}QHM)24 zo?*|fJq#Djm~Z6Jp_|d8phre)7q2nivU-c5eOr6ul|TGw?El2yxLvEZ#@;u*Mb*_+ z-Opb?+rq@yM8Rle2(>y!$f$u~YSZJW(J?|GYOHMrVYo$uf2U(8ODQ6u3;`Ae++t-! zq};*)hUGR5rwD{=ARvUHK)6M?NC?%+3n7F80~8sG3=j%Hs2Y%UtZPzWs$f^FIsc-^ z|7EWJV*_&H_Pg%VYS|BC+BHn!P7Fm#39U64njI#;^z#GvV1PiolK9J>MS#GH#bQ|f z!^+PTY2x`C7jze_U7+LP;~}zXU?bYJaCTymIFXuH3#7EzPa)=0O~{(G|J^s{*u0;cysyE<7)mZG*ZPh%ih z%AMIz8=(RVRI>?@c1;iq08}EAkma+N#FbZ-XRX+}LT~@c1EQ<$*F+1Z&limzHcAwm z5G$IwY^Lae?|o5St2&}>tG9}jN`>x>r89NB9((J)k9)6s=I|L^Yb$HrbvxII&R;vH zd+Yu!-Thnl>vrnUN!KU9TM+sFjm67%5hJs5bA^mhM~w`_G7Q5N%_(YHb2+C5G&|~Y zt;ZUDNHsb`qho+YiWFEP5PVfo=W4H%q5m6Hi=dbb6eh7H00t{-ORWI}p& zI?P@%2Tor(jaeTu)pMrIZg%S8$$^ZJQE9s+IPZYrJWvL>Vist&yb!W12pLWBO4If$ zU|19omLW(rUaqDk7bzpqY&{?#ntKO|grT7Q{;QX!)eZ?kZk>pBoHS=xPyjR$3T3@a z4AlAQ6dF&u<&-xFX#t)#UP2%~A;7BXsvfEqR3Gn&7l zwzRTToWFT)$jU`OT82CeDV(-sdT~}>R%u37y7=MM2PNIRI25<)-MTnCKf5^QV~lvo zoFyf<&)hC)(WhnUt9P$TKO}spv1?*i5gYQMbn&JoWp3w=l%-^(R#w$iN?veu^vhTQ0jiY|GaVgiZ*xPGR!TM^ zqRw}o`rm~M_hZ^g#LDHP+BHZf1X!*)@5{Yw$D%fBB^vp`+QtiQ8#o03YGP`l?A4{$ z({3HRDUSVgl%%GoLS{}DjGsFZp1usjgz!%&lgr_A>Ss84`4o(sGaf&Ne?sla_*O2mgZ)1B--^I ze-nYQ3_&#^3`+!DQw~EIRuiSLM8HKMAYfP&Xd(`c(IEl>%K)&1TdK^pr3<)ilL(j2 zr9`uD93g}f0aLqI90Pq{Ux)tqoM2&yB_GEeOA$tEIzj`M6 z-*ft2*n)-zx~;o5GLKWKxkX@v5QSmICSm?sTrxHpfDd!A3hC_Lj$0?rkWTU8h}rrFUiTv8S>=$lea?# z49S$#RO^6H04&RL!;ge8gr&f3ZiaviL8M4g2f4aGAhHA*)Exf;T-N|01i9S^C@_Qp z%2MvYLI#jgX-+!gPQV33Zcji0n!VV#O*RMu5+q!+9}*-804mA~TAApTrc31=_jx{< z3;;L2WrF|P9KZj3eqB9Li>{5#KPuELXq|~#8DX^BlmJ)9_-gezfTr6Dk!3W+bjkuH zTz3^$17Qe@piL1nEFvN>n$#%6P@n{X0WM8OSwx`_HLK9MbJ7J21(pQ>0kHPtUom&&^@ z`Z;U@0NndNv;O;lh&n3|g=}_cV8+%p(yvjlENbShHvO$_f3I~sG*K*Sx{$cmSfmVa zU7u2^6kNwn%7{ydjXrVd#6Tbjdp7O?4_^<^)e#X2NJ5?k!^s1uprot>bOb`uxS=iC zb$AzS{Ba}n@6`|1E?ol-UAht9Kws$EsS7Nfy987!6?E;`h2$0Hk%+euLc);YPr^gv z`uFZvP*p7!0ss(72?zxMEOLc&LMTv{FoYoG5>4D8naCgzmT>#L3pl?NDF6Yk5giE- zf`4=TG9d(zYK~?{BwE*<<*N5AFq+0-LI|ZAxna2TC@AGls8UfR4wi<6IfWIIHe7uj z1OU74y$tK$(6+p1csS~#p_y>t~zwf0P#hs4_GgFBCbB4{EG1SAA0VKiPQgBp*M zn_vckvItZnlfarqYa)Z61*Zgs1t|lc1W4-FsW0&k@Rqopc9Uo3W{QKtg2csT#bTLE zUNd9yEO}8$Va$`TP@;`S0BJW<{c*>gp<-zGMbe zRaZelaUldg4}!At5_CIz1nzj>h5B{sgLkkGre&tWr<4SRPmoW=vYE>wM7lx+5|(N` zNk)^qCa94SWWGeDNDw&1!PyYPEifdSO($7EwugmbnXy{6aPPyY`rmW@|F0ap{A!Ge zj-Y>^CRW)hwWi?#5rG0~^$bCx?e?VF+AC6qXj%ghzzeynOSrrhV)7H4|n}6n{?oT%D4hTGOb$t%=*IpNFXtsg96Q zfi|N-wUIB>>J-q(D+o0UU4_Up45v%9ItB$;+ZezIuy&Cx5pc}|4B=z|2;u5x47Ycj zfJ=8Gm*LPvB|s=LT3;5q9sDRD3y2~H)D9+k3Tbi88blQRr*rn33Fi0f17^F< zR-V3gM(uX$uzJmsHR@j7daAwdc&W|JEYuTbPgFY(9;Tk*I$gct$BpWrHt$sr7&TC> zE7Dc%SigfE(08CZ=0&u6_R=}(pr=9Vy<7LPo!WI&M?8yAmseJ>kx`MI&s{y!&D6|P zj*Mno5{BEM6&bGZ%H`5EX%GDB$`H1;Z=2=PSma9&H)S-7dlACa&)5-mBB47x_rIanTuVYN=nCR6| z&dt#_2xtXay9S2+T1Ei5?OqsQSk9<`wkXOngkf3yDn&uR_a>4{dEj6TxV= zpHVYHM$HJ36)?aGv zyA00UI14AQp2W9)k(isGTiL2<%eekM`{gT@N&v#JfZVbV0)z$1qECRamoPP;;wXhJYG@Xxq6p>mf*O zdXpiHW(-i<*3ISn3Dr0qnwk+37AdlnQna$bd)1xV zby9zL^+A2-;cfNqBfHfr7p+nc>^(sJ;MN0G{d)D)O4;?sEJ%7eL^_r!t)ra>S zRu33GP|Ywz{nO?>>H&QRsG>sOsolNY)oB@NeeXWF-NwkoRKXBN%`l9*_6#9Ln?~j8 zs;H^2)-Fu`O2+^KW$;Vggb?P-_DEdnpIhXtsTm@&RI`l=rIf|mHfPPM^sm#OpxNex z(=iNX0npA+ue=e%v|s$o^O67Qy!|Ev;&wgK-T=v{ZjH=xG;O6wDdoHZ&3{OcTLXpK zjvD-%ygnDO5ekG*7BvxwK)?b4Di8=*V1UYGGU7VXH7O%2Bg%EGtLdSgZl>Kjb~SZf zIK?n1ILNG(T}w0X8(wC8`}Q~g`P9z_ZYK|$msgaVhdc~6cN{v{JUu(zaOUD!7P0ZM z=9f=gGG97>skyC9W8=XShL}XZeQ!S6d6ZeW{|lr1!h#9mZ^F&=boG?T2x?a;10!Go zx#&aN>BHs78I}|+A+5+gLW$tLI`C9LM~brGAzR|+;}rwT8c81GD2j5 zFcb;H3N!_DK&~(@U|1HA<%|Zy84O@F53o>2ZDgP)Pft#-E0@bB4(c)cJ|bMj|0_s4mVn4Abhh7N_$hfhGMl#`?9j}o_2M_|Z+!Eo>5J?PfS zfdqyHk?G53K(~%vVb|Inuxa@wf%_wm%BK-w1Ks!eBO~LH(?*#@N4+;&G;5LBk=;L=b?MOAe9FS9rU8ME&6_o8X6}2(*St^PKIXri zIcjp?*g^BsvQqQlhe76porajFWu%!+`(e7}hflHQzwG_l%+LLiY3TDXm*k{G13f)G zB~s*StU^tl6&V&70gFP;uyA!c)HHlB8aZYuVYE#i+;%>INCg5R=Wq&$M%MAmIw&IK zo+!dl!U$LjT#XECwX;@?5D9mpCpUuJ^M(|Fb~e$kG@z1z#0E$Y* z%iCKUNzC;`awVmR+zM)K>Va}q144lzoKj%4MNW;S)YLyHP!teuxjnLo0)|r5w5$|Gc|hUF$m50il6-bnh=zC=kHH)jzEZ-0tLkd@b>*XymXfAuk*6a~-l%`_DXe=ElQ%b98?C*eKKv^M%7Q-T}SlO5f+su1@Q$Emzrd*?|yxcRxMg3jhp=T(Q|5@!>>L2YVp$5%kbNQz1XyQ zBbP}{JN@902h;~2eIR$-dk4;`bq>Ax?i;zwW-OuN;zD)pZP(%2-q+yn;dke+oVlX# zxna+SpL%s9dUfhaljlq+%E&xb^vIn{1QI#@B$tL96plAPaUuKAKZ^CyZ6BtUvI7d?t2s3j2S&{S)@1uV~pL)N|A;UKwy}Z z7T6-~qzKGn<049wSg@&yBnkjv5Gi3XAW}h0jFc#_f=#U?Aq@>f_TRFgOxz#Xu$eOl zi`#_AP9zZ#u>n;Lhx4j%VeOpUq7DES{j+QL4?U1+i+5bYKDTLJqx|7w-w+T)Kg9J~ zV0W9H`R351h$mtS@{W{jVX%&aWb zI;$40x$-(O%-FPQBZvqWHMmIEKBx9&Z@%?vy|QU3#Uj3e@{JPbWw392<zGHDpOqj})l+Uo=Vfbn8p1l|6EXjX2N47E`yeKENR#K!i(dB>3Kv z;)k*F?FLXxL}wPjgt2pP=|@(c^5F_tLW~K8l<%_`Uz8D1Ml0=$(!N&O#z2(v%a$u! zux8DghQ5h*Y1g`K;?|EoFH;zatBOLG2PUoiud|WA8%GZ1Uz_Hss&y)+WcfC=!+Pcd0kK$7i+$cR?1f?H z#-5=unlzx;120!;F{H^97Z>BE>u=gyHNENwi&rheh4n9_?wz_LDLDyi7k-GSA4N)1 zG6oO49T}OYuxjo~)UQ*|o;Nq&3S$hOd2A%^9()%vGcwSzZAV;m!G*Z=lBRfn(kx_V zWuiydo{Aa07A%}sH6baXSSc}@#fbQ>Y?3Z1Um{}97*a-gBq>CM|SgJ3B)fopHA~+E3(mKjY?8V`dT1pgIumLcol;tj5y(|En zotLey>vipx`sdf*nO{(#X3m>wMvQvWU-97zzh3?N{?$FN@?U=IWi$2tY5q%NUiRbT z3!)b~AlpPs*Tp||zx zt=_QvhkBX0ncjVmKcJScS?RqoevCK#!Qoz9T%7mTq_@=cxih@yM!(?o=-A!6pnd~y z-6!kRv!kC=6K78JJkRs`bm?6cvk{j(25^ef(Q2d;bR^XKKW!KP>=2Q}pAThS->PQCBff6{W_!rF*2UtO z4l0SMx;SePKeON~@LB=H_MIqUD8&*4+r@A}K^_L*Ja|*X28|AU`o$KtV$BMF$Rk7i zT|e*gZ@B7uzkK-${)DOH&C@SE?Jrri#6R!+I{tN6UhBW`+9)$^_B8*6*GKu`aM&L) zbc8=?#$Lhwkz-GBf=9@4DYVe&Tp>xpL*p&R#V4D$nyohr-dQ(neF*7p3i) zp|nv-j8^vIK%h`nnvFXLwSYwwEAmWG*Gg<`#67!WrQCk86iG?qHpqEuRFWnTq*f3) z8xz~H%AGMx!ezBCJU>2M<@Pr}>iEyD-``#!caGjvv20j(Z%`@aXw)M1qPk=Rap+ts!znQa%wCqzZ7GPk?PgY%5U4U?V@!buRH1ce*TWl zJ5Kq&IeAH&OHL?HozP0FwKW67z>ndz0&Gjk8-9Gfz0>al)<`W*{}yyI zLfRK4emJCjU$hYcYbC}PVTW8}Z=zu9F_B{DrLd4JQ&@EdVVNuvZ-#A1ckB{rhPh*Gxhk*AD=Y=B&aLcS5Oc*-fS zz&fmbrNoa@;_Ki|v`Ma3rs$3FZ*@&eO>5A&@g=zRu7P-J)YHh?pNX?-)NqQ>grH@QR`_}EFZgc5w@6G(gl~L* z+WZ;WXP;H;)3zc$%|2sMC-#H$Bef{ON%OuyXk(Q9am+5E+{Ud^Upm$t3}fmi$2}DIj>r^YFU}TWSkoL@-rvz ze)OJ`Nl8hk652v~VGq1EQ2+4IEH%2ytYC}E|^C{h+|D_71(goVLMTlHp;e`bS5j_C^a zAGtG!#p-vR{jiP3S$h%^D44Uu%1?sV@t@5r`r8;tjkHkbNS2?Fo}7?Z#4MzU2{w7q zX2h1nJOpl;QGx_4hiy~o0WgVS9mp@O&def&Qi(uLZpK;Jr}V%ZZ~kHH*IU1wxnO3m zOB**)eY*ES`zzX`@0ESgZ$LkqyLfKZW=)%6(p!^oRqwtye)6~)`O-5Lo*(g?H}2K3 z=-#C}+O%ww^U&~z@cz_UY4_cEAF5SJN0Y`){T|o#JbCZnd&(CS6kN4t<(jX$v~Pbr z=X7q!H&9R-T6{7H+m`17i3-C5peVRHt@c{%|Lm-YA@?~60b&DSw!MRZoNG-?%4z)_ z=CrLYi4<(*CfkC~rYjPWFq9BUGD*?-abBgDr+m^5!2JKs#{E}~GJbL|i7Sk&oE(>5 z#4PSSjUi=H6hnm)Afq7n!=dOiZG=i}M;R9Om^9eH*0_>V9*jU(DKSQjMVVqfS|f&` zwF|mN4V29V5e@NuU(%A(qQC6?rRw_&-oK7Oc<`PF@%{HZvFF#HaXSAro_YCMj2QML zYM)&jN-LF`no6Tye-X`^HpkU{u0nocJ}$oKVw%5vp*noz5Z-uxG|sAC6C%K#U-!_g zh415zn{Us3f6@EDyfl2&H#w(ID@BS~7*L8tnAmr1PJ=q0LS)?WX!of->M835P z5+aumB4lwTh!xrHsFa|&_@Xk2sjp4n-Vwm!|Di#C%*K|OEl)?G3e5zu^22ctb48y_EuSg4%u~(vlnjjGY6iA7sc_4}ap@7M- zku${NYH-n7hPc=nD;q0v{s=J%L$L_XijU8ZKbh6C|Lf~&0oe18*6=Sq5Umw{-j{jx zW!1`@jEYm$IB^^^5X<=}%}F}FI>kPRZAd7@u$g4;Vzh`7P^`qdnNW&d5j?gCUvWWU zLHEZXtotZ1vuFTBK&&%p^O_x0Fc^NsUbF!wrKP{_FnX=he z-FkKY&%gbg8;wR!j-N5%Xs<3k*VU+T_G!Qf9CB2S1|=n|v|=Z=h_&Cxv;`>vHKU*`Xc1U2s=x;9l1nW>RSF!R7$5D`t@pBD_WqJNX2KW@ ze|9*u(s+0JL{3X9BfswZ4ITS-LRwlGOni4D@(T+jIXM{}dUe3bj8j;+av`?w_*Sac zsK)m{dOzkZnv4C1_ha6Y`CPejW&UyZk65~DX`N?ZexX-Njg(>$F%ZuPYkP{?GbWIg zv$j2JjId&@V0^efGs?`1JG5S=Fo89P#*Lk8jI9 zeku+IipVwtp$Kb3_Hz?8n#fZmQEfja#X_3=sAeI}LYjoNA2l8k6i3Aq*lL&p)-a-l z;TblXm~`--jqwD~!lcC*4HhkIJYlgFSdD0A&iy1c8{|OVcQ8XkT3=^ zMh#ks@~%I=)jOteo*_$ z+Kt#UL#{>?De?omp`fpfOI8kP_(p`u^L!gKv&nrGD^)Cd_=$)5M5EDLdUoxFueN=K z>9ePsJ>Tsq{_%$&61VT%j`T|D=-dA)Y~Q@?bh)zSQkJb=hQ^I9L9c6j;qiwb&%15# zZ3)YkEz^}NR>HV<$04sEuXyQ-W%9*_t?|F@+lvh!Z#Z^Qg9bzIec=8*Sx2+tthE~r zf=wkQqhLrGf+A*ZSS#YFCPmt?Q6vhAnhCWz@pi(CKfHinhiXNXfdqWu*U*j7&VF0PUM`P_DuJy=pU4O|4R>K_QyY8 zdQMtGI3bQAF|O6+3lozBoGgnVmMI&cOo6a%8zw@;tRS}Wf|Pt8AjTmMC1SlBrCG-U zj6OqYK6T=hdiL>=>ldw9_{H7>d(pXj7tEaa9(}p(YYcm87{I$DKbWCMBjtMQNjz7*D_wwgD|IRu-z1Z_9Eh9aJ6`QCdVPR?51{o_5tS zEnm-2R#5{$6cHq2^-gKOnd6Fr&8~^|DM#u zgoLCPomwV+`qAdp2K5`HzBge;>iKofPwm^UZ^HCh(^8L~IF`C*@dv3FT+$$Q(tA_l zA9-qM>L*(_r%W3=HKkkk?kUGk9!YG|yN#K%c;1QIZn$-QvG21EGJplPV@!akMaP~E zju1tVQEh|gmaK3HF|ZNa&x8S#@N^IK<*sdA2ZwzN=XRCV# z-TT4fRZDhht?}4{kK&rKRG*d+x8Y;ge1MbC2h$ z_&DuDgC$NIXs`~8(MlU=VeRNRtqhA+?2E+Cmmqo4YFHHmET+^Rft3dfAY%L8m3SZ` z5F#aPyWQG+5J#w6Wv|Q2B3N!zAPE|g3o0Zfyu9S=#{Xyy|MUV8kJ zp0jxFsk`nOa`dyUpBFTXq3{y$mjokjEol~UVRNeX8h z+Xz9JStxL^g9I=FvkAZjx!|8IaRblUkg!vu1xht>b`i*=UG-h1j1or!m9`P9u432j zmPK?dOV<-G)}b)5N@8@x3s2lIb>`IS7dO1vpE2b<{$T9~Sg>RPo_qdTjCg*8Y~A=d z=bk=|(Ql8&2kSn-Wo<8$g|im&<;^a~sMki}+a2Gc+2zg9twR@nX81^%GHWV6`TP^y zaz}q`+wra6vrEro=hixBQ}fH3pNd2ZNyOTU4QskUs4MAX_lU8A5^)rZQ||=@6O~ib zXj{U>20nurQ4s!g+<=Hn`~E{F(C)cDt(k!KEvf9(;S^+lNk^JaKs4@{bNqdw=@DS@UKetX8dB zgOM+eY?V+swMerVk0nNB*>lESB4KSg3IS^|Eaa+0i>0uj+_@ueZ?N49#z2BHNuJg& zL7P|!u}7M1{<19(6kMwIjv*!@Q3PAxz)|ZS$WJTs%YXXy(SHa){`3OraLuF zT{ba0YAihlKscy8V=oVJt&Sw%t{H$L>uInA2Eb6TUvqsRVn`ASC0zpu*kPd#6?Yo{)$r!r1fz3!@Os@6TPZnbWmx>ud@_T;oL zzS)|7&9&E7yX4}=)oPt}cC}3(Zmjs-j_noOwry9f?G^2+RVrVp+MMaLtCmSCQ>I+G z^40qE=v}SWS+%MSx$Vx1Cr_TdZr=3S)svIrBG8@@(d_v}0u6@f*ukhp2SNeIU;8oS ztb%7zC9#hZbfsL3$Q4C14w;f`a8&B=N@l|%PWRh{0Z9c#YnM+>+j%Un%KvoZ{;Nja zK~oa5a*7(9Um+#SiStCk?qIT)e$0a)@;~%F~|F+HTa;N~uM&CeOMsudv{Tfj136w=P|!Y)Tm_SGFAHzc&w6 zD_6r6Ew7-FsEeJ`3MSgqo;m1GC$jnwyV1!&SKiK@rE@o^aJDZ5b${1i)P|ouZ z<75~%@M!JBS&IZQMFs*J35wK}@r9(=ubnB9_HQ?ie`36`@P`KHq$XBMjn{<|=neyK z3+$W`%i&Wg&c5W#vG!v3pN$1ba1psbD%gLvMU%}4^4)xEBO)TFjvwsR&ufCyL{W|rkb!y+a z+O+XgD}DLRS5>dR?wV>%8aJs{v*uaV)~#HZzH85}Ds5U{QLSC;w$-Ypr&oL8;m512 z*|6rO^~*o5kXo@^v3Ot|XThnbExRG2on2_w&P&q4ljvYiX!~>|q7{Ud^=efqk1LoI z<>rw&C^JW*Z0snu$70zwOUQWZG zd`uBYo!_9YTEB5UO`JZNX1za4&0RQG-EhryYWsKJ(i`u*iA|d~sr!cBuWq{jCY6zs z2@n$Boi@2&C=`Nbu_)GQx2=0=7aeYa6*($PwDr1_Vj;zf$cVA7rA3t%9oT0LTvD?1 zR?PLLSYa4)+J9kpKm@^4>j@%E5~&)}Wk%21TJK-Aq5r4jk)D@alIW>$YFHN=<8)DQ z8=b8-B$f;&h}j6J#NeGgjRnSTaEFqVf)iIDmXL`UWu;0IPhCy1z((BVVS>v;Sd5Jr zL=3`EFOGh3aFa$&)ZKU9UG(_K$0M7zd|Wi@)lr2zf7nrY?bX*7t=qW1Xu*Q{#c#a* zM$t#>KPs$yQQg9njiP-A z_ZQtU@b(-N^-pwd*J+Iog?+7r#TQnN?$m*eh}h?gNiyx5DFIwIr!-L1*xH*Sq?klA zJNvLR?mz_z2R34hFp_wL=miy%dE$a^F8oi~$X^WtkX7WL7pKU?d&-BC4_UFIJy+bt zXpazxRVo2O+Ig=*z}@ABK(WB>KLjOm+`d(Un0UdslK){Rh|yYeQAQD7edg6U1qB7C z)@@p+$4nSQ>p$K=Pdxji8gSzPwRits_0Ht+wE447)u6ivsWz?Js3H`pIg94fq7{qP zh!G=HG!jt_8Z}VsH?5=bQzp>+^WIl8=gd^sUwysWe`voDsMh57CR~Li_`=#C>dr^D zbe65l=gu0XSu1PPN(IjloHs5HNUW3>#)uWPI(@u?R;}^goSA6e zwmA;%If(9EyW{c}m*d3A6R3SbZQOh3y^&7sI(2(`)bmYJ&n{P_MHu3-=aP~{{MapD z3i-N;4`Lb+16#E|VD6RkLR@&j0!3yUE+7bOi7f=&WDJ*E!Cr})i2)&!uu_I{iz;R2 z6#mB=2^~0;)viw2lwx7)ZCU=rBAEan6SV0LOtcx2p&OZOVI{Ay1EXwNEsY_P% z+BLFLQd6^-mqd_cHK+sWj{RP;l1&3@t^j)ssGOf1qCDm(fW@zV)3Ag zA!UoT0~=AINV9-MDdl|J06Z`lzB@;OAhs0Dw;-57yAB~memJ`*ef`(_|Lf|fv50q3 z^)hX%B*y0$<2+Uf1O{Z;3p;`$<)uA&-+q9=eoA|w(PdtoMdKIm{gT=@7NLyd_#TZ;3s>?!kEt>tN2bIT-fvFkIa5Vw5Xa z78f?W5a-u9AIn!PN1Zx#aM48<;nqR7V)BeBIB?(q>es7>)-Bs$?xMM9-mV2c`S4?v zUM2mycU~D=FRff!v1oU$1o1j4^(UZ!_3yuCy%0y}*jXYap7sm?QRFcw*h?IEU|OZNGq|GBCYIBwAlEEC)S-KJF!q;VT&w9 z3!gr5+8goch|Oy^uKn=H(IZV)u33cxhYq0oRo$^Xc{v!250|e|Z;pEt+qP~)hj#6; zZToh0@aF?E@b-b2I&mt_uX{c!SE{VCbF%Tkum{ob!iMO6We==aupCvZq|>%RIgknGidlijFR9tTqr$AytQ<>lca)!WH{TQ(AeZkC=r_w z!^)}+VhPI-kBu*)$OX|U zeX(TDVuZtC_54fEKEZjhAQiwhA=fs5tZFCf zvp%m}^3me5NeuvO|Esn9g9joaUPeK2#R~CWfru4901gGm)Iwq_t%yzd0Si=soEbqK zFRLUG$KL`_P(q~G1n5e^(mV>Rm1Y<~Db1qnrQw(IW_&=Rg@s5+PAIx-&>d6X7&p4O zdbMh&5)u-QzWVa3M>Dfd9c|a8U3Oet{Eba-0TefI<^uU4rM<-61c=Wp;b{y+^Rky558aFxCty8zk zcieYJpTsJOKE!%Ju)z-z@x|8=@r^)*t>6|~M2To3}V+25SIr{%j`(4sX`z#?QfqzOuqM5-Ec0a>_QVvZTWlW3cmwo!qs1SAJsT6L@p%oA~&% zk1=liSia_}YkAA(pW({u`yw+l3qv0p%57SW3e{KQ?X7v}2!szUA08H(hh$!V4N4O-xKY`s%B% z9?j0qIohsk`{Mu(H*VDU$a60~cO)|__nfQy^htcRlCvML+@^zR5htMv@c>rOIU@2L|ZY& z0LsRb*}&x=IyD+O3eTv(Gea;ZAZr))Jn_K}trC>={y4Bd*!p7tDrsH3`?phnOM(8u z1KGO$P_6STrj!jSoz1Z2wn$m;gBc)=m|M+&Pz-_E#Zrpc-f(Pjgt9NvN;QorR?x zs}`@shbunJuT-g0%Cz^Ug&Q}z7#B3S5Z`S5%4B9`WAvEOX?NW@1S^-W!0Z|CN50v% zt*B?uo~cWhF2M^gjl#-B%TcdRy@CtsU6B0R%ddxeUfuJ?>Eow=HsF>4`!Y{v#%V2V z3|TKk3?UpQiTFeUQOdW~c|}-!tw`CQdsLuv9SybE9^-I zr4f531K9hQY!J_3OJQQz7HP4pV=)Gzv@c7*Y^Fg1C^jTrr<% zBQ{8Ttq& zzB>_zj~eI9Y3wi0`O4BT9&k&t#Li6bln6_|9Gwi}*?$ z0#`(Agq7X=B+NcYED*7!Z>YpZ04P``4NHlQBx@Bpc`Uci>>US^0G$4J+Q{FHt{0uv zXy2|AqDga;~CSYWBz+{(V|5QYTdF; za>IrV(YZ%wEMKw$>sNoIs#K{QKXt}buW6Ges9UctzS{C-@u^d%sE+C5(u5dap+)js^6H* z1q|UNpA+^U%B~}}s<`~wTK>TU`SjbvH7X{AlRSbCb_UCc<$I7rlh*S9V!T>VUxi`g zzvMcI8F3sxECTbuo^4ZUk*5mC8oS^S&2b9s8^8ku;xPmqFT#*`j(Sl+9@M>q?p`!| z!JMDpnfOl9^oi4x&bzQq(!!++;|AX~IBEWz`AI|W7?O1L^;ahyJ#jqYijG$#Y+AP| zseHNeNn^*1OG-&eN$PcVulPB0=Olgk^_NL=XU<8gTq`~4spp>xzwpZQ$%|GjitEy$ zOO^GT)(?OO5fO&=EWH+I4=nh;Q;BH_i)JH6w9=wghz*Rm;v3F@#l?a|lv1KZxp);E z@9%J}aeb~)*hK+#X zffH0h6a`56c*~|C3GoR9?@pPBp-&7$)!Nl$_Iq<=-+}$IaN$DfbyaUkODQ9-zy7+^ zuU8LSKHh?Mo!ZI%{rlz0Yp#@`4-UiP#fxR|-M8bt*)vhMaa~LwKMfzQ`$(@|`$5LS zr3()|{pgd63knL@_Y0u|!G%C#Dlx4Y;xsi10;8N8Z*3v)+AILGpvoAhymKBXGs9-3 zyGeEdH4qDn6IuY4Uv@SUFQB~a!s-m~zsdtSd9v_=D#;1Ht@P1w26%JoLVYW{-O9 z#f+bS{W)pY)LBWDYNRLKegEAFqu+WxX~B|(N$ zXy9#0H{5hX(%~aVlG=4?pR{r9#-!w=+A9-h&wD@V)2&0&@(~jj3hzE#pAU77)&IUphiV1F%r((H9HpNB7Vnm8;{47wi21Iz^ z)Y-jBt`#?&rk)W{S?rip5x`?XtqLgyz+cu?`@;pY^S9#_!jO<>YZ%2SEMW%3#CR&j zuD~oatn|>aQVJyjK<5@IC;>B+ZSZRi!2Mc4i;bTwEjlc0bFF-MfJ8lrcu@%< z#Q8bdszv7(8BH%~I_|Bv-zxrb_YY{?xG^e}tH3K(tdQ@%`wm}k{~CAQc^6)M=|wqy z=(zZP6d!F^hZ!@cqh7;$^6i)3N}JYguxia}Z2NW_s#mX$q4z%`qn>+FHmu)(wA54_ zIC8Mf9k<`sw*1-Uqe0LXQRf(F9Sd?3n2X+R_pnjk(?~^d-(&bfg6floE!m zBVZGzoOBV+ECPDMGF~Dq@O`0bsYzLt!wEJ22YdHlGJZIeootK=g|srk^NM?|_O7&% zUQ4|h5R|=OP+}ui(Qoa-`sO8b#3rD3oVWt+pNM_p1JEK`#PBpbi_rkL;>^tkMv-W?r?W_GCeDuMo7R_56-2T<}y=~gI-aBQ= zl)ayPy7_Q%r1;p+KmELK(UL{`ZW}ac-;N)5?BD*~_RPie7Vm3vNt1mSHN0rwPrvRt zu==A9j{W@WPy3oRZN9QeyJm+Aiwa33KFDtYJQ49-Ak;UAi7pJF6+|l$APT#y3?NKO z#Y$5IbY9Fx6p{EbI}i)p5U^c=3+wF1wk(D5SyfC3pT4eHt^b(Z`_IOK4?oVfuIF(v|~v}hpstpWwnS_=Ra`vzJG z!-K&0;faW+!8rvvsza~N2WwWVIbrPhv9jg!EvSB0b&MZ7ULJkqQLO!FEq>aw8%-{0 zf{K+Y$*K=l$4bWje+?jZy~0|GW+ zW@4YgT(W665cIg5GK)PdO{xN~LP~iTjFK|B3{xX5(Ja`q`!8E1|AE5Vn|V61d6hKL zM9jh>tN`T>G=~xx45i5N))cT8AQ$hjU2vSMB-SickY#Lr&w%Z)%~Jpo-vFVIQ!}|g za7rwP8xR3|T39@f^N!_tBVQP~Zr4vguBct>+^cW7=@ztT-hwhtoT9>_LcBX+B9<&! zg3B(uj4D*DfK_W&W6t|?P*_xmJMS2x8eY;6Z;pNgBcFU4UAuI}?w@`_rw*O4b?fKU z@WO@|HtbO(CMKn9+Pv}BPgiaF?y_c==jNGg( zHVsIT*j6SMJTMqalZdt5pne)iIU7+VP}XXcxLgs7u5I2>aA61jmqskN#D|u!K)k?B z0VFE+;)q5gl2$n_^3A8;)IXJR>h=vEtWTIUZF1SoTQ;BFsbfd{^6Ss&*1aprq?M7G zvu9R*boir*g9qP+rp=n-$}6wTc>0;Aqp!dAdX<}Q?1xA+itGDbAHDyc`_<7CN6+rv zvo|1uTL<2X^h#AS-+24YU2`VQ*$_DyQJw}H9||4>>@mbBFA6bA1X{$820}@P8ttaA zoRe5S-#0E`j#%7Kg6wj@iVcjFW4bL7;5o9IZxN6!y=nt6%&KB?T#iZ%{a5u$Kbl#X zmRhYsG5dxn78kS1PGO6l2mq9grUulnAV{EgUavcY09dM1yOwN0H4GS#QlgY%t+WIg z+z^P?nzf_Qp*^-+F)Dz^krVme3(vgpL3Untr*dV>wZ8j~yU@1v74*^ikI=bOXWVi3 zoyadJK%3TW=ydjJm7AN3DU+sR`SRtswCSZ(BfSRRczrZx&wL-o7~FB&?W%F(OX!Wa z-oWEShoeugKKO0l-n8xCZ6BO-Am;%fDuqP`Aquf4P)0*E37e=^L~P?{mYE2|5-!Fe z&`ODKd=M#7BM%?*oqi|yLLF`_0TP!B!Be#9Yoy`{L*;!H$P9%hWJ6iacEXN-_ zko*XTm2Hz2AYeH0+ZjI*d|JZ7idZSCOI9hR`A}lfBQq$_9|qB^;D@2Lz`gKNU$r~O zg8-q}KLkJ|War?DHf_f*SUmqo{krvwuDkKNg70>Gm%Dh$qTEdzH|4f!*(&#i7hlL* zylipaN9#Vy`QU>Oa>|w~o4fm`-8oM@@kGHl+rG|SwqjZC{v!u+)6=WuUUlQug+KqY zCwJ+prKh)lwe9p}mt1;!Y%43tu( zF45S;#oosj!Rp&erjCOP!_5N=N;Hl@q9Q*tJHH(M&7<)-6)MGhI$}z#h%le|O|e74 zNxT6ol(C1OEt_CUA)mk;VsoVhY@oFPv_+CyYk}4hr=6#2T|eukdiHAx@d+l9lOMWa zz>Rla)u(TnpMU!qtJkc;-UIv4vrjLSFING3e*GECS1!k{pLSu`BSZ1U7hj@m*|J!= zY^i+t)t9np(IPzZ=uot3*$N-6UW@e`*JIZYKcZWoZn&&zGkm}E`@H#!=ldPobzD@w zcKN)5f&%iRv8RRy>q!MgghX`gIkgTvEiJA+G)lY+EZ}(5T zbC)h#n!Dq>9l0%Aw9M^$b>Gw9ZvQrW@zTXPn>KCCY1688&Zrkh<-R|6R_^+Z8**2z zT9unxHZ^z8FF)tp|HyrL-|hS^ciGD2r++)L_jI-Ns;8H%SW@Sq5fAl9t63ouuRRW4 zN9;K?oG0SjN>HVq+digosT0~>62c|5DT4HgGv+~pL~#4_C3+3SII)0E1fYmmiRi<} zb5j3mEzh_>2*7z&)5^vvio&^rRw9))71!0#qrEks=%SAy@CHv@I)EB`bX<4u=b;!~%K|E(8@ zBA{w=Ld68FqRh4utOSQx(CLTSp-F2OuK%EWMiQlzqdlEnNGU`28M@ScB))dkl?Jp3 ze8B1-gBkGz+7kfciAYk#q~dW?-tBPSIp=gb|J?J@taWp|HTo^wbMHOqcSAokYSai_ zJ9kA|S{kNLpN_U|+vD2nuf_1;!|~>rH}S<6U*OWFP4U2k4`A&_YtgBDXMFe7x9HKm zC+@!eE+i)>nZ7slJy{efUN`iKp*!*pWqI%*UPwfKM?=K&K#W0P$Hs9i0HBpR184zu zL;HqpO0M%Z>|MaY{Fhu50X@N%>Ot%`JJpf_;z{{AIfef<7l>Ab_>c~VmGXny-1g97 zl+fe>|iXR0FAL?HznfY0(P$&TNc)hAZNtt=R`yThU2lP z9{c)-T|dq_bmUOE$E@;@OVS&$n{u56;QPizlw}O59_7!yL(Jg=W z{5kngJ@r&!(@UBbez)Vh{B;}G<=;N&wt}1b-Bi%IednU-Gp84JXxqM^Nz*0;7u36; z@byuz`O!%9miH#SS3RvB z7^A>4C;+3und53?m9QhO%L>B-F(Q84k(2rV+y$Z(ke-^9rb)%BmbgRgj3K`>`ZLDw zQNo`L1B(jm;wn2STFGC1zdJBsdBWgD*jH6lgwVTV-<@^h#EDeZ`! zvhL$5OZ0@fB3f2GiHo|i4Xw}LBe%>!@3vE#>3ziz$kx^?RAyz|yOm!lXe3II{}h8WQK<1he9 zq2%D;i+SP#cNp4ItHJjLAmp!XBo?HEavz^*BXJV70QBwJ0UV?H{F`yxvW-{;S{XypPZs%mVa1x8E_du&D6FlOvz3 zHu}}kRdNeXSNUYaCskUsYLR~Q=+TOOujyO$=RH5C&!0Q5>YYRGta|?Wb*r>)+q%lH zdwxwHaMLa6)2F^yrBk~O>9-CURB`3v6_u-0sZx3K+D|GsZF6a*jhi>6?)c%mT3f%` zdJ_st%B$qI zyKck!O&jpUh$paP=MI@Nb~2xL?zwHAdG(o=DHX~_0UsJ7F(){1kAHtj*qic?cd9B0 z0LJ1X09e>|Mu$c47M;6=mtg&^2PhCol*5ro%<}#%!x$k2O40-o-;l)@wt@nSeUZV5 z7ubk6yJ0`}5GCNmQJ;1bu%)ovG%96zY_UAHL-6?HUF-v%c#@c&=->Ouz1QD--OZJm z<>*sSKXr8D#~Y5`He_&ax6a*;G{3C*kvoUnd1TL?JxBUn)hDxj`ErNvyYIe3L+&1O zXxlg24h^|`$g!`!`Rc@zk3V^6@#4jYCQh7qXzGkyS8V)r(QA&R+f4o(}@IrOY3($B%)JGao9p;hl|8=^TjC2X5fk zF#u3Z;TGe%jq!ZL|5SImf73`v3hQ_X+s*GhP+SDpz;v-TiQ0Ufx?3DpcgoojRjmzkc|9>*sjr!H1BWm&^Cw zcQ3#4$}4zl>|2;JWeVPYXB@x$@+-V;`!?Ki>n+&+-FDp2zaI~KXeg(rr=#c9y|Cw( zJ-FrOThO511^o8w@8Cqni5?F<_CV8eXO}Am+)^1+@}glyI`)H@)S%Qu2NUTk8pI~4 z6!jOQt+*rv68k<0*1_3a005^Sl@{x~N5qhsA$nwWuRBYcBAoS+gH$SOSz1phW)2DTh?%gqK z<}4gMco1dFmBqE!UW@pIcx>Ie6@^8G$jCT_`yRXx#l^+=aMg#nq{$`N|J!~Hx@8b5 zRj7!^9)1*$4}BbacJD#sMvc+CTQ8|ut)_S6$dOw%uG?5SE}Rsbn9|!`i&bK=)3*M> zz=PP(8FL~f=Rv?={_cSM1Eytx7@N|b7^jN=I4}RVLjx+L0>BpZcT-2yxh0iu`NzTj5EC1N=$0}uHWK_z|&aSk3*X}Yz zly=+gw^gc9vu33|yZ2O@Giy%y4jnoqwQAj}T%(2;^MU>QJ|Fwe*p0a-a~&!>^Wgcj zFP&i{N}n-hN+~hSc2~ju!m7p zRE$-NSEBi)&9V2ly||^{EvQ+e1}?ws@{|L|4&AzKNNaq(xo&_6Weh#it=Dy`1k zhG#ziAIvG{#kuikpv0E6qj;_SO%yK?sK^;nPhh)hD zGKhuj>$0pIvk;Vu{k4iYUkv6bv0;MIV0P35e(#?7fDdvT@-K^*7Zw*P02uM~({DfY z$b(0&yYYHq!~XNnJ@02_XPJ!~H*(*;ef{0Lclq6WboV>7Z*OMKn8^=5@{pPF-V9T- zc1`ox&_~VN6UOo6X_L)Ek3DP>6B5lU&%R_fY~H|2RxUMz@4wx&YToLsi8ChlPDzfp zE^n~9+P-SThBP#kDAB(|nhF4Xu@?)=8FeK}-!|g9Wr$%4=HLDfhvF^qX=Nx%{G7t# z#J`%C|BK;iB}T-L8c{LoD)5wI&mm_TsY5}^{rwqVW+_=$1AvJD<(xM|fCe#<4TLfo zL!<$aF}Bl<2S`)|VZ=?EKIQh$zuM}3`OTMl?xK0&*Is-r9F7l%A0P3!o-}Q8_@!50 z3P1F~L*eRYRSz#*yx4nq*duXMCQk`p()f~auim}Fdw$vzzUG>%Lu*#A3Gd#uD?IA? zQQ^YE!npne`-c}UT)@j$El;Rk1;#ve`q!-itd3V_Kc z9h*0tc@Usv%ZcN(&)Bfe+hb$3dtwX)He!EF2ue80{NL1>Fq#`NVzU4M2w8EBjfIet z9G2AjcC{4Q7VM0H4R%G}O$iAIZQShqDZC3l^`X z`#Zng>38kk)$iE8qnSAFUGvzJkC~}6rkNV&)-;a~9d5>rA7`e{nBu?u=F6r{i`JKn zoj9g*sBD709<+VE!NM71tPsGBlJnmHtb#96Y4FOWKlt4(5<}$zur)2X-i=PpVE-$E zNJehtFKO~K>`_>d7xD9=%(~=269R0c6apv&Eo~(B3MDuL|1yflVeixaN zZWliN;qo9vK@lQCaWMfvK}LbsxN+nCmo#qt@*@vDl3DM9dIzRXoqDjSpy+soautr} z<>eiC`<-|8-_rk<14V_!hYIrxGq-%YB$2-_Z^)ybJD2~*RSa@ZSJfK(o)m@5cx3f&tE?OpP|Ik z3u_#Y9lKrv6^i{h_?aNUAX-O~;zNah`49dd3;|%26d2pBX++3GYNBs&1h{m6Fhr~06@8O%K6vdblp|!K3?B+|Mz<{T3p%!nVA{b{_S?8RW5_K zMvujFBcDTNMkcbdvQWQ%efYjlUwrntM2jQH%Fe=_cif4)@3|X(u|bt8RgjUDiP>{z zqiW5nv~SP8=;_?k2aX;-y7J*+5C52RBIj>t!#`=@=3ZxTd7NP*e=vzyQIp^cXa0Ra zIpFtSFSSJ&F&NK)35UFYyO;Vmqo^QK9FahB2;dBIp%hsC6UX~+$M269Fu+kJ7z}DF1fhzp_;X79?Hzj zJd$%d_vFwAh8>wdb6fI{rOO%Wv!5l9pSOhja@ zT4?|R^EP01x>D@+e9m zFB&Cy9^g_7h)B6|=W!D!PhQgRhJIJxGvuBtdi3mpjvYFldf=f4Fl*+l3isc8A1YR; zgyt=qN3ZVN_sC_UwAG4@dt(@B`H}}e7N$4jxF1c3WpN~zU{&d0 zwNWAKStuQoY-uJ0YOKJNF12yf zM$DZv7hiAx23NGXf+CTKdij-?@%*#Tqe|5(7#AIeJ7*9DD7R*Onu@D2_@E zI2#14joM)d#wIiPWQc^I0Z?Ech6$QrOq7&MS_p0c1q~yHzybw@C`E~4<{%K4&JSb1 zNF1bR!of+hNM0KlJc} z^4@#z#ou$+-AGF{5$UJdUrm~(C0X06`xyf}$O3*ZA#1_Wj7i_r?~I~xL7x}1ejs=FKqE=4K8X2h`` z?qWb7>|+;uG9_$)bNaw+h~mBCw8YR~0+2I35CFT6=HwRp#tTs6pYqv3&#>SHi*vDI zZ#6MOHvi#w3r z3+PqSfq@$)jxzn3Bcg1lBPu+g{$z#z z&xQza{B-1aVT40uUloa?Yy#8*I0_B~s}A}ddSquO;-0q@e2&qa!F0kRCJha(gTJ%! z4-Y^>#T0tx)#q=mQLRSO2kX{kJw0kTr* zKW437y*ewsa(Y(B&K>jj@86$w=JQXOaQjLmqHAfyWxJ3KJ(z=Qq+#th@Cev000jt&2o^tm%>1?J7dCk(Zh<0 zeGzSpudL|{gzb#lO44rLm5p~0gvfJV2#8#EF##IvTW>>yi9iHnpx^->$PkfYkP$=5 z3t2>^G{OF(wrnu~_#lcS@_i_+!5~mH8dV`LB#1&H9we-ag(yxykb+YM@$bI-?pHTl zcf+*ay?Wo%rga-EUA`3Gee*5Gj~|cq8`op#qr;G$lT9ZNoxu1p?;t552|cdtiBH#U zriK?bMA(bN1$FD==G*(D00Ke%zHj#{@%%F{VAYBh=+wF+CcO79e);WJq*tiiW7(?Z zJK8pHn~@vIAySIq6WJ6E5VW?ZCi_Gd5fF>JScnNmK(Xev>=sn)Je075HsxGE92_KU z(S&9}frwYGOk(C=E$;u-*nTK0;=!3Y^;)9j28l}j%lT|9eX^DSFHy`o&1a{TnLr;uM* zh_1c5(wH~L$iQ0$(((n%P`h?*T+{a&v~AOtE^KlUHhr`aGbYc#B^Niwmd`e$dbR2p z{@8H5H*E%0C|7~{tyo3_`VWw2o_R*PcItX$=l46ey*Bo>FSCzjC&a6GMMR1ys-i@S zd{VYr3kV1iU`OJ5KN z;v@zDGWPFD#Fce+K?U1@no(d(Tv~GczuiXuZj?>*ii?bRVEf#P0I+h!rs2@E0LrE) z2oWiVLNw`s<9AJN+42vhiOJSs5ZbS6@eV zPBvDpUWMco&;L-Hf5bha&q#HjW=YhOw``g)*sS(D#O`@bT)6XmsJlNQh5B zz4Pi~@B_D_@r9R!4jnym{f=EbcU4QTUc`|o88SrHVIyWk%uJ+gERfj{`$lO^@L3@c zCRpzbPyuDd?!2+3GhE?41z`|dRSLyuL#M{_SrmK9Pp=&R&tD+tR!qwS5&JC4nyy_> z5&2^zLY(Ww>@E(#=8ys84g~u(1w{hZAdlEysv=~xNA~P6WLQW+fs7&1I{25!5RvvB zjUM%h#3N{j04>!;#R7^eE>;MKgL^g9@49}Ii;4=nv}@D0(#~x=qL*FX46B!~!c|vY zh18TZ`sVYm=>CTvMB|1Rf=kvDVa4J{g#s@tDy1Au2_jad zDJffslh`U3?7k2RU_h{@vEh(sj^S{*O36q6YH|OsM#xAWLsXPmiImvLpAW}v!BJTN zxpFrY(4H*pI(~!5#WLA2kq8;`NKq_|0sutDkctn>sT0S`y!Gz5!M(foN_yp)S5Z(@ zpwrK<8d)@JVQNBrJk~7z0OvKTgBu3kn6dNw9c39sids{J&^aE1Q&YIvNcT#oNkoq^#_QVvK0aoY)pE z0Es3Lz=}aa1Xdu;1Y3d^#7yL9EEQJPV{r_Z(I8=?1d2ln5Evq8GCoL!ZFzArM81Hu z7JG$>1AT9n?a#AvV_UWfI{OKof{(0x))**wicG-v6yMG_8S@$7Loiz>n zzTbz1Qy1XFjccW-py;9zFOTfG?7ijlj~zQ4dk#AxAgwjnXBZ~3MrNFb$?h5UnH1b( zi6Oi50-m_kx#Qv~cHUzNzcKHFjZuY===op=TtZq0U}YKHKYXf0rE0xSs5aNF=QBu*jTp4_X!5d zS5Tm+f)5pqMiu#_D4(>8fv5x`MT&G`W?@{9zC8}7q@=!e-@^~&A3S*I*rIuh_D!BL zW#20=zp`)E>{$oq%$s-Wjh9F7kB^Vvf74Aj?f>la&kj5@>e-ANukCkW$e=q9bne#a z(AVF7bLjEspFCBuT!o`k#!NnZ$0K(hUb1S*k=G}TK2g3*`OLGb)x7kz39mOSms-w; z0U0&~VgfU1vd_#6B4%P?VxOJsXrByWQ%NaIeX(_~P#9ef@&m!AO-2j}i!_JSCYz)WU2DEm} zM#y%|A{JIKhCoE+Qp%ZWb7ovwSX6M%rY#?1;j#tjb5kFgGiA0^tx^@kMm&mb-)=+O z)@^ZX|64J5z+jAgZX`ba>{Hx$#Vj_nHsh*H>n&cyIlOid%~4hDAAu$^Z%wgPvI+jcf0!5a*PJzab_Ka#v`%P;>` z7s%OF%kIlA_CvA5iiN}k^}B^2F4hy!piIPUoE_O(?j%IaWcQQ=$OLY`**9iG#7u@k z#(*?|jDaF!FME$bWQ;NaD@-7}mZYLYD(XiS`J^ctr-(>Fkt)hAit9I^-!BCP1@8{H zZ9x8c=bW=|^7zU7dSBgV-|7!m?|b%zXAkV%yYJw_IScm9oIPvbs25(?H)p}z{qN76 zedN^_UO7-EE$zS!*WYkp>z7{~eEP+a$FAvh^}+iF-*c#A?+%B)`EJ|cCtrB-aEqqR zj+IF((|OX2ch4!CT*d$(u(37+I~PpBbC#Sb#6F4fiEM>?@*UM_YZ)`KK`cep+LBeb z37@fl-F{{;pbljfg|52f>|=kmmVdZFIy5_b-^pS>FKVDm$2FJ@v6R4O4!URs)i6QM z&2|T){Vx0S_Hl^;MA>$KAPyFth?vM27bmm)Bw2-x-FUtWW|2>XC{eI!qZCyXM?^m9 zNR)I=q*zlVqDhfPArju3^xo3mJ$irj-Hvb1>)yM2jV6sRsS!e`#^N~(tJbJit=!pX zpIyCIx1QDGb$s;)?!Ld;L-#&d_QZ)3RR{DNP_2BK^68B)YMj3O{Uv38*!4r@o3FX4 z%DJ`9sge+%P-V^R56Yh|$V= zb#X|51l-(8(B>XELnL8qqycPL>G)7VrXQ6zKl$OrKe=!JW#f|iRZbQX@`@sUD0uA> zNH0stjG1f&N%9dxHf&%IKl?z$uyZ2Bq*Tcq0s%6&Dw@DXh=>vr63mz1Y&qwX&o=jL z-=;m5%vg-H)H0}3s}8R2a}9plvm5iL&%u}=43EX$ry%_euP?Sk7 zgU%g0p=NpwI=kjsSU7hflHw9^@kNcOO~*EL{P;1v@yh6G41!o)@CLHdOM(FzGO>6MmqLNocT-^Y?Iqzn5OH8aL0KSihAPs6x!CV; zw5JFt*rCEg5e+}ST*8s#$1?wGdVv5yTIIy=kL5?TV(WcNY%e(JI+!>Ho=ng@8*u14 zup0nC45%?Nh@fqJiJ4<*14JB)!Fw<;Wa&jeSU2lED)=DZkb>_kGGs3efXL6+6eSgj zk}i%EYXEVR#!s5tu5G)YcmD8wt($MXxyD&F&Z<$iOxYTrZ~Cn2fkOwr*XG#Z|lOwjouAKQOG~kz+^FZ@J-?^a^Dvq+eLC zL6yZ*7ggT1XIG`cH{Di!!{-~XJ)NHyvNCpZd&Q7pBCvgNEKI)f-O*uRZYY@OH0j;R6Z7UwpRGS!w^k1taI60D#~-~X zpM6TLUAtC4@ywI@?%RjxjLb|u_T8~+#Rn_(1CKqRTeWDV%atpqm#kih;N_W=7ZQdPz+cbA!6ez1Rfi+a#-CWco+h+($G|tUl<pqGMeSBEt=iPfE1N#q*eDm$L=%P6b`L#D+&wA~R*N^q> z(Q7v15zUQ8Lc!%het~uo59Jq;CPSKx^FEYOhDj0I&tbzIikKBeNwKZaY$3I#1bt`#q*Vr)~WJnj70v&bCMgSsmPUM7Zo?A1gQrSxHyg%pt;%7!Y zqmz@8^`*@()f+c$(9gg0yq-6Eu3oWvrM`XO?Rxi5yY*AgJ*_`lzg7=_=5ak_&>cD_ zH&>6H^tM{LW|e+;#3Q;{lV-X~r7C*qnq}&p=@Znp9ouQi>ZQ$Io$y+Vij^xy4I2f4 z0uxy`#E>tn0GD59`wbEsHxF(viu3e?v&DL^&V~Z7!`Y8Wpb<)Vwhc?>U679lPS(+UMcGfrF@eUNt=T#B+FMYj5xdUx!pkDWT+bK!@JYQ)DU zao~X{IE@Vvu@Lzpv4su14gg#WZrG?O9>+@D#xKdOD-*HJ&L$S39dz6zwgK0(1i`34Hc z(XPgji5O+Q59fi9ebzHPEM{_+;0^$z>|7`-qM*_=ghP|2QHv0F!8ShM-fRjf~qW0P6pl+SIm^o)AE^g5n`+xWi-MjR_xo4k) zBS(*-THR`Rdf3x=?b%mRwQ^OYrKF-+mlp7&QEXlH87h=5kL!9}n{eRB!K*fYx~W1! zD1n&+r3yPx1xJUo3MgRfrNDV$uyaSHJdTpo8xkP`7y%<-r3;oxAQqf5Cb9l`mB0VH zwfxfz)dsc8e|s>ysA0{dcw!c%h?vO9b8UdzB1?$f+$1DIqzw^^C@svaM2XQ#D`MDu z5n>@lP{KYdPm?V=%}_*Sdn805hKwkqgv1Cm#IO?W84;t!R}ha4pfvmC-Q^sIgPu#?onvaq`qDv})Q?9X)=G z9(ryl9vS=~k`q(X-<>_N+ZC6zoCRUqhR`P^epC^NSXdE@G7epnfnpKGMu@n?n-d`t zW4TUyaX3jG3yCpg*HtJ)WR4j>?d#u89aaCKG8GrqEceT?f@s6431RD1DUc}lELz)U z-4dWhr{MF5U~B>rgo(AKFIihurNo6umB-}!3Pi#>AoP@k;zQ9x#}1``wd3pSI<@Uc zcRzk#%Jes&iMn3+`D&!)h zjPHR#QRQeWzO|fDFMH)hin0!{y;@V3?lslb> zlw|`Hl${-0Gs*}=Dk54>?e^$soD3I>hc{mujXLL^kJOYjRZv)@Mm+x%>eN0DSN6CP z^QO#2)hbo>q#2WG$B#QO>WNXPQZXIHMMdh%ZC|Q4CccTq6Bpu>FFrYU_L4bWE+2W> zf`Wnqa$c*oDI+R~Dj4yV7$`+>!E-2Mt3?nQCK6?Vycn_$u%pvONSw{EF^UX`StzVk zpC$#L;K;gPataeYSGy^u{0Q71o0H&zR|F9d!ST1SUWZlJSrkc* z9}YwV+GbU`z$#gUU_d6gXqaO048D9|@<&#ATWPDA=xBRqEnkcHt zCryg<>4J=K|=kEf>1ns(yJkx%B}GplRy2s z=VbTmdgfNHSouVp4xJn`?#)xa@1N>EBAOYJcg-OUt7r_ClvfnGSH!KC-~7QUViB%B*rJm*)`8X=f0h>Yxgcpes40if45DVH)+9-KKU5l znfNxo`~F)@m^7hy@BY16&6~EE->hx3%)Fe;a8!|w`lQMCHOO8bz9B`%ckk`RVIoF3 zfb0?p4cV}*{S)xk#D+?Oy}?u3=OkhhNhyCXz7v@+jee0F>B7u6T_YumQnw_x+j1A?bnm< z&6|1hw|&2z>ULfC%nIcyoQx00pL+f6*E5(oqtC52%Ot0m7#$w$4+QZL1*M$0z{Q1>OF*^^5IgFMV&1C?&KL@Ss>|tPsEDE{ z(h`n-^7V;>f47!@>VbUxTh_t$DG8^ui~RV+ur9PNj@Zx>gv5s;4Hj{9jaCd_2%1O? zE9Gfs!$f41K!mln@QK-?2GNR$#J0AEZTK!EN|YEjqO1zu6--gk%D|8oVL&SbpEW!J z0S3OO3BJvR_CSC@d?F`K8Kdw-|!Y4&VMQta(`L6zP*AIm~yY)_- zyKpW>z4QW>&t8smYn~Hs-lSQ=r45_X<=tD+the98(&>wfDp#l+KX1b9aFZ_0kerxI z^C!%s?Cfmy(4Yt6hraMAW-gtLCx$%cjd=6vF1_3KI#{J_#o|abqS=To(l4UKSTqF| zCGP9&;vwRTf>_~zb2C-IU&7@@#1u}eAmNYWgW^&uz4J$prT|&8z5rRq=63>I2 z=V6!>*=BEnhhZ-sEnwe%E)WF+PT5MN#7^O;Hj>tc+6Pu?zGnXmKiDQt(+ois6w0LtOBcp@qaaW<$fu z(g+M;C0dD1C1Kl+iLD=_JSAWj0|l)FFeFe$7#d1!sTolk9{ZMF*BZVL@;sl2p?vn3 zLXc<#D(hfY;;MN|mZnrM-?4t(`WJ0nwXU$RuuyKh^Y-}L2Hl3~Gp1qeD{t|x-Mgq~ z=N<`5RxQDkqn^f!Sxf2cYG-kihL_yhkfc9X$z4rY!O+lr68{f9pMU`IW7a z9G{E_2i>1KZNYopyR_;$Ipl>zL=+iOY@h_zj)X;-DBE`M#>U+YMA?|D^+3qQQ^^88 zWo*33#aFF&YrmIq${;1RQrYb(Xr02Yoh!O0jMLI8-PteEWtfIt*+=%5rmKxC9} zeEV^+(Zs$BX8M|xuc@d6F_2HX(3lYNAY)z{J*h(Z@A36Qr% zL@@m< z9+U*H-5I50Q3C#+NnB4&#R99OVJFvRTX54}oG}}5z9=zi(q2do9X*shdeWQM+&kdz z@buAB5b>k5?&FWp*a8m5I3v=AzGyeNnM|MT{8w1b_|e;*|K)nZ7TbT#D?XMV zHSvM>e_}>vrW`$dB&}VK_9^RDeUw~1 zy;}0yuZ~MDU#472-&?LunmPade#wt(&t})o^<2gHzlq3Xk}ubPQ4TF8*ooz`LyzS{b%bNPn!@jTF`D&jn zka@!>`kj22oE;f)w5QvnHdyX1k5V9nSx5=wxJUYnUydJYQM2aXmP-253xtULJty+N z%!ry4Hb#Lhmu9&$Az=xuoQ#Ruzb81gkWoe)$~K}%NRbhx+2t3CD2udMu`f!96dTrJ zShFu0L<){O<|u2UNRb%LY&6Kx1r`NrVxtW?C5$rKu=4z3@_Zs~jPiW;JuZRDe^ILSe zwhN9PKaSNiSIW0Pe24X)Zoso|K99#abo!3N40L*g4HY~PIKyGx->OIX4!s&YY7<1Uedib9R*Ro(oL zip%|9j%$g`j^nu-j}}JCP(V9~kg_&n1HmG0gB#1m3X=hBJjmIIF`~pbnhlf$u>lc9 zJ}dUEiY+-YMV72x((>5Jqi4S|;q{Xnwro7LXvN|jPkGs&F8TQ6>k~$wc=DwYCpLY$ zDP!CB+fFZ?zWBJO^s!ETIvxA&haJZjuUwM%*nN*49sa=ZqxBotJ^Ix*Umtyc{_Na~ zFTD8pM~go^deiMU?;kK^;J!-bD{tsOr2m1OgQwzsL)sYQSzu!Ai$izP1OPz7W=%$qGMsBO2CtG z?e>haX#zyXh+<=`wne%61+m3Chb7>t6zG<*L`^ZRHzIFw^-MJmzZtj7M%nVsQeT97g z(+^m`WdmNC@Cu$D^Ng&Xydrcg<3#7rzu8hbAufS^pB0ImnvEno$sb^5G|!8e_h%R;6m9eODq0Y$^uh!*I{!j+Z_|w~zxZ-WN=Pa*cFsGUz!FkW2ow@vOYInrDlkaEWY`8t1Edv` z)-fxwS>BeSa4x1XMu)Mn5Gc#lV{deELuHax!yw7AW4t5}g zji!jNjR`Ifqm5Y4gQKKPG-7R%j6#%^$E=7+QIr+?MzL=tyA-vD8VMtAnZyVBKr_^&2W~VHR&6D^}bOD0PGYdQs z5x8*%$>Q$_5gZmCiX$ymkHU=3`-P5gcpw z?VCqXB;+eN2BV9*1DBH!-(u3+|(xdq?v{=Tqt>kfs(Mm<*i#aEvf&RI6EaPya& z3!7ikqHx8!l|?fb&njN?$=ae1H?J)!lU%mQuwOLnwa227XmRnuV+R||SUR&oQaC~E z0vaV^lnw6?2?9he&f^GBf+(@tZks=7*v_Ss^J)PO!^9`oDDzc~N+%BeOLp=9Vmvpf z#g5&%g+FH$`>D3pkPsOmVpajzu*Eg4XJvwCbvEKuB8rGeSTuu`5mpolVgo>8LZUZj z#@Oyelrm-hbkwR*3%RFrv3Ap1Z2sb7Or1Lwvlh=n%S&5g!?O?bwdRtCrx0T|c1T9sO{2&9jk}osH$Im($j-zo0iKjENjMe)QngJ+E3>`>fi< zMFqtnVxtus&$u@-MjKYK%fu*q*swC1B%)cAW-*$JEz*sOMYh(a$;CieL4#PV1el|u zIV##0Q4;l)F}^Y;s*MkAe9GWfvQ9y;9hS(gGY4PV_1yZyVIN^MBk% z{%(9(nE%OXLS9yppWxI$_5gGjD984MOQ>Wh2fFNBlz45#0;|Y!A`=!O&E%>;*fn6o zntjJ`_^ipDHHaF;!m&$)BlfbO!eUKKx|nh18KaD6oQ)_p99A5b8ms<+wuUc62$(Ngpnl-$vX!XZy3MbB;T$pn@yXgC$ zzi+-|?UHj7yachHhbTX4lwqS-IL7?g`7z4HQI+_XNfLJXM?oc#-<>hUg8gx%tJN&~ z2@(CnO5cBZfiMfsu32uuw^?}=;)q0GNSNIrAPO8@0)c^xBIhpIH&Sp90t3auN|ADc zUHcF)PSR-Dx|}hoqg_n)S%cxq36PFRR1-mYLfR-OQfxQ1au)q6N8DF1Vb;3^*L1zQs7|dqkuMi~R`Bz_Ukh%0_~wEZ z4VxD>yYSM;3->--IAZYQn7m|Khr=fir+A(RVNt}Q1ooMYFG_q-3IGN3Wvti1EH;4Q63=|ns5K;oxBEC+DkLUTT7hPDnLY0f}z4>nGenmG33$7k=EsmW!PM<9J zm>Se+fUCOp#fB^dH862aA}w*dogcT$TNzx5#y*+3chYBEP_3d zgf+V?brw?WlO|;Y>Ei60gcS@a0xL1TGQO_`qQxFk)<%SgOt9fg?Ws~WV)I2TI){M* zvw{H10H@Lt@_#;-yQ^(2`nM;U{!hmI+dJeI#;Ff~$|4_HS&9Q>|S{1O}FRIdwWiP^FFQef8G0Q!Ogw<6MITQe0W$5}X;Ion{S_%OB1UXPL$e|lQA)G;LgI-M5G%6pg**iYiy~M1p5WqZ zVmn|eB7#dU5{R}{OvM;Y1cRV#gzc+NK&&+{!LUJmV!XMr&kYNw%$|B_pU%B2t(d+H zZCka&n8{?diAlF(6L1a9652csPEm^ zMCQCUGqqdmuBcn94$iA_E?V5!3ItWAYK3&%*!8-ksmo`yc(mWc9~ViH5?0n5V^~EF zpo}qE+`N?pQ-#=2EH8xF07i(}=m>}oYMkzIS?vw)|L^whziebA6fe*9%>y~b(S!s~ z720~JaDscWuC)th2EpMoYy_kjESj`Hpg3yrddTx6zc4>->AGdTy0q$C{K$*L;@dTE zt9!NYiL9_sA(F0FCfOak0p?9aASTuPd zgpGQB^b6R!Z7ZI5a0C`lU5LZS4y&)e*qXI*%g6hleE9Lzd55#(NJs@)(1r~m0xLIn zbc$~iUG2Tk#syo+QLaw>VLfJLt)3P8WDhdSA2`}TRIEUruw8Fs6tg`zg9OVG>QDY) zBbIUqC{BaLqFS-s$?|CCkybAyQFu2_d^Z zSw|wU&1@6^AEZ15W0+w3gMg#Httnt2tTllRL|n5K?THo>EAV8vSXU`qIoh>-msz7H zyg9o6H8+=SeBs5YQn4}`wr^Y*4uuoWu6{OFOk0Kyt=ePU^tTI+oj9I&b=SU_^VTf% zXxkm*XH77-4!ccHc=m0)@Zd9Oe`#ABJ9P}#JaD6VZ`4%Xrb%n0S4givVg971L;KzP zMQ(n+5@SJka5*^on8dL}wh*qB`-(7$Zz)2>?5a82+#+Je5kWGSPyV*yS=E32XV&m9 zJrDqxGNj$91xkA*#svS*mJVb&bApP$Y@ z9oN7Az2AYF4g+8;AEtYEmj~`f78t z55M@RdTLxU!x-&Qw_ z#`W)zSs3m?euR4_vQ|`s(;qB1v4_&f4U)a+_ZPH-kmuy z@4BAXWga|yFyqznuVrlca!bZ-kKA6+vdQJA>Yh{g)cud&ck1BbgBjP~abxa;_a>gG zcW&KHZ7yw>nO|5CDs3aAEW+d{d(1|gm~Bxhlp5Hg*u+5>EC-Py!y3#M{R%5GqJ>!p zQeDGF8x~ChMF_lhJ3kD!pZ?TFSjFtfQ4hhqDqrYOVi@7HxNU7~RsPEYc>hh~?MK>e z{jDJK%ZZ|Bnw65W6Ar-HiWLr+(%zVe5@Q(-7URSStb|1qlZFv3er(%Hg13x}%=mlndSK7~1N&AEdGs#) zviDcq^ThppW1ky1H8}-W4!Q~l4j;gsj}1ZX>b1F3%MQ5niF>f|^Gz5%#p))FKih;LrQ$3 z#jpk&&4#u3RmpDiYiGLRFxmI^dp!2pGh#odDC?64k0y?|?}?APx9|F+ zBEnbOzruj4Z$ZtfH8J$|hj8yL_h8FcpP~MF_0hY1Pc*rpF{ZyX89)583x&l6czD2r zh!4kO*;|W14D9-KHy*m_e#C|2@Yc{b(EgGuP^DbOv~{1aZ=IW;8%MB(<81KHz86Cz zHYj3Cf0#hFOQkBS8ZyjU$9ehRffFmHm!JKw*tq|yK}1r%Lh^#W(Wohi_;KVsj{rT3 zuzemAAW0$-<0?2i8wo1)`XCZgsmaN_cFWq@pKkxG^}Nxuz4B@0;%@47LsE-I&6B>~ z`E}Cywdy3TnzSt5Bc1T%-H#`W;gsz=x2L@K#`L5n7c@?|w)-{7%^NpMS^vp~lzVQu zE9LQfhR2;%tw!p71Mf}UymfQx`L*k$%y?rO?LTzjw=RKxGBqpx8jVM56Yh5LRTXk1LM4b+xK3dwxXC8D|WM zJzFSRa>fL25kKnX9L)$l_0WiAZ7*-T_t(9@;w|#;B zSN22g>a}tIfP3-afcx>;H=m=yITujhj(yPR{0lMt#VOeR+a44Z72)Cj45u? zVdX!&et&y`0KgMBH~TR^tUukAQ&dUAW+~cu2RrSwLj=A>%r*&*lwo@tN|a+rz}}Ez zIYf&Ll+VVZFza0?Hl(c*OSDyLL6OgzoE*|_Xj>}6dLa%$_)(9=#4Zbm4!tutYxib^ zJ>y6JUwh{r9#zpb@G~=achd_ABoKP*^-&NXb^pcTH>p*Q{*wjO4#o{ek6e#GKI#c@=hcQGRJLYW`KH&pa6xg+N72A-#4 zq9)@7goA#v6BM{6<3wCg;6P0RF~-cys<+$4)_1b*{a2sZKY$s-n_f#Mc>Y0-J&?-w zEYA}u^uXwgGV;s7u%41iQ3k1Lt~FcvY4`e81G~KLk&~N4hkW@q3p={kbmr<=)5k+T zGF2^8#dPudMP=By5z5y6+e~JY+4SkKu_o2wG`;oNFt#Inr}_A~ljhmuzcu@L`+&eiVhQAl1DZM1YKu3S@%oI)x)c zM8<{2kn1O1Ao9Heq(mS?4P}cNu4h!~CltYv5iS_v$OKY`T*wUqA|r?lkmTtYyF4@)W9YDI@=&V{&qGElp}SNG(&m^w#X`Y|*3rE3k6YYW(b*FL2|ojaadC z1swnWIQ(hndR)1AHGcZd=jiL{gVRS(#Vfb3;_f41IBeob%*oBcQGG{Z!GJ*Ac5o-Y zH~j;=9CZbo)eObnt$X6(3rDN(IKH!-yTuc^hKvZLz-3|bbNLYzHF>H-UFW*wN9a0+ zaHJMSoR<*&3Kj9&NeKCmn{WPG0|EfxQP6Esq|NS@>*N+!FYaVB>L`VkY6BG>dFx_<1}@TD-q3`~LCKx(V8VpU- z7g;b>Qx&2TChS5{HC0hHRdE>?)gcsB6H0RGGq=%iy?^8W!v`nUY+aih_c$)+qc=ZD zT{L-qO1ah*Q!n4RlKL>_K~A-DRnsqQiAWtVc5v#FbxYG8CnaP#RW0}Yx-;n)Z(U07 z|LNfL1 zal5*z>IH;(shfhTgo^u~Ce_o&w4>$ZmRkNls(%bzAe1Z{xZrB8!^0tj3G_B4sHdm8 zu1V;r-4vP3OatA$rDxU%B*fRlTddf&x=x^fVEMf#_ruB^tKsI3t57_+1jNS2z|H7e z&|zq2xD#`aT-tV?pa6)CkAr0!e}Hy_JHnMc7obkn+7SI98m`{F3MCttf$5)o4WEtp z6z)E_3kit{FnP|DWNU8jnZBKS$LD0^T7FGNggj*^Dp#e!(08UpBTR5r5e`)m8Zwm& zhEA8eN>2pgnsm3Sl(`Z{xQa}69C+?RK#r8i+z6Ss@5;(^)kM6I&h3&B01VgT}JRO6oe$qsRXp{+6FrlG> zPP-yB!Bod{9g`vwLR-LPjCu-J|cD5*Gp0>wy&0Y@y3PJm`4xOE0wI6c46b0w6`Y=Pg}5lQQDKFgbb(C zk$HOMv5d$EQ5pTd7?_conU+zvVr_owepHi3iH|%1iC!k4yUO`<{gS6sTqjpoQL~U8 zMvx1d$|yEf(T;EwTr<$`|sI#IIIB$>bK`e zSYYc0tzh(!kDzsOY^&ZheN$8b)ZMJ!+*7EMzst4HZ zcIeu)6ONuUmR!Gc!@o(@2B0}r=-#9Yq-CaO?mHJ=@ZhCGH7gVud6uA)a4r~fV1R<5 z5KOi=0X?21vvi~)0?Ig$$NnZvXAr&QjnKbrUO(@*?{@ar_4nUM&uv{lz&lpuKmlcB zfDkHs8Grym2!NiAfdI%6ky0ryZi<;-iMk%RDSS)Qm6LxUi75$?k&_7~rqB(0HQiVLlyJSLUwlTp40}a^o z)LnVX2hJJggv9xqX|~0DbLzlum0Eyn`h#&zb067p)^nOuPul6UD2n`4* zgrEk507pUy;T(WaAy6P?6c89iK!QL4RIUP}>}MstF4-wmppe?CL5p2(W^bC%k*`2T0zKTv7K90Q8`)#OI zz6QM7>J|KY`E*ggQXTe6vu==+n+@HXc7-pNOwcm3GJJQP3ac@q>)V%eoGKMU7O^4+ z211_J09>&kndl*eewsy@!AAfv*4IL;h=*nmZtzd15B?he(FueQA{x{yv-p;iTQeP+ zryN=$28W3q?59=Ql2or5e{1q6ucT zTg|N+wMlU^S$1yOzr}2`+M)l)10gpz2cl!{!SS=l!Dh9>v}H43=h5BZZgGd*NB7{f z)D%2^@g#gSYb=y4Rt9z*3&V#`;^1j&GW4JL1_T!fhEvxg;L@GTSieF&h)sM{Yu|G5T>dgE&;K2h$z)Nqr0>$(s-|Yz zRZm2D;^dsm-8Bd*V?Zy&c>taVNL8eiQgYzSWtQ zk!FjyeAad~>Z&c!FTgh9tM?seE}yZ-#Xqv`J+{y4ZgR68Jbl%ZPM=s;>BlA~ed7 zYl_MRBTm^hf#|0Xm1}vf5#baJP*CI=10=c;kZu&@NKqA(fFAeI{q-_tCz~#shGQ#!1Cnwb5#vC`@Ox)Xxt$NvVkY5THE}R=ws=%xZS=In)X!TRNrtg%A zo_!^C4i$pr#Z+AxYKTZV7fiZLIprMlbd2CYQE(>fCZzjN?})*W>xn#EV*)gWs-_@U zrHjic6r!mNTyAttFrg9V(lG=H`o$&O&Dsmbnd_8b&rx7iQvKNfCz^!O3<7|{z# zg_MN-Cl0`kJJ-=_wZWiIhCoJECOmlb5Kdk=iPl^ze!FT8Y(2CcyxhHD&+)zZH03Ft zzIGNqoHGW>hLnNbC-y*eOfnkI3~@pTr;KtLCZ!w@ z1ZBXH5Ka}!3Cd6nV@&2vAPAyRjsU_GT?J5EOB4+dAV7;lf#MX`;!bdi1}{$0;ts{# zo#GC~Dei8?i?p~yad)@>{tRI<$;_L)w|DP7=j`r1zi{6YI>Jl9qYFcZiYNXf{fs?_23>Gys~O)HS~bveDrg(O8_y}Nyyj1& z{>Y|r%d;%|WR?y=N;eEZz#%`iB4WeLq$gh2s4n)H(BZ2;ThWL_d$bJ@n3##f{Nl1~ zRr1=WM>HmZ1PpYA@US922#d52oTe}GQGeX0-&!+=GnZ{dC|53`iXGvT|>zs ztkQ&U?_8gCzV2&rgJNMi>HAxo5cd9&ZfTA|Xo?w0L-=V_@uT8#F?Sn@qCOvD%8$zM zO*!<4a<@wQ6iU{>ls4KZ{WJPZ9WNDCI(47`g*37eYEPeo$uYktH!7!!hpFfku0+Og7ccY^ac z;{DNldETf;l37}lIB4iG-hN}C@OQuqqELu8SFf|>HrAQ*ZP3bKAMuQ=raH2M$LL{X zPY$$7L+HyyX~*a8DORiP&W27@WyAZ_(~aq9h>ZNF-?C%!*ZPfac<$xThSmJmgGfz# zWHv7IzXPn^aJf%!LFB6#%!d5)XcFI$MIM(lB|Jamrk{r5Gu@>Spx~8{!p4glo3)#Q+_Ec)NPrEY#u3vTLL=WCt$y^gGj1N9)_O99zd)`fc~QI zQ~hW{ph2&$)y`$Z`&cqTw>Q_ya}*6hX5~@p+}Oh};?IJ$=_-uLD?EB~?PH}Fk8%a- z7cg?JD2yZGKqhi5Oms91LZApx?&U)@sfGJUtLEB85e13Ekp$z> zsexcuI1wOpR8`)+Hn1!`Duo|g0*_E;AQPqYF$Q39vA zSmIcQLP;tPG^doYZ2DPHP%x8YZmjH_Q>bNlveK@|j<0p>_0N>_WUVb)v%@QmGx~Aj zfmzL+H~*{Ljah8Z881DlpP@!uh>ez!Z2oR$!&*;)8*O*Ub4F%W<9@t)qeNPF_UUcN zy}@|k7;>Z7)S|*ayVb}7$-sycGM=X?Rf}0B#Pyb_CwXCL3{|q&hhkYr3X@T9(4^i0 zG;GyRk%zbv2gNcIOKU=ccQ=ZXx@;hgiBf_l6f}%~#_CLAm^n)FBN0&u@ADozMUXA8 z9}2~mtxB#K?v5bgmMuHpO(uJd?~hC06NXoZs7#k?1OGRViIx7sZm<->fmGszpNHQC zW0B))B@1~Ba)dwd9)@ck%JGt^ zc4K-Amr_3cpXR22$KENaYW$fG;`KON{<253h+4$Mi{?WkU+Iu8DCMxumokTj*hqmkbB z(ZjCz7kz%|1G;4S?eHwrqy^8$_Bg$sK22JcTJ-h0TZ} zN8p4MOcGIDjS~;gnQ}@E#;L9f{K3{QGs@`cngbX#bzej6dz*{Wf4aVZ{C7NozPso& zg`=AA#E|lH?{R8+Cj0!;`6>ec&E@sw&7~xZ7mvd8FdCMS_KfYmUZDwf&d($HQ+hjv zGfUluJRQ*-P?hWT;Sf@jp!Dc}h; zgDF?kGfT@)Aj8c%I8df2BK+RO2p3@k4uDI=2ztlBH|10`wo2r zfta3}kCQp=n$8%t8Zjl!Hutj9L|va}byEG}PK3dUv^*X5IEck17V`ETZfP_Gn!)+} zg;t@t&hH$HVVLp!e45Rh!Pn0A{rmO|V1G*@O-^Kn^dkC_{7Q39Nhg_=OAP*jLW3YL zQgyZFzzj8Y>QfAY1|AxU@%t@{{I&JUK(E2h^BOlr+n7^7#b+<&T)X|agia9!t^Mz* znOjp`D^|jYUQga?#XFZ%LF%h!{aYm39pP>b2}vDwj{)xF1@Gu9U;`)x3#~y*}&8_fIv=$1P0}P z+Q`Mb70v8SwqQ1c7mFK}#~B(Wa@Y5N<9>_c!leuDz$WyAvBbei)0osrBAN=<{Ul2A zmktTwpfKU05AYEe3M;ihb$k|QZWiNZhY!G-BzRY1?~m%(tIJcFA3&V;^G$M>9Ee?k z-03sPj!Lks$xOxPNBdBz5_dQTuq?MEcK&e%sh5cH5Ue~&!kAJ9-wC|j`)@M;bY0>;lJ5HnVRi0*UYYS z&zi??1h@6y&iqLpE`@0O&dwJ4jCyas-$(DgZ`bSNZuhf;>$Dzq%zT{^>pUDH)h{so zB3!?laYN=-)hES8q|3BrFhqanL@25!g_$#o?AVJ5aT*RQvOta4WfzNs%{lo6<)7&6 zuq@zqLx$36Du)LHtWe?NDj5`R+^NK>LR@XgF$pcn4-0kQqjltyaoTg?O_Ojy&f#n+ zXC{PP7xwT)o=8l$3`8M+#L5sAI}cz}#!g3wc)8?3hTS=HLt8F3n3bJgK>?uqPE$aIYa17p?*(<^dff{{8FpZH~BGG ze!utC6dFe7WGu7=t|T6%Y)TI7YYQ_{{*Xw!)!1qHj%v?XiOPYKayPI-_wp;`_C{8$ z=-|WzxGa@^kV;LJij3zXkCPRJvXB*u3!vxir|mnRB%kiplo#*aevz|SGwb+z$&j~Y zVdhvU_eT>!UIK2YoD*5d34{J?fHJwZnjiA!3#n%p>EhXzZ=J?HJGu9Rn#;|h@@?cH zE;iZLA_5)=6W#*|@sYm? z|3Jj?2vMU^@Cf7iuA{7S?fu_yRSvl#1~}x=>u#fJDpN3+OKiUZ76Mi-i1I8R#0~{8 zwARCzXn6+NOE&%9$KO&>m3L?$2VqvbohY8MwFRd1qtP&ByqTKwLYvdb4#XDi?-^Wq zAEU>IQX%aYK=Pd@zVceYzis&K-#U)m^S$KB3LoiIenaffsf6-DJ;)+a!H?k>^ViMq zlcs8?m|3VPnsXD;(l^XUgbMvERrZeJFpZw=@NZ33k}%CPlJN#s$WveF2C;HMpa6Y= z0+f)j>6NXR!OCp97RR;p1THzvCXW_(tqe`OvTXftOii~b&yGAK%jxge^~jrY4RVQu z-IQ!=#QGhVy5^{P7wzI?kWUJxb~*5K(MtFh}jIRY8H z9t|L*n@g?6dJwJath=tcb{MhO)h;&|(X$XzWXjyz3#Y{HI$i`sl}wm|xmc8N6)Wv& zHRG(<@iem&(ioxREoJse88GpXvh0l?C?U`i7+0D)U7V%vNati^2Uzx9@}x<8CT4AP zZ24WJ{dlc6bIUICv?!AoK85dbNcjEssA5A7UWM}4Bb1C4s+E%~BT#qCVaH_0dky|i_0~RE8$S(C)t7W^rh=do*W9rzcrSDu62V zIi^fOpX*Wwb7PJa&&3iq(Z~d5-aHvNtl%U5AMZ?tOswI9Mt5jrOkaJ|C@~)FnWvp9 zw!ddO8cJ}>Z4Sj_cHrStQi=vVRJoc;WBSuUl~l!qawUD@sVP7v=!kR1B97dAl$f&g z_La)$#O)pJ5|1IrF~U9%1t#T-%zM{uJVUcT1Psqp@8iyxunrO{BsrXFhkm|w+K5IE z%+(Gb@w0>ct89*!{4$#R*$`4+HG_17a0IJ`p?I7fU}4|iaWi>Yp(nm!LRTziAf*{Z zvWb{i**V6y6RIxNkmWLQb?bLs7V8KI5G+z8B!2i?^!7#n%1Zy_yyP?{S|BaqQXE;9 z7?G*S8eMGsEh1%Y1s|z23rYF;uoW5o@nNHS!rz?UWoEQrwB8~Y*>?lHUo|*zRqFn( zA2v+gZ#4}(PFJbKCNpagY0p}~y=1>QFP7OYEXQ`bkgZ(X%1fyQ@JHp(fdV6XaIkCiX9@X@RUC(}~t+Aj!hN?yfIbtPbdP;04CXz6q%eM*{0 zQY!ITri4-80hNi7lCgwOG*L1b@&G}Iaj!(0wvx%dMV(SA)`gW(s0u0Yg(|G?YjxWk z{H9RYeEGHgG?g}Ays@p6l9}qmPGRNcd2l}Ts|1G+7B}_@YVSTfwW3XDK7mH=@k5gj z8iZM}Zh63XRJKt>u)?psP|`pT)G)1t0K)CK{S<10Zc^m;sg`mP_le zMkfIr1G~SwH8Wqn8{2X9{3fR8nNa9BD4JiKqkpu~6MH!fnDWYk>dL$S*xx2*z1)?y zxyfZxxCCv1TXL6^B1&2;g|=+{cURoD?a&yecSXgw#7_R1u<)p6rRSH5wGsp^3+-M1 zzkYZ^+zjKyODl6g)Z&yVt#G1-w_;Fcvb*)U zmGk7P*>PAlCB#c8>H`h!XKz98pCTbuO=anpSv9teHiO|SF_dt@6|_}ZwNeAijZzX> zbv`YY2^>SkamSxz18mGCQpXOe7uM26dWZcMaYv3svgg0@s77x7AQN%Iy+Kjgocb=i z=0Ut~E+!TnO26yu7wTph-d(7Q6q@~P_wJ^uHvmIBd-R;g*N2?3DNKE48D(VpGY7{X zAuLbEQ{kpLgH7Vz@smK$*i+--^#r!#V!JQ9r7M^zcaUDn8`1DwY)m|yXezUqfUSC5 z-fu5#63c?fg^`K9$AhlDcZJ{mt>A&?)9`^$sA5Krly3-lf^u41xeG1R$~4OoOEYci z_Qo*XrpOau5);)q!C%@KC+U9{E29Ef+QGJc&ZCBjPQeAaUb-K(SjI#$3 z8FcRa(wh;m_&oC8)FLcG7qPX05$l3V`Y`bYJn;i!vxe>T$EM@k?xnuRizQWQzqWtL z<)xM*x{1R2FtwUjExo4I!ZeJ(-*{YAu13f;7wbnR`p68FdZ0zeY8fuKKAM&oYGj5gtmyIYvj1Y(TjbzbA9hKkkar!Lm20&b1M6^h zDPGTPDj_`(_PNIW7Ds?!?CBHs@l(w#zIujxR%|E4pfU?QD)l>50Oi){FtKD~-`0^< z$m}+dwKW& z=EJ}?eg=?9CRWDT-OW*K=K^CK)j_dJnzA@s6=P4;5X3K&_PF(R!bE}_JBbDGva=)< zy*6U$$T&jND>pc@D|Dlt)!+O36GLs(Ne?T=BPx#Lz;>LEWN)Ja?`o0LTjmE@|60rX zCQmJ4{P8otmltBvt>wlxzBuF$FNF+>J-h_y2Qdh-Py$no@1Qk^J*`mE54C`tfEpeJ z6&fHhM=K)=lhC4pf(T7@{Xp=4u_WVn+_@p~9=is^7&E~6iy~v)H7Aa=^NXxe_GLos^asEqe z7y0ZJ7dl)QH^;N-Gx#fWt=|t5^v!t39}aH+#^Km|dDDxauB_H6dUDF$lo`0i^_Cj) zL`WLjr+D@eIK5_jtOnJ_pRT8P#@pG;1|j$vxX91iH~!^rReX<%?|)Vhbj)v> zS%0{)o_{MiMmoQ~iN9d%;k8Ev4khX@7d=+t+K4_~8+PE)YG2f}Lm&t7YuF(ij$6Aj z@ij(_d0aD^-uS!6Xp6cz3Rx9jeeFY! zm2@C{`}6QgxX#A-_cr^Z0hBjq=tB>V*Ai<}moWit83NY4tte$FrOD^Re_7jg=4^*) zI?t(pedzD;mkV_~R4e&pkR$l(xfq3!qFxH;vzz@7?Vqqe%XnX`1S@Vb2TyO^?R@(z z-(uTvzyFa)NlRR6?Tzpn9wjr>m59aHr%Qt}sdpnOV+fsL$w0E(Sq@hW-8{ zt=Wcsz)k>GZsfuW3MiuQwXV&1dvT_Q_RTiu#%z=9rC#z8%|Fa!Nb|8M@z2>zGO6ut zX#f#6P265<5KNH{6+~&`pO-oNk0|lyT-NZHN_Hj9wWtv!W0~;Gv9Z)Rq@W0c1A+w= z#Fx^Td-Vl4kY*ckk&+x3{BL(-Ohkvj^kZJVafKsS_uLdR|BMy#+RvOZx6B2FV)Qh5 z(hq_`FJqp4K-R2Ti{i20_eA?T{bOmeL*M+&%DaY-4|Mg)dbnteW3EXN(7!ojTpCnfv;0S{nhRMKUze6R z!xZC%7wz>^O_3JA^@4SjBn`Ec1Yj14%nA`#nrQ)AE(B6rDhTiQrmd4)q-#EKPcVf{ zLJSndj_-dmgTI10# zqA(@zH-(WJ-&8tJ+jg1#g`JAgZJi%(^}oL|017TVoQC)67aJ+KrU~?NOT+x{j~^G# z<59=c^Au$rhq2x+lu(IqyB*rU-TH^nv> z=*k$bteGmwU+<&K#Wu>4dB7ou;>9NXMNz}{EU6=0l5jW{+FYEyOMQdNt*_dhSi`@^f-Xc& z*8>}=a;ufTe1)~M(5uvbXc??C?5q57HhWk#J>Ot8m%C=g{8DBgGg)J;@+}X_c#Fzp z^l40{roLoxxR?Ij3utPGyYrA3sV`U@!P(*r%ul+>5tY zZz`6D>Wpeov-6VPyZhX($~Q5(Rkh49sjk$0+LqGq=?re)iAmqzKi>ZVa6R6`=$JsN zWA5^Br@Uf!s!c$oX3sDCr)eVbJjR~D^Yiz0s@0q3NxNKMr3vD{$oRttj?w-{X3X1b zGnHUO$!J(UMcreoX~?5idv(}t`o0~P-udeSptVj{$W1rk@GFGfvR4UjsYUMc2MLIS zw7lqzWi!#dZz85Y>VAFju#n$eoR}NuY70hN7?j5waUuznw@>Sh9!gsmi#J88e4KE3 zQ`R-jnjT&*CaUKO+l(p~!_u{*%`Xl`z5`n}j7;-RRk~UYRx8?xg$MNKS|knp8eGZX z<-*%}^NbYWg(}+Tc%5q&eqb-Oga4cG>1`q!!`lWqjnGNQx5E{K%4Zsv{*#@ujC7hM zoSgJ?n2y#huv#d@>-yZ$kG)WAJjB;`KbDOY)b#_-p~g|ZZ3Xj)(7wfRC?2H; z53^*8OZ90%K^5vtdOe5euF5;78hYJLZ*S6aqwCa%>M|wtJNZyY(H2L6k?SwzN#>@G z;_>>IcQ;nFwpp_Q?Fd{Lb#2_nySff1Cz3X6NeJ3zw)l3RD}{4vFLfL)l|QWcSYULe z-1#+C&Fe1NyM6~OV%#4atiEW-DP}gPm(OZY{91Z`xGul*tQs^xk)>xc=(IJOaWoYR zx=(9;*>E}kll}=-_l+75PSACQ-(V^44lR>hQjDe}0*)1Z`x)k6<`&p^U7p`~H5pT= zv8v;7M)v`*j^b%qbl%IEk+|IptUdh|iRWYmHzUS+GrF9!Rlj>QML^vaKIu@hOogx6 z1Pp$Oi1K?XvqZHYf59P)uVqk$>PaEEQ1!J8K*f>7Mz{_(k)a4u64^gTQJsLjf8#+R zs5r#z2trPMLU?WyhHHqb$UKX8wR0y`0Z_lfocb(dwDWYr@hmu{qjC5sg!a4DfJzR( zHc}CTcXLib2nc!fxhRNSdQHt&ak}q=%L7n~iy(L=z0pXu6qhsQixiLij9S;9WhFd$Bjz@N~iuTZ}}}R-F&LDq6Ld>_-oYL98c3{3&gyS|8gT|t# z3J;YMHIe%!2IohWVt}ZE3eu%Y3;>ZQ&XaJDmQNX+-e`Z<^u{YH;fm1Qh-T!jZh!6j zoDi1{ekQu@CDy6@Veli{wvFXD6lJL>!Tq_oZ#(rchC%@;mE$L2{cb+oxiaYqiv)jB^kDs#S47t<3%)B%Zk{mX5J$`!qgB4&^g0fJa zF@iG5eimN&hzkP4#Lmc`FyNu&3GFgznfAzjL0iLNA&DyVRM7g}sWlV%Kuqsxjy)D8 zI2jm1h+plQ1{qQ?9oi=pO35S+OQZ3}7h7B9PTNv^H*WLUr7_#~puz_oHiwmkNm}KJ zPK%*bmHzw>4!L5OE7X#7v}$vVL6q?ELRSP>V3ZNR)@f<2XOe)+loK z>|CntT}fTXg3&QFCE1Pua2&S%aDx0k4HR6%hBokF;J%&D?ib_t2j3G{jvMaNm1g9( zHyD)6gDyd&*ZhI8Z5SF%Dx~Qe))IqHoTNtC1HIjobL3O$;Z-xNT5=CQtaDa^3a4fdG0bxGmlFn&D$#rpX>HGlkn^t>gl;xHeFe*H?9Do2dw!1l@A4(h~MWMyT{SMS=0 zb}JG`6$&-;M5RZYsH+3{R2tLe)qc7Y*-JGjkb6<_!3_7?twn&i#~LJ(@qEwt-0Ew3 z4LXHqhEN#2ZU;Kct~uBLIgNXx(k3PuNSXec}{m#tP^_6YcNa{?h%oNpj6Y z$l^+(naz4xP5)>lvVkf(R+- zOw90FagjhEcw{kxR{WcLDE)^m^Zj;s%(ToVP-XmQC@*S$>@hVii0;c(IWtFk2A)$I zd5%~NHezIpZ;b;xlb11fX0taDTzG?IFirN4S#NmbC1214dyZHm^AeF7&$RboCs%=c zdFy1WDMwhl%b`~3U(;o4?u^@Z_e(>dELM9zx>^|;8lC`<>;!|5y}&7jMen5;{(wq4 z#_RQHp>8dPh>L-Wh9=~vb_0X?{@=odPz(|VVoU~8>3{j3%hTgLnMSE8tG6sU5op)C#$*}{Fxu6 zclm9iF#nJ{~FZkndrG>FcL=0>xT>49x|tZpytB+<*)xr z!ua6XkHUaS=O_w;1VcVV!LT+@$4?)0J|p=nFiJNq6j;1g^^tU;NN~P=Jvm#_w}|1f zqMVmaPgE0wNHdJ0m}H}4B`T*CQghIQDb#3=_xL4qq4|ddgo3^fMP-7zBgCD)6~+*3 zv5S{R-|3Gf?{O5p7M%od}w4Y9`6)aVxVL^c1-Wp zLVRjW*?;?bBV~4aqQ{}F{`A7(Hk*1k+46{tX{QkQPn6~{PB@F{@*NW;A{t(RMuoPh znp)yiUtgcs;cSIoRr|%rVIb1mhm}_DS9)L>dp&YYQ=FZ&F#23+!bUl=X1X`g%pws4 zOB-P1po6aQ?JazM7+0Q+%sm;Qti+LBo&N^6<(|#$Cz!Z%%m#P$;n)}C+sM(~zU<3q zi7VQytHmA|l3E%tM=JSHCD>SWee^Q5V%IO7rVQfEST3ZroJ4ga9*IAv2)fFN+#AHftRC@$@}McGEaOp80}ricUq<1Mw_Ld z(lNxtFDGrM@S0WnLMI0^-kPs5J^&h|52taM*Df|# zrKDwN>;D95WwqM`OIYh8bitjUue{yUVQpcp>n&AAJ8FM`EL7W8%C=56dIe*dSBxiT z6^YE%6XQrRlq8INtenf~ecQ!o=Je5YkVPO(juS^)X)D%U3OoGW=&i<37WK;$$9a{B z1koFzIw?0j|H;8Hc-UMwonH1{M3=(kJXEjzsbVMX zRh=Bf=DwLkbnb>D!dw`EIs@_EnfT0741%ynzk`U%hGRlxpmRJ?J2q;CKg1_`Jw$!t z_RP`eoZzoQU$qCfhS5P=rpNw*GYPvSZy@B@kwyb&R?-2it6hIjHd#(HSeM%*BHls# z#d%`;0=Eby%+)_t|M|Q}KRdyrgwZ zenWV|QGaSupf!OgL=rW(tHH4tN;fm*i{%%P3&xBmr+(dQ6bq0+>3KJIky3%=Q&Vz* zh8h4y!J3FosSK9Pn=q8hg}^T>oHSp|FEZepmA3uc(kA%hNOxs7|GrUwF}%d_uyl_j z8{G|+tg8)jE7<&IU&)Tz#a1P6GW8FcYFi&1cg9;9h4ibR!0)tW)~-M6blgS-+;Gu4 zpKo;jKlD^5{B+jW`{~msC8=1_m6y5+;m9Xd{2dH1)>uzZk4O}WfUBX33bxnHf;lE8 zCP*?8e~d`L)!IW*Q8Cs!wHoN@e#{CjkHNMjG}o?K1D+Mq>q?}9`_)`eTF$D zGDpGSr=d~hQLw=@D@Y`T>{&f0N9L~T@ZEFFvX|hR^PZH%NNk$cdfPMFp|*T-nU#`_ zUrq>X=wE;8FT>|apDXN5GwZGRy?;3P5-c~19JMyT|M#5g`tOEMqqAConD-kbKR@4a zb0E4gE)KH^i%JTU0?bQ{P9WmbUS~WQQ?FjGQ3Om}>G5=~a=kX!V{D(;+ef#F-Um&4 z|7v@aO9YDR(YLY~=Q{NWE!+8Jcw;-uW4{pb(g&K?9e03E{71p%dO@p5t23Q?jp6GN z*7CIP|N5_z>P&I82g%QPt`=$8_s?XGC#lc}6+xmIOHdd7q_pX4DpBLp*`*ui=Kre} zNJ3X%{I%laxVwLq{r!k%ld8ga*4IMe8@^pf63*$~?M~tmJePRZsn?9q4+WbX-gMIE zZ}XE)n=vFJq?!dX@y%yvXU#x#Q3nSFeGv4xqO-j_+i(Cn0fx5$^W}R%LBSUAZNKZ$ z{2xxM*`E_wf3D0cC&)wr&QW5rHp~~zcrv-#pU`Jx5 z#T)fXKZflTCq18F!`{3!+$0?Jyv1jb5)tWM&p!kqjAWLR`#(?dV{bRl1wW;Tgx6hHQJJP{`ENB_dd+E*>ti6&y%e7fBT!nfo$Y?dxE2xFA?4>;CAvWOW2#=XT96s zVRt+$l2N5VM)&jQ&$<#4zczDobEVJE&p!sm1=zKp4}6v|=^A}}_Iyfss=e2cKYMw0 zd&IR5vkkT`(^Mb%D}=a6_(ONd@x4S^OL1EzrYRo%0iuWmI?B}Z%0z6ENBgvk*nQ{( z4Sd9I1rh|CGU((MJJoF(4EJk$;O{OQCg_8i65>$@>5}DKDwZYg z(OK7S!`S2oxxOSP>5b=Gksx3e^n;+`{s_o_-7j}~)_Vds7V6CD%QY$-A8(fIIy9>E z-vV4Bcybc9G!#!&N28QYe08=VK3{7pr(t5UH3@_uv+teQlPI|n42Ua5fE^CH%~k51 zjHPp3+Waj*VbX6WkcuHrYj8Q7y((79NhaVhd-MN?MBLV4`uh5R(D0c`czs{IcpdM^ z1;1s!A(cEVuE-QztrGnsTd&pPVgK&|i|&Zbnw3O%v4z~rVYYf!oCWa_c?s-D{#X6l ziO>Nr5blZTTMTO}`3o9he6(oJgiySvx>(R8fp|aw+KA9tj3+70#5dNuYV~HT{6@8Eh zv0m`CM@&KK2P{kVD}Nqj^ll|#LaWo2wph=e0KmGT(dG=W7U}~y%u%LOQ=WKeZzi9U zVYeS#TxRCaO0Bw(@ob?Cn}u4$7&4LUN z0RJ*3b;Ds-24ZuC`gOfxjmhv5IwF;IUDgvU_crh(SbQ9>#d8u3JNR(7IpJ2QLng{e zVO4ETq0x6Yoe6V_a~v%F{P+;%*ue|A+Hn^XA#|YS5jf^nEhl2$B;~8a#?Sr?r-fJE z*Ro7^GMQa4%RFERTRVZ}iBa=b5vmyI-2Md)>2Vi};eZx}Bnv@H?MvWcsqZ98Bk?v`D=o762l`sicHN;{VaL zbA34HbbGv%g7f};Mjyeb#cI2|t+n+}n^(XGbax_WqSTsNt zi0ouJSDM1%wll}qnEK=VrN?q7?MQ%+F@a3G&MWY4xjLBFY0g?_iv5}l@0#&QO}+Tj z*{-B_Zi^yZI;C2rJ@EzSpL~C{cFj`Pq6%TD-2YRf|1O!BI#tGg@H`tMj$ zmUo3z8WVH3wde(#eNv9K?avACKt1V^Bxky%5k?zAqDw>-VIsh=N#_aN9E|Nb-=tmt z^Fw;FOr5dha}6aBOh~{}4svjCn6EMrk&%({tFPw-#OZfZl9C(1s2C1J5uQC9R=o=g z4<~%x8perzy?OR7rHwNP2(#_O&-}U{+>WSF$3+j3NZd5Mo(`Id#w->?nKSXlNgH4t zT%<(Ql=|i5%MBOc_qvA%52QOeHi9NQ>xW#m%J|0L+s-Qw2hL+spR4JVs5jT zqg>g#{kuJTs`Uae>_#jPzSQSnNN$c=<^_j@O8*WEp-L9^RXm>BkD|F0FNe=5vH5SqwTU6R>f9FE!eQ#>M4N0u5rEd5K-?7LHE- z@?0Ox1XfqG>4c(_EH~P&$UZ$kfBE+-9GLEiyFj+u+a66x(rL04m6b*Hx}MQ&1mI}& z3NUQFuTo7S;pBtCfKlVVt^%`;vtHvB>l7Ggp~u(5=T)HCj1mLKjURv+PmGpyx} zf_Z(UeSgVreRki)>da3Cb+MvSfL~v58TZndMEb)?U zG+n1kswPm`!D2VLYwe#!EU8dPJz&lL>HY#Q_uKiu{}B+q1$V|5fWFxuFLKGq$wfv- zMlyah9l^Z-YL|?nVh9ecVu41P+HeI>^|Cc9b^QU5IN#z_+#wo>qy^jwE2s558-cJF zZzjJBDuCjh*9Hg8CoA>Z#$WWJh5$Av)C4fO%kN6U7OGu7-Vjt&59h5Rtgh5lf*jym z@o;S51XYK}0^kPasvV4qC!0EE_7#YnsP{bRz*St$f$+lv3IQ_k9w3%1&|<}`6H>u% z)y_sn`6{(0pXdxab!UfeS4EO`>~mJ@4|OD2^?l|`qpCu4lwyA5HjOm&nk65MihfUf z%Eat7DQ8SMgu5WtKGQJ)gP^S?ewcbvsTsq`pTg*sr%-q9T9@iNJ|73k>KoTW678K2-99|{XSh3)zlWN)W|WZI#Z7sN#8FmuK4Sc zx0(Em@#6C7axa1GqHfOK)MQBibM3Eq9LlYBY#ie1PTB>*9gl38!0&m;lQ> zoMovqf9+-EcR89g%k0X@DbZ)vhGe;k^P)UzU z$pdGJ3R47Nsf3b4T$ebF6D77(oYM#RID3CJ5Ct*frVOraUiyKBs|llgcnu#yc3!v2 zHrh(WMC0H9jrl<5RK@Bxi%Z1Q?`t-Pe-hL*G-hiRI!y;^mAdyEVOUfFy}i9`R8$BD zb5;K{yyq)GZSr4gvMc@{TLXwIf!k)$B%9x5#A|cl?Jbbji~!{LG+Ud>)7kanm_1F8;$UnIT{*TAb{{h0kX;Qczu|%T5CF*0Hn#l=4Rfrt1DapO+@N9T5A>V?S0)_ zY2(rD^djVUIlw*M-PJ7feSO(M!J&butJ|FdQby14ciQCE#dNz4OihpDmm#7;)aE3N z+-gMU04}P*Z{;{7Rn|T2Tv&pimBD16_t=J0YTRxK~%7} z@s7Ci;%Y%@4r6^sLk+3q11As}wU!o%g@C5NYA1H_Y|8fPUkq*i z4?!kypsSOWHosvLegMUc8(r@DrT{)yR{ReCz==#+*pyUM|AXXlhs}Ys+oJ`9($Z2) zA|g~kP;|}~a2s#8nqz79yk&VqDoC){Xj4ui_$^LCTDndyg+=u7=J?BaCZA!==Vjn9 zd%F!l;i)d$_?~qLFyDbeW|D%A2EOa*yOUCda!Yk2>UJxh8XxbqT+i8igxkYX5SfWu z^TavOkO5>?mSLtU1e^MOg6g1+m^WR(o7!f?p@Uoe&&(0Vqgq)8+npj7h?f6d3%+X^ z)MO@e9;BUgwA#*ltjSA~-TF)m0zK2wZ=ldiMfSG>5T~(cjbM7S&pS%CZ@Dg9ttv1%3UKFL^Z( z{hrZfZxRllSsNS%0pL$8ItB*o_V%_ZcgC2K@XOt1pOD8D!8w4imwfIY5r%&(xIOl+%RP47y(?3H-3v0?G4Oq*g)aU*|~hbKGA{qw*Cv*&P0 zGT&>gl=IWXzl5Cf*v<9_t}+<+v4w3?39=GnLGhB<2%@`{XttTXY7aaS4oB6&RQfpiYap9L>jfJ{**fTx^eS0O|GYxM}SlFb2fH$hykO z$pK?`2Lft4Dge40-qFFjRf-i!{jW6D^sK2TWVq@ z7V=1xkdiV3Ug)cx-j6R&1o2%-aQEhzAk47NwyHtAW*?r0?(fA~tHTy8*%N=QN%_#n z3Q!6V5#+}UiZvqpt#|bCC9iKUtMORY({$Q%-CVc-`R@y*<0_dZ0{-bJ>kA2vWp3;z!h3h|I`3?Tno16qdd#1M5hGU61eY z0Aax&DA;4GHO7N`XJ_`Stu9ssJhsHR^eS6_08;C%?|rS_MaW^+4P0{_$cJfKT3Uqf zSaj(et`F2@tFPHp3Y_9@!5@#f59b>Htz0J{ja?%|A&I_+M-l$Ye?a?rG*q_-&-c#DM{UyBxI>9 zS)%MFm5eP*imVkODqDpzmTWD`Q^-2TmdIda8^-cp^ZWttAMk$O`6Yc~<~!eW&ULQq zI;T;};imUNo<8DqXTC%stg+>lt8v)9l1B&JE#*7fHz&qU#3eoWyyv2_;_2#br3cnT zzV^LgbN%T+^)wQLiDeDh9~b+%C5(?cGYv7*(iON`seMAdmH6V^Iq+h zm(L?&67<}ve3RMBgljjzV~U$o)SJg!pPiwvFeeYU<(c;1H@7ZgE-aLrzOWQ#=iul^ zn%#j_`3&Xy6g|uykar7MaKOPq_TPLn9`GIxm~z^Bet(7}4&UMa$apb%yzRYE4KRwX$3B%*ABW`!+Y zm|bpv`8R{ftOWpE0Dkm&XxJ{!j4Js|Sb@&Q#>cY*cSA8!tj0&>Mj&3*$cvhQNwWDE z;y<}(w<7Yc9fi`U0fET+cwsTI&yCSyzn)u@wW1{TCD^2V zz>YO{o=o)}9vux2n4_&}NE&I$F`jTKAV09G@byHxV&nx@^HUG(#XgG_c^n)-j z56>Ll%NDrt*t#1wW^m5*CUu|+xW~?Lo&x*EI_<0q1^dBM+dI~Ra z^i~n|hpR(4o)A}8NF-lc$37ijx~(BTMeby&8%1gR_H~ogsu=whMkbFy2IQp>=u9MU z7za$BNYq3O>qsnnzTE3qHtS-)eXjg!!y!NxRd)+~hT<#A%Zq>_8$=PDGBu@PylZ7= zXV-vlS5i>m77z&IlT0snr&f0CG*0g`)?dC&B7yvD$_V`P@k&lhI9_<$4q4ega=?5c zplcr5Exlr4IE4usP z7hrj+yY-N8^b$#3e<9p(B#DHWR^M9Bmj~7N6O^eoD@!(zL6>&xC|qM|T3<>H7E^ca zxN&cXkw2r$rNDIY1+n}13yuXFd)8hAi@dW8)i-d&gbbaWGYz7P(@y>>f#sD=>nT@$ zib=(@^KZS6RryZYDP^AaTr$~BY1-=7aQ3YcPqHL*{Z)P)UJ;z3&BOZKo zF!TcnjHtH@85$aDR&(#A%@!BT9@!ub!4_kpsi}Dl`RsVU*dd%jujQ+uQ2u_f_dFLW zIP=MqLXg5!xu>KVXo1qIs={9`D&q;nzkmO33#REYHE@KRm$zZQH-Ha8SPzG?5WTz5 zLg6a}vQ9*F^m+XGJS#DX%*jdIk(_U2o+S_nh8cQ6rxi9I z|AcU!o|~Yg*r}zQfv=tzpOTUipPHIl3{sFFb0i|md%SD56X}?)DSkHr%04GIFL}Rv zZ$$#2GRH#UL4r&B%crPhW>$9g-LvRp)JkSOH5p8+9d&T%G#s-QB+7T#*`6&IOP)&E zC1+&Vbb%|k@Ji?~`uc0brbtP^my%b2V%c#VVk6$>yD*XyHJEwDGEM8+cLXUG)>H2; z!U?dQ>&Y=O#*1L(%9f>Wx-`G4=0syqmgv~n*eu1WrA-3+e!ma>s|%&)Inh&Y0dIj& z*KssP;;DO2nE+g;NN|-Tyu}dGz7%!$2Il;vJ=i(DQIa@B+5&1K9YrEl<9zm2!ON(d zpLU>ozm5_JWmPm6RVClEQ4+du$zfqf4RFjenVFeq_{240NhBxO@lKVMl}>!v7Vm!T zVs3|tPu_7V4Gf(%i8Mz2t#S*rAn^C+z8#f(nx;?G+an_*!z0jwMxST~A9iNlUz;63 z1)h$f`c~Wd4?Tj=wPBrGKp#nkb2C=mlvRJ4?&t zX#RNbKSRf?iXAp)XJ^aE*kKeP5yTuXEel@uufQxzOPp6+&9#HF_xxKSVrM&-l;DOt z+*^yU0N%^IM^EFoY%;E}^#K+>Ynx?gdhOUND=Rw)6h1_#Llq9fmQh~ME4~G=c%DFr ziiv5gj}(c-n7;R7)Z>;>g7jLnaouT(4^>wezvxI_k!i`pZM}o^5A;+OZDzEEL>d_! zw701Y)O=}87Dc}E3lsqn=FrCbaqFHaM!YvSik7OB)D3EdpP>13ix3h?PEPJ0xq6I% z`s24BXTjO{b!|15AG9TGZT(e2b6F;S0iWf14Y6eie~F&rUg_sM*{eI=W^PmOLk$w4 z2F;&`;py?`{%-T)2Yb%l!KOqbO2u}1_=}fuy3Rx2>0u-84I8R;1LkZ219d3m!A*jK zMOS}*Dns$pddb?nl6pG%hr@WO%g;;b#|Q8lqob3P8Sg-Cb|MlRkxTl-p$~D5ps>Dw zSa;wAM0yY2QDBX1nm~`)#K|kpaQKuUw-L00%w$Xr?gq^{dAYY@;T~KCUAcdAPX)rF zU%+3sqgVVnhIvFFQcw|RrQNFf9{#zy&yhus6POWC6v7P|Srslh(N=nVi@}3$0jL9P?OR31TrcZb?1y2oXhI z4o>b5PTgruF&R>1CFl^6hM2ToonA;`=DyOWmmq$zVo}=$0*)jZd6-m6M&Vb?ETn3>vjc zzrMS>+Y`}wM{)Dv7S~V;!gwYIUh5cRrp0)CbaZqM9IXRu4GXp~MCbfxe|O%CkEh{o zyj0wKb@nPKG+91A6ndDoH6yp?+94LH^&>!rdBT=Yii-;YH*d%@%{O>qQ)NAdb_Z}^ zp&!^t2Pk|N8YjQKz5Nv2Y2fJ7DB;~^b*v1)qT1#YhKBD!<>{DN9x+N$<2f3&2 zrww6x9kjLmk}ci*CoANZLtlxaL-YOi1$cBQAMyV(@kg(4wqextT8 z?Y4Y{!Shn7qeR2TA-ilm5j@UAW&SzTTJC%x3%=Y@K46Mp9!KikIOIUsg%3s-jj@G#q75=ciy!;{Z$M->c zdT=>JbU9>r0RC?VX+w;pzMkX$hjW;O8~`!tNA7z7QFfgzmTk>7;TZm!_7||=RziYM zu6cn(>x*+D07@Q!XBaHW8bHZLJTIH1q-5^tr(FN#+yyp#xSd-_L)kPPzpInb)^{tT z#*~#9`QKp)rS|mn90N7}U{M+9*RWnPFxCVe7aya$pLKjyhYzt)Nl8gj7#Lg>*YJ?q zrlO)!;zoJ9^(+)nM)0CjLzrZ6k_nh(6tw20RNVlz+Am-BWL_WddWgb0yWKE>G&?&> zZhLf)V+^!yWz3Dtxwaw69vfI=@x(b4sog`g;RX6NKIzD5Fhf!3p6z&``&yYK@u z2LWvmUjjkLYnU509Ty*fh5}fm!6P1*)C=?tP4xE!H8R;C4gcq@EsDi=;g6$^_Xxj? zOz`4p&K$CR5Io!i{XI8N9s>&t_$_iiqbZ=akJ_B;;kEUF2~CZB%k&*?+WIR^+eab@ zKbQjqo9+Jv_fs-DPD$n-Q3>EQj|H{gAJMnkCRh^=mml1~q(9GXYi_zFoStOO*-#P} za0oM_mSQa&q6b1o17{c4yt)>ZGKgF{=Q=a;O%B@mnA*b=Is28B+ai6p7J zTbH9M11%AP2Z(Gakof22&f@LC^qTcqI8ZpSbl&jf$vKqy=AJ5|B+B!?G6)5d&U6&01$i&@! z@;+v$@1}|iz8nbSXNUS3(Sk(;2+)}u`NmPj7>INrG-(AdX;{PAtYS`RyQ0(WmDyon zU@!m&mJWJn@z&dkl@)rh1jwvzUIkcMzSxx1zg%1}nh`=W#Ff5_5>@@3l#_GmD~kNe zzJ2?C;63*osjCBB83uN%sDp{CD<~+0dr73l#@g<|2m;c_$16r$Gd=*5lf&pv7J8lo zbyfcsWrft(@EFQ@kNl_q1@=<#kT3fyd#=gBFq~Ea6 z=mYhUWsqpDn5fIxO~8qJBC6{Ass!qWGSN$R@#}1Npzq!l!48Z(7X(MbTPsb&>3h}? z((mFLw%#&N4gmp7O)~qX8%&Tp!!Iz!BF&SP%n8;)kx>%-xT3_MzgOkJd;TD@ z3uQ2XWx+c|*7SXY#8hsGk<2iso`%!j1kPBGnibh5Bf~G?`Tq9CIgHlCA=EG3OAO*c zS*-V;pTM5UxWCkn9h@x5NDZ8hzVndquN(^;wF}3F>9`1LvW628(jNWobMn(c4k^DA^ii+yvSLE$v zv7|CItq7*HwjpH_owbp?fMK%DF` zuknBw+?qO_1lVFR!$R~_OWebgfSHl0dg_!v$meG0R`*tzJPcIr&enYMNl42dgBT{& zVXIUt7!@{!N{u1oIR>F)8>*(iYM0)Dm-P)>gY|f!f{$AQI*FAvM0R`bynmnl>A%Ax zMh6pa-f=QDDU|eI-VDzZ>9a>yRn_T?6^Ni2%wrpTOJ!GhIym{cpszzVpr zcb%M^7_;p*nO`n`?tze}p}!#@u*BrxA#_Y9P|L1r6bYNd=_7oa-QnTkSr$chtaS-( zxDbop;EH^o6XXP~L9kUpypIQV5(^IMJp(&TX}1m--zsC-oS`2Bdx&KK?0d2M33|($e|+DF3|&2$_PpAqVxOZXf6@c4RiEYPN)v zSdz!;-SsQZFh$+fom($|qxaW}twThSSeH4`y!Q+WlsaG)%q;=yNqh z)@KY}N{RIxb^cNz49#ZP1ML(F<){zdJ0n@ucF*%!GcvYiepb7ZMVZ-wqntS%rZ{prL3%M+*)8mY_suO`{tnDw{w*N z%hn;!tCTO6Y+Y&jn!(A*xtfmqVs3pHg|GjY2XTL0?xLy^9v}D&RpxfL;@q6ub1`eyn%Qg5%-(yR-7vQ;jF}nu82|tPGu#Ab4FCXB zE(b3??d8hT2mgiv09P&HFnycQoUPfgccMX2J$py}a|w*{JvCQ>fx>#ft--OyXc8Ru z`+nkA*kUg4zNmEGqp=sf97DV+S)p;gRL>c%z+u!>Kvpo!O5?XtlDlQP`_SOEdFe8b zciR<|11oK%oyqNV1*rQn<2s|r?2@JPp2f?su1i%|n zF{qv>9R7bx|7%GYJr193#%tpb@$9tRCkHvmEaKbel}ixNhWT!ewaWJ**dkNUq0JFqN8BNWY4&nIB#p(1X) zn$8~_Ty)Tdz||K~rrGRmjtos=RcpUacBHJ{y~mWp@PN^$crW}TE*n`7Q~OSb$=}<& zU-{@U(Uf}s8eoVn9mF~oZfMOM&ITca6sVj4GgNIf#0S5^95CQ8l`2kkK~O=yJV?HT zEb=ks1V6gbL0@7a1yrO{XIH0HN71K5-iW5nW$sr1IRQ7Rt};AG0<~k6r2^R@5^T2) z-PUIRJ?h5C64ZO8@U}#THwEzbsi+M#P!GrKoeWUEcuRhTxCgG_)FXJXkf^HwPc@Q| zO9=rTag6$YN9~Pl?vJU6c>*hGf+44A3gRD+OtJCymlQjo#!*LaHxWBvh55v^%cIXsL9!i*2YrSV(5#nZc z(R6V=40CL?w6ML-sXKZdnSj3OOUWU7zc|*2VGQ*gm{iragxu>B{HpPl3 zwtz(Qqk?$+<$|5zBL!BT$bPD%?-j0m4LXQ{F5 zwpRKIA6ixYhrydne3DArwic-w#@IU|hE={YUVr^pIF9L+s28?0f;;_kQv4r)727g_ zGHF+2&|)JB5R)V^wK+uRWnYAYJ3Pt-S?u5kqA(HDA1^(+3km^Df>nM*!|sbcmjL$o7+|JwGu6Z@JxkJQv>7MS!G7;&lljfn9{F9g#p0I9EGnwa*JM#E0LA zl8Pv&^%t+|q3+paj7)e`{<|ldhFJdPq!HL*A3oQ~@m~2&577C05?P2%v|ahyv3W3M zhl=j+@!80aXJE&6#V^D!Ih|B@&HZN-Fcn2RX_W0y(l;)r#aVn=ZBIV+oVm@jyxuO@ zhD2w2t4xlwH60W9pl|NATuPY00A1A%s6t|{9i)|q@Lu%DGxR}eF`R+b8STfHHEK;K zYuW~?u{R$gi|x{Hzrc$lm2wl+u#@L%FSEDRLzMKK*bFE);DA} z|A>Ue#t%daFXC`q3*nr3SLf&Q2DZh85!PNq*WFnZESIu~iZ#p6r#ma?-V;E{6%Hkf zUTU`V&EMoV*y_$s#BEvswtM82}e@{ZhD8~^1s=A znzXkJx+eTwOqZgSHCg-EQwU1?R3`aJ;tc)O?9e%BAJn7iXH2kJ>BL0j{vN$Q zzrV%WjuV5H(q8=}hg^~b$1LI~qoYBWO+G>_&%s*Y($a17Q1w}ll}hE^d;+@8SAAy? z%)>PrLQO4ykBmVtLjs)Oa=eiB2^~)0Q;yx+aVuJQqP6JH+ppgTF4Do8kLYTLLjFDR zxPxu^4QXkqe}U`om9r@e*Sz-5@idZoxLswA1;#ZOhA+goa}7paw=xx*CrZ12%qU=B z0tdOVR6SeU&n}suntChwD=#7i1NvQpH%2iNHp`@e)(vl0@1~B?1-q6AEN#o6xqDj} zh9ac9@nnW9KhIV*HdUEN9gV%At)PUO9^IZNK7}1ny|KhUfYUA;0X^MJdsb{Ky1MN9 z_jK^A9jlexJIJB?yi7!-hX1YZ75IN_*f$OIn|nIMSrF8gkE35`$J=Fwb`>>qk_##e z?jze6)KwzRI9&LGUExt~$YKGq^;rI66-od-RC8T`lW}{Sc8>ncGId23$nBY&Ol$G) ziOu{I^nqB6WnkDWd~BfQ(^d#XDl>LC*cO!s>kfthHotBrsH(o-B*P-(M><@3=+A zVVH5mUtKV7k|Lt}_&c9~_?9d!vY?E4X}16^AjZc%vn<1)Z4_xUK9)QnoiK8WM+_W+kK7;-w6j3xm^ zN=h{`x!1A>a2FvfX=HwynRI*Jk?*T0{{HobbIMRckt~V!BEJ+ z#z~NGcf%|zj~h@ata{CXIWp;&T$~bPC^R5uhCXNZ*S@DUr~J9txEY%YveGj-13+gP zcsth(GXF2}>t9iyI1~QY+NjQcp?QYLwy)k^o~r()hsWV0J3+0G7sRK(wqRGXqL9h% zD?UZQGrB5O&7lzt%euH;um}S>3Vdvg8#^?^bks@<375W8Mc+bR3iZ4}+v-=CP*o1` z9}n+bp0Tfo_-YtmriH_`S~MQHiX#K9UjKcoXozM#*ElGT^WziaMZ*eJ2g1*fgvBhpIRxC3?}=y7X&7i<448A-&kxK zD}SJ*9HXLAi7u-e1l<||G#|4k_~2>7lV=@%9JfT#9$Dp=kE!7ewLCo5A$6%ZnQ<*` zmzAx3;y=*B9aQs$^6Y)3gp)=I+NXXpIUn?2Z2sBYztUP=+Mvg6wMp{>Lgf|-7=V0L zZFtxdnSJ=}?Cs{U1MiH7-e{a$3Z70Ik-uGa4 z(_%)v9IWZ#m~6#7N~~8h@S)9!$#>g6$*t4Vv55Q!aACR`pxv?K&$w%faJ7hir@bpJ zVxOgvusZKRb(Q!_1#ZpN3MleSj$uBtO`Npu_Q54wLo|^DF4ZbQI>+Bcocc}ii#+gP zFMlIF*hiU(3wVF$z#~c$`a|1*x{D57^{zJ@x0V)j-UYj__Ka=!_94+%7T&I!{H`PX zM!+3Mqq>jr_vWI;A>59YpN`wC5Dnv=s7a7o68ZEl=_*N?)QOa6&~OGFJLAYcU#9GB zTdF7;5x;16^Xq)A^!l&b+uc06Ru?wtiSB4=ML!W47YKGeNOrVO?a_gieE^>2@ZR3O ztJ_*-gx*`Gg*qG@MTBm1UWc*T#5U!hEju|C(kRB-|H&UpIc3=jnZFM_s|%d5zVGK0 zq&l2fuQ@zjEnsG?;KO*v21RzfS{8WhQY}Ww3=y6+msRzeDv?n%;2j#4E z3r-YGf(tnV!)Lc66&3Y@w~Hb`vL^8mWT)rLLvQJdL`S=|RF_is9*b?NS;^`7zNb%_ zf;l^s1qrQi%-Sn-0p5(Y3u5F#Jvk04D5zgf%Jc!rGYL&QI;O8D>uOSXjiUj_e7iJ= z%x(&&Ump)!7r2rAPI$7&-QrH_%Lt%t%;H0!oCJ3ELm721kW-Bk1lxKiz3t)iqb}Pu zNEO=dONaPu+8V$Of4roJyKe+)NL5K zlSb(ELrypND+@WEp~>^yBr2TU$G0yrQr{%NabC}qF!$`8C0-eB8}-8?n4z*(PHFZ* zabyK__d@5|*HU)K()e%N(}kyDC8;x1(;3nl8752PKXu^4I>a^X5*ITc$^!wYia(Ef<*`7{ce|;V{Gx~YsckG%3V84{wB#gO-wlpyY z@7Cqf$}se;wYq^O4|SQR-E7pfn&DblKK}gwulxBKx;`LxY@J`sqsm~|{YV*cBT6LO ztcOy!;GNc5%?(iSQBjg^^qMiet3p}stp4&W+DJK-MJy{|zOeSLrL87M_P5QMZ9LpZ z33@x7t}#xUyaGTVgT?OnF@)QE zrexyIf-fTDGYA9ySm00vcqU9eB>82u3hoMXJUa`crs!~nV4#%&$ zI#c8)Iqf{N1A*bTfc(n$_cxYA5Jg{j*cKt@^eK}ZQK)^g|F)|;^6x}2%uyJ9Pd+sO zh!x@XoV^*l6nAH3%qDouOPKu9Cjzs_A7O>Bm#9h4z?JXZWX zW%iWhFCy}a4bv`IxIxa>!>uZ=w>S@q-Iwb6sBhltXgqVl0;06Wt_(?h+%`DNsqlU} zL%%D!K$(Hu56GPpU>Tycu}o(uo&XGkX60=BzFdCyhQ6VT$Z55m5Ygx2m4_SVpSJD$ z3Swm4vB4GRC`+)u@^G|48K&bR2EqW;c^T?Cw>hvOiwOFwPZ*2{`&Kx>c~t(V=L5bQ zFSF{~eoxmlvtNR$Qs1-uxfLQPl%hR1u|VX*J4i(vaY`6SO zZC`+0kbl&5&II~Ks`+9?QV6qKwmRLN^2}&(w{BW%NrP9E5QGPM7L|^@FCcc>ryxS> zWZ6&gW!#JZvqd2GD4~+Uj0-ofB-g}ZoRTAHN2UncE9Qu{eGH*#{T^W&6WRZZ12+CT zh<42w2d^1dQzP8#@BbC}`VKfl*( z7*noGQi&?u8R_!_S`G@WjE0HbvS{USjJz;zc$~SabIOWqUD8A7{`nd4_kNLk@{Y$s zF6}k3=bKA}PZc3nvL7ANv{%AOh1yMdzMmw%dU;rVGV${~1DrD7O8V~Uyk$N!>Tx=13^Dj*=Oy+M?a*y zSynjYG|&LAi8z%Ga%pNnIvdVVId)Z5OIO#GE5FdBGz!i6c$^^&)O|B$hwb9sbYe^n zjJlVGv(`cMmQXAExNoP)p8Qb{L33@sC}`5OR9muiaL&+^oTF<#^OHsRzUT%fgwkmo zxOL__R8i9+2eX?yx7qm0-$@lEIN%U;ofDO|lQ@EDBd;THp>9ind(4q&5o#Uy{`vR( zuSDj8IeqeWag>xtbxvaJ%E&`S11584gE}2Z%S+0OcS+N0S@EUI99-_KkS{KL(7fT2 zdIE31Fo81$y`44022@&ICkbHWeD4@QLuyN1ou3b&sa zf0^mDGEZ$y<874Ge1({P1PT=#?|YMDarQ$M+^K#LKN?Ez@oIBrslK6>Uz5M%T0I1) z*n$%@f3<0TG;J;jUS^-Sbo%bc2)*MK`T2A7wu!o_Y_Sjcb7)|K+X|S2hMyp?lia8t ztO|`)z0h_vdwucS*}Eb1*$ZB`z9Rh#591z-@A7T5#~GX5$s(rI4|1YFli*p~1T-X+ z1FBuohrp*|J}#aRSj|UvBd%6|B<&L}=IaS{#KqN^lax4uMXV-Ug1WBdL3dxdmg#0m zfOUN61j(Rd8hH{R#6 z0y&dV!|_C3yHo!RbgEcwU%dWm<{!yJOWiAsl84yK$S!kRE=3U8`Qf-TMgrR9bvw^! zc)b~CSy{~i-BP;{+JUqL{M#u9Mw5<1*UkpEix|;W&9qI}31Kw@*~+HpO)EMkbrpjU zX{2&3t6|v^v~w{H@a>6le`mBRlo7J@GL&}s?|oxG)mFRDfVKQJen=0y8m{rqjAk?( zY8wI$7H=69AlRGrwU?DX!9eGq-rlx&4LnlP`;%@N+oaY}+_O^o_(ieN2*hLbZCP!I zqeh?#H00D8_i8)-#j_j}ChlCb7zPQ`_3}*4Lc?hD{;KtYzoCsR!lrM2zkN8gvFKec z%+1%KMvhc96fB9JHsySN#x=};?-Cd~q;^ozz{R_plaAr1^si9I&-@SHDsLKLE{gK0 z^9(niPQo>IeYf=Mq9OM|u|zL_!Cfmv)XxA|f;ZT4O8|4Ps;h^=~q_~d%~4*>OOul*si zlG%-JX!6GAz)k6HPV(A|P5wb)58dLJJOlIa;zh@Po7%U;9gm~S3^0qYkl3}Nig+?@ zGXa^XKg)SUlVe(K8U;3zYN_lPpgvl?3HojSN)YI%MZ6YqhbcU&Zjdo^p`qu%EVc^6 zyVE3SZ&6=LN_BLa{vC*0I#8#r9TRr0?~vGwUxUut-S*>|kb)hJKgID)SBzn9HZmkw zvwY$IB&03L3d#VfO+*QUtSb^#k|~4gGBP2Tg-?nnF4n{PQ`KgHv|FPQhSJ6ds|icp zNM>alm^UQsOhP}-d4bDYn+a!Ri$wf5|*JHCM$Ayp^jQ>-!h0TUiA;X%!<`O zFl(7w$%e|gq|?4vthH8jP)5TA#^SY)WXhTh07g%rz`kRNKByv6x9@oWH8yKyH~5$qDW3c?EHLg^XSx5=RMV-^Xw@y zPC(3(e#fXvV+6o^{XyrL08+_}yCL^PLr#QqYkK-DIu8l(@oyZ)IIMeLmtSK=+`Wr5 z@8va1Ow6zB{$~Y|X67$$hy<85X<}jkU}8lg{ojIE>DKqFwf^j9q#>2Im4<}NBo&iZ zhN~5mp%_{L>OfW8qB1Y8&f zj!FSY0#*S~y)G?I_ucsG8|lrNZ*$D71ev`hb4gaaB^u6$1c9FTc4%2!w|qsK>BiNL(u2%U26>(pFJP~prk#uGc-#OB7j?sd@+4pqa7P3vRsa;zi%JX_4dBgn!1|Gf?^m`zCrY`+ z8+6`)MlKkXOpHE?*AxyIzzA)P4xIS^d?T|IvH=G`YBR*zh3}TGF%(;M+x}(Yn81vlLk{`yV0Lz%Q~>6F|> zPkn1h)rQHD+;G$>He42(NfR{h>Dr_H;J46v$AdD1K+I%Q#;wPcDgL&$7|6_54n0GI z#_X$rczAg1Js{jZRyIp`ZYf2m0t)_OHrNEj10nM6(VQ7{<4a0Ou z_@l~+$Ng5kT1Seuh?|Z+lI_@y1dI&;zE;bqu@smTCRE&U`G83kLqi9BbaFqKF)r|1 zt|ab$A<1L`$vBhA!^5L%VK8YDWQ9e_aJcTQ2e+EX{jX~Hw0h?`0-7YSN&p3|F^shY zyrOeT=9FG?*T6M7{RcwIA_pQ%xJgqtZ6^VtnV11mX;&EoX?I$y>nyDVU*@$GBwCkJ zM*DvNVt_$JV5I<+B-lxx1{X<)BA4Vaj@3?cfBa%CYByYOzV+8!YvbaBE#;ibzvqE9VqTGl_nFho!ec-8(!B#KYqNg~3E1sU+E1Eq2X? z_ifId`d-5c0f&$A1NkxF@=?HptAL0J{1kymxt@L1QhnsbA(cuDphQcdU`?99v|pkH zor9UB5I#-8P*NGP&RoAZ=B#;h`?&X?C|?N_n84C3puY=10adZEeEs=L2EOvj`Tb2J z#ib-PcZWri$rgwwARZnE7z`$DN?3+SnTofTtWU1^-Ia@f>0?}Eq%&pV0&D~S7zO&) z%3D4z5Dt|=8vV&?DrVO)0iiil77w(FiD77FSWHHRSU;~yK7GS%a#I%*Fgbv%fsUUf{8>T+l&of9gm&$?~2sd-mZNf z0B!{;0)QC>k_5a?f$S%7?#-)K4;Xinjy4a17CsQO25W#+rq%Efkz%DlOnyRYBoCi9 z%=yj7tFdCvcjU9bm}Dhl-%x;S0Iz{=avuRAO*!U_|9p4ohwq$O>=zgg37(t@V*0gO<<@gsp1Yk&a)xK8vN^s@WSzt?i8Xb2}7 zN{N+(X3cvDc4sOC5-beOp-8p4c~LdL`at!lt&7%g0fH&u^=#nCB(Qbs(BLC~xP9Ex z+G1b7KvMa*i!-(|*#hwd#KWTx!^{E#(4fjIe!n62(6?((_h}qw0Hz;6GjQ8T;Ab0e zT%2nOXCzpaX**URG{D&Vg1bzHKmsIKN?*Mt&E@7_E=h@+wGW%X5p5MQitxb;7Y_XM zQ&$cO_$@93cisAv$pX^n>gzl_JkoIv#}t&v_17vP-#qris;7^5v-Xr+joAiZ+CVf4 z{FO%>@%-way?sQ=sy!R2mP2%^rGPZ+14TT5v`{iDR&Juuu%gH6Z{B~9KJV766>zl! zZ1e$O#=#dpTCt-1n1N4PA-T*72$#VJe1kqO2IAq-$52EfmSeOW@kV{>fa8D9A)B#>qgD3uzFZ)N5I5kq+sT@J1n6=td1$#{G4lCkhvkQ*tEeOCzL_i_UJS&;BZFbVl*}@$R8THXMbzV`KefFq zw^ckWpfW0qU%99M{BHlW(@r*0sUz4*_t<>PangwTDH_rV~NqL?P zY?c7>llr7Jc7c``n2PF?)U->o~jsM-B$GClS08UnVh z8!+Xo*7?6J5|R87vZ*q{tQ8S?F5gZijZ>X#9^Z_&{`o!goxc{206qYK7pKlG z{P@tElHcDkVY}aQB2w?a>h<1&7X$I|$PidFu@oYhR4td?xgq=D_vT|P1U-rxLXQh6X@ z=j@G$crg$U50Cu{gNY5|M2`9Cb=R-hGWGrXU;BYE8h{17FN#NA!9RU*7{}{Qgq~9#P!EAC%&GGha zE0(*!CY<} zVFD}{_@P@o;ym}pkKW{=ha8MpeG#+v+`8QiNz#&u(7gR<=MS5gxWH8|U}=D+EofY6 z2gJKfr1wTdgwDKvV_p#5!=o3%#3GU9i_d;ytNz+wwp`viqXRMECjf4s-+xL@G#Ms4 zHbiJwGYQY?=pmSxr6xjc`O)-;wR4>xj{J@WVr9V7IP?P?^6?RoV2eIdxQa;ht-!ix zfp~cIJ`CHC^*{gKvVNESY4fkb4j;E~wcz3ZO zLsK~w3wi~Cy&nc^Z6|X0zMP)yc{(P7i(HT_%w#U^k{9GG4H$E^c7GVm0UiVkP5C@`ZS2v^7I2Fahi zq?Hgi1AqYXPyhGUpyQ(o%_MjJ9oy$wARZnbdjT^G7R3FktTnfPebKq4)%KMH1Z*Jc z3gai%RsX5ys&ywzxDn6l05oeNCXxV566_bGclBkG;4iGqJp=)++fX%0Uw;3G7Vvuu zID1lD|G{P7JQ8qraAheCsWUH#?xpee6@*Uj280F~tpjd^W@0e25CFhh5L_aFSZV4| z>uHb9!$cyPC8L%UAKp8A-fBN^ya6Bql&hk$pYac$X_77V<5^ockkfOChy-gg1?iAU zNxu|!b;|W^kEu|LtNf0FOuB4JA{{W;4NBqW1x2b~0I7&i=%ma3ltYFBpml4eRCm7z z?FtkQ85e(;Z<_LTiUYvwN4(Q;+0)auBqGM}w$ZpOz?AInv+F#bfMha4Lk3(erE6Ku zVQIxeoT$xqqnk@ZR!h+5CVWAyi44D!FoL!fDs5=a9#U(Yk$5nW9|ZuPw#|TY1Uxb6 z9+W9aGdKL?+LfWh7Bp3r0`@B4R|;sv|{u>TFY1=lf~<*gRv|nL`3w z4l6b&;+KIIOJ_N(sXHt68LU|&$G`TiL$+Yx$0<}?kOZ!|_Qxw;{@H&H-WqOkBHeW^ z`vXrvG7#yvH3>M$+H7ojb$njK>cQipTZ@LcNpmP`Sp+Qrh6WST6G~gPfmlFt3kki^ zjKtRDjjG&KI%CF+W&7vpCYB|h^HU#+qby*QuM10cH>{Q*iGU?pE+@9EIt5d<41i%kaGnh64#v;

R?&tj0B|4fn_-t?vN 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 L)#S=vnFReGm>HP{ diff --git a/_static/thumbs/qgan3.png b/_static/thumbs/qgan3.png deleted file mode 100644 index 0985937bed3491990a6ee5ed6d206c5bbbb250da..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19379 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�kO`BQBAMvLa?-U2ICjY zaimzg0f=x1Jmw&hLjL~G<6GiQyuD&?#vQ8iqf%3SrcpJ3<1H>O&Vu`NQkB1f1cSh7 z9VcFur@YW+QX>ft-FF)MJYHe<*S%`y#iz+DV6w97Rz!d+GHi`NSsiVN`w=~_Bc72-qoLBq;>Yi-$R{Ffxgre;!ON2=AEUbd7Sxk>k&-6%o(R?r5Gb}pmJPZXHLfrX{Z?ZJ5ZpD zeXhN$oYoqkX-;`ysB<{Io&Ug8sKtJ9x$R>CD2-jK5UB`kRjzy)+`>NEn(9>Gtd#~E zv%R<16(fyW)MY32`XUI*7%$j7Kb2ED04@u?Jz$0vR7??x87uyeGR1NlgpJuQ1GE=g zpfh&n!zoj(dDi%KOVH=Gh32}D7i+1bv8x-2Ew0WH5XA{RRvH9-L0t-CyWN#pU48<>``_r>m0#%Es?-Q0v`yw4xNxmzgim1u0u zNK4B6`qdYNjUlT*o;*Ppe|zwI&1nxz>|a2PYHDhnI#EME zc$bgCyf`5`niwvn3t3>ntFHuW{dBjLR=eI?92FfG>HLCQsmMsw8b)mb*ihH|bP2go zuG8+wlZJF1ufWPKetT%`8mpKIawJ@%c~5dX2qnQEKS~es5zEZwS8o{uMMc8r(NQh$ zt5b`uiUs`Jw{Mg4TN4|2pE7`rxSZlTjr5yq_Bzs8F;J>`%)7{urZuwCfawVVfz_(8X5%^F)KFF>u1-v# zq>h%`(*kn4SmN~gmYa(k*SVyBaFc|pKQp@{Q|0?AcyC^32hgi1LYt*@b??x6pU|Hl zZzFBuiwjR!^g^9HHxG~M{T0xDcA<9_+s{aRP19JwD#qedvUkjC2qZXc#3iY8HbvJw zRNrFTfdwTZCzplu0ljQ~=!F3T!1{&;u%B56W;d#oWo0ozT0=I~-Gx@50!W{{0#<*j zpxkDp$ICmY5j@GQEEm#HG#M4<2M?Bjn=W^9*58Nz31SEAwg+Ynz#^c=-G$wYytwe) zXeO*V@Y6A@T-|hfAYx~y5C3IhhN&GnBH{rk3_`8EJ^sp zap(mH2hSF@(wH;^U~N6%sjaEO#KbfNmX55a{usAzkUWSE4#os}*$f;7%xjEU&>xo) zEWTtps0z{tWO889@e|Y1K7;k1toCqscXt3f1CDHC7_}%lxA9FNu)u@BuvPH%6oXA+ zw71xPpMe1h+#En|iHwhLgOx%GCvXV2_RFn@LWJKeS40&}0Vjv;9|EG)R{gbiNN6a4 zL$jt}LTH4PGAi*rX4w7x{TF8&1<-h-fo>qB#Y91SWiYj|ZpGntN(GN+fME9N1!>0X z8E1!^CvE6b8URBf=|MnA`Bt7I2|yH_MS-MGpx#P7e%#*O-E7K32;0oTfgAV|RQ-9! zO0c0Fw}R-Lt5sY063B!=xA}3r0^m+K zIngmOb1N&Z_?#OI&o|GnZ{_(Nd8L3!N`k#9 zJgS$@cWFw4ZayR*@!*b3rO+0YW2o-U(*!gAasp>*Fh61$YahF;IdsWyrc2skiil`N z#ZbK!ll=N1_gjt{CuzQXMpyoui5Tvir|l1&>8+D4G&TMh@1>c&wop%BTft^4TS=q( z!pUlD9U`w+Q^_8a5`CK7nhSVi7$LsH8I zGAdeB5us|lm^(#%1ttUzgwrct`cnuc!9I=#2NEU02>z^jQ}B%Dq_Ai|B^XKi79_az zOO(cr^0xI#bz;)~{Al$jK;hnQY0C8n|C(;;lb1!h0UMIZiGBqdXNcx&Swt2(D)smF z-iCKAH0Ym*T#rjjwN+>E|KM(rC~v!zmQO6_p(Z+br557M8$4E`kc@C0q{SgFeR@OI znQv>u?of|8^?Txbms|OVZ{D}ta?npMmVd);k-N58PrPCxkm6(f@8T#>D+wh03DR~W z2#c(_Cg~TY+=BPb$kD-t0KM_e#fii56(?e8Y3bs){(1=jEKTY)bSgst|3ebqOC2$} z2KCbmdjUJqHzU?9S8eK=!k#b)3dSYb*Ci0%d%|V?m)WlNP!oC&s-%hvAu>D#Y#V+E zC|pbMgEEhH0iVx9v?R|#7@`r#6cg;?p_5uEUne7ttY3!?Mj!q7E_?gPfo1K$`yAjM zL?k55=iBwYpoeRf*;0TDfJ7^Ba>IMCSHFiFm+ItrT_g*?QJ%Q{RRjzMH-Hosi=P#C zjwG)6dFJQU%n(2prxNO)oAy4=3%~D)o|*jtzx!<70x$b~FCde?k!$Dh&}_280hDVl z&y&~4R2MiZ6`N%fvd^9|@$nJc+uIKp7(4@|7pm4zfR+fPdI_!rQm@h}x9c9xdkOg$ zNOna%@<&&JRu9@06n|i-L=+V5XNMa|y#?NHdb+o?TGf+!b-AAb~N|JxWTc&bAD{uZq=G+OOCPhnU{q(F#_*Z}WxTx=usKAFR)sja1e zVAG#Je*j{<2Knb$LI4QjA+zBJoM$m8P3fXz@n_;y-~K9)zCdGkCM8@Z`eY#cJSFah z_yERz<(1b$h~SWr`4F~zBz#c(B9`EDxnWRqFra{p%d8D%5K&Qe!taX*t_ZBl0)!{X z-4t2&-<3FBBm&V)d^le(w7gspWHrzY-~0L^%)8?M+zS_z0!IX zBE^3ewVykX4w?r!<$Jh9NBz|yCj_h@yO@yB4k$pm-un`H0|9M+{qZA!v+m^G>HZ3` zgJa|1AlWV8kDuDb<`-b1K;ltNOREh6c&D%rkkAl~EZ-GYu-{6A7gb~Z%JkOKQ>1&+DW5&e7j z?x6-X{(;Ti{-AskiDOSe?qX(R`(9I%3~lS`eCGN##H%`D*rr}mt}HD%0Qp0bbw=gn zw&m_5Sb`n8KrgtSqF0b?_9aYcY`-stu3hg_8MM7rvTGr*sCKC9cHlsIiqrvOL>2@SwkoX4!Lc%<MTuH`|ZfutM*3kz1}Xwe=L_kfNtFrW(ZP@-mJLgn5y5fM@4*?JBZ9v)Is zfabjiHUJMCH6%f(a9EhEeua%PL@h1Ix~;#lb~l^Ku{Pgq&v`iS;K1pfzyFj+Z?!H* zOBgl$?28777WM!FZO0Rw^bHRW_pn)rN_n*bT>fLTvBP!cuZ=ca0TY4Ls|_2Tgh^)z zTxMp60Mk0?dkjE35xLU9#9FvTapaY>DE0BJicevB+Q3e znnSEBhAc`Kw{4RT3{*r}IU7KK1arn=Ddq+2mG?#vY6La~f@mCYmuR1>{Zw6-F*Bh% z_^?^0Rv?lmqtY{0>*kpAQ)DWFi5A2~Vb2q;hnELLw4OUCAUiH)=j1yU==D2KQIi8& z(AL)8IySekfTFR+@uEtynO0*vwgK^7g4X%sn~h>Yb&xT0yb@zBDKBX#MO1bgxwE?d z_jBVUj=0WdfTl&Hth+HbkvTmolchH@=IJ>q=0|)h^`Ri;NNV`Ew;! zQNb; zXx`M)a)V?6A2OR_uf|Kh0ilFAFkV!96D+ifMhv*WzaWAMdb|`8f8ydU0>5TrW^VJk zny{E6-)qa1dY)Fx?%yvu)cn=(Gw6Cz`kwOK}xqR}^akl;(53(;6 z^jL9&G9y>i&(%rLgbH~nqS_TbgGg7;Dz~%QwxX$eVJ}FyZ|eDWo5wWa$<>^OU#rHe zN!dyfM?Pri*QI1R5BqgiY{2zWaZ<$Q?h}%k7tVU*M3Gb&Kv{qz_nsue%7Eg!l?pY_ zdz?E(#2L9zTd731Z!5dk@)#mAl5^m?wjN@jqYsqYyg)#8n(RGZ?QgtXf#QqwSHBud zdV)=sF~q>bvlqS6*w6qe>I0Tmq*bAMi=I`(hcwpCklCXCCQAr#^Z6(r&IK)b#_*o{ z@QN~SXw!SQeCW8CeTP#P`gAqQr6@H}f5dLU!LQUlWj{N{#(pDD?tLlQGAWikTPW$I z$Mj%Hh;8}Ww{fc}b92n{)0zi~6rVdkC2H0>%`h@ToKaGkti)DMG{QmJ$n`q9x=&Z9 zlx&f|&SPnu>RLq!dt!L&u9+*P*Lzp9f!c+gU+&**M6#{#O3=sFUzyx(MoS<1x0l1A z-FY`4Q*S7F^s?T{bLH*o`>-+NG?{bRvixbeNBSt4As*?r&OJuz%FWrR_;EcK+XE1t zDjDC-8ZxcG7P_NcmNypIp3AfTzO(3b^mQelQ=_CHt}X{2CoPpx_v8B%1rV;waY1Bl zF?1{qtPa$RGX$Fb8!aS@l3Vh574p7zQoD=Q;8oXbSe(o5(tE{hL5Wws-$ptfYkZ|E z_ia2M2(}`U>n!Vb5Ki$8d*_#?F2?2Lq?A+YI2Hcn&J)G#cZz$2X;xV-WTdMay57+t zV>*dX)H}`ZJB3?p#wD!^)=-hWKu1Nbr9@rq(5^1TMaBOa^V#5^blzI?6O=e~GnY>@ zg9`D7JrZUA*k-@j%!8gRyz&wc_2YO&MHcp{Hrk>ENq?P%jcuJiiJCLscjNp^xu9W} zvcYl8PHi;hj(<8SiYrc4P9dFq_upgoJE`CERaeZ(nPdOtrK>rmc_ASG~@DJ+x6?{%5)+LfXnxE4oTVpMH zDAcLZckGisWO7mY)c!&v3X-1R_SzElT98i45>QQ8RsZ-#eORUyf%Xp{4;mD>OK`Jy>w&NBQi~A{M>jKYjSjN zl<5Piu@t45SFNoxNxuoMDb;iA&elQq^-FoYHbNh9MUA@g<0GWY3*TyZE`{+hy+^6~ zNkmG~HPt))(T@Div+l*(vCxfLyPDw_zfNGbWHx`~meSqf>;7$=U-4ekhjRM^ z$_^_nQ;ULbVrW|h*SK?dMopjlysB3Bo4-1(-C4welo;V5nlZ|+Oh5eP|1?{ONYe>; z;Kipn))r%)dBYl#dP&#;>)z#8XTY1qU zr-#bgarbFfd|1|oNVazROlE$jd8sjbUE+$BqHD)W_0jxdP(IbDr>zskD9zJ^P}+wt z!a`l(W6 zi5n#~i1WXQd|ck`evQ-oD8qMUm* zPd5BuO8&Vt;@*`Qrh$(E?&Eucytnjn{#9jnHR``)5G>m+orA36XjQ$jm^}JUV5W}w zmlT3)YxD^0mo7_&QN(B`;ydDXwg4v?rj}9acu`AiDQ$HX{t#x@H zu9x^kF^M@pktbS))3ZCy{-SNm0H0ux@$()J28Q<~b$9w4k>Q*2aclqkYGTE~1f236 z{2?8>H&4_>%QFec@=ETAHhO5`%_Oh zWT(9Y7B`iwOczC9EK4PD-Z2GI*W7a?S(iDN zRNqHTLJnJoK4_e;P2!;vM>UX);H8<&l--$(l=5~V)a1nBes$Msh=tU^hgP>VUQuP7 z*1}ws~oG)O! z*Do4;yI)~0T^=S@MDs_e|1x95kx@T_JEC}FbRc}_gYd_7qTxOb!ZekM8qU-T%p_4N z89dE2o4+GynG!EWHL~KBJl6I9mb$ydg@#CGxGS(ZWvNqp^*%9P}BA zt_7N8v5gu}B8w4Gs>j9ZxVU`X{lQzYoYCoSWDORXqTELM2i8<=5So57QbGF3nzE7r z@0GJLhk3F5c7U(imja%<@x;7d#z892zkJI6yG27{VQgrqsu*x4J?ySMq%w9m=yoH~ z0z`{Uj|Fsrg|TP&Mf>H(5OB6xUB~QaUL+RLE)@@|4-5NaEpa%Uy&kg~(!ucH$&mJv zwAW_>M~+9{=1-3B7i#Up_IB{{K43~5KQWQgUG1P2De#PxKi4u4DcKRY9?d zrN8y*>>HJH>Cgq*(l0yu19{BZwZQsF19lw4=H||Hikh+mE_0$uq1;o7aL|)th&+;Y z32s^WsmrObdTT?j+Pvp!`DG6Yv zndcUIBXZB4wJwRAPw9XHh=h8ObmD)I|3^dDgUTCSW3#K_GeVU*`vz1zg>$WkU$kEb z^Na2kiY`?Xkx5MXrqq+|pFC}ko^Uq5Fc7OND-QP>5%LwgzS!4w9@4lY>VoSFX$D~5 zkIc=NwPzqH(tz}+pFDY@6l!yyvTE(QjS_*1l0Z&J^uuPZxnHuR`64X~w3jX66Z>rs z9Oph$^v~Z&qk68DU34f(*vomAQkk032T7h=iXXpg_f@u{*obiovayB2giqbYo&q>B zA%ko8#l@*J_WvcgG=l923>5+^@SRjD2srAMYOnAfsTj(Z8{RfX4kdK%8SrRTT?s|f z3tZSTb}=r7k|-tGNoMKYyEo%El|LvvotofdCnIHhxYsZw&y1nU4_?exXc+S;3Gjca(8l8Q+7oU$^6Aiia@0s>EmIZ7BAyzL9qWJL}OPwwCcRfp!l3u3!X zM991vkP2Yby^x1Q<}${rT{)5QKM+qc20uXfvpIa71+}q{`{Pe>M{DwdKn4J>Gz{gA zmAVK2#-hLhL@rj>a>yV^elUf%Ex|!Y2aIyk<0ZIRjEeD+U}r!chpyUn+vQf1g@uI) zPXf|LXp5kaS9DGlNEg9fXrMr~T2Fn1*YKQ!rH0UAL#j&Q`x8X9EKQ0(KxkG+1swqjPRp+WaGt*_GH> zHR;GDt)f|o6c4Ynufbufkr+W)|}#~9OI$9lk8WEB)15Ap$()vHeZ``AQu`blMR z(-hi>DFwQD6q--L?ECx=Fgm8PfSBLj4)4}1vUv%L3v#k@f2H^7h%=~*t$F3s9iYF# zxE_S)UJq%!_+~{3q9D=$YrOSv2V6qb*D!rLkgLs0^LCXU1U4FEegTM}>x`G{cPzS3 z3~Ys<6AQ@eM-IA~Iba{_kv;W&Hne*m?jtQN?XlKV*x-;XX=-XRs#BzOf2x8YZLaN=8GofN$A6r@Vh$ZnVi!ze zwt`?Jsi~=1J7NY3HAvZY)14$z!BKLEte@L;=V7oC|K*?x>dr!t$yKl_VUl>Nm6()N zRZDC2YHZD^iWWSmj0a)t%*X= z6D=x9-e-t}In^51h8U59&V;mlz}|icbBd+uR=rAjOjY^&aChE-j)#nJB0%kOhxp>u zBO_3X#m@f~Ly7>@)CP!|Xji*Dfbq{CAd7-n?66&X%;Uaiim1Cfv-#qEW`)~m2=m&= zUSK;@mjWS$_>vMnof6AGPzt+E!o{C}YXmm&EVye%aQ=}3Hjw+s=;$W4f|>?cbhm#+ zt>8x)gTUC8$d3cs3kb9!+1a^z)vX=(nzKdu|dWy;rxL>EBojX3g~t`V0z(F@C1O!`vs;fRaI5p*0VAy z=H64qa~a(LQRfcK2g4E}oyghE&F%>L$1rbhdU3Lgl#M@82_mOFp`JYkUlHkdRxZW9 zgq)2z{1KRTiAWFPc*ZBytTPtsmd$YP9EgRBkd_rT^UDB->b;H?6j4a*ZIP^5s8*g7 zvN-++nc*lQjwj#Tg8B?eC|^j3A}4&2Gw62pmoA`9okGNEtki}qJA0t=jfK9)Ccl%O z#Z;B^9r3r(20oWDnVHN;;Rv#bT2)Rg;HXM#Xh1@z7gp7=BjASK$SIWlQK*W7Q%*z> zoz8$1JW^0)P>B7S%?g{16+A+S>%~5J*#fF_XtS{qd?iqE(!bS-&^MvwYUZ!P{Qg|b z3w_}gZa8-0AnoLRnqN;zV}=qorC(HQWkyp7N-6lU?HwKS8L8g$zdAWgPIl%lpdbsS zC_}^%zSqgw8F(e2!8U{14q`JnqZR*tbs~LKIAcHRpnQ=N`r6Zpa5A zPZcBzN1j9;c8rX~fjLlSKhp!7q!Jtlq(y^_N4z(heJzBvXdz}t3n6 z|47UOE-(ptoeyY}=A;gBS+&3=ALfja`WFBqw+2xlSR?g%hxbr{q}pmA+?YV*0|g0$fLX zXD3Yl$HP62iim*pa%}X8RIrE!lmBfn@F7kBSuJo6pw_2-|IQ7u=c%evj*#3p$Xu(w zzCO}F&-n5-1cS!S7j7of*?~I)Wn>XTyM|X6r;|{iV07mza`?Q{E3AX17QVtge*PF_ zA{I=N{l3SyKvKk+-Re-)KyFK9&_e(18iT~y>OI$-yYMCdOT0Y1h=5SltdmG3jNMmW zU!5c8Yls*XU&k@Qydo^wAwm^08*u87iF^0mW-@RX#K0H;$p%Be&{Xz!WDEWdTA_$C4 zOx(dP5qdp~NcG&qf)q?6Bnn}<(Q(OVx7Mp*>p+adcB1Ul`Brr}1lLerw^F+j(9kGD zY$P%=vgUL-3AP^;@-wu4Rzb~)^|)L3CjOnPf2WE-=+di4Ekla<>#L@M~U>dx(8 zj(-6{TP;e0Q>HL+K?T+tFN6nd;BgjIP#Fxo4r3g~%;G4(nxu&v0)Vj9Z&T{v>BY+I zKzXaadWM;01UV}h{3LVuB^_My0$3?ccIEppdcj;;?JcH_&qzv#}-geOSRH6)o#xQ z5LiGFV`fClBW=}rk+0hh<-83n&K=mL4^tBoEN)XT!o_eQRjHt6BBZw7D+tX1GBk*GPrz+_T|iTCaSDLMbp3CZ7*t*I z_kKgxG8!6?_V4rMzf7IKO{PyuQY;5!obyiE&AEof@oj7_nqCLO>Oap%i!E+JeOiW8 z-wJf5G`KLyW|#>2aX-IeK4d)8HOgtvx6+6lX~7_$C;76-=2`i29K zN+7o*Ae1Z#S~DbIeRN(7!I?^c)DLv!fcNhOWT!r>hG5|o&Tc>Ov5*SpRYOtCmMR}c z$vxvA`5vXk9;-I&k^BLQp2mpJ4&TYw*3=bPUfk{24e()d>gs(Td@ z%%?3WoAG46A*iS2yjNam#Gyl$1QQIuY{z(DfBu{9FB$+Feia^F;m;r?mS~pobyTib z;LEdY0x8{-NtFBSeNxrr2AWrigAs?7mPltD$tIvd0aObfvPNSiC9;F92az)YtP;APR60EP4+B=@789AwstR*+2k! z$XX1ATY17ZyY7d1bkxa9!WReDI!WGG^y@P$C_iL)scOg3cG}lXR`Ke?Hs6&(8X5+> zWRJI|kanktNFwlMDCT2DCK$+3H<(kJ-bi2D*$rPiP{O<~axiJ4BZcWO9$U76{)B6# z(bW5bKW?tpl%TW>Anes%Ts;?{Jar8?CCfc2q42Z~>r(Ka{xeyLpy)+PPyX@$${brp5X9nS*F+i@I8nfI>dg#$bPcztOxH;$5&) zO#8k*I@GT>I3nKJTxS1yk%d-QCNj3Bh3ViYybzP-LFIF0FH3A+Ak?)L{rN0^5RCUOFY)3z%`ctK&Yd!a!;#1WIkfsF=UY{AFrN*<&hXY4iV)DYwRY zyJ?#~GE~zvACZ%1F>h{B{Iy3t>F%r`(kj=(Mlap1P9ceakt25fxIbYt*qBGfeK5eC zm*$0a#B20fcSh-*0xy)0NY9c0>Ut6tt@X5L=aMbALg<>VODc(_Fjdm6(IVv6D)EQ6 z+89{U<7@6>n7L3ZMV;0u`N1UqrrBM-KKT(hA9QCd#=D~feM7gU_>B0m>0&xk(3{ga z?kH2&654v+&;FT>tr;rs8%~j4tG(-46t|mx`?I3(FfN3gjIwmTl;OMo1%4m+H~;_u diff --git a/_static/thumbs/qng_optimization.png b/_static/thumbs/qng_optimization.png deleted file mode 100644 index 5ebf981af0a16629a1d0d3a9ecc38d5d39a840db..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42026 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 Bk6i!& diff --git a/_static/thumbs/qnn_output_28_0.png b/_static/thumbs/qnn_output_28_0.png deleted file mode 100644 index 4fd4a104b9c2d1300dc782b0da659395876b714d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21546 zcma&NWl&sA8!d`UaCdii*Py}S#e+iv1a}69;BLV!xVyVUaCdii_q+Li+#mPWsXA26 zrfLSdd%7Q8YjvoyqBPPM{4Zc&U`R6GB>n>f1CIe-xNtDQI}W;k-N3+9uVf^?s)5qa z)?7h^b9a|l7o%3H7yS-NRLu{+5f%ry3rt$#Ma5*k1*4L-1yDMne}|(K^^HLl>*|^i zJzO-bLTX@R-dz7V633+7US8+5E^7KHnJofSqov| z%&&s{`~;GTmNjNSu!7z#QhIv&S$kj|Nx8pL;R!Ww6dKbbHEm`XfCV~|@0m5qlL8Cl z&-(u_d5U?>9y1X&2X@h=Dz($ABiz{yOG1mu)5pQ5gQJ*5#f5HO5o>8$EQHx?rQ71% z+ZDm`X7~zPUUVl`a>d^5&C^HL$C#ol8@XCKhMEyrz|91fhjwQtL>7EYLAIsx!PU(x zj>nSve+0*u?uf;HaAZBx1(523cS|-6ghk}jHg;=ue!Bb>JTBqg>*zGj#3I~sJA75y zmb(da|D9Y1vL0tjqKBZ8pIXru?V?;*8L587()R)5jD%g^2ph!NOf7~C39mAq;!@j5 zKxb7jCe$bk;w($2ugMQYH9EK$aZta8OFnF|$SHAfds;DT{l`$c1N_fnJZk7frRgdh z7_+S}1I;ZJhhza8zdGgT`o=57xtiXetrb(}JU`W5L<1m(wMw;v8reri=}S*+

7%i#*bC-(8B5nk(`_g9x#W_mrmmG^epCq*Z>&PG;(k^yWlfT(qD^Y;{9dSZ%1 z`$BZ~SK~h!P2VgZS|crRs6VGmJjKoshDXar7AFjh=s^c;jrsp*$N%4t7JzMO`Q!D| zxvkEDL612LHKPk2hV; z0-RWGx@ko!nI*uAcJdQmv|4fl+$&oMva>A-i%JfTCR}SP>zHS^?s)$<&_K{{*>qwO zDZ6nOq$)iAG!%{hR1V;+?SzzzF#d6M33R1(`BIc|;5?#Sg0rSg4l)M7p#@7yORo(# z`29*-a|6!YCJGd*p8{-&RzvBZJX(U!AxG0O z>#!Sf+MKKmj@!ZCJDhl0!!1L@>#j23A)9|GVz=-7++SB^nuX894k1!u)vgyi*) z4)+Chfj2F?`x)V8CJJPrlD7#qTCoVA%o$E`l?C|)1-EULU6++Vl&|-{>S4QeRq9!I zsHk*7uv1@@OIVVHY-1a|2UTbdzM?R1V7GL8Mmx)g zTJZsHoFG9eZw&5E^AxRz%#ToktE9n5d`>LRcTChP{O*fiv|rJ`fB!myj0oGw)Zx(3 zS7aNndRlN6@gDJ$#-!vo8J)I^IQ4semJQCf8Tw8v4%WnJ>E^G9-uzZ?_f|S0_U_!( zr?)KMowI_`HH0)U#7Sc~emXhe$AJAYDZ-a$P&)`04E3LX$xpS$tJN=?UpDpBf<4%k z#Y`gezRFxiCnqN}Ac>C)2f*>@$Td2g$Ui7Za zr{Pv*n?ojZ5=0*@plpSK!kO~fRy!U!oL+34#)kXFnfG(j!>e>oGhWZ!3zTVMPJA7G zkGkZ#97X9+!5JkZJ5s>~qe=p;z&iMr37+4)hls@jvV%K7A^pwa@Q;we1QkL9#Ho=( z*2BC1kXEgo93SgEEr=>F=PTGfJ1iy*=ib%TT1!$yjD@Ef4v`2zx~ z{C(ytD>5zNSNjjoPnN#a50WfY4bFbGA<=5D-&UWplRpb2Eyxqm>DS+yMiZUO{&nGQ z;Et;v5d-e;fK7ut2~u>2<{OVz%^B0mNS4NH=+seeN(@`Jct@H!<}p-_@S`iIhrbWt z(uQ(F*fx4j4089(-3QlxSln2)Jw$f7zwk{*=!0Ps5E+Y71K~kN?@dp)VMqN81O{8m zM`Tq!uWXu|_W74vnVR-53>E-0aWu6ql4i5a@D0v!Cv?-`vh1>KXo)mHQ`aU$OKEUj{Bbq=sy`&HFPlhkeR15>&lBlp*r z!@+oQf&qAtrcUbvhx?z|YS|jt{rA?bPE5Y=Z%&lg{%j(oJ*I^{;wR(wV-rlOHd}FI zNy-|ULA~3CVi)j;vba!PeA)+u4+qHKgSrEF3XV)B2zB`K+cH#L>vykwDcfciH-880ai%iAU&zADn)% z&6D;c25c1Hi4JlbhF$O{D0o0cZEqV0@Hgv0=*K5!G3K{DM6`RavF**4_k!oIuRttY zUT@ja^Z#Z^saP4W$Tfy8m7xu32J9KM#P^>z5|F5J@xbfnERnpZpjkwZvng*j@hPE0MwO>&CL@`ga3S^>&STwl7~E1H+&R3wTWYvNGxNVVZeK5Ox-ui{<;3KXK7-+Ct81W zm<;9G?@E+FuAXAXeuL|mCiO-8Mz%aEoNfS7fKxfIG5Bvtz>_4#6o)wVFg57cb7;eW z!`tEsUaH`qb}SDzCx5nICdrE)E5gHjzF;mLVM^1qQa4*O6|D@tEo^6OaZ)&f$zxR19i{x z6^7&}QQ|gY8&v-$LmsDGRa=Xx$IBp0t*8O1B$7}yuO^1mtkM8@7R z%#eBGub&+NS02UX^)Cxxgz0Zi{*xjRI3pe~*7FMzV@RIQ-Dq9^Dwf-uHDAPk#+X-G zJ6Li7Z;dBv3(tPAZR`ve&zb{*aw?&)$K~!5kQ%^kE|qc zwXn3)%wbiYbxKLaWh1=g9`d%QXoc-RQyZ7UN(%l)`OJ`GJ(Zs3#e87O zqo4dZkjMl2(N95G#tUll7i#288q^S$`e=IBO?KQG@=mb%dj3Wmz{`$Qf$yA<3f@Q- zn(Rx`A#mJ^yIkDr(UWb#GvV2h1{8neCgCpE|8j^{AJ#Pz&uF`M5S$Lpyu3CokrRom zyu+;h+Qi*FbsnvQy)`tcxlqaa(2fpQ-3Bsd-7Vc5Ptu@{A@b4J0pJbs$}>sBVjyj}muMR@TET>YFLodUg?^Z=p; zz4meDRD`W|L!Mq2tB-G+m*#}923_eaBUfa@JA^(7r!@PEJCfZZV#lJotbOE56Z$vF zj3mvD;T@6E`&gZAi3KjHyQrO{+L~km!N_lE^yr1R8DgD(yh}cEjhqruf*!`UMn`A( zj|g0ZfFOD}00AD)1Y7F%xO2w1qAiv&DE;4`4BH!q=Djk)XPC-(R<@<@H3N!0j{%XDKLVoAhuM_t85Y|pu=0H@@Z^f_NKa+JqO(FbS z1`-3D3-Z#o;Jv`!DG{8nm``55C2B8iTD#Lrq?aQuITIr=A2umR=&=-G>TS;LRj~O7 zPL>VBfX!5>y_Wsc>+3}tq{Ir-s?QZ1SQ|LY150PtLt}fTyA;tT%A=d(O`60?&eKDi zPw=e=*eG%808lckwR&nfAz>6fJf>vEi&>mrt=+tquCjljKe2~mF}Et}FWWE}%8Xm? zh0il`(%5L~u%cZSZ68#UakF(fAKUE}Z?&(9DqH_4zmYat_7#JV#{=0V_dub87G5I6 zW_0iFW~)F9=wgBjJoIMZk}ux}dV1me3QpRZ-Lg}w8@}4vlrC_0Vq?Sa4#RsDm)%?r zEJGlx%^TI@rgO8#rkR>e(Y*5_*xt~_7uSKvm$Ap%x^I$sK*W|0El+DJ1dA%^(F@g> zjeFMIYJ`TpUqH65)Fbz=$zN3t*g4B_;yExvY91nT>didm$lU81!sU_vF*w#ZNz-9D zb3H=(aS#4XH65HVm?JK(cJbynL0$UAwwttIRC_2-^k=l`{c2Ds;-PL-BJqMgyJ|Ra zET=%o-t9Hwj6lisIQB(3@oac!(i1thfQpYk0nTA(MBdF2>br6R}X^f zp`#mgsG?IHUepNN`ldVot6c9MlI z4scQ2mVrKSczL`(5pT4;AnDsEDjKHL`h@p=jtB1SUz=6Or_9C1W9tr|?Qn&{g1W*E z3J&7VjCF94YucY?$L=d(66=-FL1LKzKK#-YzqL{F?iX>veYlE@6fuV%>`9320o^kG z;%{91YPxv|;YxO?33UE(k6U=u2?oe!jz1w5=~M4;@cbmKOtGnjx2b?B$fk< zn~64Z8KH$dd%hh-_3@*7Iq(tO3Onc34E>_@OL)p}A=VH|Ys}~|A)#5a-~_GU1O}P{ zG!l+16qhtcI#6somB$>`vlKepz99K4#?_O8R9+#6P^m$YxG)tEu&2a%+ZAneoNy|= z2nen7As_9&3xDnXnsZ>5T#&TX`=ep0L1NjNnK)ytnZjmojYwQe?-DD&-iG^JVZW;z z&CuRZ!c&g4B|^QQ;c6+EqyA#nM#!V`Gw{JBP4u4-7-$aV_W#ZQ{>CwVOOV3Gn_@7| zU$2?$%H={4#}K5~jn!L0x*6QB!=T;35S;)kVkkE!$H=labgg8lyi977;m)8L*%qet zQq6zZN}VFF5|Y^i#bMascI5I$;b34XW!3GSwI+%^U7fR#z;65>sKY}_sN*p3kDE)$ zlqL6~YFlt{hi6E?2X-+3s3Ha6#>73Q)oe6o$dBg2wH5F4`rO9I{L^$Q@RftUT1$ zvs`5`gf6f-cN@_9mIj|w6Ja;^>w)2&)f zZe&el{M@M|N{?)fO05~8co-?kY;3beE(|lziaR(MHv83B(W<(dVEBiogrWOSxaUK> zYaKRxA{s@VDwwPo-(VWRHy|>pCki17!I9eCUwk8dZ`7b^B{Wq}D)!#@9%?UF0+02v z(JYoQ41~V^;R<^hZ)2Gya}ltQ;w>mJkP)%uQk+OK^c9K`*IVM$|0qfH3(ZgGAQuAG z(4_p}=SkA^B^hBA95MR|H&&%9qw8l^N|#-ieV7Smj$HB8Y?WazM1mlG6g3fqAK1t9 zzCku(xy}gFy{a;6Am+z~%-yRW@5j%@JE#T{dXv5L66N3pIx%g?P#ABp)h}fbnb6~4 z_Yhp&&UKrPJ`l1|2MS2Nm#&~ZF()!=>c?NgTplqSx0%-<_b;|x(JKbQYsc27N()X- zOkL+;wr-X$)NR5dUfa*C)Ygb94HY8WOn=}hVgErQLzDO*%EQK)%ac;Gf;HhULX<%c zFM}#M)y^ne3JOepH(2;2ZgtTT;S@&%Bj=t<(-sc6B6+ub_&0LMR6%@~phw*)$8Sh?KY0t(HgM8v)>N2Rm_|6p)mmg@H zN)}W_-qH45cSZPevIG|iUq->k9h-IAsr*7R8OPBE^=2-56+fuukO<$aAL*I5_A2j# z85_>6j6a~fd9A^`wkhmhARayWQ>{hR8#L+etks4DAzX--``<<`4tCZ^Adee*J;E%t z+ww!Axv{EU44@SoS?Ub-c>`-;{tG^yF#l$Z+=Va(qhJeZ1OM)WdP!b|r8T?V&JF$X zO-AM^c+F&O>8u_|JN6h~pbn;T?zkF&oevh~Q-TkXBZ#4_59YesNA-Q?v?5 zxKKQo_WTq1Q$ViDkYb`X6Xm+0sNql0@?Yrdk!v^G!B$=2Q-}FI^~>EjJoBY6)VX1ckR(t9k4A5k zg)+vGsOCstvirr*!Uh6#`50{{j$l8XV};F3vJnh44&a*!V+J?(+-fVbkav!#UD63N zyF*m1T6Os$aIJxGi1&&7Pb+bHeTzuUcndU`20KlG?0%Tw&0SAL-ci50dW(c(pwxPB z!GAz4OhCB68c+nlEgRHlS?8}@J__~;Y~!VH$C>Le#lrdm{JGw z?-EmR<)F0BxI6xR6*c*F5}cfjt9T-mc_wLtZOcSHZAw`2R|_e_KjE&evzql3<6BYi z)q3RdOcOYWL<=5KoC+E88!WTbomT{OCdHx}m4wm(&)X^OTDNV|Yog=3BQj1q2ra42 z8-Ak94Zl)*%yxgDHBS?<;Mex`b2UDGJV*TF-s!1w@Vbfou~4W^;i~M#<9bQs0_+vq zm9EgS#eVc7asB!Cws)}a@g6Nat1gQ>C?9mnVryL*aIY=>HLhu|K4|4H0s*L^`K58N zot`n83+?WYzq{GZ@E&}wz+&gW*<`+2o@ep=URc;y)w{@d6{FP5n$IF@Hx?suk48HP z*%{pN`RsOF8#?`2HcezV=yZAZ`TryLKqFD_zKr0#|5FsJgTIN8+$X(^yGA2CyG5c( zfFa;&jfaQFBJ}ZkBf<+j#EHUCGb}Whm>n`lRnFiPqJLn((4d&UurvnKbK0j13WFY2 z#9y6aOdU5yNlg<+tq`Y3XqmY&!kAzHu*X&RYFpS0TL9sGH6a$UX6;N4ZE~DiBp# zS>t5iHtTHvj_XMa5x~(2VCnT<_u?}=tExk857o{EZiHbdk(3wk9%mJDO>eWhZ6_YB zi+XXSs~e439+=yc9va&!6?c%dYHbmCyzO8S8ar@~FQd|CMFZeeCFg9gD_E4p1^vAU zvBh(9#zz=RAME$~gmt-h*{y;ieac>7=guu)Xpr^?W^8^)9EVK8utZrnUYQE7-&fZz6$~qqHpsSSVZ_hP( zuvMe7d1oY7edG0XP*Ty|Y!JWv6tc4m7D4BqEB5Wg=*_LexF|I^P#ehA5fA(z1AaKaS;OCKm4 z%bLtCsR%Qh7sQBb>Kh8Lk+Sb?A^^RoPaN&JCrV7PR43ylH&x@qKx5stHFuEC8WAS| z)l`pe#-RV;uLeHbfElJYQa!!m+6VwVnAGKT3NufQ`h^ej+R~E76sKX5}`q`nQcvL_s#J=wgA`A?h2HttC7>Wz`H-*1Dux4VyH9bCes4}{DSszIcFLL z$g;*-QV=$^0rVpf#}WC*GDt3%7n}F}jW=88DB^ggX+Ho;(5T{XTc%m!evRE3sqvf7 zPxDG%Z%)p5BN_W_WCbT*$OLD>M#dy{4|=vU*y5Q^^N!!(uqDWs(as;6(_~3O)c+`A z09FK-jjgz-@^4)QW;~cpbfBiD`qefLZ1swh)7V%etVQ$A*?;F(C?@O4>)FWK>3Mp= z7H*brK!pv<>}kF6g941}JXMu=r5d1D$>pI&!8{lqt|xI&u6mYnNWtJ^-|S~628{&X zE9;N{AlV(Ra!5r zJq8%iy?>>f%0|{q7c2PnpR#Hfg@JI7s%0hw58@DRk5}!v$@-?-BBIW>ZGH`iY6yGL zFRaLi&>&x-E~&rxMPDOvP{q>o02A*_zD4k6JHkQr#nHqB04D`rFJ`WfgbEHpio`70 z59#Rtz8egabUso{RJdy-47)7q*JPxLbl|1gn5@URk%gXU!T;dCR3$}w#HNMF@x1_h zfEe-Xgm5G5Uv;J96H>jeAH!MZuz#T%uG7J!LBeu=((l~)_fLp|i@xyB+7 zfH3uKD-k4qzc$L$c_Dl>)ZMUO6*=+VM^0T~4CO6@*~nVWx==bN=?Rf6a(mO4Pg-&h z&$+SO8v|GuA(&o=C4w2%WVi(g*qofE)i{(s(f*! zs&bH>Gwwfg)=OgEs_qI93(nn!;rk(LKU>}IPt24Qx^m?3{;-C8xITYS^x&8MPBt_T zYH56~+X!H+*dYqML#8)+#S4M)MtlX2S;-D1W-&}XWpcirjay^XHSnFVWjC?=(l|P1 z>e(rQ*#6XSXp=>tTW1q>Dc;QBG=@g**}rV^w3laK%yTN({48U!Rxf`l6zehG7A%7o zFNVEvMI93;Dtw7nGA)F)I56azep0092FcY@+7bESBx;8YTO=NY63(istUtID?s~od z47pg&@1ke&G>)EbL-H_2(7ot=H8vkzJ%~z#qfp-3pxs+##D$ByQO`_?QK$%XTWK^K zqhs;5$j-FEM79|74}LxnJYXmgERY9w*P1m-Fze9VU`pUjh1m6At`G(gC%z2I3g%(~ zkz6nIAX{-V?r;}g5}>R^dP-=k@Ay0SOra`uT{_g+Q?Y`=!ole;(@tZ*vrC|)GAowe zwxa-c08#5J-aTjw`2no~o^bYRMFhfZB6-A}!xO;(L8=pgH^F^F@b)I8PuYbqsK4O| zJhZ#h7vW-Nv5$?cCAGkx^?+1cXslqRk)*a?`LhyIC2@JXprm?HdJy4lW;ZeFftw_J zt`@}H0w2BMzj6VC;iNZc#LMcGJ0tR-1>YBB!TxL$zSpW!4=eOYe3qi=&U)MuCj=}A zfMXh>WQsG6l^KU=lk8 zc3fZ2BQ`RIl_hyhjQ`Wu9FI2;8OmiK?%#7@6ZGOm8FljoN|C-vex6XmQuq^>za%BC z6G8ZaSZ&C0aB7iTp7<}ciW24B-$6}vs7Vs@eEHk!saFb+h?2eKZuW|~6r`MUcd9!r* zmgFVzwZA$(dF$)flO(Zp?T%8~q^0%djrR4{LVrw;V@Ohvn~L8&hmT*qJTo+&zSlaaxOic%p(N&Z4`byXknhZeM*M|-6Z5_*#i?zOk}fLK-s;5p_erVAS{&ADHkAhQ#JS$7M!*YrWW1pX2sTLyRE zSzK7xOp!kV6C}%4Zu;uP(G{RS2lGO~6BiWS?D!sQ>WgD4T8Qy;9wSXBYh!|-t8diE z@`x(8Ii=SyZ6FJw@|BfF*21C>(8FL`>&}|ST@FAyf-FyT&b)M{ z#EFW8ySzrucaADEHMmfb=wAtFYlVa5HUfgM^kx>d)kb_Y_5%5Jr=XXR1lX?F%9*_< zoipCAGDP0Ss=uss=1A%8fn{Y&iIjhNFPEl@jko8xlrv;0*?BdYjoCW!g_?Dy@$g$v z89LFM&5O#&8CK6f7GWY76U@%Am2y(BM5)-@*m!T9Q`w(;P`31pFkPPFw0nX4Bk+@( z)PDQ9?|y51=E915TUOk}a9h-yr~L-j{L}OUH#g$D%Ij?F=BBJ9#vWI$Intg@E-PoK zB&m%g@E_ma2PlnypHIp}$_inTh1dYe2)ziQ&MW+TIm{%hLP*tNUVnqoce#+<(CWd< z5t+_DV=d6_h;*@oI$Om8FLCk>7(j2;0tOALgnUvjltQB(9}*58xnVwt9J4iqISIhW zKlqt3xYx^qV(*kw8d}|N7}>CW=cdDXZ*gcU*sX)i-sUQ}AfK@0BKZQ~{LR+G)~nom z4XW=;4st8h>^mG!JEtNJZ%rqmbrRdGgq~)*t{|uLTtpds^y5nj91b=Ua-N7*@N${^ zNv=rB6V_)f<{o2EN@m9J+l>@!i>>D&1eRXduac(){C6=vxYr+rCaIXIxL zoJ6KyV(H1Z$xhxr0~euHQkSQxX7(>@qeJPvpEZJUWhfwfa{8KZr?3CLI@*VEqW|cg zt;JTblg|uprY24dJsK$5`Bf^D^#xt#O+H_MaL&nI{Z9Y=Ns?K~vjstKVUqKax-)L0 zy**S!k3WviU(pLa77(xn970@=oPfODm{aHTwfu0Hk2<0Di`)$VDc8I<|XL$HJ5%9oW19-l*3eMN0Tx-qXtU38Acw z-i^+80mZCT!>hA$TgQ>X#;RtJBaqa|G=S2nUG8G9cuuJpo)^+)ed!&U$H$X{0L2>7 z8I+TD7>_#?J&ya0bP}}H{7Y=3@0&L}1qUUY3S{m8fPT~C$|mFKA3aZfsD*kZTkGqcB(o*Wk2~0y?xXYmO-4-MtR+?a zI+Oy*zp*H24I1rtrHqmYcRsDjLa>%^a8?kzIT!Vy8*N9tn_aHOZdlj*q|ZNaR^WLl z0Bu)J?dP|J+<|e}NO`b7d$gRAM}n{$-*g0h#e;PO)Dx10tQXVuK@^%-Wd)t> z;N~Sqx9xNq+y%LFd*+tP%OY6kzY!4;Ntr(HPbBHUgXp;f0C|u=S1_|gAf{C4QX|O) zN?3%sU*~rt?_@2)g)?{|QBnHf@*?vs)36Td0CZ-$ScTVMQpxB}h_oakF+ou%Etc*J zIS2cAmtzorZT@8_n$mGiV4=FyQ?sZSot%==&&fBg1^xW_m6@G*g!E{OvZMA=z>01i z{kTd3mZ6Aw^E}`6=JvRduACvitRv#X`^CWKW7owc@d7w}v2;L=r#MY^Kt}w0O-B5* zjrJuY@Xtch&3+=!(_D^0b+JSN|B)j00rP%m+Pg)h?D|?X512=avguS!mTP0dndY124-QrPhQL;nt5N!; z=gDePInOzlf5FOHk=FmI|JnJnEMp^ao4X)1Z9@B3-!Gy{Qb`JE6aI42Vs9ZKmnF%ueSa%whIz7=#J`X_S?oUVuIy8l|$P1fCqidkNY zYSYE&OY*$ycfgB?@|u?WJ2g&sGHelwT_z5KP6?Wf+^PABLnX9x*_*s&lurnW>P;AS_dUtrHI0dq-@6dl0@6Xpx zhbsKpC3B~y+v4}FIOM%$t4=XwM^mpq2L37sGfI2|8sD7I*AmZ99Ydv!XM($dkojL#(Yy;%`+}H&L)Gbl|DFKzp!k|viJDeaPp@^?a$lIxnl}0*84L~f4+Fm2NxIQk> zEPj|LAx+-7uT_|Z*lwT&W^fv&L^imiIiq#k5E2|m- z0;9)~B_AWL;VM*V+U5V0^+dg_g?Lk0c(_o!hr4FkxBwhnen{Tppr(*wee3s(A&bo`H?0;|I;BcCe!%%CLh7V`C+siW{Ux?sw14t!67n7=Oz$B& zKsNeu@rWuxCjt5#g3dKTRbPMoRd|UXBuLzlWP_ruyL~u5A{-s<;xk&Zns8ilLuzRV z$GySp&0R|(lDwQo+xpcr4=H^8%%+&4&psX)q#CcGp|E}O=W@6mH@_Drk3v(h_f?@E zENk~UiGsu%3UBVlFI_?Igm;-(O6e6Uw-92MKU^`ezS^SDmzICapQ;QXf*|UB5gvU+ zlajYxK*x3!gw9m9o;Omp97|-y{*PCWB!SGV_G;K`J#9uDM_7046O3#BwylFF$G+_& z4{hz&2C>#9AoVkbE1fs42H;;LYvxui~8Ng{cwEqMZtLOj>QD|yi8skCPrIB ztG>Ir?Jz(=7;?R^O+I6==T5*mgSKhO%QO@xMoqPjMN9Pb<>ifuU_z&+e^5!NX3LV^ zsDv6J#UQl7yhhK%_dzvGx|~s#;iZ0+nk5b5wIR=U!Edq2!RS7JlM#HrV)ph}H-yQT z0;oCSGtn{d%;#r1;q^DRmIBA4Nx7$?$E+B_l!$C`*|NYtT1V8F+uy{Toe-g}{XbYB z<9>13p(|er!D50{g4seiOM95~vbLpS6Pty$hMj;steLnS!CHzTshOj*_FBX6s`#Te z43e>qQ9a6Dk3G+ITu3UdJtMy>F@bpu-zy)L>^9If#$q$|ItKD4j?0rO<)r>iP25M` zWFw8*zge)TTg(1%>>}_yCsw-#h0psl(m;qnJ!4~GMLIP1>>O`?DGVkGE(wXQ%l^J8 zx`3qH;r`zG zkQcpG5~~@=ElSqn3(W5WNv^{p!RP{(%$&Pnl!Eq+kt840~#C zZ;zr?J}*pz<{Dg$i;t!OIm4+^Paf&35k6BB;C~K%`mY>91c=&VU!B2ygkL5Q6^QYV zt2_eM_FC=ddExc7Dh`#^wfo_h9b*(wC)tOXjHq zD;wB`&q#!9cNVe{1)P^4Apw#k>huS} zfq652RcTSHN=45NdDDLeHe)>EokI*i4OLGPaE`B=%xaE*a2EYqtV%Jpz%X-d2%k08 z6eM83_*YskeV{cU>Y3QNBR=SIMr4hTLaCaFhYoRFKxvv!3*?2k&|~Nvl39ZgnrAIDAaMJh@*i0CGjaaFNkRND5WyAYOj5 zmLTOQl~s&a%VEizg39tj=JfGwOZ;Ee$kWDiq_wjFvAV+B4%ptURaX$=BDb>Nb_B#L zwmRjIjp{iyayXH6*THlUO2tsU9)9wij*g{|#N?3yio(A@6PO{jvDo22!AcetPe@m& zW$}aMJk=hjrQl{Dh^Gq{avIj|VRVCo-=4UV>8(Yku+?~q^LqF=`D2wHCSStwtPXsd z#sLC(o^ZF>d7>If$c!z4a>4q;bGa4N^Tcu*#@O1S?CIY_?qi@F!th$ULuS!l-OjU` zkLuDcVP65dY)El5r)0cmcND5)RXuEOI?qea-@P)6#U-J}+lR{Sr}L_F_H8I(^rwd! zxQJBRx@TAQID9J>8L$nVNbJ8=K*;jj9FeUjSO=#K`mX1E~X^xlNg$ci-bguPJg*AChl{-yn{mm%e03?ioHe zj%`6hL&H))BCfLTcX@C1cQgZ<0AAp>gi+4A&O{RIM*q(@94%RJyAz$Oqd-cKPsrG5 zFkV}#-b|NKf=)3pN~@9P-6?m)Llth#3xYj+9z28muIAp&q9)4k_S;K#)f;77Pw|93 zYkAcSu@?wx6>fx$v{@FlY!|wlpivX$;lKMggrK=QVhj%IAVy}g7Rb-3r}c)SerH&} zk_!tybC|TR6{?3hwpX+u|KK&lywgVkB!LdoIiI?pOGP0~qqDE?Ph*{0s#P$h zMn3^GQa$s)>&zrp5Xl)$rI;;b;)swsDC9wN5Qh*7*}qkk=3)-KtVK; zbF!EdL!4-jgoPTtPx3n~*!uD;ty@B zuSlly&?woRszVuqcnQCcH(=^@4m?BzmKzczWhzFU0-q9`^aSa>zVbxe$nx%EB~qur zWxZ6EJOFOdwovOSEfSY=e|d-{=KdM-lz;bYBXN(3j%>ay3UYWxQ3&0S{&7RI zdW1qRGVnLlrfl0+D4R?t8vl6MV17@s7AnBX6vZvQsGOBa4GkM{b@{423@{w3YBE3X z2Eh`bsO&QSoxjH?KEXj%_9BLiPh>0Jn6Sef-&*NGlyCpT;W*voeT2SWjAkSvc+dlJ|rd<3VlD0Jg&*|S?>|p z`?YIk5ScJprls^pzzysx6pldi-HddRo%$3@1qpCTCH1rqV9VFCH7=M6OV-1CL;7-U z)rWHFi0;+*Yb-Vt_f01@PtoA5J7ZCE#AP7h9fNFG0sDx~yraDdo*3M7j73lA$y<+K zG)Nf9AXy(seRbZ1^a#j^4CcPROb;HC8HRU0BpR5kH&p1^wX4siLa?GM+nSOT_!dsk zliYDt$Sg|cPAfyIC!zSCVr`zXia|G1xQ&^@I=_0sa@U#T&H25nOxU;Wz;Jyyy(*&9 zW0_t$+7&79z;L(^%++T_`SOM(ewPosoCw5@B?DQN1cc`xS}ihnRA5N8gw`+?SD&R< zVH|vb!Xi0+CjDXWoafI4>t-@S;F-}G`d=xAS4^H?J zK_#&{-@GW?lnl@osz?)LV4cT95f>F5yhR3Ls?DMqaqwE2<72>bch*}0g$8gfLUc`` zssX%KhQW=iA$LA6BlNKP zPikYX+4}N{N{`GWo(}npR^+nH2XSZ8r1o_m`~Asg-J_;3^AEhX8vf|87Iz2`-ZDd2 zR#r;?UrYc?N&f;cbwyepjIIiu2#phC!2-fiI1>F*_JF3n$@!Y8t@%i<VG5BX<@XEEuqd@e6AAw;_m$oC++&L4kQ=Wydfhvq{}wlQ1kl< zH%Usa*Ru};(+tcIzQBc=V#hhI3yuY&{;2v>x(OO;XVQcUi#Bi?b8oO?L2nCP;a6~s zW}{rx3?=k$ICHH0C)Lf-Y_^wz$8FAeQuz+=o!gUWTLbG~=$mfZ$Ph%)PMYJ&>c zYLr&D4w9@`2KJB)503-!H>(yO9fC2&BFtaCm(kdLVbHh{K3)n!9(aiJmiKxmxeyI~ z+=zS(TG9YAGQ#3IMJ!~T3_dTM01*FY4~e6QRM-nVjSb!mm49Eca_GL3Byn6Yn;IQ| z@5FHb==5qx>(>dNr{&x8*T4OA7Hk2*hI8YnoZoI%;gv1x z4HF$80V-S&qMo>ItUad{=uYgIkm2#n>|%_-eRg6R~dFjVoeOV~u);pR20pKr&;55hL03F6=uU=CNS7vCp`-rt)aVgqz0at05aXrC5_35vpjOjKa zBsR=>Jx62%M!Pv-n``KH{ zdy=k2K|1JwS~?fHuW?epi|`orD1?z~r7K_!ru~cUl)>4Ujg$IT$xyyCKqFKEZi?@7 zu^PRbJw~ck_|dfmmmKnAfqKzq^2=(>Zg02yy~h4~rvhmPJH2DgASMGp9M0f@>(@O& z&KI&zh0VL037_Sm=x~M&V^DOl_SKID`||z?GvtA@DL1nl{xCs?E6aq%NVswQH;zo5 z52g3FvCgZ(7Eo;|6DM{(wmg2 zV!*qWL`gOk3^6usP1Xg=tqMHX>K_-z=^e7@#@opW&dhi!^lrQA`&WQigU&t?)g*kYr~ANu z3l!OS->xV*MlL_*-|zczPPI?SP}Yt~P-*<)bs zHY|3RRDu^l9YO|r+Lyq$y^DKUkyHc%2Dm1BCyG=RLE(ZCPH_bdp)2@myuSRJC`-`n;g&g$?^W+O+YnbR)haRbBPoKIZJGm z!K=o!$F`}usc*<$g-0s{&E~?u-8-Piao%&}Tw}gKehD6=C}Bu`_xyc%c*CYMEierS z#-ke?V}0$J+5VpQG$;c|OlAMM#UCuETkB~OUAEY4BNREafs>4mW)A(Uiyg)1EuHCT zMa^*xp!ZYbe)@keD> zOtf}H>!su@P2k^=tIv@#0k52C+y4s=)7J5HH-M)3{pMgfbrp>i)tA05NYOBvSJe{B)uT(LLH+6gUx5b2u;J&M*^ri(m!3NPKj+jO?h zc>vc~_t99mf5lU<0%{ZW#YiF$?SA^!Tl3Q`L(23+9rCM-Scqyd2gWzS7_l4jei#XR z&+?V}qJh{TsaUu!ITNv8!!VQ(L*AVm-hDj^goz0i_6N|AxSjo9o!>|q@6l$pHB?TD zDe1^V<%0BkyzXDLs*Jj~gwfx^ zTS~}6nK6x;RL;{POr;VU#vPxQ4$?>^r*3XJ6v}$)k1e)3dm)D}{v&lkIC-|p?j9LdjCZ` z!Ih$v`ll|e!rmS{=z1G=ocJ&XY9{R7(lqrL+GlwE&4cJIr;zh|n-$PG)}~lRQ;@Dg ziebw5mYY5>?4Fq0sFl-g4#2x(%9)1|{2SifPKd8z$9(OkVUu3aXZY#`l*4QRNTSG^ zTH58g1hiIjV)frk)dXD*w=etQpbk*x0janPCp!2Tj1rTh5;UIUFW&He?JP+alR&4W+kBoSwQ@H93NEW6N{J6ASWe z;<++ge~vWo>~`-kio*SY%VkQyPTE+YSP^^lDI^G}X zV9ANNKE@5yzarj|LGAgtQvS+n34B+{8ES20(^rvRxy<-+_u6Q4joGXBMzg>AP3LHJ zvwwDB{EM5G`y^5SKZEB#FV6jqWuGS6~!1V{&Y7*HnXKzioB+U5yQ#W&bj|40s z!B;#9jO_8vJ2mkfR22uVUh5imahnvzf=y8ivki&dl-5_d*T(mX|2el`)8j1UI^wk* zMZD&AUEd$l_3&SS~Po=QBmUE8zs^D;%TdhK=M?705+I^xIcXoYtMJ-c=%bWl&e zPW;!a^ZfGchUVetIeAa_q(_9CfgdfZc`QkWdgKe0IjvraikuBsF+nQ$YTQ!u$VvJg z5LT-S`yGMI#_RkRYE8Iz$9AfPjnt3ENst#3e1&OQe@**Bn8x^=_lf3E>(q_4Wh4sq z!hbk!q%N(r#$haZF12FdEv%80YdIbX%AHNdm!6^qx*Ch$=Mr+}q9PKTL(SCAg;tDw z+M*cQ1PaX9%H7;Jh*QJKG}2#-&r*GGu8;2sb9-e6WP_wWsdW#v$##MMZjH8U?iP7-SEg)`jGQUrkR(3}tHauhSHmwQDA% zivR`hxn1ZhSvr+Ez90xf9sW34{v9=@&E3NQsTXt^p0OiY=c|#x_?T!L>5Sw(hK_LJ z-g;a7i2P9I_N?>?GpaD|!c$N^L$ylIh1sPYUxj9kktPuv)QNwtaOXR7DMCx5D(Amj zPwdr3PIUMt8=})HVeI`+8QD|clU*)ZWUfgHf}POnk{Y&{{?rK|Ul;==@=ezxepWZ$ zRFH9Q`{(zO@cg>p%NfPi27xc4_4l(1jvzTvyRmDT7_t25@5}HUZ7OKOvG~w#TNn2J z-=Lgs&3JlwZK;8e{B?}Ktvs63c58`$AqGJtwLIsaQA8LSOX*ill@@?U_N1++ojy9uAp?NC#)f9IrVWM|R^AaHIv2 z*y;ulk+wB7y!O*-usX7;aewaO6GcXZQc6?aC*m}T71_y@K^yRK)J}NDMBBx-H@NX?jLWYcAE{s9I?#J5 zo)?+qcoXa@yW10zkYXYll^Ur`Ykibu$IB;}#TD-6$ME&|k$f?X=N5#ue^(6fR=l7n z9f;f~srSlso{CxyHid}Mo zQjC^&X=Am8!OWUhhBCutd3t<(oV1|tQ9F*W2t9;|mo8U%weqE{we)4u9|ps45AhubsX1RuM)c(+eCx<1Y0J0d7pY^&QZ-L*)wO+q7W3)7DbI{ARNaFZpE$W)<>0 ziNi$(no-ifXz)*nopmVui%==@)}Kh*0JNCR3)n+#L{f9Vx z;h$wMdsh;I%A8kIHy)R}AH@FUqw<_|={f_rcR zU{Et*{42lu9L=j*X{M=kcLSKW!$mh5bzA1;w)IU99GGbI6wE}tsY$>;q7syXx23FW zGu2#1CrXAh`~iZPaLim8t@xnQSN|@|kuHZGi*{vJ66fFc$+AZmX82PrK+TKcvbtd> zU0!s1w+lMgT~Rj&PLbu*9nN7c(03~{E5%@PQeAag=VDo%27=z>w5xJe0qG)W>Z^(* ze5l;oZOyKiVXio2z88CL7c_12iA*e^F->4ElN1Rd^zUiqnOtudO5u@eLO4pWyW$cz z{FRcMBSw5YN!S7>+=#UjWXG3yCkpAmC$R_mdm zO?9_-|H8dI!Wigl1pP}&*|3+1x!Afl+rre6QaFprefWUbq?|0pN_E@M=^F=L-Jv@E zc*378tW~_P^I*_}jMX~>>c1>F7V~0d7^f@XAJ<9=rCEHK_artHqobdi!`Gdl%VE$p zZ2?B?n^Hv9Z*Qfh_E6S{fs|f7D((X2NLVdtDQhbED-c=9c7|fvDGn4Etb2Z%7(QU@qUdti_`vLGsgm1fud)cSJ7Tj(@OSWBiu%zAs)hg zdTRtN-4`rqbKBxMGPm&Qf_|NgE z^K$0lv!_?uKYqLmYxHtjzp9FDUr}LJFXEx_8EE58!}UdQ@iwblMyG2@X3XcYwe@HY zh8|imye{RiH}Nua|5V8@D*wp^>@dw~pbXsWzT>UINOU3JN_Ytad9%9hrLlc|+*$f(@vSjvXA9p@&yIl;-a2V2^ z5<*`rq70^wMvbQ97eJ|NeLo0H4F2VxrN+f~)x6AlmqD2U(G!;NX2%(dc>9Q70u1SnOAH z(yX95eH~D3i7nh=rt$3iuE8%=rFM>0aI3|b)VBgydCNt#e^4sMrNMj3@y&kvU4y}r z@AHK}$5q&)byPpI<){0i{{)e2m@(g+p{Vz;2Qevmgj@L0l8dg)tohN$6{eMhqn5|& z0`uT!K5D0YLJ}|gu^Trum&?qC_j=4miL&O0-|E*e8aGz7XPwb3P6K)*4E8ogzvW0K zv-u<=AShs_?}Cv)rJ2#VSC(`3{n@{8VMUwim$=sNfDPex=pn;B7}mR6J3a+q6Ku<= z_Dd?Xl+4c}erve=CI??G)dZdrcy^m!rS4%qApY=Rsi}DH{ICn_%-V9;vs6EHFpE$V zHZHv&H6%auHsFJ;%30$A;tGVw^d6!Xd*9xUG!``AvD9T-z43d=~_0JZmGUJfPfFoX-u~ zpj7OmcAdJt$r$))k70F4{&Kmj5qaPpE)Y#`Z3d>}6}u)8^n#Ch-1t0nE721b;m-d0 zSQ;3J)hi(K&8xsSr{(1~&8k=KvH;2`&F^?KFfe%yc$Iqy?R(N*aBcv(krWRV69cG5 zhXGS54GfPAWxl_e&#h45yJ7?1^#j=z>M5yEi-{%Faxmn}bgWTj+78I_i<4CZX!zR{ zzdKZ-vlQq0V|mttSBhyB3+-Rb8Ed=BRQ=-%hxoi4zIXsRS0T=-7$KQesOA4r(N%Oa zhyR3{Y|eNyGG3Ig$aWhwA}4Om<3{TR9F0WtF76~b6G8$|J0JUD-3O3tJ7?9yT~>1$ zw`B#W^I$T6-rL~&5!ni{VnM3XLfdJKT<`laK?&s7e{77y*sF)?tcd(N=m48yiJnChl?lDc9sV=-hBJ=k0w)rr4N2d3fD zWCTxQHt+PoE{=lPZzrii88`S-M1W||H=V@yAn4O6dCdKxm$>KBFLNvEfcaP2EJ!h< zG!jvOkNr^i3Q-^{*aZ>4ImI8q-EsHgWUKJuY{gMOY!&_Bl<#~pTd^mJ?dbmSTX&(S zg)o|ZTC@ebEdk7QCg4c)dw|CEng+$!jFnC@koqkcJ2|HiEa~(gfugHkK%xKz6nUb3 z=eDtXI%Z*pAxZoTzppTdBX#~S!sP3BF~BSiInbzZh)m1a#T zu{G^cr52$%k4J{*eRjRRcjHp>4K+;T{w$fMq3tSL$>0+0!ZMie!mAvpXhTYm#k; zzw^!M3axpnQqAOdfIraFv;PNNhdfyDC*N#ZS2OJP_m>`bpe-1CM!dIw%ceTPaik6n z-ueF^VIs>UWKB;4ie!MZ_Nmz;;b;DD?w|x&^gn3Lch;Lmm$1inJ!8cklrTW0e7ypY J4?S>M{{a`0p0)r0 diff --git a/_static/thumbs/rotoselect_structure.png b/_static/thumbs/rotoselect_structure.png deleted file mode 100644 index a38f3318e3e7980742e3f08d78de6f68732c3356..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7147 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%0DXDS<-Xr4o0d9gEKp^N$4*EEvcF1)gKc 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

KDF=w{}qpy>(lDXw7JKc-I?fpx*{1YjW zYqLPy2&ag{^D(1XLK1k)xRX{%ktmY_5T^qdgmi{o23Z9cWJyKLVDJf+3yn}E!uAzx zG*2wgnj#xvcl6xY114`zHJTc6d902fWAaE6*+)0a5LJFDGb|#H?g(|x#EJ=ZkCW85&8cB@5Afd zhg#s#eA}OZm`7MX^S?yw;_n;B`&)$cN8IQzHAsaqomhkbQvd^qB>;}1H`1n(&`E(* z*BX#6aX>$uu=+d(fSYt0oUO>4gnD-tp`=Ho^2zK|*~6|i+b?o%SCsuJVqW(QsfGq?JRfrCljGWqf{+@4XW9s%~|PvGn| zGOAGyjt8;xY>&+T@HVwX19Vn;#JJ5gpdCmYV|(U2sN24){vmjj>4uMCCPMk(Z*JaZ zO5~p96T)7(eXRsbzK%FcAS*)oIgUEBcO{|U>K?BA$~IZf3OD{Dr_F+SGwx1%2NRrE zRk+}SgfsbB3LNkUQ(f74^O}wl1lpmlXDmN}- zl+V@xGhtwA+dO;j>H#2#_+os22uzuY!FfEAGCFC%N>m5Bkyo;np19I9iCz5%`2CI! z)4!1qSG^hTf%M7lG7qJ{SJ#b~fX%IyL^L*kxoy+Gib;S1aJhr6RzId$+4zOE(S<&) z^D(i-m1HSnBxsUNJ8YvHgD*R3DP^R2=>T>jddhS2mXL^uDjm3mdWpcPf&?U0y|~VE^$p-+(xXs<* z2@J8AD=6Hd%KI=e{%T50w~?OBTyu;&$?&5~3{>EDtVN*&r5T~0+7h#0Z&&N@Ybt~y z6Z=9y!KwJ#HF7wSRe5_FHOQs_2QKFRExMNcIEVrIl7E zdPto8^Lg{zL;{~x7x3^={7^T0{7=9ymY5lD5t1>kMg}$Job3~m$8~YSl8>D#1{^AH z3Nj+OyYE0a!Bp!KkgE7ELe0Z>Nw@#3s4QS>8T#kdo}#x$L*M(X_drs$R6L}o%CyVW z!H2k&VE?N~VKO|y_D?}!{+`6b08&7D0$zqRBu+lP_PIEZ$rxTd$3HfqN;ysXU5iFV*W-cp+YswcDSwE zm_?JeJrf7*WQ?3-roLh+#lpbR-He~RoUB9F>pYTz*jlvWu_)2nNOctv(%2qJ=IT^H z?jON~R6Z%%@s|~&*1su(goF3ZnRZCY2?ToQpx*Zo7h@c6#_=siV4iB>_~iQ zVCkQ&+rQ#9J_DJHY2MHGSVNqETu-N{1~3a%aIXQ^R`No*mRhCe8=MsAR=j{osp;#V zyEiZBhccTnNRS4(>`8}p#1ns#|2k$;Ij@r54{8Za$4!qTv;;qX+hu!q&H;MiLG$&e zpP(PFZieO#%Q>p(SE3YfmH;c(k&T$nk$0mjeA$i6Ps-W)#XuQ}BZd1ykFB3R@t1cg zft`cZ=bu^@3}=V)IlJ}_BX}L@k0Lh|F@p+Fsg^2aAp!C< z{tQ3e@pTyUh4htHUq$VhBWH-SD6L3@G^rXSY@pcKL4CS1L#Q)KXt@Mxx2gqTtM`i7 z(fI*C8rKpa3Y z1y}aqUq;CU8K=2@D0M`!>T(>|)EP4LoS~%Vo=qWv*t!1m{XE9e^XlSKU&sdiaPdvZ zgocOBd`0b1{7k$+m(`BFRA1RaJQ%l(%*kVBi&gq#$bdlGzCm}c@fkrvyne+L@PC}G z)I-P=!-%z(f2<9IIxlh>xhUb>cpQy zm`YS5B|%Z4#I`GyCxlX#yei&=xwti|5 zQ7(DuNQ6mS>L(0Qz1f}hxqFU%7jOqvMl%v5til3>zsSHzA z+7?#4qN}Bp^-(MVIQ-SdQ(1iN6d97`BHc7WFf4dNO{@GhUln|G9=@`Lpm^qRA0iH%8Q^_3J?6tCC%_ z*7&bKE&a8<9tUWj0|bT0)pZSt&%Ta>_e3Me_{ZYoz86`- zBEw@|qaw{jOU+^=8ZBFnwwR|NJ8OLa`9KNGnc0U-Ds$J)cQa_}qejfRFX6>8UD}=7 z$Qoh_bWpg_4E~O4=sAwi0{r!;Kp*et8T&_S9El5W!YJDwjA8b3`l0=vfD@7^^Lj}A zN`Y!ADIw|sOUvJ60mGE!dG4wcPq*+giu2?)@&V+?%u)##nDhBq)BG6bjew9@!(9B%^^m z?DTSl1~}4FyqLl`Sdp#mAMms$2#?6)jtM*q`{?Rdtp z6J_e}Bk3O)_Zfjs9s@}U(U!_Cz~m_4q+5)4x$Y0#LjE&^h;xLjNE}-2ZZfwz;T{v zq_}Y$9J9A3Ync1$h(0#e@uC)18hWU6zY+4$)1yvc17qnh2~q=pSgAVlf*4;xv*Xr2 zwW~_sWspzqQa>a7K^QH|9X<`!F(}TCCP(vLqF4QF=ctyltrUtCtZB;dOqAJV%CzXd zx}zuCUTNi~8aI!4P;cBPCoTBp&)*G&X4RLt1NiQmu)6HH0yA6`cB|=YCXeXQ3QBOx zs~_#SGnXVSq`+u4MdTX|D67FTS7p^$2CXtpfd2b}LmVhdo0~k2mZ-8I0N0~r?eswC zPgwiw)Z-UMoZ)bN=W6MDut;dYm}_)ZHxoZ9p!U18ctE>hLpoNTEM{Om&aTAWm7NJ+ zEC7_P=lp}{%R^tRd3v|E*}z-j;il*Bx8vB<`v@OHTzhb3!TTz zdiloPVfKvqGTKrqKfBd9X@wCmWH^F0a8LEBwzru@riM}?TGA;oEtRR~7}-Q@aP@H- z_ON9=K-3_tr;9cPf&gU}M?qXxZZ7PGxP~F69MbcHz~2Twvx0ayA)9uj#w;82Cg!R; zCxO-)Dk$)>+7;K!+^H&+@3G%IeEx!lG{rfWpj~P?L{tejE|4LFFa=gGKo@MiSOk3W@Ev7xti6ge2Auy|I@O+RFbLBuNWYGcV!R3^yg8e z*fLW6Ev1b@&r?LdAa=>8FdgriTyN+7h_*J>`i@cCayJ0#DGGxghA`giXo~FoeTY<_ zW^Ye|FgQTp^254Ai%JtvLjfCpTqtdZhjEbShSagi=2>V^QBRq2cpTuS03rmOOL4nVV)jA`pTW( z6XX*hsYi5XE-noTdHHSQgtOg1qlbi{&WFa3NSfcc=;246u!%60aFQqut_kwwT@+lM zZXZ+<;@;8QqNWlWM2ysljM33bU}D0x4(e}N0>Hpsfub49>FnJT4{{02n4*)M=)8Za zpKVm+0J1uz-a@u+3#H=)xZ;XK5;jRxTSmqnfTj-7%arQ?ov!(qR7f{*D5S~k})+;d0yz=b^T z<^GM`f=_tICZ)D~Nv&Wu=(U!;9f@NC1gHJj5QKEB(o_K+J52V-2qX5+Ck7bOYVQ#^ zcAqMfp)4l;jg_7IcGtRFgTBXA2PN-Op(-GDQNSs{IIa)Z=xB=Ed$=3W?6 znj}sIzII{-<)jO|h~prIbGCNj%6`eDyrR5faSvlCpLKbcpC8Xs#n)<-+&>rB$wm8{ z=OB)np$yr6pE7pyo0miY^gLFaR_KdoV zSq<7ic1%Arlk<0NQb`qBX~eYebUhLh2XujWZUA0^Qr_2}VH)q~vyZtqLWjAAsy5W5 ziOyD%>wXCB^=z=-Dqx%dF4!-^R0avLRKymMP5V^7=M??3+YZX!Y+kTpOOYTKFEqpJo zv9g|j$czbGkJRHIXSU+svgzbefc(3o;dW$rhYshykKBlmOGs#yo8jqvR;Xaq8_5Fq zuFljl(!5}P|D~9PYR0Upt!rRfI&wl;IJDhvRnD^vHuR@lKs>$zShinqO1JNL7uoep zX*w$p5H1h_R-8cSrB&THM?7)exRe}55aqJ)`CMKYDfD7Z2V2gc9> z&M#V<9fBjDfzUubcJWH&`B1#xtx+cPd;Py^)i7W*MMp=+ zF=!8f2^{{tJ^=7CEkkc!RXHF*^2MvqpHh12eXNsb*^%HpwI})T4$mRFXz^QFA$)Cl zrK!?z^FY3QG3$sngX!D?KlZybtI9=i0va^{mqkO{@z~5*IdYn8dhjgYpuTYZi4dX5 z^hm2{U-V_jO|7htS4qf4bd6$pWPB6BA>oUHq&3ZAw#ejaj}4RA;#c%HV1P7?-5WkTQ`aBS3;ejdl(Rc&{yF0 zl5r5uuFplJQV;w4BJ9uz9$krT5-%f_&JAZuF3Chs70rTeo<)>`dCRH3TX;M*g_jzyIi=YpHs+a2WtN5~sohNydR9#q>9a>}0D%Yo}8b z;K^rM<~_{#DG=n~ow(5llK5|in^N__KPK7GN6?7y24F;FzGYA{ylzHdL!ZQeOT66U zOh(X#O=88}NPU*)O+Nnc1F}91x~vV0055Fcf@i9C-Y`MoTUI2de{L-nF*jpsy?d#t zgT~GK;sLJ{;o#TV;yP!-4@i~@uE)-+q|J4`L>Ae@`w28rG#PWIskHS7g}8�F5Tb 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

+IjyZf*$U4v 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

h(ic>EG~?C+3L_xOW?^IsbOCJwj|xEMBL zH?Z*O2}IKT2{ww19svCIt-+qnUEXF1ZrLmO@ZEcQG>3WrV*488`ErH%_+U}T%3|gv z2OzOvQfvNCoO##3t^$eq`@wvSY-#AU`rB&ScbxF1JtSb($Rbe8zaaXWrcE&Cv*W|` z^8SiVi1Mg)^Ac2#e)Q`1j7`R$fu6_<+ksR%r`j2@5v7(y^X7P<2YP1Th21!T06wUQ z$~2r@G|C>hJ!8rS1g5Uz85!5Ax%1GB>oxpe#p__mqFgV5e{`>^u{oE{4wK>DI)^}6 z@{a#)KBDuvkqe4Viy9`{^_lXBCPeY6kenBm+vhd_jIk=iK+b&wz%%m<4u zT?RZdzEs~!l@PC+K;J*g^|lM?mQ)I9?GBsiomSXhc`d8i9SpP>+7VEW?_}&MF)bqZ zKy_%{Fk@46R1_~VcxSJtVUrx7;&I3Gx7z~m-?wq$eJy>B(yBqkZvK*lD>;4?m$)nb zfYguclpT_KeRjYuMpH7cWaC!y0D(cpTO`-|5qyA%3(&n`)wtb#}G*TL;bfQGY$lp!J+h ztFXr8^`o%-o?uM*?bRjK124M&27^MA6~lWzx4&;B$SUf~RL0+zFSD_bY^*3_z_#@} zR_WWP@-!Yo!so<3gtik)sVcr64GID6VYJRkQWpf%= zsrNr?Z0%^|vg+C;6K(iO7vVI@iZGdw&_*dXb$U>@_j<4Q*tA@~aZj)<`o^&~eC3;S z0BOu+12sWN^y5F}=_Cw*(C zvvRX-?R`$jMuS@UK_oxKu4^bI0r<0E~wbzyjq7tq*|!|3tot?2H_2nUi5Z z#G0^05~g?TYR^Z?3nPaP!$E%NB(+}&Hhij6U z0N~!j&tm(jc=quL-CwavO}|PcOg`?{-RQU8A-(85AiqNBq8~o5a-X@Wci^B+vI!KQ zPqu2JPu^Sov2TmWn0#hYlp7@EA}xQ$5DzHsE_%!zin)EWA0vS&{R!4u$4b7&>|59< zJ4%0S!ndY=Ob`jx*JNrthsjH`Iy`h43vjPsySBO=klD}}53%!7E<+x@$$R3VtnpwP z6iS`v5P&nWa*N%1eOr*GYv?sJ$>>}i(Cw?1u4y1I2WZhu-9{jq*suF~9vAMsoEM_+ zP7rnkLZtX}VP}=}eE4hl6^Xn1EyUCp$fEq8?_})`GphaAX@~3FD)^6V?iu^iWh>Lx zfo%H%9h( z@2&S%y=B&(4=+{7qlpiMszs>|=yB&E)f2!Bp-Mdp6QsNps7eu%tw_Kfa{u>YhBm?w z>HgvpYgLC(96%qRmdyO*&w!2Wt zpEr;*FuR!U($jDpA1t7}e99z8D{jiOA)@j5)Qje0Q-O0)J?}f$f2FS&Tv9%I!`wa4 zQ|MObp=SO;#ce}kOfj=BwzBE|V}BD~9yH|sp=udAIHk;^fxmlwA$_u@WTG~LABBAT zaq~0OAh5|6jN`L@ywKF2?b&prj*eyibT;N;lnL?shza3)O1!Lj^ZOD-UeC)e%VXD$ z-6>sftwocip+DJ2`JK6t10UEaozk~cL;)bIh49)^Cb+~RJbQ5#>yM7brSj= z_1B)G_+%p&S5$A)Bt-RSu$!;8$7y)TtaPF({4O#_T$1}^m5r!wGu$yXuOsA2LsUZQvc0b!PiMc!T57%QV>X!uW%Y@x+35ud7CZW=#q? zno;z8?8C(Lo*0rXwiF6#<({SMFCTmWGkzJ@k5osq9KU~p_x*HdZnHqef9)-CadD_2 zM8}x#yc8fl@cPNwT(mRI6(^`2euP&f76n<>1_G?#2g+iPQkAoTh#voYmRqAsy3QN~ z=uXi`C(lT~e#aL^>SjeC6B~=u-Z>x3O0I*vkMc4QUC-g%ptk515xJSik+K~IV4>m? zW2MO5^?MJa;(b?#QjUPLQNa35uq9?~68)eWXs6kk;mVd}oC>lI^UW)FbC z2ht))lZ*U!o@V?@2!~DT|!n5T)TZAq4KC;DNWWVzHpJsJLC#_Cg}%1a*3Q1@g@KPHHa zwrpR)h0@tT4I;~vNP+PgFJ@NGRpZc59W`}a`Agfy-kN^LK8bt*g^HIuLTdjRaA}9j z$==H!6!~26J&tvwKIVj(BJl%q<~MVlEhcj1(zCHjdkdo%go<$SPUVA--v4xvIWSzq zpHzh(YN3xF4R_sBZD;4FtE7L4G~`b$DlKsS;dCS(<<%ulppLLXtDM^$$F9=jUy(yH z>Kp@nci&w29g}Ap9ge%>Q zyNoJPGujJHXdhaH+i=UrH|CyGA!vrI<0U4bX?ZI5Y~Tk;!;2%O%@lj*I(Z4a!dx8M z=64tao<_^lem?HPG!xr<_F=@3BC}4$`!vxiwnb3$sWfvc126+7pSmhpM*&aum zgT0e5&akm-HyA&joUrr(d>eMw$;yNjUBR8TF%%0jyxg5zupz>C-5Gq2(MZ;aKj>)& zWk2xoD|Q|(hHCo8W#0UW_xRpp*;Sqi5tmchpG6c(?;~jI2_G-3yeNIY#iMb^jVtE@ z1r0KJG%wyg*drSs`bH#gu{8Q!Ssd&xu5#JNO#2dMANpmkRy1(f9s67lxaRzNxMu31 z01WoKblhh)EWw-f&gxVIdqRzJz{|l5j1g@~rlN7fwJ~7Lun(*L{LM)YG3_j9UQi58 zZW*NJCnS03>u`glSP*EoWI2rrlg(|#?sEHVzt{Y%ZI(eFSA9VOhewIh`89D=T{&$L zt)c+vs^z+lzw`)6e7J=dbx)GFETp)?xlrS!(*@rRgRBFMJj9`D^TCcfi+%SEQoo4c zwUuio)73Fk9?fhv=~@(1WGzSZxx|&-P0F)@&AhBDh9EzgViTd?!+A*jJNe2_p5*>J z^w+mUa+l1V5pD@udx3OHYt3{cQ5L?pj9K~cY6+Lme|GMEPZ2cTk;#YsU5qoy>&Q-t z(AJ4s#Cpz37uRz)Uk`BYdZXBMG#zV=>@F>z*c8y5^_9Fy@!6n}9-%UOFGPpti}hQNKL9+|bxD$;}p zAcqo~{a(WkOY1zrni?&)N6Pc`Z3^o*l3(BA|XhLBoQ*YkO1}fG0`#Pr_}#lDWh2W{@ms?FF!Af0^e6# zYK+EawLF;`>QL8St9U(u0LOF)A!jGu96(Usp%P`2BWl|S-m?xYqo%I--8K7%FK+6+ zOV$;~m)ik!_}a~1vm9+rVrUus>wyV}5PzCYD3V#ObouhnY$Zjt6=rdj^Bd(eg+;z1 zv%OZ`srm8KQQOO}LydoHe#I}0<0Q_FF8>M8azkBn+0Vm-fYgUuO<`$Xb8Su&d7vK` zvd61FJ#`_@*@zkMvHTC%yH;BC|6{!KOmzEL^ZySCZhqT*@*np5e}Vh`pYQpfpvnIs iK>Yu1^uIxC)5YYMHY2Mw=p-iKV|3f(R>e*G|Na-Ab^X!+ diff --git a/_static/thumbs/universal_dnn.png b/_static/thumbs/universal_dnn.png deleted file mode 100644 index bdfdd16edff347a9a348a4ccea6897d5e0ac1f52..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40857 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 Al>h($ diff --git a/_static/thumbs/vqls_zoom.png b/_static/thumbs/vqls_zoom.png deleted file mode 100644 index 1bce7b344c31f78aca573bacc8453affbaeffdd9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12068 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 N|KgQ&;WItI{{;%dZU6uP diff --git a/_templates/filters.html b/_templates/filters.html index 3d1f538d09..4a5f65f74e 100644 --- a/_templates/filters.html +++ b/_templates/filters.html @@ -1,115 +1,115 @@ -

-
-
-
- -
-

Filters

-
- - - - - - - - - - - -
    -
  1. -
  2. -
  3. -
  4. -
  5. -
  6. - -
  7. -
  8. -
  9. -
  10. -
  11. -
-
-
-
- - - - +
+
+
+
+ +
+

Filters

+
+ + + + + + + + + + + +
    +
  1. +
  2. +
  3. +
  4. +
  5. +
  6. + +
  7. +
  8. +
  9. +
  10. +
  11. +
+
+
+
+ + + + diff --git a/_templates/page.html b/_templates/page.html index 1880e4aedd..2b0b379ec5 100644 --- a/_templates/page.html +++ b/_templates/page.html @@ -1,30 +1,30 @@ -{% extends "layout.html" %} - -{%- block comments -%} - {% if pagename in ["demos_getting-started", "demos_qml", "demos_optimization", "demos_quantum-computing", "demos_quantum-chemistry"] %} - {% include "filters.html" %} - {% elif pagename == "videos" %} - {% else %} - {% include "localtoc.html" %} - {% endif %} -{%- endblock %} - -{% block body %} - {{ body }} - - -{% endblock %} +{% extends "layout.html" %} + +{%- block comments -%} + {% if pagename in ["demos_getting-started", "demos_qml", "demos_optimization", "demos_quantum-computing", "demos_quantum-chemistry"] %} + {% include "filters.html" %} + {% elif pagename == "videos" %} + {% else %} + {% include "localtoc.html" %} + {% endif %} +{%- endblock %} + +{% block body %} + {{ body }} + + +{% endblock %} diff --git a/demonstrations/barren_gadgets/barren_gadgets.py b/demonstrations/barren_gadgets/barren_gadgets.py index 0e2929dc41..09d3c89df7 100644 --- a/demonstrations/barren_gadgets/barren_gadgets.py +++ b/demonstrations/barren_gadgets/barren_gadgets.py @@ -1,130 +1,130 @@ -import pennylane as qml -from pennylane import numpy as np - -def non_identity_obs(obs): - return [o for o in obs if not isinstance(o, qml.Identity)] - -class PerturbativeGadgets: - """ Class to generate the gadget Hamiltonian corresponding to a given - computational hamiltonian according to the gadget construction derived - by Faehrmann & Cichy - - Args: - perturbation_factor (float) : parameter controlling the magnitude of the - perturbation (aa pre-factor to \lambda_max) - """ - def __init__(self, perturbation_factor=1): - self.perturbation_factor = perturbation_factor - - def gadgetize(self, Hamiltonian, target_locality=3): - """Generation of the perturbative gadget equivalent of the given - Hamiltonian according to the proceedure in Cichy, Fährmann et al. - Args: - Hamiltonian (qml.Hamiltonian) : target Hamiltonian to decompose - into more local terms - target_locality (int > 2) : desired locality of the resulting - gadget Hamiltonian - Returns: - Hgad (qml.Hamiltonian) : gadget Hamiltonian - """ - # checking for unaccounted for situations - self.run_checks(Hamiltonian, target_locality) - computational_qubits, computational_locality, computational_terms = self.get_params(Hamiltonian) - Hamiltonian_coeffs, Hamiltonian_ops = Hamiltonian.terms() - - # total qubit count, updated progressively when adding ancillaries - total_qubits = computational_qubits - #TODO: check proper convergence guarantee - gap = 1 - perturbation_norm = np.sum(np.abs(Hamiltonian_coeffs)) \ - + computational_terms * (computational_locality - 1) - lambda_max = gap / (4 * perturbation_norm) - l = self.perturbation_factor * lambda_max - sign_correction = (-1)**(computational_locality % 2 + 1) - # creating the gadget Hamiltonian - coeffs_anc = [] - coeffs_pert = [] - obs_anc = [] - obs_pert = [] - ancillary_register_size = int(computational_locality / (target_locality - 2)) - for str_count, string in enumerate(Hamiltonian_ops): - previous_total = total_qubits - total_qubits += ancillary_register_size - # Generating the ancillary part - for anc_q in range(previous_total, total_qubits): - coeffs_anc += [0.5, -0.5] - obs_anc += [qml.Identity(anc_q), qml.PauliZ(anc_q)] - # Generating the perturbative part - for anc_q in range(ancillary_register_size): - term = qml.PauliX(previous_total+anc_q) @ qml.PauliX(previous_total+(anc_q+1)%ancillary_register_size) - term = qml.prod(term, *non_identity_obs(string.operands)[ - (target_locality-2)*anc_q:(target_locality-2)*(anc_q+1)]) - obs_pert.append(term) - coeffs_pert += [l * sign_correction * Hamiltonian_coeffs[str_count]] \ - + [l] * (ancillary_register_size - 1) - coeffs = coeffs_anc + coeffs_pert - obs = obs_anc + obs_pert - Hgad = qml.Hamiltonian(coeffs, obs) - return Hgad - - def get_params(self, Hamiltonian): - """ retrieving the parameters n, k and r from the given Hamiltonian - Args: - Hamiltonian (qml.Hamiltonian) : Hamiltonian from which to get the - relevant parameters - Returns: - computational_qubits (int) : total number of qubits acted upon by - the Hamiltonian - computational_locality (int) : maximum number of qubits acted upon - by a single term of the Hamiltonian - computational_terms (int) : number of terms in the sum - composing the Hamiltonian - """ - _, Hamiltonian_ops = Hamiltonian.terms() - # checking how many qubits the Hamiltonian acts on - computational_qubits = len(Hamiltonian.wires) - # getting the number of terms in the Hamiltonian - computational_terms = len(Hamiltonian_ops) - # getting the locality, assuming all terms have the same - computational_locality = max([len(non_identity_obs(Hamiltonian_ops[s])) - for s in range(computational_terms)]) - return computational_qubits, computational_locality, computational_terms - - def run_checks(self, Hamiltonian, target_locality): - """ method to check a few conditions for the correct application of - the methods - Args: - Hamiltonian (qml.Hamiltonian) : Hamiltonian of interest - target_locality (int > 2) : desired locality of the resulting - gadget Hamiltonian - Returns: - None - """ - _, Hamiltonian_ops = Hamiltonian.terms() - computational_qubits, computational_locality, _ = self.get_params(Hamiltonian) - computational_qubits = len(Hamiltonian.wires) - if computational_qubits != Hamiltonian.wires[-1] + 1: - raise Exception('The studied computational Hamiltonian is not acting on ' + - 'the first {} qubits. '.format(computational_qubits) + - 'Decomposition not implemented for this case') - # Check for same string lengths - localities=[] - for string in Hamiltonian_ops: - localities.append(len(non_identity_obs(string))) - if len(np.unique(localities)) > 1: - raise Exception('The given Hamiltonian has terms with different locality.' + - ' Gadgetization not implemented for this case') - # validity of the target locality given the computational locality - if target_locality < 3: - raise Exception('The target locality can not be smaller than 3') - ancillary_register_size = computational_locality / (target_locality - 2) - if int(ancillary_register_size) != ancillary_register_size: - raise Exception('The locality of the Hamiltonian and the target' + - ' locality are not compatible. The gadgetization' + - ' with "unfull" ancillary registers is not' + - ' supported yet. Please choose such that the' + - ' computational locality is divisible by the' + - ' target locality - 2') - - - +import pennylane as qml +from pennylane import numpy as np + +def non_identity_obs(obs): + return [o for o in obs if not isinstance(o, qml.Identity)] + +class PerturbativeGadgets: + """ Class to generate the gadget Hamiltonian corresponding to a given + computational hamiltonian according to the gadget construction derived + by Faehrmann & Cichy + + Args: + perturbation_factor (float) : parameter controlling the magnitude of the + perturbation (aa pre-factor to \lambda_max) + """ + def __init__(self, perturbation_factor=1): + self.perturbation_factor = perturbation_factor + + def gadgetize(self, Hamiltonian, target_locality=3): + """Generation of the perturbative gadget equivalent of the given + Hamiltonian according to the proceedure in Cichy, Fährmann et al. + Args: + Hamiltonian (qml.Hamiltonian) : target Hamiltonian to decompose + into more local terms + target_locality (int > 2) : desired locality of the resulting + gadget Hamiltonian + Returns: + Hgad (qml.Hamiltonian) : gadget Hamiltonian + """ + # checking for unaccounted for situations + self.run_checks(Hamiltonian, target_locality) + computational_qubits, computational_locality, computational_terms = self.get_params(Hamiltonian) + Hamiltonian_coeffs, Hamiltonian_ops = Hamiltonian.terms() + + # total qubit count, updated progressively when adding ancillaries + total_qubits = computational_qubits + #TODO: check proper convergence guarantee + gap = 1 + perturbation_norm = np.sum(np.abs(Hamiltonian_coeffs)) \ + + computational_terms * (computational_locality - 1) + lambda_max = gap / (4 * perturbation_norm) + l = self.perturbation_factor * lambda_max + sign_correction = (-1)**(computational_locality % 2 + 1) + # creating the gadget Hamiltonian + coeffs_anc = [] + coeffs_pert = [] + obs_anc = [] + obs_pert = [] + ancillary_register_size = int(computational_locality / (target_locality - 2)) + for str_count, string in enumerate(Hamiltonian_ops): + previous_total = total_qubits + total_qubits += ancillary_register_size + # Generating the ancillary part + for anc_q in range(previous_total, total_qubits): + coeffs_anc += [0.5, -0.5] + obs_anc += [qml.Identity(anc_q), qml.PauliZ(anc_q)] + # Generating the perturbative part + for anc_q in range(ancillary_register_size): + term = qml.PauliX(previous_total+anc_q) @ qml.PauliX(previous_total+(anc_q+1)%ancillary_register_size) + term = qml.prod(term, *non_identity_obs(string.operands)[ + (target_locality-2)*anc_q:(target_locality-2)*(anc_q+1)]) + obs_pert.append(term) + coeffs_pert += [l * sign_correction * Hamiltonian_coeffs[str_count]] \ + + [l] * (ancillary_register_size - 1) + coeffs = coeffs_anc + coeffs_pert + obs = obs_anc + obs_pert + Hgad = qml.Hamiltonian(coeffs, obs) + return Hgad + + def get_params(self, Hamiltonian): + """ retrieving the parameters n, k and r from the given Hamiltonian + Args: + Hamiltonian (qml.Hamiltonian) : Hamiltonian from which to get the + relevant parameters + Returns: + computational_qubits (int) : total number of qubits acted upon by + the Hamiltonian + computational_locality (int) : maximum number of qubits acted upon + by a single term of the Hamiltonian + computational_terms (int) : number of terms in the sum + composing the Hamiltonian + """ + _, Hamiltonian_ops = Hamiltonian.terms() + # checking how many qubits the Hamiltonian acts on + computational_qubits = len(Hamiltonian.wires) + # getting the number of terms in the Hamiltonian + computational_terms = len(Hamiltonian_ops) + # getting the locality, assuming all terms have the same + computational_locality = max([len(non_identity_obs(Hamiltonian_ops[s])) + for s in range(computational_terms)]) + return computational_qubits, computational_locality, computational_terms + + def run_checks(self, Hamiltonian, target_locality): + """ method to check a few conditions for the correct application of + the methods + Args: + Hamiltonian (qml.Hamiltonian) : Hamiltonian of interest + target_locality (int > 2) : desired locality of the resulting + gadget Hamiltonian + Returns: + None + """ + _, Hamiltonian_ops = Hamiltonian.terms() + computational_qubits, computational_locality, _ = self.get_params(Hamiltonian) + computational_qubits = len(Hamiltonian.wires) + if computational_qubits != Hamiltonian.wires[-1] + 1: + raise Exception('The studied computational Hamiltonian is not acting on ' + + 'the first {} qubits. '.format(computational_qubits) + + 'Decomposition not implemented for this case') + # Check for same string lengths + localities=[] + for string in Hamiltonian_ops: + localities.append(len(non_identity_obs(string))) + if len(np.unique(localities)) > 1: + raise Exception('The given Hamiltonian has terms with different locality.' + + ' Gadgetization not implemented for this case') + # validity of the target locality given the computational locality + if target_locality < 3: + raise Exception('The target locality can not be smaller than 3') + ancillary_register_size = computational_locality / (target_locality - 2) + if int(ancillary_register_size) != ancillary_register_size: + raise Exception('The locality of the Hamiltonian and the target' + + ' locality are not compatible. The gadgetization' + + ' with "unfull" ancillary registers is not' + + ' supported yet. Please choose such that the' + + ' computational locality is divisible by the' + + ' target locality - 2') + + + diff --git a/demonstrations/barren_gadgets/layered_ansatz.py b/demonstrations/barren_gadgets/layered_ansatz.py index 7a26d9e059..22f324835e 100644 --- a/demonstrations/barren_gadgets/layered_ansatz.py +++ b/demonstrations/barren_gadgets/layered_ansatz.py @@ -1,54 +1,54 @@ -import pennylane as qml -from pennylane import numpy as np - -""" Based on the SimplifiedTwoDesign template from pennylane -https://docs.pennylane.ai/en/latest/code/api/pennylane.SimplifiedTwoDesign.html -as proposed in `Cerezo et al. (2021) `_. -but changin the Y-rotations for a random choice of {X, Y, Z}-rotations. -""" - -def build_ansatz(initial_layer_weights, weights, wires, gate_sequence=None): - - n_layers = qml.math.shape(weights)[0] - op_list = [] - - # initial rotations - for i in range(len(wires)): - op_list.append(qml.RY(initial_layer_weights[i], wires=wires[i])) - - # generating the rotation sequence - if gate_sequence is None: - gate_sequence = generate_random_gate_sequence(qml.math.shape(weights)) - - # repeated layers - for layer in range(n_layers): - - # even layer of entanglers - even_wires = [wires[i : i + 2] for i in range(0, len(wires) - 1, 2)] - for i, wire_pair in enumerate(even_wires): - op_list.append(qml.CZ(wires=wire_pair)) - op_list.append(gate_sequence[layer, i, 0].item()(weights[layer, i, 0], wires=wire_pair[0])) - op_list.append(gate_sequence[layer, i, 1].item()(weights[layer, i, 1], wires=wire_pair[1])) - # op_list.append(qml.RX(weights[layer, i, 0], wires=wire_pair[0])) - # op_list.append(qml.RX(weights[layer, i, 1], wires=wire_pair[1])) - - # odd layer of entanglers - odd_wires = [wires[i : i + 2] for i in range(1, len(wires) - 1, 2)] - for i, wire_pair in enumerate(odd_wires): - op_list.append(qml.CZ(wires=wire_pair)) - op_list.append(gate_sequence[layer, len(wires) // 2 + i, 0].item()(weights[layer, len(wires) // 2 + i, 0], wires=wire_pair[0])) - op_list.append(gate_sequence[layer, len(wires) // 2 + i, 1].item()(weights[layer, len(wires) // 2 + i, 1], wires=wire_pair[1])) - # op_list.append(qml.RX(weights[layer, len(wires) // 2 + i, 0], wires=wire_pair[0])) - # op_list.append(qml.RX(weights[layer, len(wires) // 2 + i, 1], wires=wire_pair[1])) - - return op_list - -def generate_random_gate_sequence(shape): - gate_set = [qml.RX, qml.RY, qml.RZ] - return np.random.choice(gate_set, size=shape) - -def get_parameter_shape(n_layers, n_wires): - if n_wires == 1: - return [(n_wires,), (n_layers,)] - return [(n_wires,), (n_layers, n_wires - 1, 2)] - +import pennylane as qml +from pennylane import numpy as np + +""" Based on the SimplifiedTwoDesign template from pennylane +https://docs.pennylane.ai/en/latest/code/api/pennylane.SimplifiedTwoDesign.html +as proposed in `Cerezo et al. (2021) `_. +but changin the Y-rotations for a random choice of {X, Y, Z}-rotations. +""" + +def build_ansatz(initial_layer_weights, weights, wires, gate_sequence=None): + + n_layers = qml.math.shape(weights)[0] + op_list = [] + + # initial rotations + for i in range(len(wires)): + op_list.append(qml.RY(initial_layer_weights[i], wires=wires[i])) + + # generating the rotation sequence + if gate_sequence is None: + gate_sequence = generate_random_gate_sequence(qml.math.shape(weights)) + + # repeated layers + for layer in range(n_layers): + + # even layer of entanglers + even_wires = [wires[i : i + 2] for i in range(0, len(wires) - 1, 2)] + for i, wire_pair in enumerate(even_wires): + op_list.append(qml.CZ(wires=wire_pair)) + op_list.append(gate_sequence[layer, i, 0].item()(weights[layer, i, 0], wires=wire_pair[0])) + op_list.append(gate_sequence[layer, i, 1].item()(weights[layer, i, 1], wires=wire_pair[1])) + # op_list.append(qml.RX(weights[layer, i, 0], wires=wire_pair[0])) + # op_list.append(qml.RX(weights[layer, i, 1], wires=wire_pair[1])) + + # odd layer of entanglers + odd_wires = [wires[i : i + 2] for i in range(1, len(wires) - 1, 2)] + for i, wire_pair in enumerate(odd_wires): + op_list.append(qml.CZ(wires=wire_pair)) + op_list.append(gate_sequence[layer, len(wires) // 2 + i, 0].item()(weights[layer, len(wires) // 2 + i, 0], wires=wire_pair[0])) + op_list.append(gate_sequence[layer, len(wires) // 2 + i, 1].item()(weights[layer, len(wires) // 2 + i, 1], wires=wire_pair[1])) + # op_list.append(qml.RX(weights[layer, len(wires) // 2 + i, 0], wires=wire_pair[0])) + # op_list.append(qml.RX(weights[layer, len(wires) // 2 + i, 1], wires=wire_pair[1])) + + return op_list + +def generate_random_gate_sequence(shape): + gate_set = [qml.RX, qml.RY, qml.RZ] + return np.random.choice(gate_set, size=shape) + +def get_parameter_shape(n_layers, n_wires): + if n_wires == 1: + return [(n_wires,), (n_layers,)] + return [(n_wires,), (n_layers, n_wires - 1, 2)] + diff --git a/demonstrations/ensemble_multi_qpu.py b/demonstrations/ensemble_multi_qpu.py index eaf0bc3ea3..8b68a43741 100644 --- a/demonstrations/ensemble_multi_qpu.py +++ b/demonstrations/ensemble_multi_qpu.py @@ -1,581 +1,581 @@ -r""" -Ensemble classification with Rigetti and Qiskit devices -======================================================= - -.. meta:: - :property="og:description": We demonstrate how two QPUs can be - combined in parallel to help solve a machine learning classification problem, - using PyTorch and PennyLane. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/ensemble_diagram.png - -.. related - - tutorial_variational_classifier Variational classifier - -*Author: Tom Bromley — Posted: 14 February 2020. Last updated: 13 December 2021.* - -This tutorial outlines how two QPUs can be combined in parallel to help solve a machine learning -classification problem. - -.. warning:: - This demo does not work with the latest version of Qiskit or the Pennylane-Qiskit plugin and - is compatible with Python versions 3.10 and lower. It is compatible with ``qiskit==0.46`` and - ``pennylane-qiskit==0.35.1``. Older versions of Qiskit and the Pennylane-Qiskit plugin should - not be installed in environments with an existing installation of Qiskit 1.0 or above. - -We use the ``rigetti.qvm`` device to simulate one QPU and the ``qiskit.aer`` device to -simulate another. Each QPU makes an independent prediction, and an ensemble model is -formed by choosing the prediction of the most confident QPU. The iris dataset is used in this -tutorial, consisting of three classes of iris flower. Using a pre-trained model and the PyTorch -interface, we'll see that ensembling allows the QPUs to specialize towards -different classes. - -Let's begin by importing the prerequisite libraries: -""" - -from collections import Counter - -import dask -import matplotlib.pyplot as plt -import numpy as np -import pennylane as qml -import sklearn.datasets -import sklearn.decomposition -import torch -from matplotlib.lines import Line2D -from matplotlib.patches import Patch - -############################################################################## -# This tutorial requires the ``pennylane-rigetti`` and ``pennylane-qiskit`` packages, which can be -# installed by following the instructions `here `__. We also -# make use of the `PyTorch interface `_, which can be installed from `here -# `__. -# -# .. warning:: -# Rigetti's QVM and Quil Compiler services must be running for this tutorial to execute. They -# can be installed by consulting the `Rigetti documentation -# `__ or, for users with Docker, by running: -# -# .. code-block:: bash -# -# docker run -d -p 5555:5555 rigetti/quilc -R -p 5555 -# docker run -d -p 5000:5000 rigetti/qvm -S -p 5000 -# -# Load data -# --------- -# -# The next step is to load the iris dataset. - -n_features = 2 -n_classes = 3 -n_samples = 150 - -data = sklearn.datasets.load_iris() -x = data["data"] -y = data["target"] - -############################################################################## -# We shuffle the data and then embed the four features into a two-dimensional space for ease of -# plotting later on. The first two principal components of the data are used. - -np.random.seed(1967) - -data_order = np.random.permutation(np.arange(n_samples)) -x, y = x[data_order], y[data_order] - -pca = sklearn.decomposition.PCA(n_components=n_features) -pca.fit(x) -x = pca.transform(x) - -############################################################################## -# We will be encoding these two features into quantum circuits using :class:`~.pennylane.RX` -# rotations, and hence renormalize our features to be between :math:`[-\pi, \pi].` - - -x_min = np.min(x, axis=0) -x_max = np.max(x, axis=0) - -x = 2 * np.pi * (x - x_min) / (x_max - x_min) - np.pi - -############################################################################## -# The data is split between a training and a test set. This tutorial uses a model that is -# pre-trained on the training set. - - -split = 125 - -x_train = x[:split] -x_test = x[split:] -y_train = y[:split] -y_test = y[split:] - -############################################################################## -# Finally, let's take a quick look at our data: - - -colours = ["#ec6f86", "#4573e7", "#ad61ed"] - - -def plot_points(x_train, y_train, x_test, y_test): - c_train = [] - c_test = [] - - for y in y_train: - c_train.append(colours[y]) - - for y in y_test: - c_test.append(colours[y]) - - plt.scatter(x_train[:, 0], x_train[:, 1], c=c_train) - plt.scatter(x_test[:, 0], x_test[:, 1], c=c_test, marker="x") - - plt.xlabel("Feature 1", fontsize=16) - plt.ylabel("Feature 2", fontsize=16) - - ax = plt.gca() - ax.set_aspect(1) - - c_transparent = "#00000000" - - custom_lines = [ - Patch(facecolor=colours[0], edgecolor=c_transparent, label="Class 0"), - Patch(facecolor=colours[1], edgecolor=c_transparent, label="Class 1"), - Patch(facecolor=colours[2], edgecolor=c_transparent, label="Class 2"), - Line2D([0], [0], marker="o", color=c_transparent, label="Train", - markerfacecolor="black", markersize=10), - Line2D([0], [0], marker="x", color=c_transparent, label="Test", - markerfacecolor="black", markersize=10), - ] - - ax.legend(handles=custom_lines, bbox_to_anchor=(1.0, 0.75)) - - -plot_points(x_train, y_train, x_test, y_test) -plt.show() - -############################################################################## -# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_001.png -# :width: 80% -# :align: center -# - -############################################################################## -# This plot shows us that class 0 points can be nicely separated, but that there is an overlap -# between points from classes 1 and 2. -# -# Define model -# ------------ -# -# Our model is summarized in the figure below. We use two 4-qubit devices: ``4q-qvm`` -# from the pennyLane-rigetti plugin and ``qiskit.aer`` from the PennyLane-Qiskit plugin. -# -# Data is input using :class:`~.pennylane.RX` rotations and then a different circuit is enacted -# for each device with a unique set of trainable parameters. The output of both circuits is a -# :class:`~.pennylane.PauliZ` measurement on three of the qubits. This is then fed through a -# softmax function, resulting in two 3-dimensional probability vectors corresponding to the 3 -# classes. -# -# Finally, the ensemble model chooses the QPU which is most confident about its prediction -# (i.e., the class with the highest overall probability over all QPUs) and uses that to make a -# prediction. -# -# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_diagram.png -# :width: 80% -# :align: center -# -# Quantum nodes -# ^^^^^^^^^^^^^ -# -# We begin by defining the two quantum devices and the circuits to be run on them. - -n_wires = 4 - -dev0 = qml.device("rigetti.qvm", device="4q-qvm") -dev1 = qml.device("qiskit.aer", wires=4) -devs = [dev0, dev1] - -############################################################################## -# .. note:: -# If you have access to Rigetti hardware, you can swap out ``rigetti.qvm`` for ``rigetti.qpu`` -# and specify the hardware device to run on. Users with access to the IBM hardware can -# swap ``qiskit.aer`` for ``qiskit.remote`` and specify their chosen backend (see `here -# `__). -# -# -# The circuits for both QPUs are shown in the figure below: -# -# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/diagram_circuits.png -# :width: 80% -# :align: center - - -def circuit0(params, x=None): - for i in range(n_wires): - qml.RX(x[i % n_features], wires=i) - qml.Rot(*params[1, 0, i], wires=i) - - qml.CZ(wires=[1, 0]) - qml.CZ(wires=[1, 2]) - qml.CZ(wires=[3, 0]) - - for i in range(n_wires): - qml.Rot(*params[1, 1, i], wires=i) - return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)), qml.expval(qml.PauliZ(2)) - - -def circuit1(params, x=None): - for i in range(n_wires): - qml.RX(x[i % n_features], wires=i) - qml.Rot(*params[0, 0, i], wires=i) - - qml.CZ(wires=[0, 1]) - qml.CZ(wires=[1, 2]) - qml.CZ(wires=[1, 3]) - - for i in range(n_wires): - qml.Rot(*params[0, 1, i], wires=i) - return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)), qml.expval(qml.PauliZ(2)) - - -############################################################################## -# We finally combine the two devices into a :class:`~.pennylane.QNode` list: - - -qnodes = [ - qml.QNode(circuit0, dev0), - qml.QNode(circuit1, dev1), -] - -############################################################################## -# Postprocessing into a prediction -# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# -# The ``predict_point`` function below allows us to find the ensemble prediction, as well as keeping -# track of the individual predictions from each QPU. -# -# We include a ``parallel`` keyword argument for evaluating the :class:`~.pennylane.QNode` list -# in a parallel asynchronous manner. This feature requires the ``dask`` library, which can be -# installed using ``pip install "dask[delayed]"``. When ``parallel=True``, we are able to make -# predictions faster because we do not need to wait for one QPU to output before running on the -# other. - -def decision(softmax): - return int(torch.argmax(softmax)) - - -def predict_point(params, x_point=None, parallel=True): - if parallel: - results = tuple(dask.delayed(q)(params, x=torch.from_numpy(x_point)) for q in qnodes) - results = torch.tensor(dask.compute(*results, scheduler="threads")) - else: - results = tuple(q(params, x=x_point) for q in qnodes) - results = torch.tensor(results) - softmax = torch.nn.functional.softmax(results, dim=1) - choice = torch.where(softmax == torch.max(softmax))[0][0] - chosen_softmax = softmax[choice] - return decision(chosen_softmax), decision(softmax[0]), decision(softmax[1]), int(choice) - - -############################################################################## -# Next, let's define a function to make a predictions over multiple data points. - - -def predict(params, x=None, parallel=True): - predictions_ensemble = [] - predictions_0 = [] - predictions_1 = [] - choices = [] - - for i, x_point in enumerate(x): - if i % 10 == 0 and i > 0: - print("Completed up to iteration {}".format(i)) - results = predict_point(params, x_point=x_point, parallel=parallel) - predictions_ensemble.append(results[0]) - predictions_0.append(results[1]) - predictions_1.append(results[2]) - choices.append(results[3]) - - return predictions_ensemble, predictions_0, predictions_1, choices - - -############################################################################## -# Make predictions -# ---------------- -# -# To test our model, we first load a pre-trained set of parameters which can also be downloaded -# by clicking :download:`here <../_static/demonstration_assets/ensemble_multi_qpu/params.npy>`. - - -params = np.load("../_static/demonstration_assets/ensemble_multi_qpu/params.npy") - -############################################################################## -# We can then make predictions for the training and test datasets. - - -print("Predicting on training dataset") -p_train, p_train_0, p_train_1, choices_train = predict(params, x=x_train) -print("Predicting on test dataset") -p_test, p_test_0, p_test_1, choices_test = predict(params, x=x_test) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# Predicting on training dataset -# Completed up to iteration 10 -# Completed up to iteration 20 -# Completed up to iteration 30 -# Completed up to iteration 40 -# Completed up to iteration 50 -# Completed up to iteration 60 -# Completed up to iteration 70 -# Completed up to iteration 80 -# Completed up to iteration 90 -# Completed up to iteration 100 -# Completed up to iteration 110 -# Completed up to iteration 120 -# Predicting on test dataset -# Completed up to iteration 10 -# Completed up to iteration 20 - -############################################################################## -# Analyze performance -# ------------------- -# -# The last thing to do is test how well the model performs. We begin by looking at the accuracy. -# -# Accuracy -# ^^^^^^^^ - - -def accuracy(predictions, actuals): - count = 0 - - for i in range(len(predictions)): - if predictions[i] == actuals[i]: - count += 1 - - accuracy = count / (len(predictions)) - return accuracy - -############################################################################## - -print("Training accuracy (ensemble): {}".format(accuracy(p_train, y_train))) -print("Training accuracy (QPU0): {}".format(accuracy(p_train_0, y_train))) -print("Training accuracy (QPU1): {}".format(accuracy(p_train_1, y_train))) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# Training accuracy (ensemble): 0.824 -# Training accuracy (QPU0): 0.648 -# Training accuracy (QPU1): 0.296 - -############################################################################## - -print("Test accuracy (ensemble): {}".format(accuracy(p_test, y_test))) -print("Test accuracy (QPU0): {}".format(accuracy(p_test_0, y_test))) -print("Test accuracy (QPU1): {}".format(accuracy(p_test_1, y_test))) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# Test accuracy (ensemble): 0.72 -# Test accuracy (QPU0): 0.56 -# Test accuracy (QPU1): 0.24 - -############################################################################## -# These numbers tell us a few things: -# -# - On both training and test datasets, the ensemble model outperforms the predictions from each -# QPU. This provides a nice example of how QPUs can be used in parallel to gain a performance -# advantage. -# -# - The accuracy of QPU0 is much higher than the accuracy of QPU1. This does not mean that one -# device is intrinsically better than the other. In fact, another set of parameters can lead to -# QPU1 becoming more accurate. We will see in the next section that the difference in accuracy -# is due to specialization of each QPU, which leads to overall better performance of the -# ensemble model. -# -# - The test accuracy is lower than the training accuracy. Here our focus is on analyzing the -# performance of the ensemble model, rather than minimizing the generalization error. -# -# Choice of QPU -# ^^^^^^^^^^^^^ -# -# Is there a link between the class of a datapoint and the QPU chosen to make the prediction in -# the ensemble model? Let's investigate. - - -# Combine choices_train and choices_test to simplify analysis -choices = np.append(choices_train, choices_test) -print("Choices: {}".format(choices)) -print("Choices counts: {}".format(Counter(choices))) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# Choices: [0 0 1 1 0 0 1 0 0 0 1 0 0 0 0 0 0 1 1 0 0 1 0 1 1 0 0 0 1 0 0 1 0 1 0 0 0 -# 0 1 1 0 0 0 0 0 0 0 1 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 1 1 0 0 0 0 1 0 0 0 -# 0 0 0 0 0 1 1 1 1 0 0 0 1 0 1 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 -# 1 0 0 0 1 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 1 1 0 1 0 0 -# 0 0] -# Choices counts: Counter({0: 110, 1: 40}) - -############################################################################## -# The following lines keep track of choices and corresponding predictions in the ensemble model. - - -predictions = np.append(p_train, p_test) -choice_vs_prediction = np.array([(choices[i], predictions[i]) for i in range(n_samples)]) - -############################################################################## -# We can hence find the predictions each QPU was responsible for. - - -choices_vs_prediction_0 = choice_vs_prediction[choice_vs_prediction[:, 0] == 0] -choices_vs_prediction_1 = choice_vs_prediction[choice_vs_prediction[:, 0] == 1] -predictions_0 = choices_vs_prediction_0[:, 1] -predictions_1 = choices_vs_prediction_1[:, 1] - - -expl = "When QPU{} was chosen by the ensemble, it made the following distribution of " \ - "predictions:\n{}" -print(expl.format("0", Counter(predictions_0))) -print("\n" + expl.format("1", Counter(predictions_1))) -print("\nDistribution of classes in iris dataset: {}".format(Counter(y))) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# When QPU0 was chosen by the ensemble, it made the following distribution of predictions: -# Counter({0: 55, 2: 55}) -# -# When QPU1 was chosen by the ensemble, it made the following distribution of predictions: -# Counter({1: 37, 0: 3}) -# -# Distribution of classes in iris dataset: Counter({0: 50, 2: 50, 1: 50}) - -############################################################################## -# These results show us that QPU0 specializes to making predictions on classes 0 and 2, -# while QPU1 specializes to class 1. -# -# Visualization -# ^^^^^^^^^^^^^ -# -# We conclude by visualizing the correct and incorrect predictions on the dataset. The following -# function plots correctly predicted points in green and incorrectly predicted points in red. - - -colours_prediction = {"correct": "#83b5b9", "incorrect": "#f98d91"} -markers = ["o", "v", "d"] - - -def plot_points_prediction(x, y, p, title): - c = {0: [], 1: [], 2: []} - x_ = {0: [], 1: [], 2: []} - - for i in range(n_samples): - x_[y[i]].append(x[i]) - if p[i] == y[i]: - c[y[i]].append(colours_prediction["correct"]) - else: - c[y[i]].append(colours_prediction["incorrect"]) - - for i in range(n_classes): - x_class = np.array(x_[i]) - plt.scatter(x_class[:, 0], x_class[:, 1], c=c[i], marker=markers[i]) - - plt.xlabel("Feature 1", fontsize=16) - plt.ylabel("Feature 2", fontsize=16) - plt.title("Predictions from {} model".format(title)) - - ax = plt.gca() - ax.set_aspect(1) - - c_transparent = "#00000000" - - custom_lines = [ - Patch( - facecolor=colours_prediction["correct"], - edgecolor=c_transparent, label="Correct" - ), - Patch( - facecolor=colours_prediction["incorrect"], - edgecolor=c_transparent, label="Incorrect" - ), - Line2D([0], [0], marker=markers[0], color=c_transparent, label="Class 0", - markerfacecolor="black", markersize=10), - Line2D([0], [0], marker=markers[1], color=c_transparent, label="Class 1", - markerfacecolor="black", markersize=10), - Line2D([0], [0], marker=markers[2], color=c_transparent, label="Class 2", - markerfacecolor="black", markersize=10), - ] - - ax.legend(handles=custom_lines, bbox_to_anchor=(1.0, 0.75)) - - -############################################################################## -# We can again compare the ensemble model with the individual models from each QPU. - - -plot_points_prediction(x, y, predictions, "ensemble") # ensemble -plt.show() - -############################################################################## -# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_002.png -# :width: 80% -# :align: center -# - -############################################################################## - -plot_points_prediction(x, y, np.append(p_train_0, p_test_0), "QPU0") # QPU 0 -plt.show() - -############################################################################## -# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_003.png -# :width: 80% -# :align: center -# - -############################################################################## - -plot_points_prediction(x, y, np.append(p_train_1, p_test_1), "QPU1") # QPU 1 -plt.show() - -############################################################################## -# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_004.png -# :width: 80% -# :align: center -# - -############################################################################## -# These plots reinforce the specialization of the two QPUs. QPU1 concentrates on doing a good job -# at predicting class 1, while QPU0 is focused on classes 0 and 2. By combining together, -# the resultant ensemble performs better. -# -# This tutorial shows how QPUs can work in parallel to realize a performance advantage. Check out -# our :doc:`vqe_parallel` tutorial to see how multiple QPUs can be -# evaluated asynchronously to speed up calculating the potential energy surface of molecular -# hydrogen! - -############################################################################## -# About the author -# ---------------- -# +r""" +Ensemble classification with Rigetti and Qiskit devices +======================================================= + +.. meta:: + :property="og:description": We demonstrate how two QPUs can be + combined in parallel to help solve a machine learning classification problem, + using PyTorch and PennyLane. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/ensemble_diagram.png + +.. related + + tutorial_variational_classifier Variational classifier + +*Author: Tom Bromley — Posted: 14 February 2020. Last updated: 13 December 2021.* + +This tutorial outlines how two QPUs can be combined in parallel to help solve a machine learning +classification problem. + +.. warning:: + This demo does not work with the latest version of Qiskit or the Pennylane-Qiskit plugin and + is compatible with Python versions 3.10 and lower. It is compatible with ``qiskit==0.46`` and + ``pennylane-qiskit==0.35.1``. Older versions of Qiskit and the Pennylane-Qiskit plugin should + not be installed in environments with an existing installation of Qiskit 1.0 or above. + +We use the ``rigetti.qvm`` device to simulate one QPU and the ``qiskit.aer`` device to +simulate another. Each QPU makes an independent prediction, and an ensemble model is +formed by choosing the prediction of the most confident QPU. The iris dataset is used in this +tutorial, consisting of three classes of iris flower. Using a pre-trained model and the PyTorch +interface, we'll see that ensembling allows the QPUs to specialize towards +different classes. + +Let's begin by importing the prerequisite libraries: +""" + +from collections import Counter + +import dask +import matplotlib.pyplot as plt +import numpy as np +import pennylane as qml +import sklearn.datasets +import sklearn.decomposition +import torch +from matplotlib.lines import Line2D +from matplotlib.patches import Patch + +############################################################################## +# This tutorial requires the ``pennylane-rigetti`` and ``pennylane-qiskit`` packages, which can be +# installed by following the instructions `here `__. We also +# make use of the `PyTorch interface `_, which can be installed from `here +# `__. +# +# .. warning:: +# Rigetti's QVM and Quil Compiler services must be running for this tutorial to execute. They +# can be installed by consulting the `Rigetti documentation +# `__ or, for users with Docker, by running: +# +# .. code-block:: bash +# +# docker run -d -p 5555:5555 rigetti/quilc -R -p 5555 +# docker run -d -p 5000:5000 rigetti/qvm -S -p 5000 +# +# Load data +# --------- +# +# The next step is to load the iris dataset. + +n_features = 2 +n_classes = 3 +n_samples = 150 + +data = sklearn.datasets.load_iris() +x = data["data"] +y = data["target"] + +############################################################################## +# We shuffle the data and then embed the four features into a two-dimensional space for ease of +# plotting later on. The first two principal components of the data are used. + +np.random.seed(1967) + +data_order = np.random.permutation(np.arange(n_samples)) +x, y = x[data_order], y[data_order] + +pca = sklearn.decomposition.PCA(n_components=n_features) +pca.fit(x) +x = pca.transform(x) + +############################################################################## +# We will be encoding these two features into quantum circuits using :class:`~.pennylane.RX` +# rotations, and hence renormalize our features to be between :math:`[-\pi, \pi].` + + +x_min = np.min(x, axis=0) +x_max = np.max(x, axis=0) + +x = 2 * np.pi * (x - x_min) / (x_max - x_min) - np.pi + +############################################################################## +# The data is split between a training and a test set. This tutorial uses a model that is +# pre-trained on the training set. + + +split = 125 + +x_train = x[:split] +x_test = x[split:] +y_train = y[:split] +y_test = y[split:] + +############################################################################## +# Finally, let's take a quick look at our data: + + +colours = ["#ec6f86", "#4573e7", "#ad61ed"] + + +def plot_points(x_train, y_train, x_test, y_test): + c_train = [] + c_test = [] + + for y in y_train: + c_train.append(colours[y]) + + for y in y_test: + c_test.append(colours[y]) + + plt.scatter(x_train[:, 0], x_train[:, 1], c=c_train) + plt.scatter(x_test[:, 0], x_test[:, 1], c=c_test, marker="x") + + plt.xlabel("Feature 1", fontsize=16) + plt.ylabel("Feature 2", fontsize=16) + + ax = plt.gca() + ax.set_aspect(1) + + c_transparent = "#00000000" + + custom_lines = [ + Patch(facecolor=colours[0], edgecolor=c_transparent, label="Class 0"), + Patch(facecolor=colours[1], edgecolor=c_transparent, label="Class 1"), + Patch(facecolor=colours[2], edgecolor=c_transparent, label="Class 2"), + Line2D([0], [0], marker="o", color=c_transparent, label="Train", + markerfacecolor="black", markersize=10), + Line2D([0], [0], marker="x", color=c_transparent, label="Test", + markerfacecolor="black", markersize=10), + ] + + ax.legend(handles=custom_lines, bbox_to_anchor=(1.0, 0.75)) + + +plot_points(x_train, y_train, x_test, y_test) +plt.show() + +############################################################################## +# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_001.png +# :width: 80% +# :align: center +# + +############################################################################## +# This plot shows us that class 0 points can be nicely separated, but that there is an overlap +# between points from classes 1 and 2. +# +# Define model +# ------------ +# +# Our model is summarized in the figure below. We use two 4-qubit devices: ``4q-qvm`` +# from the pennyLane-rigetti plugin and ``qiskit.aer`` from the PennyLane-Qiskit plugin. +# +# Data is input using :class:`~.pennylane.RX` rotations and then a different circuit is enacted +# for each device with a unique set of trainable parameters. The output of both circuits is a +# :class:`~.pennylane.PauliZ` measurement on three of the qubits. This is then fed through a +# softmax function, resulting in two 3-dimensional probability vectors corresponding to the 3 +# classes. +# +# Finally, the ensemble model chooses the QPU which is most confident about its prediction +# (i.e., the class with the highest overall probability over all QPUs) and uses that to make a +# prediction. +# +# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_diagram.png +# :width: 80% +# :align: center +# +# Quantum nodes +# ^^^^^^^^^^^^^ +# +# We begin by defining the two quantum devices and the circuits to be run on them. + +n_wires = 4 + +dev0 = qml.device("rigetti.qvm", device="4q-qvm") +dev1 = qml.device("qiskit.aer", wires=4) +devs = [dev0, dev1] + +############################################################################## +# .. note:: +# If you have access to Rigetti hardware, you can swap out ``rigetti.qvm`` for ``rigetti.qpu`` +# and specify the hardware device to run on. Users with access to the IBM hardware can +# swap ``qiskit.aer`` for ``qiskit.remote`` and specify their chosen backend (see `here +# `__). +# +# +# The circuits for both QPUs are shown in the figure below: +# +# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/diagram_circuits.png +# :width: 80% +# :align: center + + +def circuit0(params, x=None): + for i in range(n_wires): + qml.RX(x[i % n_features], wires=i) + qml.Rot(*params[1, 0, i], wires=i) + + qml.CZ(wires=[1, 0]) + qml.CZ(wires=[1, 2]) + qml.CZ(wires=[3, 0]) + + for i in range(n_wires): + qml.Rot(*params[1, 1, i], wires=i) + return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)), qml.expval(qml.PauliZ(2)) + + +def circuit1(params, x=None): + for i in range(n_wires): + qml.RX(x[i % n_features], wires=i) + qml.Rot(*params[0, 0, i], wires=i) + + qml.CZ(wires=[0, 1]) + qml.CZ(wires=[1, 2]) + qml.CZ(wires=[1, 3]) + + for i in range(n_wires): + qml.Rot(*params[0, 1, i], wires=i) + return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)), qml.expval(qml.PauliZ(2)) + + +############################################################################## +# We finally combine the two devices into a :class:`~.pennylane.QNode` list: + + +qnodes = [ + qml.QNode(circuit0, dev0), + qml.QNode(circuit1, dev1), +] + +############################################################################## +# Postprocessing into a prediction +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# +# The ``predict_point`` function below allows us to find the ensemble prediction, as well as keeping +# track of the individual predictions from each QPU. +# +# We include a ``parallel`` keyword argument for evaluating the :class:`~.pennylane.QNode` list +# in a parallel asynchronous manner. This feature requires the ``dask`` library, which can be +# installed using ``pip install "dask[delayed]"``. When ``parallel=True``, we are able to make +# predictions faster because we do not need to wait for one QPU to output before running on the +# other. + +def decision(softmax): + return int(torch.argmax(softmax)) + + +def predict_point(params, x_point=None, parallel=True): + if parallel: + results = tuple(dask.delayed(q)(params, x=torch.from_numpy(x_point)) for q in qnodes) + results = torch.tensor(dask.compute(*results, scheduler="threads")) + else: + results = tuple(q(params, x=x_point) for q in qnodes) + results = torch.tensor(results) + softmax = torch.nn.functional.softmax(results, dim=1) + choice = torch.where(softmax == torch.max(softmax))[0][0] + chosen_softmax = softmax[choice] + return decision(chosen_softmax), decision(softmax[0]), decision(softmax[1]), int(choice) + + +############################################################################## +# Next, let's define a function to make a predictions over multiple data points. + + +def predict(params, x=None, parallel=True): + predictions_ensemble = [] + predictions_0 = [] + predictions_1 = [] + choices = [] + + for i, x_point in enumerate(x): + if i % 10 == 0 and i > 0: + print("Completed up to iteration {}".format(i)) + results = predict_point(params, x_point=x_point, parallel=parallel) + predictions_ensemble.append(results[0]) + predictions_0.append(results[1]) + predictions_1.append(results[2]) + choices.append(results[3]) + + return predictions_ensemble, predictions_0, predictions_1, choices + + +############################################################################## +# Make predictions +# ---------------- +# +# To test our model, we first load a pre-trained set of parameters which can also be downloaded +# by clicking :download:`here <../_static/demonstration_assets/ensemble_multi_qpu/params.npy>`. + + +params = np.load("../_static/demonstration_assets/ensemble_multi_qpu/params.npy") + +############################################################################## +# We can then make predictions for the training and test datasets. + + +print("Predicting on training dataset") +p_train, p_train_0, p_train_1, choices_train = predict(params, x=x_train) +print("Predicting on test dataset") +p_test, p_test_0, p_test_1, choices_test = predict(params, x=x_test) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Predicting on training dataset +# Completed up to iteration 10 +# Completed up to iteration 20 +# Completed up to iteration 30 +# Completed up to iteration 40 +# Completed up to iteration 50 +# Completed up to iteration 60 +# Completed up to iteration 70 +# Completed up to iteration 80 +# Completed up to iteration 90 +# Completed up to iteration 100 +# Completed up to iteration 110 +# Completed up to iteration 120 +# Predicting on test dataset +# Completed up to iteration 10 +# Completed up to iteration 20 + +############################################################################## +# Analyze performance +# ------------------- +# +# The last thing to do is test how well the model performs. We begin by looking at the accuracy. +# +# Accuracy +# ^^^^^^^^ + + +def accuracy(predictions, actuals): + count = 0 + + for i in range(len(predictions)): + if predictions[i] == actuals[i]: + count += 1 + + accuracy = count / (len(predictions)) + return accuracy + +############################################################################## + +print("Training accuracy (ensemble): {}".format(accuracy(p_train, y_train))) +print("Training accuracy (QPU0): {}".format(accuracy(p_train_0, y_train))) +print("Training accuracy (QPU1): {}".format(accuracy(p_train_1, y_train))) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Training accuracy (ensemble): 0.824 +# Training accuracy (QPU0): 0.648 +# Training accuracy (QPU1): 0.296 + +############################################################################## + +print("Test accuracy (ensemble): {}".format(accuracy(p_test, y_test))) +print("Test accuracy (QPU0): {}".format(accuracy(p_test_0, y_test))) +print("Test accuracy (QPU1): {}".format(accuracy(p_test_1, y_test))) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Test accuracy (ensemble): 0.72 +# Test accuracy (QPU0): 0.56 +# Test accuracy (QPU1): 0.24 + +############################################################################## +# These numbers tell us a few things: +# +# - On both training and test datasets, the ensemble model outperforms the predictions from each +# QPU. This provides a nice example of how QPUs can be used in parallel to gain a performance +# advantage. +# +# - The accuracy of QPU0 is much higher than the accuracy of QPU1. This does not mean that one +# device is intrinsically better than the other. In fact, another set of parameters can lead to +# QPU1 becoming more accurate. We will see in the next section that the difference in accuracy +# is due to specialization of each QPU, which leads to overall better performance of the +# ensemble model. +# +# - The test accuracy is lower than the training accuracy. Here our focus is on analyzing the +# performance of the ensemble model, rather than minimizing the generalization error. +# +# Choice of QPU +# ^^^^^^^^^^^^^ +# +# Is there a link between the class of a datapoint and the QPU chosen to make the prediction in +# the ensemble model? Let's investigate. + + +# Combine choices_train and choices_test to simplify analysis +choices = np.append(choices_train, choices_test) +print("Choices: {}".format(choices)) +print("Choices counts: {}".format(Counter(choices))) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Choices: [0 0 1 1 0 0 1 0 0 0 1 0 0 0 0 0 0 1 1 0 0 1 0 1 1 0 0 0 1 0 0 1 0 1 0 0 0 +# 0 1 1 0 0 0 0 0 0 0 1 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 1 1 0 0 0 0 1 0 0 0 +# 0 0 0 0 0 1 1 1 1 0 0 0 1 0 1 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 +# 1 0 0 0 1 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 1 1 0 1 0 0 +# 0 0] +# Choices counts: Counter({0: 110, 1: 40}) + +############################################################################## +# The following lines keep track of choices and corresponding predictions in the ensemble model. + + +predictions = np.append(p_train, p_test) +choice_vs_prediction = np.array([(choices[i], predictions[i]) for i in range(n_samples)]) + +############################################################################## +# We can hence find the predictions each QPU was responsible for. + + +choices_vs_prediction_0 = choice_vs_prediction[choice_vs_prediction[:, 0] == 0] +choices_vs_prediction_1 = choice_vs_prediction[choice_vs_prediction[:, 0] == 1] +predictions_0 = choices_vs_prediction_0[:, 1] +predictions_1 = choices_vs_prediction_1[:, 1] + + +expl = "When QPU{} was chosen by the ensemble, it made the following distribution of " \ + "predictions:\n{}" +print(expl.format("0", Counter(predictions_0))) +print("\n" + expl.format("1", Counter(predictions_1))) +print("\nDistribution of classes in iris dataset: {}".format(Counter(y))) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# When QPU0 was chosen by the ensemble, it made the following distribution of predictions: +# Counter({0: 55, 2: 55}) +# +# When QPU1 was chosen by the ensemble, it made the following distribution of predictions: +# Counter({1: 37, 0: 3}) +# +# Distribution of classes in iris dataset: Counter({0: 50, 2: 50, 1: 50}) + +############################################################################## +# These results show us that QPU0 specializes to making predictions on classes 0 and 2, +# while QPU1 specializes to class 1. +# +# Visualization +# ^^^^^^^^^^^^^ +# +# We conclude by visualizing the correct and incorrect predictions on the dataset. The following +# function plots correctly predicted points in green and incorrectly predicted points in red. + + +colours_prediction = {"correct": "#83b5b9", "incorrect": "#f98d91"} +markers = ["o", "v", "d"] + + +def plot_points_prediction(x, y, p, title): + c = {0: [], 1: [], 2: []} + x_ = {0: [], 1: [], 2: []} + + for i in range(n_samples): + x_[y[i]].append(x[i]) + if p[i] == y[i]: + c[y[i]].append(colours_prediction["correct"]) + else: + c[y[i]].append(colours_prediction["incorrect"]) + + for i in range(n_classes): + x_class = np.array(x_[i]) + plt.scatter(x_class[:, 0], x_class[:, 1], c=c[i], marker=markers[i]) + + plt.xlabel("Feature 1", fontsize=16) + plt.ylabel("Feature 2", fontsize=16) + plt.title("Predictions from {} model".format(title)) + + ax = plt.gca() + ax.set_aspect(1) + + c_transparent = "#00000000" + + custom_lines = [ + Patch( + facecolor=colours_prediction["correct"], + edgecolor=c_transparent, label="Correct" + ), + Patch( + facecolor=colours_prediction["incorrect"], + edgecolor=c_transparent, label="Incorrect" + ), + Line2D([0], [0], marker=markers[0], color=c_transparent, label="Class 0", + markerfacecolor="black", markersize=10), + Line2D([0], [0], marker=markers[1], color=c_transparent, label="Class 1", + markerfacecolor="black", markersize=10), + Line2D([0], [0], marker=markers[2], color=c_transparent, label="Class 2", + markerfacecolor="black", markersize=10), + ] + + ax.legend(handles=custom_lines, bbox_to_anchor=(1.0, 0.75)) + + +############################################################################## +# We can again compare the ensemble model with the individual models from each QPU. + + +plot_points_prediction(x, y, predictions, "ensemble") # ensemble +plt.show() + +############################################################################## +# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_002.png +# :width: 80% +# :align: center +# + +############################################################################## + +plot_points_prediction(x, y, np.append(p_train_0, p_test_0), "QPU0") # QPU 0 +plt.show() + +############################################################################## +# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_003.png +# :width: 80% +# :align: center +# + +############################################################################## + +plot_points_prediction(x, y, np.append(p_train_1, p_test_1), "QPU1") # QPU 1 +plt.show() + +############################################################################## +# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_004.png +# :width: 80% +# :align: center +# + +############################################################################## +# These plots reinforce the specialization of the two QPUs. QPU1 concentrates on doing a good job +# at predicting class 1, while QPU0 is focused on classes 0 and 2. By combining together, +# the resultant ensemble performs better. +# +# This tutorial shows how QPUs can work in parallel to realize a performance advantage. Check out +# our :doc:`vqe_parallel` tutorial to see how multiple QPUs can be +# evaluated asynchronously to speed up calculating the potential energy surface of molecular +# hydrogen! + +############################################################################## +# About the author +# ---------------- +# diff --git a/demonstrations/gbs.py b/demonstrations/gbs.py index 044c2f6c56..f2e1984e33 100644 --- a/demonstrations/gbs.py +++ b/demonstrations/gbs.py @@ -1,488 +1,488 @@ -r""" -.. role:: html(raw) - :format: html - -Quantum advantage with Gaussian Boson Sampling -============================================== - -.. meta:: - :property="og:description": Using light to perform tasks beyond the reach of classical computers. - - :property="og:image": https://pennylane.ai/qml/_static/demo_thumbnails/opengraph_demo_thumbnails/gbs_expt2.png - -.. related:: - - tutorial_gaussian_transformation Gaussian transformation - qsim_beyond_classical Beyond classical computing with qsim - qonn Optimizing a quantum optical neural network - tutorial_photonics Photonic quantum computers - -*Authors: Josh Izaac and Nathan Killoran — Posted: 04 December 2020. Last updated: 04 December 2020.* - -.. warning:: - This demo is only compatible with PennyLane version ``0.29`` or below. - -On the journey to large-scale fault-tolerant quantum computers, one of the first major -milestones is to demonstrate a quantum device carrying out tasks that are beyond the reach of -any classical algorithm. The launch of Xanadu's Borealis device marked an important milestone -within the quantum computing community, wherein our very own quantum computational advantage -experiment using quantum photonics was demonstrated in our `Nature paper `__. -Among other quantum advantage achievements are the Google Quantum team's experiment as can be seen in their paper -`Quantum supremacy using a programmable superconducting processor `__ [#Arute2019]_, -and the experiment from the team led by Chao-Yang Lu and Jian-Wei as can be seen in their paper -`Quantum computational advantage using photons `__ -[#Zhong2020]_. - -While Google's experiment performed the task of :doc:`random circuit sampling ` -using a superconducting processor, both Chao-Yang Lu and Jian-Wei's team and Xanadu leveraged the -quantum properties of light to tackle a task called -`Gaussian Boson Sampling `__ (GBS). - -This tutorial will walk you through the basic elements of GBS, motivate why it is -classically challenging, and show you how to explore GBS using PennyLane and the photonic -quantum devices accessible via the -`PennyLane-Strawberry Fields plugin `__. If you are -interested in possible applications of GBS, or want to access programmable GBS hardware -via the cloud, check out the -`Strawberry Fields website `__ for more details. - -| - -.. image:: /_static/demonstration_assets/gbs/gbs_expt2.png - :align: center - :width: 80% - :target: javascript:void(0); - -.. figure:: /_static/demonstration_assets/gbs/gbs_expt1.png - :align: center - :width: 80% - :target: javascript:void(0); - - *Illustration of the experimental setup used by Zhong et al. in Quantum computational advantage - using photons* [#Zhong2020]_. - -The origins of GBS ------------------- - -Let's first explain the name. `Boson `__ refers to bosonic -matter, which, along with fermions, makes up one of the two elementary classes of particles. -The most prevalent bosonic system in our everyday lives is light, which is made of particles -called photons. Another famous example, though much harder to find, is the Higgs boson. -The distinguishing characteristic of bosons is that they follow "Bose-Einstein statistics", -which very loosely means that the particles like to bunch together (contrast this to fermionic -matter like electrons, which must follow the Pauli Exclusion Principle and keep apart). - -This property can be observed in simple interference experiments such as the -`Hong-Ou Mandel setup `__. -If two single photons are interfered on a balanced beamsplitter, they will both emerge at -the same output port—there is zero probability that they will emerge at separate outputs. -This is a simple but notable quantum property of light; if electrons were brought -together in a similar experiement, they would always appear at separate output ports. - -Gaussian Boson Sampling [#hamilton2017]_ is, in fact, a member of a larger family of -"Boson Sampling" algorithms, -stemming back to the initial proposal of Aaronson and Arkhipov [#aaronson2013]_ in 2013. -Boson Sampling is quantum interferometry writ large. Aaronson and Arkhipov's original proposal -was to inject many single photons into distinct input ports of a large interferometer, then -measure which output ports they appear at. The natural interference properties of bosons -means that photons will appear at the output ports in very unique and specific ways. Boson -Sampling was not proposed with any kind of practical real-world use-case in mind. Like -the random circuit sampling, it's just a quantum system being its best self. With sufficient -size and quality, it is strongly believed to be hard for a classical computer to simulate this efficiently. - -Finally, the "Gaussian" in GBS refers to the fact that we modify the original Boson Sampling -proposal slightly: instead of injecting single photons—which are hard to jointly create in the -size and quality needed to demonstrate Boson Sampling conclusively—we instead use states of -light that are experimentally less demanding (though still challenging!). -These states of light are called Gaussian states, -because they bear strong connections to the -`Gaussian (or Normal) distribution `__ -from statistics. In practice, we use a particular Gaussian state called a -`squeezed state `__ for the inputs, -since these are arguably the most non-classical of Gaussian states. - - -.. note:: While computationally hard to simulate, Boson Sampling devices, on their own, - are not capable of universal quantum computing. However, in combination with other - components, GBS is a key building block for a - universal device [#Bourassa2020]_. - - -Coding a GBS algorithm ----------------------- - -The researchers in [#Zhong2020]_ experimentally demonstrate a GBS device by preparing 50 -squeezed states and injecting them into a 100-mode interferometer. In this demo, -in order to keep things classically simulable, we will stick to a much simpler setting -consisting of 4 squeezed states injected into a 4-mode interferometer. At a high level, -an interferometer on :math:`N` modes can be represented using an :math:`N\times N` unitary -matrix :math:`U.` When decomposed into a quantum optical circuit, the interferometer will -be made up of beamsplitters and phase shifters. - -.. image:: /_static/demonstration_assets/gbs/gbs_circuit2.png - :align: center - :width: 90% - :target: javascript:void(0); - -.. raw:: html - -
- -Simulating this circuit using PennyLane is easy; we can simply read off the gates from left -to right, and convert it into a QNode. -""" - -import numpy as np - -# set the random seed -np.random.seed(42) - -# import PennyLane -import pennylane as qml - -############################################################################## -# We must define the unitary matrix we would like to embed in the circuit. -# We will use SciPy to generate a Haar-random unitary: - -from scipy.stats import unitary_group - -# define the linear interferometer -U = unitary_group.rvs(4) -print(U) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# [[ 0.23648826-0.48221431j 0.06829648+0.04447898j 0.51150074-0.09529866j -# 0.55205719-0.35974699j] -# [-0.11148167+0.69780321j -0.24943828+0.08410701j 0.46705929-0.43192981j -# 0.16220654-0.01817602j] -# [-0.22351926-0.25918352j 0.24364996-0.05375623j -0.09259829-0.53810588j -# 0.27267708+0.66941977j] -# [ 0.11519953-0.28596729j -0.90164923-0.22099186j -0.09627758-0.13105595j -# -0.0200152 +0.12766128j]] -# -# We can now use this to construct the circuit, choosing a compatible -# device. For the simulation, we can use the Strawberry Fields -# Gaussian backend. This backend is perfectly suited for simulation of GBS, -# as the initial states are Gaussian, and all gates transform Gaussian states to other -# Gaussian states. - -n_wires = 4 -cutoff = 10 - -dev = qml.device("strawberryfields.gaussian", wires=n_wires, cutoff_dim=cutoff) - - -@qml.qnode(dev) -def gbs_circuit(): - # prepare the input squeezed states - for i in range(n_wires): - qml.Squeezing(1.0, 0.0, wires=i) - - # linear interferometer - qml.InterferometerUnitary(U, wires=range(n_wires)) - return qml.probs(wires=range(n_wires)) - - -############################################################################## -# A couple of things to note in this particular example: -# -# 1. To prepare the input single mode squeezed vacuum state :math:`|re^{i\phi}\rangle,` -# where :math:`r = 1` and :math:`\phi=0,` we -# apply a squeezing gate (:class:`~pennylane.Squeezing`) to each of the wires (initially in -# the vacuum state). -# -# 2. Next we apply the linear interferometer to all four wires using -# :class:`~pennylane.Interferometer` and the unitary matrix ``U``. This operator -# decomposes the unitary matrix representing the linear interferometer into single-mode -# rotation gates (:class:`~pennylane.PhaseShift`) and two-mode beamsplitters -# (:class:`~pennylane.Beamsplitter`). After applying the interferometer, we will denote the -# output state by :math:`|\psi'\rangle.` -# -# 3. GBS takes place physically in an infinite-dimensional Hilbert space, -# which is not practical for simulation. We need to set an upper limit on the maximum -# number of photons we can detect. This is the -# ``cutoff`` value we defined above; we will only be considering detection events -# containing 0 to 9 photons per mode. -# -# We can now execute the QNode, and extract the resulting probability distribution: - -probs = gbs_circuit().reshape([cutoff] * n_wires) -print(probs.shape) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# (10, 10, 10, 10) -# - -############################################################################## -# For example, element ``[1,2,0,1]`` represents the probability of -# detecting 1 photon on wire -# ``0`` and wire ``3``, and 2 photons at wire ``1``, i.e., the value -# -# .. math:: \text{prob}(1,2,0,1) = \left|\langle{1,2,0,1} \mid \psi'\rangle \right|^2. -# -# Let's extract and view the probabilities of measuring various Fock states. - -# Fock states to measure at output -measure_states = [(0, 0, 0, 0), (1, 1, 0, 0), (0, 1, 0, 1), (1, 1, 1, 1), (2, 0, 0, 0)] - -# extract the probabilities of calculating several -# different Fock states at the output, and print them out -for i in measure_states: - print(f"|{''.join(str(j) for j in i)}>: {probs[i]}") - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# |0000>: 0.17637844761413496 -# |1100>: 0.03473293649420282 -# |0101>: 0.011870900427255589 -# |1111>: 0.005957399165336106 -# |2000>: 0.02957384308320549 -# - -############################################################################## -# The GBS Distribution -# -------------------- -# -# Hamilton et al. [#hamilton2017]_ showed that the probability of -# measuring a final state containing only 0 or 1 photons per mode is given by -# -# .. math:: -# -# \left|\langle{n_1,n_2,\dots,n_N}\middle|{\psi'}\rangle\right|^2 = -# \frac{\left|\text{Haf}[(U(\bigoplus_i\mathrm{tanh}(r_i))U^T)]_{st}\right|^2}{\prod_{i=1}^N \cosh(r_i)} -# -# i.e., the sampled single-photon probability distribution is proportional to the **hafnian** of a -# submatrix of :math:`U(\bigoplus_i\mathrm{tanh}(r_i))U^T.` -# -# .. note:: -# -# The hafnian of a matrix is defined by -# -# .. math:: \text{Haf}(A) = \sum_{\sigma \in \text{PMP}_{2N}}\prod_{i=1}^N A_{\sigma(2i-1)\sigma(2i)}, -# -# where :math:`\text{PMP}_{2N}` is the set of all perfect matching permutations of :math:`2N` elements. In graph theory, the -# hafnian calculates the number of perfect `matchings -# `_ in a graph with -# adjacency matrix :math:`A.` -# -# Compare this to the permanent, which calculates the number of perfect matchings on a *bipartite* -# graph. Notably, the permanent appears in vanilla Boson Sampling in a similar way -# that the hafnian appears in GBS. -# The hafnian turns out to be a generalization of the permanent, with the relationship -# -# .. math:: -# -# \text{Per(A)} = \text{Haf}\left(\left[\begin{matrix} -# 0&A\\ A^T&0 -# \end{matrix}\right]\right). -# -# As any algorithm that could calculate (or even approximate) the hafnian could also calculate the -# permanent—a `#P-hard problem `__---it follows that -# calculating or approximating the hafnian must also be a classically hard problem. This lies behind -# the classical hardness of GBS. -# -# In this demo, we will use the same squeezing parameter, :math:`z=r,` for -# all input states; this allows us to simplify this equation. To start with, the hafnian expression -# simply becomes :math:`\text{Haf}[(UU^T\mathrm{tanh}(r))]_{st},` removing the need for the direct sum. -# -# Thus, we have -# -# .. math:: -# -# \left|\left\langle{n_1,n_2,\dots,n_N}\middle|{\psi'}\right\rangle\right|^2 = -# \frac{\left|\text{Haf}[(UU^T\tanh(r))]_{st}\right|^2}{n_1!n_2!\cdots n_N!\cosh^N(r)}. -# -# Now that we have the theoretical formulas, as well as the probabilities from our simulated GBS -# QNode, we can compare the two and see whether they agree. -# -# In order to calculate the probability of different GBS events classically, we need a -# method for calculating the hafnian. -# For this, we will use `The Walrus -# `_ library (which is installed as a dependency of the -# PennyLane-SF plugin): - -from thewalrus import hafnian as haf - -############################################################################## -# Now, for the right-hand side numerator, we first calculate the submatrix -# :math:`A = [(UU^T\mathrm{tanh}(r))]_{st}:` - -A = np.dot(U, U.T) * np.tanh(1) - -############################################################################## -# In GBS, we determine the submatrix by taking the -# rows and columns corresponding to the measured Fock state. For example, to calculate the submatrix -# in the case of the output measurement :math:`\left|{1,1,0,0}\right\rangle,` -# we have - -print(A[:, [0, 1]][[0, 1]]) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# [[ 0.19343159-0.54582922j 0.43418269-0.09169615j] -# [ 0.43418269-0.09169615j -0.27554025-0.46222197j]] -# - -############################################################################## -# i.e., we consider only the rows and columns where a photon was detected, which gives us -# the submatrix corresponding to indices :math:`0` and :math:`1.` - -############################################################################## -# Comparing to simulation -# ----------------------- -# -# Now that we have a method for calculating the hafnian, let's compare the output to that provided by -# the PennyLane QNode. -# -# **Measuring** :math:`|0,0,0,0\rangle` **at the output** -# -# This corresponds to the hafnian of an *empty* matrix, which is simply 1: - -print(1 / np.cosh(1) ** 4) -print(probs[0, 0, 0, 0]) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# 0.1763784476141347 -# 0.17637844761413496 -# - -############################################################################## -# **Measuring** :math:`|1,1,0,0\rangle` **at the output** - -A = (np.dot(U, U.T) * np.tanh(1))[:, [0, 1]][[0, 1]] -print(np.abs(haf(A)) ** 2 / np.cosh(1) ** 4) -print(probs[1, 1, 0, 0]) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# 0.03473293649420271 -# 0.03473293649420282 -# - -############################################################################## -# **Measuring** :math:`|0,1,0,1\rangle` **at the output** - -A = (np.dot(U, U.T) * np.tanh(1))[:, [1, 3]][[1, 3]] -print(np.abs(haf(A)) ** 2 / np.cosh(1) ** 4) -print(probs[0, 1, 0, 1]) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# 0.011870900427255558 -# 0.011870900427255589 -# - -############################################################################## -# **Measuring** :math:`|1,1,1,1\rangle` **at the output** -# -# This corresponds to the hafnian of the full matrix :math:`A=UU^T\mathrm{tanh}(r):` - -A = np.dot(U, U.T) * np.tanh(1) -print(np.abs(haf(A)) ** 2 / np.cosh(1) ** 4) -print(probs[1, 1, 1, 1]) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# 0.005957399165336081 -# 0.005957399165336106 -# - -############################################################################## -# **Measuring** :math:`|2,0,0,0\rangle` **at the output** -# -# Since we have two photons in mode ``q[0]``, we take two copies of the -# first row and first column, making sure to divide by :math:`2!:` - -A = (np.dot(U, U.T) * np.tanh(1))[:, [0, 0]][[0, 0]] -print(np.abs(haf(A)) ** 2 / (2 * np.cosh(1) ** 4)) -print(probs[2, 0, 0, 0]) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# 0.029573843083205383 -# 0.02957384308320549 -# -# The PennyLane simulation results agree (with almost negligible numerical error) to the -# expected result from the Gaussian boson sampling equation! -# -# This demo provides an entry-level walkthrough to the ideas behind GBS, -# providing you with the basic code needed for exploring the ideas behind -# the photonic quantum advantage paper. Try changing the number of modes, -# the number of injected squeezed states, or the cutoff dimension, and -# see how each of these affect the classical computation time. If you're -# interested in learning more about GBS, or about photonic quantum -# computing in general, the -# `Strawberry Fields website `__ is a great resource. -# -# References -# ---------- -# -# .. [#Arute2019] -# -# Arute, F., Arya, K., Babbush, R., et al. "Quantum supremacy using a programmable -# superconducting processor" -# `Nature 574, 505-510 (2019) `__. -# -# .. [#Zhong2020] -# -# Zhong, H.-S., Wang, H., Deng, Y.-H., et al. (2020). Quantum computational advantage using photons. Science, 10.1126/science.abe8770. -# -# .. [#hamilton2017] -# -# Craig S. Hamilton, Regina Kruse, Linda Sansoni, Sonja Barkhofen, Christine Silberhorn, -# and Igor Jex. Gaussian boson sampling. Physical Review Letters, 119:170501, Oct 2017. -# arXiv:1612.01199, doi:10.1103/PhysRevLett.119.170501. -# -# .. [#aaronson2013] -# -# Scott Aaronson and Alex Arkhipov. The computational complexity of linear optics. Theory of -# Computing, 9(1):143–252, 2013. doi:10.4086/toc.2013.v009a004. -# -# .. [#Bourassa2020] -# -# Bourassa, J. E., Alexander, R. N., Vasmer, et al. (2020). Blueprint for a scalable -# photonic fault-tolerant quantum computer. arXiv preprint arXiv:2010.02905. -# -# -# About the author -# ---------------- -# +r""" +.. role:: html(raw) + :format: html + +Quantum advantage with Gaussian Boson Sampling +============================================== + +.. meta:: + :property="og:description": Using light to perform tasks beyond the reach of classical computers. + + :property="og:image": https://pennylane.ai/qml/_static/demo_thumbnails/opengraph_demo_thumbnails/gbs_expt2.png + +.. related:: + + tutorial_gaussian_transformation Gaussian transformation + qsim_beyond_classical Beyond classical computing with qsim + qonn Optimizing a quantum optical neural network + tutorial_photonics Photonic quantum computers + +*Authors: Josh Izaac and Nathan Killoran — Posted: 04 December 2020. Last updated: 04 December 2020.* + +.. warning:: + This demo is only compatible with PennyLane version ``0.29`` or below. + +On the journey to large-scale fault-tolerant quantum computers, one of the first major +milestones is to demonstrate a quantum device carrying out tasks that are beyond the reach of +any classical algorithm. The launch of Xanadu's Borealis device marked an important milestone +within the quantum computing community, wherein our very own quantum computational advantage +experiment using quantum photonics was demonstrated in our `Nature paper `__. +Among other quantum advantage achievements are the Google Quantum team's experiment as can be seen in their paper +`Quantum supremacy using a programmable superconducting processor `__ [#Arute2019]_, +and the experiment from the team led by Chao-Yang Lu and Jian-Wei as can be seen in their paper +`Quantum computational advantage using photons `__ +[#Zhong2020]_. + +While Google's experiment performed the task of :doc:`random circuit sampling ` +using a superconducting processor, both Chao-Yang Lu and Jian-Wei's team and Xanadu leveraged the +quantum properties of light to tackle a task called +`Gaussian Boson Sampling `__ (GBS). + +This tutorial will walk you through the basic elements of GBS, motivate why it is +classically challenging, and show you how to explore GBS using PennyLane and the photonic +quantum devices accessible via the +`PennyLane-Strawberry Fields plugin `__. If you are +interested in possible applications of GBS, or want to access programmable GBS hardware +via the cloud, check out the +`Strawberry Fields website `__ for more details. + +| + +.. image:: /_static/demonstration_assets/gbs/gbs_expt2.png + :align: center + :width: 80% + :target: javascript:void(0); + +.. figure:: /_static/demonstration_assets/gbs/gbs_expt1.png + :align: center + :width: 80% + :target: javascript:void(0); + + *Illustration of the experimental setup used by Zhong et al. in Quantum computational advantage + using photons* [#Zhong2020]_. + +The origins of GBS +------------------ + +Let's first explain the name. `Boson `__ refers to bosonic +matter, which, along with fermions, makes up one of the two elementary classes of particles. +The most prevalent bosonic system in our everyday lives is light, which is made of particles +called photons. Another famous example, though much harder to find, is the Higgs boson. +The distinguishing characteristic of bosons is that they follow "Bose-Einstein statistics", +which very loosely means that the particles like to bunch together (contrast this to fermionic +matter like electrons, which must follow the Pauli Exclusion Principle and keep apart). + +This property can be observed in simple interference experiments such as the +`Hong-Ou Mandel setup `__. +If two single photons are interfered on a balanced beamsplitter, they will both emerge at +the same output port—there is zero probability that they will emerge at separate outputs. +This is a simple but notable quantum property of light; if electrons were brought +together in a similar experiement, they would always appear at separate output ports. + +Gaussian Boson Sampling [#hamilton2017]_ is, in fact, a member of a larger family of +"Boson Sampling" algorithms, +stemming back to the initial proposal of Aaronson and Arkhipov [#aaronson2013]_ in 2013. +Boson Sampling is quantum interferometry writ large. Aaronson and Arkhipov's original proposal +was to inject many single photons into distinct input ports of a large interferometer, then +measure which output ports they appear at. The natural interference properties of bosons +means that photons will appear at the output ports in very unique and specific ways. Boson +Sampling was not proposed with any kind of practical real-world use-case in mind. Like +the random circuit sampling, it's just a quantum system being its best self. With sufficient +size and quality, it is strongly believed to be hard for a classical computer to simulate this efficiently. + +Finally, the "Gaussian" in GBS refers to the fact that we modify the original Boson Sampling +proposal slightly: instead of injecting single photons—which are hard to jointly create in the +size and quality needed to demonstrate Boson Sampling conclusively—we instead use states of +light that are experimentally less demanding (though still challenging!). +These states of light are called Gaussian states, +because they bear strong connections to the +`Gaussian (or Normal) distribution `__ +from statistics. In practice, we use a particular Gaussian state called a +`squeezed state `__ for the inputs, +since these are arguably the most non-classical of Gaussian states. + + +.. note:: While computationally hard to simulate, Boson Sampling devices, on their own, + are not capable of universal quantum computing. However, in combination with other + components, GBS is a key building block for a + universal device [#Bourassa2020]_. + + +Coding a GBS algorithm +---------------------- + +The researchers in [#Zhong2020]_ experimentally demonstrate a GBS device by preparing 50 +squeezed states and injecting them into a 100-mode interferometer. In this demo, +in order to keep things classically simulable, we will stick to a much simpler setting +consisting of 4 squeezed states injected into a 4-mode interferometer. At a high level, +an interferometer on :math:`N` modes can be represented using an :math:`N\times N` unitary +matrix :math:`U.` When decomposed into a quantum optical circuit, the interferometer will +be made up of beamsplitters and phase shifters. + +.. image:: /_static/demonstration_assets/gbs/gbs_circuit2.png + :align: center + :width: 90% + :target: javascript:void(0); + +.. raw:: html + +
+ +Simulating this circuit using PennyLane is easy; we can simply read off the gates from left +to right, and convert it into a QNode. +""" + +import numpy as np + +# set the random seed +np.random.seed(42) + +# import PennyLane +import pennylane as qml + +############################################################################## +# We must define the unitary matrix we would like to embed in the circuit. +# We will use SciPy to generate a Haar-random unitary: + +from scipy.stats import unitary_group + +# define the linear interferometer +U = unitary_group.rvs(4) +print(U) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# [[ 0.23648826-0.48221431j 0.06829648+0.04447898j 0.51150074-0.09529866j +# 0.55205719-0.35974699j] +# [-0.11148167+0.69780321j -0.24943828+0.08410701j 0.46705929-0.43192981j +# 0.16220654-0.01817602j] +# [-0.22351926-0.25918352j 0.24364996-0.05375623j -0.09259829-0.53810588j +# 0.27267708+0.66941977j] +# [ 0.11519953-0.28596729j -0.90164923-0.22099186j -0.09627758-0.13105595j +# -0.0200152 +0.12766128j]] +# +# We can now use this to construct the circuit, choosing a compatible +# device. For the simulation, we can use the Strawberry Fields +# Gaussian backend. This backend is perfectly suited for simulation of GBS, +# as the initial states are Gaussian, and all gates transform Gaussian states to other +# Gaussian states. + +n_wires = 4 +cutoff = 10 + +dev = qml.device("strawberryfields.gaussian", wires=n_wires, cutoff_dim=cutoff) + + +@qml.qnode(dev) +def gbs_circuit(): + # prepare the input squeezed states + for i in range(n_wires): + qml.Squeezing(1.0, 0.0, wires=i) + + # linear interferometer + qml.InterferometerUnitary(U, wires=range(n_wires)) + return qml.probs(wires=range(n_wires)) + + +############################################################################## +# A couple of things to note in this particular example: +# +# 1. To prepare the input single mode squeezed vacuum state :math:`|re^{i\phi}\rangle,` +# where :math:`r = 1` and :math:`\phi=0,` we +# apply a squeezing gate (:class:`~pennylane.Squeezing`) to each of the wires (initially in +# the vacuum state). +# +# 2. Next we apply the linear interferometer to all four wires using +# :class:`~pennylane.Interferometer` and the unitary matrix ``U``. This operator +# decomposes the unitary matrix representing the linear interferometer into single-mode +# rotation gates (:class:`~pennylane.PhaseShift`) and two-mode beamsplitters +# (:class:`~pennylane.Beamsplitter`). After applying the interferometer, we will denote the +# output state by :math:`|\psi'\rangle.` +# +# 3. GBS takes place physically in an infinite-dimensional Hilbert space, +# which is not practical for simulation. We need to set an upper limit on the maximum +# number of photons we can detect. This is the +# ``cutoff`` value we defined above; we will only be considering detection events +# containing 0 to 9 photons per mode. +# +# We can now execute the QNode, and extract the resulting probability distribution: + +probs = gbs_circuit().reshape([cutoff] * n_wires) +print(probs.shape) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# (10, 10, 10, 10) +# + +############################################################################## +# For example, element ``[1,2,0,1]`` represents the probability of +# detecting 1 photon on wire +# ``0`` and wire ``3``, and 2 photons at wire ``1``, i.e., the value +# +# .. math:: \text{prob}(1,2,0,1) = \left|\langle{1,2,0,1} \mid \psi'\rangle \right|^2. +# +# Let's extract and view the probabilities of measuring various Fock states. + +# Fock states to measure at output +measure_states = [(0, 0, 0, 0), (1, 1, 0, 0), (0, 1, 0, 1), (1, 1, 1, 1), (2, 0, 0, 0)] + +# extract the probabilities of calculating several +# different Fock states at the output, and print them out +for i in measure_states: + print(f"|{''.join(str(j) for j in i)}>: {probs[i]}") + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# |0000>: 0.17637844761413496 +# |1100>: 0.03473293649420282 +# |0101>: 0.011870900427255589 +# |1111>: 0.005957399165336106 +# |2000>: 0.02957384308320549 +# + +############################################################################## +# The GBS Distribution +# -------------------- +# +# Hamilton et al. [#hamilton2017]_ showed that the probability of +# measuring a final state containing only 0 or 1 photons per mode is given by +# +# .. math:: +# +# \left|\langle{n_1,n_2,\dots,n_N}\middle|{\psi'}\rangle\right|^2 = +# \frac{\left|\text{Haf}[(U(\bigoplus_i\mathrm{tanh}(r_i))U^T)]_{st}\right|^2}{\prod_{i=1}^N \cosh(r_i)} +# +# i.e., the sampled single-photon probability distribution is proportional to the **hafnian** of a +# submatrix of :math:`U(\bigoplus_i\mathrm{tanh}(r_i))U^T.` +# +# .. note:: +# +# The hafnian of a matrix is defined by +# +# .. math:: \text{Haf}(A) = \sum_{\sigma \in \text{PMP}_{2N}}\prod_{i=1}^N A_{\sigma(2i-1)\sigma(2i)}, +# +# where :math:`\text{PMP}_{2N}` is the set of all perfect matching permutations of :math:`2N` elements. In graph theory, the +# hafnian calculates the number of perfect `matchings +# `_ in a graph with +# adjacency matrix :math:`A.` +# +# Compare this to the permanent, which calculates the number of perfect matchings on a *bipartite* +# graph. Notably, the permanent appears in vanilla Boson Sampling in a similar way +# that the hafnian appears in GBS. +# The hafnian turns out to be a generalization of the permanent, with the relationship +# +# .. math:: +# +# \text{Per(A)} = \text{Haf}\left(\left[\begin{matrix} +# 0&A\\ A^T&0 +# \end{matrix}\right]\right). +# +# As any algorithm that could calculate (or even approximate) the hafnian could also calculate the +# permanent—a `#P-hard problem `__---it follows that +# calculating or approximating the hafnian must also be a classically hard problem. This lies behind +# the classical hardness of GBS. +# +# In this demo, we will use the same squeezing parameter, :math:`z=r,` for +# all input states; this allows us to simplify this equation. To start with, the hafnian expression +# simply becomes :math:`\text{Haf}[(UU^T\mathrm{tanh}(r))]_{st},` removing the need for the direct sum. +# +# Thus, we have +# +# .. math:: +# +# \left|\left\langle{n_1,n_2,\dots,n_N}\middle|{\psi'}\right\rangle\right|^2 = +# \frac{\left|\text{Haf}[(UU^T\tanh(r))]_{st}\right|^2}{n_1!n_2!\cdots n_N!\cosh^N(r)}. +# +# Now that we have the theoretical formulas, as well as the probabilities from our simulated GBS +# QNode, we can compare the two and see whether they agree. +# +# In order to calculate the probability of different GBS events classically, we need a +# method for calculating the hafnian. +# For this, we will use `The Walrus +# `_ library (which is installed as a dependency of the +# PennyLane-SF plugin): + +from thewalrus import hafnian as haf + +############################################################################## +# Now, for the right-hand side numerator, we first calculate the submatrix +# :math:`A = [(UU^T\mathrm{tanh}(r))]_{st}:` + +A = np.dot(U, U.T) * np.tanh(1) + +############################################################################## +# In GBS, we determine the submatrix by taking the +# rows and columns corresponding to the measured Fock state. For example, to calculate the submatrix +# in the case of the output measurement :math:`\left|{1,1,0,0}\right\rangle,` +# we have + +print(A[:, [0, 1]][[0, 1]]) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# [[ 0.19343159-0.54582922j 0.43418269-0.09169615j] +# [ 0.43418269-0.09169615j -0.27554025-0.46222197j]] +# + +############################################################################## +# i.e., we consider only the rows and columns where a photon was detected, which gives us +# the submatrix corresponding to indices :math:`0` and :math:`1.` + +############################################################################## +# Comparing to simulation +# ----------------------- +# +# Now that we have a method for calculating the hafnian, let's compare the output to that provided by +# the PennyLane QNode. +# +# **Measuring** :math:`|0,0,0,0\rangle` **at the output** +# +# This corresponds to the hafnian of an *empty* matrix, which is simply 1: + +print(1 / np.cosh(1) ** 4) +print(probs[0, 0, 0, 0]) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# 0.1763784476141347 +# 0.17637844761413496 +# + +############################################################################## +# **Measuring** :math:`|1,1,0,0\rangle` **at the output** + +A = (np.dot(U, U.T) * np.tanh(1))[:, [0, 1]][[0, 1]] +print(np.abs(haf(A)) ** 2 / np.cosh(1) ** 4) +print(probs[1, 1, 0, 0]) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# 0.03473293649420271 +# 0.03473293649420282 +# + +############################################################################## +# **Measuring** :math:`|0,1,0,1\rangle` **at the output** + +A = (np.dot(U, U.T) * np.tanh(1))[:, [1, 3]][[1, 3]] +print(np.abs(haf(A)) ** 2 / np.cosh(1) ** 4) +print(probs[0, 1, 0, 1]) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# 0.011870900427255558 +# 0.011870900427255589 +# + +############################################################################## +# **Measuring** :math:`|1,1,1,1\rangle` **at the output** +# +# This corresponds to the hafnian of the full matrix :math:`A=UU^T\mathrm{tanh}(r):` + +A = np.dot(U, U.T) * np.tanh(1) +print(np.abs(haf(A)) ** 2 / np.cosh(1) ** 4) +print(probs[1, 1, 1, 1]) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# 0.005957399165336081 +# 0.005957399165336106 +# + +############################################################################## +# **Measuring** :math:`|2,0,0,0\rangle` **at the output** +# +# Since we have two photons in mode ``q[0]``, we take two copies of the +# first row and first column, making sure to divide by :math:`2!:` + +A = (np.dot(U, U.T) * np.tanh(1))[:, [0, 0]][[0, 0]] +print(np.abs(haf(A)) ** 2 / (2 * np.cosh(1) ** 4)) +print(probs[2, 0, 0, 0]) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# 0.029573843083205383 +# 0.02957384308320549 +# +# The PennyLane simulation results agree (with almost negligible numerical error) to the +# expected result from the Gaussian boson sampling equation! +# +# This demo provides an entry-level walkthrough to the ideas behind GBS, +# providing you with the basic code needed for exploring the ideas behind +# the photonic quantum advantage paper. Try changing the number of modes, +# the number of injected squeezed states, or the cutoff dimension, and +# see how each of these affect the classical computation time. If you're +# interested in learning more about GBS, or about photonic quantum +# computing in general, the +# `Strawberry Fields website `__ is a great resource. +# +# References +# ---------- +# +# .. [#Arute2019] +# +# Arute, F., Arya, K., Babbush, R., et al. "Quantum supremacy using a programmable +# superconducting processor" +# `Nature 574, 505-510 (2019) `__. +# +# .. [#Zhong2020] +# +# Zhong, H.-S., Wang, H., Deng, Y.-H., et al. (2020). Quantum computational advantage using photons. Science, 10.1126/science.abe8770. +# +# .. [#hamilton2017] +# +# Craig S. Hamilton, Regina Kruse, Linda Sansoni, Sonja Barkhofen, Christine Silberhorn, +# and Igor Jex. Gaussian boson sampling. Physical Review Letters, 119:170501, Oct 2017. +# arXiv:1612.01199, doi:10.1103/PhysRevLett.119.170501. +# +# .. [#aaronson2013] +# +# Scott Aaronson and Alex Arkhipov. The computational complexity of linear optics. Theory of +# Computing, 9(1):143–252, 2013. doi:10.4086/toc.2013.v009a004. +# +# .. [#Bourassa2020] +# +# Bourassa, J. E., Alexander, R. N., Vasmer, et al. (2020). Blueprint for a scalable +# photonic fault-tolerant quantum computer. arXiv preprint arXiv:2010.02905. +# +# +# About the author +# ---------------- +# diff --git a/demonstrations/tutorial_adaptive_circuits.py b/demonstrations/tutorial_adaptive_circuits.py index 79d3041fb6..3e29579ccd 100644 --- a/demonstrations/tutorial_adaptive_circuits.py +++ b/demonstrations/tutorial_adaptive_circuits.py @@ -1,426 +1,426 @@ -r""" - -Adaptive circuits for quantum chemistry -======================================= - -.. meta:: - :property="og:description": Learn how to build quantum chemistry circuits adaptively - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_adaptive_circuits.png - -.. related:: - tutorial_quantum_chemistry Building molecular Hamiltonians - tutorial_vqe A brief overview of VQE - tutorial_givens_rotations Givens rotations for quantum chemistry - - -*Author: Soran Jahangiri — Posted: 13 September 2021. Last updated: 10 April 2023* - -The key component of variational quantum algorithms for quantum chemistry is the circuit used to -prepare electronic ground states of a molecule. The variational quantum eigensolver (VQE) -[#peruzzo2014]_, [#yudong2019]_ is the method of choice for performing such quantum chemistry -simulations on quantum devices with few qubits. For a given molecule, the appropriate circuit can -be generated by using a pre-selected wavefunction ansatz, for example, the unitary coupled cluster -with single and double excitations (UCCSD) [#romero2017]_. In this approach, we include all -possible single and double excitations of electrons from the occupied spin-orbitals of a reference -state to the unoccupied spin-orbitals [#givenstutorial]_. This makes the construction of the ansatz -straightforward for any given molecule. However, using a pre-constructed ansatz has the disadvantage -of reducing performance in favour of generality: the approach may work well in many cases, but it -will not be optimized for a specific problem. - -In practical applications, including all possible excitations usually increases the cost of the -simulations without improving the accuracy of the results. This motivates implementing a strategy -that allows for approximation of the contribution of the excitations and selects only those -excitations that are found to be important for the given molecule. This can be done by using -adaptive methods to construct a circuit for each given problem [#grimsley2019]_. Using adaptive -circuits helps improve performance at the cost of reducing generality. - -.. figure:: /_static/demonstration_assets/adaptive_circuits/main.png - :width: 75% - :align: center - - Examples of selecting specific gates to generate adaptive circuits. - -In this tutorial, you will learn how to **adaptively** build customized quantum chemistry circuits -to perform ADAPT-VQE [#grimsley2019]_ simulations. This includes a recipe to adaptively select gates -that have a significant contribution to the desired state, while neglecting those that have a small -contribution. You will also learn how to use PennyLane to leverage the sparsity of a molecular -Hamiltonian to make the computation of the expectation values even more efficient. Let's get -started! - -Adaptive circuits ------------------ - -The main idea behind building adaptive circuits is to compute the gradients with respect to all -possible excitation gates and then select gates based on the magnitude of the computed gradients. - -There are different ways to make use of the gradient information and here we discuss one of -these strategies and apply it to compute the ground state energy of LiH. This method requires constructing the -Hamiltonian and determining all possible excitations, which we can do with functionality built into PennyLane. -But we first need to define the molecular parameters, including atomic symbols and coordinates. -Note that the atomic coordinates are in `Bohr `_. -""" - -import pennylane as qml -import jax -import numpy as np -import time - -from pennylane import qchem -from jax import numpy as jnp - -jax.config.update("jax_enable_x64", True) - -symbols = ["Li", "H"] -geometry = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 2.969280527]]) -molecule = qchem.Molecule(symbols, geometry) - -############################################################################## -# We now compute the molecular Hamiltonian in the -# `STO-3G `_ basis and obtain the electronic -# excitations. We restrict ourselves to single and double excitations, but higher-level ones such -# as triple and quadruple excitations can be considered as well. Each of these electronic excitations -# is represented by a gate that excites electrons from the occupied orbitals of a reference state to -# the unoccupied ones. This allows us to prepare a state that is a superposition of the reference -# state and all of the excited states. - -H, qubits = qchem.molecular_hamiltonian( - molecule, - active_electrons=2, - active_orbitals=5 -) - -active_electrons = 2 - -singles, doubles = qchem.excitations(active_electrons, qubits) - -print(f"Total number of excitations = {len(singles) + len(doubles)}") - -############################################################################## -# Note that we have a total of 24 excitations which can be represented by the same number of -# excitation gates [#givenstutorial]_. Let's now use an :class:`~.pennylane.AdaptiveOptimizer` -# implemented in PennyLane to construct an adaptive circuit. -# -# Adaptive Optimizer -# ~~~~~~~~~~~~~~~~~~ -# The adaptive optimizer -# grows an input quantum circuit by adding and optimizing gates selected from a user-defined -# collection of operators. The algorithm first appends all of the gates provided in the initial -# operator pool and computes the circuit gradients with respect to the gate parameters. It retains -# the gate which has the largest gradient and then optimizes its parameter. -# The process of growing the circuit can be repeated until the computed gradients converge to zero. -# Let's use :class:`~.pennylane.AdaptiveOptimizer` to perform an ADAPT-VQE [#grimsley2019]_ -# simulation and build an adaptive circuit for LiH. -# -# We first create the operator pool which contains all single and double excitations. - -singles_excitations = [qml.SingleExcitation(0.0, x) for x in singles] -doubles_excitations = [qml.DoubleExcitation(0.0, x) for x in doubles] -operator_pool = doubles_excitations + singles_excitations - -############################################################################## -# We now define an initial circuit that prepares a Hartree-Fock state and returns the expectation -# value of the Hamiltonian. We also need to define a device. - -hf_state = qchem.hf_state(active_electrons, qubits) -dev = qml.device("default.qubit", wires=qubits) -@qml.qnode(dev) -def circuit(): - [qml.PauliX(i) for i in np.nonzero(hf_state)[0]] - return qml.expval(H) - -############################################################################## -# We instantiate the optimizer and use it to build the circuit adaptively. - -opt = qml.optimize.AdaptiveOptimizer() -for i in range(len(operator_pool)): - circuit, energy, gradient = opt.step_and_cost(circuit, operator_pool) - if i % 3 == 0: - print("n = {:}, E = {:.8f} H, Largest Gradient = {:.3f}".format(i, energy, gradient)) - print(qml.draw(circuit, decimals=None)()) - print() - if gradient < 3e-3: - break - -############################################################################## -# The resulting energy matches the exact energy of the ground electronic state of LiH, which is -# -7.8825378193 Ha, within chemical accuracy. Note that some of the gates appear more than once in -# the circuit. By default, :class:`~.pennylane.AdaptiveOptimizer` does not eliminate the selected -# gates from the pool. We can set ``drain_pool=True`` to prevent repetition of the gates by -# removing the selected gate from the operator pool. - -@qml.qnode(dev) -def circuit(): - [qml.PauliX(i) for i in np.nonzero(hf_state)[0]] - return qml.expval(H) - -opt = qml.optimize.AdaptiveOptimizer() -for i in range(len(operator_pool)): - circuit, energy, gradient = opt.step_and_cost(circuit, operator_pool, drain_pool=True) - if i % 2 == 0: - print("n = {:}, E = {:.8f} H, Largest Gradient = {:.3f}".format(i, energy, gradient)) - print(qml.draw(circuit, decimals=None)()) - print() - if gradient < 3e-3: - break - -############################################################################## -# Manual construction -# ~~~~~~~~~~~~~~~~~~~ -# We can also build adaptive circuits manually by adding groups of gates one at a time. We follow -# these steps: -# -# 1. Compute gradients for all double excitations. -# 2. Select the double excitations with gradients larger than a pre-defined threshold. -# 3. Perform VQE to obtain the optimized parameters for the selected double excitations. -# 4. Repeat steps 1 and 2 for the single excitations. -# 5. Perform the final VQE optimization with all the selected excitations. -# -# We create a circuit that applies a selected group of gates to the reference Hartree-Fock state. - - -# Re-define H using Jax Arrays -molecule = qchem.Molecule(symbols, jnp.array(geometry)) -H, qubits = qchem.molecular_hamiltonian( - molecule, - active_electrons=2, - active_orbitals=5 -) - -def circuit_1(params, excitations): - qml.BasisState(jnp.array(hf_state), wires=range(qubits)) - - for i, excitation in enumerate(excitations): - if len(excitation) == 4: - qml.DoubleExcitation(params[i], wires=excitation) - else: - qml.SingleExcitation(params[i], wires=excitation) - return qml.expval(H) - -############################################################################## -# We now construct our first group of gates by including all the double excitations and compute the -# gradient for each one. We also need to define a cost -# function. We initialize the parameter values to zero such that the gradients are computed -# with respect to the Hartree-Fock state. - - -dev = qml.device("lightning.qubit", wires=qubits) -cost_fn = qml.QNode(circuit_1, dev, interface="jax") - -circuit_gradient = jax.grad(cost_fn, argnums=0) - -params = [0.0] * len(doubles) -grads = circuit_gradient(params, excitations=doubles) - -for i in range(len(doubles)): - print(f"Excitation : {doubles[i]}, Gradient: {grads[i]}") - -############################################################################## -# The computed gradients have different values, reflecting the contribution of each gate -# in the final state prepared by the circuit. Many of the gradient values are zero and we select -# those gates that have a gradient above a pre-defined threshold, which we set to :math:`10^{-5}.` - -doubles_select = [doubles[i] for i in range(len(doubles)) if abs(grads[i]) > 1.0e-5] -doubles_select - -############################################################################## -# There are only 6 double excitation gates, out of the original 16, that have gradients above the -# threshold. We add the selected gates to the circuit and optimize it to determine -# the updated parameters for the selected gates. We also need to define an optimizer. Note that the -# optimization is not very costly as we only have six gates in our circuit. - -import optax - -params_doubles = jnp.zeros(len(doubles_select)) - -opt = optax.sgd(learning_rate=0.5) # sgd stands for StochasticGradientDescent -opt_state = opt.init(params_doubles) - -for n in range(10): - gradient = jax.grad(cost_fn, argnums=0)(params_doubles, excitations=doubles_select) - updates, opt_state = opt.update(gradient, opt_state) - params_doubles = optax.apply_updates(params_doubles, updates) - -############################################################################## -# Now, we keep the selected gates in the circuit and compute the gradients with respect to all of -# the single excitation gates, selecting those that have a non-negligible gradient. To do that, we -# need to slightly modify our circuit such that parameters of the double excitation gates are kept -# fixed while the gradients are computed for the single excitation gates. - - -def circuit_2(params, excitations, gates_select, params_select): - qml.BasisState(hf_state, wires=range(qubits)) - - for i, gate in enumerate(gates_select): - if len(gate) == 4: - qml.DoubleExcitation(params_select[i], wires=gate) - elif len(gate) == 2: - qml.SingleExcitation(params_select[i], wires=gate) - - for i, gate in enumerate(excitations): - if len(gate) == 4: - qml.DoubleExcitation(params[i], wires=gate) - elif len(gate) == 2: - qml.SingleExcitation(params[i], wires=gate) - return qml.expval(H) - - -############################################################################## -# We now compute the gradients for the single excitation gates. - -cost_fn = qml.QNode(circuit_2, dev, interface="jax") -circuit_gradient = jax.grad(cost_fn, argnums=0) -params = [0.0] * len(singles) - -grads = circuit_gradient( - params, - excitations=singles, - gates_select=doubles_select, - params_select=params_doubles -) - -for i in range(len(singles)): - print(f"Excitation : {singles[i]}, Gradient: {grads[i]}") - -############################################################################## -# Similar to the double excitation gates, we select those single excitations that have a gradient -# larger than a predefined threshold. - -singles_select = [singles[i] for i in range(len(singles)) if abs(grads[i]) > 1.0e-5] -singles_select - -############################################################################## -# We now have all of the gates we need to build our circuit. The selected single and double -# excitation gates are highlighted in the figure below. -# -# .. figure:: /_static/demonstration_assets/adaptive_circuits/adapted_circuit.png -# :width: 90% -# :align: center -# -# We perform a final circuit optimization to get the ground-state energy. The resulting energy -# should match the exact energy of the ground electronic state of LiH which is -7.8825378193 Ha. - -cost_fn = qml.QNode(circuit_1, dev, interface="jax") - -params = jnp.zeros(len(doubles_select + singles_select)) - -gates_select = doubles_select + singles_select -opt_state = opt.init(params) - -for n in range(10): - t1 = time.time() - gradient = jax.grad(cost_fn, argnums=0)(params, excitations=doubles_select) - updates, opt_state = opt.update(gradient, opt_state) - params = optax.apply_updates(params, updates) - energy = cost_fn(params, doubles_select) - t2 = time.time() - print("n = {:}, E = {:.8f} H, t = {:.2f} s".format(n, energy, t2 - t1)) - -############################################################################## -# Success! We obtained the ground state energy of LiH, within chemical accuracy, by having -# only 10 gates in our circuit. This is less than half of the total number of single and double -# excitations of LiH (24). - -############################################################################## -# Sparse Hamiltonians -# ------------------- -# -# Molecular Hamiltonians and quantum states are sparse. For instance, let’s look at the Hamiltonian -# we built for LiH. We can compute its matrix representation in the computational basis using the -# Hamiltonian function :meth:`~.pennylane.Hamiltonian.sparse_matrix`. This function -# returns the matrix in the SciPy `sparse coordinate `_ format. - -H_sparse = H.sparse_matrix() -H_sparse - -############################################################################## -# The matrix has :math:`1024^2=1,048,576` entries, but only :math:`11264` of them are non-zero. -# -# .. figure:: /_static/demonstration_assets/adaptive_circuits/h_sparse.png -# :width: 65% -# :align: center -# -# Matrix representation of the LiH Hamiltonian in the computational basis. -# -# Leveraging this sparsity can significantly reduce the -# simulation times. We use the implemented functionality in PennyLane for computing the expectation -# value of the sparse Hamiltonian observable. This can reduce the cost of simulations by -# orders of magnitude depending on the size of the molecule. We use the selected gates obtained in -# the previous steps and perform the final optimization step with the sparse method. Note that the -# sparse method currently only works with the parameter-shift differentiation method. - -excitations = doubles_select + singles_select - -params = jnp.zeros(len(excitations)) - -@qml.qnode(dev, diff_method="parameter-shift", interface="jax") -def circuit(params): - qml.BasisState(hf_state, wires=range(qubits)) - - for i, excitation in enumerate(excitations): - if len(excitation) == 4: - qml.DoubleExcitation(params[i], wires=excitation) - elif len(excitation) == 2: - qml.SingleExcitation(params[i], wires=excitation) - - return qml.expval(qml.SparseHamiltonian(H_sparse, wires=range(qubits))) - - -for n in range(10): - t1 = time.time() - gradient = jax.grad(cost_fn, argnums=0)(params, excitations=doubles_select) - updates, opt_state = opt.update(gradient, opt_state) - params = optax.apply_updates(params, updates) - energy = cost_fn(params, doubles_select) - t2 = time.time() - print("n = {:}, E = {:.8f} H, t = {:.2f} s".format(n, energy, t2 - t1)) - -############################################################################## -# Using the sparse method reproduces the ground state energy while the optimization time is -# much shorter. The average iteration time for the sparse method is about 18 times smaller than that -# of the original non-sparse approach. The performance of the sparse optimization will be even -# better for larger molecules. -# -# Conclusions -# ----------- -# We have learned that building quantum chemistry circuits adaptively and using the -# functionality for sparse objects makes molecular simulations significantly more efficient. We -# learned how to use an adaptive optimizer implemented in PennyLane, that selects the gates one at -# time, to perform ADAPT-VQE [#grimsley2019]_ simulations. We also followed an adaptive strategy -# that selects a group of gates based on information about the gradients. -# -# References -# ---------- -# -# .. [#peruzzo2014] -# -# A. Peruzzo, J. McClean *et al.*, "A variational eigenvalue solver on a photonic -# quantum processor". `Nat. Commun. 5, 4213 (2014). -# `__ -# -# .. [#yudong2019] -# -# Y. Cao, J. Romero, *et al.*, "Quantum Chemistry in the Age of Quantum Computing". -# `Chem. Rev. 2019, 119, 19, 10856-10915. -# `__ -# -# .. [#romero2017] -# -# J. Romero, R. Babbush, *et al.*, "Strategies for quantum computing molecular -# energies using the unitary coupled cluster ansatz". `arXiv:1701.02691 -# `_ -# -# .. [#givenstutorial] -# -# :doc:`tutorial_givens_rotations` -# -# .. [#grimsley2019] -# -# H. R. Grimsley, S. E. Economou, E. Barnes, N. J. Mayhall, "An adaptive -# variational algorithm for exact molecular simulations on a quantum computer". -# `Nat. Commun. 2019, 10, 3007. -# `__ -# -# -# About the author -# ---------------- -# +r""" + +Adaptive circuits for quantum chemistry +======================================= + +.. meta:: + :property="og:description": Learn how to build quantum chemistry circuits adaptively + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_adaptive_circuits.png + +.. related:: + tutorial_quantum_chemistry Building molecular Hamiltonians + tutorial_vqe A brief overview of VQE + tutorial_givens_rotations Givens rotations for quantum chemistry + + +*Author: Soran Jahangiri — Posted: 13 September 2021. Last updated: 10 April 2023* + +The key component of variational quantum algorithms for quantum chemistry is the circuit used to +prepare electronic ground states of a molecule. The variational quantum eigensolver (VQE) +[#peruzzo2014]_, [#yudong2019]_ is the method of choice for performing such quantum chemistry +simulations on quantum devices with few qubits. For a given molecule, the appropriate circuit can +be generated by using a pre-selected wavefunction ansatz, for example, the unitary coupled cluster +with single and double excitations (UCCSD) [#romero2017]_. In this approach, we include all +possible single and double excitations of electrons from the occupied spin-orbitals of a reference +state to the unoccupied spin-orbitals [#givenstutorial]_. This makes the construction of the ansatz +straightforward for any given molecule. However, using a pre-constructed ansatz has the disadvantage +of reducing performance in favour of generality: the approach may work well in many cases, but it +will not be optimized for a specific problem. + +In practical applications, including all possible excitations usually increases the cost of the +simulations without improving the accuracy of the results. This motivates implementing a strategy +that allows for approximation of the contribution of the excitations and selects only those +excitations that are found to be important for the given molecule. This can be done by using +adaptive methods to construct a circuit for each given problem [#grimsley2019]_. Using adaptive +circuits helps improve performance at the cost of reducing generality. + +.. figure:: /_static/demonstration_assets/adaptive_circuits/main.png + :width: 75% + :align: center + + Examples of selecting specific gates to generate adaptive circuits. + +In this tutorial, you will learn how to **adaptively** build customized quantum chemistry circuits +to perform ADAPT-VQE [#grimsley2019]_ simulations. This includes a recipe to adaptively select gates +that have a significant contribution to the desired state, while neglecting those that have a small +contribution. You will also learn how to use PennyLane to leverage the sparsity of a molecular +Hamiltonian to make the computation of the expectation values even more efficient. Let's get +started! + +Adaptive circuits +----------------- + +The main idea behind building adaptive circuits is to compute the gradients with respect to all +possible excitation gates and then select gates based on the magnitude of the computed gradients. + +There are different ways to make use of the gradient information and here we discuss one of +these strategies and apply it to compute the ground state energy of LiH. This method requires constructing the +Hamiltonian and determining all possible excitations, which we can do with functionality built into PennyLane. +But we first need to define the molecular parameters, including atomic symbols and coordinates. +Note that the atomic coordinates are in `Bohr `_. +""" + +import pennylane as qml +import jax +import numpy as np +import time + +from pennylane import qchem +from jax import numpy as jnp + +jax.config.update("jax_enable_x64", True) + +symbols = ["Li", "H"] +geometry = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 2.969280527]]) +molecule = qchem.Molecule(symbols, geometry) + +############################################################################## +# We now compute the molecular Hamiltonian in the +# `STO-3G `_ basis and obtain the electronic +# excitations. We restrict ourselves to single and double excitations, but higher-level ones such +# as triple and quadruple excitations can be considered as well. Each of these electronic excitations +# is represented by a gate that excites electrons from the occupied orbitals of a reference state to +# the unoccupied ones. This allows us to prepare a state that is a superposition of the reference +# state and all of the excited states. + +H, qubits = qchem.molecular_hamiltonian( + molecule, + active_electrons=2, + active_orbitals=5 +) + +active_electrons = 2 + +singles, doubles = qchem.excitations(active_electrons, qubits) + +print(f"Total number of excitations = {len(singles) + len(doubles)}") + +############################################################################## +# Note that we have a total of 24 excitations which can be represented by the same number of +# excitation gates [#givenstutorial]_. Let's now use an :class:`~.pennylane.AdaptiveOptimizer` +# implemented in PennyLane to construct an adaptive circuit. +# +# Adaptive Optimizer +# ~~~~~~~~~~~~~~~~~~ +# The adaptive optimizer +# grows an input quantum circuit by adding and optimizing gates selected from a user-defined +# collection of operators. The algorithm first appends all of the gates provided in the initial +# operator pool and computes the circuit gradients with respect to the gate parameters. It retains +# the gate which has the largest gradient and then optimizes its parameter. +# The process of growing the circuit can be repeated until the computed gradients converge to zero. +# Let's use :class:`~.pennylane.AdaptiveOptimizer` to perform an ADAPT-VQE [#grimsley2019]_ +# simulation and build an adaptive circuit for LiH. +# +# We first create the operator pool which contains all single and double excitations. + +singles_excitations = [qml.SingleExcitation(0.0, x) for x in singles] +doubles_excitations = [qml.DoubleExcitation(0.0, x) for x in doubles] +operator_pool = doubles_excitations + singles_excitations + +############################################################################## +# We now define an initial circuit that prepares a Hartree-Fock state and returns the expectation +# value of the Hamiltonian. We also need to define a device. + +hf_state = qchem.hf_state(active_electrons, qubits) +dev = qml.device("default.qubit", wires=qubits) +@qml.qnode(dev) +def circuit(): + [qml.PauliX(i) for i in np.nonzero(hf_state)[0]] + return qml.expval(H) + +############################################################################## +# We instantiate the optimizer and use it to build the circuit adaptively. + +opt = qml.optimize.AdaptiveOptimizer() +for i in range(len(operator_pool)): + circuit, energy, gradient = opt.step_and_cost(circuit, operator_pool) + if i % 3 == 0: + print("n = {:}, E = {:.8f} H, Largest Gradient = {:.3f}".format(i, energy, gradient)) + print(qml.draw(circuit, decimals=None)()) + print() + if gradient < 3e-3: + break + +############################################################################## +# The resulting energy matches the exact energy of the ground electronic state of LiH, which is +# -7.8825378193 Ha, within chemical accuracy. Note that some of the gates appear more than once in +# the circuit. By default, :class:`~.pennylane.AdaptiveOptimizer` does not eliminate the selected +# gates from the pool. We can set ``drain_pool=True`` to prevent repetition of the gates by +# removing the selected gate from the operator pool. + +@qml.qnode(dev) +def circuit(): + [qml.PauliX(i) for i in np.nonzero(hf_state)[0]] + return qml.expval(H) + +opt = qml.optimize.AdaptiveOptimizer() +for i in range(len(operator_pool)): + circuit, energy, gradient = opt.step_and_cost(circuit, operator_pool, drain_pool=True) + if i % 2 == 0: + print("n = {:}, E = {:.8f} H, Largest Gradient = {:.3f}".format(i, energy, gradient)) + print(qml.draw(circuit, decimals=None)()) + print() + if gradient < 3e-3: + break + +############################################################################## +# Manual construction +# ~~~~~~~~~~~~~~~~~~~ +# We can also build adaptive circuits manually by adding groups of gates one at a time. We follow +# these steps: +# +# 1. Compute gradients for all double excitations. +# 2. Select the double excitations with gradients larger than a pre-defined threshold. +# 3. Perform VQE to obtain the optimized parameters for the selected double excitations. +# 4. Repeat steps 1 and 2 for the single excitations. +# 5. Perform the final VQE optimization with all the selected excitations. +# +# We create a circuit that applies a selected group of gates to the reference Hartree-Fock state. + + +# Re-define H using Jax Arrays +molecule = qchem.Molecule(symbols, jnp.array(geometry)) +H, qubits = qchem.molecular_hamiltonian( + molecule, + active_electrons=2, + active_orbitals=5 +) + +def circuit_1(params, excitations): + qml.BasisState(jnp.array(hf_state), wires=range(qubits)) + + for i, excitation in enumerate(excitations): + if len(excitation) == 4: + qml.DoubleExcitation(params[i], wires=excitation) + else: + qml.SingleExcitation(params[i], wires=excitation) + return qml.expval(H) + +############################################################################## +# We now construct our first group of gates by including all the double excitations and compute the +# gradient for each one. We also need to define a cost +# function. We initialize the parameter values to zero such that the gradients are computed +# with respect to the Hartree-Fock state. + + +dev = qml.device("lightning.qubit", wires=qubits) +cost_fn = qml.QNode(circuit_1, dev, interface="jax") + +circuit_gradient = jax.grad(cost_fn, argnums=0) + +params = [0.0] * len(doubles) +grads = circuit_gradient(params, excitations=doubles) + +for i in range(len(doubles)): + print(f"Excitation : {doubles[i]}, Gradient: {grads[i]}") + +############################################################################## +# The computed gradients have different values, reflecting the contribution of each gate +# in the final state prepared by the circuit. Many of the gradient values are zero and we select +# those gates that have a gradient above a pre-defined threshold, which we set to :math:`10^{-5}.` + +doubles_select = [doubles[i] for i in range(len(doubles)) if abs(grads[i]) > 1.0e-5] +doubles_select + +############################################################################## +# There are only 6 double excitation gates, out of the original 16, that have gradients above the +# threshold. We add the selected gates to the circuit and optimize it to determine +# the updated parameters for the selected gates. We also need to define an optimizer. Note that the +# optimization is not very costly as we only have six gates in our circuit. + +import optax + +params_doubles = jnp.zeros(len(doubles_select)) + +opt = optax.sgd(learning_rate=0.5) # sgd stands for StochasticGradientDescent +opt_state = opt.init(params_doubles) + +for n in range(10): + gradient = jax.grad(cost_fn, argnums=0)(params_doubles, excitations=doubles_select) + updates, opt_state = opt.update(gradient, opt_state) + params_doubles = optax.apply_updates(params_doubles, updates) + +############################################################################## +# Now, we keep the selected gates in the circuit and compute the gradients with respect to all of +# the single excitation gates, selecting those that have a non-negligible gradient. To do that, we +# need to slightly modify our circuit such that parameters of the double excitation gates are kept +# fixed while the gradients are computed for the single excitation gates. + + +def circuit_2(params, excitations, gates_select, params_select): + qml.BasisState(hf_state, wires=range(qubits)) + + for i, gate in enumerate(gates_select): + if len(gate) == 4: + qml.DoubleExcitation(params_select[i], wires=gate) + elif len(gate) == 2: + qml.SingleExcitation(params_select[i], wires=gate) + + for i, gate in enumerate(excitations): + if len(gate) == 4: + qml.DoubleExcitation(params[i], wires=gate) + elif len(gate) == 2: + qml.SingleExcitation(params[i], wires=gate) + return qml.expval(H) + + +############################################################################## +# We now compute the gradients for the single excitation gates. + +cost_fn = qml.QNode(circuit_2, dev, interface="jax") +circuit_gradient = jax.grad(cost_fn, argnums=0) +params = [0.0] * len(singles) + +grads = circuit_gradient( + params, + excitations=singles, + gates_select=doubles_select, + params_select=params_doubles +) + +for i in range(len(singles)): + print(f"Excitation : {singles[i]}, Gradient: {grads[i]}") + +############################################################################## +# Similar to the double excitation gates, we select those single excitations that have a gradient +# larger than a predefined threshold. + +singles_select = [singles[i] for i in range(len(singles)) if abs(grads[i]) > 1.0e-5] +singles_select + +############################################################################## +# We now have all of the gates we need to build our circuit. The selected single and double +# excitation gates are highlighted in the figure below. +# +# .. figure:: /_static/demonstration_assets/adaptive_circuits/adapted_circuit.png +# :width: 90% +# :align: center +# +# We perform a final circuit optimization to get the ground-state energy. The resulting energy +# should match the exact energy of the ground electronic state of LiH which is -7.8825378193 Ha. + +cost_fn = qml.QNode(circuit_1, dev, interface="jax") + +params = jnp.zeros(len(doubles_select + singles_select)) + +gates_select = doubles_select + singles_select +opt_state = opt.init(params) + +for n in range(10): + t1 = time.time() + gradient = jax.grad(cost_fn, argnums=0)(params, excitations=doubles_select) + updates, opt_state = opt.update(gradient, opt_state) + params = optax.apply_updates(params, updates) + energy = cost_fn(params, doubles_select) + t2 = time.time() + print("n = {:}, E = {:.8f} H, t = {:.2f} s".format(n, energy, t2 - t1)) + +############################################################################## +# Success! We obtained the ground state energy of LiH, within chemical accuracy, by having +# only 10 gates in our circuit. This is less than half of the total number of single and double +# excitations of LiH (24). + +############################################################################## +# Sparse Hamiltonians +# ------------------- +# +# Molecular Hamiltonians and quantum states are sparse. For instance, let’s look at the Hamiltonian +# we built for LiH. We can compute its matrix representation in the computational basis using the +# Hamiltonian function :meth:`~.pennylane.Hamiltonian.sparse_matrix`. This function +# returns the matrix in the SciPy `sparse coordinate `_ format. + +H_sparse = H.sparse_matrix() +H_sparse + +############################################################################## +# The matrix has :math:`1024^2=1,048,576` entries, but only :math:`11264` of them are non-zero. +# +# .. figure:: /_static/demonstration_assets/adaptive_circuits/h_sparse.png +# :width: 65% +# :align: center +# +# Matrix representation of the LiH Hamiltonian in the computational basis. +# +# Leveraging this sparsity can significantly reduce the +# simulation times. We use the implemented functionality in PennyLane for computing the expectation +# value of the sparse Hamiltonian observable. This can reduce the cost of simulations by +# orders of magnitude depending on the size of the molecule. We use the selected gates obtained in +# the previous steps and perform the final optimization step with the sparse method. Note that the +# sparse method currently only works with the parameter-shift differentiation method. + +excitations = doubles_select + singles_select + +params = jnp.zeros(len(excitations)) + +@qml.qnode(dev, diff_method="parameter-shift", interface="jax") +def circuit(params): + qml.BasisState(hf_state, wires=range(qubits)) + + for i, excitation in enumerate(excitations): + if len(excitation) == 4: + qml.DoubleExcitation(params[i], wires=excitation) + elif len(excitation) == 2: + qml.SingleExcitation(params[i], wires=excitation) + + return qml.expval(qml.SparseHamiltonian(H_sparse, wires=range(qubits))) + + +for n in range(10): + t1 = time.time() + gradient = jax.grad(cost_fn, argnums=0)(params, excitations=doubles_select) + updates, opt_state = opt.update(gradient, opt_state) + params = optax.apply_updates(params, updates) + energy = cost_fn(params, doubles_select) + t2 = time.time() + print("n = {:}, E = {:.8f} H, t = {:.2f} s".format(n, energy, t2 - t1)) + +############################################################################## +# Using the sparse method reproduces the ground state energy while the optimization time is +# much shorter. The average iteration time for the sparse method is about 18 times smaller than that +# of the original non-sparse approach. The performance of the sparse optimization will be even +# better for larger molecules. +# +# Conclusions +# ----------- +# We have learned that building quantum chemistry circuits adaptively and using the +# functionality for sparse objects makes molecular simulations significantly more efficient. We +# learned how to use an adaptive optimizer implemented in PennyLane, that selects the gates one at +# time, to perform ADAPT-VQE [#grimsley2019]_ simulations. We also followed an adaptive strategy +# that selects a group of gates based on information about the gradients. +# +# References +# ---------- +# +# .. [#peruzzo2014] +# +# A. Peruzzo, J. McClean *et al.*, "A variational eigenvalue solver on a photonic +# quantum processor". `Nat. Commun. 5, 4213 (2014). +# `__ +# +# .. [#yudong2019] +# +# Y. Cao, J. Romero, *et al.*, "Quantum Chemistry in the Age of Quantum Computing". +# `Chem. Rev. 2019, 119, 19, 10856-10915. +# `__ +# +# .. [#romero2017] +# +# J. Romero, R. Babbush, *et al.*, "Strategies for quantum computing molecular +# energies using the unitary coupled cluster ansatz". `arXiv:1701.02691 +# `_ +# +# .. [#givenstutorial] +# +# :doc:`tutorial_givens_rotations` +# +# .. [#grimsley2019] +# +# H. R. Grimsley, S. E. Economou, E. Barnes, N. J. Mayhall, "An adaptive +# variational algorithm for exact molecular simulations on a quantum computer". +# `Nat. Commun. 2019, 10, 3007. +# `__ +# +# +# About the author +# ---------------- +# diff --git a/demonstrations/tutorial_adversarial_attacks_QML.metadata.json b/demonstrations/tutorial_adversarial_attacks_QML.metadata.json index 79f6a3580b..ba6ee618b5 100644 --- a/demonstrations/tutorial_adversarial_attacks_QML.metadata.json +++ b/demonstrations/tutorial_adversarial_attacks_QML.metadata.json @@ -1,79 +1,79 @@ -{ - "title": "Adversarial attacks and robustness for quantum machine learning", - "authors": [ - { - "username": "mxw" - }, - { - "username": "kil" - - } - ], - "dateOfPublication": "2024-09-16T00:00:00+00:00", - "dateOfLastModification": "2024-11-08T00:00:00+00:00", - "categories": ["Quantum Machine Learning", "Quantum Computing"], - "tags": ["Quantum machine learning", "QML", "Adversarial attacks", "Quantum security"], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_adversarial_attacks_qml.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_adversarial_attacks_qml.png" - } - ], - "seoDescription": "Learn how to construct adversarial attacks on classification networks and how to make quantum machine learning (QML) models more robust.", - "doi": "", - "references": [ - { - "id": "Wendlinger2024", - "type": "preprint", - "title": "A Comparative Analysis of Adversarial Robustness for Quantum and Classical Machine Learning Models", - "authors": "Maximilian Wendlinger, Kilian Tscharke, Pascal Debus", - "year": "2024", - "doi": "10.48550/arXiv.2404.16154", - "url": "https://arxiv.org/abs/2404.16154" - }, - { - "id": "Goodfellow2014", - "type": "preprint", - "title": "Explaining and harnessing adversarial examples", - "authors": "Ian J. Goodfellow, Jonathon Shlens, Christian Szegedy", - "year": "2014", - "doi": "10.48550/arXiv.1412.6572", - "url": "https://arxiv.org/abs/1412.6572" - - }, - { - "id": "Liu2020", - "type": "preprint", - "title": "A rigorous and robust quantum speed-up in supervised machine learning", - "authors": "Yunchao Liu, Srinivasan Arunachalam, Kristan Temme", - "year": "2020", - "doi": "10.48550/arXiv.2010.02174", - "url": "https://arxiv.org/abs/2010.02174" - - }, - { - "id": "Lu2019", - "type": "preprint", - "title": "Quantum Adversarial Machine Learning", - "authors": "Sirui Lu, Lu-Ming Duan, Dong-Ling Deng", - "year": "2019", - "doi": "10.48550/arXiv.2001.00030", - "url": "https://arxiv.org/abs/2001.00030" - - } - ], - "basedOnPapers": ["10.48550/arXiv.2404.16154"], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_variational_classifier", - "weight": 1.0 - } - ], - "hardware": [] -} +{ + "title": "Adversarial attacks and robustness for quantum machine learning", + "authors": [ + { + "username": "mxw" + }, + { + "username": "kil" + + } + ], + "dateOfPublication": "2024-09-16T00:00:00+00:00", + "dateOfLastModification": "2024-11-08T00:00:00+00:00", + "categories": ["Quantum Machine Learning", "Quantum Computing"], + "tags": ["Quantum machine learning", "QML", "Adversarial attacks", "Quantum security"], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_adversarial_attacks_qml.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_adversarial_attacks_qml.png" + } + ], + "seoDescription": "Learn how to construct adversarial attacks on classification networks and how to make quantum machine learning (QML) models more robust.", + "doi": "", + "references": [ + { + "id": "Wendlinger2024", + "type": "preprint", + "title": "A Comparative Analysis of Adversarial Robustness for Quantum and Classical Machine Learning Models", + "authors": "Maximilian Wendlinger, Kilian Tscharke, Pascal Debus", + "year": "2024", + "doi": "10.48550/arXiv.2404.16154", + "url": "https://arxiv.org/abs/2404.16154" + }, + { + "id": "Goodfellow2014", + "type": "preprint", + "title": "Explaining and harnessing adversarial examples", + "authors": "Ian J. Goodfellow, Jonathon Shlens, Christian Szegedy", + "year": "2014", + "doi": "10.48550/arXiv.1412.6572", + "url": "https://arxiv.org/abs/1412.6572" + + }, + { + "id": "Liu2020", + "type": "preprint", + "title": "A rigorous and robust quantum speed-up in supervised machine learning", + "authors": "Yunchao Liu, Srinivasan Arunachalam, Kristan Temme", + "year": "2020", + "doi": "10.48550/arXiv.2010.02174", + "url": "https://arxiv.org/abs/2010.02174" + + }, + { + "id": "Lu2019", + "type": "preprint", + "title": "Quantum Adversarial Machine Learning", + "authors": "Sirui Lu, Lu-Ming Duan, Dong-Ling Deng", + "year": "2019", + "doi": "10.48550/arXiv.2001.00030", + "url": "https://arxiv.org/abs/2001.00030" + + } + ], + "basedOnPapers": ["10.48550/arXiv.2404.16154"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_variational_classifier", + "weight": 1.0 + } + ], + "hardware": [] +} diff --git a/demonstrations/tutorial_backprop.py b/demonstrations/tutorial_backprop.py index c55bbf280b..9db4a40766 100644 --- a/demonstrations/tutorial_backprop.py +++ b/demonstrations/tutorial_backprop.py @@ -1,456 +1,456 @@ -r""" -Quantum gradients with backpropagation -====================================== - -.. meta:: - :property="og:description": Using backpropagation can speed up training of quantum circuits compared to the parameter-shift rule—if you are using a simulator. - - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/sphx_glr_tutorial_backprop_002.png - -.. related:: - - tutorial_quantum_natural_gradient Quantum natural gradient - -*Author: Josh Izaac — Posted: 11 August 2020. Last updated: 31 January 2021.* - -In PennyLane, any quantum device, whether a hardware device or a simulator, can be -trained using the :doc:`parameter-shift rule ` to compute quantum -gradients. Indeed, the parameter-shift rule is ideally suited to hardware devices, as it does -not require any knowledge about the internal workings of the device; it is sufficient to treat -the device as a 'black box', and to query it with different input values in order to determine the gradient. - -When working with simulators, however, we *do* have access to the internal (classical) -computations being performed. This allows us to take advantage of other methods of computing the -gradient, such as backpropagation, which may be advantageous in certain regimes. In this tutorial, -we will compare and contrast the parameter-shift rule against backpropagation, using -the PennyLane :class:`default.qubit ` -device. - -The parameter-shift rule ------------------------- - -The parameter-shift rule states that, given a variational quantum circuit :math:`U(\boldsymbol -\theta)` composed of parametrized Pauli rotations, and some measured observable :math:`\hat{B},` the -derivative of the expectation value - -.. math:: - - \langle \hat{B} \rangle (\boldsymbol\theta) = - \langle 0 \vert U(\boldsymbol\theta)^\dagger \hat{B} U(\boldsymbol\theta) \vert 0\rangle - -with respect to the input circuit parameters :math:`\boldsymbol{\theta}` is given by - -.. math:: - - \nabla_{\theta_i}\langle \hat{B} \rangle(\boldsymbol\theta) - = \frac{1}{2} - \left[ - \langle \hat{B} \rangle\left(\boldsymbol\theta + \frac{\pi}{2}\hat{\mathbf{e}}_i\right) - - \langle \hat{B} \rangle\left(\boldsymbol\theta - \frac{\pi}{2}\hat{\mathbf{e}}_i\right) - \right]. - -Thus, the gradient of the expectation value can be calculated by evaluating the same variational -quantum circuit, but with shifted parameter values (hence the name, parameter-shift rule!). - -Let's have a go implementing the parameter-shift rule manually in PennyLane. -""" -import pennylane as qml -from jax import numpy as jnp -from matplotlib import pyplot as plt -import jax - -jax.config.update("jax_platform_name", "cpu") -jax.config.update('jax_enable_x64', True) - -# set the random seed -key = jax.random.PRNGKey(42) - - -# create a device to execute the circuit on -dev = qml.device("default.qubit", wires=3) - - -def CNOT_ring(wires): - """Apply CNOTs in a ring pattern""" - n_wires = len(wires) - - for w in wires: - qml.CNOT([w % n_wires, (w + 1) % n_wires]) - - -@qml.qnode(dev, diff_method="parameter-shift") -def circuit(params): - qml.RX(params[0], wires=0) - qml.RY(params[1], wires=1) - qml.RZ(params[2], wires=2) - - CNOT_ring(wires=[0, 1, 2]) - - qml.RX(params[3], wires=0) - qml.RY(params[4], wires=1) - qml.RZ(params[5], wires=2) - - CNOT_ring(wires=[0, 1, 2]) - return qml.expval(qml.PauliY(0) @ qml.PauliZ(2)) - - -############################################################################## -# Let's test the variational circuit evaluation with some parameter input: - -# initial parameters -params = jax.random.normal(key, [6]) - - -print("Parameters:", params) -print("Expectation value:", circuit(params)) - -############################################################################## -# We can also draw the executed quantum circuit: - -fig, ax = qml.draw_mpl(circuit, decimals=2)(params) -plt.show() - - -############################################################################## -# Now that we have defined our variational circuit QNode, we can construct -# a function that computes the gradient of the :math:`i\text{th}` parameter -# using the parameter-shift rule. - -def parameter_shift_term(qnode, params, i): - shifted = params.copy() - shifted = shifted.at[i].add(jnp.pi/2) - forward = qnode(shifted) # forward evaluation - - shifted = shifted.at[i].add(-jnp.pi) - backward = qnode(shifted) # backward evaluation - - return 0.5 * (forward - backward) - -# gradient with respect to the first parameter -print(parameter_shift_term(circuit, params, 0)) - -############################################################################## -# In order to compute the gradient with respect to *all* parameters, we need -# to loop over the index ``i``: - -def parameter_shift(qnode, params): - gradients = jnp.zeros([len(params)]) - - for i in range(len(params)): - gradients = gradients.at[i].set(parameter_shift_term(qnode, params, i)) - - return gradients - -print(parameter_shift(circuit, params)) - -############################################################################## -# We can compare this to PennyLane's *built-in* quantum gradient support by using -# the ``jax.grad`` function. Remember, when we defined the -# QNode, we specified that we wanted it to be differentiable using the parameter-shift -# method (``diff_method="parameter-shift"``). - -grad_function = jax.grad(circuit) -print(grad_function(params)[0]) - -############################################################################## -# Alternatively, we can directly compute quantum gradients of QNodes using -# PennyLane's built in :mod:`qml.gradients ` module: - -print(jnp.stack(qml.gradients.param_shift(circuit)(params))) - -############################################################################## -# If you count the number of quantum evaluations, you will notice that we had to evaluate the circuit -# ``2*len(params)`` number of times in order to compute the quantum gradient with respect to all -# parameters. While reasonably fast for a small number of parameters, as the number of parameters in -# our quantum circuit grows, so does both -# -# 1. the circuit depth (and thus the time taken to evaluate each expectation value or 'forward' pass), and -# -# 2. the number of parameter-shift evaluations required. -# -# Both of these factors increase the time taken to compute the gradient with -# respect to all parameters. -# -# Benchmarking -# ~~~~~~~~~~~~ -# -# Let's consider an example with a significantly larger number of parameters. -# We'll make use of the :class:`~pennylane.StronglyEntanglingLayers` template -# to make a more complicated QNode. - -dev = qml.device("default.qubit", wires=4) - -@qml.qnode(dev, diff_method="parameter-shift") -def circuit(params): - qml.StronglyEntanglingLayers(params, wires=[0, 1, 2, 3]) - return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3)) - -# initialize circuit parameters -param_shape = qml.StronglyEntanglingLayers.shape(n_wires=4, n_layers=15) -params = jax.random.normal(key, param_shape) * 0.1 - -print(params.size) -print(circuit(params)) - -############################################################################## -# This circuit has 180 parameters. Let's see how long it takes to perform a forward -# pass of the circuit. - -import timeit - -reps = 3 -num = 10 -times = timeit.repeat("circuit(params)", globals=globals(), number=num, repeat=reps) -forward_time = min(times) / num - -print(f"Forward pass (best of {reps}): {forward_time} sec per loop") - - -############################################################################## -# We can now estimate the time taken to compute the full gradient vector, -# and see how this compares. - -# create the gradient function -grad_fn = jax.grad(circuit) - -times = timeit.repeat("grad_fn(params)", globals=globals(), number=num, repeat=reps) -backward_time = min(times) / num - -print(f"Gradient computation (best of {reps}): {backward_time} sec per loop") - - -############################################################################## -# Based on the parameter-shift rule, we expect that the amount of time to compute the quantum -# gradients should be approximately :math:`2p\Delta t_{f}` where :math:`p` is the number of -# parameters and :math:`\Delta t_{f}` if the time taken for the forward pass. Let's verify this: - -print(2 * forward_time * params.size) - - -############################################################################## -# Backpropagation -# --------------- -# -# An alternative to the parameter-shift rule for computing gradients is -# `reverse-mode autodifferentiation `__. -# Unlike the parameter-shift method, which requires :math:`2p` circuit evaluations for -# :math:`p` parameters, reverse-mode requires only a *single* forward pass of the -# differentiable function to compute -# the gradient of all variables, at the expense of increased memory usage. -# During the forward pass, the results of all intermediate subexpressions are stored; -# the computation is then traversed *in reverse*, with the gradient computed by repeatedly -# applying the chain rule. -# In most classical machine learning settings (where we are training scalar loss functions -# consisting of a large number of parameters), -# reverse-mode autodifferentiation is the -# preferred method of autodifferentiation—the reduction in computational time enables larger and -# more complex models to be successfully trained. The backpropagation algorithm is a particular -# special-case of reverse-mode autodifferentiation, which has helped lead to the machine learning -# explosion we see today. -# -# In quantum machine learning, however, the inability to store and utilize the results of -# *intermediate* quantum operations on hardware remains a barrier to using backprop; -# while reverse-mode -# autodifferentiation works fine for small quantum simulations, only the -# parameter-shift rule can be used to compute gradients on quantum hardware directly. Nevertheless, -# when training quantum models via classical simulation, it's useful to explore the regimes where -# reverse-mode differentiation may be a better choice than the parameter-shift rule. -# -# Benchmarking -# ~~~~~~~~~~~~ -# -# When creating a QNode, :doc:`PennyLane supports various methods of differentiation -# `, including ``"parameter-shift"`` (which we used previously), -# ``"finite-diff"``, ``"reversible"``, and ``"backprop"``. While ``"parameter-shift"`` works with all devices -# (simulator or hardware), ``"backprop"`` will only work for specific simulator devices that are -# designed to support backpropagation. -# -# One such device is :class:`default.qubit `. It -# has backends written using TensorFlow, JAX, and Autograd, so when used with the -# TensorFlow, JAX, and Autograd interfaces respectively, supports backpropagation. -# In this demo, we will use the JAX interface. - -dev = qml.device("default.qubit", wires=4) - -############################################################################## -# When defining the QNode, we specify ``diff_method="backprop"`` to ensure that -# we are using backpropagation mode. Note that this is the *default differentiation -# mode* for the ``default.qubit`` device. - - -@qml.qnode(dev, diff_method="backprop") -def circuit(params): - qml.StronglyEntanglingLayers(params, wires=[0, 1, 2, 3]) - return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3)) - -# initialize circuit parameters -param_shape = qml.StronglyEntanglingLayers.shape(n_wires=4, n_layers=15) -params = jax.random.normal(key, param_shape) * 0.1 - -print(circuit(params)) - -############################################################################## -# Let's see how long it takes to perform a forward pass of the circuit. - -import timeit - -reps = 3 -num = 10 -times = timeit.repeat("circuit(params)", globals=globals(), number=num, repeat=reps) -forward_time = min(times) / num -print(f"Forward pass (best of {reps}): {forward_time} sec per loop") - - -############################################################################## -# Comparing this to the forward pass from ``default.qubit``, we note that there is some potential -# overhead from using backpropagation. We can now estimate the time required to perform a -# gradient computation via backpropagation: - -times = timeit.repeat("jax.grad(circuit)(params)", globals=globals(), number=num, repeat=reps) -backward_time = min(times) / num -print(f"Backward pass (best of {reps}): {backward_time} sec per loop") - -############################################################################## -# Unlike with the parameter-shift rule, the time taken to perform the backwards pass appears -# of the order of a single forward pass! This can significantly speed up training of simulated -# circuits with many parameters. -# -# Time comparison -# --------------- -# -# Let's compare the two differentiation approaches as the number of trainable parameters -# in the variational circuit increases, by timing both the forward pass and the gradient -# computation as the number of layers is allowed to increase. - -dev = qml.device("default.qubit", wires=4) - -def circuit(params): - qml.StronglyEntanglingLayers(params, wires=[0, 1, 2, 3]) - return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3)) - -############################################################################## -# We'll continue to use the same ansatz as before, but to reduce the time taken -# to collect the data, we'll reduce the number and repetitions of timings per data -# point. Below, we loop over a variational circuit depth ranging from 0 (no gates/ -# trainable parameters) to 20. Each layer will contain :math:`3N` parameters, where -# :math:`N` is the number of wires (in this case, we have :math:`N=4`). - -reps = 2 -num = 3 - -forward_shift = [] -gradient_shift = [] -forward_backprop = [] -gradient_backprop = [] - -qnode_shift = jax.jit(qml.QNode(circuit, dev, diff_method="parameter-shift")) -qnode_backprop = jax.jit(qml.QNode(circuit, dev, diff_method="backprop")) - -grad_qnode_shift = jax.jit(jax.grad(qml.QNode(circuit, dev, diff_method="parameter-shift"))) -grad_qnode_backprop = jax.jit(jax.grad(qml.QNode(circuit, dev, diff_method="backprop"))) - -for depth in range(0, 21): - param_shape = qml.StronglyEntanglingLayers.shape(n_wires=4, n_layers=depth) - params = jax.random.normal(key, param_shape) * 0.1 - - num_params = params.size - - # forward pass timing - # =================== - - - # parameter-shift - t = timeit.repeat("qnode_shift(params)", globals=globals(), number=num, repeat=reps) - forward_shift.append([num_params, min(t) / num]) - - # backprop - t = timeit.repeat("qnode_backprop(params)", globals=globals(), number=num, repeat=reps) - forward_backprop.append([num_params, min(t) / num]) - - if num_params == 0: - continue - - # Gradient timing - # =============== - - # parameter-shift - t = timeit.repeat("grad_qnode_shift(params)", globals=globals(), number=num, repeat=reps) - gradient_shift.append([num_params, min(t) / num]) - - # backprop - t = timeit.repeat("grad_qnode_backprop(params)", globals=globals(), number=num, repeat=reps) - gradient_backprop.append([num_params, min(t) / num]) - -gradient_shift = jnp.array(gradient_shift).T -gradient_backprop = jnp.array(gradient_backprop).T -forward_shift = jnp.array(forward_shift).T -forward_backprop = jnp.array(forward_backprop).T - -############################################################################## -# We now import matplotlib, and plot the results. - -plt.style.use("bmh") - -fig, ax = plt.subplots(1, 1, figsize=(6, 4)) - -ax.plot(*gradient_shift, '.-', label="Parameter-shift") -ax.plot(*gradient_backprop, '.-', label="Backprop") -ax.set_ylabel("Time (s)") -ax.set_xlabel("Number of parameters") -ax.legend() - -plt.show() - -############################################################################## -# .. raw:: html -# -#
-# -# We can see that the computational time for the parameter-shift rule increases with -# increasing number of parameters, as expected, whereas the computational time -# for backpropagation appears much more constant, with perhaps a minute linear increase -# with :math:`p.` Note that the plots are not perfectly linear, with some 'bumpiness' or -# noisiness. This is likely due to low-level operating system jitter, and -# other environmental fluctuations—increasing the number of repeats can help smooth -# out the plot. -# -# For a better comparison, we can scale the time required for computing the quantum -# gradients against the time taken for the corresponding forward pass: - -gradient_shift = gradient_shift.at[1].divide(forward_shift[1, 1:]) -gradient_backprop = gradient_backprop.at[1].divide(forward_backprop[1, 1:]) - -fig, ax = plt.subplots(1, 1, figsize=(6, 4)) - -ax.plot(*gradient_shift, '.-', label="Parameter-shift") -ax.plot(*gradient_backprop, '.-', label="Backprop") - -# perform a least squares regression to determine the linear best fit/gradient -# for the normalized time vs. number of parameters -x = gradient_shift[0] -m_shift, c_shift = jnp.polyfit(*gradient_shift, deg=1) -m_back, c_back = jnp.polyfit(*gradient_backprop, deg=1) - -ax.plot(x, m_shift * x + c_shift, '--', label=f"{m_shift:.2f}p{c_shift:+.2f}") -ax.plot(x, m_back * x + c_back, '--', label=f"{m_back:.2f}p{c_back:+.2f}") - -ax.set_ylabel("Normalized time") -ax.set_xlabel("Number of parameters") -ax.set_xscale("log") -ax.set_yscale("log") -ax.legend() - -plt.show() - -############################################################################## -# .. raw:: html -# -#
-# -# We can now see clearly that there is constant overhead for backpropagation with -# ``default.qubit``, but the parameter-shift rule scales as :math:`\sim 2p.` -# -# -# About the author -# ---------------- -# +r""" +Quantum gradients with backpropagation +====================================== + +.. meta:: + :property="og:description": Using backpropagation can speed up training of quantum circuits compared to the parameter-shift rule—if you are using a simulator. + + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/sphx_glr_tutorial_backprop_002.png + +.. related:: + + tutorial_quantum_natural_gradient Quantum natural gradient + +*Author: Josh Izaac — Posted: 11 August 2020. Last updated: 31 January 2021.* + +In PennyLane, any quantum device, whether a hardware device or a simulator, can be +trained using the :doc:`parameter-shift rule ` to compute quantum +gradients. Indeed, the parameter-shift rule is ideally suited to hardware devices, as it does +not require any knowledge about the internal workings of the device; it is sufficient to treat +the device as a 'black box', and to query it with different input values in order to determine the gradient. + +When working with simulators, however, we *do* have access to the internal (classical) +computations being performed. This allows us to take advantage of other methods of computing the +gradient, such as backpropagation, which may be advantageous in certain regimes. In this tutorial, +we will compare and contrast the parameter-shift rule against backpropagation, using +the PennyLane :class:`default.qubit ` +device. + +The parameter-shift rule +------------------------ + +The parameter-shift rule states that, given a variational quantum circuit :math:`U(\boldsymbol +\theta)` composed of parametrized Pauli rotations, and some measured observable :math:`\hat{B},` the +derivative of the expectation value + +.. math:: + + \langle \hat{B} \rangle (\boldsymbol\theta) = + \langle 0 \vert U(\boldsymbol\theta)^\dagger \hat{B} U(\boldsymbol\theta) \vert 0\rangle + +with respect to the input circuit parameters :math:`\boldsymbol{\theta}` is given by + +.. math:: + + \nabla_{\theta_i}\langle \hat{B} \rangle(\boldsymbol\theta) + = \frac{1}{2} + \left[ + \langle \hat{B} \rangle\left(\boldsymbol\theta + \frac{\pi}{2}\hat{\mathbf{e}}_i\right) + - \langle \hat{B} \rangle\left(\boldsymbol\theta - \frac{\pi}{2}\hat{\mathbf{e}}_i\right) + \right]. + +Thus, the gradient of the expectation value can be calculated by evaluating the same variational +quantum circuit, but with shifted parameter values (hence the name, parameter-shift rule!). + +Let's have a go implementing the parameter-shift rule manually in PennyLane. +""" +import pennylane as qml +from jax import numpy as jnp +from matplotlib import pyplot as plt +import jax + +jax.config.update("jax_platform_name", "cpu") +jax.config.update('jax_enable_x64', True) + +# set the random seed +key = jax.random.PRNGKey(42) + + +# create a device to execute the circuit on +dev = qml.device("default.qubit", wires=3) + + +def CNOT_ring(wires): + """Apply CNOTs in a ring pattern""" + n_wires = len(wires) + + for w in wires: + qml.CNOT([w % n_wires, (w + 1) % n_wires]) + + +@qml.qnode(dev, diff_method="parameter-shift") +def circuit(params): + qml.RX(params[0], wires=0) + qml.RY(params[1], wires=1) + qml.RZ(params[2], wires=2) + + CNOT_ring(wires=[0, 1, 2]) + + qml.RX(params[3], wires=0) + qml.RY(params[4], wires=1) + qml.RZ(params[5], wires=2) + + CNOT_ring(wires=[0, 1, 2]) + return qml.expval(qml.PauliY(0) @ qml.PauliZ(2)) + + +############################################################################## +# Let's test the variational circuit evaluation with some parameter input: + +# initial parameters +params = jax.random.normal(key, [6]) + + +print("Parameters:", params) +print("Expectation value:", circuit(params)) + +############################################################################## +# We can also draw the executed quantum circuit: + +fig, ax = qml.draw_mpl(circuit, decimals=2)(params) +plt.show() + + +############################################################################## +# Now that we have defined our variational circuit QNode, we can construct +# a function that computes the gradient of the :math:`i\text{th}` parameter +# using the parameter-shift rule. + +def parameter_shift_term(qnode, params, i): + shifted = params.copy() + shifted = shifted.at[i].add(jnp.pi/2) + forward = qnode(shifted) # forward evaluation + + shifted = shifted.at[i].add(-jnp.pi) + backward = qnode(shifted) # backward evaluation + + return 0.5 * (forward - backward) + +# gradient with respect to the first parameter +print(parameter_shift_term(circuit, params, 0)) + +############################################################################## +# In order to compute the gradient with respect to *all* parameters, we need +# to loop over the index ``i``: + +def parameter_shift(qnode, params): + gradients = jnp.zeros([len(params)]) + + for i in range(len(params)): + gradients = gradients.at[i].set(parameter_shift_term(qnode, params, i)) + + return gradients + +print(parameter_shift(circuit, params)) + +############################################################################## +# We can compare this to PennyLane's *built-in* quantum gradient support by using +# the ``jax.grad`` function. Remember, when we defined the +# QNode, we specified that we wanted it to be differentiable using the parameter-shift +# method (``diff_method="parameter-shift"``). + +grad_function = jax.grad(circuit) +print(grad_function(params)[0]) + +############################################################################## +# Alternatively, we can directly compute quantum gradients of QNodes using +# PennyLane's built in :mod:`qml.gradients ` module: + +print(jnp.stack(qml.gradients.param_shift(circuit)(params))) + +############################################################################## +# If you count the number of quantum evaluations, you will notice that we had to evaluate the circuit +# ``2*len(params)`` number of times in order to compute the quantum gradient with respect to all +# parameters. While reasonably fast for a small number of parameters, as the number of parameters in +# our quantum circuit grows, so does both +# +# 1. the circuit depth (and thus the time taken to evaluate each expectation value or 'forward' pass), and +# +# 2. the number of parameter-shift evaluations required. +# +# Both of these factors increase the time taken to compute the gradient with +# respect to all parameters. +# +# Benchmarking +# ~~~~~~~~~~~~ +# +# Let's consider an example with a significantly larger number of parameters. +# We'll make use of the :class:`~pennylane.StronglyEntanglingLayers` template +# to make a more complicated QNode. + +dev = qml.device("default.qubit", wires=4) + +@qml.qnode(dev, diff_method="parameter-shift") +def circuit(params): + qml.StronglyEntanglingLayers(params, wires=[0, 1, 2, 3]) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3)) + +# initialize circuit parameters +param_shape = qml.StronglyEntanglingLayers.shape(n_wires=4, n_layers=15) +params = jax.random.normal(key, param_shape) * 0.1 + +print(params.size) +print(circuit(params)) + +############################################################################## +# This circuit has 180 parameters. Let's see how long it takes to perform a forward +# pass of the circuit. + +import timeit + +reps = 3 +num = 10 +times = timeit.repeat("circuit(params)", globals=globals(), number=num, repeat=reps) +forward_time = min(times) / num + +print(f"Forward pass (best of {reps}): {forward_time} sec per loop") + + +############################################################################## +# We can now estimate the time taken to compute the full gradient vector, +# and see how this compares. + +# create the gradient function +grad_fn = jax.grad(circuit) + +times = timeit.repeat("grad_fn(params)", globals=globals(), number=num, repeat=reps) +backward_time = min(times) / num + +print(f"Gradient computation (best of {reps}): {backward_time} sec per loop") + + +############################################################################## +# Based on the parameter-shift rule, we expect that the amount of time to compute the quantum +# gradients should be approximately :math:`2p\Delta t_{f}` where :math:`p` is the number of +# parameters and :math:`\Delta t_{f}` if the time taken for the forward pass. Let's verify this: + +print(2 * forward_time * params.size) + + +############################################################################## +# Backpropagation +# --------------- +# +# An alternative to the parameter-shift rule for computing gradients is +# `reverse-mode autodifferentiation `__. +# Unlike the parameter-shift method, which requires :math:`2p` circuit evaluations for +# :math:`p` parameters, reverse-mode requires only a *single* forward pass of the +# differentiable function to compute +# the gradient of all variables, at the expense of increased memory usage. +# During the forward pass, the results of all intermediate subexpressions are stored; +# the computation is then traversed *in reverse*, with the gradient computed by repeatedly +# applying the chain rule. +# In most classical machine learning settings (where we are training scalar loss functions +# consisting of a large number of parameters), +# reverse-mode autodifferentiation is the +# preferred method of autodifferentiation—the reduction in computational time enables larger and +# more complex models to be successfully trained. The backpropagation algorithm is a particular +# special-case of reverse-mode autodifferentiation, which has helped lead to the machine learning +# explosion we see today. +# +# In quantum machine learning, however, the inability to store and utilize the results of +# *intermediate* quantum operations on hardware remains a barrier to using backprop; +# while reverse-mode +# autodifferentiation works fine for small quantum simulations, only the +# parameter-shift rule can be used to compute gradients on quantum hardware directly. Nevertheless, +# when training quantum models via classical simulation, it's useful to explore the regimes where +# reverse-mode differentiation may be a better choice than the parameter-shift rule. +# +# Benchmarking +# ~~~~~~~~~~~~ +# +# When creating a QNode, :doc:`PennyLane supports various methods of differentiation +# `, including ``"parameter-shift"`` (which we used previously), +# ``"finite-diff"``, ``"reversible"``, and ``"backprop"``. While ``"parameter-shift"`` works with all devices +# (simulator or hardware), ``"backprop"`` will only work for specific simulator devices that are +# designed to support backpropagation. +# +# One such device is :class:`default.qubit `. It +# has backends written using TensorFlow, JAX, and Autograd, so when used with the +# TensorFlow, JAX, and Autograd interfaces respectively, supports backpropagation. +# In this demo, we will use the JAX interface. + +dev = qml.device("default.qubit", wires=4) + +############################################################################## +# When defining the QNode, we specify ``diff_method="backprop"`` to ensure that +# we are using backpropagation mode. Note that this is the *default differentiation +# mode* for the ``default.qubit`` device. + + +@qml.qnode(dev, diff_method="backprop") +def circuit(params): + qml.StronglyEntanglingLayers(params, wires=[0, 1, 2, 3]) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3)) + +# initialize circuit parameters +param_shape = qml.StronglyEntanglingLayers.shape(n_wires=4, n_layers=15) +params = jax.random.normal(key, param_shape) * 0.1 + +print(circuit(params)) + +############################################################################## +# Let's see how long it takes to perform a forward pass of the circuit. + +import timeit + +reps = 3 +num = 10 +times = timeit.repeat("circuit(params)", globals=globals(), number=num, repeat=reps) +forward_time = min(times) / num +print(f"Forward pass (best of {reps}): {forward_time} sec per loop") + + +############################################################################## +# Comparing this to the forward pass from ``default.qubit``, we note that there is some potential +# overhead from using backpropagation. We can now estimate the time required to perform a +# gradient computation via backpropagation: + +times = timeit.repeat("jax.grad(circuit)(params)", globals=globals(), number=num, repeat=reps) +backward_time = min(times) / num +print(f"Backward pass (best of {reps}): {backward_time} sec per loop") + +############################################################################## +# Unlike with the parameter-shift rule, the time taken to perform the backwards pass appears +# of the order of a single forward pass! This can significantly speed up training of simulated +# circuits with many parameters. +# +# Time comparison +# --------------- +# +# Let's compare the two differentiation approaches as the number of trainable parameters +# in the variational circuit increases, by timing both the forward pass and the gradient +# computation as the number of layers is allowed to increase. + +dev = qml.device("default.qubit", wires=4) + +def circuit(params): + qml.StronglyEntanglingLayers(params, wires=[0, 1, 2, 3]) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3)) + +############################################################################## +# We'll continue to use the same ansatz as before, but to reduce the time taken +# to collect the data, we'll reduce the number and repetitions of timings per data +# point. Below, we loop over a variational circuit depth ranging from 0 (no gates/ +# trainable parameters) to 20. Each layer will contain :math:`3N` parameters, where +# :math:`N` is the number of wires (in this case, we have :math:`N=4`). + +reps = 2 +num = 3 + +forward_shift = [] +gradient_shift = [] +forward_backprop = [] +gradient_backprop = [] + +qnode_shift = jax.jit(qml.QNode(circuit, dev, diff_method="parameter-shift")) +qnode_backprop = jax.jit(qml.QNode(circuit, dev, diff_method="backprop")) + +grad_qnode_shift = jax.jit(jax.grad(qml.QNode(circuit, dev, diff_method="parameter-shift"))) +grad_qnode_backprop = jax.jit(jax.grad(qml.QNode(circuit, dev, diff_method="backprop"))) + +for depth in range(0, 21): + param_shape = qml.StronglyEntanglingLayers.shape(n_wires=4, n_layers=depth) + params = jax.random.normal(key, param_shape) * 0.1 + + num_params = params.size + + # forward pass timing + # =================== + + + # parameter-shift + t = timeit.repeat("qnode_shift(params)", globals=globals(), number=num, repeat=reps) + forward_shift.append([num_params, min(t) / num]) + + # backprop + t = timeit.repeat("qnode_backprop(params)", globals=globals(), number=num, repeat=reps) + forward_backprop.append([num_params, min(t) / num]) + + if num_params == 0: + continue + + # Gradient timing + # =============== + + # parameter-shift + t = timeit.repeat("grad_qnode_shift(params)", globals=globals(), number=num, repeat=reps) + gradient_shift.append([num_params, min(t) / num]) + + # backprop + t = timeit.repeat("grad_qnode_backprop(params)", globals=globals(), number=num, repeat=reps) + gradient_backprop.append([num_params, min(t) / num]) + +gradient_shift = jnp.array(gradient_shift).T +gradient_backprop = jnp.array(gradient_backprop).T +forward_shift = jnp.array(forward_shift).T +forward_backprop = jnp.array(forward_backprop).T + +############################################################################## +# We now import matplotlib, and plot the results. + +plt.style.use("bmh") + +fig, ax = plt.subplots(1, 1, figsize=(6, 4)) + +ax.plot(*gradient_shift, '.-', label="Parameter-shift") +ax.plot(*gradient_backprop, '.-', label="Backprop") +ax.set_ylabel("Time (s)") +ax.set_xlabel("Number of parameters") +ax.legend() + +plt.show() + +############################################################################## +# .. raw:: html +# +#
+# +# We can see that the computational time for the parameter-shift rule increases with +# increasing number of parameters, as expected, whereas the computational time +# for backpropagation appears much more constant, with perhaps a minute linear increase +# with :math:`p.` Note that the plots are not perfectly linear, with some 'bumpiness' or +# noisiness. This is likely due to low-level operating system jitter, and +# other environmental fluctuations—increasing the number of repeats can help smooth +# out the plot. +# +# For a better comparison, we can scale the time required for computing the quantum +# gradients against the time taken for the corresponding forward pass: + +gradient_shift = gradient_shift.at[1].divide(forward_shift[1, 1:]) +gradient_backprop = gradient_backprop.at[1].divide(forward_backprop[1, 1:]) + +fig, ax = plt.subplots(1, 1, figsize=(6, 4)) + +ax.plot(*gradient_shift, '.-', label="Parameter-shift") +ax.plot(*gradient_backprop, '.-', label="Backprop") + +# perform a least squares regression to determine the linear best fit/gradient +# for the normalized time vs. number of parameters +x = gradient_shift[0] +m_shift, c_shift = jnp.polyfit(*gradient_shift, deg=1) +m_back, c_back = jnp.polyfit(*gradient_backprop, deg=1) + +ax.plot(x, m_shift * x + c_shift, '--', label=f"{m_shift:.2f}p{c_shift:+.2f}") +ax.plot(x, m_back * x + c_back, '--', label=f"{m_back:.2f}p{c_back:+.2f}") + +ax.set_ylabel("Normalized time") +ax.set_xlabel("Number of parameters") +ax.set_xscale("log") +ax.set_yscale("log") +ax.legend() + +plt.show() + +############################################################################## +# .. raw:: html +# +#
+# +# We can now see clearly that there is constant overhead for backpropagation with +# ``default.qubit``, but the parameter-shift rule scales as :math:`\sim 2p.` +# +# +# About the author +# ---------------- +# diff --git a/demonstrations/tutorial_barren_gadgets.py b/demonstrations/tutorial_barren_gadgets.py index 0eacb1fff7..f0d19253c1 100644 --- a/demonstrations/tutorial_barren_gadgets.py +++ b/demonstrations/tutorial_barren_gadgets.py @@ -1,390 +1,390 @@ -r""" -Perturbative Gadgets for Variational Quantum Algorithms -========================================== - -.. meta:: - :property="og:description": Use perturbative gadgets to avoid cost-function-dependent barren plateaus - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_barren_gadgets.png - - -.. related:: - tutorial_barren_plateaus Barren plateaus in quantum neural networks¶ - tutorial_local_cost_functions Alleviating barren plateaus with local cost functions - -*Author: Simon Cichy — Posted: 09 December 2022. Last updated: 09 December 2022.* - -Variational quantum algorithms are seen as one of the most primising candidates -for useful applications of quantum computers in the near term, but there are -still a few hurdles to overcome when it comes to practical implementation. -One of them, is the trainability. -In other words, one needs to ensure that the cost function is not flat. -In this tutorial, we will explore the application of perturbative gadgets in -variational quantum algorithms to outgo the issue of cost-function-dependent -barren plateaus, as proposed in Ref. [#cichy2022]_ - -Some context ------------- - -Barren plateaus refer to the phenomenon where the gradients of the cost function -decay exponentially with the size of the problem. Essentially, the cost -landscape becomes flat, with exception of some small regions, e.g., around -the minimum. -That is a problem because increasing the precision of the cost -function requires more measurements from the quantum device due to shot noise, -and an exponential number of measurements would render the algorithm impractical. -If you are not familiar yet with the concept of barren plateaus, I recommend you -first check out the demonstrations on :doc:`barren plateaus ` -and :doc:`avoiding barren plateaus with local cost functions `. - -As presented in the second aforementioned demo, barren plateaus are more severe when using global -cost functions compared to local ones. -A global cost function requires the simultaneous measurement of all -qubits at once. In contrast, a local one is constructed from terms that only -act on a small subset of qubits. - -We want to explore this topic further and learn about one possible mitigation -strategy. -Thinking about Variational Quantum Eigensolver (VQE) applications, let us consider cost functions that are -expectation values of Hamiltonians such as - -.. math:: C(\theta) = \operatorname{Tr} \left[ H V(\theta) |00\ldots 0\rangle \! \langle 00\ldots 0| V(\theta)^\dagger\right]. - -Here :math:`|00\ldots 0\rangle` is our initial state, -:math:`V(\theta)` is the circuit ansatz and :math:`H` the Hamiltonian -whose expectation value we need to minimize. -In some cases, it is easy to find a local cost function which can substitute a global one with the same ground state. -Take, for instance, the following Hamiltonians that induce global and local cost functions, respectively. - - -.. math:: H_G = \mathbb{I} - |00\ldots 0\rangle \! \langle 00\ldots 0| \quad \textrm{ and } \quad H_L = \mathbb{I} - \frac{1}{n} \sum_j |0\rangle \! \langle 0|_j. - -Those are two different Hamiltonians (not just different formulations of the -same one), but they share the same ground state: - - -.. math:: |\psi_{\textrm{min}} \rangle = |00\ldots 0\rangle. - -Therefore, one can work with either Hamiltonian to perform the VQE routine. -However, it is not always so simple. -What if we want to find the minimum eigenenergy of -:math:`H = X \otimes X \otimes Y \otimes Z + Z \otimes Y \otimes X \otimes X` ? -It is not always trivial to construct a local cost -function that has the same minimum as the cost function of interest. -This is where perturbative gadgets come into play! - - -The definitions ---------------- -Perturbative gadgets are a common tool in adiabatic quantum computing. -Their goal is to find a Hamiltonian with local interactions that mimics -another Hamiltonian with more complex couplings. - -Ideally, they would want to implement the target Hamiltonian with complex couplings, but since it's hard to implement more than few-body interactions on hardware, they cannot do so. Perturbative gadgets work by increasing the dimension of the Hilbert space (i.e., the number -of qubits) and "encoding" the target Hamiltonian in the low-energy -subspace of a so-called "gadget" Hamiltonian. - -Let us now construct such a gadget Hamiltonian tailored for VQE applications. -First, we start from a target Hamiltonian that is a linear combination of -Pauli words acting on :math:`k` qubits each: - -.. math:: H^\text{target} = \sum_i c_i h_i, - -where :math:`h_i = \sigma_{i,1} \otimes \sigma_{i,2} \otimes \ldots \otimes \sigma_{i,k},` -:math:`\sigma_{i,j} \in \{ X, Y, Z \},` and :math:`c_i \in \mathbb{R}.` -Now we construct the gadget Hamiltonian. -For each term :math:`h_i,` we will need :math:`k` additional qubits, which we -call auxiliary qubits, and to add two terms to the Hamiltonian: -an "unperturbed" part :math:`H^\text{aux}_i` and a perturbation :math:`V_i` -of strength :math:`\lambda.` -The unperturbed part penalizes each of the newly added qubits for not being in -the :math:`|0\rangle` state - -.. math:: H^\text{aux}_i = \sum_{j=1}^k |1\rangle \! \langle 1|_{i,j} = \sum_{j=1}^k \frac{1}{2}(\mathbb{I} - Z_{i,j}). - -On the other hand, the perturbation part implements one of the operators in the Pauli word -:math:`\sigma_{i,j}` on the corresponding qubit of the target register and a -pair of Pauli :math:`X` gates on two of the auxiliary qubits: - -.. math:: V_i = \sum_{j=1}^k c_{i,j} \sigma_{i,j} \otimes X_{i,j} \otimes X_{i,(j+1) \mathrm{mod }k}. - -In the end, - -.. math:: H^\text{gad} = \sum_{i} \left( H^\text{aux}_i + \lambda V_i \right). - - - -To grasp this idea better, this is what would result from working with a Hamiltonian -acting on a total of :math:`8` qubits and having :math:`3` terms, each of them being a -:math:`4`-body interaction. - -.. figure:: ../_static/demonstration_assets/barren_gadgets/gadget-terms-tutorial.png - :align: center - :width: 90% - -For each of the terms :math:`h_1`, :math:`h_2,` and :math:`h_3` we add :math:`4` auxiliary qubits. -In the end, our gadget Hamiltonian acts on :math:`8+3\cdot 4 = 20` qubits. - -The penalization (red) acts only on the auxiliary registers, penalizing each -qubit individually, while the perturbations couple the target with the auxiliary qubits. - -As shown in Ref. [#cichy2022]_, this construction results in a spectrum that, for low energies, is similar -to that of the original Hamiltonian. -This means that by minimizing the gadget Hamiltonian and reaching its global -minimum, the resulting state will be close to the global minimum of -:math:`H^\text{target}.` - -Since it is a local cost function, it is better behaved with respect to -barren plateaus than the global cost function, making it more trainable. -As a result, one can mitigate the onset of cost-function-dependent barren -plateaus by substituting the global cost function with the resulting gadget -and using that for training instead. That is what we will do in the rest of this tutorial. -""" - -############################################################################## -# First, a few imports. PennyLane and NumPy of course, and a few -# functions specific to our tutorial. -# The ``PerturbativeGadget`` class allows the user to generate the gadget Hamiltonian -# from a user-given target Hamiltonian in an automated way. -# For those who want to check its inner workings, -# you can find the code here: -# :download:`barren_gadgets.py `. -# The functions ``get_parameter_shape``, ``generate_random_gate_sequence``, and -# ``build_ansatz`` (for the details: -# :download:`layered_ansatz.py <../_static/demonstration_assets/barren_gadgets/layered_ansatz.py>` -# ) are there to build the parameterized quantum circuit we use in this demo. -# The first computes the shape of the array of trainable parameters that the -# circuit will need. The second generates a random sequence of Pauli rotations -# from :math:`\{R_X, R_Y, R_Z\}` with the right dimension. -# Finally, ``build_ansatz`` puts the pieces together. - -import pennylane as qml -from pennylane import numpy as np -from barren_gadgets.barren_gadgets import PerturbativeGadgets -from barren_gadgets.layered_ansatz import ( - generate_random_gate_sequence, - get_parameter_shape, - build_ansatz, -) - -np.random.seed(3) - -############################################################################## -# Now, let's take the example given above: -# -# .. math:: H = X \otimes X \otimes Y \otimes Z + Z \otimes Y \otimes X \otimes X. -# -# First, we construct our target Hamiltonian in PennyLane. -# For this, we use the -# :class:`~pennylane.Hamiltonian` class. - - -H_target = qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliY(2) @ qml.PauliZ(3) \ - + qml.PauliZ(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliX(3) - -############################################################################## -# Now we can check that we constructed what we wanted. - -print(H_target) - -############################################################################## -# We indeed have a Hamiltonian composed of two terms with the expected Pauli -# words. -# Next, we can construct the corresponding gadget Hamiltonian. -# Using the class ``PerturbativeGadgets``, we can automatically -# generate the gadget Hamiltonian from the target Hamiltonian. -# The object ``gadgetizer`` will contain all the information about the settings of -# the gadgetization procedure (there are quite a few knobs one can tweak, -# but we'll skip that for now). -# Then, the method ``gadgetize`` takes a -# :class:`~pennylane.Hamiltonian` -# object and generates the -# corresponding gadget Hamiltonian. - -gadgetizer = PerturbativeGadgets() -H_gadget = gadgetizer.gadgetize(H_target) -H_gadget - -############################################################################## -# So, let's see what we got. -# We started with 4 target qubits (labelled ``0`` to ``3``) and two 4-body terms. -# Thus we get 4 additional qubits twice (``4`` to ``11``). -# The first 16 elements of our Hamiltonian correspond to the unperturbed part. -# The last 8 are the perturbation. They are a little scrambled, but one can -# recognize the 8 Paulis from the target Hamiltonian on the qubits ``0`` to -# ``3`` and the cyclic pairwise :math:`X` structure on the auxiliaries. -# Indeed, they are :math:`(X_4X_5, X_5X_6, X_6X_7, X_7X_4)` and -# :math:`(X_8X_9, X_9X_{10}, X_{10}X_{11}, X_{11}X_8).` - -############################################################################## -# Training with the gadget Hamiltonian -# ----------------------------------- -# Now that we have a little intuition on how the gadget Hamiltonian construction -# works, we will use it to train. -# Classical simulations of qubit systems are expensive, so we will simplify further -# to a target Hamiltonian with a single term, and show that using the -# gadget Hamiltonian for training allows us to minimize the target Hamiltonian. -# So, let us construct the two Hamiltonians of interest. - - -H_target = 1 * qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliY(2) @ qml.PauliZ(3) -gadgetizer = PerturbativeGadgets(perturbation_factor=10) -H_gadget = gadgetizer.gadgetize(H_target) - -############################################################################## -# Then we need to set up our variational quantum algorithm. -# That is, we choose a circuit ansatz with randomly initialized weights, -# the cost function, the optimizer with its step size, the number of -# optimization steps, and the device to run the circuit on. -# For an ansatz, we will use a variation of the -# `qml.SimplifiedTwoDesign `_, -# which was proposed in previous -# works on cost-function-dependent barren plateaus [#cerezo2021]_. -# I will skip the details of the construction, since it is not our focus here, -# and just show what it looks like. -# Here is the circuit for a small example - -shapes = get_parameter_shape(n_layers=3, n_wires=5) -init_weights = [np.pi / 4] * shapes[0][0] -weights = np.random.uniform(0, np.pi, size=shapes[1]) - - -@qml.qnode(qml.device("default.qubit", wires=range(5))) -def display_circuit(weights): - build_ansatz(initial_layer_weights=init_weights, weights=weights, wires=range(5)) - return qml.expval(qml.PauliZ(wires=0)) - -import matplotlib.pyplot as plt -qml.draw_mpl(display_circuit)(weights) -plt.show() - -############################################################################## -# Now we build the circuit for our actual experiment. - - -# Total number of qubits: target + auxiliary -num_qubits = 4 + 1 * 4 - -# Other parameters of the ansatz: weights and gate sequence -shapes = get_parameter_shape(n_layers=num_qubits, n_wires=num_qubits) -init_weights = [np.pi / 4] * shapes[0][0] -weights = np.random.uniform(0, np.pi, size=shapes[1]) -random_gate_sequence = generate_random_gate_sequence(qml.math.shape(weights)) - -############################################################################## -# For the classical optimization, we will use the standard gradient descent -# algorithm and perform 500 iterations. For the quantum part, we will simulate -# our circuit using the -# `default.qubit `_ -# simulator. - -opt = qml.GradientDescentOptimizer(stepsize=0.1) -max_iter = 500 -dev = qml.device("default.qubit", wires=range(num_qubits)) - -############################################################################## -# Finally, we will use two cost functions and create a -# `QNode `_ for each. -# The first cost function, the training cost, is the loss function of the optimization. -# For the training, we use the gadget Hamiltonian. To ensure -# that our gadget optimization is proceeding as intended, -# we also define another cost function based on the target Hamiltonian. -# We will evaluate its value at each iteration for monitoring purposes, but it -# will not be used in the optimization. - - - -@qml.qnode(dev) -def training_cost(weights): - build_ansatz( - initial_layer_weights=init_weights, - weights=weights, - wires=range(num_qubits), - gate_sequence=random_gate_sequence, - ) - return qml.expval(H_gadget) - - -@qml.qnode(dev) -def monitoring_cost(weights): - build_ansatz( - initial_layer_weights=init_weights, - weights=weights, - wires=range(num_qubits), - gate_sequence=random_gate_sequence, - ) - return qml.expval(H_target) - - -############################################################################## -# The idea is that if we reach the global minimum for the gadget Hamiltonian, we -# should also be close to the global minimum of the target Hamiltonian, which is -# what we are ultimately interested in. -# To see the results and plot them, we will save the cost values -# at each iteration. - -costs_lists = {} -costs_lists["training"] = [training_cost(weights)] -costs_lists["monitoring"] = [monitoring_cost(weights)] - -############################################################################## -# Now everything is set up, let's run the optimization and see how it goes. -# Be careful, this will take a while. - -for it in range(max_iter): - weights = opt.step(training_cost, weights) - costs_lists["training"].append(training_cost(weights)) - costs_lists["monitoring"].append(monitoring_cost(weights)) - - -plt.style.use("seaborn") - -plt.figure() -plt.plot(costs_lists["training"]) -plt.plot(costs_lists["monitoring"]) -plt.legend(["training", "monitoring"]) -plt.xlabel("Number of iterations") -plt.ylabel("Cost values") -plt.show() - -############################################################################## -# -# Since our example target Hamiltonian is a single Pauli string, we know -# without needing any training that it has only :math:`\pm 1` eigenvalues. -# It is a very simple example, but we see that the training of our circuit using -# the gadget Hamiltonian as a cost function did indeed allow us to reach the -# global minimum of the target cost function. -# -# Now that you have an idea of how you can use perturbative gadgets in -# variational quantum algorithms, you can try applying them to more complex -# problems! However, be aware of the exponential scaling of classical -# simulations of quantum systems; adding linearly many auxiliary qubits -# quickly becomes hard to simulate. -# For those interested in the theory behind it or more formal statements of -# "how close" the results using the gadget are from the targeted ones, -# check out the original paper [#cichy2022]_. -# There you will also find further discussions on the advantages and limits of -# this proposal, -# as well as a more general recipe to design other gadget -# constructions with similar properties. -# Also, the complete code with explanations on how to reproduce the -# figures from the paper can be found in -# `this repository `_. -# -# References -# ---------- -# -# .. [#cichy2022] -# -# Cichy, S., Faehrmann, P.K., Khatri, S., Eisert, J. -# "A perturbative gadget for delaying the onset of barren plateaus in variational quantum algorithms." `arXiv:2210.03099 -# `__, 2022. -# -# .. [#cerezo2021] -# -# Cerezo, M., Sone, A., Volkoff, T. et al. -# "Cost function dependent barren plateaus in shallow parametrized quantum circuits." `Nat Commun 12, 1791 -# `__, 2021. -# -# About the author -# ---------------- +r""" +Perturbative Gadgets for Variational Quantum Algorithms +========================================== + +.. meta:: + :property="og:description": Use perturbative gadgets to avoid cost-function-dependent barren plateaus + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_barren_gadgets.png + + +.. related:: + tutorial_barren_plateaus Barren plateaus in quantum neural networks¶ + tutorial_local_cost_functions Alleviating barren plateaus with local cost functions + +*Author: Simon Cichy — Posted: 09 December 2022. Last updated: 09 December 2022.* + +Variational quantum algorithms are seen as one of the most primising candidates +for useful applications of quantum computers in the near term, but there are +still a few hurdles to overcome when it comes to practical implementation. +One of them, is the trainability. +In other words, one needs to ensure that the cost function is not flat. +In this tutorial, we will explore the application of perturbative gadgets in +variational quantum algorithms to outgo the issue of cost-function-dependent +barren plateaus, as proposed in Ref. [#cichy2022]_ + +Some context +------------ + +Barren plateaus refer to the phenomenon where the gradients of the cost function +decay exponentially with the size of the problem. Essentially, the cost +landscape becomes flat, with exception of some small regions, e.g., around +the minimum. +That is a problem because increasing the precision of the cost +function requires more measurements from the quantum device due to shot noise, +and an exponential number of measurements would render the algorithm impractical. +If you are not familiar yet with the concept of barren plateaus, I recommend you +first check out the demonstrations on :doc:`barren plateaus ` +and :doc:`avoiding barren plateaus with local cost functions `. + +As presented in the second aforementioned demo, barren plateaus are more severe when using global +cost functions compared to local ones. +A global cost function requires the simultaneous measurement of all +qubits at once. In contrast, a local one is constructed from terms that only +act on a small subset of qubits. + +We want to explore this topic further and learn about one possible mitigation +strategy. +Thinking about Variational Quantum Eigensolver (VQE) applications, let us consider cost functions that are +expectation values of Hamiltonians such as + +.. math:: C(\theta) = \operatorname{Tr} \left[ H V(\theta) |00\ldots 0\rangle \! \langle 00\ldots 0| V(\theta)^\dagger\right]. + +Here :math:`|00\ldots 0\rangle` is our initial state, +:math:`V(\theta)` is the circuit ansatz and :math:`H` the Hamiltonian +whose expectation value we need to minimize. +In some cases, it is easy to find a local cost function which can substitute a global one with the same ground state. +Take, for instance, the following Hamiltonians that induce global and local cost functions, respectively. + + +.. math:: H_G = \mathbb{I} - |00\ldots 0\rangle \! \langle 00\ldots 0| \quad \textrm{ and } \quad H_L = \mathbb{I} - \frac{1}{n} \sum_j |0\rangle \! \langle 0|_j. + +Those are two different Hamiltonians (not just different formulations of the +same one), but they share the same ground state: + + +.. math:: |\psi_{\textrm{min}} \rangle = |00\ldots 0\rangle. + +Therefore, one can work with either Hamiltonian to perform the VQE routine. +However, it is not always so simple. +What if we want to find the minimum eigenenergy of +:math:`H = X \otimes X \otimes Y \otimes Z + Z \otimes Y \otimes X \otimes X` ? +It is not always trivial to construct a local cost +function that has the same minimum as the cost function of interest. +This is where perturbative gadgets come into play! + + +The definitions +--------------- +Perturbative gadgets are a common tool in adiabatic quantum computing. +Their goal is to find a Hamiltonian with local interactions that mimics +another Hamiltonian with more complex couplings. + +Ideally, they would want to implement the target Hamiltonian with complex couplings, but since it's hard to implement more than few-body interactions on hardware, they cannot do so. Perturbative gadgets work by increasing the dimension of the Hilbert space (i.e., the number +of qubits) and "encoding" the target Hamiltonian in the low-energy +subspace of a so-called "gadget" Hamiltonian. + +Let us now construct such a gadget Hamiltonian tailored for VQE applications. +First, we start from a target Hamiltonian that is a linear combination of +Pauli words acting on :math:`k` qubits each: + +.. math:: H^\text{target} = \sum_i c_i h_i, + +where :math:`h_i = \sigma_{i,1} \otimes \sigma_{i,2} \otimes \ldots \otimes \sigma_{i,k},` +:math:`\sigma_{i,j} \in \{ X, Y, Z \},` and :math:`c_i \in \mathbb{R}.` +Now we construct the gadget Hamiltonian. +For each term :math:`h_i,` we will need :math:`k` additional qubits, which we +call auxiliary qubits, and to add two terms to the Hamiltonian: +an "unperturbed" part :math:`H^\text{aux}_i` and a perturbation :math:`V_i` +of strength :math:`\lambda.` +The unperturbed part penalizes each of the newly added qubits for not being in +the :math:`|0\rangle` state + +.. math:: H^\text{aux}_i = \sum_{j=1}^k |1\rangle \! \langle 1|_{i,j} = \sum_{j=1}^k \frac{1}{2}(\mathbb{I} - Z_{i,j}). + +On the other hand, the perturbation part implements one of the operators in the Pauli word +:math:`\sigma_{i,j}` on the corresponding qubit of the target register and a +pair of Pauli :math:`X` gates on two of the auxiliary qubits: + +.. math:: V_i = \sum_{j=1}^k c_{i,j} \sigma_{i,j} \otimes X_{i,j} \otimes X_{i,(j+1) \mathrm{mod }k}. + +In the end, + +.. math:: H^\text{gad} = \sum_{i} \left( H^\text{aux}_i + \lambda V_i \right). + + + +To grasp this idea better, this is what would result from working with a Hamiltonian +acting on a total of :math:`8` qubits and having :math:`3` terms, each of them being a +:math:`4`-body interaction. + +.. figure:: ../_static/demonstration_assets/barren_gadgets/gadget-terms-tutorial.png + :align: center + :width: 90% + +For each of the terms :math:`h_1`, :math:`h_2,` and :math:`h_3` we add :math:`4` auxiliary qubits. +In the end, our gadget Hamiltonian acts on :math:`8+3\cdot 4 = 20` qubits. + +The penalization (red) acts only on the auxiliary registers, penalizing each +qubit individually, while the perturbations couple the target with the auxiliary qubits. + +As shown in Ref. [#cichy2022]_, this construction results in a spectrum that, for low energies, is similar +to that of the original Hamiltonian. +This means that by minimizing the gadget Hamiltonian and reaching its global +minimum, the resulting state will be close to the global minimum of +:math:`H^\text{target}.` + +Since it is a local cost function, it is better behaved with respect to +barren plateaus than the global cost function, making it more trainable. +As a result, one can mitigate the onset of cost-function-dependent barren +plateaus by substituting the global cost function with the resulting gadget +and using that for training instead. That is what we will do in the rest of this tutorial. +""" + +############################################################################## +# First, a few imports. PennyLane and NumPy of course, and a few +# functions specific to our tutorial. +# The ``PerturbativeGadget`` class allows the user to generate the gadget Hamiltonian +# from a user-given target Hamiltonian in an automated way. +# For those who want to check its inner workings, +# you can find the code here: +# :download:`barren_gadgets.py `. +# The functions ``get_parameter_shape``, ``generate_random_gate_sequence``, and +# ``build_ansatz`` (for the details: +# :download:`layered_ansatz.py <../_static/demonstration_assets/barren_gadgets/layered_ansatz.py>` +# ) are there to build the parameterized quantum circuit we use in this demo. +# The first computes the shape of the array of trainable parameters that the +# circuit will need. The second generates a random sequence of Pauli rotations +# from :math:`\{R_X, R_Y, R_Z\}` with the right dimension. +# Finally, ``build_ansatz`` puts the pieces together. + +import pennylane as qml +from pennylane import numpy as np +from barren_gadgets.barren_gadgets import PerturbativeGadgets +from barren_gadgets.layered_ansatz import ( + generate_random_gate_sequence, + get_parameter_shape, + build_ansatz, +) + +np.random.seed(3) + +############################################################################## +# Now, let's take the example given above: +# +# .. math:: H = X \otimes X \otimes Y \otimes Z + Z \otimes Y \otimes X \otimes X. +# +# First, we construct our target Hamiltonian in PennyLane. +# For this, we use the +# :class:`~pennylane.Hamiltonian` class. + + +H_target = qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliY(2) @ qml.PauliZ(3) \ + + qml.PauliZ(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliX(3) + +############################################################################## +# Now we can check that we constructed what we wanted. + +print(H_target) + +############################################################################## +# We indeed have a Hamiltonian composed of two terms with the expected Pauli +# words. +# Next, we can construct the corresponding gadget Hamiltonian. +# Using the class ``PerturbativeGadgets``, we can automatically +# generate the gadget Hamiltonian from the target Hamiltonian. +# The object ``gadgetizer`` will contain all the information about the settings of +# the gadgetization procedure (there are quite a few knobs one can tweak, +# but we'll skip that for now). +# Then, the method ``gadgetize`` takes a +# :class:`~pennylane.Hamiltonian` +# object and generates the +# corresponding gadget Hamiltonian. + +gadgetizer = PerturbativeGadgets() +H_gadget = gadgetizer.gadgetize(H_target) +H_gadget + +############################################################################## +# So, let's see what we got. +# We started with 4 target qubits (labelled ``0`` to ``3``) and two 4-body terms. +# Thus we get 4 additional qubits twice (``4`` to ``11``). +# The first 16 elements of our Hamiltonian correspond to the unperturbed part. +# The last 8 are the perturbation. They are a little scrambled, but one can +# recognize the 8 Paulis from the target Hamiltonian on the qubits ``0`` to +# ``3`` and the cyclic pairwise :math:`X` structure on the auxiliaries. +# Indeed, they are :math:`(X_4X_5, X_5X_6, X_6X_7, X_7X_4)` and +# :math:`(X_8X_9, X_9X_{10}, X_{10}X_{11}, X_{11}X_8).` + +############################################################################## +# Training with the gadget Hamiltonian +# ----------------------------------- +# Now that we have a little intuition on how the gadget Hamiltonian construction +# works, we will use it to train. +# Classical simulations of qubit systems are expensive, so we will simplify further +# to a target Hamiltonian with a single term, and show that using the +# gadget Hamiltonian for training allows us to minimize the target Hamiltonian. +# So, let us construct the two Hamiltonians of interest. + + +H_target = 1 * qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliY(2) @ qml.PauliZ(3) +gadgetizer = PerturbativeGadgets(perturbation_factor=10) +H_gadget = gadgetizer.gadgetize(H_target) + +############################################################################## +# Then we need to set up our variational quantum algorithm. +# That is, we choose a circuit ansatz with randomly initialized weights, +# the cost function, the optimizer with its step size, the number of +# optimization steps, and the device to run the circuit on. +# For an ansatz, we will use a variation of the +# `qml.SimplifiedTwoDesign `_, +# which was proposed in previous +# works on cost-function-dependent barren plateaus [#cerezo2021]_. +# I will skip the details of the construction, since it is not our focus here, +# and just show what it looks like. +# Here is the circuit for a small example + +shapes = get_parameter_shape(n_layers=3, n_wires=5) +init_weights = [np.pi / 4] * shapes[0][0] +weights = np.random.uniform(0, np.pi, size=shapes[1]) + + +@qml.qnode(qml.device("default.qubit", wires=range(5))) +def display_circuit(weights): + build_ansatz(initial_layer_weights=init_weights, weights=weights, wires=range(5)) + return qml.expval(qml.PauliZ(wires=0)) + +import matplotlib.pyplot as plt +qml.draw_mpl(display_circuit)(weights) +plt.show() + +############################################################################## +# Now we build the circuit for our actual experiment. + + +# Total number of qubits: target + auxiliary +num_qubits = 4 + 1 * 4 + +# Other parameters of the ansatz: weights and gate sequence +shapes = get_parameter_shape(n_layers=num_qubits, n_wires=num_qubits) +init_weights = [np.pi / 4] * shapes[0][0] +weights = np.random.uniform(0, np.pi, size=shapes[1]) +random_gate_sequence = generate_random_gate_sequence(qml.math.shape(weights)) + +############################################################################## +# For the classical optimization, we will use the standard gradient descent +# algorithm and perform 500 iterations. For the quantum part, we will simulate +# our circuit using the +# `default.qubit `_ +# simulator. + +opt = qml.GradientDescentOptimizer(stepsize=0.1) +max_iter = 500 +dev = qml.device("default.qubit", wires=range(num_qubits)) + +############################################################################## +# Finally, we will use two cost functions and create a +# `QNode `_ for each. +# The first cost function, the training cost, is the loss function of the optimization. +# For the training, we use the gadget Hamiltonian. To ensure +# that our gadget optimization is proceeding as intended, +# we also define another cost function based on the target Hamiltonian. +# We will evaluate its value at each iteration for monitoring purposes, but it +# will not be used in the optimization. + + + +@qml.qnode(dev) +def training_cost(weights): + build_ansatz( + initial_layer_weights=init_weights, + weights=weights, + wires=range(num_qubits), + gate_sequence=random_gate_sequence, + ) + return qml.expval(H_gadget) + + +@qml.qnode(dev) +def monitoring_cost(weights): + build_ansatz( + initial_layer_weights=init_weights, + weights=weights, + wires=range(num_qubits), + gate_sequence=random_gate_sequence, + ) + return qml.expval(H_target) + + +############################################################################## +# The idea is that if we reach the global minimum for the gadget Hamiltonian, we +# should also be close to the global minimum of the target Hamiltonian, which is +# what we are ultimately interested in. +# To see the results and plot them, we will save the cost values +# at each iteration. + +costs_lists = {} +costs_lists["training"] = [training_cost(weights)] +costs_lists["monitoring"] = [monitoring_cost(weights)] + +############################################################################## +# Now everything is set up, let's run the optimization and see how it goes. +# Be careful, this will take a while. + +for it in range(max_iter): + weights = opt.step(training_cost, weights) + costs_lists["training"].append(training_cost(weights)) + costs_lists["monitoring"].append(monitoring_cost(weights)) + + +plt.style.use("seaborn") + +plt.figure() +plt.plot(costs_lists["training"]) +plt.plot(costs_lists["monitoring"]) +plt.legend(["training", "monitoring"]) +plt.xlabel("Number of iterations") +plt.ylabel("Cost values") +plt.show() + +############################################################################## +# +# Since our example target Hamiltonian is a single Pauli string, we know +# without needing any training that it has only :math:`\pm 1` eigenvalues. +# It is a very simple example, but we see that the training of our circuit using +# the gadget Hamiltonian as a cost function did indeed allow us to reach the +# global minimum of the target cost function. +# +# Now that you have an idea of how you can use perturbative gadgets in +# variational quantum algorithms, you can try applying them to more complex +# problems! However, be aware of the exponential scaling of classical +# simulations of quantum systems; adding linearly many auxiliary qubits +# quickly becomes hard to simulate. +# For those interested in the theory behind it or more formal statements of +# "how close" the results using the gadget are from the targeted ones, +# check out the original paper [#cichy2022]_. +# There you will also find further discussions on the advantages and limits of +# this proposal, +# as well as a more general recipe to design other gadget +# constructions with similar properties. +# Also, the complete code with explanations on how to reproduce the +# figures from the paper can be found in +# `this repository `_. +# +# References +# ---------- +# +# .. [#cichy2022] +# +# Cichy, S., Faehrmann, P.K., Khatri, S., Eisert, J. +# "A perturbative gadget for delaying the onset of barren plateaus in variational quantum algorithms." `arXiv:2210.03099 +# `__, 2022. +# +# .. [#cerezo2021] +# +# Cerezo, M., Sone, A., Volkoff, T. et al. +# "Cost function dependent barren plateaus in shallow parametrized quantum circuits." `Nat Commun 12, 1791 +# `__, 2021. +# +# About the author +# ---------------- # \ No newline at end of file diff --git a/demonstrations/tutorial_differentiable_HF.py b/demonstrations/tutorial_differentiable_HF.py index e06dc3fa4d..d1820613d1 100644 --- a/demonstrations/tutorial_differentiable_HF.py +++ b/demonstrations/tutorial_differentiable_HF.py @@ -1,393 +1,393 @@ -r""" - -Differentiable Hartree-Fock -=========================== - -.. meta:: - :property="og:description": Learn how to use the differentiable Hartree-Fock solver - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/differentiable_HF.png - -.. related:: - tutorial_quantum_chemistry Building molecular Hamiltonians - tutorial_vqe A brief overview of VQE - tutorial_givens_rotations Givens rotations for quantum chemistry - tutorial_adaptive_circuits Adaptive circuits for quantum chemistry - - -*Author: Soran Jahangiri — Posted: 09 May 2022. Last updated: 09 May 2022.* - -In this tutorial, you will learn how to use PennyLane's differentiable Hartree-Fock solver -[#arrazola2021]_. The quantum chemistry module in PennyLane, :mod:`qml.qchem `, -provides built-in methods for constructing -atomic and molecular orbitals, building Fock matrices and solving the self-consistent field -equations to obtain optimized orbitals which can be used to construct fully-differentiable -molecular Hamiltonians. PennyLane allows users to natively compute derivatives of all these objects -with respect to the underlying parameters using the methods of -`automatic differentiation `_. We -introduce a workflow to jointly optimize circuit parameters, nuclear coordinates and basis set -parameters in a variational quantum eigensolver algorithm. You will also learn how to visualize the -atomic and molecular orbitals which can be used to create an animation like this: - -.. figure:: /_static/demonstration_assets/differentiable_HF/h2.gif - :width: 60% - :align: center - - The bonding molecular orbital of hydrogen visualized during a full geometry, circuit and - basis set optimization. - -Let's get started! - -Differentiable Hamiltonians ---------------------------- - -Variational quantum algorithms aim to calculate the energy of a molecule by constructing a -parameterized quantum circuit and finding a set of parameters that minimize the expectation value of -the electronic `molecular Hamiltonian `_. The -optimization can be carried out by computing the gradients of the expectation value with respect to -these parameters and iteratively updating them until convergence is achieved. In principle, the -optimization process is not limited to the circuit parameters and can be extended to include the -parameters of the Hamiltonian that can be optimized concurrently with the circuit parameters. The -aim is now to obtain the set of parameters that minimize the following expectation value - -.. math:: \left \langle \Psi(\theta) | H(\beta) | \Psi(\theta) \right \rangle, - -where :math:`\theta` and :math:`\beta` represent the circuit and Hamiltonian parameters, -respectively. - -Computing the gradient of a molecular Hamiltonian is challenging because the dependency of the -Hamiltonian on the molecular parameters is typically not very straightforward. This makes symbolic -differentiation methods, which obtain derivatives of an input function by direct mathematical -manipulation, of limited scope. Furthermore, numerical differentiation methods based on -`finite differences `_ are not always -reliable due to their intrinsic instability, especially when the number of -differentiable parameters is large. These limitations can be alleviated by using automatic -differentiation methods which can be used to compute exact gradients of a function, implemented with -computer code, using resources comparable to those required to evaluate the function itself. - -Efficient optimization of the molecular Hamiltonian parameters in a variational quantum algorithm -is essential for tackling problems such as -`geometry optimization `_ and vibrational -frequency -calculations. These problems require computing the first- and second-order derivatives of the -molecular energy with respect to nuclear coordinates which can be efficiently obtained if the -variational workflow is automatically differentiable. Another important example is the simultaneous -optimization of the parameters of the basis set used to construct the atomic orbitals which can in -principle increase the accuracy of the computed energy without increasing the number of qubits in a -quantum simulation. The joint optimization of the circuit and Hamiltonian parameters can also be -used when the chemical problem involves optimizing the parameters of external potentials. - -The Hartree-Fock method ------------------------ - -The main goal of the Hartree-Fock method is to obtain molecular orbitals that minimize the -energy of a system where electrons are treated as independent particles that experience a mean field -generated by the other electrons. These optimized molecular orbitals are then used to -construct one- and two-body electron integrals in the basis of molecular orbitals, :math:`\phi,` - -.. math:: h_{pq} =\int dx \,\phi_p^*(x)\left(-\frac{\nabla^2}{2}-\sum_{i=1}^N\frac{Z_i}{|r-R_i|}\right)\phi_q(x),\\\\ -.. math:: h_{pqrs} = \int dx_1 dx_2\, \frac{\phi_p^*(x_1)\phi_q^*(x_2)\phi_r(x_2)\phi_s(x_1)}{|r_1-r_2|}. - -These integrals are used to generate a differentiable -`second-quantized `_ molecular Hamiltonian as - - -.. math:: H=\sum_{pq} h_{pq}a_p^\dagger a_q +\frac{1}{2}\sum_{pqrs}h_{pqrs}a_p^\dagger a_q^\dagger a_r a_s, - -where :math:`a^\dagger` and :math:`a` are the fermionic creation and annihilation operators, -respectively. This Hamiltonian is then transformed to the qubit basis. Let's see how this can be -done in PennyLane. - -To get started, we need to define the atomic symbols and the nuclear coordinates of the molecule. -For the hydrogen molecule we have -""" - -import pennylane as qml -import numpy as np -import jax -import jax.numpy as jnp -import matplotlib.pyplot as plt -np.set_printoptions(precision=5) -jax.config.update("jax_enable_x64", True) - -symbols = ["H", "H"] -# optimized geometry at the Hartree-Fock level -geometry = jnp.array([[-0.672943567415407, 0.0, 0.0], - [ 0.672943567415407, 0.0, 0.0]]) - -############################################################################## -# We can now compute the Hartree-Fock energy and its gradient with respect to the -# nuclear coordinates. To do that, we create a molecule object that stores all the molecular -# parameters needed to perform a Hartree-Fock calculation. - -mol = qml.qchem.Molecule(symbols, geometry) - -############################################################################## -# The Hartree-Fock energy can now be computed with the -# :func:`~.pennylane.qchem.hf_energy` function which is a function transform - -qml.qchem.hf_energy(mol)() - -############################################################################## -# We now compute the gradient of the energy with respect to the nuclear coordinates - -jax.grad(qml.qchem.hf_energy(mol), argnums=0)(geometry, mol.coeff, mol.alpha) - -############################################################################## -# The obtained gradients are equal or very close to zero because the geometry we used here has been -# previously optimized at the Hartree-Fock level. You can use a different geometry and verify that -# the newly computed gradients are not all zero. -# -# We can also compute the values and gradients of several other quantities that are obtained during -# the Hartree-Fock procedure. These include all integrals over basis functions, matrices formed from -# these integrals and the one- and two-body integrals over molecular orbitals [#arrazola2021]_. -# Let's look at a few examples. -# -# We first compute the overlap integral between the two S-type atomic orbitals of the hydrogen -# atoms. Here we are using the `STO-3G `_ -# basis set in which each of the atomic orbitals is represented by one basis function composed of -# three primitive Gaussian functions. These basis functions can be accessed from the molecule -# object as - -S1 = mol.basis_set[0] -S2 = mol.basis_set[1] - -############################################################################## -# We can check the parameters of the basis functions as - -for param in S1.params: - print(param) - -############################################################################## -# This returns the exponents, contraction coefficients and the centres of the three Gaussian -# functions of the STO-3G basis set. These parameters can be also obtained individually by using -# ``S1.alpha``, ``S1.coeff`` and ``S1.r``, respectively. You can verify that both of the ``S1`` an -# ``S2`` atomic orbitals have the same exponents and contraction coefficients but are centred on -# different hydrogen atoms. You can also verify that the orbitals are S-type by printing the angular -# momentum quantum numbers with - -S1.l - -############################################################################## -# This gives us a tuple of three integers, representing the exponents of the :math:`x,` :math:`y` -# and :math:`z` components in the Gaussian functions [#arrazola2021]_. -# -# We can now compute the overlap integral, -# -# .. math:: -# -# S_{\mu \nu} = \int \chi_\mu^* (r) \chi_\nu (r) dr -# -# between the atomic orbitals :math:`\chi,` by passing the orbitals and the initial values of their -# centres to the :func:`~.pennylane.qchem.overlap_integral` function. The centres of the orbitals -# are those of the hydrogen atoms by default and are therefore treated as differentiable parameters -# by PennyLane. - -qml.qchem.overlap_integral(S1, S2)() - -############################################################################## -# You can verify that the overlap integral between two identical atomic orbitals is equal to one. -# We can now compute the gradient of the overlap integral with respect to the orbital centres - -jax.grad(qml.qchem.overlap_integral(S1, S2))(geometry, mol.coeff, mol.alpha) - -############################################################################## -# Can you explain why some of the computed gradients are zero? -# -# Let's now plot the atomic orbitals and their overlap. We can do it by using -# the :py:meth:`~.pennylane.qchem.Molecule.atomic_orbital` function, which evaluates the -# atomic orbital at a given coordinate. For instance, the value of the S orbital on the first -# hydrogen atom can be computed at the origin as - -V1 = mol.atomic_orbital(0) -V1(0.0, 0.0, 0.0) - -############################################################################## -# We can evaluate this orbital at different points along the :math:`x` axis and plot it. - -x = np.linspace(-5, 5, 1000) -plt.plot(x, V1(x, 0.0, 0.0), color='teal') -plt.xlabel('X [Bohr]') -plt.show() - -############################################################################## -# We can also plot the second S orbital and visualize the overlap between them - -V2 = mol.atomic_orbital(1) -plt.plot(x, V1(x, 0.0, 0.0), color='teal') -plt.plot(x, V2(x, 0.0, 0.0), color='teal') -plt.fill_between( - x, np.minimum(V1(x, 0.0, 0.0), V2(x, 0.0, 0.0)), color = 'red', alpha = 0.5, hatch = '||') -plt.xlabel('X [Bohr]') -plt.show() - -############################################################################## -# By looking at the orbitals, can you guess at what distance the value of the overlap becomes -# negligible? Can you verify your guess by computing the overlap at that distance? -# -# Similarly, we can plot the molecular orbitals of the hydrogen molecule obtained from the -# Hartree-Fock calculations. We plot the cross-section of the bonding orbital on the :math:`x-y` -# plane. - -n = 30 # number of grid points along each axis -qml.qchem.hf_energy(mol)() -mol.mo_coefficients = mol.mo_coefficients.T -mo = mol.molecular_orbital(0) -x, y = np.meshgrid(np.linspace(-2, 2, n), - np.linspace(-2, 2, n)) -val = np.vectorize(mo)(x, y, 0) -val = np.array([val[i][j] for i in range(n) for j in range(n)]).reshape(n, n) - -fig, ax = plt.subplots() -co = ax.contour(x, y, val, 10, cmap='summer_r', zorder=0) -ax.clabel(co, inline=2, fontsize=10) -plt.scatter(mol.coordinates[:,0], mol.coordinates[:,1], s = 80, color='black') -ax.set_xlabel('X [Bohr]') -ax.set_ylabel('Y [Bohr]') -plt.show() - -############################################################################## -# VQE simulations -# --------------- -# -# By performing the Hartree-Fock calculations, we obtain a set of one- and two-body integrals -# over molecular orbitals that can be used to construct the molecular Hamiltonian with the -# :func:`~.pennylane.qchem.molecular_hamiltonian` function. - -hamiltonian, qubits = qml.qchem.molecular_hamiltonian(mol) -print(hamiltonian) - -############################################################################## -# The Hamiltonian contains 15 terms and, importantly, the coefficients of the Hamiltonian are all -# differentiable. We can construct a circuit and perform a VQE simulation in which both of the -# circuit parameters and the nuclear coordinates are optimized simultaneously by using the computed -# gradients. We will have two sets of differentiable parameters: the first set is the -# rotation angles of the excitation gates which are applied to the reference Hartree-Fock state -# to construct the exact ground state. The second set contains the nuclear coordinates of the -# hydrogen atoms. - -dev = qml.device("default.qubit", wires=4) -def energy(): - @qml.qnode(dev, interface="jax") - def circuit(*args): - qml.BasisState(np.array([1, 1, 0, 0]), wires=range(4)) - qml.DoubleExcitation(*args[0], wires=[0, 1, 2, 3]) - mol = qml.qchem.Molecule(symbols, geometry, alpha=args[3], coeff=args[2]) - H = qml.qchem.molecular_hamiltonian(mol, args=args[1:])[0] - return qml.expval(H) - return circuit - -############################################################################## -# Note that we only use the :class:`~.pennylane.DoubleExcitation` gate as the -# :class:`~.pennylane.SingleExcitation` ones can be neglected in this particular example -# [#szabo1996]_. We now compute the gradients of the energy with respect to the circuit parameter -# and the nuclear coordinates and update the parameters iteratively. Note that the nuclear -# coordinate gradients are simply the forces on the atomic nuclei. - -# initial value of the circuit parameter -circuit_param = jnp.array([0.0]) - -geometry = jnp.array([[0.0, 0.02, -0.672943567415407], - [0.1, 0.0, 0.672943567415407]]) - -for n in range(36): - mol = qml.qchem.Molecule(symbols, geometry) - args = [circuit_param, geometry, mol.coeff, mol.alpha] - # gradient for circuit parameters - g_param = jax.grad(energy(), argnums = 0)(*args) - circuit_param = circuit_param - 0.25 * g_param[0] - - # gradient for nuclear coordinates - forces = jax.grad(energy(), argnums = 1)(*args) - geometry = geometry - 0.5 * forces - - if n % 5 == 0: - print(f'n: {n}, E: {energy()(*args):.8f}, Force-max: {abs(forces).max():.8f}') - -############################################################################## -# After 35 steps of optimization, the forces on the atomic nuclei and the gradient of the -# circuit parameter are both approaching zero, and the energy of the molecule is that of the -# optimized geometry at the -# `full-CI `_ level: -# :math:`-1.1373060483` Ha. You can print the optimized geometry and verify that the final bond -# length of hydrogen is identical to the one computed with full-CI which is :math:`1.389` -# `Bohr `_. -# -# We are now ready to perform a full optimization where the circuit parameters, the nuclear -# coordinates and the basis set parameters are all differentiable parameters that can be optimized -# simultaneously. - -symbols = ["H", "H"] -# initial values of the nuclear coordinates -geometry = jnp.array([[0.0, 0.0, -0.672943567415407], - [0.0, 0.0, 0.672943567415407]]) - -# initial values of the basis set contraction coefficients -coeff = jnp.array([[0.1543289673, 0.5353281423, 0.4446345422], - [0.1543289673, 0.5353281423, 0.4446345422]]) - -# initial values of the basis set exponents -alpha = jnp.array([[3.42525091, 0.62391373, 0.1688554], - [3.42525091, 0.62391373, 0.1688554]]) - -# initial value of the circuit parameter -circuit_param = jnp.array([0.0]) - -mol = qml.qchem.Molecule(symbols, geometry, coeff=coeff, alpha=alpha) -args = [circuit_param, geometry, coeff, alpha] - -for n in range(36): - args = [circuit_param, geometry, coeff, alpha] - mol = qml.qchem.Molecule(symbols, geometry, alpha=alpha, coeff=coeff) - - # gradient for circuit parameters - g_param = jax.grad(energy(), argnums=[0, 1, 2, 3])(*args)[0] - circuit_param = circuit_param - 0.25 * g_param[0] - - # gradient for nuclear coordinates - value, gradients = jax.value_and_grad(energy(), argnums=[1, 2, 3])(*args) - geometry = geometry - 0.5 * gradients[0] - alpha = alpha - 0.25 * gradients[2] - coeff = coeff - 0.25 * gradients[1] - - if n % 5 == 0: - print(f'n: {n}, E: {value:.8f}, Force-max: {abs(gradients[0]).max():.8f}') - -############################################################################## -# You can also print the gradients of the circuit and basis set parameters and confirm that they are -# approaching zero. The computed energy of :math:`-1.14040160` Ha is -# lower than the full-CI energy, :math:`-1.1373060483` Ha (obtained with the STO-3G basis set for -# the hydrogen molecule) because we have optimized the basis set parameters in our example. This -# means that we can reach a lower energy for hydrogen without increasing the basis set size, which -# would otherwise lead to a larger number of qubits. -# -# Conclusions -# ----------- -# This tutorial introduces an important feature of PennyLane that allows performing -# fully-differentiable Hartree-Fock and subsequently VQE simulations. This feature provides two -# major benefits: i) All gradient computations needed for parameter optimization can be carried out -# with the elegant methods of automatic differentiation which facilitates simultaneous optimizations -# of circuit and Hamiltonian parameters in applications such as VQE molecular geometry -# optimizations. ii) By optimizing the molecular parameters such as the exponent and contraction -# coefficients of Gaussian functions of the basis set, one can reach a lower energy without -# increasing the number of basis functions. Can you think of other interesting molecular parameters -# that can be optimized along with the nuclear coordinates and basis set parameters that we -# optimized in this tutorial? -# -# References -# ---------- -# -# .. [#arrazola2021] -# -# Juan Miguel Arrazola, Soran Jahangiri, Alain Delgado, Jack Ceroni *et al.*, "Differentiable -# quantum computational chemistry with PennyLane". `arXiv:2111.09967 -# `__ -# -# .. [#szabo1996] -# -# Attila Szabo, Neil S. Ostlund, "Modern Quantum Chemistry: Introduction to Advanced Electronic -# Structure Theory". Dover Publications, 1996. -# -# -# About the author -# ---------------- -# +r""" + +Differentiable Hartree-Fock +=========================== + +.. meta:: + :property="og:description": Learn how to use the differentiable Hartree-Fock solver + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/differentiable_HF.png + +.. related:: + tutorial_quantum_chemistry Building molecular Hamiltonians + tutorial_vqe A brief overview of VQE + tutorial_givens_rotations Givens rotations for quantum chemistry + tutorial_adaptive_circuits Adaptive circuits for quantum chemistry + + +*Author: Soran Jahangiri — Posted: 09 May 2022. Last updated: 09 May 2022.* + +In this tutorial, you will learn how to use PennyLane's differentiable Hartree-Fock solver +[#arrazola2021]_. The quantum chemistry module in PennyLane, :mod:`qml.qchem `, +provides built-in methods for constructing +atomic and molecular orbitals, building Fock matrices and solving the self-consistent field +equations to obtain optimized orbitals which can be used to construct fully-differentiable +molecular Hamiltonians. PennyLane allows users to natively compute derivatives of all these objects +with respect to the underlying parameters using the methods of +`automatic differentiation `_. We +introduce a workflow to jointly optimize circuit parameters, nuclear coordinates and basis set +parameters in a variational quantum eigensolver algorithm. You will also learn how to visualize the +atomic and molecular orbitals which can be used to create an animation like this: + +.. figure:: /_static/demonstration_assets/differentiable_HF/h2.gif + :width: 60% + :align: center + + The bonding molecular orbital of hydrogen visualized during a full geometry, circuit and + basis set optimization. + +Let's get started! + +Differentiable Hamiltonians +--------------------------- + +Variational quantum algorithms aim to calculate the energy of a molecule by constructing a +parameterized quantum circuit and finding a set of parameters that minimize the expectation value of +the electronic `molecular Hamiltonian `_. The +optimization can be carried out by computing the gradients of the expectation value with respect to +these parameters and iteratively updating them until convergence is achieved. In principle, the +optimization process is not limited to the circuit parameters and can be extended to include the +parameters of the Hamiltonian that can be optimized concurrently with the circuit parameters. The +aim is now to obtain the set of parameters that minimize the following expectation value + +.. math:: \left \langle \Psi(\theta) | H(\beta) | \Psi(\theta) \right \rangle, + +where :math:`\theta` and :math:`\beta` represent the circuit and Hamiltonian parameters, +respectively. + +Computing the gradient of a molecular Hamiltonian is challenging because the dependency of the +Hamiltonian on the molecular parameters is typically not very straightforward. This makes symbolic +differentiation methods, which obtain derivatives of an input function by direct mathematical +manipulation, of limited scope. Furthermore, numerical differentiation methods based on +`finite differences `_ are not always +reliable due to their intrinsic instability, especially when the number of +differentiable parameters is large. These limitations can be alleviated by using automatic +differentiation methods which can be used to compute exact gradients of a function, implemented with +computer code, using resources comparable to those required to evaluate the function itself. + +Efficient optimization of the molecular Hamiltonian parameters in a variational quantum algorithm +is essential for tackling problems such as +`geometry optimization `_ and vibrational +frequency +calculations. These problems require computing the first- and second-order derivatives of the +molecular energy with respect to nuclear coordinates which can be efficiently obtained if the +variational workflow is automatically differentiable. Another important example is the simultaneous +optimization of the parameters of the basis set used to construct the atomic orbitals which can in +principle increase the accuracy of the computed energy without increasing the number of qubits in a +quantum simulation. The joint optimization of the circuit and Hamiltonian parameters can also be +used when the chemical problem involves optimizing the parameters of external potentials. + +The Hartree-Fock method +----------------------- + +The main goal of the Hartree-Fock method is to obtain molecular orbitals that minimize the +energy of a system where electrons are treated as independent particles that experience a mean field +generated by the other electrons. These optimized molecular orbitals are then used to +construct one- and two-body electron integrals in the basis of molecular orbitals, :math:`\phi,` + +.. math:: h_{pq} =\int dx \,\phi_p^*(x)\left(-\frac{\nabla^2}{2}-\sum_{i=1}^N\frac{Z_i}{|r-R_i|}\right)\phi_q(x),\\\\ +.. math:: h_{pqrs} = \int dx_1 dx_2\, \frac{\phi_p^*(x_1)\phi_q^*(x_2)\phi_r(x_2)\phi_s(x_1)}{|r_1-r_2|}. + +These integrals are used to generate a differentiable +`second-quantized `_ molecular Hamiltonian as + + +.. math:: H=\sum_{pq} h_{pq}a_p^\dagger a_q +\frac{1}{2}\sum_{pqrs}h_{pqrs}a_p^\dagger a_q^\dagger a_r a_s, + +where :math:`a^\dagger` and :math:`a` are the fermionic creation and annihilation operators, +respectively. This Hamiltonian is then transformed to the qubit basis. Let's see how this can be +done in PennyLane. + +To get started, we need to define the atomic symbols and the nuclear coordinates of the molecule. +For the hydrogen molecule we have +""" + +import pennylane as qml +import numpy as np +import jax +import jax.numpy as jnp +import matplotlib.pyplot as plt +np.set_printoptions(precision=5) +jax.config.update("jax_enable_x64", True) + +symbols = ["H", "H"] +# optimized geometry at the Hartree-Fock level +geometry = jnp.array([[-0.672943567415407, 0.0, 0.0], + [ 0.672943567415407, 0.0, 0.0]]) + +############################################################################## +# We can now compute the Hartree-Fock energy and its gradient with respect to the +# nuclear coordinates. To do that, we create a molecule object that stores all the molecular +# parameters needed to perform a Hartree-Fock calculation. + +mol = qml.qchem.Molecule(symbols, geometry) + +############################################################################## +# The Hartree-Fock energy can now be computed with the +# :func:`~.pennylane.qchem.hf_energy` function which is a function transform + +qml.qchem.hf_energy(mol)() + +############################################################################## +# We now compute the gradient of the energy with respect to the nuclear coordinates + +jax.grad(qml.qchem.hf_energy(mol), argnums=0)(geometry, mol.coeff, mol.alpha) + +############################################################################## +# The obtained gradients are equal or very close to zero because the geometry we used here has been +# previously optimized at the Hartree-Fock level. You can use a different geometry and verify that +# the newly computed gradients are not all zero. +# +# We can also compute the values and gradients of several other quantities that are obtained during +# the Hartree-Fock procedure. These include all integrals over basis functions, matrices formed from +# these integrals and the one- and two-body integrals over molecular orbitals [#arrazola2021]_. +# Let's look at a few examples. +# +# We first compute the overlap integral between the two S-type atomic orbitals of the hydrogen +# atoms. Here we are using the `STO-3G `_ +# basis set in which each of the atomic orbitals is represented by one basis function composed of +# three primitive Gaussian functions. These basis functions can be accessed from the molecule +# object as + +S1 = mol.basis_set[0] +S2 = mol.basis_set[1] + +############################################################################## +# We can check the parameters of the basis functions as + +for param in S1.params: + print(param) + +############################################################################## +# This returns the exponents, contraction coefficients and the centres of the three Gaussian +# functions of the STO-3G basis set. These parameters can be also obtained individually by using +# ``S1.alpha``, ``S1.coeff`` and ``S1.r``, respectively. You can verify that both of the ``S1`` an +# ``S2`` atomic orbitals have the same exponents and contraction coefficients but are centred on +# different hydrogen atoms. You can also verify that the orbitals are S-type by printing the angular +# momentum quantum numbers with + +S1.l + +############################################################################## +# This gives us a tuple of three integers, representing the exponents of the :math:`x,` :math:`y` +# and :math:`z` components in the Gaussian functions [#arrazola2021]_. +# +# We can now compute the overlap integral, +# +# .. math:: +# +# S_{\mu \nu} = \int \chi_\mu^* (r) \chi_\nu (r) dr +# +# between the atomic orbitals :math:`\chi,` by passing the orbitals and the initial values of their +# centres to the :func:`~.pennylane.qchem.overlap_integral` function. The centres of the orbitals +# are those of the hydrogen atoms by default and are therefore treated as differentiable parameters +# by PennyLane. + +qml.qchem.overlap_integral(S1, S2)() + +############################################################################## +# You can verify that the overlap integral between two identical atomic orbitals is equal to one. +# We can now compute the gradient of the overlap integral with respect to the orbital centres + +jax.grad(qml.qchem.overlap_integral(S1, S2))(geometry, mol.coeff, mol.alpha) + +############################################################################## +# Can you explain why some of the computed gradients are zero? +# +# Let's now plot the atomic orbitals and their overlap. We can do it by using +# the :py:meth:`~.pennylane.qchem.Molecule.atomic_orbital` function, which evaluates the +# atomic orbital at a given coordinate. For instance, the value of the S orbital on the first +# hydrogen atom can be computed at the origin as + +V1 = mol.atomic_orbital(0) +V1(0.0, 0.0, 0.0) + +############################################################################## +# We can evaluate this orbital at different points along the :math:`x` axis and plot it. + +x = np.linspace(-5, 5, 1000) +plt.plot(x, V1(x, 0.0, 0.0), color='teal') +plt.xlabel('X [Bohr]') +plt.show() + +############################################################################## +# We can also plot the second S orbital and visualize the overlap between them + +V2 = mol.atomic_orbital(1) +plt.plot(x, V1(x, 0.0, 0.0), color='teal') +plt.plot(x, V2(x, 0.0, 0.0), color='teal') +plt.fill_between( + x, np.minimum(V1(x, 0.0, 0.0), V2(x, 0.0, 0.0)), color = 'red', alpha = 0.5, hatch = '||') +plt.xlabel('X [Bohr]') +plt.show() + +############################################################################## +# By looking at the orbitals, can you guess at what distance the value of the overlap becomes +# negligible? Can you verify your guess by computing the overlap at that distance? +# +# Similarly, we can plot the molecular orbitals of the hydrogen molecule obtained from the +# Hartree-Fock calculations. We plot the cross-section of the bonding orbital on the :math:`x-y` +# plane. + +n = 30 # number of grid points along each axis +qml.qchem.hf_energy(mol)() +mol.mo_coefficients = mol.mo_coefficients.T +mo = mol.molecular_orbital(0) +x, y = np.meshgrid(np.linspace(-2, 2, n), + np.linspace(-2, 2, n)) +val = np.vectorize(mo)(x, y, 0) +val = np.array([val[i][j] for i in range(n) for j in range(n)]).reshape(n, n) + +fig, ax = plt.subplots() +co = ax.contour(x, y, val, 10, cmap='summer_r', zorder=0) +ax.clabel(co, inline=2, fontsize=10) +plt.scatter(mol.coordinates[:,0], mol.coordinates[:,1], s = 80, color='black') +ax.set_xlabel('X [Bohr]') +ax.set_ylabel('Y [Bohr]') +plt.show() + +############################################################################## +# VQE simulations +# --------------- +# +# By performing the Hartree-Fock calculations, we obtain a set of one- and two-body integrals +# over molecular orbitals that can be used to construct the molecular Hamiltonian with the +# :func:`~.pennylane.qchem.molecular_hamiltonian` function. + +hamiltonian, qubits = qml.qchem.molecular_hamiltonian(mol) +print(hamiltonian) + +############################################################################## +# The Hamiltonian contains 15 terms and, importantly, the coefficients of the Hamiltonian are all +# differentiable. We can construct a circuit and perform a VQE simulation in which both of the +# circuit parameters and the nuclear coordinates are optimized simultaneously by using the computed +# gradients. We will have two sets of differentiable parameters: the first set is the +# rotation angles of the excitation gates which are applied to the reference Hartree-Fock state +# to construct the exact ground state. The second set contains the nuclear coordinates of the +# hydrogen atoms. + +dev = qml.device("default.qubit", wires=4) +def energy(): + @qml.qnode(dev, interface="jax") + def circuit(*args): + qml.BasisState(np.array([1, 1, 0, 0]), wires=range(4)) + qml.DoubleExcitation(*args[0], wires=[0, 1, 2, 3]) + mol = qml.qchem.Molecule(symbols, geometry, alpha=args[3], coeff=args[2]) + H = qml.qchem.molecular_hamiltonian(mol, args=args[1:])[0] + return qml.expval(H) + return circuit + +############################################################################## +# Note that we only use the :class:`~.pennylane.DoubleExcitation` gate as the +# :class:`~.pennylane.SingleExcitation` ones can be neglected in this particular example +# [#szabo1996]_. We now compute the gradients of the energy with respect to the circuit parameter +# and the nuclear coordinates and update the parameters iteratively. Note that the nuclear +# coordinate gradients are simply the forces on the atomic nuclei. + +# initial value of the circuit parameter +circuit_param = jnp.array([0.0]) + +geometry = jnp.array([[0.0, 0.02, -0.672943567415407], + [0.1, 0.0, 0.672943567415407]]) + +for n in range(36): + mol = qml.qchem.Molecule(symbols, geometry) + args = [circuit_param, geometry, mol.coeff, mol.alpha] + # gradient for circuit parameters + g_param = jax.grad(energy(), argnums = 0)(*args) + circuit_param = circuit_param - 0.25 * g_param[0] + + # gradient for nuclear coordinates + forces = jax.grad(energy(), argnums = 1)(*args) + geometry = geometry - 0.5 * forces + + if n % 5 == 0: + print(f'n: {n}, E: {energy()(*args):.8f}, Force-max: {abs(forces).max():.8f}') + +############################################################################## +# After 35 steps of optimization, the forces on the atomic nuclei and the gradient of the +# circuit parameter are both approaching zero, and the energy of the molecule is that of the +# optimized geometry at the +# `full-CI `_ level: +# :math:`-1.1373060483` Ha. You can print the optimized geometry and verify that the final bond +# length of hydrogen is identical to the one computed with full-CI which is :math:`1.389` +# `Bohr `_. +# +# We are now ready to perform a full optimization where the circuit parameters, the nuclear +# coordinates and the basis set parameters are all differentiable parameters that can be optimized +# simultaneously. + +symbols = ["H", "H"] +# initial values of the nuclear coordinates +geometry = jnp.array([[0.0, 0.0, -0.672943567415407], + [0.0, 0.0, 0.672943567415407]]) + +# initial values of the basis set contraction coefficients +coeff = jnp.array([[0.1543289673, 0.5353281423, 0.4446345422], + [0.1543289673, 0.5353281423, 0.4446345422]]) + +# initial values of the basis set exponents +alpha = jnp.array([[3.42525091, 0.62391373, 0.1688554], + [3.42525091, 0.62391373, 0.1688554]]) + +# initial value of the circuit parameter +circuit_param = jnp.array([0.0]) + +mol = qml.qchem.Molecule(symbols, geometry, coeff=coeff, alpha=alpha) +args = [circuit_param, geometry, coeff, alpha] + +for n in range(36): + args = [circuit_param, geometry, coeff, alpha] + mol = qml.qchem.Molecule(symbols, geometry, alpha=alpha, coeff=coeff) + + # gradient for circuit parameters + g_param = jax.grad(energy(), argnums=[0, 1, 2, 3])(*args)[0] + circuit_param = circuit_param - 0.25 * g_param[0] + + # gradient for nuclear coordinates + value, gradients = jax.value_and_grad(energy(), argnums=[1, 2, 3])(*args) + geometry = geometry - 0.5 * gradients[0] + alpha = alpha - 0.25 * gradients[2] + coeff = coeff - 0.25 * gradients[1] + + if n % 5 == 0: + print(f'n: {n}, E: {value:.8f}, Force-max: {abs(gradients[0]).max():.8f}') + +############################################################################## +# You can also print the gradients of the circuit and basis set parameters and confirm that they are +# approaching zero. The computed energy of :math:`-1.14040160` Ha is +# lower than the full-CI energy, :math:`-1.1373060483` Ha (obtained with the STO-3G basis set for +# the hydrogen molecule) because we have optimized the basis set parameters in our example. This +# means that we can reach a lower energy for hydrogen without increasing the basis set size, which +# would otherwise lead to a larger number of qubits. +# +# Conclusions +# ----------- +# This tutorial introduces an important feature of PennyLane that allows performing +# fully-differentiable Hartree-Fock and subsequently VQE simulations. This feature provides two +# major benefits: i) All gradient computations needed for parameter optimization can be carried out +# with the elegant methods of automatic differentiation which facilitates simultaneous optimizations +# of circuit and Hamiltonian parameters in applications such as VQE molecular geometry +# optimizations. ii) By optimizing the molecular parameters such as the exponent and contraction +# coefficients of Gaussian functions of the basis set, one can reach a lower energy without +# increasing the number of basis functions. Can you think of other interesting molecular parameters +# that can be optimized along with the nuclear coordinates and basis set parameters that we +# optimized in this tutorial? +# +# References +# ---------- +# +# .. [#arrazola2021] +# +# Juan Miguel Arrazola, Soran Jahangiri, Alain Delgado, Jack Ceroni *et al.*, "Differentiable +# quantum computational chemistry with PennyLane". `arXiv:2111.09967 +# `__ +# +# .. [#szabo1996] +# +# Attila Szabo, Neil S. Ostlund, "Modern Quantum Chemistry: Introduction to Advanced Electronic +# Structure Theory". Dover Publications, 1996. +# +# +# About the author +# ---------------- +# diff --git a/demonstrations/tutorial_doubly_stochastic.py b/demonstrations/tutorial_doubly_stochastic.py index b75b365834..58a18ea688 100644 --- a/demonstrations/tutorial_doubly_stochastic.py +++ b/demonstrations/tutorial_doubly_stochastic.py @@ -1,407 +1,407 @@ -r""" -Doubly stochastic gradient descent -================================== - -.. meta:: - :property="og:description": Minimize a Hamiltonian via an adaptive shot optimization - strategy with doubly stochastic gradient descent. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/single_shot.png - -.. related:: - - tutorial_backprop Quantum gradients with backpropagation - tutorial_quantum_natural_gradient Quantum natural gradient - tutorial_rosalin Frugal shot optimization with Rosalin - -*Author: Josh Izaac — Posted: 16 October 2019. Last updated: 20 January 2021.* - -In this tutorial we investigate and implement the doubly stochastic gradient descent -paper from `Ryan Sweke et al. (2019) `__. In this paper, -it is shown that quantum gradient descent, where a finite number of measurement samples -(or *shots*) are used to estimate the gradient, is a form of stochastic gradient descent. -Furthermore, if the optimization involves a linear combination of expectation values -(such as VQE), sampling from the terms in this linear combination can further reduce required -resources, allowing for "doubly stochastic gradient descent". - -Note that based on very similar observations, `Jonas Kuebler et al. (2019) `_ -recently proposed an optimizer (which they call the *individual Coupled Adaptive -Number of Shots (iCANS)* optimizer) that adapts the shot number of -measurements during training. - -Background ----------- - -In classical machine learning, `stochastic gradient descent -`_ is a common optimization strategy -where the standard gradient descent parameter update rule, - -.. math:: \theta^{(t+1)} = \theta^{(t)} - \eta \nabla \mathcal{L}(\theta^{(t)}), - -is modified such that - -.. math:: \theta^{(t+1)} = \theta^{(t)} - \eta g^{(t)}(\theta^{(t)}) - -where :math:`\eta` is the step-size, and :math:`\{g^{(t)}(\theta)\}` is a sequence of random -variables such that - -.. math:: \mathbb{E}[g^{(t)}(\theta)] = \nabla\mathcal{L}(\theta). - -In general, stochastic gradient descent is preferred over standard gradient -descent for several reasons: - -1. Samples of the gradient estimator :math:`g^{(t)}(\theta)` can typically - be computed much more efficiently than :math:`\mathcal{L}(\theta),` - -2. Stochasticity can help to avoid local minima and saddle points, - -3. Numerical evidence shows that convergence properties are superior to regular gradient descent. - -In variational quantum algorithms, a parametrized quantum circuit :math:`U(\theta)` -is optimized by a classical optimization loop in order to minimize a function of the expectation -values. For example, consider the expectation values - -.. math:: \langle A_i \rangle = \langle 0 | U(\theta)^\dagger A_i U(\theta) | 0\rangle - -for a set of observables :math:`\{A_i\},` and loss function - -.. math:: \mathcal{L}(\theta, \langle A_1 \rangle, \dots, \langle A_M \rangle). - -While the expectation values can be calculated analytically in classical simulations, -on quantum hardware we are limited to *sampling* from the expectation values; as the -number of samples (or shots) increase, we converge on the analytic expectation value, but can -never recover the exact expression. Furthermore, the parameter-shift rule -(`Schuld et al., 2018 `__) allows for analytic -quantum gradients to be computed from a linear combination of the variational circuits' -expectation values. - -Putting these two results together, `Sweke et al. (2019) `__ -show that samples of the expectation value fed into the parameter-shift rule provide -unbiased estimators of the quantum gradient—resulting in a form of stochastic gradient descent -(referred to as QSGD). Moreover, they show that convergence of the stochastic gradient -descent is guaranteed in sufficiently simplified settings, even in the case where the number -of shots is 1! - -.. note:: - - It is worth noting that the smaller the number of shots used, the larger the - variance in the estimated expectation value. As a result, it may take - more optimization steps for convergence than using a larger number of shots, - or an exact value. - - At the same time, a reduced number of shots may significantly reduce the - wall time of each optimization step, leading to a reduction in the overall - optimization time. - -""" - -############################################################################## -# Let's consider a simple example in PennyLane, comparing analytic gradient -# descent (with exact expectation values) to stochastic gradient descent -# using a finite number of shots. -# -# A single-shot stochastic gradient descent -# ----------------------------------------- -# -# Consider the Hamiltonian -# -# .. math:: -# -# H = \begin{bmatrix} -# 8 & 4 & 0 & -6\\ -# 4 & 0 & 4 & 0\\ -# 0 & 4 & 8 & 0\\ -# -6 & 0 & 0 & 0 -# \end{bmatrix}. -# -# We can solve for the ground state energy using -# the variational quantum eigensolver (VQE) algorithm. -# -# Let's use the ``lightning.qubit`` simulator for both the analytic gradient, -# as well as the estimated gradient using number of shots :math:`N\in\{1, 100\}.` - -import pennylane as qml -import numpy as np -from pennylane import numpy as pnp - -np.random.seed(3) - -from pennylane import expval -from pennylane.templates.layers import StronglyEntanglingLayers - -num_layers = 2 -num_wires = 2 -eta = 0.01 -steps = 200 - -dev_analytic = qml.device("lightning.qubit", wires=num_wires, shots=None) -dev_stochastic = qml.device("lightning.qubit", wires=num_wires, shots=1000) - -############################################################################## -# We can use ``qml.Hermitian`` to directly specify that we want to measure -# the expectation value of the matrix :math:`H:` - -H = np.array([[8, 4, 0, -6], [4, 0, 4, 0], [0, 4, 8, 0], [-6, 0, 0, 0]]) - - -def circuit(params): - StronglyEntanglingLayers(weights=params, wires=[0, 1]) - return expval(qml.Hermitian(H, wires=[0, 1])) - - -############################################################################## -# Now, we create three QNodes, each corresponding to a device above, -# and optimize them using gradient descent via the parameter-shift rule. - -qnode_analytic = qml.QNode(circuit, dev_analytic, interface="autograd", diff_method="parameter-shift") -qnode_stochastic = qml.QNode(circuit, dev_stochastic, interface="autograd", diff_method="parameter-shift") - -param_shape = StronglyEntanglingLayers.shape(n_layers=num_layers, n_wires=num_wires) -init_params = pnp.random.uniform(low=0, high=2 * np.pi, size=param_shape, requires_grad=True) - -# Optimizing using exact gradient descent - -cost_GD = [] -params_GD = init_params -opt = qml.GradientDescentOptimizer(eta) - -for _ in range(steps): - cost_GD.append(qnode_analytic(params_GD)) - params_GD = opt.step(qnode_analytic, params_GD) - -# Optimizing using stochastic gradient descent with shots=1 - -cost_SGD1 = [] -params_SGD1 = init_params -opt = qml.GradientDescentOptimizer(eta) - -for _ in range(steps): - cost_SGD1.append(qnode_stochastic(params_SGD1, shots=1)) - params_SGD1 = opt.step(qnode_stochastic, params_SGD1, shots=1) - -# Optimizing using stochastic gradient descent with shots=100 - -cost_SGD100 = [] -params_SGD100 = init_params -opt = qml.GradientDescentOptimizer(eta) - -for _ in range(steps): - cost_SGD100.append(qnode_stochastic(params_SGD100, shots=100)) - params_SGD100 = opt.step(qnode_stochastic, params_SGD100, shots=100) - -############################################################################## -# Note that in the latter two cases we are sampling from an unbiased -# estimator of the cost function, not the analytic cost function. -# -# To track optimization convergence, approaches could include: -# -# * Evaluating the cost function with a larger number of samples at specified -# intervals, -# -# * Keeping track of the *moving average* of the low-shot cost evaluations. -# -# We can now plot the cost against optimization step for the three cases above. - -from matplotlib import pyplot as plt - -plt.style.use("seaborn") -plt.plot(cost_GD[:100], label="Vanilla gradient descent") -plt.plot(cost_SGD100[:100], "--", label="QSGD (100 shots)") -plt.plot(cost_SGD1[:100], ".", label="QSGD (1 shot)") - -# analytic ground state -min_energy = min(np.linalg.eigvalsh(H)) -plt.hlines(min_energy, 0, 100, linestyles=":", label="Ground-state energy") - -plt.ylabel("Cost function value") -plt.xlabel("Optimization steps") -plt.legend() -plt.show() - -############################################################################## -# Using the trained parameters from each optimization strategy, we can -# evaluate the analytic quantum device: - -print("Vanilla gradient descent min energy = ", qnode_analytic(params_GD)) -print( - "Stochastic gradient descent (shots=100) min energy = ", - qnode_analytic(params_SGD100), -) -print( - "Stochastic gradient descent (shots=1) min energy = ", qnode_analytic(params_SGD1) -) - - -############################################################################## -# Amazingly, we see that even the ``shots=1`` optimization converged -# to a reasonably close approximation of the ground-state energy! - -############################################################################## -# Doubly stochastic gradient descent for VQE -# ------------------------------------------ -# -# As noted in `Sweke et al. (2019) `__, -# variational quantum algorithms often include terms consisting of linear combinations -# of expectation values. This is true of the parameter-shift rule (where the -# gradient of each parameter is determined by shifting the parameter by macroscopic -# amounts and taking the difference), as well as VQE, where the Hamiltonian -# is usually decomposed into a sum of Pauli expectation values. -# -# Consider the Hamiltonian from the previous section. As this Hamiltonian is a -# Hermitian observable, we can always express it as a sum of Pauli matrices using -# the relation -# -# .. math:: -# -# H = \sum_{i,j=0,1,2,3} a_{i,j} (\sigma_i\otimes \sigma_j), -# -# where -# -# .. math:: -# -# a_{i,j} = \frac{1}{4}\text{tr}[(\sigma_i\otimes \sigma_j )H], ~~ \sigma = \{I, X, Y, Z\}. -# -# Applying this, we can see that -# -# .. math:: -# -# H = 4 + 2I\otimes X + 4I \otimes Z - X\otimes X + 5 Y\otimes Y + 2Z\otimes X. -# -# To perform "doubly stochastic" gradient descent, we simply apply the stochastic -# gradient descent approach from above, but in addition also uniformly sample -# a subset of the terms for the Hamiltonian expectation at each optimization step. -# This inserts another element of stochasticity into the system—all the while -# convergence continues to be guaranteed! -# -# Let's create a QNode that randomly samples a single term from the above -# Hamiltonian as the observable to be measured. - -I = np.identity(2) -X = np.array([[0, 1], [1, 0]]) -Y = np.array([[0, -1j], [1j, 0]]) -Z = np.array([[1, 0], [0, -1]]) - -terms = np.array( - [ - 2 * np.kron(I, X), - 4 * np.kron(I, Z), - -np.kron(X, X), - 5 * np.kron(Y, Y), - 2 * np.kron(Z, X), - ] -) - - -@qml.qnode(dev_stochastic, interface="autograd") -def circuit(params, n=None): - StronglyEntanglingLayers(weights=params, wires=[0, 1]) - idx = np.random.choice(np.arange(5), size=n, replace=False) - A = np.sum(terms[idx], axis=0) - return expval(qml.Hermitian(A, wires=[0, 1])) - - -def loss(params, shots=None): - return 4 + (5 / 1) * circuit(params, shots=shots, n=1) - - -############################################################################## -# Optimizing the circuit using gradient descent via the parameter-shift rule: - -cost = [] -params = init_params -opt = qml.GradientDescentOptimizer(0.005) - -for _ in range(250): - cost.append(loss(params, shots=100)) - params = opt.step(loss, params, shots=100) - - -############################################################################## -# During doubly stochastic gradient descent, we are sampling from terms of the -# analytic cost function, so it is not entirely instructive to plot the cost -# versus optimization step—partial sums of the terms in the Hamiltonian -# may have minimum energy below the ground state energy of the total Hamiltonian. -# Nevertheless, we can keep track of the cost value moving average during doubly -# stochastic gradient descent as an indicator of convergence. - - -def moving_average(data, n=3): - ret = np.cumsum(data, dtype=np.float64) - ret[n:] = ret[n:] - ret[:-n] - return ret[n - 1:] / n - - -average = np.vstack([np.arange(25, 200), moving_average(cost, n=50)[:-26]]) - -plt.plot(cost_GD, label="Vanilla gradient descent") -plt.plot(cost, ".", label="Doubly QSGD") -plt.plot(average[0], average[1], "--", label="Doubly QSGD (moving average)") -plt.hlines(min_energy, 0, 200, linestyles=":", label="Ground state energy") - -plt.ylabel("Cost function value") -plt.xlabel("Optimization steps") -plt.xlim(-2, 200) -plt.legend() -plt.show() - -############################################################################## -# Finally, verifying that the doubly stochastic gradient descent optimization -# correctly provides the ground state energy when evaluated for a larger -# number of shots: - -print("Doubly stochastic gradient descent min energy = ", qnode_analytic(params)) - -############################################################################## -# While stochastic gradient descent requires more optimization steps to achieve -# convergence, it is worth noting that it requires significantly fewer quantum -# device evaluations, and thus may as a result take less time overall. - -############################################################################## -# Adaptive stochasticity -# ---------------------- -# -# To improve on the convergence, we may even consider a crude "adaptive" modification -# of the doubly stochastic gradient descent optimization performed above. In this -# approach, we successively increase the number of terms we are sampling from as -# the optimization proceeds, as well as increasing the number of shots. - -cost = [] -params = init_params -opt = qml.GradientDescentOptimizer(0.005) - -for i in range(250): - n = min(i // 25 + 1, 5) - - def loss(params, shots=None): - return 4 + (5 / n) * circuit(params, shots=shots, n=n) - - cost.append(loss(params, shots=int(1 + (n - 1) ** 2))) - params = opt.step(loss, params, shots=int(1 + (n - 1) ** 2)) - -average = np.vstack([np.arange(25, 200), moving_average(cost, n=50)[:-26]]) - -plt.plot(cost_GD, label="Vanilla gradient descent") -plt.plot(cost, ".", label="Adaptive QSGD") -plt.plot(average[0], average[1], "--", label="Adaptive QSGD (moving average)") -plt.hlines(min_energy, 0, 250, linestyles=":", label="Ground state energy") - -plt.ylabel("Cost function value") -plt.xlabel("Optimization steps") -plt.xlim(-2, 200) -plt.legend() -plt.show() - -print("Adaptive QSGD min energy = ", qnode_analytic(params)) - -############################################################################## -# References -# ---------- -# -# 1. Ryan Sweke, Frederik Wilde, Johannes Jakob Meyer, Maria Schuld, Paul K. Fährmann, -# Barthélémy Meynard-Piganeau, Jens Eisert. "Stochastic gradient descent for -# hybrid quantum-classical optimization." `arXiv:1910.01155 -# `__, 2019. -# -# -# About the author -# ---------------- +r""" +Doubly stochastic gradient descent +================================== + +.. meta:: + :property="og:description": Minimize a Hamiltonian via an adaptive shot optimization + strategy with doubly stochastic gradient descent. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/single_shot.png + +.. related:: + + tutorial_backprop Quantum gradients with backpropagation + tutorial_quantum_natural_gradient Quantum natural gradient + tutorial_rosalin Frugal shot optimization with Rosalin + +*Author: Josh Izaac — Posted: 16 October 2019. Last updated: 20 January 2021.* + +In this tutorial we investigate and implement the doubly stochastic gradient descent +paper from `Ryan Sweke et al. (2019) `__. In this paper, +it is shown that quantum gradient descent, where a finite number of measurement samples +(or *shots*) are used to estimate the gradient, is a form of stochastic gradient descent. +Furthermore, if the optimization involves a linear combination of expectation values +(such as VQE), sampling from the terms in this linear combination can further reduce required +resources, allowing for "doubly stochastic gradient descent". + +Note that based on very similar observations, `Jonas Kuebler et al. (2019) `_ +recently proposed an optimizer (which they call the *individual Coupled Adaptive +Number of Shots (iCANS)* optimizer) that adapts the shot number of +measurements during training. + +Background +---------- + +In classical machine learning, `stochastic gradient descent +`_ is a common optimization strategy +where the standard gradient descent parameter update rule, + +.. math:: \theta^{(t+1)} = \theta^{(t)} - \eta \nabla \mathcal{L}(\theta^{(t)}), + +is modified such that + +.. math:: \theta^{(t+1)} = \theta^{(t)} - \eta g^{(t)}(\theta^{(t)}) + +where :math:`\eta` is the step-size, and :math:`\{g^{(t)}(\theta)\}` is a sequence of random +variables such that + +.. math:: \mathbb{E}[g^{(t)}(\theta)] = \nabla\mathcal{L}(\theta). + +In general, stochastic gradient descent is preferred over standard gradient +descent for several reasons: + +1. Samples of the gradient estimator :math:`g^{(t)}(\theta)` can typically + be computed much more efficiently than :math:`\mathcal{L}(\theta),` + +2. Stochasticity can help to avoid local minima and saddle points, + +3. Numerical evidence shows that convergence properties are superior to regular gradient descent. + +In variational quantum algorithms, a parametrized quantum circuit :math:`U(\theta)` +is optimized by a classical optimization loop in order to minimize a function of the expectation +values. For example, consider the expectation values + +.. math:: \langle A_i \rangle = \langle 0 | U(\theta)^\dagger A_i U(\theta) | 0\rangle + +for a set of observables :math:`\{A_i\},` and loss function + +.. math:: \mathcal{L}(\theta, \langle A_1 \rangle, \dots, \langle A_M \rangle). + +While the expectation values can be calculated analytically in classical simulations, +on quantum hardware we are limited to *sampling* from the expectation values; as the +number of samples (or shots) increase, we converge on the analytic expectation value, but can +never recover the exact expression. Furthermore, the parameter-shift rule +(`Schuld et al., 2018 `__) allows for analytic +quantum gradients to be computed from a linear combination of the variational circuits' +expectation values. + +Putting these two results together, `Sweke et al. (2019) `__ +show that samples of the expectation value fed into the parameter-shift rule provide +unbiased estimators of the quantum gradient—resulting in a form of stochastic gradient descent +(referred to as QSGD). Moreover, they show that convergence of the stochastic gradient +descent is guaranteed in sufficiently simplified settings, even in the case where the number +of shots is 1! + +.. note:: + + It is worth noting that the smaller the number of shots used, the larger the + variance in the estimated expectation value. As a result, it may take + more optimization steps for convergence than using a larger number of shots, + or an exact value. + + At the same time, a reduced number of shots may significantly reduce the + wall time of each optimization step, leading to a reduction in the overall + optimization time. + +""" + +############################################################################## +# Let's consider a simple example in PennyLane, comparing analytic gradient +# descent (with exact expectation values) to stochastic gradient descent +# using a finite number of shots. +# +# A single-shot stochastic gradient descent +# ----------------------------------------- +# +# Consider the Hamiltonian +# +# .. math:: +# +# H = \begin{bmatrix} +# 8 & 4 & 0 & -6\\ +# 4 & 0 & 4 & 0\\ +# 0 & 4 & 8 & 0\\ +# -6 & 0 & 0 & 0 +# \end{bmatrix}. +# +# We can solve for the ground state energy using +# the variational quantum eigensolver (VQE) algorithm. +# +# Let's use the ``lightning.qubit`` simulator for both the analytic gradient, +# as well as the estimated gradient using number of shots :math:`N\in\{1, 100\}.` + +import pennylane as qml +import numpy as np +from pennylane import numpy as pnp + +np.random.seed(3) + +from pennylane import expval +from pennylane.templates.layers import StronglyEntanglingLayers + +num_layers = 2 +num_wires = 2 +eta = 0.01 +steps = 200 + +dev_analytic = qml.device("lightning.qubit", wires=num_wires, shots=None) +dev_stochastic = qml.device("lightning.qubit", wires=num_wires, shots=1000) + +############################################################################## +# We can use ``qml.Hermitian`` to directly specify that we want to measure +# the expectation value of the matrix :math:`H:` + +H = np.array([[8, 4, 0, -6], [4, 0, 4, 0], [0, 4, 8, 0], [-6, 0, 0, 0]]) + + +def circuit(params): + StronglyEntanglingLayers(weights=params, wires=[0, 1]) + return expval(qml.Hermitian(H, wires=[0, 1])) + + +############################################################################## +# Now, we create three QNodes, each corresponding to a device above, +# and optimize them using gradient descent via the parameter-shift rule. + +qnode_analytic = qml.QNode(circuit, dev_analytic, interface="autograd", diff_method="parameter-shift") +qnode_stochastic = qml.QNode(circuit, dev_stochastic, interface="autograd", diff_method="parameter-shift") + +param_shape = StronglyEntanglingLayers.shape(n_layers=num_layers, n_wires=num_wires) +init_params = pnp.random.uniform(low=0, high=2 * np.pi, size=param_shape, requires_grad=True) + +# Optimizing using exact gradient descent + +cost_GD = [] +params_GD = init_params +opt = qml.GradientDescentOptimizer(eta) + +for _ in range(steps): + cost_GD.append(qnode_analytic(params_GD)) + params_GD = opt.step(qnode_analytic, params_GD) + +# Optimizing using stochastic gradient descent with shots=1 + +cost_SGD1 = [] +params_SGD1 = init_params +opt = qml.GradientDescentOptimizer(eta) + +for _ in range(steps): + cost_SGD1.append(qnode_stochastic(params_SGD1, shots=1)) + params_SGD1 = opt.step(qnode_stochastic, params_SGD1, shots=1) + +# Optimizing using stochastic gradient descent with shots=100 + +cost_SGD100 = [] +params_SGD100 = init_params +opt = qml.GradientDescentOptimizer(eta) + +for _ in range(steps): + cost_SGD100.append(qnode_stochastic(params_SGD100, shots=100)) + params_SGD100 = opt.step(qnode_stochastic, params_SGD100, shots=100) + +############################################################################## +# Note that in the latter two cases we are sampling from an unbiased +# estimator of the cost function, not the analytic cost function. +# +# To track optimization convergence, approaches could include: +# +# * Evaluating the cost function with a larger number of samples at specified +# intervals, +# +# * Keeping track of the *moving average* of the low-shot cost evaluations. +# +# We can now plot the cost against optimization step for the three cases above. + +from matplotlib import pyplot as plt + +plt.style.use("seaborn") +plt.plot(cost_GD[:100], label="Vanilla gradient descent") +plt.plot(cost_SGD100[:100], "--", label="QSGD (100 shots)") +plt.plot(cost_SGD1[:100], ".", label="QSGD (1 shot)") + +# analytic ground state +min_energy = min(np.linalg.eigvalsh(H)) +plt.hlines(min_energy, 0, 100, linestyles=":", label="Ground-state energy") + +plt.ylabel("Cost function value") +plt.xlabel("Optimization steps") +plt.legend() +plt.show() + +############################################################################## +# Using the trained parameters from each optimization strategy, we can +# evaluate the analytic quantum device: + +print("Vanilla gradient descent min energy = ", qnode_analytic(params_GD)) +print( + "Stochastic gradient descent (shots=100) min energy = ", + qnode_analytic(params_SGD100), +) +print( + "Stochastic gradient descent (shots=1) min energy = ", qnode_analytic(params_SGD1) +) + + +############################################################################## +# Amazingly, we see that even the ``shots=1`` optimization converged +# to a reasonably close approximation of the ground-state energy! + +############################################################################## +# Doubly stochastic gradient descent for VQE +# ------------------------------------------ +# +# As noted in `Sweke et al. (2019) `__, +# variational quantum algorithms often include terms consisting of linear combinations +# of expectation values. This is true of the parameter-shift rule (where the +# gradient of each parameter is determined by shifting the parameter by macroscopic +# amounts and taking the difference), as well as VQE, where the Hamiltonian +# is usually decomposed into a sum of Pauli expectation values. +# +# Consider the Hamiltonian from the previous section. As this Hamiltonian is a +# Hermitian observable, we can always express it as a sum of Pauli matrices using +# the relation +# +# .. math:: +# +# H = \sum_{i,j=0,1,2,3} a_{i,j} (\sigma_i\otimes \sigma_j), +# +# where +# +# .. math:: +# +# a_{i,j} = \frac{1}{4}\text{tr}[(\sigma_i\otimes \sigma_j )H], ~~ \sigma = \{I, X, Y, Z\}. +# +# Applying this, we can see that +# +# .. math:: +# +# H = 4 + 2I\otimes X + 4I \otimes Z - X\otimes X + 5 Y\otimes Y + 2Z\otimes X. +# +# To perform "doubly stochastic" gradient descent, we simply apply the stochastic +# gradient descent approach from above, but in addition also uniformly sample +# a subset of the terms for the Hamiltonian expectation at each optimization step. +# This inserts another element of stochasticity into the system—all the while +# convergence continues to be guaranteed! +# +# Let's create a QNode that randomly samples a single term from the above +# Hamiltonian as the observable to be measured. + +I = np.identity(2) +X = np.array([[0, 1], [1, 0]]) +Y = np.array([[0, -1j], [1j, 0]]) +Z = np.array([[1, 0], [0, -1]]) + +terms = np.array( + [ + 2 * np.kron(I, X), + 4 * np.kron(I, Z), + -np.kron(X, X), + 5 * np.kron(Y, Y), + 2 * np.kron(Z, X), + ] +) + + +@qml.qnode(dev_stochastic, interface="autograd") +def circuit(params, n=None): + StronglyEntanglingLayers(weights=params, wires=[0, 1]) + idx = np.random.choice(np.arange(5), size=n, replace=False) + A = np.sum(terms[idx], axis=0) + return expval(qml.Hermitian(A, wires=[0, 1])) + + +def loss(params, shots=None): + return 4 + (5 / 1) * circuit(params, shots=shots, n=1) + + +############################################################################## +# Optimizing the circuit using gradient descent via the parameter-shift rule: + +cost = [] +params = init_params +opt = qml.GradientDescentOptimizer(0.005) + +for _ in range(250): + cost.append(loss(params, shots=100)) + params = opt.step(loss, params, shots=100) + + +############################################################################## +# During doubly stochastic gradient descent, we are sampling from terms of the +# analytic cost function, so it is not entirely instructive to plot the cost +# versus optimization step—partial sums of the terms in the Hamiltonian +# may have minimum energy below the ground state energy of the total Hamiltonian. +# Nevertheless, we can keep track of the cost value moving average during doubly +# stochastic gradient descent as an indicator of convergence. + + +def moving_average(data, n=3): + ret = np.cumsum(data, dtype=np.float64) + ret[n:] = ret[n:] - ret[:-n] + return ret[n - 1:] / n + + +average = np.vstack([np.arange(25, 200), moving_average(cost, n=50)[:-26]]) + +plt.plot(cost_GD, label="Vanilla gradient descent") +plt.plot(cost, ".", label="Doubly QSGD") +plt.plot(average[0], average[1], "--", label="Doubly QSGD (moving average)") +plt.hlines(min_energy, 0, 200, linestyles=":", label="Ground state energy") + +plt.ylabel("Cost function value") +plt.xlabel("Optimization steps") +plt.xlim(-2, 200) +plt.legend() +plt.show() + +############################################################################## +# Finally, verifying that the doubly stochastic gradient descent optimization +# correctly provides the ground state energy when evaluated for a larger +# number of shots: + +print("Doubly stochastic gradient descent min energy = ", qnode_analytic(params)) + +############################################################################## +# While stochastic gradient descent requires more optimization steps to achieve +# convergence, it is worth noting that it requires significantly fewer quantum +# device evaluations, and thus may as a result take less time overall. + +############################################################################## +# Adaptive stochasticity +# ---------------------- +# +# To improve on the convergence, we may even consider a crude "adaptive" modification +# of the doubly stochastic gradient descent optimization performed above. In this +# approach, we successively increase the number of terms we are sampling from as +# the optimization proceeds, as well as increasing the number of shots. + +cost = [] +params = init_params +opt = qml.GradientDescentOptimizer(0.005) + +for i in range(250): + n = min(i // 25 + 1, 5) + + def loss(params, shots=None): + return 4 + (5 / n) * circuit(params, shots=shots, n=n) + + cost.append(loss(params, shots=int(1 + (n - 1) ** 2))) + params = opt.step(loss, params, shots=int(1 + (n - 1) ** 2)) + +average = np.vstack([np.arange(25, 200), moving_average(cost, n=50)[:-26]]) + +plt.plot(cost_GD, label="Vanilla gradient descent") +plt.plot(cost, ".", label="Adaptive QSGD") +plt.plot(average[0], average[1], "--", label="Adaptive QSGD (moving average)") +plt.hlines(min_energy, 0, 250, linestyles=":", label="Ground state energy") + +plt.ylabel("Cost function value") +plt.xlabel("Optimization steps") +plt.xlim(-2, 200) +plt.legend() +plt.show() + +print("Adaptive QSGD min energy = ", qnode_analytic(params)) + +############################################################################## +# References +# ---------- +# +# 1. Ryan Sweke, Frederik Wilde, Johannes Jakob Meyer, Maria Schuld, Paul K. Fährmann, +# Barthélémy Meynard-Piganeau, Jens Eisert. "Stochastic gradient descent for +# hybrid quantum-classical optimization." `arXiv:1910.01155 +# `__, 2019. +# +# +# About the author +# ---------------- # \ No newline at end of file diff --git a/demonstrations/tutorial_fermionic_operators.py b/demonstrations/tutorial_fermionic_operators.py index d06f98aafb..673c5a705a 100644 --- a/demonstrations/tutorial_fermionic_operators.py +++ b/demonstrations/tutorial_fermionic_operators.py @@ -1,231 +1,231 @@ -r""" - -Fermionic operators -=================== - -.. meta:: - :property="og:description": Learn how to work with fermionic operators - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_fermionic_operators.png - -.. related:: - tutorial_quantum_chemistry Building molecular Hamiltonians - tutorial_vqe A brief overview of VQE - -*Author: Soran Jahangiri — Posted: 27 June 2023. Last updated: 27 June 2023.* - -Fermionic creation and annihilation operators are commonly used to construct -`Hamiltonians `_ and other observables of molecules and spin -systems [#surjan]_. In this demo, you will learn how to use PennyLane to create fermionic operators -and map them to a qubit representation for use in quantum algorithms. - -Constructing fermionic operators --------------------------------- -The fermionic `creation and annihilation `_ -operators can be constructed in PennyLane similarly to Pauli operators by using -:class:`~.pennylane.FermiC` and :class:`~.pennylane.FermiA` for creation and annihilation operators, -respectively. -""" - -from pennylane import FermiC, FermiA - -a0_dag = FermiC(0) -a1 = FermiA(1) - -############################################################################## -# We used the compact notations ``a0_dag`` to denote a creation operator applied to -# the :math:`0\text{th}` orbital and ``a1`` to denote an annihilation operator applied to the -# :math:`1\text{st}` orbital. Once created, these operators can be multiplied or added to each other -# to create new operators. A product of fermionic operators will be called a *Fermi word* and a -# linear combination of Fermi words will be called a *Fermi sentence*. - -fermi_word = a0_dag * a1 -fermi_sentence = 1.3 * a0_dag * a1 + 2.4 * a1 -print(fermi_sentence) - -############################################################################## -# In this simple example, we first created the operator :math:`a^{\dagger}_0 a_1` and then created -# the linear combination :math:`1.3 a^{\dagger}_0 a_1 + 2.4 a_1.` We can also perform -# arithmetic operations between Fermi words and Fermi sentences. - -fermi_sentence = fermi_sentence * fermi_word + 2.3 * fermi_word -print(fermi_sentence) - -############################################################################## -# Beyond multiplication, summation, and subtraction, we can exponentiate fermionic operators in -# PennyLane to an integer power. For instance, we can create a more complicated operator -# -# .. math:: -# -# 1.2 \times a_0^{\dagger} + 0.5 \times a_1 - 2.3 \times \left ( a_0^{\dagger} a_1 \right )^2, -# -# in the same way that you would write down the operator on a piece of paper: - -fermi_sentence = 1.2 * a0_dag + 0.5 * a1 - 2.3 * (a0_dag * a1) ** 2 -print(fermi_sentence) - -############################################################################## -# This Fermi sentence can be mapped to the qubit basis using the -# `Jordan-Wigner `_ -# transformation to get a linear combination of Pauli operators. - -from pennylane import jordan_wigner - -pauli_sentence = jordan_wigner(fermi_sentence) -pauli_sentence - -############################################################################## -# Fermionic Hamiltonians -# ---------------------- -# Now that we have nice tools to create and manipulate fermionic operators, we can build some -# interesting fermionic Hamiltonians. -# -# A toy model -# ^^^^^^^^^^^ -# Our first example is a toy Hamiltonian inspired by the -# `Hückel method `_, which is a method for -# describing molecules with alternating single and double bonds. Our toy model is a simplified -# version of the Hückel Hamiltonian and assumes only two orbitals and a single electron. -# -# .. math:: -# -# H = \alpha \left (a^{\dagger}_0 a_0 + a^{\dagger}_1 a_1 \right ) + -# \beta \left (a^{\dagger}_0 a_1 + a^{\dagger}_1 a_0 \right ). -# -# This Hamiltonian can be constructed with pre-defined values :math:`\alpha = 0.01` and -# :math:`\beta = -0.02.` - -h1 = 0.01 * (FermiC(0) * FermiA(0) + FermiC(1) * FermiA(1)) -h2 = -0.02 * (FermiC(0) * FermiA(1) + FermiC(1) * FermiA(0)) -h = h1 + h2 -print(h) - -############################################################################## -# The fermionic Hamiltonian can be converted to the qubit Hamiltonian with: - -h = jordan_wigner(h) - -############################################################################## -# The matrix representation of the qubit Hamiltonian in the computational basis can be diagonalized -# to get its eigenpairs. - -import numpy as np - -val, vec = np.linalg.eigh(h.sparse_matrix().toarray()) -print(f"eigenvalues:\n{val}") -print() -print(f"eigenvectors:\n{np.real(vec.T)}") - -############################################################################## -# -# Hydrogen molecule -# ^^^^^^^^^^^^^^^^^ -# The `second quantized `_ molecular electronic -# Hamiltonian is usually constructed as -# -# .. math:: -# H = \sum_{\alpha \in \{\uparrow, \downarrow \} } \sum_{pq} c_{pq} a_{p,\alpha}^{\dagger} -# a_{q, \alpha} + \frac{1}{2} \sum_{\alpha, \beta \in \{\uparrow, \downarrow \} } \sum_{pqrs} -# c_{pqrs} a_{p, \alpha}^{\dagger} a_{q, \beta}^{\dagger} a_{r, \beta} a_{s, \alpha}, -# -# where :math:`\alpha` and :math:`\beta` denote the electron spin and :math:`p, q, r, s` are the -# orbital indices. The coefficients :math:`c` are integrals over -# molecular orbitals that are obtained from -# `Hartree-Fock `_ -# calculations. These integrals can be computed with PennyLane using the -# :func:`~.pennylane.qchem.electron_integrals` function. We can build the molecular Hamiltonian for -# the hydrogen molecule as an example. We first define the atom types and the atomic coordinates. - -import pennylane as qml -from jax import numpy as jnp - -symbols = ["H", "H"] -geometry = jnp.array([[-0.67294, 0.0, 0.0], [0.67294, 0.0, 0.0]]) - -############################################################################## -# Then we compute the one- and two-electron integrals, which are the coefficients :math:`c` in the -# second quantized molecular Hamiltonian defined above. We also obtain the core constant, which is -# later used to calculate the contribution of the nuclear energy to the Hamiltonian. - -mol = qml.qchem.Molecule(symbols, geometry) -core, one, two = qml.qchem.electron_integrals(mol)() - -############################################################################## -# These integrals are computed over molecular orbitals. Each molecular orbital contains a pair of -# electrons with different spins. We have assumed that the spatial distribution of these electron -# pairs is the same to simplify the calculation of the integrals. However, to properly account for -# all electrons, we need to duplicate the integrals for electrons with the same spin. For example, -# the :math:`pq` integral, which is the integral over the orbital :math:`p` and the orbital -# :math:`q,` can be used -# for both spin-up and spin-down electrons. Then, if we have a :math:`2 \times 2` matrix of such -# integrals, it will become a :math:`4 \times 4` matrix. The code block below simply extends the -# integrals by duplicating terms to account for both spin-up and spin-down electrons. - -for i in range(4): - if i < 2: - one = one.repeat(2, axis=i) - two = two.repeat(2, axis=i) - -############################################################################## -# We can now construct the fermionic Hamiltonian for the hydrogen molecule. The one-body terms, -# which are the first part in the Hamiltonian above, can be added first. We will use -# `itertools `_ to efficiently -# create all the combinations we need. Some of these combinations are not allowed because of spin -# restrictions and we need to exclude them. You can find more details about -# constructing a molecular Hamiltonian in reference [#surjan]_. - -import itertools - -n = one.shape[0] - -h = 0.0 - -for p, q in itertools.product(range(n), repeat=2): - if p % 2 == q % 2: # to account for spin-forbidden terms - h += one[p, q] * FermiC(p) * FermiA(q) - -############################################################################## -# The two-body terms can be added with: - -for p, q, r, s in itertools.product(range(n), repeat=4): - if p % 2 == s % 2 and q % 2 == r % 2: # to account for spin-forbidden terms - h += two[p, q, r, s] / 2 * FermiC(p) * FermiC(q) * FermiA(r) * FermiA(s) - -############################################################################## -# We then simplify the Hamiltonian to remove terms with negligible coefficients and then map it to -# the qubit basis. - -h.simplify() -h = jordan_wigner(h) - -############################################################################## -# We also need to include the contribution of the nuclear energy. - -h += np.sum(core * qml.Identity(0)) - -############################################################################## -# This gives us the qubit Hamiltonian which can be used as an input for quantum algorithms. We can -# also compute the ground-state energy by diagonalizing the matrix representation of the -# Hamiltonian in the computational basis. - -np.linalg.eigh(h.sparse_matrix().toarray())[0].min() - -############################################################################## -# Summary -# ------- -# This demo explains how to create and manipulate fermionic operators in PennyLane, which is as -# easy as writing the operators on paper. PennyLane supports several arithmetic operations between -# fermionic operators and provides tools for mapping them to the qubit basis. This makes it easy and -# intuitive to construct complicated fermionic Hamiltonians such as -# `molecular Hamiltonians `_. -# -# References -# ---------- -# -# .. [#surjan] -# -# Peter R. Surjan, "Second Quantized Approach to Quantum Chemistry". Springer-Verlag, 1989. -# -# -# About the author -# ---------------- -# +r""" + +Fermionic operators +=================== + +.. meta:: + :property="og:description": Learn how to work with fermionic operators + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_fermionic_operators.png + +.. related:: + tutorial_quantum_chemistry Building molecular Hamiltonians + tutorial_vqe A brief overview of VQE + +*Author: Soran Jahangiri — Posted: 27 June 2023. Last updated: 27 June 2023.* + +Fermionic creation and annihilation operators are commonly used to construct +`Hamiltonians `_ and other observables of molecules and spin +systems [#surjan]_. In this demo, you will learn how to use PennyLane to create fermionic operators +and map them to a qubit representation for use in quantum algorithms. + +Constructing fermionic operators +-------------------------------- +The fermionic `creation and annihilation `_ +operators can be constructed in PennyLane similarly to Pauli operators by using +:class:`~.pennylane.FermiC` and :class:`~.pennylane.FermiA` for creation and annihilation operators, +respectively. +""" + +from pennylane import FermiC, FermiA + +a0_dag = FermiC(0) +a1 = FermiA(1) + +############################################################################## +# We used the compact notations ``a0_dag`` to denote a creation operator applied to +# the :math:`0\text{th}` orbital and ``a1`` to denote an annihilation operator applied to the +# :math:`1\text{st}` orbital. Once created, these operators can be multiplied or added to each other +# to create new operators. A product of fermionic operators will be called a *Fermi word* and a +# linear combination of Fermi words will be called a *Fermi sentence*. + +fermi_word = a0_dag * a1 +fermi_sentence = 1.3 * a0_dag * a1 + 2.4 * a1 +print(fermi_sentence) + +############################################################################## +# In this simple example, we first created the operator :math:`a^{\dagger}_0 a_1` and then created +# the linear combination :math:`1.3 a^{\dagger}_0 a_1 + 2.4 a_1.` We can also perform +# arithmetic operations between Fermi words and Fermi sentences. + +fermi_sentence = fermi_sentence * fermi_word + 2.3 * fermi_word +print(fermi_sentence) + +############################################################################## +# Beyond multiplication, summation, and subtraction, we can exponentiate fermionic operators in +# PennyLane to an integer power. For instance, we can create a more complicated operator +# +# .. math:: +# +# 1.2 \times a_0^{\dagger} + 0.5 \times a_1 - 2.3 \times \left ( a_0^{\dagger} a_1 \right )^2, +# +# in the same way that you would write down the operator on a piece of paper: + +fermi_sentence = 1.2 * a0_dag + 0.5 * a1 - 2.3 * (a0_dag * a1) ** 2 +print(fermi_sentence) + +############################################################################## +# This Fermi sentence can be mapped to the qubit basis using the +# `Jordan-Wigner `_ +# transformation to get a linear combination of Pauli operators. + +from pennylane import jordan_wigner + +pauli_sentence = jordan_wigner(fermi_sentence) +pauli_sentence + +############################################################################## +# Fermionic Hamiltonians +# ---------------------- +# Now that we have nice tools to create and manipulate fermionic operators, we can build some +# interesting fermionic Hamiltonians. +# +# A toy model +# ^^^^^^^^^^^ +# Our first example is a toy Hamiltonian inspired by the +# `Hückel method `_, which is a method for +# describing molecules with alternating single and double bonds. Our toy model is a simplified +# version of the Hückel Hamiltonian and assumes only two orbitals and a single electron. +# +# .. math:: +# +# H = \alpha \left (a^{\dagger}_0 a_0 + a^{\dagger}_1 a_1 \right ) + +# \beta \left (a^{\dagger}_0 a_1 + a^{\dagger}_1 a_0 \right ). +# +# This Hamiltonian can be constructed with pre-defined values :math:`\alpha = 0.01` and +# :math:`\beta = -0.02.` + +h1 = 0.01 * (FermiC(0) * FermiA(0) + FermiC(1) * FermiA(1)) +h2 = -0.02 * (FermiC(0) * FermiA(1) + FermiC(1) * FermiA(0)) +h = h1 + h2 +print(h) + +############################################################################## +# The fermionic Hamiltonian can be converted to the qubit Hamiltonian with: + +h = jordan_wigner(h) + +############################################################################## +# The matrix representation of the qubit Hamiltonian in the computational basis can be diagonalized +# to get its eigenpairs. + +import numpy as np + +val, vec = np.linalg.eigh(h.sparse_matrix().toarray()) +print(f"eigenvalues:\n{val}") +print() +print(f"eigenvectors:\n{np.real(vec.T)}") + +############################################################################## +# +# Hydrogen molecule +# ^^^^^^^^^^^^^^^^^ +# The `second quantized `_ molecular electronic +# Hamiltonian is usually constructed as +# +# .. math:: +# H = \sum_{\alpha \in \{\uparrow, \downarrow \} } \sum_{pq} c_{pq} a_{p,\alpha}^{\dagger} +# a_{q, \alpha} + \frac{1}{2} \sum_{\alpha, \beta \in \{\uparrow, \downarrow \} } \sum_{pqrs} +# c_{pqrs} a_{p, \alpha}^{\dagger} a_{q, \beta}^{\dagger} a_{r, \beta} a_{s, \alpha}, +# +# where :math:`\alpha` and :math:`\beta` denote the electron spin and :math:`p, q, r, s` are the +# orbital indices. The coefficients :math:`c` are integrals over +# molecular orbitals that are obtained from +# `Hartree-Fock `_ +# calculations. These integrals can be computed with PennyLane using the +# :func:`~.pennylane.qchem.electron_integrals` function. We can build the molecular Hamiltonian for +# the hydrogen molecule as an example. We first define the atom types and the atomic coordinates. + +import pennylane as qml +from jax import numpy as jnp + +symbols = ["H", "H"] +geometry = jnp.array([[-0.67294, 0.0, 0.0], [0.67294, 0.0, 0.0]]) + +############################################################################## +# Then we compute the one- and two-electron integrals, which are the coefficients :math:`c` in the +# second quantized molecular Hamiltonian defined above. We also obtain the core constant, which is +# later used to calculate the contribution of the nuclear energy to the Hamiltonian. + +mol = qml.qchem.Molecule(symbols, geometry) +core, one, two = qml.qchem.electron_integrals(mol)() + +############################################################################## +# These integrals are computed over molecular orbitals. Each molecular orbital contains a pair of +# electrons with different spins. We have assumed that the spatial distribution of these electron +# pairs is the same to simplify the calculation of the integrals. However, to properly account for +# all electrons, we need to duplicate the integrals for electrons with the same spin. For example, +# the :math:`pq` integral, which is the integral over the orbital :math:`p` and the orbital +# :math:`q,` can be used +# for both spin-up and spin-down electrons. Then, if we have a :math:`2 \times 2` matrix of such +# integrals, it will become a :math:`4 \times 4` matrix. The code block below simply extends the +# integrals by duplicating terms to account for both spin-up and spin-down electrons. + +for i in range(4): + if i < 2: + one = one.repeat(2, axis=i) + two = two.repeat(2, axis=i) + +############################################################################## +# We can now construct the fermionic Hamiltonian for the hydrogen molecule. The one-body terms, +# which are the first part in the Hamiltonian above, can be added first. We will use +# `itertools `_ to efficiently +# create all the combinations we need. Some of these combinations are not allowed because of spin +# restrictions and we need to exclude them. You can find more details about +# constructing a molecular Hamiltonian in reference [#surjan]_. + +import itertools + +n = one.shape[0] + +h = 0.0 + +for p, q in itertools.product(range(n), repeat=2): + if p % 2 == q % 2: # to account for spin-forbidden terms + h += one[p, q] * FermiC(p) * FermiA(q) + +############################################################################## +# The two-body terms can be added with: + +for p, q, r, s in itertools.product(range(n), repeat=4): + if p % 2 == s % 2 and q % 2 == r % 2: # to account for spin-forbidden terms + h += two[p, q, r, s] / 2 * FermiC(p) * FermiC(q) * FermiA(r) * FermiA(s) + +############################################################################## +# We then simplify the Hamiltonian to remove terms with negligible coefficients and then map it to +# the qubit basis. + +h.simplify() +h = jordan_wigner(h) + +############################################################################## +# We also need to include the contribution of the nuclear energy. + +h += np.sum(core * qml.Identity(0)) + +############################################################################## +# This gives us the qubit Hamiltonian which can be used as an input for quantum algorithms. We can +# also compute the ground-state energy by diagonalizing the matrix representation of the +# Hamiltonian in the computational basis. + +np.linalg.eigh(h.sparse_matrix().toarray())[0].min() + +############################################################################## +# Summary +# ------- +# This demo explains how to create and manipulate fermionic operators in PennyLane, which is as +# easy as writing the operators on paper. PennyLane supports several arithmetic operations between +# fermionic operators and provides tools for mapping them to the qubit basis. This makes it easy and +# intuitive to construct complicated fermionic Hamiltonians such as +# `molecular Hamiltonians `_. +# +# References +# ---------- +# +# .. [#surjan] +# +# Peter R. Surjan, "Second Quantized Approach to Quantum Chemistry". Springer-Verlag, 1989. +# +# +# About the author +# ---------------- +# diff --git a/demonstrations/tutorial_givens_rotations.py b/demonstrations/tutorial_givens_rotations.py index 1c312bf402..9413006ffb 100644 --- a/demonstrations/tutorial_givens_rotations.py +++ b/demonstrations/tutorial_givens_rotations.py @@ -1,506 +1,506 @@ -r""" - -Givens rotations for quantum chemistry -====================================== - -.. meta:: - :property="og:description": Discover the building blocks of quantum circuits for - quantum chemistry - - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/Givens_rotations.png - -.. related:: - tutorial_quantum_chemistry Building molecular Hamiltonians - tutorial_vqe A brief overview of VQE - - -*Author: Juan Miguel Arrazola — Posted: 30 June 2021. Last updated: 30 June 2021.* - -In the book `"Sophie's world" `_, the young -protagonist receives a white envelope containing a letter -with an intriguing question: "Why is Lego the most ingenious toy in the world?" At first baffled by -this curious message, she decides to reflect on the question. As told by the book's narrator, -she arrives at a conclusion: - -*The best thing about them was that with Lego she could construct any kind of object. And then -she could separate the blocks and construct something new. What more could one ask of a toy? -Sophie decided that Lego really could be called the most ingenious toy in the world.* - - - -.. figure:: ../_static/demonstration_assets/givens_rotations/lego-circuit.svg - :align: center - :width: 50% - - -In this tutorial, you will learn about the building blocks of quantum circuits for quantum -chemistry: Givens rotations. These are operations that can be used to construct any kind of -particle-conserving circuit. We discuss single and double excitation gates, which are particular -types of Givens rotations that play an important role in quantum chemistry. Notably, -controlled single excitation gates are universal for particle-conserving unitaries. You will also -learn how to use these gates to build arbitrary states of a fixed number of particles. - -Particle-conserving unitaries ------------------------------ - -Understanding the electronic structure of molecules is arguably the central problem in quantum chemistry. -Quantum computers tackle this problem by using systems of qubits to -represent the quantum states of the electrons. One method is to consider a -collection of `molecular orbitals `_, which -capture the three-dimensional region of space occupied by the electrons. Each orbital can be -occupied by at most two electrons, each with a different spin orientation. In this case we refer to -*spin orbitals* that can be occupied by a single electron. - -The state of electrons in a molecule can then be described by specifying how the -orbitals are occupied. The `Jordan-Wigner representation -`_ provides a -convenient way to do this: we associate a qubit with each spin orbital and -use its states to represent occupied :math:`|1\rangle` or unoccupied -:math:`|0\rangle` spin orbitals. - -An :math:`n`-qubit state with `Hamming weight `_ -:math:`k`, i.e., with :math:`k` qubits in state :math:`|1\rangle,` represents a state of -:math:`k` electrons in :math:`n` spin orbitals. For example :math:`|1010\rangle` is a state of -two electrons in two spin orbitals. More generally, superpositions over all basis states with a -fixed number of particles are valid states of the electrons in a molecule. These are states such as - -.. math:: - - |\psi\rangle = c_1|1100\rangle + c_2|1010\rangle + c_3|1001\rangle + c_4|0110\rangle + - c_5|0101\rangle + c_6|0011\rangle, - -for some coefficients :math:`c_i.` - -.. figure:: ../_static/demonstration_assets/givens_rotations/orbitals_states.png - :align: center - :width: 50% - - States of a system with six spin orbitals and three electrons. Orbitals are for illustration; - they correspond to carbon dioxide, which has more electrons and orbitals. - -Because the number of electrons in a molecule is -fixed, any transformation must conserve the number of particles. We refer to these as -**particle-conserving unitaries**. When designing quantum circuits and algorithms for quantum -chemistry, it is desirable to employ only particle-conserving gates that guarantee that the -states of the system remain valid. This raises the questions: what are the simplest -particle-conserving unitaries? Like Legos, how can they be used to construct any quantum circuit for -quantum chemistry applications? - -Givens rotations ----------------- - -Consider single-qubit gates. In their most general form, they perform the transformation - -.. math:: - - U|0\rangle &= a |0\rangle + b |1\rangle,\\ - U|1\rangle &= c |1\rangle + d |0\rangle, - -where :math:`|a|^2+|b|^2=|c|^2+|d|^2=1` and :math:`ab^* + cd^*=0.` This gate is -particle-conserving only if :math:`b=d=0,` which means that the only single-qubit gates that -preserve particle number are diagonal gates of the form - -.. math:: - - U = \begin{pmatrix} - e^{i\theta} & 0\\ - 0 & e^{i\phi} - \end{pmatrix}. - -On their own, these gates are not very interesting. They can only be used to change the -relative phases of states in a superposition; they cannot be used to create and control such -superpositions. So let's take a look at two-qubit gates. - -Basis states of two qubits can be categorized depending on -their number of particles. - -We have: - -- :math:`|00\rangle` with zero particles, -- :math:`|01\rangle,|10\rangle` with one particle, and -- :math:`|11\rangle` with two particles. - -We can now consider transformations that couple the states :math:`|01\rangle,|10\rangle.` These -are gates of the form - -.. math:: - - U|01\rangle &= a |01\rangle + b |10\rangle\\ - U|10\rangle &= c |10\rangle + d |01\rangle. - -This should be familiar: the unitary has the same form as a single-qubit gate, except that the -states :math:`|01\rangle, |10\rangle` respectively take the place of :math:`|0\rangle, -|1\rangle`. This correspondence has a name: the `dual-rail qubit -`_, where a two-level system is constructed -by specifying in which of two possible systems a single particle is located. The -difference compared to single-qubit gates is that any values of the parameters :math:`a, -b,c,d` give rise to a valid particle-conserving unitary. Take for instance the two-qubit gate - -.. math:: - - G(\theta)=\begin{pmatrix} - 1 & 0 & 0 & 0\\ - 0 & \cos (\theta/2) & -\sin (\theta/2) & 0\\ - 0 & \sin(\theta/2) & \cos(\theta/2) & 0\\ - 0 & 0 & 0 & 1 - \end{pmatrix}. - - -This is an example of a `Givens rotation `_: a -rotation in a two-dimensional subspace of a larger Hilbert space. In this case, we are performing a -Givens rotation in a two-dimensional subspace of the four-dimensional space of two-qubit states. -This gate allows us to create superpositions by exchanging the particle -between the two qubits. Such transformations can be interpreted as a **single excitation**, -where we view the exchange from :math:`|10\rangle` to :math:`|01\rangle` as exciting the electron -from the first to the second qubit. - -.. figure:: ../_static/demonstration_assets/givens_rotations/Givens_rotations_1.png - :align: center - :width: 35% - - A Givens rotation can be used to couple states that differ by a single excitation. - -This gate is implemented in PennyLane as the :class:`~.pennylane.SingleExcitation` operation. -We can use it to prepare an equal superposition of three-qubit states with a single particle -:math:`\frac{1}{\sqrt{3}}(|001\rangle + |010\rangle + |100\rangle).` We apply two single -excitation gates with parameters :math:`\theta, \phi` to the initial state :math:`|100\rangle.` -The excitation gates act on qubits 0,1 and 0,2 respectively. This yields the state - -.. math:: - - |\psi\rangle = \cos(\theta/2)\cos(\phi/2)|100\rangle - \sin(\theta/2)|010\rangle - - \cos(\theta/2)\sin(\phi/2)|001\rangle. - -Since the amplitude of :math:`|010\rangle` must be :math:`1/\sqrt{3},` we conclude that -:math:`-\sin(\theta/2)=1/\sqrt{3}.` This in turn implies that :math:`\cos(\theta/2)=\sqrt{2/3}` and -therefore :math:`-\sin(\phi/2)=1/\sqrt{2}.` Thus, to prepare an equal superposition state we -choose the angles of rotation to be - -.. math:: - - \theta &= - 2 \arcsin(1/\sqrt{3}),\\ - \phi &= - 2 \arcsin(1/\sqrt{2}). - -This can be implemented in PennyLane as follows: -""" - -import pennylane as qml -from jax import numpy as jnp -import jax - -jax.config.update("jax_enable_x64", True) -dev = qml.device('lightning.qubit', wires=3) - -@qml.qnode(dev, interface="jax") -def circuit(x, y): - # prepares the reference state |100> - qml.BasisState(jnp.array([1, 0, 0]), wires=[0, 1, 2]) - # applies the single excitations - qml.SingleExcitation(x, wires=[0, 1]) - qml.SingleExcitation(y, wires=[0, 2]) - return qml.state() - -x = -2 * jnp.arcsin(jnp.sqrt(1/3)) -y = -2 * jnp.arcsin(jnp.sqrt(1/2)) -print(circuit(x, y)) - -############################################################################## -# The components of the output state are ordered according to their binary -# representation, so entry 1 is :math:`|001\rangle`, entry 2 is :math:`|010\rangle,` and entry 4 is -# :math:`|100\rangle,` meaning we indeed prepared the desired state. We can check this by -# reshaping the output state - -tensor_state = circuit(x, y).reshape(2, 2, 2) -print("Amplitude of state |001> = ", tensor_state[0, 0, 1]) -print("Amplitude of state |010> = ", tensor_state[0, 1, 0]) -print("Amplitude of state |100> = ", tensor_state[1, 0, 0]) - -############################################################################## -# We can also study **double excitations** involving the transfer of two particles. For example, -# consider a Givens rotation in the subspace spanned by the states -# :math:`|1100\rangle` and :math:`|0011\rangle.` These -# states differ by a double excitation since we can map :math:`|1100\rangle` to -# :math:`|0011\rangle` by exciting the particles from the first two qubits to the last two. -# Mathematically, this gate can be represented by a unitary :math:`G^{(2)}(\theta)` that performs -# the mapping -# -# .. math:: -# -# G^{(2)}|0011\rangle &= \cos (\theta/2)|0011\rangle + \sin (\theta/2)|1100\rangle\\ -# G^{(2)}|1100\rangle &= \cos (\theta/2)|1100\rangle - \sin (\theta/2)|0011\rangle, -# -# while leaving all other basis states unchanged. This gate is implemented in PennyLane as the -# :class:`~.pennylane.DoubleExcitation` operation. -# -# -# .. figure:: ../_static/demonstration_assets/givens_rotations/Givens_rotations_2.png -# :align: center -# :width: 35% -# -# A Givens rotation can also be used to couple states that differ by a double excitation. -# -# -# In the context of quantum chemistry, it is common to consider excitations on a fixed reference -# state and include only the excitations that preserve the spin orientation of the electron. -# For a system with :math:`n` qubits and :math:`k` particles, this reference state is typically -# chosen as the state with the first :math:`k` qubits in state :math:`|1\rangle,` and the remainder -# in state :math:`|0\rangle.` -# PennyLane allows you to obtain all such excitations using the function -# :func:`~.pennylane.qchem.excitations`. Let's employ it to build a circuit that includes -# all single and double excitations acting on a reference state of three particles in six qubits. -# We apply a random rotation for each gate: - -nr_particles = 3 -nr_qubits = 6 - -singles, doubles = qml.qchem.excitations(3, 6) -print(f"Single excitations = {singles}") -print(f"Double excitations = {doubles}") - -############################################################################## -# Now we continue to build the circuit: - -from jax import random - -dev2 = qml.device('lightning.qubit', wires=6) - -@qml.qnode(dev2, interface="jax") -def circuit2(x, y): - # prepares reference state - qml.BasisState(jnp.array([1, 1, 1, 0, 0, 0]), wires=[0, 1, 2, 3, 4, 5]) - # apply all single excitations - for i, s in enumerate(singles): - qml.SingleExcitation(x[i], wires=s) - # apply all double excitations - for j, d in enumerate(doubles): - qml.DoubleExcitation(y[j], wires=d) - return qml.state() - -# random angles of rotation -key = random.PRNGKey(0) -key_x, key_y = random.split(key) -x = random.normal(key_x, shape=(len(singles),)) -y = random.normal(key_y, shape=(len(singles),)) - -output = circuit2(x, y) - -############################################################################## -# We can check which basis states appear in the resulting superposition to confirm that they -# involve only states with three particles. - -# constructs binary representation of states with non-zero amplitude -import numpy as np - -states = [np.binary_repr(i, width=6) for i in range(len(output)) if output[i] != 0] -print(states) - -############################################################################## -# Besides these Givens rotations, there are other versions that have been -# reported in the literature and used to construct circuits for quantum chemistry. For instance, -# Ref. [#anselmetti]_ considers a different sign convention for single-excitation gates, -# -# .. math:: -# G(\theta)=\begin{pmatrix} -# 1 & 0 & 0 & 0\\ -# 0 & \cos (\theta/2) & \sin (\theta/2) & 0\\ -# 0 & -\sin(\theta/2) & \cos(\theta/2) & 0\\ -# 0 & 0 & 0 & 1 -# \end{pmatrix}, -# -# and Ref. [#barkoutsos]_ introduces the particle-conserving gates listed below, which are all -# Givens rotations -# -# .. math:: -# U_1(\theta, \phi) &= \begin{pmatrix} -# 1 & 0 & 0 & 0\\ -# 0 & \cos (\theta) & e^{i\phi}\sin (\theta) & 0\\ -# 0 & e^{-i\phi}\sin(\theta) & -\cos(\theta) & 0\\ -# 0 & 0 & 0 & 1 -# \end{pmatrix},\\ -# -# U_2(\theta) &= \begin{pmatrix} -# 1 & 0 & 0 & 0\\ -# 0 & \cos (2\theta) & -i\sin (2\theta) & 0\\ -# 0 & -i\sin(2\theta) & \cos(2\theta) & 0\\ -# 0 & 0 & 0 & 1 -# \end{pmatrix}. -# -# Givens rotations are a powerful abstraction for understanding -# quantum circuits for quantum chemistry. Instead of thinking of single-qubit gates and CNOTs as the -# building-blocks of quantum circuits, we can be more clever and select two-dimensional subspaces -# spanned by states with an equal number of particles, and use Givens rotations in that subspace -# to construct the circuits. 🧠 -# -# Controlled excitation gates -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# -# Single-qubit gates and CNOT gates are universal for quantum -# computing: they can be used to implement any conceivable quantum computation. If Givens -# rotations are analogous to single-qubit gates, then **controlled** Givens rotations are -# analogous to two-qubit gates. In universality constructions, the ability to control operations -# based on the states of other qubits is essential, so for this reason it's natural to study -# controlled Givens rotations. The simplest of these are controlled single-excitation gates, -# which are three-qubit gates that perform the mapping -# -# .. math:: -# CG(\theta) |101\rangle &= \cos (\theta/2)|101\rangle + \sin (\theta/2)|110\rangle,\\ -# CG(\theta) |110\rangle &= \cos (\theta/2)|110\rangle - \sin (\theta/2)|101\rangle, -# -# while leaving all other basis states unchanged. This gate only excites a particle -# from the second to third qubit, and vice versa, if the first (control) qubit is in state -# :math:`|1\rangle.` This is a useful property: as the name suggests, it provides us with better -# control over the transformations we want to apply. Suppose we aim to prepare the state -# -# .. math:: -# |\psi\rangle = \frac{1}{2}(|110000\rangle + |001100\rangle + |000011\rangle + |100100\rangle). -# -# Some inspection is enough to see that the states :math:`|001100\rangle` and :math:`|000011\rangle` -# differ by a double excitation from the reference state :math:`|110000\rangle.` Meanwhile, the state -# :math:`|100100\rangle` differs by a single excitation. It is thus tempting to think that applying -# two double-excitation gates and a single-excitation gate can be used to prepare the target state. -# It won't work! Applying the single-excitation gate on qubits 1 and 3 will also lead to an -# undesired contribution for the state :math:`|011000\rangle` through a coupling with -# :math:`|001100\rangle.` Let's check that this is the case: - -dev = qml.device('default.qubit', wires=6) - -@qml.qnode(dev, interface="jax") -def circuit3(x, y, z): - qml.BasisState(jnp.array([1, 1, 0, 0, 0, 0]), wires=[i for i in range(6)]) - qml.DoubleExcitation(x, wires=[0, 1, 2, 3]) - qml.DoubleExcitation(y, wires=[0, 1, 4, 5]) - qml.SingleExcitation(z, wires=[1, 3]) - return qml.state() - -x = -2 * jnp.arcsin(jnp.sqrt(1/4)) -y = -2 * jnp.arcsin(jnp.sqrt(1/3)) -z = -2 * jnp.arcsin(jnp.sqrt(1/2)) - -output = circuit3(x, y, z) -states = [np.binary_repr(i, width=6) for i in range(len(output)) if output[i] != 0] -print(states) - -############################################################################## -# Indeed, we have a non-zero coefficient for :math:`|011000\rangle.` To address this problem, -# we can instead apply the single-excitation gate controlled on the -# state of the first qubit. This ensures that there is no coupling with the state :math:`| -# 001100\rangle` since here the first qubit is in state :math:`|0\rangle.` Let's implement the circuit -# above, this time controlling on the state of the first qubit and verify that we can prepare the -# desired state. To perform the control, we use the :func:`~.pennylane.ctrl` transform: - -@qml.qnode(dev, interface="jax") -def circuit4(x, y, z): - qml.BasisState(jnp.array([1, 1, 0, 0, 0, 0]), wires=[i for i in range(6)]) - qml.DoubleExcitation(x, wires=[0, 1, 2, 3]) - qml.DoubleExcitation(y, wires=[0, 1, 4, 5]) - # single excitation controlled on qubit 0 - qml.ctrl(qml.SingleExcitation, control=0)(z, wires=[1, 3]) - return qml.state() - -output = circuit4(x, y, z) -states = [np.binary_repr(i, width=6) for i in range(len(output)) if output[i] != 0] -print(states) - -############################################################################## -# It was proven in Ref. [#arrazola]_ that controlled single-excitation gates are universal for -# particle-conserving unitaries. With enough ingenuity, you can use these operations to construct -# any kind of circuit for quantum chemistry applications. What more could you ask from a gate? -# -# State preparation -# ----------------- -# -# We can bring all these pieces together and implement a circuit capable of preparing -# four-qubit states of two particles with real coefficients. The main idea is that we can -# perform the construction one basis state at a time by applying a suitable excitation gate, -# which may need to be controlled. -# -# -# .. figure:: ../_static/demonstration_assets/givens_rotations/circuit.png -# :align: center -# :width: 70% -# -# A circuit for preparing four-qubit states with two particles. -# -# -# Starting from the reference state :math:`|1100\rangle,` we create a superposition -# with the state :math:`|1010\rangle` by applying a single-excitation gate on qubits 1 and 2. -# Similarly, we create a superposition with the state :math:`|1001\rangle` with a single -# excitation between qubits 1 and 3. This leaves us with a state of the form -# -# .. math:: -# |\psi\rangle = a |1100\rangle + b |1010\rangle + c |1001\rangle. -# -# We can now perform two single excitations from qubit 0 to qubits 2 and 3. These will have -# to be controlled on the state of qubit 1. Finally, applying a double-excitation gate on all qubits -# can create a superposition of the form -# -# .. math:: -# -# |\psi\rangle = c_1|1100\rangle + c_2|1010\rangle + c_3|1001\rangle + c_4|0110\rangle + -# c_5|0101\rangle + c_6|0011\rangle, -# -# which is our intended outcome. Let's use this approach to create an equal superposition over -# all two-particle states on four qubits. We follow the same strategy as before, setting the angle -# of the :math:`k`-th Givens rotation as :math:`-2 \arcsin(1/\sqrt{n-k}),` where :math:`n` is the -# number of basis states in the superposition. - -dev = qml.device('default.qubit', wires=4) - -@qml.qnode(dev, interface="jax") -def state_preparation(params): - qml.BasisState(jnp.array([1, 1, 0, 0]), wires=[0, 1, 2, 3]) - qml.SingleExcitation(params[0], wires=[1, 2]) - qml.SingleExcitation(params[1], wires=[1, 3]) - # single excitations controlled on qubit 1 - qml.ctrl(qml.SingleExcitation, control=1)(params[2], wires=[0, 2]) - qml.ctrl(qml.SingleExcitation, control=1)(params[3], wires=[0, 3]) - qml.DoubleExcitation(params[4], wires=[0, 1, 2, 3]) - return qml.state() - -n = 6 -params = jnp.array([-2 * jnp.arcsin(1/jnp.sqrt(n-i)) for i in range(n-1)]) - -output = state_preparation(params) -# sets very small coefficients to zero -output = jnp.where(jnp.abs(output) < 1e-10, 0.0, output) -states = [np.binary_repr(i, width=4) for i in range(len(output)) if output[i] != 0] -print("Basis states = ", states) -print("Output state =", output) - -############################################################################## -# Success! This is the equal superposition state we wanted to prepare. 🚀 -# -# When it comes to quantum circuits for quantum chemistry, a wide variety of -# architectures have been proposed. Researchers in the field are faced with the apparent -# choice of making a selection among these circuits to conduct their computations and benchmark new -# algorithms. Like a kid in a toy store, it is challenging to pick just one. -# -# Ultimately, the aim of this tutorial is to provide you with the conceptual and software tools -# to implement any of these proposed circuits, and **also to design your own**. It's not only fun -# to play with toys; it's also fun to build them. -# -# -# References -# ---------- -# -# .. [#anselmetti] -# -# G-L. R. Anselmetti, D. Wierichs, C. Gogolin, Christian, and R. M. Parrish, -# "Local, Expressive, Quantum-Number-Preserving VQE Ansatze -# for Fermionic Systems", arXiv:2104.05695, (2021). -# -# .. [#barkoutsos] -# -# P. KL. Barkoutsos, Panagiotis, et al., "Quantum algorithms for electronic structure -# calculations: Particle-hole Hamiltonian and optimized wave-function expansions", Physical -# Review A 98(2), 022322, (2018). -# -# .. [#arrazola] -# -# J. M. Arrazola, O. Di Matteo, N. Quesada, S. Jahangiri, A. Delgado, N. Killoran, "Universal -# quantum circuits for quantum chemistry", arXiv:2106.13839, (2021) -# -# -# About the author -# ---------------- -# +r""" + +Givens rotations for quantum chemistry +====================================== + +.. meta:: + :property="og:description": Discover the building blocks of quantum circuits for + quantum chemistry + + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/Givens_rotations.png + +.. related:: + tutorial_quantum_chemistry Building molecular Hamiltonians + tutorial_vqe A brief overview of VQE + + +*Author: Juan Miguel Arrazola — Posted: 30 June 2021. Last updated: 30 June 2021.* + +In the book `"Sophie's world" `_, the young +protagonist receives a white envelope containing a letter +with an intriguing question: "Why is Lego the most ingenious toy in the world?" At first baffled by +this curious message, she decides to reflect on the question. As told by the book's narrator, +she arrives at a conclusion: + +*The best thing about them was that with Lego she could construct any kind of object. And then +she could separate the blocks and construct something new. What more could one ask of a toy? +Sophie decided that Lego really could be called the most ingenious toy in the world.* + + + +.. figure:: ../_static/demonstration_assets/givens_rotations/lego-circuit.svg + :align: center + :width: 50% + + +In this tutorial, you will learn about the building blocks of quantum circuits for quantum +chemistry: Givens rotations. These are operations that can be used to construct any kind of +particle-conserving circuit. We discuss single and double excitation gates, which are particular +types of Givens rotations that play an important role in quantum chemistry. Notably, +controlled single excitation gates are universal for particle-conserving unitaries. You will also +learn how to use these gates to build arbitrary states of a fixed number of particles. + +Particle-conserving unitaries +----------------------------- + +Understanding the electronic structure of molecules is arguably the central problem in quantum chemistry. +Quantum computers tackle this problem by using systems of qubits to +represent the quantum states of the electrons. One method is to consider a +collection of `molecular orbitals `_, which +capture the three-dimensional region of space occupied by the electrons. Each orbital can be +occupied by at most two electrons, each with a different spin orientation. In this case we refer to +*spin orbitals* that can be occupied by a single electron. + +The state of electrons in a molecule can then be described by specifying how the +orbitals are occupied. The `Jordan-Wigner representation +`_ provides a +convenient way to do this: we associate a qubit with each spin orbital and +use its states to represent occupied :math:`|1\rangle` or unoccupied +:math:`|0\rangle` spin orbitals. + +An :math:`n`-qubit state with `Hamming weight `_ +:math:`k`, i.e., with :math:`k` qubits in state :math:`|1\rangle,` represents a state of +:math:`k` electrons in :math:`n` spin orbitals. For example :math:`|1010\rangle` is a state of +two electrons in two spin orbitals. More generally, superpositions over all basis states with a +fixed number of particles are valid states of the electrons in a molecule. These are states such as + +.. math:: + + |\psi\rangle = c_1|1100\rangle + c_2|1010\rangle + c_3|1001\rangle + c_4|0110\rangle + + c_5|0101\rangle + c_6|0011\rangle, + +for some coefficients :math:`c_i.` + +.. figure:: ../_static/demonstration_assets/givens_rotations/orbitals_states.png + :align: center + :width: 50% + + States of a system with six spin orbitals and three electrons. Orbitals are for illustration; + they correspond to carbon dioxide, which has more electrons and orbitals. + +Because the number of electrons in a molecule is +fixed, any transformation must conserve the number of particles. We refer to these as +**particle-conserving unitaries**. When designing quantum circuits and algorithms for quantum +chemistry, it is desirable to employ only particle-conserving gates that guarantee that the +states of the system remain valid. This raises the questions: what are the simplest +particle-conserving unitaries? Like Legos, how can they be used to construct any quantum circuit for +quantum chemistry applications? + +Givens rotations +---------------- + +Consider single-qubit gates. In their most general form, they perform the transformation + +.. math:: + + U|0\rangle &= a |0\rangle + b |1\rangle,\\ + U|1\rangle &= c |1\rangle + d |0\rangle, + +where :math:`|a|^2+|b|^2=|c|^2+|d|^2=1` and :math:`ab^* + cd^*=0.` This gate is +particle-conserving only if :math:`b=d=0,` which means that the only single-qubit gates that +preserve particle number are diagonal gates of the form + +.. math:: + + U = \begin{pmatrix} + e^{i\theta} & 0\\ + 0 & e^{i\phi} + \end{pmatrix}. + +On their own, these gates are not very interesting. They can only be used to change the +relative phases of states in a superposition; they cannot be used to create and control such +superpositions. So let's take a look at two-qubit gates. + +Basis states of two qubits can be categorized depending on +their number of particles. + +We have: + +- :math:`|00\rangle` with zero particles, +- :math:`|01\rangle,|10\rangle` with one particle, and +- :math:`|11\rangle` with two particles. + +We can now consider transformations that couple the states :math:`|01\rangle,|10\rangle.` These +are gates of the form + +.. math:: + + U|01\rangle &= a |01\rangle + b |10\rangle\\ + U|10\rangle &= c |10\rangle + d |01\rangle. + +This should be familiar: the unitary has the same form as a single-qubit gate, except that the +states :math:`|01\rangle, |10\rangle` respectively take the place of :math:`|0\rangle, +|1\rangle`. This correspondence has a name: the `dual-rail qubit +`_, where a two-level system is constructed +by specifying in which of two possible systems a single particle is located. The +difference compared to single-qubit gates is that any values of the parameters :math:`a, +b,c,d` give rise to a valid particle-conserving unitary. Take for instance the two-qubit gate + +.. math:: + + G(\theta)=\begin{pmatrix} + 1 & 0 & 0 & 0\\ + 0 & \cos (\theta/2) & -\sin (\theta/2) & 0\\ + 0 & \sin(\theta/2) & \cos(\theta/2) & 0\\ + 0 & 0 & 0 & 1 + \end{pmatrix}. + + +This is an example of a `Givens rotation `_: a +rotation in a two-dimensional subspace of a larger Hilbert space. In this case, we are performing a +Givens rotation in a two-dimensional subspace of the four-dimensional space of two-qubit states. +This gate allows us to create superpositions by exchanging the particle +between the two qubits. Such transformations can be interpreted as a **single excitation**, +where we view the exchange from :math:`|10\rangle` to :math:`|01\rangle` as exciting the electron +from the first to the second qubit. + +.. figure:: ../_static/demonstration_assets/givens_rotations/Givens_rotations_1.png + :align: center + :width: 35% + + A Givens rotation can be used to couple states that differ by a single excitation. + +This gate is implemented in PennyLane as the :class:`~.pennylane.SingleExcitation` operation. +We can use it to prepare an equal superposition of three-qubit states with a single particle +:math:`\frac{1}{\sqrt{3}}(|001\rangle + |010\rangle + |100\rangle).` We apply two single +excitation gates with parameters :math:`\theta, \phi` to the initial state :math:`|100\rangle.` +The excitation gates act on qubits 0,1 and 0,2 respectively. This yields the state + +.. math:: + + |\psi\rangle = \cos(\theta/2)\cos(\phi/2)|100\rangle - \sin(\theta/2)|010\rangle - + \cos(\theta/2)\sin(\phi/2)|001\rangle. + +Since the amplitude of :math:`|010\rangle` must be :math:`1/\sqrt{3},` we conclude that +:math:`-\sin(\theta/2)=1/\sqrt{3}.` This in turn implies that :math:`\cos(\theta/2)=\sqrt{2/3}` and +therefore :math:`-\sin(\phi/2)=1/\sqrt{2}.` Thus, to prepare an equal superposition state we +choose the angles of rotation to be + +.. math:: + + \theta &= - 2 \arcsin(1/\sqrt{3}),\\ + \phi &= - 2 \arcsin(1/\sqrt{2}). + +This can be implemented in PennyLane as follows: +""" + +import pennylane as qml +from jax import numpy as jnp +import jax + +jax.config.update("jax_enable_x64", True) +dev = qml.device('lightning.qubit', wires=3) + +@qml.qnode(dev, interface="jax") +def circuit(x, y): + # prepares the reference state |100> + qml.BasisState(jnp.array([1, 0, 0]), wires=[0, 1, 2]) + # applies the single excitations + qml.SingleExcitation(x, wires=[0, 1]) + qml.SingleExcitation(y, wires=[0, 2]) + return qml.state() + +x = -2 * jnp.arcsin(jnp.sqrt(1/3)) +y = -2 * jnp.arcsin(jnp.sqrt(1/2)) +print(circuit(x, y)) + +############################################################################## +# The components of the output state are ordered according to their binary +# representation, so entry 1 is :math:`|001\rangle`, entry 2 is :math:`|010\rangle,` and entry 4 is +# :math:`|100\rangle,` meaning we indeed prepared the desired state. We can check this by +# reshaping the output state + +tensor_state = circuit(x, y).reshape(2, 2, 2) +print("Amplitude of state |001> = ", tensor_state[0, 0, 1]) +print("Amplitude of state |010> = ", tensor_state[0, 1, 0]) +print("Amplitude of state |100> = ", tensor_state[1, 0, 0]) + +############################################################################## +# We can also study **double excitations** involving the transfer of two particles. For example, +# consider a Givens rotation in the subspace spanned by the states +# :math:`|1100\rangle` and :math:`|0011\rangle.` These +# states differ by a double excitation since we can map :math:`|1100\rangle` to +# :math:`|0011\rangle` by exciting the particles from the first two qubits to the last two. +# Mathematically, this gate can be represented by a unitary :math:`G^{(2)}(\theta)` that performs +# the mapping +# +# .. math:: +# +# G^{(2)}|0011\rangle &= \cos (\theta/2)|0011\rangle + \sin (\theta/2)|1100\rangle\\ +# G^{(2)}|1100\rangle &= \cos (\theta/2)|1100\rangle - \sin (\theta/2)|0011\rangle, +# +# while leaving all other basis states unchanged. This gate is implemented in PennyLane as the +# :class:`~.pennylane.DoubleExcitation` operation. +# +# +# .. figure:: ../_static/demonstration_assets/givens_rotations/Givens_rotations_2.png +# :align: center +# :width: 35% +# +# A Givens rotation can also be used to couple states that differ by a double excitation. +# +# +# In the context of quantum chemistry, it is common to consider excitations on a fixed reference +# state and include only the excitations that preserve the spin orientation of the electron. +# For a system with :math:`n` qubits and :math:`k` particles, this reference state is typically +# chosen as the state with the first :math:`k` qubits in state :math:`|1\rangle,` and the remainder +# in state :math:`|0\rangle.` +# PennyLane allows you to obtain all such excitations using the function +# :func:`~.pennylane.qchem.excitations`. Let's employ it to build a circuit that includes +# all single and double excitations acting on a reference state of three particles in six qubits. +# We apply a random rotation for each gate: + +nr_particles = 3 +nr_qubits = 6 + +singles, doubles = qml.qchem.excitations(3, 6) +print(f"Single excitations = {singles}") +print(f"Double excitations = {doubles}") + +############################################################################## +# Now we continue to build the circuit: + +from jax import random + +dev2 = qml.device('lightning.qubit', wires=6) + +@qml.qnode(dev2, interface="jax") +def circuit2(x, y): + # prepares reference state + qml.BasisState(jnp.array([1, 1, 1, 0, 0, 0]), wires=[0, 1, 2, 3, 4, 5]) + # apply all single excitations + for i, s in enumerate(singles): + qml.SingleExcitation(x[i], wires=s) + # apply all double excitations + for j, d in enumerate(doubles): + qml.DoubleExcitation(y[j], wires=d) + return qml.state() + +# random angles of rotation +key = random.PRNGKey(0) +key_x, key_y = random.split(key) +x = random.normal(key_x, shape=(len(singles),)) +y = random.normal(key_y, shape=(len(singles),)) + +output = circuit2(x, y) + +############################################################################## +# We can check which basis states appear in the resulting superposition to confirm that they +# involve only states with three particles. + +# constructs binary representation of states with non-zero amplitude +import numpy as np + +states = [np.binary_repr(i, width=6) for i in range(len(output)) if output[i] != 0] +print(states) + +############################################################################## +# Besides these Givens rotations, there are other versions that have been +# reported in the literature and used to construct circuits for quantum chemistry. For instance, +# Ref. [#anselmetti]_ considers a different sign convention for single-excitation gates, +# +# .. math:: +# G(\theta)=\begin{pmatrix} +# 1 & 0 & 0 & 0\\ +# 0 & \cos (\theta/2) & \sin (\theta/2) & 0\\ +# 0 & -\sin(\theta/2) & \cos(\theta/2) & 0\\ +# 0 & 0 & 0 & 1 +# \end{pmatrix}, +# +# and Ref. [#barkoutsos]_ introduces the particle-conserving gates listed below, which are all +# Givens rotations +# +# .. math:: +# U_1(\theta, \phi) &= \begin{pmatrix} +# 1 & 0 & 0 & 0\\ +# 0 & \cos (\theta) & e^{i\phi}\sin (\theta) & 0\\ +# 0 & e^{-i\phi}\sin(\theta) & -\cos(\theta) & 0\\ +# 0 & 0 & 0 & 1 +# \end{pmatrix},\\ +# +# U_2(\theta) &= \begin{pmatrix} +# 1 & 0 & 0 & 0\\ +# 0 & \cos (2\theta) & -i\sin (2\theta) & 0\\ +# 0 & -i\sin(2\theta) & \cos(2\theta) & 0\\ +# 0 & 0 & 0 & 1 +# \end{pmatrix}. +# +# Givens rotations are a powerful abstraction for understanding +# quantum circuits for quantum chemistry. Instead of thinking of single-qubit gates and CNOTs as the +# building-blocks of quantum circuits, we can be more clever and select two-dimensional subspaces +# spanned by states with an equal number of particles, and use Givens rotations in that subspace +# to construct the circuits. 🧠 +# +# Controlled excitation gates +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Single-qubit gates and CNOT gates are universal for quantum +# computing: they can be used to implement any conceivable quantum computation. If Givens +# rotations are analogous to single-qubit gates, then **controlled** Givens rotations are +# analogous to two-qubit gates. In universality constructions, the ability to control operations +# based on the states of other qubits is essential, so for this reason it's natural to study +# controlled Givens rotations. The simplest of these are controlled single-excitation gates, +# which are three-qubit gates that perform the mapping +# +# .. math:: +# CG(\theta) |101\rangle &= \cos (\theta/2)|101\rangle + \sin (\theta/2)|110\rangle,\\ +# CG(\theta) |110\rangle &= \cos (\theta/2)|110\rangle - \sin (\theta/2)|101\rangle, +# +# while leaving all other basis states unchanged. This gate only excites a particle +# from the second to third qubit, and vice versa, if the first (control) qubit is in state +# :math:`|1\rangle.` This is a useful property: as the name suggests, it provides us with better +# control over the transformations we want to apply. Suppose we aim to prepare the state +# +# .. math:: +# |\psi\rangle = \frac{1}{2}(|110000\rangle + |001100\rangle + |000011\rangle + |100100\rangle). +# +# Some inspection is enough to see that the states :math:`|001100\rangle` and :math:`|000011\rangle` +# differ by a double excitation from the reference state :math:`|110000\rangle.` Meanwhile, the state +# :math:`|100100\rangle` differs by a single excitation. It is thus tempting to think that applying +# two double-excitation gates and a single-excitation gate can be used to prepare the target state. +# It won't work! Applying the single-excitation gate on qubits 1 and 3 will also lead to an +# undesired contribution for the state :math:`|011000\rangle` through a coupling with +# :math:`|001100\rangle.` Let's check that this is the case: + +dev = qml.device('default.qubit', wires=6) + +@qml.qnode(dev, interface="jax") +def circuit3(x, y, z): + qml.BasisState(jnp.array([1, 1, 0, 0, 0, 0]), wires=[i for i in range(6)]) + qml.DoubleExcitation(x, wires=[0, 1, 2, 3]) + qml.DoubleExcitation(y, wires=[0, 1, 4, 5]) + qml.SingleExcitation(z, wires=[1, 3]) + return qml.state() + +x = -2 * jnp.arcsin(jnp.sqrt(1/4)) +y = -2 * jnp.arcsin(jnp.sqrt(1/3)) +z = -2 * jnp.arcsin(jnp.sqrt(1/2)) + +output = circuit3(x, y, z) +states = [np.binary_repr(i, width=6) for i in range(len(output)) if output[i] != 0] +print(states) + +############################################################################## +# Indeed, we have a non-zero coefficient for :math:`|011000\rangle.` To address this problem, +# we can instead apply the single-excitation gate controlled on the +# state of the first qubit. This ensures that there is no coupling with the state :math:`| +# 001100\rangle` since here the first qubit is in state :math:`|0\rangle.` Let's implement the circuit +# above, this time controlling on the state of the first qubit and verify that we can prepare the +# desired state. To perform the control, we use the :func:`~.pennylane.ctrl` transform: + +@qml.qnode(dev, interface="jax") +def circuit4(x, y, z): + qml.BasisState(jnp.array([1, 1, 0, 0, 0, 0]), wires=[i for i in range(6)]) + qml.DoubleExcitation(x, wires=[0, 1, 2, 3]) + qml.DoubleExcitation(y, wires=[0, 1, 4, 5]) + # single excitation controlled on qubit 0 + qml.ctrl(qml.SingleExcitation, control=0)(z, wires=[1, 3]) + return qml.state() + +output = circuit4(x, y, z) +states = [np.binary_repr(i, width=6) for i in range(len(output)) if output[i] != 0] +print(states) + +############################################################################## +# It was proven in Ref. [#arrazola]_ that controlled single-excitation gates are universal for +# particle-conserving unitaries. With enough ingenuity, you can use these operations to construct +# any kind of circuit for quantum chemistry applications. What more could you ask from a gate? +# +# State preparation +# ----------------- +# +# We can bring all these pieces together and implement a circuit capable of preparing +# four-qubit states of two particles with real coefficients. The main idea is that we can +# perform the construction one basis state at a time by applying a suitable excitation gate, +# which may need to be controlled. +# +# +# .. figure:: ../_static/demonstration_assets/givens_rotations/circuit.png +# :align: center +# :width: 70% +# +# A circuit for preparing four-qubit states with two particles. +# +# +# Starting from the reference state :math:`|1100\rangle,` we create a superposition +# with the state :math:`|1010\rangle` by applying a single-excitation gate on qubits 1 and 2. +# Similarly, we create a superposition with the state :math:`|1001\rangle` with a single +# excitation between qubits 1 and 3. This leaves us with a state of the form +# +# .. math:: +# |\psi\rangle = a |1100\rangle + b |1010\rangle + c |1001\rangle. +# +# We can now perform two single excitations from qubit 0 to qubits 2 and 3. These will have +# to be controlled on the state of qubit 1. Finally, applying a double-excitation gate on all qubits +# can create a superposition of the form +# +# .. math:: +# +# |\psi\rangle = c_1|1100\rangle + c_2|1010\rangle + c_3|1001\rangle + c_4|0110\rangle + +# c_5|0101\rangle + c_6|0011\rangle, +# +# which is our intended outcome. Let's use this approach to create an equal superposition over +# all two-particle states on four qubits. We follow the same strategy as before, setting the angle +# of the :math:`k`-th Givens rotation as :math:`-2 \arcsin(1/\sqrt{n-k}),` where :math:`n` is the +# number of basis states in the superposition. + +dev = qml.device('default.qubit', wires=4) + +@qml.qnode(dev, interface="jax") +def state_preparation(params): + qml.BasisState(jnp.array([1, 1, 0, 0]), wires=[0, 1, 2, 3]) + qml.SingleExcitation(params[0], wires=[1, 2]) + qml.SingleExcitation(params[1], wires=[1, 3]) + # single excitations controlled on qubit 1 + qml.ctrl(qml.SingleExcitation, control=1)(params[2], wires=[0, 2]) + qml.ctrl(qml.SingleExcitation, control=1)(params[3], wires=[0, 3]) + qml.DoubleExcitation(params[4], wires=[0, 1, 2, 3]) + return qml.state() + +n = 6 +params = jnp.array([-2 * jnp.arcsin(1/jnp.sqrt(n-i)) for i in range(n-1)]) + +output = state_preparation(params) +# sets very small coefficients to zero +output = jnp.where(jnp.abs(output) < 1e-10, 0.0, output) +states = [np.binary_repr(i, width=4) for i in range(len(output)) if output[i] != 0] +print("Basis states = ", states) +print("Output state =", output) + +############################################################################## +# Success! This is the equal superposition state we wanted to prepare. 🚀 +# +# When it comes to quantum circuits for quantum chemistry, a wide variety of +# architectures have been proposed. Researchers in the field are faced with the apparent +# choice of making a selection among these circuits to conduct their computations and benchmark new +# algorithms. Like a kid in a toy store, it is challenging to pick just one. +# +# Ultimately, the aim of this tutorial is to provide you with the conceptual and software tools +# to implement any of these proposed circuits, and **also to design your own**. It's not only fun +# to play with toys; it's also fun to build them. +# +# +# References +# ---------- +# +# .. [#anselmetti] +# +# G-L. R. Anselmetti, D. Wierichs, C. Gogolin, Christian, and R. M. Parrish, +# "Local, Expressive, Quantum-Number-Preserving VQE Ansatze +# for Fermionic Systems", arXiv:2104.05695, (2021). +# +# .. [#barkoutsos] +# +# P. KL. Barkoutsos, Panagiotis, et al., "Quantum algorithms for electronic structure +# calculations: Particle-hole Hamiltonian and optimized wave-function expansions", Physical +# Review A 98(2), 022322, (2018). +# +# .. [#arrazola] +# +# J. M. Arrazola, O. Di Matteo, N. Quesada, S. Jahangiri, A. Delgado, N. Killoran, "Universal +# quantum circuits for quantum chemistry", arXiv:2106.13839, (2021) +# +# +# About the author +# ---------------- +# diff --git a/demonstrations/tutorial_haar_measure.py b/demonstrations/tutorial_haar_measure.py index 5807b5ab34..edde00d2d7 100644 --- a/demonstrations/tutorial_haar_measure.py +++ b/demonstrations/tutorial_haar_measure.py @@ -1,812 +1,812 @@ -r""".. role:: html(raw) - :format: html - -Understanding the Haar measure -============================== - -.. meta:: - :property="og:description": Learn all about the Haar measure and how to randomly sample quantum states. - - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/spherical_int_dtheta.png - -.. related:: - - tutorial_unitary_designs Unitary designs - quantum_volume Quantum volume - qsim_beyond_classical Beyond classical computing with qsim - tutorial_barren_plateaus Barren plateaus in quantum neural networks - - -*Author: Olivia Di Matteo — Posted: 22 March 2021. Last updated: 22 March 2021.* - -If you've ever dug into the literature about random quantum circuits, -variational ansatz structure, or anything related to the structure and -properties of unitary operations, you've likely come across a statement like the -following: "Assume that :math:`U` is sampled uniformly at random from the Haar -measure". In this demo, we're going to unravel this cryptic statement and take -an in-depth look at what it means. You'll gain an understanding of the general -concept of a *measure*, the Haar measure and its special properties, and you'll -learn how to sample from it using tools available in PennyLane and other -scientific computing frameworks. By the end of this demo, you'll be able to -include that important statement in your own work with confidence! - -.. note:: - - To get the most out of this demo, it is helpful if you are familiar with - `integration of multi-dimensional functions - `__, the `Bloch sphere - `__, and the conceptual ideas - behind `decompositions - `__ and factorizations of - unitary matrices (see, e.g., 4.5.1 and 4.5.2 of [#NandC2000]_). - -Measure -------- - -`Measure theory `__ is a -branch of mathematics that studies things that are measurable—think length, -area, or volume, but generalized to mathematical spaces and even higher -dimensions. Loosely, the measure tells you about how "stuff" is distributed and -concentrated in a mathematical set or space. An intuitive way to understand -the measure is to think about a sphere. An arbitrary point on a sphere can be -parametrized by three numbers—depending on what you're doing, you may use -Cartesian coordinates :math:`(x, y, z),` or it may be more convenient to use -spherical coordinates :math:`(\rho, \phi, \theta).` - -Suppose you wanted to compute the volume of a solid sphere with radius -:math:`r.` This can be done by integrating over the three coordinates -:math:`\rho, \phi,` and :math:`\theta.` Your first thought here may be to simply -integrate each parameter over its full range, like so: - -.. math:: - - V = \int_0^{r} \int_0^{2\pi} \int_0^{\pi} d\rho~ d\phi~ d\theta = 2\pi^2 r - -But, we know that the volume of a sphere of radius :math:`r` is -:math:`\frac{4}{3}\pi r^3,` so what we got from this integral is clearly wrong! -Taking the integral naively like this doesn't take into account the structure of -the sphere with respect to the parameters. For example, consider -two small, infinitesimal elements of area with the same difference in -:math:`\theta` and :math:`\phi,` but at different values of :math:`\theta:` - -.. figure:: /_static/demonstration_assets/haar_measure/spherical_int_dtheta.png - :align: center - :width: 50% - - -Even though the differences :math:`d\theta` and :math:`d\phi` themselves are the -same, there is way more "stuff" near the equator of the sphere than there is -near the poles. We must take into account the value of :math:`\theta` when -computing the integral! Specifically, we multiply by the function -:math:`\sin\theta`---the properties of the :math:`\sin` function mean that the -most weight will occur around the equator where :math:`\theta=\pi/2,` and the -least weight near the poles where :math:`\theta=0` and :math:`\theta=\pi.` - -Similar care must be taken for :math:`\rho.` The contribution to volume of -parts of the sphere with a large :math:`\rho` is far more than for a small -:math:`\rho`---we should expect the contribution to be proportional to -:math:`\rho^2,` given that the surface area of a sphere of radius :math:`r` is -:math:`4\pi r^2.` - -On the other hand, for a fixed :math:`\rho` and :math:`\theta,` the length of -the :math:`d\phi` is the same all around the circle. If put all these facts -together, we find that the actual expression for the integral should look like -this: - -.. math:: - - V = \int_0^r \int_0^{2\pi} \int_0^{\pi} \rho^2 \sin \theta~ d\rho~ d\phi~ - d\theta = \frac{4}{3}\pi r^3 - -These extra terms that we had to add to the integral, :math:`\rho^2 \sin -\theta`, constitute the *measure*. The measure weights portions of the sphere -differently depending on where they are in the space. While we need to know the -measure to properly integrate over the sphere, knowledge of the measure also -gives us the means to perform another important task, that of sampling points in -the space uniformly at random. We can't simply sample each parameter from the -uniform distribution over its domain—as we experienced already, this doesn't -take into account how the sphere is spread out over space. The measure describes -the distribution of each parameter and gives a recipe for sampling them in order -to obtain something properly uniform. - -The Haar measure ----------------- - -Operations in quantum computing are described by unitary matrices. -Unitary matrices, like points on a sphere, can be expressed in terms of a fixed -set of coordinates, or parameters. For example, the most general single-qubit rotation -implemented in PennyLane (:class:`~.pennylane.Rot`) is expressed in terms of three -parameters like so, - -.. math:: - - U(\phi, \theta, \omega) = \begin{pmatrix} e^{-i(\phi + \omega)/2} - \cos(\theta/2) & -e^{i(\phi - \omega)/2} \sin(\theta/2) - \\ e^{-i(\phi - \omega)/2} \sin(\theta/2) & e^{i(\phi + - \omega)/2} \cos(\theta/2) \end{pmatrix}. - -For every dimension :math:`N,` the unitary matrices of size :math:`N \times N` -constitute the *unitary group* :math:`U(N).` We can perform operations on -elements of this group, such as apply functions to them, integrate over them, or -sample uniformly over them, just as we can do to points on a sphere. When we do -such tasks with respect to the sphere, we have to add the measure in order to -properly weight the different regions of space. The *Haar measure* provides the -analogous terms we need for working with the unitary group. - -For an :math:`N`-dimensional system, the Haar measure, often denoted by -:math:`\mu_N,` tells us how to weight the elements of :math:`U(N).` For -example, suppose :math:`f` is a function that acts on elements of :math:`U(N),` -and we would like to take its integral over the group. We must write this -integral with respect to the Haar measure, like so: - -.. math:: - - \int_{V \in U(N)} f(V) d\mu_N(V). - -As with the measure term of the sphere, :math:`d\mu_N` itself can be broken down -into components depending on individual parameters. While the Haar -measure can be defined for every dimension :math:`N,` the mathematical form gets -quite hairy for larger dimensions—in general, an :math:`N`-dimensional unitary -requires at least :math:`N^2 - 1` parameters, which is a lot to keep track of! -Therefore we'll start with the case of a single qubit :math:`(N=2),` then show -how things generalize. - -Single-qubit Haar measure -~~~~~~~~~~~~~~~~~~~~~~~~~ - -The single-qubit case provides a particularly nice entry point because we can -continue our comparison to spheres by visualizing single-qubit states on the -Bloch sphere. As expressed above, the measure provides a recipe for sampling -elements of the unitary group in a properly uniform manner, given the structure -of the group. One useful consequence of this is that it provides a method to -sample quantum *states* uniformly at random—we simply generate Haar-random -unitaries, and apply them to a fixed basis state such as :math:`\vert 0\rangle.` - -We'll see how this works in good time. First, we'll take a look at what happens -when we ignore the measure and do things *wrong*. Suppose we sample quantum -states by applying unitaries obtained by the parametrization above, but sample -the angles :math:`\omega, \phi,` and :math:`\theta` from the flat uniform -distribution between :math:`[0, 2\pi)` (fun fact: there is a measure implicit in -this kind of sampling too! It just has a constant value, because each point is -equally likely to be sampled). - -""" - -import pennylane as qml -import numpy as np -import matplotlib.pyplot as plt - -# set the random seed -np.random.seed(42) - -# Use the mixed state simulator to save some steps in plotting later -dev = qml.device('default.mixed', wires=1) - -@qml.qnode(dev) -def not_a_haar_random_unitary(): - # Sample all parameters from their flat uniform distribution - phi, theta, omega = 2 * np.pi * np.random.uniform(size=3) - qml.Rot(phi, theta, omega, wires=0) - return qml.state() - -num_samples = 2021 - -not_haar_samples = [not_a_haar_random_unitary() for _ in range(num_samples)] - -###################################################################### -# In order to plot these on the Bloch sphere, we'll need to do one more -# step, and convert the quantum states into Bloch vectors. -# - -X = np.array([[0, 1], [1, 0]]) -Y = np.array([[0, -1j], [1j, 0]]) -Z = np.array([[1, 0], [0, -1]]) - -# Used the mixed state simulator so we could have the density matrix for this part! -def convert_to_bloch_vector(rho): - """Convert a density matrix to a Bloch vector.""" - ax = np.trace(np.dot(rho, X)).real - ay = np.trace(np.dot(rho, Y)).real - az = np.trace(np.dot(rho, Z)).real - return [ax, ay, az] - -not_haar_bloch_vectors = np.array([convert_to_bloch_vector(s) for s in not_haar_samples]) - -###################################################################### -# With this done, let's find out where our "uniformly random" states ended up: - -def plot_bloch_sphere(bloch_vectors): - """ Helper function to plot vectors on a sphere.""" - fig = plt.figure(figsize=(6, 6)) - ax = fig.add_subplot(111, projection='3d') - fig.subplots_adjust(left=0, right=1, bottom=0, top=1) - - ax.grid(False) - ax.set_axis_off() - ax.view_init(30, 45) - - # Draw the axes (source: https://github.com/matplotlib/matplotlib/issues/13575) - x, y, z = np.array([[-1.5,0,0], [0,-1.5,0], [0,0,-1.5]]) - u, v, w = np.array([[3,0,0], [0,3,0], [0,0,3]]) - ax.quiver(x, y, z, u, v, w, arrow_length_ratio=0.05, color="black", linewidth=0.5) - - ax.text(0, 0, 1.7, r"|0⟩", color="black", fontsize=16) - ax.text(0, 0, -1.9, r"|1⟩", color="black", fontsize=16) - ax.text(1.9, 0, 0, r"|+⟩", color="black", fontsize=16) - ax.text(-1.7, 0, 0, r"|–⟩", color="black", fontsize=16) - ax.text(0, 1.7, 0, r"|i+⟩", color="black", fontsize=16) - ax.text(0,-1.9, 0, r"|i–⟩", color="black", fontsize=16) - - ax.scatter( - bloch_vectors[:,0], bloch_vectors[:,1], bloch_vectors[:, 2], c='#e29d9e', alpha=0.3 - ) - plt.show() - -plot_bloch_sphere(not_haar_bloch_vectors) - -###################################################################### -# You can see from this plot that even though our parameters were sampled from a -# uniform distribution, there is a noticeable amount of clustering around the poles -# of the sphere. Despite the input parameters being uniform, the output is very -# much *not* uniform. Just like the regular sphere, the measure is larger near -# the equator, and if we just sample uniformly, we won't end up populating that -# area as much. To take that into account we will need to sample from the proper -# Haar measure, and weight the different parameters appropriately. -# -# For a single qubit, the Haar measure looks much like the case of a sphere, -# minus the radial component. Intuitively, all qubit state vectors have length -# 1, so it makes sense that this wouldn't play a role here. The parameter that -# we will have to weight differently is :math:`\theta,` and in fact the -# adjustment in measure is identical to that we had to do with the polar axis of -# the sphere, i.e., :math:`\sin \theta.` In order to sample the :math:`\theta` -# uniformly at random in this context, we must sample from the distribution -# :math:`\hbox{Pr}(\theta) = \sin \theta.` We can accomplish this by setting up -# a custom probability distribution with -# `rv_continuous `__ -# in ``scipy``. - -from scipy.stats import rv_continuous - -class sin_prob_dist(rv_continuous): - def _pdf(self, theta): - # The 0.5 is so that the distribution is normalized - return 0.5 * np.sin(theta) - -# Samples of theta should be drawn from between 0 and pi -sin_sampler = sin_prob_dist(a=0, b=np.pi) - -@qml.qnode(dev) -def haar_random_unitary(): - phi, omega = 2 * np.pi * np.random.uniform(size=2) # Sample phi and omega as normal - theta = sin_sampler.rvs(size=1)[0] # Sample theta from our new distribution - qml.Rot(phi, theta, omega, wires=0) - return qml.state() - -haar_samples = [haar_random_unitary() for _ in range(num_samples)] -haar_bloch_vectors = np.array([convert_to_bloch_vector(s) for s in haar_samples]) - -plot_bloch_sphere(haar_bloch_vectors) - -###################################################################### -# We see that when we use the correct measure, our qubit states are now -# much better distributed over the sphere. Putting this information together, -# we can now write the explicit form for the single-qubit Haar measure: -# -# .. math:: -# -# d\mu_2 = \sin \theta d\theta \cdot d\omega \cdot d\phi. -# -# Show me more math! -# ~~~~~~~~~~~~~~~~~~ -# -# While we can easily visualize the single-qubit case, this is no longer -# possible when we increase the number of qubits. Regardless, we can still -# obtain a mathematical expression for the Haar measure in arbitrary -# dimensions. In the previous section, we expressed the Haar measure in terms of -# a set of parameters that can be used to specify the unitary group -# :math:`U(2).` Such a parametrization is not unique, and in fact there are -# multiple ways to *factorize*, or decompose an :math:`N`-dimensional unitary -# operation into a set of parameters. -# -# Many of these parametrizations come to us from the study of photonics. Here, -# arbitrary operations are broken down into elementary operations involving only -# a few parameters which correspond directly to parameters of the physical -# apparatus used to implement them (beamsplitters and phase shifts). Rather than -# qubits, such operations act on modes, or *qumodes*. They are expressed as -# elements of the :math:`N`-dimensional `special unitary group -# `__. This group, written -# as :math:`SU(N),` is the continuous group consisting of all :math:`N \times N` -# unitary operations with determinant 1 (essentially like :math:`U(N),` minus -# a potential global phase). -# -# -# .. note:: -# -# Elements of :math:`SU(N)` and :math:`U(N)` can still be considered as -# multi-qubit operations in the cases where :math:`N` is a power of 2, but -# they must be translated from continuous-variable operations into qubit -# operations. (In PennyLane, this can be done by feeding the unitaries to -# the :class:`~.pennylane.QubitUnitary` operation directly. Alternatively, -# one can use *quantum compilation* to express the operations as a sequence -# of elementary gates such as Pauli rotations and CNOTs.) -# -# .. admonition:: Tip -# -# If you haven't had many opportunities to work in terms of qumodes, the -# `Strawberry Fields documentation -# `__ is a -# good starting point. -# -# For example, we saw already above that for :math:`N=2,` we can write -# -# .. math:: -# -# U(\phi, \theta, \omega) = \begin{pmatrix} e^{-i(\phi + \omega)/2} -# \cos(\theta/2) & -e^{i(\phi - \omega)/2} \sin(\theta/2) -# \\ e^{-i(\phi - \omega)/2} \sin(\theta/2) & e^{i(\phi + -# \omega)/2} \cos(\theta/2) \end{pmatrix}. -# -# -# This unitary can be factorized as follows: -# -# .. math:: -# -# U(\phi, \theta, \omega) = -# \begin{pmatrix} -# e^{-i\omega/2} & 0 \\ 0 & e^{i\omega/2} -# \end{pmatrix} -# \begin{pmatrix} -# \cos(\theta/2) & -\sin(\theta/2) \\ \sin(\theta/2) & \cos(\theta/2) -# \end{pmatrix} -# \begin{pmatrix} -# e^{-i\phi/2} & 0 \\ 0 & e^{i\phi/2} -# \end{pmatrix} -# -# The middle operation is a beamsplitter; the other two operations are phase -# shifts. We saw earlier that for :math:`N=2,` :math:`d\mu_2 = \sin\theta -# d\theta d\omega d\phi`---note how the parameter in the beamsplitter -# contributes to the measure in a different way than those of the phase -# shifts. As mentioned above, for larger values of :math:`N` there are multiple -# ways to decompose the unitary. Such decompositions rewrite elements in -# :math:`SU(N)` acting on :math:`N` modes as a sequence of operations acting -# only on 2 modes, :math:`SU(2),` and single-mode phase shifts. Shown below are -# three examples [#deGuise2018]_, [#Clements2016]_, [#Reck1994]_: -# -# .. figure:: /_static/demonstration_assets/haar_measure/unitaries.png -# :align: center -# :width: 95% -# -# -# In these graphics, every wire is a different mode. Every box represents an -# operation on one or more modes, and the number in the box indicates the number -# of parameters. The boxes containing a ``1`` are simply phase shifts on -# individual modes. The blocks containing a ``3`` are :math:`SU(2)` transforms -# with 3 parameters, such as the :math:`U(\phi, \theta, \omega)` above. Those -# containing a ``2`` are :math:`SU(2)` transforms on pairs of modes with 2 -# parameters, similar to the 3-parameter ones but with :math:`\omega = \phi.` -# -# Although the decompositions all produce the same set of operations, their -# structure and parametrization may have consequences in practice. The first [#deGuise2018]_ -# has a particularly convenient form that leads to a recursive definition -# of the Haar measure. The decomposition is formulated recursively such that an -# :math:`SU(N)` operation can be implemented by sandwiching an :math:`SU(2)` -# transformation between two :math:`SU(N-1)` transformations, like so: -# -# | -# -# .. figure:: /_static/demonstration_assets/haar_measure/sun.svg -# :align: center -# :width: 80% -# -# | -# -# The Haar measure is then constructed recursively as a product of 3 -# terms. The first term depends on the parameters in the first :math:`SU(N-1)` -# transformation; the second depends on the parameters in the lone :math:`SU(2)` -# transformation; and the third term depends on the parameters in the other -# :math:`SU(N-1)` transformation. -# -# :math:`SU(2)` is the "base case" of the recursion—we simply have the Haar measure -# as expressed above. -# -# | -# -# .. figure:: /_static/demonstration_assets/haar_measure/su2_haar.svg -# :align: center -# :width: 25% -# -# | -# -# Moving on up, we can write elements of :math:`SU(3)` as a sequence of three -# :math:`SU(2)` transformations. The Haar measure :math:`d\mu_3` then consists -# of two copies of :math:`d\mu_2,` with an extra term in between to take into -# account the middle transformation. -# -# | -# -# .. figure:: /_static/demonstration_assets/haar_measure/su3_haar.svg -# :align: center -# :width: 80% -# -# | -# -# For :math:`SU(4)` and upwards, the form changes slightly, but still follows -# the pattern of two copies of :math:`d\mu_{N-1}` with a term in between. -# -# | -# -# .. figure:: /_static/demonstration_assets/haar_measure/su4_premerge.svg -# :align: center -# :width: 90% -# -# | -# -# For larger systems, however, the recursive composition allows for some of the -# :math:`SU(2)` transformations on the lower modes to be grouped. We can take -# advantage of this and aggregate some of the parameters: -# -# | -# -# .. figure:: /_static/demonstration_assets/haar_measure/su4_triangle_merge.svg -# :align: center -# :width: 100% -# -# | -# -# This leads to one copy of :math:`d\mu_{N-1},` which we'll denote as -# :math:`d\mu_{N-1}^\prime,` containing only a portion of the full set of terms -# (as detailed in [#deGuise2018]_, this is called a *coset measure*). -# -# | -# -# .. figure:: /_static/demonstration_assets/haar_measure/su4_haar.svg -# :align: center -# :width: 100% -# -# | -# -# Putting everything together, we have that -# -# .. math:: -# -# d\mu_N = d\mu_{N-1}^\prime \times \sin \theta_{N-1} -# \sin^{2(N-2)}\left(\frac{\theta_{N-1}}{2}\right) d\theta_{N-1} d\omega_{N-1} \times d\mu_{N-1} -# -# The middle portion depends on the value of :math:`N,` and the parameters -# :math:`\theta_{N-1}` and :math:`\omega_{N-1}` contained in the :math:`(N-1)`'th -# :math:`SU(N)` transformation. This is thus a convenient, systematic way to -# construct the :math:`N`-dimensional Haar measure for the unitary group. As a -# final note, even though unitaries can be parametrized in different ways, the -# underlying Haar measure is *unique*. This is a consequence of it being an -# invariant measure, as will be shown later. -# -# Haar-random matrices from the :math:`QR` decomposition -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# -# Nice-looking math aside, sometimes you just need to generate a large number of -# high-dimensional Haar-random matrices. It would be very cumbersome to sample -# and keep track of the distributions of so many parameters; furthermore, the -# measure above requires you to parametrize your operations in a fixed way. -# There is a much quicker way to perform the sampling by taking a (slightly -# modified) `QR decomposition -# `__ of complex-valued -# matrices. This algorithm is detailed in [#Mezzadri2006]_, and consists of the -# following steps: -# -# 1. Generate an :math:`N \times N` matrix :math:`Z` with complex numbers :math:`a+bi` -# where both :math:`a` and :math:`b` are normally distributed with mean 0 and variance 1 -# (this is sampling from the distribution known as the *Ginibre ensemble*). -# 2. Compute a QR decomposition :math:`Z = QR.` -# 3. Compute the diagonal matrix :math:`\Lambda = \hbox{diag}(R_{ii}/|R_{ii}|).` -# 4. Compute :math:`Q^\prime = Q \Lambda,` which will be Haar-random. -# -# - -from numpy.linalg import qr - -def qr_haar(N): - """Generate a Haar-random matrix using the QR decomposition.""" - # Step 1 - A, B = np.random.normal(size=(N, N)), np.random.normal(size=(N, N)) - Z = A + 1j * B - - # Step 2 - Q, R = qr(Z) - - # Step 3 - Lambda = np.diag([R[i, i] / np.abs(R[i, i]) for i in range(N)]) - - # Step 4 - return np.dot(Q, Lambda) - -###################################################################### -# Let's check that this method actually generates Haar-random unitaries -# by trying it out for :math:`N=2` and plotting on the Bloch sphere. -# - -@qml.qnode(dev) -def qr_haar_random_unitary(): - qml.QubitUnitary(qr_haar(2), wires=0) - return qml.state() - -qr_haar_samples = [qr_haar_random_unitary() for _ in range(num_samples)] -qr_haar_bloch_vectors = np.array([convert_to_bloch_vector(s) for s in qr_haar_samples]) -plot_bloch_sphere(qr_haar_bloch_vectors) - -###################################################################### -# As expected, we find our qubit states are distributed uniformly over the -# sphere. This particular method is what's implemented in packages like -# ``scipy``'s `unitary_group -# `__ -# function. -# -# Now, it's clear that this method works, but it is also important to -# understand *why* it works. Step 1 is fairly straightforward—the base of our -# samples is a matrix full of complex values chosen from a typical -# distribution. This isn't enough by itself, since unitary matrices also -# have constraints—their rows and columns must be orthonormal. -# These constraints are where step 2 comes in—the outcome of a generic -# QR decomposition consists of an *orthonormal* matrix :math:`Q,` and and upper -# triangular matrix :math:`R.` Since our original matrix was complex-valued, we end -# up with a :math:`Q` that is in fact already unitary. But why not stop there? Why -# do we then perform steps 3 and 4? -# -# Steps 3 and 4 are needed because, while the QR decomposition yields a unitary, -# it is not a unitary that is properly Haar-random. In [#Mezzadri2006]_, it is -# explained that a uniform distribution over unitary matrices should also yield -# a uniform distribution over the *eigenvalues* of those matrices, i.e., every -# eigenvalue should be equally likely. Just using the QR decomposition out of -# the box produces an *uneven* distribution of eigenvalues of the unitaries! -# This discrepancy stems from the fact that the QR decomposition is not unique. -# We can take any unitary diagonal matrix :math:`\Lambda,` and re-express the decomposition -# as :math:`QR = Q\Lambda \Lambda^\dagger R = Q^\prime R^\prime.` Step 3 removes this -# redundancy by fixing a :math:`\Lambda` that depends on :math:`R,` leading to a unique -# value of :math:`Q^\prime = Q \Lambda,` and a uniform distribution of eigenvalues. -# -# .. admonition:: Try it! -# -# Use the ``qr_haar`` function above to generate random unitaries and construct -# a distribution of their eigenvalues. Then, comment out the lines for steps 3 and -# 4 and do the same—you'll find that the distribution is no longer uniform. -# Check out reference [#Mezzadri2006]_ for additional details and examples. - -###################################################################### -# Fun (and not-so-fun) facts -# -------------------------- -# -# We've now learned what the Haar measure is, and both an analytical and -# numerical means of sampling quantum states and unitary operations uniformly at -# random. The Haar measure also has many neat properties that play a role in -# quantum computing. -# -# Invariance of the Haar measure -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# -# Earlier, we showed that the Haar measure is used when integrating functions over -# the unitary group: -# -# .. math:: -# -# \int_{V \in U(N)} f(V) d\mu_N(V). -# -# One of the defining features of the Haar measure is that it is both left and -# right *invariant* under unitary transformations. That is, -# -# .. math:: -# -# \int_{V \in U(N)} f(\color{red}{W}V) d\mu_N(V) = \int_{V \in U(N)} f(V\color{red}{W}) d\mu_N(V) = \int_{V \in U(N)} f(V) d\mu_N(V). -# -# This holds true for *any* other :math:`N\times N` unitary :math:`W!` A -# consequence of such invariance is that if :math:`V` is Haar-random, then so is -# :math:`V^T,` :math:`V^\dagger,` and any product of another unitary matrix and -# :math:`V` (where the product may be taken on either side). -# -# Another consequence of this invariance has to do with the structure of the entries -# themselves: they must all come from the same distribution. This is because the -# measure remains invariant under permutations, since permutations are unitary--- -# the whole thing still has to be Haar random no matter how the entries are ordered, -# so all distributions must be the same. The specific distribution is complex -# numbers :math:`a+bi` where both :math:`a` and :math:`b` has mean 0 and variance -# :math:`1/N` [#Meckes2014]_ (so, much like Ginibre ensemble we used in the QR decomposition -# above, but with a different variance and constraints due to orthonormality). -# -# Concentration of measure -# ~~~~~~~~~~~~~~~~~~~~~~~~ -# -# An unfortunate (although interesting) property of the Haar measure is that it -# suffers from the phenomenon of `concentration of measure -# `__. Most of the -# "stuff" in the space concentrates around a certain area, and this gets worse -# as the size of the system increases. You can see the beginnings of by looking -# at the sphere. For the 3-dimensional sphere, we saw graphically how there is -# concentration around the equator, and how the measure takes that into account -# with the additional factor of :math:`\sin \theta.` This property becomes -# increasingly prominent for `higher-dimensional spheres -# `__. -# -# .. important:: -# -# The concentration described here is not referring to what we witnessed -# earlier on, when we sampled quantum states (points on the Bloch sphere) -# incorrectly and found that they clustered around the poles. However, that -# is not unrelated. Concentration of measure refers to where the measure -# itself is concentrated, and which parts of the space should be more heavily -# weighted. For the case of the sphere, it is the equatorial area, and when -# we didn't sample properly and take that concentration into account, we -# obtained an uneven distribution. -# -# Let's consider an :math:`N`-dimensional unit sphere. Points on the sphere, or -# vectors in this space, are parametrized by :math:`N-1` real coordinates. -# Suppose we have some function :math:`f` that maps points on that sphere to -# real numbers. Sample a point :math:`x` on that sphere from the uniform -# measure, and compute the value of :math:`f(x).` How close do you think the -# result will be to the mean value of the function, :math:`E[f],` over the -# entire sphere? -# -# A result called `Levy's lemma -# `__ -# [#Gerken2013]_, [#Hayden2006]_ expresses how likely it is that :math:`f(x)` is a specific -# distance away from the mean. It states that, for an :math:`x` selected -# uniformly at random, the probability that :math:`f(x)` deviates from -# :math:`E[f]` by some amount :math:`\epsilon` is bounded by: -# -# .. math:: -# -# \hbox{Pr}(|f(x) - E[f]| \ge \epsilon) \leq 2 \exp\left[-\frac{N\epsilon^2}{9\pi^3 \eta^2}\right]. -# -# A constraint on the function :math:`f` is that it must be `Lipschitz -# continuous `__, where -# :math:`\eta` is the *Lipschitz constant* of the function. The important aspect -# here is the likelihood of deviating significantly from the mean by an amount -# :math:`\epsilon` decreases exponentially with :math:`\epsilon.` Furthermore, -# increasing the dimension :math:`N` also makes the deviation exponentially less -# likely. -# -# Now, this result seems unrelated to quantum states—it concerns higher- -# dimensional spheres. However, recall that a quantum state vector is a complex -# vector whose squared values sum to 1, similar to vectors on a sphere. If you -# "unroll" a quantum state vector of dimension :math:`N = 2^n` by stacking its -# real and complex parts, you end with a vector of length :math:`2 \cdot 2^{n}` -# which ends up behaving just like a unit vector on the sphere in this -# dimension. Given that measure concentrates on spheres, and quantum state -# vectors can be converted to vectors on spheres, functions on random quantum -# states will also demonstrate concentration. -# -# This is bad news! To do useful things in quantum computing, we need a lot of -# qubits. But the more qubits we have, the more our randomly sampled states will -# look the same (specifically, random states will concentrate around the -# maximally entangled state [#Hayden2006]_). This has important consequences for -# near-term algorithms (as detailed in the next section), and any algorithm that -# involves uniform sampling of quantum states and operations. -# -# Haar measure and barren plateaus -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# -# Suppose you are venturing out to solve a new problem using an algorithm such -# as the :doc:`variational quantum eigensolver `. A -# critical component of such methods is the choice of :doc:`variational ansatz -# `. Having now learned a bit about the properties of -# the Haar measure, you may think it would make sense to use this for the -# parametrization. Variational ansaetze are, after all, parametrized quantum -# circuits, so why not choose an ansatz that corresponds directly to a -# parametrization for Haar-random unitaries? The initial parameter selection -# will give you a state in the Hilbert space uniformly at random. Then, since -# this ansatz spans the entire Hilbert space, you're guaranteed to be able to -# represent the target ground state with your ansatz, and it should be able to -# find it with no issue ... right? -# -# Unfortunately, while such an ansatz is extremely *expressive* (i.e., it is -# capable of representing any possible state), these ansaetze actually suffer -# the most from the barren plateau problem [#McClean2018]_, [#Holmes2021]_. -# :doc:`Barren plateaus ` are regions in the -# cost landscape of a parametrized circuit where both the gradient and its -# variance approach 0, leading the optimizer to get stuck in a local minimum. -# This was explored recently in the work of [#Holmes2021]_, wherein closeness to -# the Haar measure was actually used as a metric for expressivity. The closer -# things are to the Haar measure, the more expressive they are, but they are -# also more prone to exhibiting barren plateaus. -# -# -# .. figure:: /_static/demonstration_assets/haar_measure/holmes-costlandscapes.png -# :align: center -# :width: 50% -# -# Image source: [#Holmes2021]_. A highly expressive ansatz that can access -# much of the space of possible unitaries (i.e., an ansatz capable of -# producing unitaries in something close to a Haar-random manner) is very -# likely to have flat cost landscapes and suffer from the barren plateau -# problem. -# -# It turns out that the types of ansaetze know as *hardware-efficient ansaetze* -# also suffer from this problem if they are "random enough" (this notion will be -# formalized in a future demo). It was shown in [#McClean2018]_ that this is a -# consequence of the concentration of measure phenomenon described above. The -# values of gradients and variances can be computed for classes of circuits on -# average by integrating with respect to the Haar measure, and it is shown that -# these values decrease exponentially in the number of qubits, and thus huge -# swaths of the cost landscape are simply and fundamentally flat. -# -# Conclusion -# ---------- -# -# The Haar measure plays an important role in quantum computing—anywhere -# you might be dealing with sampling random circuits, or averaging over -# all possible unitary operations, you'll want to do so with respect -# to the Haar measure. -# -# There are two important aspects of this that we have yet to touch upon, -# however. The first is whether it is efficient to sample from the Haar measure—given -# that the number of parameters to keep track of is exponential in the -# number of qubits, certainly not. But a more interesting question is do we -# *need* to always sample from the full Haar measure? The answer to this is -# "no" in a very interesting way. Depending on the task at hand, you may be able -# to take a shortcut using something called a *unitary design*. In an upcoming -# demo, we will explore the amazing world of unitary designs and their -# applications! -# -# References -# ---------- -# -# .. [#NandC2000] -# -# M. A. Nielsen, and I. L. Chuang (2000) "Quantum Computation and Quantum Information", -# Cambridge University Press. -# -# .. [#deGuise2018] -# -# H. de Guise, O. Di Matteo, and L. L. Sánchez-Soto. (2018) "Simple factorization -# of unitary transformations", `Phys. Rev. A 97 022328 -# `__. -# (`arXiv `__) -# -# .. [#Clements2016] -# -# W. R. Clements, P. C. Humphreys, B. J. Metcalf, W. S. Kolthammer, and -# I. A. Walmsley (2016) “Optimal design for universal multiport -# interferometers”, \ `Optica 3, 1460–1465 -# `__. -# (`arXiv `__) -# -# .. [#Reck1994] -# -# M. Reck, A. Zeilinger, H. J. Bernstein, and P. Bertani (1994) “Experimental -# realization of any discrete unitary operator”, `Phys. Rev. Lett.73, 58–61 -# `__. -# -# .. [#Mezzadri2006] -# -# F. Mezzadri (2006) "How to generate random matrices from the classical compact groups". -# (`arXiv `__) -# -# .. [#Meckes2014] -# -# E. Meckes (2019) `"The Random Matrix Theory of the Classical Compact Groups" -# `_, Cambridge University Press. -# -# .. [#Gerken2013] -# -# M. Gerken (2013) "Measure concentration: Levy's Lemma" -# (`lecture notes `__). -# -# -# .. [#Hayden2006] -# -# P. Hayden, D. W. Leung, and A. Winter (2006) "Aspects of generic -# entanglement", `Comm. Math. Phys. Vol. 265, No. 1, pp. 95-117 -# `__. -# (`arXiv `__) -# -# .. [#McClean2018] -# -# J. R. McClean, S. Boixo, V. N. Smelyanskiy, R. Babbush, and H. Neven -# (2018) "Barren plateaus in quantum neural network training -# landscapes", `Nature Communications, 9(1) -# `__. -# (`arXiv `__) -# -# .. [#Holmes2021] -# -# Z. Holmes, K. Sharma, M. Cerezo, and P. J. Coles (2021) "Connecting ansatz -# expressibility to gradient magnitudes and barren plateaus". (`arXiv -# `__) +r""".. role:: html(raw) + :format: html + +Understanding the Haar measure +============================== + +.. meta:: + :property="og:description": Learn all about the Haar measure and how to randomly sample quantum states. + + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/spherical_int_dtheta.png + +.. related:: + + tutorial_unitary_designs Unitary designs + quantum_volume Quantum volume + qsim_beyond_classical Beyond classical computing with qsim + tutorial_barren_plateaus Barren plateaus in quantum neural networks + + +*Author: Olivia Di Matteo — Posted: 22 March 2021. Last updated: 22 March 2021.* + +If you've ever dug into the literature about random quantum circuits, +variational ansatz structure, or anything related to the structure and +properties of unitary operations, you've likely come across a statement like the +following: "Assume that :math:`U` is sampled uniformly at random from the Haar +measure". In this demo, we're going to unravel this cryptic statement and take +an in-depth look at what it means. You'll gain an understanding of the general +concept of a *measure*, the Haar measure and its special properties, and you'll +learn how to sample from it using tools available in PennyLane and other +scientific computing frameworks. By the end of this demo, you'll be able to +include that important statement in your own work with confidence! + +.. note:: + + To get the most out of this demo, it is helpful if you are familiar with + `integration of multi-dimensional functions + `__, the `Bloch sphere + `__, and the conceptual ideas + behind `decompositions + `__ and factorizations of + unitary matrices (see, e.g., 4.5.1 and 4.5.2 of [#NandC2000]_). + +Measure +------- + +`Measure theory `__ is a +branch of mathematics that studies things that are measurable—think length, +area, or volume, but generalized to mathematical spaces and even higher +dimensions. Loosely, the measure tells you about how "stuff" is distributed and +concentrated in a mathematical set or space. An intuitive way to understand +the measure is to think about a sphere. An arbitrary point on a sphere can be +parametrized by three numbers—depending on what you're doing, you may use +Cartesian coordinates :math:`(x, y, z),` or it may be more convenient to use +spherical coordinates :math:`(\rho, \phi, \theta).` + +Suppose you wanted to compute the volume of a solid sphere with radius +:math:`r.` This can be done by integrating over the three coordinates +:math:`\rho, \phi,` and :math:`\theta.` Your first thought here may be to simply +integrate each parameter over its full range, like so: + +.. math:: + + V = \int_0^{r} \int_0^{2\pi} \int_0^{\pi} d\rho~ d\phi~ d\theta = 2\pi^2 r + +But, we know that the volume of a sphere of radius :math:`r` is +:math:`\frac{4}{3}\pi r^3,` so what we got from this integral is clearly wrong! +Taking the integral naively like this doesn't take into account the structure of +the sphere with respect to the parameters. For example, consider +two small, infinitesimal elements of area with the same difference in +:math:`\theta` and :math:`\phi,` but at different values of :math:`\theta:` + +.. figure:: /_static/demonstration_assets/haar_measure/spherical_int_dtheta.png + :align: center + :width: 50% + + +Even though the differences :math:`d\theta` and :math:`d\phi` themselves are the +same, there is way more "stuff" near the equator of the sphere than there is +near the poles. We must take into account the value of :math:`\theta` when +computing the integral! Specifically, we multiply by the function +:math:`\sin\theta`---the properties of the :math:`\sin` function mean that the +most weight will occur around the equator where :math:`\theta=\pi/2,` and the +least weight near the poles where :math:`\theta=0` and :math:`\theta=\pi.` + +Similar care must be taken for :math:`\rho.` The contribution to volume of +parts of the sphere with a large :math:`\rho` is far more than for a small +:math:`\rho`---we should expect the contribution to be proportional to +:math:`\rho^2,` given that the surface area of a sphere of radius :math:`r` is +:math:`4\pi r^2.` + +On the other hand, for a fixed :math:`\rho` and :math:`\theta,` the length of +the :math:`d\phi` is the same all around the circle. If put all these facts +together, we find that the actual expression for the integral should look like +this: + +.. math:: + + V = \int_0^r \int_0^{2\pi} \int_0^{\pi} \rho^2 \sin \theta~ d\rho~ d\phi~ + d\theta = \frac{4}{3}\pi r^3 + +These extra terms that we had to add to the integral, :math:`\rho^2 \sin +\theta`, constitute the *measure*. The measure weights portions of the sphere +differently depending on where they are in the space. While we need to know the +measure to properly integrate over the sphere, knowledge of the measure also +gives us the means to perform another important task, that of sampling points in +the space uniformly at random. We can't simply sample each parameter from the +uniform distribution over its domain—as we experienced already, this doesn't +take into account how the sphere is spread out over space. The measure describes +the distribution of each parameter and gives a recipe for sampling them in order +to obtain something properly uniform. + +The Haar measure +---------------- + +Operations in quantum computing are described by unitary matrices. +Unitary matrices, like points on a sphere, can be expressed in terms of a fixed +set of coordinates, or parameters. For example, the most general single-qubit rotation +implemented in PennyLane (:class:`~.pennylane.Rot`) is expressed in terms of three +parameters like so, + +.. math:: + + U(\phi, \theta, \omega) = \begin{pmatrix} e^{-i(\phi + \omega)/2} + \cos(\theta/2) & -e^{i(\phi - \omega)/2} \sin(\theta/2) + \\ e^{-i(\phi - \omega)/2} \sin(\theta/2) & e^{i(\phi + + \omega)/2} \cos(\theta/2) \end{pmatrix}. + +For every dimension :math:`N,` the unitary matrices of size :math:`N \times N` +constitute the *unitary group* :math:`U(N).` We can perform operations on +elements of this group, such as apply functions to them, integrate over them, or +sample uniformly over them, just as we can do to points on a sphere. When we do +such tasks with respect to the sphere, we have to add the measure in order to +properly weight the different regions of space. The *Haar measure* provides the +analogous terms we need for working with the unitary group. + +For an :math:`N`-dimensional system, the Haar measure, often denoted by +:math:`\mu_N,` tells us how to weight the elements of :math:`U(N).` For +example, suppose :math:`f` is a function that acts on elements of :math:`U(N),` +and we would like to take its integral over the group. We must write this +integral with respect to the Haar measure, like so: + +.. math:: + + \int_{V \in U(N)} f(V) d\mu_N(V). + +As with the measure term of the sphere, :math:`d\mu_N` itself can be broken down +into components depending on individual parameters. While the Haar +measure can be defined for every dimension :math:`N,` the mathematical form gets +quite hairy for larger dimensions—in general, an :math:`N`-dimensional unitary +requires at least :math:`N^2 - 1` parameters, which is a lot to keep track of! +Therefore we'll start with the case of a single qubit :math:`(N=2),` then show +how things generalize. + +Single-qubit Haar measure +~~~~~~~~~~~~~~~~~~~~~~~~~ + +The single-qubit case provides a particularly nice entry point because we can +continue our comparison to spheres by visualizing single-qubit states on the +Bloch sphere. As expressed above, the measure provides a recipe for sampling +elements of the unitary group in a properly uniform manner, given the structure +of the group. One useful consequence of this is that it provides a method to +sample quantum *states* uniformly at random—we simply generate Haar-random +unitaries, and apply them to a fixed basis state such as :math:`\vert 0\rangle.` + +We'll see how this works in good time. First, we'll take a look at what happens +when we ignore the measure and do things *wrong*. Suppose we sample quantum +states by applying unitaries obtained by the parametrization above, but sample +the angles :math:`\omega, \phi,` and :math:`\theta` from the flat uniform +distribution between :math:`[0, 2\pi)` (fun fact: there is a measure implicit in +this kind of sampling too! It just has a constant value, because each point is +equally likely to be sampled). + +""" + +import pennylane as qml +import numpy as np +import matplotlib.pyplot as plt + +# set the random seed +np.random.seed(42) + +# Use the mixed state simulator to save some steps in plotting later +dev = qml.device('default.mixed', wires=1) + +@qml.qnode(dev) +def not_a_haar_random_unitary(): + # Sample all parameters from their flat uniform distribution + phi, theta, omega = 2 * np.pi * np.random.uniform(size=3) + qml.Rot(phi, theta, omega, wires=0) + return qml.state() + +num_samples = 2021 + +not_haar_samples = [not_a_haar_random_unitary() for _ in range(num_samples)] + +###################################################################### +# In order to plot these on the Bloch sphere, we'll need to do one more +# step, and convert the quantum states into Bloch vectors. +# + +X = np.array([[0, 1], [1, 0]]) +Y = np.array([[0, -1j], [1j, 0]]) +Z = np.array([[1, 0], [0, -1]]) + +# Used the mixed state simulator so we could have the density matrix for this part! +def convert_to_bloch_vector(rho): + """Convert a density matrix to a Bloch vector.""" + ax = np.trace(np.dot(rho, X)).real + ay = np.trace(np.dot(rho, Y)).real + az = np.trace(np.dot(rho, Z)).real + return [ax, ay, az] + +not_haar_bloch_vectors = np.array([convert_to_bloch_vector(s) for s in not_haar_samples]) + +###################################################################### +# With this done, let's find out where our "uniformly random" states ended up: + +def plot_bloch_sphere(bloch_vectors): + """ Helper function to plot vectors on a sphere.""" + fig = plt.figure(figsize=(6, 6)) + ax = fig.add_subplot(111, projection='3d') + fig.subplots_adjust(left=0, right=1, bottom=0, top=1) + + ax.grid(False) + ax.set_axis_off() + ax.view_init(30, 45) + + # Draw the axes (source: https://github.com/matplotlib/matplotlib/issues/13575) + x, y, z = np.array([[-1.5,0,0], [0,-1.5,0], [0,0,-1.5]]) + u, v, w = np.array([[3,0,0], [0,3,0], [0,0,3]]) + ax.quiver(x, y, z, u, v, w, arrow_length_ratio=0.05, color="black", linewidth=0.5) + + ax.text(0, 0, 1.7, r"|0⟩", color="black", fontsize=16) + ax.text(0, 0, -1.9, r"|1⟩", color="black", fontsize=16) + ax.text(1.9, 0, 0, r"|+⟩", color="black", fontsize=16) + ax.text(-1.7, 0, 0, r"|–⟩", color="black", fontsize=16) + ax.text(0, 1.7, 0, r"|i+⟩", color="black", fontsize=16) + ax.text(0,-1.9, 0, r"|i–⟩", color="black", fontsize=16) + + ax.scatter( + bloch_vectors[:,0], bloch_vectors[:,1], bloch_vectors[:, 2], c='#e29d9e', alpha=0.3 + ) + plt.show() + +plot_bloch_sphere(not_haar_bloch_vectors) + +###################################################################### +# You can see from this plot that even though our parameters were sampled from a +# uniform distribution, there is a noticeable amount of clustering around the poles +# of the sphere. Despite the input parameters being uniform, the output is very +# much *not* uniform. Just like the regular sphere, the measure is larger near +# the equator, and if we just sample uniformly, we won't end up populating that +# area as much. To take that into account we will need to sample from the proper +# Haar measure, and weight the different parameters appropriately. +# +# For a single qubit, the Haar measure looks much like the case of a sphere, +# minus the radial component. Intuitively, all qubit state vectors have length +# 1, so it makes sense that this wouldn't play a role here. The parameter that +# we will have to weight differently is :math:`\theta,` and in fact the +# adjustment in measure is identical to that we had to do with the polar axis of +# the sphere, i.e., :math:`\sin \theta.` In order to sample the :math:`\theta` +# uniformly at random in this context, we must sample from the distribution +# :math:`\hbox{Pr}(\theta) = \sin \theta.` We can accomplish this by setting up +# a custom probability distribution with +# `rv_continuous `__ +# in ``scipy``. + +from scipy.stats import rv_continuous + +class sin_prob_dist(rv_continuous): + def _pdf(self, theta): + # The 0.5 is so that the distribution is normalized + return 0.5 * np.sin(theta) + +# Samples of theta should be drawn from between 0 and pi +sin_sampler = sin_prob_dist(a=0, b=np.pi) + +@qml.qnode(dev) +def haar_random_unitary(): + phi, omega = 2 * np.pi * np.random.uniform(size=2) # Sample phi and omega as normal + theta = sin_sampler.rvs(size=1)[0] # Sample theta from our new distribution + qml.Rot(phi, theta, omega, wires=0) + return qml.state() + +haar_samples = [haar_random_unitary() for _ in range(num_samples)] +haar_bloch_vectors = np.array([convert_to_bloch_vector(s) for s in haar_samples]) + +plot_bloch_sphere(haar_bloch_vectors) + +###################################################################### +# We see that when we use the correct measure, our qubit states are now +# much better distributed over the sphere. Putting this information together, +# we can now write the explicit form for the single-qubit Haar measure: +# +# .. math:: +# +# d\mu_2 = \sin \theta d\theta \cdot d\omega \cdot d\phi. +# +# Show me more math! +# ~~~~~~~~~~~~~~~~~~ +# +# While we can easily visualize the single-qubit case, this is no longer +# possible when we increase the number of qubits. Regardless, we can still +# obtain a mathematical expression for the Haar measure in arbitrary +# dimensions. In the previous section, we expressed the Haar measure in terms of +# a set of parameters that can be used to specify the unitary group +# :math:`U(2).` Such a parametrization is not unique, and in fact there are +# multiple ways to *factorize*, or decompose an :math:`N`-dimensional unitary +# operation into a set of parameters. +# +# Many of these parametrizations come to us from the study of photonics. Here, +# arbitrary operations are broken down into elementary operations involving only +# a few parameters which correspond directly to parameters of the physical +# apparatus used to implement them (beamsplitters and phase shifts). Rather than +# qubits, such operations act on modes, or *qumodes*. They are expressed as +# elements of the :math:`N`-dimensional `special unitary group +# `__. This group, written +# as :math:`SU(N),` is the continuous group consisting of all :math:`N \times N` +# unitary operations with determinant 1 (essentially like :math:`U(N),` minus +# a potential global phase). +# +# +# .. note:: +# +# Elements of :math:`SU(N)` and :math:`U(N)` can still be considered as +# multi-qubit operations in the cases where :math:`N` is a power of 2, but +# they must be translated from continuous-variable operations into qubit +# operations. (In PennyLane, this can be done by feeding the unitaries to +# the :class:`~.pennylane.QubitUnitary` operation directly. Alternatively, +# one can use *quantum compilation* to express the operations as a sequence +# of elementary gates such as Pauli rotations and CNOTs.) +# +# .. admonition:: Tip +# +# If you haven't had many opportunities to work in terms of qumodes, the +# `Strawberry Fields documentation +# `__ is a +# good starting point. +# +# For example, we saw already above that for :math:`N=2,` we can write +# +# .. math:: +# +# U(\phi, \theta, \omega) = \begin{pmatrix} e^{-i(\phi + \omega)/2} +# \cos(\theta/2) & -e^{i(\phi - \omega)/2} \sin(\theta/2) +# \\ e^{-i(\phi - \omega)/2} \sin(\theta/2) & e^{i(\phi + +# \omega)/2} \cos(\theta/2) \end{pmatrix}. +# +# +# This unitary can be factorized as follows: +# +# .. math:: +# +# U(\phi, \theta, \omega) = +# \begin{pmatrix} +# e^{-i\omega/2} & 0 \\ 0 & e^{i\omega/2} +# \end{pmatrix} +# \begin{pmatrix} +# \cos(\theta/2) & -\sin(\theta/2) \\ \sin(\theta/2) & \cos(\theta/2) +# \end{pmatrix} +# \begin{pmatrix} +# e^{-i\phi/2} & 0 \\ 0 & e^{i\phi/2} +# \end{pmatrix} +# +# The middle operation is a beamsplitter; the other two operations are phase +# shifts. We saw earlier that for :math:`N=2,` :math:`d\mu_2 = \sin\theta +# d\theta d\omega d\phi`---note how the parameter in the beamsplitter +# contributes to the measure in a different way than those of the phase +# shifts. As mentioned above, for larger values of :math:`N` there are multiple +# ways to decompose the unitary. Such decompositions rewrite elements in +# :math:`SU(N)` acting on :math:`N` modes as a sequence of operations acting +# only on 2 modes, :math:`SU(2),` and single-mode phase shifts. Shown below are +# three examples [#deGuise2018]_, [#Clements2016]_, [#Reck1994]_: +# +# .. figure:: /_static/demonstration_assets/haar_measure/unitaries.png +# :align: center +# :width: 95% +# +# +# In these graphics, every wire is a different mode. Every box represents an +# operation on one or more modes, and the number in the box indicates the number +# of parameters. The boxes containing a ``1`` are simply phase shifts on +# individual modes. The blocks containing a ``3`` are :math:`SU(2)` transforms +# with 3 parameters, such as the :math:`U(\phi, \theta, \omega)` above. Those +# containing a ``2`` are :math:`SU(2)` transforms on pairs of modes with 2 +# parameters, similar to the 3-parameter ones but with :math:`\omega = \phi.` +# +# Although the decompositions all produce the same set of operations, their +# structure and parametrization may have consequences in practice. The first [#deGuise2018]_ +# has a particularly convenient form that leads to a recursive definition +# of the Haar measure. The decomposition is formulated recursively such that an +# :math:`SU(N)` operation can be implemented by sandwiching an :math:`SU(2)` +# transformation between two :math:`SU(N-1)` transformations, like so: +# +# | +# +# .. figure:: /_static/demonstration_assets/haar_measure/sun.svg +# :align: center +# :width: 80% +# +# | +# +# The Haar measure is then constructed recursively as a product of 3 +# terms. The first term depends on the parameters in the first :math:`SU(N-1)` +# transformation; the second depends on the parameters in the lone :math:`SU(2)` +# transformation; and the third term depends on the parameters in the other +# :math:`SU(N-1)` transformation. +# +# :math:`SU(2)` is the "base case" of the recursion—we simply have the Haar measure +# as expressed above. +# +# | +# +# .. figure:: /_static/demonstration_assets/haar_measure/su2_haar.svg +# :align: center +# :width: 25% +# +# | +# +# Moving on up, we can write elements of :math:`SU(3)` as a sequence of three +# :math:`SU(2)` transformations. The Haar measure :math:`d\mu_3` then consists +# of two copies of :math:`d\mu_2,` with an extra term in between to take into +# account the middle transformation. +# +# | +# +# .. figure:: /_static/demonstration_assets/haar_measure/su3_haar.svg +# :align: center +# :width: 80% +# +# | +# +# For :math:`SU(4)` and upwards, the form changes slightly, but still follows +# the pattern of two copies of :math:`d\mu_{N-1}` with a term in between. +# +# | +# +# .. figure:: /_static/demonstration_assets/haar_measure/su4_premerge.svg +# :align: center +# :width: 90% +# +# | +# +# For larger systems, however, the recursive composition allows for some of the +# :math:`SU(2)` transformations on the lower modes to be grouped. We can take +# advantage of this and aggregate some of the parameters: +# +# | +# +# .. figure:: /_static/demonstration_assets/haar_measure/su4_triangle_merge.svg +# :align: center +# :width: 100% +# +# | +# +# This leads to one copy of :math:`d\mu_{N-1},` which we'll denote as +# :math:`d\mu_{N-1}^\prime,` containing only a portion of the full set of terms +# (as detailed in [#deGuise2018]_, this is called a *coset measure*). +# +# | +# +# .. figure:: /_static/demonstration_assets/haar_measure/su4_haar.svg +# :align: center +# :width: 100% +# +# | +# +# Putting everything together, we have that +# +# .. math:: +# +# d\mu_N = d\mu_{N-1}^\prime \times \sin \theta_{N-1} +# \sin^{2(N-2)}\left(\frac{\theta_{N-1}}{2}\right) d\theta_{N-1} d\omega_{N-1} \times d\mu_{N-1} +# +# The middle portion depends on the value of :math:`N,` and the parameters +# :math:`\theta_{N-1}` and :math:`\omega_{N-1}` contained in the :math:`(N-1)`'th +# :math:`SU(N)` transformation. This is thus a convenient, systematic way to +# construct the :math:`N`-dimensional Haar measure for the unitary group. As a +# final note, even though unitaries can be parametrized in different ways, the +# underlying Haar measure is *unique*. This is a consequence of it being an +# invariant measure, as will be shown later. +# +# Haar-random matrices from the :math:`QR` decomposition +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Nice-looking math aside, sometimes you just need to generate a large number of +# high-dimensional Haar-random matrices. It would be very cumbersome to sample +# and keep track of the distributions of so many parameters; furthermore, the +# measure above requires you to parametrize your operations in a fixed way. +# There is a much quicker way to perform the sampling by taking a (slightly +# modified) `QR decomposition +# `__ of complex-valued +# matrices. This algorithm is detailed in [#Mezzadri2006]_, and consists of the +# following steps: +# +# 1. Generate an :math:`N \times N` matrix :math:`Z` with complex numbers :math:`a+bi` +# where both :math:`a` and :math:`b` are normally distributed with mean 0 and variance 1 +# (this is sampling from the distribution known as the *Ginibre ensemble*). +# 2. Compute a QR decomposition :math:`Z = QR.` +# 3. Compute the diagonal matrix :math:`\Lambda = \hbox{diag}(R_{ii}/|R_{ii}|).` +# 4. Compute :math:`Q^\prime = Q \Lambda,` which will be Haar-random. +# +# + +from numpy.linalg import qr + +def qr_haar(N): + """Generate a Haar-random matrix using the QR decomposition.""" + # Step 1 + A, B = np.random.normal(size=(N, N)), np.random.normal(size=(N, N)) + Z = A + 1j * B + + # Step 2 + Q, R = qr(Z) + + # Step 3 + Lambda = np.diag([R[i, i] / np.abs(R[i, i]) for i in range(N)]) + + # Step 4 + return np.dot(Q, Lambda) + +###################################################################### +# Let's check that this method actually generates Haar-random unitaries +# by trying it out for :math:`N=2` and plotting on the Bloch sphere. +# + +@qml.qnode(dev) +def qr_haar_random_unitary(): + qml.QubitUnitary(qr_haar(2), wires=0) + return qml.state() + +qr_haar_samples = [qr_haar_random_unitary() for _ in range(num_samples)] +qr_haar_bloch_vectors = np.array([convert_to_bloch_vector(s) for s in qr_haar_samples]) +plot_bloch_sphere(qr_haar_bloch_vectors) + +###################################################################### +# As expected, we find our qubit states are distributed uniformly over the +# sphere. This particular method is what's implemented in packages like +# ``scipy``'s `unitary_group +# `__ +# function. +# +# Now, it's clear that this method works, but it is also important to +# understand *why* it works. Step 1 is fairly straightforward—the base of our +# samples is a matrix full of complex values chosen from a typical +# distribution. This isn't enough by itself, since unitary matrices also +# have constraints—their rows and columns must be orthonormal. +# These constraints are where step 2 comes in—the outcome of a generic +# QR decomposition consists of an *orthonormal* matrix :math:`Q,` and and upper +# triangular matrix :math:`R.` Since our original matrix was complex-valued, we end +# up with a :math:`Q` that is in fact already unitary. But why not stop there? Why +# do we then perform steps 3 and 4? +# +# Steps 3 and 4 are needed because, while the QR decomposition yields a unitary, +# it is not a unitary that is properly Haar-random. In [#Mezzadri2006]_, it is +# explained that a uniform distribution over unitary matrices should also yield +# a uniform distribution over the *eigenvalues* of those matrices, i.e., every +# eigenvalue should be equally likely. Just using the QR decomposition out of +# the box produces an *uneven* distribution of eigenvalues of the unitaries! +# This discrepancy stems from the fact that the QR decomposition is not unique. +# We can take any unitary diagonal matrix :math:`\Lambda,` and re-express the decomposition +# as :math:`QR = Q\Lambda \Lambda^\dagger R = Q^\prime R^\prime.` Step 3 removes this +# redundancy by fixing a :math:`\Lambda` that depends on :math:`R,` leading to a unique +# value of :math:`Q^\prime = Q \Lambda,` and a uniform distribution of eigenvalues. +# +# .. admonition:: Try it! +# +# Use the ``qr_haar`` function above to generate random unitaries and construct +# a distribution of their eigenvalues. Then, comment out the lines for steps 3 and +# 4 and do the same—you'll find that the distribution is no longer uniform. +# Check out reference [#Mezzadri2006]_ for additional details and examples. + +###################################################################### +# Fun (and not-so-fun) facts +# -------------------------- +# +# We've now learned what the Haar measure is, and both an analytical and +# numerical means of sampling quantum states and unitary operations uniformly at +# random. The Haar measure also has many neat properties that play a role in +# quantum computing. +# +# Invariance of the Haar measure +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Earlier, we showed that the Haar measure is used when integrating functions over +# the unitary group: +# +# .. math:: +# +# \int_{V \in U(N)} f(V) d\mu_N(V). +# +# One of the defining features of the Haar measure is that it is both left and +# right *invariant* under unitary transformations. That is, +# +# .. math:: +# +# \int_{V \in U(N)} f(\color{red}{W}V) d\mu_N(V) = \int_{V \in U(N)} f(V\color{red}{W}) d\mu_N(V) = \int_{V \in U(N)} f(V) d\mu_N(V). +# +# This holds true for *any* other :math:`N\times N` unitary :math:`W!` A +# consequence of such invariance is that if :math:`V` is Haar-random, then so is +# :math:`V^T,` :math:`V^\dagger,` and any product of another unitary matrix and +# :math:`V` (where the product may be taken on either side). +# +# Another consequence of this invariance has to do with the structure of the entries +# themselves: they must all come from the same distribution. This is because the +# measure remains invariant under permutations, since permutations are unitary--- +# the whole thing still has to be Haar random no matter how the entries are ordered, +# so all distributions must be the same. The specific distribution is complex +# numbers :math:`a+bi` where both :math:`a` and :math:`b` has mean 0 and variance +# :math:`1/N` [#Meckes2014]_ (so, much like Ginibre ensemble we used in the QR decomposition +# above, but with a different variance and constraints due to orthonormality). +# +# Concentration of measure +# ~~~~~~~~~~~~~~~~~~~~~~~~ +# +# An unfortunate (although interesting) property of the Haar measure is that it +# suffers from the phenomenon of `concentration of measure +# `__. Most of the +# "stuff" in the space concentrates around a certain area, and this gets worse +# as the size of the system increases. You can see the beginnings of by looking +# at the sphere. For the 3-dimensional sphere, we saw graphically how there is +# concentration around the equator, and how the measure takes that into account +# with the additional factor of :math:`\sin \theta.` This property becomes +# increasingly prominent for `higher-dimensional spheres +# `__. +# +# .. important:: +# +# The concentration described here is not referring to what we witnessed +# earlier on, when we sampled quantum states (points on the Bloch sphere) +# incorrectly and found that they clustered around the poles. However, that +# is not unrelated. Concentration of measure refers to where the measure +# itself is concentrated, and which parts of the space should be more heavily +# weighted. For the case of the sphere, it is the equatorial area, and when +# we didn't sample properly and take that concentration into account, we +# obtained an uneven distribution. +# +# Let's consider an :math:`N`-dimensional unit sphere. Points on the sphere, or +# vectors in this space, are parametrized by :math:`N-1` real coordinates. +# Suppose we have some function :math:`f` that maps points on that sphere to +# real numbers. Sample a point :math:`x` on that sphere from the uniform +# measure, and compute the value of :math:`f(x).` How close do you think the +# result will be to the mean value of the function, :math:`E[f],` over the +# entire sphere? +# +# A result called `Levy's lemma +# `__ +# [#Gerken2013]_, [#Hayden2006]_ expresses how likely it is that :math:`f(x)` is a specific +# distance away from the mean. It states that, for an :math:`x` selected +# uniformly at random, the probability that :math:`f(x)` deviates from +# :math:`E[f]` by some amount :math:`\epsilon` is bounded by: +# +# .. math:: +# +# \hbox{Pr}(|f(x) - E[f]| \ge \epsilon) \leq 2 \exp\left[-\frac{N\epsilon^2}{9\pi^3 \eta^2}\right]. +# +# A constraint on the function :math:`f` is that it must be `Lipschitz +# continuous `__, where +# :math:`\eta` is the *Lipschitz constant* of the function. The important aspect +# here is the likelihood of deviating significantly from the mean by an amount +# :math:`\epsilon` decreases exponentially with :math:`\epsilon.` Furthermore, +# increasing the dimension :math:`N` also makes the deviation exponentially less +# likely. +# +# Now, this result seems unrelated to quantum states—it concerns higher- +# dimensional spheres. However, recall that a quantum state vector is a complex +# vector whose squared values sum to 1, similar to vectors on a sphere. If you +# "unroll" a quantum state vector of dimension :math:`N = 2^n` by stacking its +# real and complex parts, you end with a vector of length :math:`2 \cdot 2^{n}` +# which ends up behaving just like a unit vector on the sphere in this +# dimension. Given that measure concentrates on spheres, and quantum state +# vectors can be converted to vectors on spheres, functions on random quantum +# states will also demonstrate concentration. +# +# This is bad news! To do useful things in quantum computing, we need a lot of +# qubits. But the more qubits we have, the more our randomly sampled states will +# look the same (specifically, random states will concentrate around the +# maximally entangled state [#Hayden2006]_). This has important consequences for +# near-term algorithms (as detailed in the next section), and any algorithm that +# involves uniform sampling of quantum states and operations. +# +# Haar measure and barren plateaus +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Suppose you are venturing out to solve a new problem using an algorithm such +# as the :doc:`variational quantum eigensolver `. A +# critical component of such methods is the choice of :doc:`variational ansatz +# `. Having now learned a bit about the properties of +# the Haar measure, you may think it would make sense to use this for the +# parametrization. Variational ansaetze are, after all, parametrized quantum +# circuits, so why not choose an ansatz that corresponds directly to a +# parametrization for Haar-random unitaries? The initial parameter selection +# will give you a state in the Hilbert space uniformly at random. Then, since +# this ansatz spans the entire Hilbert space, you're guaranteed to be able to +# represent the target ground state with your ansatz, and it should be able to +# find it with no issue ... right? +# +# Unfortunately, while such an ansatz is extremely *expressive* (i.e., it is +# capable of representing any possible state), these ansaetze actually suffer +# the most from the barren plateau problem [#McClean2018]_, [#Holmes2021]_. +# :doc:`Barren plateaus ` are regions in the +# cost landscape of a parametrized circuit where both the gradient and its +# variance approach 0, leading the optimizer to get stuck in a local minimum. +# This was explored recently in the work of [#Holmes2021]_, wherein closeness to +# the Haar measure was actually used as a metric for expressivity. The closer +# things are to the Haar measure, the more expressive they are, but they are +# also more prone to exhibiting barren plateaus. +# +# +# .. figure:: /_static/demonstration_assets/haar_measure/holmes-costlandscapes.png +# :align: center +# :width: 50% +# +# Image source: [#Holmes2021]_. A highly expressive ansatz that can access +# much of the space of possible unitaries (i.e., an ansatz capable of +# producing unitaries in something close to a Haar-random manner) is very +# likely to have flat cost landscapes and suffer from the barren plateau +# problem. +# +# It turns out that the types of ansaetze know as *hardware-efficient ansaetze* +# also suffer from this problem if they are "random enough" (this notion will be +# formalized in a future demo). It was shown in [#McClean2018]_ that this is a +# consequence of the concentration of measure phenomenon described above. The +# values of gradients and variances can be computed for classes of circuits on +# average by integrating with respect to the Haar measure, and it is shown that +# these values decrease exponentially in the number of qubits, and thus huge +# swaths of the cost landscape are simply and fundamentally flat. +# +# Conclusion +# ---------- +# +# The Haar measure plays an important role in quantum computing—anywhere +# you might be dealing with sampling random circuits, or averaging over +# all possible unitary operations, you'll want to do so with respect +# to the Haar measure. +# +# There are two important aspects of this that we have yet to touch upon, +# however. The first is whether it is efficient to sample from the Haar measure—given +# that the number of parameters to keep track of is exponential in the +# number of qubits, certainly not. But a more interesting question is do we +# *need* to always sample from the full Haar measure? The answer to this is +# "no" in a very interesting way. Depending on the task at hand, you may be able +# to take a shortcut using something called a *unitary design*. In an upcoming +# demo, we will explore the amazing world of unitary designs and their +# applications! +# +# References +# ---------- +# +# .. [#NandC2000] +# +# M. A. Nielsen, and I. L. Chuang (2000) "Quantum Computation and Quantum Information", +# Cambridge University Press. +# +# .. [#deGuise2018] +# +# H. de Guise, O. Di Matteo, and L. L. Sánchez-Soto. (2018) "Simple factorization +# of unitary transformations", `Phys. Rev. A 97 022328 +# `__. +# (`arXiv `__) +# +# .. [#Clements2016] +# +# W. R. Clements, P. C. Humphreys, B. J. Metcalf, W. S. Kolthammer, and +# I. A. Walmsley (2016) “Optimal design for universal multiport +# interferometers”, \ `Optica 3, 1460–1465 +# `__. +# (`arXiv `__) +# +# .. [#Reck1994] +# +# M. Reck, A. Zeilinger, H. J. Bernstein, and P. Bertani (1994) “Experimental +# realization of any discrete unitary operator”, `Phys. Rev. Lett.73, 58–61 +# `__. +# +# .. [#Mezzadri2006] +# +# F. Mezzadri (2006) "How to generate random matrices from the classical compact groups". +# (`arXiv `__) +# +# .. [#Meckes2014] +# +# E. Meckes (2019) `"The Random Matrix Theory of the Classical Compact Groups" +# `_, Cambridge University Press. +# +# .. [#Gerken2013] +# +# M. Gerken (2013) "Measure concentration: Levy's Lemma" +# (`lecture notes `__). +# +# +# .. [#Hayden2006] +# +# P. Hayden, D. W. Leung, and A. Winter (2006) "Aspects of generic +# entanglement", `Comm. Math. Phys. Vol. 265, No. 1, pp. 95-117 +# `__. +# (`arXiv `__) +# +# .. [#McClean2018] +# +# J. R. McClean, S. Boixo, V. N. Smelyanskiy, R. Babbush, and H. Neven +# (2018) "Barren plateaus in quantum neural network training +# landscapes", `Nature Communications, 9(1) +# `__. +# (`arXiv `__) +# +# .. [#Holmes2021] +# +# Z. Holmes, K. Sharma, M. Cerezo, and P. J. Coles (2021) "Connecting ansatz +# expressibility to gradient magnitudes and barren plateaus". (`arXiv +# `__) # \ No newline at end of file diff --git a/demonstrations/tutorial_here_comes_the_sun.py b/demonstrations/tutorial_here_comes_the_sun.py index 88da27d3e5..5278e01fd1 100644 --- a/demonstrations/tutorial_here_comes_the_sun.py +++ b/demonstrations/tutorial_here_comes_the_sun.py @@ -1,576 +1,576 @@ -r""" - -Here comes the SU(N): multivariate quantum gates and gradients -============================================================== - -.. meta:: - :property="og:description": Learn about multivariate quantum gates for optimization - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_here_comes_the_sun.png - -.. related:: - - tutorial_vqe A brief overview of VQE - tutorial_general_parshift General parameter-shift rules for quantum gradients - tutorial_unitary_designs Unitary designs and their uses in quantum computing - - -*Author: David Wierichs — Posted: 03 April 2023.* - -How do we choose an ansatz when designing a quantum circuit for a variational -quantum algorithm? And what happens if we do not start with elementary hardware-friendly -gates and compose them, but we instead use a more complex building block for local qubit -interactions and allow for multi-parameter gates from the start? -Can we differentiate such circuits, and how do they perform in optimization tasks? - -Let's find out! - -In this tutorial, you will learn about the :math:`\mathrm{SU}(N)` gate -:class:`~pennylane.SpecialUnitary`, a particular quantum gate which -can act like *any* gate on its qubits by choosing the parameters accordingly. -We will look at a custom derivative rule [#wiersema]_ for this gate and compare it to two -alternative differentiation strategies, namely finite differences and the `stochastic -parameter-shift rule `_. -Finally, we will compare the performance of -``qml.SpecialUnitary`` for a toy minimization problem to that of two other general -local gates. That is, we compare the trainability of equally expressive ansätze. - -Ansätze, so many ansätze ------------------------- - -Variational quantum algorithms have been promoted to be useful for many applications. -When designing these algorithms, a central task is to choose the quantum circuit ansatz, -which provides a parameterization of quantum states. In the course of a variational algorithm, -the circuit parameters are then optimized in order to minimize some cost function. -The choice of the ansatz can have a big impact on the quantum states that can be found -by the algorithm (expressivity) and on the optimization's behaviour (trainability). - -Typically, it also affects the -computational cost of executing the algorithm on quantum hardware and the strength of the noise -that enters the computation. Finally, the application itself influences, or -even fixes, the choice of ansatz for some variational quantum algorithms, -which can lead to constraints in the ansatz design. - -.. figure:: ../_static/demonstration_assets/here_comes_the_sun/SUN_demo_Ansatz.png - :align: center - :width: 90% - -While a number of best practices for ansatz design have been developed, -a lot is still unknown about the connection between circuit structures and the -resulting properties. Therefore, circuit design is often also based on intuition or heuristics; -an ansatz reported in the literature might just have turned out -to work particularly well for a given problem or might fall into a "standard" -category of circuits. - -If the application does not constrain the choice of ansatz, we may want to avoid choosing -somewhat arbitrary circuit ansätze that may introduce undesirable biases. -Instead, we will want to reflect the generic structure of the problem by performing a -fully general operation on the qubit register. -However, if we were to do so, the number of parameters required to produce such a general -operation would grow much too quickly. Instead, we want to consider fully general operations -*on a few qubits* and compose them into a fabric of local gates. For two-qubit operations, -the fabric could look like this: - -.. figure:: ../_static/demonstration_assets/here_comes_the_sun/SUN_demo_SU4.png - :align: center - :width: 60% - -The general local operation can be implemented by composing a suitable combination -of elementary gates, like single-qubit rotations and CNOT gates. Alternatively, we may -choose a canonical parameterization of the group that contains all local operations, and we will -see that this is an advantageous approach for the trainability of the ansatz. - -.. figure:: ../_static/demonstration_assets/here_comes_the_sun/SUN_demo_optimization.png - :align: center - :width: 60% - -Before we can use the :math:`\mathrm{SU}(N)` gate in training, we will need to -learn how to differentiate it in a quantum circuit. But first things first: -let's start with a brief math intro — no really, just a *Liettle* bit. - -The special unitary group SU(N) and its Lie algebra ---------------------------------------------------- - -The gate we will look at is given by a specific parameterization of the -`special unitary group `__ -:math:`\mathrm{SU}(N),` where :math:`N=2^n` is the Hilbert space dimension of the gate -for :math:`n` qubits. Mathematically, the group can be defined as the set of operators -(or matrices) that can be inverted by taking their adjoint and that have -determinant :math:`1.` In general, all quantum gates acting on :math:`n` qubits are -elements of :math:`\mathrm{SU}(N)` up to a global phase. - -The group :math:`\mathrm{SU}(N)` is a `Lie group `__, -and its associated `Lie algebra `__ -is :math:`\mathfrak{su}(N).` For our purposes, it will be sufficient to look at a matrix -representation of the algebra and we may define it as - -.. math:: - - \mathfrak{su}(N) = - \{\Omega \in \mathbb{C}^{N\times N}: \Omega^\dagger=-\Omega, \operatorname{Tr}[\Omega]=0\}. - -The conditions are that the elements :math:`\Omega` are *skew-Hermitian* and that their trace vanishes. -We will use so-called canonical coordinates for the algebra which are simply the coefficients -in the Pauli basis. That is, we consider the Pauli basis elements multiplied with the -imaginary unit :math:`i,` except for the identity: - -.. math:: - - G_m \in \mathcal{P}^{(n)} = i \left\{I,X,Y,Z\right\}^n \setminus \{i I^n\}. - -A Lie algebra element :math:`\Omega` can be written as - -.. math:: - - \Omega = \sum_{m=1}^d \theta_m G_m,\quad \theta_m \in \mathbb{R} - -and those coefficients :math:`\theta` are precisely the canonical coordinates. -You may ask why we included the prefactor :math:`i` in the definition of :math:`G_m` and why we excluded -the identity (times :math:`i`). This was done to match the properties of :math:`\mathfrak{su}(N);` -the prefactor makes the basis elements skew-Hermitian and the identity would not have a -vanishing trace. Indeed, one can check that the dimension of :math:`\mathfrak{su}(N)` is -:math:`4^n-1` and that there are :math:`4^n` Pauli words, so that one Pauli word — the identity — had to go -in any case... We can use the canonical coordinates of the algebra to express a *group element* in -:math:`\mathrm{SU}(N)` as well, and the ``qml.SpecialUnitary`` gate we will use is defined as - -.. math:: - - U(\boldsymbol{\theta}) = \exp\left\{\sum_{m=1}^d \theta_m G_m \right\}. - -The number of coordinates and Pauli words in :math:`\mathcal{P}^{(n)}` is :math:`d=4^n-1.` -Therefore, this will be the number of parameters that a single ``qml.SpecialUnitary`` gate acting on -:math:`n` qubits will take. For example, it takes just three parameters for a single qubit, which -is why :class:`~pennylane.Rot` and :class:`~pennylane.U3` take three parameters and may -produce *any* single-qubit rotation. It takes a modest 15 parameters for two qubits, -but it already requires 63 parameters for three qubits. - -For unitaries generated by a single operator, i.e. of the form :math:`\exp(i\theta G),` -there is a plethora of differentiation techniques that allow us to compute its derivative. -However, a standard parameter-shift rule, for example, will not do the job if there are -non-commuting terms :math:`G_m` in the multi-parameter gate :math:`U(\boldsymbol{\theta})` above. -So how *do* we compute the derivative? - -Obtaining the gradient ----------------------- - -In variational quantum algorithms, we typically use the circuit to prepare a quantum state and -then we measure some observable :math:`H.` The resulting real-valued output is considered to be the -cost function :math:`C` that should be minimized. If we want to use gradient-based optimization for -this task, we need a method to compute the gradient :math:`\nabla C` in addition to the cost -function itself. As derived in the publication [#wiersema]_, this is possible on quantum hardware -for :math:`\mathrm{SU}(N)` gates as long as the gates themselves can be implemented. -The implementation in PennyLane follows the decomposition idea described in App. F3, but the -main text of [#wiersema]_ proposes an additional method that scales better in some scenarios -(the caveat being that this method requires additional gates to be available on the quantum hardware). -Here, we will focus on the former method. -We will not go through the entire derivation, but note the following key points: - -- The gradient with respect to all :math:`d` parameters of an :math:`\mathrm{SU}(N)` gate can be - computed using :math:`2d` auxiliary circuits. Each of the circuits contains one additional - operation compared to the original circuit, namely a ``qml.PauliRot`` gate with rotation - angles of :math:`\pm\frac{\pi}{2}.` Note that these Pauli rotations act on up to :math:`n` - qubits. -- This differentiation method uses automatic differentiation during compilation and - classical coprocessing steps, but is compatible with quantum hardware. For large :math:`n,` - the classical processing steps can quickly become prohibitively expensive. -- The computed gradient is not an approximative technique but allows for an exact computation - of the gradient on simulators. On quantum hardware, this leads to unbiased gradient - estimators. - -The implementation in PennyLane takes care of creating the additional circuits and evaluating -them, and with adequate post-processing we get the gradient :math:`\nabla C.` - -Comparing gradient methods --------------------------- - -Before we dive into using ``qml.SpecialUnitary`` in an optimization task, let's compare -a few methods to compute the gradient with respect to the parameters of such a gate. -In particular, we will look at a finite difference (FD) approach, the stochastic parameter-shift -rule, and the custom gradient method we described above. - -For the first approach, we will use the standard central difference recipe given by - -.. math:: - - \partial_{\text{FD},\theta_j}C(\boldsymbol{\theta}) - =\left[C\left(\boldsymbol{\theta}+\frac{\delta}{2}\boldsymbol{e}_j\right) - -C\left(\boldsymbol{\theta}-\frac{\delta}{2}\boldsymbol{e}_j\right)\right] / \delta. - -Here, :math:`\delta` is a shift parameter that we need to choose and :math:`\boldsymbol{e}_j` is the -:math:`j`-th canonical basis vector, i.e. the all-zeros vector with a one in the -:math:`j`-th entry. This approach is agnostic to the differentiated function and does -not exploit its structure. - -In contrast, the stochastic parameter-shift rule is a differentiation recipe developed particularly -for multi-parameter gates like the :math:`\mathrm{SU}(N)` gates [#banchi]_. It involves the -approximate evaluation of an integral by sampling *splitting times* :math:`\tau` and -evaluating an expression close to the non-stochastic parameter-shift rule for each sample. -For more details, also consider the -:doc:`demo on the stochastic parameter-shift rule `. - -So, let's dive into a toy example and explore the three gradient methods! -We start by defining a simple one-qubit circuit that contains a single :math:`\mathrm{SU}(2)` -gate and measures the expectation value of :math:`H=\frac{3}{5} Z - \frac{4}{5} Y.` -As ``qml.SpecialUnitary`` requires automatic differentiation subroutines even for the -hardware-ready derivative recipe, we will make use of JAX. -""" - -import pennylane as qml -import numpy as np -import jax - -jax.config.update("jax_enable_x64", True) -jax.config.update("jax_platform_name", "cpu") -jnp = jax.numpy - -dev = qml.device("default.qubit", wires=1) -H = 0.6 * qml.PauliZ(0) - 0.8 * qml.PauliY(0) - - -def qfunc(theta): - qml.SpecialUnitary(theta, wires=0) - return qml.expval(H) - - -circuit = qml.QNode(qfunc, dev, interface="jax", diff_method="parameter-shift") - -theta = jnp.array([0.4, 0.2, -0.5]) - -############################################################################## -# Now we need to set up the differentiation methods. For this demonstration, we will -# keep the first and last entry of ``theta`` fixed and only compute the gradient for the -# second parameter. This allows us to visualize the results easily and keeps the -# computational effort to a minimum. -# -# We start with the finite-difference -# recipe, using a shift scale of :math:`\delta=0.75.` This choice of :math:`\delta,` -# which is much larger than usual for numerical differentiation on classical computers, -# is adapted to the scenario of shot-based gradients (see App. F2 of [#wiersema]_). -# We compute the derivative with respect to the second entry of theta, so we will use -# the unit vector :math:`e_2:` - -unit_vector = np.array([0.0, 1.0, 0.0]) - - -def central_diff_grad(theta, delta): - plus_eval = circuit(theta + delta / 2 * unit_vector) - minus_eval = circuit(theta - delta / 2 * unit_vector) - return (plus_eval - minus_eval) / delta - - -delta = 0.75 -print(f"Central difference: {central_diff_grad(theta, delta):.5f}") - -############################################################################## -# Next up, we implement the stochastic parameter-shift rule. Of course we do not do -# so in full generality, but for the particular circuit in this example. We will -# sample ten splitting times to obtain the gradient entry. For each splitting time, -# we need to insert a Pauli-:math:`Y` rotation because :math:`\theta_2` belongs to -# the Pauli-:math:`Y` component of :math:`A(\boldsymbol{\theta}).` For this, we define -# an auxiliary circuit. - - -@jax.jit -@qml.qnode(dev, interface="jax") -def aux_circuit(theta, tau, sign): - qml.SpecialUnitary(tau * theta, wires=0) - # This corresponds to the parameter-shift evaluations of RY at 0 - qml.RY(-sign * np.pi / 2, wires=0) - qml.SpecialUnitary((1 - tau) * theta, wires=0) - return qml.expval(H) - - -def stochastic_parshift_grad(theta, num_samples): - grad = 0 - splitting_times = np.random.random(size=num_samples) - for tau in splitting_times: - # Evaluate the two-term parameter-shift rule of the auxiliar circuit - grad += aux_circuit(theta, tau, 1.0) - aux_circuit(theta, tau, -1.0) - return grad / num_samples - - -num_samples = 10 -print(f"Stochastic parameter-shift: {stochastic_parshift_grad(theta, num_samples):.5f}") - -############################################################################## -# Finally, we can make use of the custom parameter-shift rule introduced in -# [#wiersema]_, which is readily available in PennyLane. Due to the implementation -# chosen internally, the full gradient is returned; we need to pick the second -# gradient entry manually. For this small toy problem, this is -# not an issue. - -sun_grad = jax.grad(circuit) -print(f"Custom SU(N) gradient: {sun_grad(theta)[1]:.5f}") - -############################################################################## -# We obtained three values for the gradient of interest, and they do not agree. -# So what is going on here? First, let's use automatic differentiation to compute -# the exact value and see which method agrees with it (we again need to extract the -# corresponding entry from the full gradient). - -autodiff_circuit = qml.QNode(qfunc, dev, interface="jax", diff_method="parameter-shift") -exact_grad = jax.grad(autodiff_circuit)(theta)[1] -print(f"Exact gradient: {exact_grad:.5f}") - -############################################################################## -# As we can see, automatic differentiation confirmed that the custom differentiation method -# gave us the correct result. Why do the other methods disagree? -# This is because the finite difference recipe is an *approximate* gradient -# method. This means it has an error even if all circuit evaluations are -# made exact (up to numerical precision) like in the example above. -# As for the stochastic parameter-shift rule, you may already guess why there is -# a deviation: indeed, the *stochastic* nature of this method leads to derivative -# values that are scattered around the true value. It is an unbiased estimator, -# so the average will approach the exact value with increasingly many evaluations. -# To demonstrate this, let's compute the same derivative many times and plot -# a histogram of what we get. We'll do so for ``num_samples=2``, ``10`` and ``100``. - -import matplotlib.pyplot as plt - -plt.rcParams.update({"font.size": 12}) - -fig, ax = plt.subplots(1, 1, figsize=(6, 4)) -colors = ["#ACE3FF", "#FF87EB", "#FFE096"] -for num_samples, color in zip([2, 10, 100], colors): - grads = [stochastic_parshift_grad(theta, num_samples) for _ in range(1000)] - ax.hist(grads, label=f"{num_samples} samples", alpha=0.9, color=color) -ylim = ax.get_ylim() -ax.plot([exact_grad] * 2, ylim, ls="--", c="k", label="Exact") -ax.set(xlabel=r"$\partial_{SPS,\theta_2}C(\theta)$", ylabel="Frequency", ylim=ylim) -ax.legend(loc="upper left") -plt.tight_layout() -plt.show() - -############################################################################## -# As we can see, the stochastic parameter-shift rule comes with a variance -# that can be reduced at the additional cost of evaluating the auxiliary circuit -# for more splitting times. -# -# On quantum hardware, all measurement results are statistical in nature anyway. -# So how does this stochasticity combine with the -# three differentiation methods? We will not go into detail here, but refer -# to [#wiersema]_ to see how the custom differentiation rule proposed in the -# main text leads to the lowest mean squared error. For a single-qubit circuit -# similar to the one above, but with the single gate :math:`U(\boldsymbol{\theta})=\exp(iaX+ibY),` -# the derivative and its expected variance are shown in the following -# (recoloured) plot from the manuscript: -# -# .. figure:: ../_static/demonstration_assets/here_comes_the_sun/sampled_grad.png -# :align: center -# :width: 70% -# -# As we can see, the custom :math:`\mathrm{SU}(N)` parameter-shift rule produces the -# gradient estimates with the smallest variance. For small values of the parameter -# :math:`b,` which is fixed for each panel, the custom shift rule and the stochastic -# shift rule approach the standard two-term parameter-shift rule, which would be exact -# for :math:`b=0.` -# The finite difference gradient shown here was obtained using the shift -# scale :math:`\delta=0.75,` as well. As we can see, this suppresses the variance down to -# a level comparable to those of the shift rule derivatives and this shift scale is a -# reasonable trade-off between the variance and the systematic error we observed earlier. -# As shown in App. F3 of [#wiersema]_, this scale is indeed close to the optimal choice -# if we were to compute the gradient with 100 shots per circuit. -# -# Comparing ansatz structures -# --------------------------- -# -# We discussed above that there are many circuit architectures available and that choosing -# a suitable ansatz is important but can be difficult. Here, we will compare a simple ansatz -# based on the ``qml.SpecialUnitary`` gate discussed above to other approaches that fully -# parametrize the special unitary group for the respective number of qubits. -# In particular, we will compare ``qml.SpecialUnitary`` to standard decompositions from the -# literature that parametrize :math:`\mathrm{SU}(N)` with elementary gates, as well as to a -# sequence of Pauli rotation gates that also allows us to create any special unitary. -# Let us start by defining the decomposition of a two-qubit unitary. -# We choose the decomposition, which is optimal but not unique, from [#vatan]_. -# The Pauli rotation sequence is available in PennyLane -# via ``qml.ArbitraryUnitary`` and we will not need to implement it ourselves. - - -def two_qubit_decomp(params, wires): - """Implement an arbitrary SU(4) gate on two qubits - using the decomposition from Theorem 5 in - https://arxiv.org/pdf/quant-ph/0308006.pdf""" - i, j = wires - # Single U(2) parameterization on both qubits separately - qml.Rot(*params[:3], wires=i) - qml.Rot(*params[3:6], wires=j) - qml.CNOT(wires=[j, i]) # First CNOT - qml.RZ(params[6], wires=i) - qml.RY(params[7], wires=j) - qml.CNOT(wires=[i, j]) # Second CNOT - qml.RY(params[8], wires=j) - qml.CNOT(wires=[j, i]) # Third CNOT - # Single U(2) parameterization on both qubits separately - qml.Rot(*params[9:12], wires=i) - qml.Rot(*params[12:15], wires=j) - - -# The three building blocks on two qubits we will compare are: -operations = { - ("Decomposition", "decomposition"): two_qubit_decomp, - ("PauliRot sequence",) * 2: qml.ArbitraryUnitary, - ("$\mathrm{SU}(N)$ gate", "SU(N) gate"): qml.SpecialUnitary, -} - -############################################################################## -# Now that we have the template for the composition approach in place, we construct a toy -# problem to solve using the ansätze. We will sample a random Hamiltonian in the Pauli basis -# (this time without the prefactor :math:`i,` as we want to construct a Hermitian operator) -# with independent coefficients that follow a normal distribution: -# -# .. math:: -# -# H = \sum_{m=1}^d h_m G_m,\quad h_m\sim \mathcal{N}(0,1). -# -# We will work with six qubits. - -num_wires = 6 -wires = list(range(num_wires)) -np.random.seed(62213) - -coefficients = np.random.randn(4**num_wires - 1) -# Create the matrices for the entire Pauli basis -basis = qml.ops.qubit.special_unitary.pauli_basis_matrices(num_wires) -# Construct the Hamiltonian from the normal random coefficients and the basis -H_matrix = qml.math.tensordot(coefficients, basis, axes=[[0], [0]]) -H = qml.Hermitian(H_matrix, wires=wires) -# Compute the ground state energy -E_min = min(qml.eigvals(H)) -print(f"Ground state energy: {E_min:.5f}") - -############################################################################## -# Using the toy problem Hamiltonian and the three ansätze for :math:`\mathrm{SU}(N)` operations -# from above, we create a circuit template that applies these operations in a brick-layer -# architecture with two blocks and each operation acting on ``loc=2`` qubits. -# For this we define a ``QNode``: - -loc = 2 -d = loc**4 - 1 # d = 15 for two-qubit operations -dev = qml.device("default.qubit", wires=num_wires) -# two blocks with two layers. Each layer contains three operations with d parameters -param_shape = (2, 2, 3, d) -init_params = np.zeros(param_shape) - - -def circuit(params, operation=None): - """Apply an operation in a brickwall-like pattern to a qubit register and measure H. - Parameters are assumed to have the dimensions (number of blocks, number of - wires per operation, number of operations per layer, and number of parameters - per operation), in that order. - """ - for params_block in params: - for i, params_layer in enumerate(params_block): - for j, params_op in enumerate(params_layer): - wires_op = [w % num_wires for w in range(loc * j + i, loc * (j + 1) + i)] - operation(params_op, wires_op) - return qml.expval(H) - - -qnode = qml.QNode(circuit, dev, interface="jax") -print(qml.draw(qnode)(init_params, qml.SpecialUnitary)) - -############################################################################## -# We can now proceed to prepare the optimization task using this circuit -# and an optimization routine of our choice. For simplicity, we run a vanilla gradient -# descent optimization with a fixed learning rate for 500 steps. Again, we use JAX - -# for auto-differentiation. - -learning_rate = 5e-4 -num_steps = 500 -init_params = jax.numpy.array(init_params) -grad_fn = jax.jit(jax.jacobian(qnode), static_argnums=1) -qnode = jax.jit(qnode, static_argnums=1) - -############################################################################## -# With this configuration, let's run the optimization! - -energies = {} -for (name, print_name), operation in operations.items(): - print(f"Running the optimization for the {print_name}") - params = init_params.copy() - energy = [] - for step in range(num_steps): - cost = qnode(params, operation) - params = params - learning_rate * grad_fn(params, operation) - energy.append(cost) # Store energy value - if step % 50 == 0: # Report current energy - print(f"{step:3d} Steps: {cost:.6f}") - - energy.append(qnode(params, operation)) # Final energy value - energies[name] = energy - -############################################################################## -# So, did it work? Judging from the intermediate energy values, it seems that the optimization -# outcomes differ notably. But let's take a look at the relative error in energy across the -# optimization process. - -fig, ax = plt.subplots(1, 1) -styles = [":", "--", "-"] -colors = ["#70CEFF", "#C756B2", "#FFE096"] -for (name, energy), c, ls in zip(energies.items(), colors, styles): - error = (energy - E_min) / abs(E_min) - ax.plot(list(range(len(error))), error, label=name, c=c, ls=ls, lw=2.5) - -ax.set(xlabel="Iteration", ylabel="Relative error") -ax.legend() -plt.show() - -############################################################################## -# We find that the optimization indeed performs significantly better for ``qml.SpecialUnitary`` -# than for the other two general unitaries, while using the same number of parameters and -# preserving the expressibility of the circuit ansatz. This -# means that we found a particularly well-trainable parameterization of the local unitaries which -# allows us to reduce the energy of the prepared quantum state more easily while maintaining the -# number of parameters. -# -# -# Conclusion -# ---------- -# -# To summarize, in this tutorial we introduced ``qml.SpecialUnitary``, a multi-parameter -# gate that can act like *any* gate on the qubits it is applied to and that is constructed -# with Lie theory in mind. We discussed three methods of differentiating quantum circuits -# that use this gate, showing that a new custom parameter-shift rule presented in -# [#wiersema]_ is particularly suitable to produce unbiased gradient estimates with the -# lowest variance. Afterwards, we used this differentiation technique when comparing -# the performance of ``qml.SpecialUnitary`` to that of other gates that can act -# like *any* gate locally. For this, we ran a gradient-based optimization for a toy model -# Hamiltonian and found that ``qml.SpecialUnitary`` is particularly well-trainable, achieving -# lower energies significantly quicker than the other tested gates. -# -# There are still exciting questions to answer about ``qml.SpecialUnitary``: How can the -# custom parameter-shift rule be used for other gates, and what does the so-called -# *Dynamical Lie algebra* of these gates have to do with it? How can we implement -# the ``qml.SpecialUnitary`` gate on hardware? Is the unitary time evolution implemented -# by this gate special in a physical sense? -# -# The answers to some, but not all, of these questions can be found in [#wiersema]_. -# We are certain that there are many more interesting aspects of this gate to be uncovered! -# If you want to learn more, consider the other literature references below, -# as well as the documentation of :class:`~pennylane.SpecialUnitary`. -# -# References -# ---------- -# -# .. [#vatan] -# -# Farrokh Vatan and Colin Williams, -# "Optimal Quantum Circuits for General Two-Qubit Gates", -# `arXiv:quant-ph/0308006 `__ (2003). -# -# .. [#wiersema] -# -# R. Wiersema, D. Lewis, D. Wierichs, J. F. Carrasquilla, and N. Killoran. -# "Here comes the SU(N): multivariate quantum gates and gradients" -# `arXiv:2303.11355 `__ (2023). -# -# .. [#banchi] -# -# Leonardo Banchi and Gavin E. Crooks. "Measuring Analytic Gradients of -# General Quantum Evolution with the Stochastic Parameter Shift Rule." -# `Quantum 5, 386 `__ (2021). -# -# About the author -# ---------------- -# +r""" + +Here comes the SU(N): multivariate quantum gates and gradients +============================================================== + +.. meta:: + :property="og:description": Learn about multivariate quantum gates for optimization + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_here_comes_the_sun.png + +.. related:: + + tutorial_vqe A brief overview of VQE + tutorial_general_parshift General parameter-shift rules for quantum gradients + tutorial_unitary_designs Unitary designs and their uses in quantum computing + + +*Author: David Wierichs — Posted: 03 April 2023.* + +How do we choose an ansatz when designing a quantum circuit for a variational +quantum algorithm? And what happens if we do not start with elementary hardware-friendly +gates and compose them, but we instead use a more complex building block for local qubit +interactions and allow for multi-parameter gates from the start? +Can we differentiate such circuits, and how do they perform in optimization tasks? + +Let's find out! + +In this tutorial, you will learn about the :math:`\mathrm{SU}(N)` gate +:class:`~pennylane.SpecialUnitary`, a particular quantum gate which +can act like *any* gate on its qubits by choosing the parameters accordingly. +We will look at a custom derivative rule [#wiersema]_ for this gate and compare it to two +alternative differentiation strategies, namely finite differences and the `stochastic +parameter-shift rule `_. +Finally, we will compare the performance of +``qml.SpecialUnitary`` for a toy minimization problem to that of two other general +local gates. That is, we compare the trainability of equally expressive ansätze. + +Ansätze, so many ansätze +------------------------ + +Variational quantum algorithms have been promoted to be useful for many applications. +When designing these algorithms, a central task is to choose the quantum circuit ansatz, +which provides a parameterization of quantum states. In the course of a variational algorithm, +the circuit parameters are then optimized in order to minimize some cost function. +The choice of the ansatz can have a big impact on the quantum states that can be found +by the algorithm (expressivity) and on the optimization's behaviour (trainability). + +Typically, it also affects the +computational cost of executing the algorithm on quantum hardware and the strength of the noise +that enters the computation. Finally, the application itself influences, or +even fixes, the choice of ansatz for some variational quantum algorithms, +which can lead to constraints in the ansatz design. + +.. figure:: ../_static/demonstration_assets/here_comes_the_sun/SUN_demo_Ansatz.png + :align: center + :width: 90% + +While a number of best practices for ansatz design have been developed, +a lot is still unknown about the connection between circuit structures and the +resulting properties. Therefore, circuit design is often also based on intuition or heuristics; +an ansatz reported in the literature might just have turned out +to work particularly well for a given problem or might fall into a "standard" +category of circuits. + +If the application does not constrain the choice of ansatz, we may want to avoid choosing +somewhat arbitrary circuit ansätze that may introduce undesirable biases. +Instead, we will want to reflect the generic structure of the problem by performing a +fully general operation on the qubit register. +However, if we were to do so, the number of parameters required to produce such a general +operation would grow much too quickly. Instead, we want to consider fully general operations +*on a few qubits* and compose them into a fabric of local gates. For two-qubit operations, +the fabric could look like this: + +.. figure:: ../_static/demonstration_assets/here_comes_the_sun/SUN_demo_SU4.png + :align: center + :width: 60% + +The general local operation can be implemented by composing a suitable combination +of elementary gates, like single-qubit rotations and CNOT gates. Alternatively, we may +choose a canonical parameterization of the group that contains all local operations, and we will +see that this is an advantageous approach for the trainability of the ansatz. + +.. figure:: ../_static/demonstration_assets/here_comes_the_sun/SUN_demo_optimization.png + :align: center + :width: 60% + +Before we can use the :math:`\mathrm{SU}(N)` gate in training, we will need to +learn how to differentiate it in a quantum circuit. But first things first: +let's start with a brief math intro — no really, just a *Liettle* bit. + +The special unitary group SU(N) and its Lie algebra +--------------------------------------------------- + +The gate we will look at is given by a specific parameterization of the +`special unitary group `__ +:math:`\mathrm{SU}(N),` where :math:`N=2^n` is the Hilbert space dimension of the gate +for :math:`n` qubits. Mathematically, the group can be defined as the set of operators +(or matrices) that can be inverted by taking their adjoint and that have +determinant :math:`1.` In general, all quantum gates acting on :math:`n` qubits are +elements of :math:`\mathrm{SU}(N)` up to a global phase. + +The group :math:`\mathrm{SU}(N)` is a `Lie group `__, +and its associated `Lie algebra `__ +is :math:`\mathfrak{su}(N).` For our purposes, it will be sufficient to look at a matrix +representation of the algebra and we may define it as + +.. math:: + + \mathfrak{su}(N) = + \{\Omega \in \mathbb{C}^{N\times N}: \Omega^\dagger=-\Omega, \operatorname{Tr}[\Omega]=0\}. + +The conditions are that the elements :math:`\Omega` are *skew-Hermitian* and that their trace vanishes. +We will use so-called canonical coordinates for the algebra which are simply the coefficients +in the Pauli basis. That is, we consider the Pauli basis elements multiplied with the +imaginary unit :math:`i,` except for the identity: + +.. math:: + + G_m \in \mathcal{P}^{(n)} = i \left\{I,X,Y,Z\right\}^n \setminus \{i I^n\}. + +A Lie algebra element :math:`\Omega` can be written as + +.. math:: + + \Omega = \sum_{m=1}^d \theta_m G_m,\quad \theta_m \in \mathbb{R} + +and those coefficients :math:`\theta` are precisely the canonical coordinates. +You may ask why we included the prefactor :math:`i` in the definition of :math:`G_m` and why we excluded +the identity (times :math:`i`). This was done to match the properties of :math:`\mathfrak{su}(N);` +the prefactor makes the basis elements skew-Hermitian and the identity would not have a +vanishing trace. Indeed, one can check that the dimension of :math:`\mathfrak{su}(N)` is +:math:`4^n-1` and that there are :math:`4^n` Pauli words, so that one Pauli word — the identity — had to go +in any case... We can use the canonical coordinates of the algebra to express a *group element* in +:math:`\mathrm{SU}(N)` as well, and the ``qml.SpecialUnitary`` gate we will use is defined as + +.. math:: + + U(\boldsymbol{\theta}) = \exp\left\{\sum_{m=1}^d \theta_m G_m \right\}. + +The number of coordinates and Pauli words in :math:`\mathcal{P}^{(n)}` is :math:`d=4^n-1.` +Therefore, this will be the number of parameters that a single ``qml.SpecialUnitary`` gate acting on +:math:`n` qubits will take. For example, it takes just three parameters for a single qubit, which +is why :class:`~pennylane.Rot` and :class:`~pennylane.U3` take three parameters and may +produce *any* single-qubit rotation. It takes a modest 15 parameters for two qubits, +but it already requires 63 parameters for three qubits. + +For unitaries generated by a single operator, i.e. of the form :math:`\exp(i\theta G),` +there is a plethora of differentiation techniques that allow us to compute its derivative. +However, a standard parameter-shift rule, for example, will not do the job if there are +non-commuting terms :math:`G_m` in the multi-parameter gate :math:`U(\boldsymbol{\theta})` above. +So how *do* we compute the derivative? + +Obtaining the gradient +---------------------- + +In variational quantum algorithms, we typically use the circuit to prepare a quantum state and +then we measure some observable :math:`H.` The resulting real-valued output is considered to be the +cost function :math:`C` that should be minimized. If we want to use gradient-based optimization for +this task, we need a method to compute the gradient :math:`\nabla C` in addition to the cost +function itself. As derived in the publication [#wiersema]_, this is possible on quantum hardware +for :math:`\mathrm{SU}(N)` gates as long as the gates themselves can be implemented. +The implementation in PennyLane follows the decomposition idea described in App. F3, but the +main text of [#wiersema]_ proposes an additional method that scales better in some scenarios +(the caveat being that this method requires additional gates to be available on the quantum hardware). +Here, we will focus on the former method. +We will not go through the entire derivation, but note the following key points: + +- The gradient with respect to all :math:`d` parameters of an :math:`\mathrm{SU}(N)` gate can be + computed using :math:`2d` auxiliary circuits. Each of the circuits contains one additional + operation compared to the original circuit, namely a ``qml.PauliRot`` gate with rotation + angles of :math:`\pm\frac{\pi}{2}.` Note that these Pauli rotations act on up to :math:`n` + qubits. +- This differentiation method uses automatic differentiation during compilation and + classical coprocessing steps, but is compatible with quantum hardware. For large :math:`n,` + the classical processing steps can quickly become prohibitively expensive. +- The computed gradient is not an approximative technique but allows for an exact computation + of the gradient on simulators. On quantum hardware, this leads to unbiased gradient + estimators. + +The implementation in PennyLane takes care of creating the additional circuits and evaluating +them, and with adequate post-processing we get the gradient :math:`\nabla C.` + +Comparing gradient methods +-------------------------- + +Before we dive into using ``qml.SpecialUnitary`` in an optimization task, let's compare +a few methods to compute the gradient with respect to the parameters of such a gate. +In particular, we will look at a finite difference (FD) approach, the stochastic parameter-shift +rule, and the custom gradient method we described above. + +For the first approach, we will use the standard central difference recipe given by + +.. math:: + + \partial_{\text{FD},\theta_j}C(\boldsymbol{\theta}) + =\left[C\left(\boldsymbol{\theta}+\frac{\delta}{2}\boldsymbol{e}_j\right) + -C\left(\boldsymbol{\theta}-\frac{\delta}{2}\boldsymbol{e}_j\right)\right] / \delta. + +Here, :math:`\delta` is a shift parameter that we need to choose and :math:`\boldsymbol{e}_j` is the +:math:`j`-th canonical basis vector, i.e. the all-zeros vector with a one in the +:math:`j`-th entry. This approach is agnostic to the differentiated function and does +not exploit its structure. + +In contrast, the stochastic parameter-shift rule is a differentiation recipe developed particularly +for multi-parameter gates like the :math:`\mathrm{SU}(N)` gates [#banchi]_. It involves the +approximate evaluation of an integral by sampling *splitting times* :math:`\tau` and +evaluating an expression close to the non-stochastic parameter-shift rule for each sample. +For more details, also consider the +:doc:`demo on the stochastic parameter-shift rule `. + +So, let's dive into a toy example and explore the three gradient methods! +We start by defining a simple one-qubit circuit that contains a single :math:`\mathrm{SU}(2)` +gate and measures the expectation value of :math:`H=\frac{3}{5} Z - \frac{4}{5} Y.` +As ``qml.SpecialUnitary`` requires automatic differentiation subroutines even for the +hardware-ready derivative recipe, we will make use of JAX. +""" + +import pennylane as qml +import numpy as np +import jax + +jax.config.update("jax_enable_x64", True) +jax.config.update("jax_platform_name", "cpu") +jnp = jax.numpy + +dev = qml.device("default.qubit", wires=1) +H = 0.6 * qml.PauliZ(0) - 0.8 * qml.PauliY(0) + + +def qfunc(theta): + qml.SpecialUnitary(theta, wires=0) + return qml.expval(H) + + +circuit = qml.QNode(qfunc, dev, interface="jax", diff_method="parameter-shift") + +theta = jnp.array([0.4, 0.2, -0.5]) + +############################################################################## +# Now we need to set up the differentiation methods. For this demonstration, we will +# keep the first and last entry of ``theta`` fixed and only compute the gradient for the +# second parameter. This allows us to visualize the results easily and keeps the +# computational effort to a minimum. +# +# We start with the finite-difference +# recipe, using a shift scale of :math:`\delta=0.75.` This choice of :math:`\delta,` +# which is much larger than usual for numerical differentiation on classical computers, +# is adapted to the scenario of shot-based gradients (see App. F2 of [#wiersema]_). +# We compute the derivative with respect to the second entry of theta, so we will use +# the unit vector :math:`e_2:` + +unit_vector = np.array([0.0, 1.0, 0.0]) + + +def central_diff_grad(theta, delta): + plus_eval = circuit(theta + delta / 2 * unit_vector) + minus_eval = circuit(theta - delta / 2 * unit_vector) + return (plus_eval - minus_eval) / delta + + +delta = 0.75 +print(f"Central difference: {central_diff_grad(theta, delta):.5f}") + +############################################################################## +# Next up, we implement the stochastic parameter-shift rule. Of course we do not do +# so in full generality, but for the particular circuit in this example. We will +# sample ten splitting times to obtain the gradient entry. For each splitting time, +# we need to insert a Pauli-:math:`Y` rotation because :math:`\theta_2` belongs to +# the Pauli-:math:`Y` component of :math:`A(\boldsymbol{\theta}).` For this, we define +# an auxiliary circuit. + + +@jax.jit +@qml.qnode(dev, interface="jax") +def aux_circuit(theta, tau, sign): + qml.SpecialUnitary(tau * theta, wires=0) + # This corresponds to the parameter-shift evaluations of RY at 0 + qml.RY(-sign * np.pi / 2, wires=0) + qml.SpecialUnitary((1 - tau) * theta, wires=0) + return qml.expval(H) + + +def stochastic_parshift_grad(theta, num_samples): + grad = 0 + splitting_times = np.random.random(size=num_samples) + for tau in splitting_times: + # Evaluate the two-term parameter-shift rule of the auxiliar circuit + grad += aux_circuit(theta, tau, 1.0) - aux_circuit(theta, tau, -1.0) + return grad / num_samples + + +num_samples = 10 +print(f"Stochastic parameter-shift: {stochastic_parshift_grad(theta, num_samples):.5f}") + +############################################################################## +# Finally, we can make use of the custom parameter-shift rule introduced in +# [#wiersema]_, which is readily available in PennyLane. Due to the implementation +# chosen internally, the full gradient is returned; we need to pick the second +# gradient entry manually. For this small toy problem, this is +# not an issue. + +sun_grad = jax.grad(circuit) +print(f"Custom SU(N) gradient: {sun_grad(theta)[1]:.5f}") + +############################################################################## +# We obtained three values for the gradient of interest, and they do not agree. +# So what is going on here? First, let's use automatic differentiation to compute +# the exact value and see which method agrees with it (we again need to extract the +# corresponding entry from the full gradient). + +autodiff_circuit = qml.QNode(qfunc, dev, interface="jax", diff_method="parameter-shift") +exact_grad = jax.grad(autodiff_circuit)(theta)[1] +print(f"Exact gradient: {exact_grad:.5f}") + +############################################################################## +# As we can see, automatic differentiation confirmed that the custom differentiation method +# gave us the correct result. Why do the other methods disagree? +# This is because the finite difference recipe is an *approximate* gradient +# method. This means it has an error even if all circuit evaluations are +# made exact (up to numerical precision) like in the example above. +# As for the stochastic parameter-shift rule, you may already guess why there is +# a deviation: indeed, the *stochastic* nature of this method leads to derivative +# values that are scattered around the true value. It is an unbiased estimator, +# so the average will approach the exact value with increasingly many evaluations. +# To demonstrate this, let's compute the same derivative many times and plot +# a histogram of what we get. We'll do so for ``num_samples=2``, ``10`` and ``100``. + +import matplotlib.pyplot as plt + +plt.rcParams.update({"font.size": 12}) + +fig, ax = plt.subplots(1, 1, figsize=(6, 4)) +colors = ["#ACE3FF", "#FF87EB", "#FFE096"] +for num_samples, color in zip([2, 10, 100], colors): + grads = [stochastic_parshift_grad(theta, num_samples) for _ in range(1000)] + ax.hist(grads, label=f"{num_samples} samples", alpha=0.9, color=color) +ylim = ax.get_ylim() +ax.plot([exact_grad] * 2, ylim, ls="--", c="k", label="Exact") +ax.set(xlabel=r"$\partial_{SPS,\theta_2}C(\theta)$", ylabel="Frequency", ylim=ylim) +ax.legend(loc="upper left") +plt.tight_layout() +plt.show() + +############################################################################## +# As we can see, the stochastic parameter-shift rule comes with a variance +# that can be reduced at the additional cost of evaluating the auxiliary circuit +# for more splitting times. +# +# On quantum hardware, all measurement results are statistical in nature anyway. +# So how does this stochasticity combine with the +# three differentiation methods? We will not go into detail here, but refer +# to [#wiersema]_ to see how the custom differentiation rule proposed in the +# main text leads to the lowest mean squared error. For a single-qubit circuit +# similar to the one above, but with the single gate :math:`U(\boldsymbol{\theta})=\exp(iaX+ibY),` +# the derivative and its expected variance are shown in the following +# (recoloured) plot from the manuscript: +# +# .. figure:: ../_static/demonstration_assets/here_comes_the_sun/sampled_grad.png +# :align: center +# :width: 70% +# +# As we can see, the custom :math:`\mathrm{SU}(N)` parameter-shift rule produces the +# gradient estimates with the smallest variance. For small values of the parameter +# :math:`b,` which is fixed for each panel, the custom shift rule and the stochastic +# shift rule approach the standard two-term parameter-shift rule, which would be exact +# for :math:`b=0.` +# The finite difference gradient shown here was obtained using the shift +# scale :math:`\delta=0.75,` as well. As we can see, this suppresses the variance down to +# a level comparable to those of the shift rule derivatives and this shift scale is a +# reasonable trade-off between the variance and the systematic error we observed earlier. +# As shown in App. F3 of [#wiersema]_, this scale is indeed close to the optimal choice +# if we were to compute the gradient with 100 shots per circuit. +# +# Comparing ansatz structures +# --------------------------- +# +# We discussed above that there are many circuit architectures available and that choosing +# a suitable ansatz is important but can be difficult. Here, we will compare a simple ansatz +# based on the ``qml.SpecialUnitary`` gate discussed above to other approaches that fully +# parametrize the special unitary group for the respective number of qubits. +# In particular, we will compare ``qml.SpecialUnitary`` to standard decompositions from the +# literature that parametrize :math:`\mathrm{SU}(N)` with elementary gates, as well as to a +# sequence of Pauli rotation gates that also allows us to create any special unitary. +# Let us start by defining the decomposition of a two-qubit unitary. +# We choose the decomposition, which is optimal but not unique, from [#vatan]_. +# The Pauli rotation sequence is available in PennyLane +# via ``qml.ArbitraryUnitary`` and we will not need to implement it ourselves. + + +def two_qubit_decomp(params, wires): + """Implement an arbitrary SU(4) gate on two qubits + using the decomposition from Theorem 5 in + https://arxiv.org/pdf/quant-ph/0308006.pdf""" + i, j = wires + # Single U(2) parameterization on both qubits separately + qml.Rot(*params[:3], wires=i) + qml.Rot(*params[3:6], wires=j) + qml.CNOT(wires=[j, i]) # First CNOT + qml.RZ(params[6], wires=i) + qml.RY(params[7], wires=j) + qml.CNOT(wires=[i, j]) # Second CNOT + qml.RY(params[8], wires=j) + qml.CNOT(wires=[j, i]) # Third CNOT + # Single U(2) parameterization on both qubits separately + qml.Rot(*params[9:12], wires=i) + qml.Rot(*params[12:15], wires=j) + + +# The three building blocks on two qubits we will compare are: +operations = { + ("Decomposition", "decomposition"): two_qubit_decomp, + ("PauliRot sequence",) * 2: qml.ArbitraryUnitary, + ("$\mathrm{SU}(N)$ gate", "SU(N) gate"): qml.SpecialUnitary, +} + +############################################################################## +# Now that we have the template for the composition approach in place, we construct a toy +# problem to solve using the ansätze. We will sample a random Hamiltonian in the Pauli basis +# (this time without the prefactor :math:`i,` as we want to construct a Hermitian operator) +# with independent coefficients that follow a normal distribution: +# +# .. math:: +# +# H = \sum_{m=1}^d h_m G_m,\quad h_m\sim \mathcal{N}(0,1). +# +# We will work with six qubits. + +num_wires = 6 +wires = list(range(num_wires)) +np.random.seed(62213) + +coefficients = np.random.randn(4**num_wires - 1) +# Create the matrices for the entire Pauli basis +basis = qml.ops.qubit.special_unitary.pauli_basis_matrices(num_wires) +# Construct the Hamiltonian from the normal random coefficients and the basis +H_matrix = qml.math.tensordot(coefficients, basis, axes=[[0], [0]]) +H = qml.Hermitian(H_matrix, wires=wires) +# Compute the ground state energy +E_min = min(qml.eigvals(H)) +print(f"Ground state energy: {E_min:.5f}") + +############################################################################## +# Using the toy problem Hamiltonian and the three ansätze for :math:`\mathrm{SU}(N)` operations +# from above, we create a circuit template that applies these operations in a brick-layer +# architecture with two blocks and each operation acting on ``loc=2`` qubits. +# For this we define a ``QNode``: + +loc = 2 +d = loc**4 - 1 # d = 15 for two-qubit operations +dev = qml.device("default.qubit", wires=num_wires) +# two blocks with two layers. Each layer contains three operations with d parameters +param_shape = (2, 2, 3, d) +init_params = np.zeros(param_shape) + + +def circuit(params, operation=None): + """Apply an operation in a brickwall-like pattern to a qubit register and measure H. + Parameters are assumed to have the dimensions (number of blocks, number of + wires per operation, number of operations per layer, and number of parameters + per operation), in that order. + """ + for params_block in params: + for i, params_layer in enumerate(params_block): + for j, params_op in enumerate(params_layer): + wires_op = [w % num_wires for w in range(loc * j + i, loc * (j + 1) + i)] + operation(params_op, wires_op) + return qml.expval(H) + + +qnode = qml.QNode(circuit, dev, interface="jax") +print(qml.draw(qnode)(init_params, qml.SpecialUnitary)) + +############################################################################## +# We can now proceed to prepare the optimization task using this circuit +# and an optimization routine of our choice. For simplicity, we run a vanilla gradient +# descent optimization with a fixed learning rate for 500 steps. Again, we use JAX + +# for auto-differentiation. + +learning_rate = 5e-4 +num_steps = 500 +init_params = jax.numpy.array(init_params) +grad_fn = jax.jit(jax.jacobian(qnode), static_argnums=1) +qnode = jax.jit(qnode, static_argnums=1) + +############################################################################## +# With this configuration, let's run the optimization! + +energies = {} +for (name, print_name), operation in operations.items(): + print(f"Running the optimization for the {print_name}") + params = init_params.copy() + energy = [] + for step in range(num_steps): + cost = qnode(params, operation) + params = params - learning_rate * grad_fn(params, operation) + energy.append(cost) # Store energy value + if step % 50 == 0: # Report current energy + print(f"{step:3d} Steps: {cost:.6f}") + + energy.append(qnode(params, operation)) # Final energy value + energies[name] = energy + +############################################################################## +# So, did it work? Judging from the intermediate energy values, it seems that the optimization +# outcomes differ notably. But let's take a look at the relative error in energy across the +# optimization process. + +fig, ax = plt.subplots(1, 1) +styles = [":", "--", "-"] +colors = ["#70CEFF", "#C756B2", "#FFE096"] +for (name, energy), c, ls in zip(energies.items(), colors, styles): + error = (energy - E_min) / abs(E_min) + ax.plot(list(range(len(error))), error, label=name, c=c, ls=ls, lw=2.5) + +ax.set(xlabel="Iteration", ylabel="Relative error") +ax.legend() +plt.show() + +############################################################################## +# We find that the optimization indeed performs significantly better for ``qml.SpecialUnitary`` +# than for the other two general unitaries, while using the same number of parameters and +# preserving the expressibility of the circuit ansatz. This +# means that we found a particularly well-trainable parameterization of the local unitaries which +# allows us to reduce the energy of the prepared quantum state more easily while maintaining the +# number of parameters. +# +# +# Conclusion +# ---------- +# +# To summarize, in this tutorial we introduced ``qml.SpecialUnitary``, a multi-parameter +# gate that can act like *any* gate on the qubits it is applied to and that is constructed +# with Lie theory in mind. We discussed three methods of differentiating quantum circuits +# that use this gate, showing that a new custom parameter-shift rule presented in +# [#wiersema]_ is particularly suitable to produce unbiased gradient estimates with the +# lowest variance. Afterwards, we used this differentiation technique when comparing +# the performance of ``qml.SpecialUnitary`` to that of other gates that can act +# like *any* gate locally. For this, we ran a gradient-based optimization for a toy model +# Hamiltonian and found that ``qml.SpecialUnitary`` is particularly well-trainable, achieving +# lower energies significantly quicker than the other tested gates. +# +# There are still exciting questions to answer about ``qml.SpecialUnitary``: How can the +# custom parameter-shift rule be used for other gates, and what does the so-called +# *Dynamical Lie algebra* of these gates have to do with it? How can we implement +# the ``qml.SpecialUnitary`` gate on hardware? Is the unitary time evolution implemented +# by this gate special in a physical sense? +# +# The answers to some, but not all, of these questions can be found in [#wiersema]_. +# We are certain that there are many more interesting aspects of this gate to be uncovered! +# If you want to learn more, consider the other literature references below, +# as well as the documentation of :class:`~pennylane.SpecialUnitary`. +# +# References +# ---------- +# +# .. [#vatan] +# +# Farrokh Vatan and Colin Williams, +# "Optimal Quantum Circuits for General Two-Qubit Gates", +# `arXiv:quant-ph/0308006 `__ (2003). +# +# .. [#wiersema] +# +# R. Wiersema, D. Lewis, D. Wierichs, J. F. Carrasquilla, and N. Killoran. +# "Here comes the SU(N): multivariate quantum gates and gradients" +# `arXiv:2303.11355 `__ (2023). +# +# .. [#banchi] +# +# Leonardo Banchi and Gavin E. Crooks. "Measuring Analytic Gradients of +# General Quantum Evolution with the Stochastic Parameter Shift Rule." +# `Quantum 5, 386 `__ (2021). +# +# About the author +# ---------------- +# diff --git a/demonstrations/tutorial_initial_state_preparation.py b/demonstrations/tutorial_initial_state_preparation.py index 8a2cb89e5c..dc50c939c0 100644 --- a/demonstrations/tutorial_initial_state_preparation.py +++ b/demonstrations/tutorial_initial_state_preparation.py @@ -1,364 +1,364 @@ -r""" - -Initial state preparation for quantum chemistry -=============================================== - -A high-quality initial state can significantly reduce the runtime of many quantum algorithms. From -the variational quantum eigensolver (VQE) to quantum phase estimation (QPE) and even the recent -`intermediate-scale quantum (ISQ) `_ algorithms, obtaining the ground state of a chemical system requires -a good initial state. For instance, in the case of VQE, a good initial state directly translates into fewer -optimization steps. In QPE, the probability of measuring the ground-state energy is directly -proportional to the overlap squared of the initial and ground states. Even beyond quantum chemistry, -good initial guesses are important for algorithms like quantum approximate optimization (QAOA) -and Grover search. - -Much like searching for a needle in a haystack, there are a lot of things you might try -to prepare a good guess for the ground state in the high-dimensional Hilbert space. In this -tutorial, we show how to use traditional computational chemistry techniques to -get a good initial state. Such an initial state will not be exactly -the ground state, but it will certainly be better than the standard guess of a computational -basis state :math:`|0\rangle^{\otimes N}` or the Hartree-Fock state. - -.. figure:: ../_static/demonstration_assets/initial_state_preparation/qchem_input_states.png - :align: center - :width: 65% - :target: javascript:void(0) - -Importing initial states ------------------------- -We can import initial states obtained from several post-Hartree-Fock quantum chemistry methods -to PennyLane. These methods are incredibly diverse in terms of their outputs, not always returning -an object that is easy to turn into a PennyLane state vector. - -We have already done this hard conversion work: all that you need to do is run these methods and -pass their outputs to PennyLane's :func:`~.pennylane.qchem.import_state` function. The currently -supported methods are configuration interaction with singles and doubles (CISD), coupled cluster -(CCSD), density-matrix renormalization group (DMRG) and semistochastic heat-bath configuration -interaction (SHCI). - -We now show how this works on the linear :math:`\text{H}_3^+` molecule as an example. - - -CISD states -~~~~~~~~~~~ -The first line of attack for initial state preparation is often a CISD calculation, performed with the `PySCF `_ -library. CISD is unsophisticated, but it is fast. It will not be of much help for strongly correlated molecules, -but it is better than Hartree-Fock. Here is the code example using the restricted Hartree-Fock -orbitals (:func:`~.pennylane.qchem.import_state` works for unrestricted orbitals, too). -""" - -from pyscf import gto, scf, ci -from pennylane.qchem import import_state -import numpy as np - -R = 1.2 -# create the H3+ molecule -mol = gto.M(atom=[["H", (0, 0, 0)], ["H", (0, 0, R)], ["H", (0, 0, 2 * R)]], charge=1) -# perfrom restricted Hartree-Fock and then CISD -myhf = scf.RHF(mol).run() -myci = ci.CISD(myhf).run() -wf_cisd = import_state(myci, tol=1e-1) -print(f"CISD-based state vector: \n{np.round(wf_cisd.real, 4)}") - -############################################################################## -# The final object, PennyLane's state vector ``wf_cisd``, is ready to be used as an -# initial state in a quantum circuit in PennyLane--we will showcase this below for VQE. -# -# Conversion for CISD to a state vector is straightforward: simply assign the PySCF-stored -# CI coefficients to appropriate entries in the state vector based on the determinant they correspond to. -# The second attribute passed to ``import_state()``, ``tol``, specifies the cutoff beyond -# which contributions to the wavefunctions are neglected. Internally, wavefunctions are -# stored in their Slater determinant representation. If their prefactor coefficient -# is below ``tol``, those determinants are dropped from the expression. - -############################################################################## -# CCSD states -# ~~~~~~~~~~~ -# The function :func:`~.pennylane.qchem.import_state` is general and works similarly for CCSD. It can -# automatically detect the input type and apply the appropriate conversion protocol. - -from pyscf import cc - -mycc = cc.CCSD(myhf).run() -wf_ccsd = import_state(mycc, tol=1e-1) -print(f"CCSD-based state vector: \n{np.round(wf_ccsd.real, 4)}") - -############################################################################## -# For CCSD conversion, at present the exponential form is expanded and terms are collected **to -# second order** to obtain the CI coefficients. -# -# DMRG states -# ~~~~~~~~~~~ -# For more complex or more correlated molecules, initial states from DMRG or -# SHCI will be better options. DMRG calculations involve using the library `Block2 `_, -# which can be installed with ``pip``: -# -# .. code-block:: bash -# -# pip install block2 -# -# The DMRG calculation is run on top of the molecular orbitals obtained by Hartree-Fock, -# stored in the ``myhf`` object, which we can reuse from before. -# -# .. code-block:: python -# -# from pyscf import mcscf -# from pyblock2.driver.core import DMRGDriver, SymmetryTypes -# from pyblock2._pyscf.ao2mo import integrals as itg -# -# # obtain molecular integrals and other parameters for DMRG -# mc = mcscf.CASCI(myhf, mol.nao, mol.nelectron) -# ncas, n_elec, spin, ecore, h1e, g2e, orb_sym = \ -# itg.get_rhf_integrals(myhf, mc.ncore, mc.ncas, g2e_symm=8) -# -# # initialize the DMRG solver, Hamiltonian (as matrix-product operator, MPO) and -# # state (as matrix-product state, MPS) -# driver = DMRGDriver(scratch="./dmrg_temp", symm_type=SymmetryTypes.SZ) -# driver.initialize_system(n_sites=ncas, n_elec=n_elec, spin=spin, orb_sym=orb_sym) -# mpo = driver.get_qc_mpo(h1e=h1e, g2e=g2e, ecore=ecore, iprint=0) -# ket = driver.get_random_mps(tag="GS") -# -# # execute DMRG by modifying the ket state in-place to minimize the energy -# driver.dmrg(mpo, ket, n_sweeps=30,bond_dims=[100,200],\ -# noises=[1e-3,1e-5],thrds=[1e-6,1e-7],tol=1e-6) -# -# # post-process the MPS to get an initial state -# dets, coeffs = driver.get_csf_coefficients(ket, iprint=0) -# dets = dets.tolist() -# wf_dmrg = import_state((dets, coeffs), tol=1e-1) -# print(f"DMRG-based state vector: \n {np.round(wf_dmrg, 4)}") -# -# .. code-block:: bash -# -# DMRG-based state vector -# [ 0. 0. 0. 0. 0. 0. 0. 0. 0. -# 0. 0. 0. -0.2243 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.9745 0. 0. 0. 0. 0. -# 0. 0. 0. 0. 0. 0. 0. 0. 0. -# 0. ] - -############################################################################## -# The crucial part is calling ``get_csf_coefficients()`` on the solution stored in -# MPS form in the ``ket``. This triggers an internal reconstruction calculation that -# converts the MPS to the sum of Slater determinants form, returning the output -# as a tuple ``(array([int]), array(float]))``. The first element expresses a given Slater -# determinant using Fock occupation vectors of length equal to the number of spatial -# orbitals. In Block2 notation, the entries can be ``0`` (orbital unoccupied), ``1`` (orbital occupied with spin-up -# electron), ``2`` (occupied with spin-down), and ``3`` (doubly occupied). The first -# element, ``array([int])``, must be converted to ``list`` -# for :func:`~.pennylane.qchem.import_state` to accept it. -# The second element stores the CI coefficients. -# -# In principle, this functionality can be used to generate any initial state, provided -# the user specifies a list of Slater determinants and their coefficients in this form. -# Let's take this opportunity to create the Hartree-Fock initial state, to compare the -# other states against it later on. - -hf_primer = ([[3, 0, 0]], np.array([1.0])) -wf_hf = import_state(hf_primer) - -############################################################################## -# SHCI states -# ~~~~~~~~~~~ -# -# The SHCI calculations utilize the library `Dice `_, and can be run -# using PySCF through the interface module `SHCI-SCF `_. -# For Dice, the execution process is similar to that of DMRG: -# -# .. code-block:: python -# -# from pyscf.shciscf import shci -# -# # prepare PySCF CASCI object, whose solver will be the SHCI method -# ncas, nelecas_a, nelecas_b = mol.nao, mol.nelectron // 2, mol.nelectron // 2 -# myshci = mcscf.CASCI(myhf, ncas, (nelecas_a, nelecas_b)) -# -# # set up essentials for the SHCI solver -# output_file = f"shci_output.out" -# myshci.fcisolver = shci.SHCI(myhf.mol) -# myshci.fcisolver.outputFile = output_file -# -# # execute SHCI through the PySCF interface -# e_tot, e_ci, ss, mo_coeff, mo_energies = myshci.kernel(verbose=5) -# -# # post-process the shci_output.out to extract the wave function -# # results and create the tuple of dets (list([str])) and coeffs (array([float])) -# # shci_data = (dets, coeffs) -# wf_shci = import_state(shci_data, tol=1e-1) -# print(f"SHCI-based state vector\n{wf_shci}") -# -# .. code-block:: bash -# -# SHCI-based state vector -# [ 0. 0. 0. 0. 0. 0. 0. 0. 0. -# 0. 0. 0. 0.2243 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.9745 0. 0. 0. 0. 0. -# 0. 0. 0. 0. 0. 0. 0. 0. 0. -# 0. ] - -############################################################################## -# The Dice output file prints determinants using symbols ``0`` (unoccupied orbital), -# ``a`` and ``b`` (orbital occupied with spin-up and spin-down electron, respectively), -# and ``2`` (doubly occupied orbital). These determinant outputs, and corresponding -# coefficients, should be extracted and arranged as ``(list([str]), array(float]))``, -# where each string combines all the determinant symbols ``0, a, b, 2`` for a single -# determinant with no spaces. For example, for the HF state we created in the DMRG section, -# the SHCI output should read ``([["200"]], np.array([1.]))`` - -############################################################################## -# Application: speed up VQE -# ------------------------- -# Let us now demonstrate how the choice of a better initial state shortens the runtime -# of VQE for obtaining the ground-state energy of a molecule. As a first step, create our -# linear :math:`\text{H}_3^+` molecule, a device, and a simple VQE circuit with single and double excitations: - -import pennylane as qml -from pennylane import qchem -from jax import numpy as jnp - -# generate the molecular Hamiltonian for H3+ -symbols = ["H", "H", "H"] -geometry = jnp.array([[0, 0, 0], [0, 0, R/0.529], [0, 0, 2*R/0.529]]) -molecule = qchem.Molecule(symbols, geometry, charge=1) - -H2mol, qubits = qchem.molecular_hamiltonian(molecule) -wires = list(range(qubits)) -dev = qml.device("default.qubit", wires=qubits) - -# create all possible excitations in H3+ -singles, doubles = qchem.excitations(2, qubits) -excitations = singles + doubles - -############################################################################## -# Now let's run VQE with the Hartree-Fock initial state. We first build the VQE circuit: - - -@qml.qnode(dev) -def circuit_VQE(theta, initial_state): - qml.StatePrep(initial_state, wires=wires) - for i, excitation in enumerate(excitations): - if len(excitation) == 4: - qml.DoubleExcitation(theta[i], wires=excitation) - else: - qml.SingleExcitation(theta[i], wires=excitation) - return qml.expval(H2mol) - - -def cost_fn(param): - return circuit_VQE(param, initial_state=wf_hf) - - -############################################################################## -# Next, we create the VQE optimizer, initialize the variational parameters and run the VQE optimization. -import optax -import jax -jax.config.update("jax_enable_x64", True) - -opt = optax.sgd(learning_rate=0.4) # sgd stands for StochasticGradientDescent -theta = jnp.array(jnp.zeros(len(excitations))) -delta_E, iteration = 10, 0 -results_hf = [] -opt_state = opt.init(theta) -prev_energy = cost_fn(theta) - -# run the VQE optimization loop until convergence threshold is reached -while abs(delta_E) > 1e-5: - gradient = jax.grad(cost_fn)(theta) - updates, opt_state = opt.update(gradient, opt_state) - theta = optax.apply_updates(theta, updates) - new_energy = cost_fn(theta) - delta_E = new_energy - prev_energy - prev_energy = new_energy - results_hf.append(new_energy) - if len(results_hf) % 5 == 0: - print(f"Step = {len(results_hf)}, Energy = {new_energy:.6f} Ha") -print(f"Starting with HF state took {len(results_hf)} iterations until convergence.") - -############################################################################## -# And compare with how things go when you run it with the CISD initial state: - - -def cost_fn_cisd(param): - return circuit_VQE(param, initial_state=wf_cisd) - - -theta = jnp.array(jnp.zeros(len(excitations))) -delta_E, iteration = 10, 0 -results_cisd = [] -opt_state = opt.init(theta) -prev_energy = cost_fn_cisd(theta) - -while abs(delta_E) > 1e-5: - gradient = jax.grad(cost_fn_cisd)(theta) - updates, opt_state = opt.update(gradient, opt_state) - theta = optax.apply_updates(theta, updates) - new_energy = cost_fn_cisd(theta) - delta_E = new_energy - prev_energy - prev_energy = new_energy - results_cisd.append(new_energy) - if len(results_cisd) % 5 == 0: - print(f"Step = {len(results_cisd)}, Energy = {new_energy:.6f} Ha") -print( - f"Starting with CISD state took {len(results_cisd)} iterations until convergence." -) - -############################################################################## -# Let's visualize the comparison between the two initial states, and see that indeed -# we get to the ground state much faster by starting with the CISD state. - -import matplotlib.pyplot as plt - -fig, ax = plt.subplots() -ax.plot(range(len(results_hf)), results_hf, color="r", marker="o", label="wf_hf") -ax.plot(range(len(results_cisd)), results_cisd, color="b", marker="o", label="wf_cisd") -ax.legend(fontsize=16) -ax.tick_params(axis="both", labelsize=16) -ax.set_xlabel("Iteration", fontsize=20) -ax.set_ylabel("Energy, Ha", fontsize=20) -plt.tight_layout() -plt.show() - -############################################################################## -# Indeed, the CISD state significantly shortens the VQE runtime. -# -# It is sometimes possible to foresee the extent of this speed-up of a particular initial state -# by computing its overlap with the ground state--a traditional metric of success for initial -# states in quantum algorithms. Because in our examples the states are regular arrays, computing an -# overlap between different states is as easy as computing a dot product - -print(np.dot(wf_cisd, wf_hf).real) -print(np.dot(wf_ccsd, wf_hf).real) -print(np.dot(wf_cisd, wf_ccsd).real) - -############################################################################## -# In this particular case of :math:`\text{H}_3^+,` even CISD gives the exact wavefunction, hence both overlaps -# with the HF state are identical. In more correlated molecules, overlaps will show that the more -# multireference methods DMRG and SHCI are farther away from the Hartree-Fock state, -# allowing them to perform better (you can check this by printing the overlaps with -# DMRG and SHCI in a more correlated molecule). If a ground state in such a case was known, -# the overlap to it could tell us directly the quality of the initial state. - -############################################################################## -# Conclusion -# ----------- -# This demo shows how to import initial states from outputs of traditional quantum chemistry methods -# for use in PennyLane. We showcased simple workflows for how to run -# a variety of state-of-the-art post-Hartree-Fock methods, from libraries such as -# `PySCF `_, -# `Block2 `_ and -# `Dice `_, to generate outputs that can then be -# converted to PennyLane's state vector format with a single line of code. With these -# initial states, we use the example of VQE to demonstrate how a better choice -# of initial state can lead to improved algorithmic performance. For the molecule -# used in our example, the CISD state was sufficient: however, in more correlated -# molecules, DMRG and SHCI initial states typically provide the best speed-ups. -# -# About the author -# ---------------- -# +r""" + +Initial state preparation for quantum chemistry +=============================================== + +A high-quality initial state can significantly reduce the runtime of many quantum algorithms. From +the variational quantum eigensolver (VQE) to quantum phase estimation (QPE) and even the recent +`intermediate-scale quantum (ISQ) `_ algorithms, obtaining the ground state of a chemical system requires +a good initial state. For instance, in the case of VQE, a good initial state directly translates into fewer +optimization steps. In QPE, the probability of measuring the ground-state energy is directly +proportional to the overlap squared of the initial and ground states. Even beyond quantum chemistry, +good initial guesses are important for algorithms like quantum approximate optimization (QAOA) +and Grover search. + +Much like searching for a needle in a haystack, there are a lot of things you might try +to prepare a good guess for the ground state in the high-dimensional Hilbert space. In this +tutorial, we show how to use traditional computational chemistry techniques to +get a good initial state. Such an initial state will not be exactly +the ground state, but it will certainly be better than the standard guess of a computational +basis state :math:`|0\rangle^{\otimes N}` or the Hartree-Fock state. + +.. figure:: ../_static/demonstration_assets/initial_state_preparation/qchem_input_states.png + :align: center + :width: 65% + :target: javascript:void(0) + +Importing initial states +------------------------ +We can import initial states obtained from several post-Hartree-Fock quantum chemistry methods +to PennyLane. These methods are incredibly diverse in terms of their outputs, not always returning +an object that is easy to turn into a PennyLane state vector. + +We have already done this hard conversion work: all that you need to do is run these methods and +pass their outputs to PennyLane's :func:`~.pennylane.qchem.import_state` function. The currently +supported methods are configuration interaction with singles and doubles (CISD), coupled cluster +(CCSD), density-matrix renormalization group (DMRG) and semistochastic heat-bath configuration +interaction (SHCI). + +We now show how this works on the linear :math:`\text{H}_3^+` molecule as an example. + + +CISD states +~~~~~~~~~~~ +The first line of attack for initial state preparation is often a CISD calculation, performed with the `PySCF `_ +library. CISD is unsophisticated, but it is fast. It will not be of much help for strongly correlated molecules, +but it is better than Hartree-Fock. Here is the code example using the restricted Hartree-Fock +orbitals (:func:`~.pennylane.qchem.import_state` works for unrestricted orbitals, too). +""" + +from pyscf import gto, scf, ci +from pennylane.qchem import import_state +import numpy as np + +R = 1.2 +# create the H3+ molecule +mol = gto.M(atom=[["H", (0, 0, 0)], ["H", (0, 0, R)], ["H", (0, 0, 2 * R)]], charge=1) +# perfrom restricted Hartree-Fock and then CISD +myhf = scf.RHF(mol).run() +myci = ci.CISD(myhf).run() +wf_cisd = import_state(myci, tol=1e-1) +print(f"CISD-based state vector: \n{np.round(wf_cisd.real, 4)}") + +############################################################################## +# The final object, PennyLane's state vector ``wf_cisd``, is ready to be used as an +# initial state in a quantum circuit in PennyLane--we will showcase this below for VQE. +# +# Conversion for CISD to a state vector is straightforward: simply assign the PySCF-stored +# CI coefficients to appropriate entries in the state vector based on the determinant they correspond to. +# The second attribute passed to ``import_state()``, ``tol``, specifies the cutoff beyond +# which contributions to the wavefunctions are neglected. Internally, wavefunctions are +# stored in their Slater determinant representation. If their prefactor coefficient +# is below ``tol``, those determinants are dropped from the expression. + +############################################################################## +# CCSD states +# ~~~~~~~~~~~ +# The function :func:`~.pennylane.qchem.import_state` is general and works similarly for CCSD. It can +# automatically detect the input type and apply the appropriate conversion protocol. + +from pyscf import cc + +mycc = cc.CCSD(myhf).run() +wf_ccsd = import_state(mycc, tol=1e-1) +print(f"CCSD-based state vector: \n{np.round(wf_ccsd.real, 4)}") + +############################################################################## +# For CCSD conversion, at present the exponential form is expanded and terms are collected **to +# second order** to obtain the CI coefficients. +# +# DMRG states +# ~~~~~~~~~~~ +# For more complex or more correlated molecules, initial states from DMRG or +# SHCI will be better options. DMRG calculations involve using the library `Block2 `_, +# which can be installed with ``pip``: +# +# .. code-block:: bash +# +# pip install block2 +# +# The DMRG calculation is run on top of the molecular orbitals obtained by Hartree-Fock, +# stored in the ``myhf`` object, which we can reuse from before. +# +# .. code-block:: python +# +# from pyscf import mcscf +# from pyblock2.driver.core import DMRGDriver, SymmetryTypes +# from pyblock2._pyscf.ao2mo import integrals as itg +# +# # obtain molecular integrals and other parameters for DMRG +# mc = mcscf.CASCI(myhf, mol.nao, mol.nelectron) +# ncas, n_elec, spin, ecore, h1e, g2e, orb_sym = \ +# itg.get_rhf_integrals(myhf, mc.ncore, mc.ncas, g2e_symm=8) +# +# # initialize the DMRG solver, Hamiltonian (as matrix-product operator, MPO) and +# # state (as matrix-product state, MPS) +# driver = DMRGDriver(scratch="./dmrg_temp", symm_type=SymmetryTypes.SZ) +# driver.initialize_system(n_sites=ncas, n_elec=n_elec, spin=spin, orb_sym=orb_sym) +# mpo = driver.get_qc_mpo(h1e=h1e, g2e=g2e, ecore=ecore, iprint=0) +# ket = driver.get_random_mps(tag="GS") +# +# # execute DMRG by modifying the ket state in-place to minimize the energy +# driver.dmrg(mpo, ket, n_sweeps=30,bond_dims=[100,200],\ +# noises=[1e-3,1e-5],thrds=[1e-6,1e-7],tol=1e-6) +# +# # post-process the MPS to get an initial state +# dets, coeffs = driver.get_csf_coefficients(ket, iprint=0) +# dets = dets.tolist() +# wf_dmrg = import_state((dets, coeffs), tol=1e-1) +# print(f"DMRG-based state vector: \n {np.round(wf_dmrg, 4)}") +# +# .. code-block:: bash +# +# DMRG-based state vector +# [ 0. 0. 0. 0. 0. 0. 0. 0. 0. +# 0. 0. 0. -0.2243 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.9745 0. 0. 0. 0. 0. +# 0. 0. 0. 0. 0. 0. 0. 0. 0. +# 0. ] + +############################################################################## +# The crucial part is calling ``get_csf_coefficients()`` on the solution stored in +# MPS form in the ``ket``. This triggers an internal reconstruction calculation that +# converts the MPS to the sum of Slater determinants form, returning the output +# as a tuple ``(array([int]), array(float]))``. The first element expresses a given Slater +# determinant using Fock occupation vectors of length equal to the number of spatial +# orbitals. In Block2 notation, the entries can be ``0`` (orbital unoccupied), ``1`` (orbital occupied with spin-up +# electron), ``2`` (occupied with spin-down), and ``3`` (doubly occupied). The first +# element, ``array([int])``, must be converted to ``list`` +# for :func:`~.pennylane.qchem.import_state` to accept it. +# The second element stores the CI coefficients. +# +# In principle, this functionality can be used to generate any initial state, provided +# the user specifies a list of Slater determinants and their coefficients in this form. +# Let's take this opportunity to create the Hartree-Fock initial state, to compare the +# other states against it later on. + +hf_primer = ([[3, 0, 0]], np.array([1.0])) +wf_hf = import_state(hf_primer) + +############################################################################## +# SHCI states +# ~~~~~~~~~~~ +# +# The SHCI calculations utilize the library `Dice `_, and can be run +# using PySCF through the interface module `SHCI-SCF `_. +# For Dice, the execution process is similar to that of DMRG: +# +# .. code-block:: python +# +# from pyscf.shciscf import shci +# +# # prepare PySCF CASCI object, whose solver will be the SHCI method +# ncas, nelecas_a, nelecas_b = mol.nao, mol.nelectron // 2, mol.nelectron // 2 +# myshci = mcscf.CASCI(myhf, ncas, (nelecas_a, nelecas_b)) +# +# # set up essentials for the SHCI solver +# output_file = f"shci_output.out" +# myshci.fcisolver = shci.SHCI(myhf.mol) +# myshci.fcisolver.outputFile = output_file +# +# # execute SHCI through the PySCF interface +# e_tot, e_ci, ss, mo_coeff, mo_energies = myshci.kernel(verbose=5) +# +# # post-process the shci_output.out to extract the wave function +# # results and create the tuple of dets (list([str])) and coeffs (array([float])) +# # shci_data = (dets, coeffs) +# wf_shci = import_state(shci_data, tol=1e-1) +# print(f"SHCI-based state vector\n{wf_shci}") +# +# .. code-block:: bash +# +# SHCI-based state vector +# [ 0. 0. 0. 0. 0. 0. 0. 0. 0. +# 0. 0. 0. 0.2243 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.9745 0. 0. 0. 0. 0. +# 0. 0. 0. 0. 0. 0. 0. 0. 0. +# 0. ] + +############################################################################## +# The Dice output file prints determinants using symbols ``0`` (unoccupied orbital), +# ``a`` and ``b`` (orbital occupied with spin-up and spin-down electron, respectively), +# and ``2`` (doubly occupied orbital). These determinant outputs, and corresponding +# coefficients, should be extracted and arranged as ``(list([str]), array(float]))``, +# where each string combines all the determinant symbols ``0, a, b, 2`` for a single +# determinant with no spaces. For example, for the HF state we created in the DMRG section, +# the SHCI output should read ``([["200"]], np.array([1.]))`` + +############################################################################## +# Application: speed up VQE +# ------------------------- +# Let us now demonstrate how the choice of a better initial state shortens the runtime +# of VQE for obtaining the ground-state energy of a molecule. As a first step, create our +# linear :math:`\text{H}_3^+` molecule, a device, and a simple VQE circuit with single and double excitations: + +import pennylane as qml +from pennylane import qchem +from jax import numpy as jnp + +# generate the molecular Hamiltonian for H3+ +symbols = ["H", "H", "H"] +geometry = jnp.array([[0, 0, 0], [0, 0, R/0.529], [0, 0, 2*R/0.529]]) +molecule = qchem.Molecule(symbols, geometry, charge=1) + +H2mol, qubits = qchem.molecular_hamiltonian(molecule) +wires = list(range(qubits)) +dev = qml.device("default.qubit", wires=qubits) + +# create all possible excitations in H3+ +singles, doubles = qchem.excitations(2, qubits) +excitations = singles + doubles + +############################################################################## +# Now let's run VQE with the Hartree-Fock initial state. We first build the VQE circuit: + + +@qml.qnode(dev) +def circuit_VQE(theta, initial_state): + qml.StatePrep(initial_state, wires=wires) + for i, excitation in enumerate(excitations): + if len(excitation) == 4: + qml.DoubleExcitation(theta[i], wires=excitation) + else: + qml.SingleExcitation(theta[i], wires=excitation) + return qml.expval(H2mol) + + +def cost_fn(param): + return circuit_VQE(param, initial_state=wf_hf) + + +############################################################################## +# Next, we create the VQE optimizer, initialize the variational parameters and run the VQE optimization. +import optax +import jax +jax.config.update("jax_enable_x64", True) + +opt = optax.sgd(learning_rate=0.4) # sgd stands for StochasticGradientDescent +theta = jnp.array(jnp.zeros(len(excitations))) +delta_E, iteration = 10, 0 +results_hf = [] +opt_state = opt.init(theta) +prev_energy = cost_fn(theta) + +# run the VQE optimization loop until convergence threshold is reached +while abs(delta_E) > 1e-5: + gradient = jax.grad(cost_fn)(theta) + updates, opt_state = opt.update(gradient, opt_state) + theta = optax.apply_updates(theta, updates) + new_energy = cost_fn(theta) + delta_E = new_energy - prev_energy + prev_energy = new_energy + results_hf.append(new_energy) + if len(results_hf) % 5 == 0: + print(f"Step = {len(results_hf)}, Energy = {new_energy:.6f} Ha") +print(f"Starting with HF state took {len(results_hf)} iterations until convergence.") + +############################################################################## +# And compare with how things go when you run it with the CISD initial state: + + +def cost_fn_cisd(param): + return circuit_VQE(param, initial_state=wf_cisd) + + +theta = jnp.array(jnp.zeros(len(excitations))) +delta_E, iteration = 10, 0 +results_cisd = [] +opt_state = opt.init(theta) +prev_energy = cost_fn_cisd(theta) + +while abs(delta_E) > 1e-5: + gradient = jax.grad(cost_fn_cisd)(theta) + updates, opt_state = opt.update(gradient, opt_state) + theta = optax.apply_updates(theta, updates) + new_energy = cost_fn_cisd(theta) + delta_E = new_energy - prev_energy + prev_energy = new_energy + results_cisd.append(new_energy) + if len(results_cisd) % 5 == 0: + print(f"Step = {len(results_cisd)}, Energy = {new_energy:.6f} Ha") +print( + f"Starting with CISD state took {len(results_cisd)} iterations until convergence." +) + +############################################################################## +# Let's visualize the comparison between the two initial states, and see that indeed +# we get to the ground state much faster by starting with the CISD state. + +import matplotlib.pyplot as plt + +fig, ax = plt.subplots() +ax.plot(range(len(results_hf)), results_hf, color="r", marker="o", label="wf_hf") +ax.plot(range(len(results_cisd)), results_cisd, color="b", marker="o", label="wf_cisd") +ax.legend(fontsize=16) +ax.tick_params(axis="both", labelsize=16) +ax.set_xlabel("Iteration", fontsize=20) +ax.set_ylabel("Energy, Ha", fontsize=20) +plt.tight_layout() +plt.show() + +############################################################################## +# Indeed, the CISD state significantly shortens the VQE runtime. +# +# It is sometimes possible to foresee the extent of this speed-up of a particular initial state +# by computing its overlap with the ground state--a traditional metric of success for initial +# states in quantum algorithms. Because in our examples the states are regular arrays, computing an +# overlap between different states is as easy as computing a dot product + +print(np.dot(wf_cisd, wf_hf).real) +print(np.dot(wf_ccsd, wf_hf).real) +print(np.dot(wf_cisd, wf_ccsd).real) + +############################################################################## +# In this particular case of :math:`\text{H}_3^+,` even CISD gives the exact wavefunction, hence both overlaps +# with the HF state are identical. In more correlated molecules, overlaps will show that the more +# multireference methods DMRG and SHCI are farther away from the Hartree-Fock state, +# allowing them to perform better (you can check this by printing the overlaps with +# DMRG and SHCI in a more correlated molecule). If a ground state in such a case was known, +# the overlap to it could tell us directly the quality of the initial state. + +############################################################################## +# Conclusion +# ----------- +# This demo shows how to import initial states from outputs of traditional quantum chemistry methods +# for use in PennyLane. We showcased simple workflows for how to run +# a variety of state-of-the-art post-Hartree-Fock methods, from libraries such as +# `PySCF `_, +# `Block2 `_ and +# `Dice `_, to generate outputs that can then be +# converted to PennyLane's state vector format with a single line of code. With these +# initial states, we use the example of VQE to demonstrate how a better choice +# of initial state can lead to improved algorithmic performance. For the molecule +# used in our example, the CISD state was sufficient: however, in more correlated +# molecules, DMRG and SHCI initial states typically provide the best speed-ups. +# +# About the author +# ---------------- +# diff --git a/demonstrations/tutorial_jax_transformations.py b/demonstrations/tutorial_jax_transformations.py index 476e8fa3a4..9c13c37271 100644 --- a/demonstrations/tutorial_jax_transformations.py +++ b/demonstrations/tutorial_jax_transformations.py @@ -1,311 +1,311 @@ -r""" -Using JAX with PennyLane -======================== - -.. meta:: - :property="og:description": Learn how to use JAX with PennyLane. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/jax.png - -.. related:: - - tutorial_qubit_rotation Basic tutorial: qubit rotation - tutorial_vqe A brief overview of VQE - tutorial_vqt Variational Quantum Thermalizer - -*Author: Chase Roberts — Posted: 12 April 2021. Last updated: 12 April 2021.* - -JAX is an incredibly powerful scientific computing library that has been gaining traction in -both the physics and deep learning communities. While JAX was originally designed for -classical machine learning (ML), many of its transformations are also useful -for quantum machine learning (QML), and can be used directly with PennyLane. -""" - -############################################################################## -# .. figure:: ../_static/demonstration_assets/jax_logo/jax.png -# :width: 50% -# :align: center -# -# In this tutorial, we'll go over a number of JAX transformations and show how you can -# use them to build and optimize quantum circuits. We'll show examples of how to -# do gradient descent with ``jax.grad``, run quantum circuits in parallel -# using ``jax.vmap``, compile and optimize simulations with ``jax.jit``, -# and control and seed the random nature of quantum computer simulations -# with ``jax.random``. By the end of this tutorial you should feel just as comfortable -# transforming quantum computing programs with JAX as you do transforming your -# neural networks. -# -# If this is your first time reading PennyLane code, we recommend going through -# the :doc:`basic tutorial ` -# first. It's all in vanilla NumPy, so you should be able to -# easily transfer what you learn to JAX when you come back. -# -# With that said, we begin by importing PennyLane, JAX, the JAX-provided version of NumPy and -# set up a two-qubit device for computations. We'll be using the ``default.qubit`` device -# for the first part of this tutorial. - -import jax -import jax.numpy as jnp -import pennylane as qml - -# Added to silence some warnings. -jax.config.update("jax_enable_x64", True) - -dev = qml.device("default.qubit", wires=2) - -############################################################################## -# Let's start with a simple example circuit that generates a two-qubit entangled state, -# then evaluates the expectation value of the Pauli-Z operator on the first wire. - - -@qml.qnode(dev, interface="jax") -def circuit(param): - # These two gates represent our QML model. - qml.RX(param, wires=0) - qml.CNOT(wires=[0, 1]) - - # The expval here will be the "cost function" we try to minimize. - # Usually, this would be defined by the problem we want to solve, - # but for this example we'll just use a single PauliZ. - return qml.expval(qml.PauliZ(0)) - - -############################################################################## -# We can now execute the circuit just like any other python function. -print(f"Result: {repr(circuit(0.123))}") - -############################################################################## -# Notice that the output of the circuit is a JAX ``DeviceArray``. -# In fact, when we use the ``default.qubit`` device, the entire computation -# is done in JAX, so we can use all of the JAX tools out of the box! -# -# Now let's move on to an example of a transformation. The code we wrote above is entirely -# differentiable, so let's calculate its gradient with ``jax.grad``. -print("\nGradient Descent") -print("---------------") - -# We use jax.grad here to transform our circuit method into one -# that calcuates the gradient of the output relative to the input. - -grad_circuit = jax.grad(circuit) -print(f"grad_circuit(jnp.pi / 2): {grad_circuit(jnp.pi / 2):0.3f}") - -# We can then use this grad_circuit function to optimize the parameter value -# via gradient descent. -param = 0.123 # Some initial value. - -print(f"Initial param: {param:0.3f}") -print(f"Initial cost: {circuit(param):0.3f}") - -for _ in range(100): # Run for 100 steps. - param -= grad_circuit(param) # Gradient-descent update. - -print(f"Tuned param: {param:0.3f}") -print(f"Tuned cost: {circuit(param):0.3f}") - -############################################################################# -# And that's QML in a nutshell! If you've done classical machine learning before, -# the above training loop should feel very familiar to you. The only difference is -# that we used a quantum computer (or rather, a simulation of one) as part of our -# model and cost calculation. In the end, almost all QML problems involve tuning some -# parameters and minimizing some cost function, just like classical ML. -# While classical ML focuses on learning classical systems like language or vision, -# QML is most useful for learning about quantum systems. For example, -# :doc:`finding chemical ground states ` -# or learning to :doc:`sample thermal energy states `. - - -############################################################################## -# Batching and Evolutionary Strategies -# ------------------------------------- -# -# .. figure:: ../_static/demonstration_assets/jax_logo/jaxvmap.png -# :width: 50% -# :align: center -# -# We just showed how we can use gradient methods to learn a parameter value, -# but on real quantum computing hardware, calculating gradients can be really expensive and noisy. -# Another approach is to use `evolutionary strategies `__ -# (ES) to learn these parameters. -# Here, we will be using the ``jax.vmap`` `transform `__ -# to make running batches of circuits much easier. ``vmap`` essentially transforms a single quantum computer into -# multiple running in parallel! - -print("\n\nBatching and Evolutionary Strategies") -print("------------------------------------") - -# Create a vectorized version of our original circuit. -vcircuit = jax.vmap(circuit) - -# Now, we call the ``vcircuit`` with multiple parameters at once and get back a -# batch of expectations. -# This examples runs 3 quantum circuits in parallel. -batch_params = jnp.array([1.02, 0.123, -0.571]) - -batched_results = vcircuit(batch_params) -print(f"Batched result: {batched_results}") - -############################################################################## -# Let's now set up our ES training loop. The idea is pretty simple. First, we -# calculate the expected values of each of our parameters. The cost values -# then determine the "weight" of that example. The lower the cost, the larger the weight. -# These batches are then used to generate a new set of parameters. - -# Needed to do randomness with JAX. -# For more info on how JAX handles randomness, see the documentation. -# https://jax.readthedocs.io/en/latest/jax.random.html -key = jax.random.PRNGKey(0) - -# Generate our first set of samples. -params = jax.random.normal(key, (100,)) -mean = jnp.average(params) -var = 1.0 -print(f"Initial value: {mean:0.3f}") -print(f"Initial cost: {circuit(mean):0.3f}") - -for _ in range(200): - # In this line, we run all 100 circuits in parallel. - costs = vcircuit(params) - - # Use exp(-x) here since the costs could be negative. - weights = jnp.exp(-costs) - mean = jnp.average(params, weights=weights) - - # We decrease the variance as we converge to a solution. - var = var * 0.97 - - # Split the PRNGKey to generate a new set of random samples. - key, split = jax.random.split(key) - params = jax.random.normal(split, (100,)) * var + mean - -print(f"Final value: {mean:0.3f}") -print(f"Final cost: {circuit(mean):0.3f}") - - -############################################################################# -# How to use jax.jit: Compiling Circuit Execution -# ----------------------------------------------- -# .. figure:: ../_static/demonstration_assets/jax_logo/jaxjit.png -# :width: 50% -# :align: center -# -# JAX is built on top of `XLA `__, a powerful -# numerics library that can optimize and cross compile computations to different hardware, -# including CPUs, GPUs, etc. JAX can compile its computation to XLA via the ``jax.jit`` -# `transform. `__ -# -# When compiling an XLA program, the compiler will do several rounds of optimization -# passes to enhance the performance of the computation. Because of this compilation overhead, -# you'll generally find the first time calling the function to be slow, but all subsequent -# calls are much, much faster. You'll likely want to do it if you're running -# the same circuit over and over but with different parameters, like you would find in almost -# all variational quantum algorithms. - - -print("\n\nJit Example") -print("-----------") - - -@qml.qnode(dev, interface="jax") -def circuit(param): - qml.RX(param, wires=0) - qml.CNOT(wires=[0, 1]) - return qml.expval(qml.PauliZ(0)) - - -# Compiling your circuit with JAX is very easy, just add jax.jit! -jit_circuit = jax.jit(circuit) - -import time - -# No jit. -start = time.time() -# JAX runs async, so .block_until_ready() blocks until the computation -# is actually finished. You'll only need to use this if you're doing benchmarking. -circuit(0.123).block_until_ready() -no_jit_time = time.time() - start - -# First call with jit. -start = time.time() -jit_circuit(0.123).block_until_ready() -first_time = time.time() - start - -# Second call with jit. -start = time.time() -jit_circuit(0.123).block_until_ready() -second_time = time.time() - start - - -print(f"No jit time: {no_jit_time:0.8f} seconds") -# Compilation overhead will make the first call slower than without jit... -print(f"First run time: {first_time:0.8f} seconds") -# ... but the second run time is >100x faster than the first! -print(f"Second run time: {second_time:0.8f} seconds") - - -# You can see that for the cost of some compilation overhead, we can -# greatly increase our performance of our simulation by orders of magnitude. - -############################################################################## -# Shots and Sampling with JAX -# ---------------------------- -# -# JAX was designed to enable experiments to be as repeatable as possible. Because of this, -# JAX requires us to seed all randomly generated values (as you saw in the above -# batching example). Sadly, the universe doesn't allow us to seed real quantum computers, -# so if we want our JAX to mimic a real device, we'll have to handle randomness ourselves. -# -# To learn more about how JAX handles randomness, visit their -# `documentation site. `__ -# -# .. note:: -# This example only applies if you are using ``jax.jit``. Otherwise, PennyLane -# automatically seeds and resets the random-number-generator for you on each call. -# -# To set the random number generating key, you'll have to pass the ``jax.random.PRNGKey`` -# when constructing the device. Because of this, if you want to use ``jax.jit`` with randomness, -# the device construction will have to happen within that jitted method. - -print("\n\nRandomness") -print("----------") - - -# Let's create our circuit with randomness and compile it with jax.jit. -@jax.jit -def circuit(key, param): - # Notice how the device construction now happens within the jitted method. - dev = qml.device("default.qubit", wires=2, shots=10, seed=key) - - # Now we can create our qnode within the circuit function. - @qml.qnode(dev, interface="jax", diff_method=None) - def my_circuit(): - qml.RX(param, wires=0) - qml.CNOT(wires=[0, 1]) - return qml.sample(qml.PauliZ(0)) - - return my_circuit() - - -key1 = jax.random.PRNGKey(0) -key2 = jax.random.PRNGKey(1) - -# Notice that the first two runs return exactly the same results, -print(f"key1: {circuit(key1, jnp.pi/2)}") -print(f"key1: {circuit(key1, jnp.pi/2)}") - -# The second run has different results. -print(f"key2: {circuit(key2, jnp.pi/2)}") - -################################################ -# Closing Remarks -# ---------------- -# By now, using JAX with PennyLane should feel very natural. They -# complement each other very nicely; JAX with its powerful transforms, and PennyLane -# with its easy access to quantum computers. We're still in early days of -# development, but we hope to continue to grow our ecosystem around JAX, -# and by extension, grow JAX into quantum computing and quantum machine learning. -# The future looks bright for this field, and we're excited to see what you build! -# -# -# About the author -# ---------------- -# +r""" +Using JAX with PennyLane +======================== + +.. meta:: + :property="og:description": Learn how to use JAX with PennyLane. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/jax.png + +.. related:: + + tutorial_qubit_rotation Basic tutorial: qubit rotation + tutorial_vqe A brief overview of VQE + tutorial_vqt Variational Quantum Thermalizer + +*Author: Chase Roberts — Posted: 12 April 2021. Last updated: 12 April 2021.* + +JAX is an incredibly powerful scientific computing library that has been gaining traction in +both the physics and deep learning communities. While JAX was originally designed for +classical machine learning (ML), many of its transformations are also useful +for quantum machine learning (QML), and can be used directly with PennyLane. +""" + +############################################################################## +# .. figure:: ../_static/demonstration_assets/jax_logo/jax.png +# :width: 50% +# :align: center +# +# In this tutorial, we'll go over a number of JAX transformations and show how you can +# use them to build and optimize quantum circuits. We'll show examples of how to +# do gradient descent with ``jax.grad``, run quantum circuits in parallel +# using ``jax.vmap``, compile and optimize simulations with ``jax.jit``, +# and control and seed the random nature of quantum computer simulations +# with ``jax.random``. By the end of this tutorial you should feel just as comfortable +# transforming quantum computing programs with JAX as you do transforming your +# neural networks. +# +# If this is your first time reading PennyLane code, we recommend going through +# the :doc:`basic tutorial ` +# first. It's all in vanilla NumPy, so you should be able to +# easily transfer what you learn to JAX when you come back. +# +# With that said, we begin by importing PennyLane, JAX, the JAX-provided version of NumPy and +# set up a two-qubit device for computations. We'll be using the ``default.qubit`` device +# for the first part of this tutorial. + +import jax +import jax.numpy as jnp +import pennylane as qml + +# Added to silence some warnings. +jax.config.update("jax_enable_x64", True) + +dev = qml.device("default.qubit", wires=2) + +############################################################################## +# Let's start with a simple example circuit that generates a two-qubit entangled state, +# then evaluates the expectation value of the Pauli-Z operator on the first wire. + + +@qml.qnode(dev, interface="jax") +def circuit(param): + # These two gates represent our QML model. + qml.RX(param, wires=0) + qml.CNOT(wires=[0, 1]) + + # The expval here will be the "cost function" we try to minimize. + # Usually, this would be defined by the problem we want to solve, + # but for this example we'll just use a single PauliZ. + return qml.expval(qml.PauliZ(0)) + + +############################################################################## +# We can now execute the circuit just like any other python function. +print(f"Result: {repr(circuit(0.123))}") + +############################################################################## +# Notice that the output of the circuit is a JAX ``DeviceArray``. +# In fact, when we use the ``default.qubit`` device, the entire computation +# is done in JAX, so we can use all of the JAX tools out of the box! +# +# Now let's move on to an example of a transformation. The code we wrote above is entirely +# differentiable, so let's calculate its gradient with ``jax.grad``. +print("\nGradient Descent") +print("---------------") + +# We use jax.grad here to transform our circuit method into one +# that calcuates the gradient of the output relative to the input. + +grad_circuit = jax.grad(circuit) +print(f"grad_circuit(jnp.pi / 2): {grad_circuit(jnp.pi / 2):0.3f}") + +# We can then use this grad_circuit function to optimize the parameter value +# via gradient descent. +param = 0.123 # Some initial value. + +print(f"Initial param: {param:0.3f}") +print(f"Initial cost: {circuit(param):0.3f}") + +for _ in range(100): # Run for 100 steps. + param -= grad_circuit(param) # Gradient-descent update. + +print(f"Tuned param: {param:0.3f}") +print(f"Tuned cost: {circuit(param):0.3f}") + +############################################################################# +# And that's QML in a nutshell! If you've done classical machine learning before, +# the above training loop should feel very familiar to you. The only difference is +# that we used a quantum computer (or rather, a simulation of one) as part of our +# model and cost calculation. In the end, almost all QML problems involve tuning some +# parameters and minimizing some cost function, just like classical ML. +# While classical ML focuses on learning classical systems like language or vision, +# QML is most useful for learning about quantum systems. For example, +# :doc:`finding chemical ground states ` +# or learning to :doc:`sample thermal energy states `. + + +############################################################################## +# Batching and Evolutionary Strategies +# ------------------------------------- +# +# .. figure:: ../_static/demonstration_assets/jax_logo/jaxvmap.png +# :width: 50% +# :align: center +# +# We just showed how we can use gradient methods to learn a parameter value, +# but on real quantum computing hardware, calculating gradients can be really expensive and noisy. +# Another approach is to use `evolutionary strategies `__ +# (ES) to learn these parameters. +# Here, we will be using the ``jax.vmap`` `transform `__ +# to make running batches of circuits much easier. ``vmap`` essentially transforms a single quantum computer into +# multiple running in parallel! + +print("\n\nBatching and Evolutionary Strategies") +print("------------------------------------") + +# Create a vectorized version of our original circuit. +vcircuit = jax.vmap(circuit) + +# Now, we call the ``vcircuit`` with multiple parameters at once and get back a +# batch of expectations. +# This examples runs 3 quantum circuits in parallel. +batch_params = jnp.array([1.02, 0.123, -0.571]) + +batched_results = vcircuit(batch_params) +print(f"Batched result: {batched_results}") + +############################################################################## +# Let's now set up our ES training loop. The idea is pretty simple. First, we +# calculate the expected values of each of our parameters. The cost values +# then determine the "weight" of that example. The lower the cost, the larger the weight. +# These batches are then used to generate a new set of parameters. + +# Needed to do randomness with JAX. +# For more info on how JAX handles randomness, see the documentation. +# https://jax.readthedocs.io/en/latest/jax.random.html +key = jax.random.PRNGKey(0) + +# Generate our first set of samples. +params = jax.random.normal(key, (100,)) +mean = jnp.average(params) +var = 1.0 +print(f"Initial value: {mean:0.3f}") +print(f"Initial cost: {circuit(mean):0.3f}") + +for _ in range(200): + # In this line, we run all 100 circuits in parallel. + costs = vcircuit(params) + + # Use exp(-x) here since the costs could be negative. + weights = jnp.exp(-costs) + mean = jnp.average(params, weights=weights) + + # We decrease the variance as we converge to a solution. + var = var * 0.97 + + # Split the PRNGKey to generate a new set of random samples. + key, split = jax.random.split(key) + params = jax.random.normal(split, (100,)) * var + mean + +print(f"Final value: {mean:0.3f}") +print(f"Final cost: {circuit(mean):0.3f}") + + +############################################################################# +# How to use jax.jit: Compiling Circuit Execution +# ----------------------------------------------- +# .. figure:: ../_static/demonstration_assets/jax_logo/jaxjit.png +# :width: 50% +# :align: center +# +# JAX is built on top of `XLA `__, a powerful +# numerics library that can optimize and cross compile computations to different hardware, +# including CPUs, GPUs, etc. JAX can compile its computation to XLA via the ``jax.jit`` +# `transform. `__ +# +# When compiling an XLA program, the compiler will do several rounds of optimization +# passes to enhance the performance of the computation. Because of this compilation overhead, +# you'll generally find the first time calling the function to be slow, but all subsequent +# calls are much, much faster. You'll likely want to do it if you're running +# the same circuit over and over but with different parameters, like you would find in almost +# all variational quantum algorithms. + + +print("\n\nJit Example") +print("-----------") + + +@qml.qnode(dev, interface="jax") +def circuit(param): + qml.RX(param, wires=0) + qml.CNOT(wires=[0, 1]) + return qml.expval(qml.PauliZ(0)) + + +# Compiling your circuit with JAX is very easy, just add jax.jit! +jit_circuit = jax.jit(circuit) + +import time + +# No jit. +start = time.time() +# JAX runs async, so .block_until_ready() blocks until the computation +# is actually finished. You'll only need to use this if you're doing benchmarking. +circuit(0.123).block_until_ready() +no_jit_time = time.time() - start + +# First call with jit. +start = time.time() +jit_circuit(0.123).block_until_ready() +first_time = time.time() - start + +# Second call with jit. +start = time.time() +jit_circuit(0.123).block_until_ready() +second_time = time.time() - start + + +print(f"No jit time: {no_jit_time:0.8f} seconds") +# Compilation overhead will make the first call slower than without jit... +print(f"First run time: {first_time:0.8f} seconds") +# ... but the second run time is >100x faster than the first! +print(f"Second run time: {second_time:0.8f} seconds") + + +# You can see that for the cost of some compilation overhead, we can +# greatly increase our performance of our simulation by orders of magnitude. + +############################################################################## +# Shots and Sampling with JAX +# ---------------------------- +# +# JAX was designed to enable experiments to be as repeatable as possible. Because of this, +# JAX requires us to seed all randomly generated values (as you saw in the above +# batching example). Sadly, the universe doesn't allow us to seed real quantum computers, +# so if we want our JAX to mimic a real device, we'll have to handle randomness ourselves. +# +# To learn more about how JAX handles randomness, visit their +# `documentation site. `__ +# +# .. note:: +# This example only applies if you are using ``jax.jit``. Otherwise, PennyLane +# automatically seeds and resets the random-number-generator for you on each call. +# +# To set the random number generating key, you'll have to pass the ``jax.random.PRNGKey`` +# when constructing the device. Because of this, if you want to use ``jax.jit`` with randomness, +# the device construction will have to happen within that jitted method. + +print("\n\nRandomness") +print("----------") + + +# Let's create our circuit with randomness and compile it with jax.jit. +@jax.jit +def circuit(key, param): + # Notice how the device construction now happens within the jitted method. + dev = qml.device("default.qubit", wires=2, shots=10, seed=key) + + # Now we can create our qnode within the circuit function. + @qml.qnode(dev, interface="jax", diff_method=None) + def my_circuit(): + qml.RX(param, wires=0) + qml.CNOT(wires=[0, 1]) + return qml.sample(qml.PauliZ(0)) + + return my_circuit() + + +key1 = jax.random.PRNGKey(0) +key2 = jax.random.PRNGKey(1) + +# Notice that the first two runs return exactly the same results, +print(f"key1: {circuit(key1, jnp.pi/2)}") +print(f"key1: {circuit(key1, jnp.pi/2)}") + +# The second run has different results. +print(f"key2: {circuit(key2, jnp.pi/2)}") + +################################################ +# Closing Remarks +# ---------------- +# By now, using JAX with PennyLane should feel very natural. They +# complement each other very nicely; JAX with its powerful transforms, and PennyLane +# with its easy access to quantum computers. We're still in early days of +# development, but we hope to continue to grow our ecosystem around JAX, +# and by extension, grow JAX into quantum computing and quantum machine learning. +# The future looks bright for this field, and we're excited to see what you build! +# +# +# About the author +# ---------------- +# diff --git a/demonstrations/tutorial_mapping.py b/demonstrations/tutorial_mapping.py index 96a757a506..8e88a64420 100644 --- a/demonstrations/tutorial_mapping.py +++ b/demonstrations/tutorial_mapping.py @@ -1,348 +1,348 @@ -r""" - -Mapping fermionic operators to qubit operators -============================================== - -Simulating quantum systems stands as one of the most anticipated applications of quantum -chemistry with the potential to transform our understanding of chemical and physical systems. These -simulations typically require mapping schemes that transform fermionic representations into qubit -representations. There are a variety of mapping schemes used in quantum computing but the -conventional ones are the Jordan-Wigner, Parity, and Bravyi-Kitaev transformations [#Tranter]_. In -this demo, you will learn about these mapping schemes and their implementation in PennyLane. You -will also learn how to use these mappings in the context of computing the ground state energy -of a molecular system. - -.. figure:: ../_static/demonstration_assets/mapping/long_image.png - :align: center - :width: 80% - :target: javascript:void(0) - -Jordan-Wigner Mapping ---------------------- -The state of a quantum system in the `second quantized `__ -formalism is typically represented in the occupation-number basis. For fermions, the occupation -number is either :math:`0` or :math:`1` as a result of the Pauli exclusion principle. The -occupation-number basis states can be represented by a vector that is constructed by -applying the fermionic creation operators, :math:`a^\dagger,` to a vacuum state: - -.. math:: - - a^\dagger | 0 \rangle = | 1 \rangle. - -Similarly, electrons can be removed from a state by applying the fermionic annihilation -operators :math:`a:` - -.. math:: - - a | 1 \rangle = | 0 \rangle. - -An intuitive way to represent a fermionic system in the qubit basis is to store the fermionic -occupation numbers in qubit states. This requires constructing qubit creation and annihilation -operators that can be applied to an initial state to provide the desired occupation number state. -These operators are defined as - -.. math:: - - Q^{\dagger}_j = \frac{1}{2}(X_j - iY_j), - -and - -.. math:: - - Q_j = \frac{1}{2}(X_j + iY_j), - -where :math:`X` and :math:`Y` are Pauli operators. However, an important property of fermionic -creation and annihilation operators is the anti-commutation relations between them, which is not -preserved by directly using the analogous qubit operators. - -.. math:: - - [a^{\dagger}_j, a^{\dagger}_k]_+ = 0, \:\:\:\:\:\:\: [a_j, a_k]_+=0, \:\:\:\:\:\:\: [a_j, a^{\dagger}_k]_+ = \delta_{jk} I, - -where :math:`I` is the identity operator. These relations are essential for capturing the Pauli -exclusion principle which requires the fermionic wave function to be antisymmetric. The -anti-commutation relations between fermionic operators can be incorporated by adding a sequence of -Pauli :math:`Z` operators to the qubit operators (can you verify that?). According to these, -the fermionic creation and annihilation operators can be represented in the basis of Pauli operators: - -.. math:: - - a_{j}^{\dagger} = \frac{1}{2}(X_j - iY_j) \otimes_{k`__, -:math:`a_{5}^{\dagger},` which creates an electron in the fifth orbital. One -way to do this in PennyLane is to use :func:`~.pennylane.fermi.from_string`. We -then map the operator using :func:`~.pennylane.fermi.jordan_wigner`. -""" - -import pennylane as qml -from pennylane.fermi import from_string, jordan_wigner - -qubits = 10 -fermi_op = from_string("5+") -pauli_jw = jordan_wigner(fermi_op, ps=True) -pauli_jw - -############################################################################### -# The long sequence of the :math:`Z` operations can significantly increase the -# resources needed to implement the operator on quantum hardware, as it may require using entangling -# operations across multiple qubits, which can be challenging to implement efficiently. One way to -# avoid having such long tails of :math:`Z` operations is to work in the parity basis where the -# fermionic state stores the parity instead of the occupation number. -# -# Parity Mapping -# -------------- -# In the Parity representation, the state of a fermionic system is represented with a binary -# vector that corresponds to the parity of the orbitals. Note that parity for a set of orbitals -# is defined as the sum (mod 2) of the occupation numbers. Let's look at an example using the -# PennyLane function :func:`~.pennylane.qchem.hf_state` to obtain the state of a system with -# :math:`10` orbitals and :math:`5` electrons in both the occupation-number and parity bases. - -orbitals = 10 -electrons = 5 -state_number = qml.qchem.hf_state(electrons, orbitals) -state_parity = qml.qchem.hf_state(electrons, orbitals, basis="parity") - -print("State in occupation number basis:\n", state_number) -print("State in parity basis:\n", state_parity) - -############################################################################## -# Note that Parity mapping solves the non-locality problem of the parity information by storing -# the parity of orbital :math:`j` in qubit :math:`j` while the occupation information for the -# orbital is stored non-locally. In the parity basis, we cannot represent the creation or -# annihilation of a particle in orbital -# :math:`j` by simply operating with qubit creation or annihilation operators. In fact, the state of -# the :math:`(j − 1)`-th qubit provides information about the occupation state of qubit :math:`j` -# and whether we need to act with a creation or annihilation operator. Similarly, the creation or -# annihilation of a particle in qubit :math:`j` changes the parity of all qubits following it. -# As a result, the operator that is equivalent to creation and annihilation operators in -# the parity basis is a two-qubit operator acting on qubits :math:`j` and :math:`j − 1,` and -# an update operator which updates the parity of all qubits with index larger than j: -# -# .. math:: -# -# a_{j}^{\dagger} = \frac{1}{2}(Z_{j-1} \otimes X_j - iY_j) \otimes_{k>j} X_{k} -# -# and -# -# .. math:: -# -# a_{j} = \frac{1}{2}(Z_{j-1} \otimes X_j + iY_j) \otimes_{k>j} X_{k} -# -# Let's now look at an example where we map our fermionic operator :math:`a_{5}^{\dagger}` in a -# :math:`10` qubit system with -# Parity mapping using :func:`~.pennylane.fermi.parity_transform` in PennyLane. - -qubits = 10 -pauli_pr = qml.parity_transform(fermi_op, qubits, ps=True) -pauli_pr - -############################################################################## -# It is evident from this example that the Parity mapping doesn't improve upon the -# the Jordan-Wigner mapping as the long :math:`Z` strings are now replaced by :math:`X` -# strings. However, a very important advantage of using parity mapping is the ability to taper two -# qubits by leveraging symmetries of molecular Hamiltonians. You can find -# more information about this in our -# `qubit tapering `__ demo. -# Let's look at an example. - -generators = [qml.prod(*[qml.Z(i) for i in range(qubits-1)]), qml.Z(qubits-1)] -paulixops = qml.paulix_ops(generators, qubits) -paulix_sector = [1, 1] -taper_op = qml.taper(pauli_pr, generators, paulixops, paulix_sector) -qml.simplify(taper_op) - -############################################################################### -# Note that the tapered operator doesn't include qubit :math:`8` and :math:`9.` -# -# Bravyi-Kitaev Mapping -# --------------------- -# The Bravyi-Kitaev mapping aims to improve the linear scaling of the Jordan-Wigner and Parity -# mappings, in the number of qubits, -# by storing both the occupation number and the parity non-locally. In this formalism, even-labelled -# qubits store the occupation number of orbitals and odd-labelled qubits store parity -# through partial sums of occupation numbers. The corresponding creation and annihilation operators -# are defined `here `__. -# Let's use the :func:`~.pennylane.fermi.bravyi_kitaev` function to map our :math:`a_{5}^{\dagger}` -# operator. - -pauli_bk = qml.bravyi_kitaev(fermi_op, qubits, ps=True) -pauli_bk - -############################################################################## -# It is clear that the local nature of the transformation in the Bravyi-Kitaev mapping helps to -# improve the number of qubits. This advantage becomes even more clear if you -# work with a larger qubit -# system. We now use the Bravyi-Kitaev mapping to construct a qubit Hamiltonian and -# compute its ground state energy with the `VQE `__ -# method. -# -# Energy Calculation -# ------------------ -# To perform a VQE calculation for a desired Hamiltonian, we need an initial state typically set to -# a Hartree-Fock state, and a set of excitation operators to build an ansatz that allows us to -# obtain the ground state and then compute the expectation value of the Hamiltonian. It is important -# to note that the initial state and the excitation operators should be consistent with the -# mapping scheme used for obtaining the qubit Hamiltonian. Let's now build these three components -# for :math:`H_2` and compute its ground state energy. For this example, we will use the -# Bravyi-Kitaev transformation but similar calculations can be run with the other mappings. -# -# Molecular Hamiltonian -# ^^^^^^^^^^^^^^^^^^^^^ -# First, let's build the molecular Hamiltonian. This requires defining the atomic symbols and -# coordinates. - -from pennylane import qchem -from jax import numpy as jnp -import jax - -jax.config.update("jax_enable_x64", True) - -symbols = ['H', 'H'] -geometry = jnp.array([[0.0, 0.0, -0.69434785], - [0.0, 0.0, 0.69434785]]) - -mol = qchem.Molecule(symbols, geometry) - -############################################################################## -# We then use the :func:`~pennylane.qchem.fermionic_hamiltonian` function to build -# the fermionic Hamiltonian for our molecule. - -h_fermi = qchem.fermionic_hamiltonian(mol)() - -############################################################################## -# We now use :func:`~pennylane.fermi.bravyi_kitaev` to transform the fermionic Hamiltonian to its -# qubit representation. - -electrons = 2 -qubits = len(h_fermi.wires) -h_pauli = qml.bravyi_kitaev(h_fermi, qubits, tol=1e-16) - -############################################################################## -# Initial state -# ^^^^^^^^^^^^^ -# We now need the initial state that has the correct number of electrons. We use the Hartree-Fock -# state which can be obtained in a user-defined basis by using :func:`~.pennylane.qchem.hf_state` in -# PennyLane. For that, we need to specify the number of electrons, the number of orbitals and the -# desired mapping. - -hf_state = qchem.hf_state(electrons, qubits, basis="bravyi_kitaev") - -############################################################################## -# Excitation operators -# ^^^^^^^^^^^^^^^^^^^^ -# We now build our quantum circuit with the :class:`~.pennylane.UCCSD` ansatz. This ansatz is -# constructed with a set of single and double excitation operators. In PennyLane, we have -# :class:`~.pennylane.SingleExcitation` and :class:`~.pennylane.DoubleExcitation` operators which -# are very efficient, but they are only compatible with the Jordan-Wigner mapping. Here we construct -# the excitation operators manually. We start from the fermionic single and double excitation -# operators defined as [#Yordanov]_ -# -# .. math:: -# -# T_i^k(\theta) = \theta(a_k^{\dagger}a_i - a_i^{\dagger}a_k) -# -# and -# -# .. math:: -# -# T_{ij}^{kl}(\theta) = \theta(a_k^{\dagger}a_l^{\dagger}a_i a_j - -# a_i^{\dagger}a_j^{\dagger}a_k a_l), -# -# where :math:`\theta` is an adjustable parameter. We can easily construct these fermionic -# excitation operators in PennyLane and then map them to the qubit basis with -# :func:`~pennylane.fermi.bravyi_kitaev`, similar to the way we transformed the fermionic -# Hamiltonian. - -from pennylane.fermi import from_string - -singles, doubles = qchem.excitations(electrons, qubits) - -singles_fermi = [] -for ex in singles: - singles_fermi.append(from_string(f"{ex[1]}+ {ex[0]}-") - - from_string(f"{ex[0]}+ {ex[1]}-")) - -doubles_fermi = [] -for ex in doubles: - doubles_fermi.append(from_string(f"{ex[3]}+ {ex[2]}+ {ex[1]}- {ex[0]}-") - - from_string(f"{ex[0]}+ {ex[1]}+ {ex[2]}- {ex[3]}-")) - -############################################################################## -# The fermionic operators are now mapped to qubit operators. - -singles_pauli = [] -for op in singles_fermi: - singles_pauli.append(qml.bravyi_kitaev(op, qubits, ps=True)) - -doubles_pauli = [] -for op in doubles_fermi: - doubles_pauli.append(qml.bravyi_kitaev(op, qubits, ps=True)) - -############################################################################## -# Note that we need to exponentiate these operators to be able to use them in the circuit -# [#Yordanov]_. We also use a set of pre-defined parameters to construct the excitation gates. - -params = jnp.array([0.22347661, 0.0, 0.0]) - -dev = qml.device("default.qubit", wires=qubits) - -@qml.qnode(dev) -def circuit(params): - qml.BasisState(hf_state, wires=range(qubits)) - - for i, excitation in enumerate(doubles_pauli): - qml.exp((excitation * params[i] / 2).operation()), range(qubits) - - for j, excitation in enumerate(singles_pauli): - qml.exp((excitation * params[i + j + 1] / 2).operation()), range(qubits) - - return qml.expval(h_pauli) - -print('Energy =', circuit(params)) - -############################################################################## -# Using the above circuit, we produce the ground state energy of :math:`H_2` molecule. -# -# Conclusion -# --------------- -# In this demo, we learned about various mapping schemes available in PennyLane and how -# they can be used to convert fermionic operators to qubits operators. We also learned -# the pros and cons associated with each scheme. The Jordan-Wigner mapping provides an intuitive -# approach while parity mapping allows tapering qubits in molecular systems. However, these two -# methods usually give qubit operators with a long chain of Pauli operators, which makes them -# challenging to implement on quantum hardware. The Bravyi-Kitaev mapping, on the other hand, -# emphasizes locality and resource efficiency, making it an attractive option for certain -# applications. Through this demonstration, we recognize the importance of choosing an appropriate -# mapping scheme tailored to the specific problem at hand and the available quantum -# resources. Lastly, we showed how a user can employ these different mappings in ground state energy -# calculations through an example. We would like to encourage the interested readers to run -# calculations for different molecular systems and observe how the scaling in the number of qubits -# is influenced by the selected mapping techniques. -# -# References -# ---------- -# -# .. [#Tranter] -# -# A. Tranter, S. Sofia, *et al.*, "The Bravyi–Kitaev Transformation: -# Properties and Applications". `International Journal of Quantum Chemistry 115.19 (2015). -# `__ -# -# .. [#Yordanov] -# -# Y. S. Yordanov, *et al.*, "Efficient quantum circuits for quantum computational chemistry". -# `Physical Review A 102.6 (2020). -# `__ -# -# About the author -# ---------------- +r""" + +Mapping fermionic operators to qubit operators +============================================== + +Simulating quantum systems stands as one of the most anticipated applications of quantum +chemistry with the potential to transform our understanding of chemical and physical systems. These +simulations typically require mapping schemes that transform fermionic representations into qubit +representations. There are a variety of mapping schemes used in quantum computing but the +conventional ones are the Jordan-Wigner, Parity, and Bravyi-Kitaev transformations [#Tranter]_. In +this demo, you will learn about these mapping schemes and their implementation in PennyLane. You +will also learn how to use these mappings in the context of computing the ground state energy +of a molecular system. + +.. figure:: ../_static/demonstration_assets/mapping/long_image.png + :align: center + :width: 80% + :target: javascript:void(0) + +Jordan-Wigner Mapping +--------------------- +The state of a quantum system in the `second quantized `__ +formalism is typically represented in the occupation-number basis. For fermions, the occupation +number is either :math:`0` or :math:`1` as a result of the Pauli exclusion principle. The +occupation-number basis states can be represented by a vector that is constructed by +applying the fermionic creation operators, :math:`a^\dagger,` to a vacuum state: + +.. math:: + + a^\dagger | 0 \rangle = | 1 \rangle. + +Similarly, electrons can be removed from a state by applying the fermionic annihilation +operators :math:`a:` + +.. math:: + + a | 1 \rangle = | 0 \rangle. + +An intuitive way to represent a fermionic system in the qubit basis is to store the fermionic +occupation numbers in qubit states. This requires constructing qubit creation and annihilation +operators that can be applied to an initial state to provide the desired occupation number state. +These operators are defined as + +.. math:: + + Q^{\dagger}_j = \frac{1}{2}(X_j - iY_j), + +and + +.. math:: + + Q_j = \frac{1}{2}(X_j + iY_j), + +where :math:`X` and :math:`Y` are Pauli operators. However, an important property of fermionic +creation and annihilation operators is the anti-commutation relations between them, which is not +preserved by directly using the analogous qubit operators. + +.. math:: + + [a^{\dagger}_j, a^{\dagger}_k]_+ = 0, \:\:\:\:\:\:\: [a_j, a_k]_+=0, \:\:\:\:\:\:\: [a_j, a^{\dagger}_k]_+ = \delta_{jk} I, + +where :math:`I` is the identity operator. These relations are essential for capturing the Pauli +exclusion principle which requires the fermionic wave function to be antisymmetric. The +anti-commutation relations between fermionic operators can be incorporated by adding a sequence of +Pauli :math:`Z` operators to the qubit operators (can you verify that?). According to these, +the fermionic creation and annihilation operators can be represented in the basis of Pauli operators: + +.. math:: + + a_{j}^{\dagger} = \frac{1}{2}(X_j - iY_j) \otimes_{k`__, +:math:`a_{5}^{\dagger},` which creates an electron in the fifth orbital. One +way to do this in PennyLane is to use :func:`~.pennylane.fermi.from_string`. We +then map the operator using :func:`~.pennylane.fermi.jordan_wigner`. +""" + +import pennylane as qml +from pennylane.fermi import from_string, jordan_wigner + +qubits = 10 +fermi_op = from_string("5+") +pauli_jw = jordan_wigner(fermi_op, ps=True) +pauli_jw + +############################################################################### +# The long sequence of the :math:`Z` operations can significantly increase the +# resources needed to implement the operator on quantum hardware, as it may require using entangling +# operations across multiple qubits, which can be challenging to implement efficiently. One way to +# avoid having such long tails of :math:`Z` operations is to work in the parity basis where the +# fermionic state stores the parity instead of the occupation number. +# +# Parity Mapping +# -------------- +# In the Parity representation, the state of a fermionic system is represented with a binary +# vector that corresponds to the parity of the orbitals. Note that parity for a set of orbitals +# is defined as the sum (mod 2) of the occupation numbers. Let's look at an example using the +# PennyLane function :func:`~.pennylane.qchem.hf_state` to obtain the state of a system with +# :math:`10` orbitals and :math:`5` electrons in both the occupation-number and parity bases. + +orbitals = 10 +electrons = 5 +state_number = qml.qchem.hf_state(electrons, orbitals) +state_parity = qml.qchem.hf_state(electrons, orbitals, basis="parity") + +print("State in occupation number basis:\n", state_number) +print("State in parity basis:\n", state_parity) + +############################################################################## +# Note that Parity mapping solves the non-locality problem of the parity information by storing +# the parity of orbital :math:`j` in qubit :math:`j` while the occupation information for the +# orbital is stored non-locally. In the parity basis, we cannot represent the creation or +# annihilation of a particle in orbital +# :math:`j` by simply operating with qubit creation or annihilation operators. In fact, the state of +# the :math:`(j − 1)`-th qubit provides information about the occupation state of qubit :math:`j` +# and whether we need to act with a creation or annihilation operator. Similarly, the creation or +# annihilation of a particle in qubit :math:`j` changes the parity of all qubits following it. +# As a result, the operator that is equivalent to creation and annihilation operators in +# the parity basis is a two-qubit operator acting on qubits :math:`j` and :math:`j − 1,` and +# an update operator which updates the parity of all qubits with index larger than j: +# +# .. math:: +# +# a_{j}^{\dagger} = \frac{1}{2}(Z_{j-1} \otimes X_j - iY_j) \otimes_{k>j} X_{k} +# +# and +# +# .. math:: +# +# a_{j} = \frac{1}{2}(Z_{j-1} \otimes X_j + iY_j) \otimes_{k>j} X_{k} +# +# Let's now look at an example where we map our fermionic operator :math:`a_{5}^{\dagger}` in a +# :math:`10` qubit system with +# Parity mapping using :func:`~.pennylane.fermi.parity_transform` in PennyLane. + +qubits = 10 +pauli_pr = qml.parity_transform(fermi_op, qubits, ps=True) +pauli_pr + +############################################################################## +# It is evident from this example that the Parity mapping doesn't improve upon the +# the Jordan-Wigner mapping as the long :math:`Z` strings are now replaced by :math:`X` +# strings. However, a very important advantage of using parity mapping is the ability to taper two +# qubits by leveraging symmetries of molecular Hamiltonians. You can find +# more information about this in our +# `qubit tapering `__ demo. +# Let's look at an example. + +generators = [qml.prod(*[qml.Z(i) for i in range(qubits-1)]), qml.Z(qubits-1)] +paulixops = qml.paulix_ops(generators, qubits) +paulix_sector = [1, 1] +taper_op = qml.taper(pauli_pr, generators, paulixops, paulix_sector) +qml.simplify(taper_op) + +############################################################################### +# Note that the tapered operator doesn't include qubit :math:`8` and :math:`9.` +# +# Bravyi-Kitaev Mapping +# --------------------- +# The Bravyi-Kitaev mapping aims to improve the linear scaling of the Jordan-Wigner and Parity +# mappings, in the number of qubits, +# by storing both the occupation number and the parity non-locally. In this formalism, even-labelled +# qubits store the occupation number of orbitals and odd-labelled qubits store parity +# through partial sums of occupation numbers. The corresponding creation and annihilation operators +# are defined `here `__. +# Let's use the :func:`~.pennylane.fermi.bravyi_kitaev` function to map our :math:`a_{5}^{\dagger}` +# operator. + +pauli_bk = qml.bravyi_kitaev(fermi_op, qubits, ps=True) +pauli_bk + +############################################################################## +# It is clear that the local nature of the transformation in the Bravyi-Kitaev mapping helps to +# improve the number of qubits. This advantage becomes even more clear if you +# work with a larger qubit +# system. We now use the Bravyi-Kitaev mapping to construct a qubit Hamiltonian and +# compute its ground state energy with the `VQE `__ +# method. +# +# Energy Calculation +# ------------------ +# To perform a VQE calculation for a desired Hamiltonian, we need an initial state typically set to +# a Hartree-Fock state, and a set of excitation operators to build an ansatz that allows us to +# obtain the ground state and then compute the expectation value of the Hamiltonian. It is important +# to note that the initial state and the excitation operators should be consistent with the +# mapping scheme used for obtaining the qubit Hamiltonian. Let's now build these three components +# for :math:`H_2` and compute its ground state energy. For this example, we will use the +# Bravyi-Kitaev transformation but similar calculations can be run with the other mappings. +# +# Molecular Hamiltonian +# ^^^^^^^^^^^^^^^^^^^^^ +# First, let's build the molecular Hamiltonian. This requires defining the atomic symbols and +# coordinates. + +from pennylane import qchem +from jax import numpy as jnp +import jax + +jax.config.update("jax_enable_x64", True) + +symbols = ['H', 'H'] +geometry = jnp.array([[0.0, 0.0, -0.69434785], + [0.0, 0.0, 0.69434785]]) + +mol = qchem.Molecule(symbols, geometry) + +############################################################################## +# We then use the :func:`~pennylane.qchem.fermionic_hamiltonian` function to build +# the fermionic Hamiltonian for our molecule. + +h_fermi = qchem.fermionic_hamiltonian(mol)() + +############################################################################## +# We now use :func:`~pennylane.fermi.bravyi_kitaev` to transform the fermionic Hamiltonian to its +# qubit representation. + +electrons = 2 +qubits = len(h_fermi.wires) +h_pauli = qml.bravyi_kitaev(h_fermi, qubits, tol=1e-16) + +############################################################################## +# Initial state +# ^^^^^^^^^^^^^ +# We now need the initial state that has the correct number of electrons. We use the Hartree-Fock +# state which can be obtained in a user-defined basis by using :func:`~.pennylane.qchem.hf_state` in +# PennyLane. For that, we need to specify the number of electrons, the number of orbitals and the +# desired mapping. + +hf_state = qchem.hf_state(electrons, qubits, basis="bravyi_kitaev") + +############################################################################## +# Excitation operators +# ^^^^^^^^^^^^^^^^^^^^ +# We now build our quantum circuit with the :class:`~.pennylane.UCCSD` ansatz. This ansatz is +# constructed with a set of single and double excitation operators. In PennyLane, we have +# :class:`~.pennylane.SingleExcitation` and :class:`~.pennylane.DoubleExcitation` operators which +# are very efficient, but they are only compatible with the Jordan-Wigner mapping. Here we construct +# the excitation operators manually. We start from the fermionic single and double excitation +# operators defined as [#Yordanov]_ +# +# .. math:: +# +# T_i^k(\theta) = \theta(a_k^{\dagger}a_i - a_i^{\dagger}a_k) +# +# and +# +# .. math:: +# +# T_{ij}^{kl}(\theta) = \theta(a_k^{\dagger}a_l^{\dagger}a_i a_j - +# a_i^{\dagger}a_j^{\dagger}a_k a_l), +# +# where :math:`\theta` is an adjustable parameter. We can easily construct these fermionic +# excitation operators in PennyLane and then map them to the qubit basis with +# :func:`~pennylane.fermi.bravyi_kitaev`, similar to the way we transformed the fermionic +# Hamiltonian. + +from pennylane.fermi import from_string + +singles, doubles = qchem.excitations(electrons, qubits) + +singles_fermi = [] +for ex in singles: + singles_fermi.append(from_string(f"{ex[1]}+ {ex[0]}-") + - from_string(f"{ex[0]}+ {ex[1]}-")) + +doubles_fermi = [] +for ex in doubles: + doubles_fermi.append(from_string(f"{ex[3]}+ {ex[2]}+ {ex[1]}- {ex[0]}-") + - from_string(f"{ex[0]}+ {ex[1]}+ {ex[2]}- {ex[3]}-")) + +############################################################################## +# The fermionic operators are now mapped to qubit operators. + +singles_pauli = [] +for op in singles_fermi: + singles_pauli.append(qml.bravyi_kitaev(op, qubits, ps=True)) + +doubles_pauli = [] +for op in doubles_fermi: + doubles_pauli.append(qml.bravyi_kitaev(op, qubits, ps=True)) + +############################################################################## +# Note that we need to exponentiate these operators to be able to use them in the circuit +# [#Yordanov]_. We also use a set of pre-defined parameters to construct the excitation gates. + +params = jnp.array([0.22347661, 0.0, 0.0]) + +dev = qml.device("default.qubit", wires=qubits) + +@qml.qnode(dev) +def circuit(params): + qml.BasisState(hf_state, wires=range(qubits)) + + for i, excitation in enumerate(doubles_pauli): + qml.exp((excitation * params[i] / 2).operation()), range(qubits) + + for j, excitation in enumerate(singles_pauli): + qml.exp((excitation * params[i + j + 1] / 2).operation()), range(qubits) + + return qml.expval(h_pauli) + +print('Energy =', circuit(params)) + +############################################################################## +# Using the above circuit, we produce the ground state energy of :math:`H_2` molecule. +# +# Conclusion +# --------------- +# In this demo, we learned about various mapping schemes available in PennyLane and how +# they can be used to convert fermionic operators to qubits operators. We also learned +# the pros and cons associated with each scheme. The Jordan-Wigner mapping provides an intuitive +# approach while parity mapping allows tapering qubits in molecular systems. However, these two +# methods usually give qubit operators with a long chain of Pauli operators, which makes them +# challenging to implement on quantum hardware. The Bravyi-Kitaev mapping, on the other hand, +# emphasizes locality and resource efficiency, making it an attractive option for certain +# applications. Through this demonstration, we recognize the importance of choosing an appropriate +# mapping scheme tailored to the specific problem at hand and the available quantum +# resources. Lastly, we showed how a user can employ these different mappings in ground state energy +# calculations through an example. We would like to encourage the interested readers to run +# calculations for different molecular systems and observe how the scaling in the number of qubits +# is influenced by the selected mapping techniques. +# +# References +# ---------- +# +# .. [#Tranter] +# +# A. Tranter, S. Sofia, *et al.*, "The Bravyi–Kitaev Transformation: +# Properties and Applications". `International Journal of Quantum Chemistry 115.19 (2015). +# `__ +# +# .. [#Yordanov] +# +# Y. S. Yordanov, *et al.*, "Efficient quantum circuits for quantum computational chemistry". +# `Physical Review A 102.6 (2020). +# `__ +# +# About the author +# ---------------- diff --git a/demonstrations/tutorial_measurement_optimize.py b/demonstrations/tutorial_measurement_optimize.py index 198b7d2bfd..f43645c3ba 100644 --- a/demonstrations/tutorial_measurement_optimize.py +++ b/demonstrations/tutorial_measurement_optimize.py @@ -1,844 +1,844 @@ -r""" -Measurement optimization -======================== - -.. meta:: - :property="og:description": Optimize and reduce the number of measurements required to evaluate a variational algorithm cost function. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/grouping.png - -.. related:: - - tutorial_vqe A brief overview of VQE - tutorial_quantum_chemistry Building molecular Hamiltonians - tutorial_qaoa_intro Intro to QAOA - -*Author: Josh Izaac — Posted: 18 January 2021. Last updated: 29 August 2023.* - -The variational quantum eigensolver (VQE) is the OG variational quantum algorithm. Harnessing -near-term quantum hardware to solve for the electronic structure of molecules, VQE is *the* -algorithm that sparked the variational circuit craze of the last 5 years, and holds great -promise for showcasing a quantum advantage on near-term quantum hardware. It has also inspired -other quantum algorithms such as the :doc:`Quantum Approximate Optimization Algorithm (QAOA) -`. - -To scale VQE beyond the regime of classical computation, however, we need to solve for the -ground state of increasingly larger molecules. A consequence is that the number of -measurements we need to make on the quantum hardware also grows polynomially—a huge bottleneck, -especially when quantum hardware access is limited and expensive. - -To mitigate this 'measurement problem', a plethora of recent research dropped over the course of -2019 and 2020 [#yen2020]_ [#izmaylov2019]_ [#huggins2019]_ [#gokhale2020]_ [#verteletskyi2020]_ , -exploring potential strategies to minimize the number of measurements required. In fact, by grouping -commuting terms of the Hamiltonian, we can significantly reduce the number of -measurements needed—in some cases, reducing the number of measurements by up to 90%! - -.. figure:: /_static/demonstration_assets/measurement_optimize/grouping.png - :width: 90% - :align: center - -In this demonstration, we revisit the VQE algorithm, see first-hand how the required number of -measurements scales as molecule size increases, and finally use these measurement optimization -strategies to minimize the number of measurements we need to make. These techniques are valuable -beyond just VQE, allowing you to add measurement optimization to your toolkit of techniques to -perform variational algorithms more efficiently. - -Revisiting VQE --------------- - -The study of :doc:`variational quantum algorithms ` was spearheaded -by the introduction of the :doc:`variational quantum eigensolver ` (VQE) algorithm in -2014 [#peruzzo2014]_. While classical variational techniques have been known for decades to estimate -the ground state energy of a molecule, VQE allowed this variational technique to be applied using -quantum computers. Since then, the field of variational quantum algorithms has evolved -significantly, with larger and more complex models being proposed (such as -:doc:`quantum neural networks `, :doc:`QGANs `, and -:doc:`variational classifiers `). However, quantum chemistry -remains one of the flagship use-cases for variational quantum algorithms, and VQE the standard-bearer. - -Part of the appeal of VQE lies within its simplicity. A circuit ansatz :math:`U(\theta)` is chosen -(typically the Unitary Coupled-Cluster Singles and Doubles -(:func:`~pennylane.templates.subroutines.UCCSD`) ansatz), and the qubit representation of the -molecular Hamiltonian is computed: - -.. math:: H = \sum_i c_i h_i, - -where :math:`h_i` are the terms of the Hamiltonian written as a tensor product of Pauli operators or the identity -acting on wire :math:`n,` :math:`P_n \in \{I, \sigma_x, \sigma_y, \sigma_z\}:` - -.. math:: h_i = \bigotimes_{n=0}^{N-1} P_n. - -(The :math:`h_i` product of Pauli terms is often referred to as a 'Pauli word' in the literature.) The cost -function of the VQE is then simply the expectation value of this Hamiltonian on the state obtained -after running the variational quantum circuit: - -.. math:: \text{cost}(\theta) = \langle 0 | U(\theta)^\dagger H U(\theta) | 0 \rangle. - -By using a classical optimizer to *minimize* this quantity, we can estimate -the ground state energy of the Hamiltonian :math:`H:` - -.. math:: H U(\theta_{min}) |0\rangle = E_{min} U(\theta_{min}) |0\rangle. - -In practice, when we are using quantum hardware to compute these expectation values we expand out -the Hamiltonian as its summation, resulting in separate expectation values that need to be calculated for each term: - -.. math:: - - \text{cost}(\theta) = \langle 0 | U(\theta)^\dagger \left(\sum_i c_i h_i\right) U(\theta) | 0 \rangle - = \sum_i c_i \langle 0 | U(\theta)^\dagger h_i U(\theta) | 0 \rangle. - -.. note:: - - How do we compute the qubit representation of the molecular Hamiltonian? This is a more - complicated story that involves applying a self-consistent field method (such as Hartree-Fock), - and then performing a fermionic-to-qubit mapping such as the Jordan-Wigner or Bravyi-Kitaev - transformations. - - For more details on this process, check out the :doc:`/demos/tutorial_quantum_chemistry` - tutorial. - -The measurement problem ------------------------ - -For small molecules, the VQE algorithm scales and performs exceedingly well. For example, for the -Hydrogen molecule :math:`\text{H}_2,` the final Hamiltonian in its qubit representation -has 15 terms that need to be measured. Let's obtain the Hamiltonian from -`PennyLane's dataset library `__ -to verify the number of terms. In this tutorial, we use the :func:`~.pennylane.data.load` -function to download the dataset of the molecule. - -""" - -import functools -import warnings -import jax -from jax import numpy as jnp -import pennylane as qml - -jax.config.update("jax_enable_x64", True) - -dataset = qml.data.load("qchem", molname="H2", bondlength=0.7)[0] -H, num_qubits = dataset.hamiltonian, len(dataset.hamiltonian.wires) - -print("Required number of qubits:", num_qubits) -print(H) - -############################################################################### -# Here, we can see that the Hamiltonian involves 15 terms, so we expect to compute 15 expectation values -# on hardware. Let's generate the cost function to check this. - -# Create a 4 qubit simulator -dev = qml.device("default.qubit", shots=1000, seed=904932) - -# number of electrons -electrons = 2 - -# Define the Hartree-Fock initial state for our variational circuit -initial_state = qml.qchem.hf_state(electrons, num_qubits) - -# Construct the UCCSD ansatz -singles, doubles = qml.qchem.excitations(electrons, num_qubits) -s_wires, d_wires = qml.qchem.excitations_to_wires(singles, doubles) -ansatz = functools.partial( - qml.UCCSD, init_state=initial_state, s_wires=s_wires, d_wires=d_wires -) - -# generate the cost function -@qml.qnode(dev, interface="jax") -def cost_circuit(params): - ansatz(params, wires=range(num_qubits)) - return qml.expval(H) - -############################################################################## -# If we evaluate this cost function, we can see that it corresponds to 15 different -# executions under the hood—one per expectation value: - -from jax import random as random -key, scale = random.PRNGKey(0), jnp.pi -params = random.normal(key, shape=(len(singles) + len(doubles),)) * scale -with qml.Tracker(dev) as tracker: # track the number of executions - print("Cost function value:", cost_circuit(params)) - -print("Number of quantum evaluations:", tracker.totals['executions']) - -############################################################################## -# How about a larger molecule? Let's try the -# `water molecule `__: - -dataset = qml.data.load('qchem', molname="H2O")[0] -H, num_qubits = dataset.hamiltonian, len(dataset.hamiltonian.wires) - -print("Required number of qubits:", num_qubits) -print("Number of Hamiltonian terms/required measurements:", len(H.terms()[0])) - -print("\n", H) - - -############################################################################## -# Simply going from two atoms in :math:`\text{H}_2` to three in :math:`\text{H}_2 \text{O}` -# resulted in over triple the number of qubits required and 1086 measurements that must be made! -# -# We can see that as the size of our molecule increases, we run into a problem: larger molecules -# result in Hamiltonians that not only require a larger number of qubits :math:`N` in their -# representation, but the number of terms in the Hamiltonian scales like -# :math:`\mathcal{O}(N^4)!` 😱😱😱 -# -# We can mitigate this somewhat by choosing smaller `basis sets -# `__ to represent the electronic structure -# wavefunction, however this would be done at the cost of solution accuracy, and doesn't reduce the number of -# measurements significantly enough to allow us to scale to classically intractable problems. -# -# .. figure:: /_static/demonstration_assets/measurement_optimize/n4.png -# :width: 70% -# :align: center -# -# The number of qubit Hamiltonian terms required to represent various molecules in the specified -# basis sets (adapted from `Minimizing State Preparations for VQE by Gokhale et al. -# `__) - - -############################################################################## -# Simultaneously measuring observables -# ------------------------------------ -# -# One of the assumptions we made above was that every term in the Hamiltonian must be measured independently. -# However, this might not be the case. From the `Heisenberg uncertainty relationship -# `__ for two -# observables :math:`\hat{A}` and :math:`\hat{B},` we know that -# -# .. math:: \sigma_A^2 \sigma_B^2 \geq \frac{1}{2}\left|\left\langle [\hat{A}, \hat{B}] \right\rangle\right|, -# -# where :math:`\sigma^2_A` and :math:`\sigma^2_B` are the variances of measuring the expectation value of the -# associated observables, and -# -# .. math:: [\hat{A}, \hat{B}] = \hat{A}\hat{B}-\hat{B}\hat{A} -# -# is the commutator. Therefore, -# -# - If the two observables :math:`\hat{A}` and :math:`\hat{B}` do not commute (:math:`[\hat{A}, -# \hat{B}] \neq 0`), then :math:`\sigma_A^2 -# \sigma_B^2 > 0` and we cannot simultaneously measure the expectation values of the two -# observables. -# -# .. -# -# - If :math:`\hat{A}` and :math:`\hat{B}` **do** commute (:math:`[\hat{A}, -# \hat{B}] = 0`), then :math:`\sigma_A^2 -# \sigma_B^2 \geq 0` and there exists a measurement basis where we can **simultaneously measure** the -# expectation value of both observables on the same state. -# -# To explore why commutativity and simultaneous measurement are related, let's assume that there -# is a complete, orthonormal eigenbasis :math:`|\phi_n\rangle` that *simultaneously -# diagonalizes* both :math:`\hat{A}` and :math:`\hat{B}:` -# -# .. math:: -# -# ① ~~ \hat{A} |\phi_n\rangle &= \lambda_{A,n} |\phi_n\rangle,\\ -# ② ~~ \hat{B} |\phi_n\rangle &= \lambda_{B,n} |\phi_n\rangle. -# -# where :math:`\lambda_{A,n}` and :math:`\lambda_{B,n}` are the corresponding eigenvalues. -# If we pre-multiply the first equation by :math:`\hat{B},` and the second by :math:`\hat{A}` -# (both denoted in blue): -# -# .. math:: -# -# \color{blue}{\hat{B}}\hat{A} |\phi_n\rangle &= \lambda_{A,n} \color{blue}{\hat{B}} -# |\phi_n\rangle = \lambda_{A,n} \color{blue}{\lambda_{B,n}} |\phi_n\rangle,\\ -# \color{blue}{\hat{A}}\hat{B} |\phi_n\rangle &= \lambda_{B,n} \color{blue}{\hat{A}} -# |\phi_n\rangle = \lambda_{A,n} \color{blue}{\lambda_{B,n}} |\phi_n\rangle. -# -# We can see that assuming a simultaneous eigenbasis requires that -# :math:`\hat{A}\hat{B}|\phi_n\rangle = \hat{B}\hat{A}|\phi_n\rangle.` Or, rearranging, -# -# .. math:: (\hat{A}\hat{B} - \hat{B}\hat{A}) |\phi_n\rangle = [\hat{A}, \hat{B}]|\phi_n\rangle = 0. -# -# Our assumption that :math:`|\phi_n\rangle` simultaneously diagonalizes both :math:`\hat{A}` and -# :math:`\hat{B}` only holds true if the two observables commute. -# -# So far, this seems awfully theoretical. What does this mean in practice? -# -# In the realm of variational circuits, we typically want to compute expectation values of an -# observable on a given state :math:`|\psi\rangle.` If we have two commuting observables, we now know that -# they share a simultaneous eigenbasis: -# -# .. math:: -# -# \hat{A} &= \sum_n \lambda_{A, n} |\phi_n\rangle\langle \phi_n|,\\ -# \hat{B} &= \sum_n \lambda_{B, n} |\phi_n\rangle\langle \phi_n|. -# -# Substituting this into the expression for the expectation values: -# -# .. math:: -# -# \langle\hat{A}\rangle &= \langle \psi | \hat{A} | \psi \rangle = \langle \psi | \left( \sum_n -# \lambda_{A, n} |\phi_n\rangle\langle \phi_n| \right) | \psi \rangle = \sum_n \lambda_{A,n} -# |\langle \phi_n|\psi\rangle|^2,\\ -# \langle\hat{B}\rangle &= \langle \psi | \hat{B} | \psi \rangle = \langle \psi | \left( \sum_n -# \lambda_{B, n} |\phi_n\rangle\langle \phi_n| \right) | \psi \rangle = \sum_n \lambda_{B,n} -# |\langle \phi_n|\psi\rangle|^2. -# -# So, assuming we know the eigenvalues of the commuting observables in advance, if we perform a -# measurement in their shared eigenbasis (the :math:`|\phi_n\rangle`), we only need to perform a **single measurement** of the -# probabilities :math:`|\langle \phi_n|\psi\rangle|^2` in order to recover both expectation values! 😍 -# -# Fantastic! But, can we use this to reduce the number of measurements we need to perform in the VQE algorithm? -# To do so, we must find the answer to two questions: -# -# 1. How do we determine which terms of the cost Hamiltonian commute? -# -# 2. How do we rotate the circuit into the shared eigenbasis prior to measurement? -# -# The answers to these questions aren't necessarily easy nor straightforward. Thankfully, there are -# some recent techniques we can harness to address both. - -############################################################################## -# Qubit-wise commuting Pauli terms -# -------------------------------- -# -# Back when we summarized the VQE algorithm, we saw that each term of the Hamiltonian is generally represented -# as a tensor product of Pauli operators: -# -# .. math:: h_i = \bigotimes_{n=0}^{N-1} P_n. -# -# Luckily, this tensor product structure allows us to take a bit of a shortcut. Rather than consider -# **full commutativity**, we can consider a more strict condition known as **qubit-wise -# commutativity** (QWC). -# -# To start with, let's consider single-qubit Pauli operators and the identity. We know that the Pauli operators -# commute with themselves as well as the identity, but they do *not* commute with -# each other: -# -# .. math:: -# -# [\sigma_i, I] = 0, ~~~ [\sigma_i, \sigma_i] = 0, ~~~ [\sigma_i, \sigma_j] = c \sigma_k \delta_{ij}. -# -# Now consider two tensor products of Pauli terms, for example :math:`X\otimes Y \otimes I` and -# :math:`X\otimes I \otimes Z.` We say that these two terms are qubit-wise commuting, since, if -# we compare each subsystem in the tensor product, we see that every one commutes: -# -# .. math:: -# -# \begin{array}{ | *1c | *1c | *1c | *1c | *1c |} -# X &\otimes &Y &\otimes &I\\ -# X &\otimes &I &\otimes &Z -# \end{array} ~~~~ \Rightarrow ~~~~ [X, X] = 0, ~~~ [Y, I] = 0, ~~~ [I, Z] = 0. -# -# As a consequence, both terms must commute: -# -# .. math:: [X\otimes Y \otimes I, X\otimes I \otimes Z] = 0. -# -# .. important:: -# -# Qubit-wise commutativity is a **sufficient** but not **necessary** condition -# for full commutativity. For example, the two Pauli terms :math:`Y\otimes Y` and -# :math:`X\otimes X` are not qubit-wise commuting, but do commute (have a go verifying this!). -# -# Once we have identified a qubit-wise commuting pair of Pauli terms, it is also straightforward to -# find the gates to rotate the circuit into the shared eigenbasis. To do so, we simply rotate -# each qubit one-by-one depending on the Pauli operator we are measuring on that wire: -# -# .. raw:: html -# -# -#
-# -# .. rst-class:: docstable -# -# +------------------+-------------------------------+ -# | Observable | Rotation gate | -# +==================+===============================+ -# | :math:`X` | :math:`RY(-\pi/2) = H` | -# +------------------+-------------------------------+ -# | :math:`Y` | :math:`RX(\pi/2)=HS^{-1}=HSZ` | -# +------------------+-------------------------------+ -# | :math:`Z` | :math:`I` | -# +------------------+-------------------------------+ -# | :math:`I` | :math:`I` | -# +------------------+-------------------------------+ -# -# .. raw:: html -# -#
-# -# Therefore, in this particular example: -# -# * Wire 0: we are measuring both terms in the :math:`X` basis, apply the Hadamard gate -# * Wire 1: we are measuring both terms in the :math:`Y` basis, apply a :math:`RX(\pi/2)` gate -# * Wire 2: we are measuring both terms in the :math:`Z` basis (the computational basis), no gate needs to be applied. -# -# Let's use PennyLane to verify this. - - -obs = [ - qml.PauliX(0) @ qml.PauliY(1), - qml.PauliX(0) @ qml.PauliZ(2) -] - - -############################################################################## -# First, let's naively use two separate circuit evaluations to measure -# the two QWC terms. - - -dev = qml.device("default.qubit", wires=3) - -@qml.qnode(dev, interface="jax") -def circuit1(weights): - qml.StronglyEntanglingLayers(weights, wires=range(3)) - return qml.expval(obs[0]) - - -@qml.qnode(dev, interface="jax") -def circuit2(weights): - qml.StronglyEntanglingLayers(weights, wires=range(3)) - return qml.expval(obs[1]) - -param_shape = qml.templates.StronglyEntanglingLayers.shape(n_layers=3, n_wires=3) -key, scale = random.PRNGKey(192933), 0.1 -weights = scale * random.normal(key, shape=param_shape) - -print("Expectation value of XYI = ", circuit1(weights)) -print("Expectation value of XIZ = ", circuit2(weights)) - -############################################################################## -# Now, let's use our QWC approach to reduce this down to a *single* measurement -# of the probabilities in the shared eigenbasis of both QWC observables: - -@qml.qnode(dev, interface="jax") -def circuit_qwc(weights): - qml.StronglyEntanglingLayers(weights, wires=range(3)) - - # rotate wire 0 into the shared eigenbasis - qml.RY(-jnp.pi / 2, wires=0) - - # rotate wire 1 into the shared eigenbasis - qml.RX(jnp.pi / 2, wires=1) - - # wire 2 does not require a rotation - - # measure probabilities in the computational basis - return qml.probs(wires=range(3)) - - -rotated_probs = circuit_qwc(weights) -print(rotated_probs) - -############################################################################## -# We're not quite there yet; we have only calculated the probabilities of the variational circuit -# rotated into the shared eigenbasis—the :math:`|\langle \phi_n |\psi\rangle|^2.` To recover the -# *expectation values* of the two QWC observables from the probabilities, recall that we need one -# final piece of information: their eigenvalues :math:`\lambda_{A, n}` and :math:`\lambda_{B, n}.` -# -# We know that the single-qubit Pauli operators each have eigenvalues :math:`(1, -1),` while the identity -# operator has eigenvalues :math:`(1, 1).` We can make use of ``np.kron`` to quickly -# generate the eigenvalues of the full Pauli terms, making sure that the order -# of the eigenvalues in the Kronecker product corresponds to the tensor product. - -eigenvalues_XYI = jnp.kron(jnp.kron(jnp.array([1, -1]), jnp.array([1, -1])), jnp.array([1, 1])) -eigenvalues_XIZ = jnp.kron(jnp.kron(jnp.array([1, -1]), jnp.array([1, 1])), jnp.array([1, -1])) - -# Taking the linear combination of the eigenvalues and the probabilities -print("Expectation value of XYI = ", jnp.dot(eigenvalues_XYI, rotated_probs)) -print("Expectation value of XIZ = ", jnp.dot(eigenvalues_XIZ, rotated_probs)) - - -############################################################################## -# Compare this to the result when we used two circuit evaluations. We have successfully used a -# single circuit evaluation to recover both expectation values! -# -# Luckily, PennyLane automatically performs this QWC grouping under the hood. We simply -# return the two QWC Pauli terms from the QNode: - -@qml.qnode(dev, interface="jax") -def circuit(weights): - qml.StronglyEntanglingLayers(weights, wires=range(3)) - return [ - qml.expval(qml.PauliX(0) @ qml.PauliY(1)), - qml.expval(qml.PauliX(0) @ qml.PauliZ(2)) - ] - - -print(circuit(weights)) - - -############################################################################## -# Behind the scenes, PennyLane is making use of our built-in -# :mod:`qml.pauli ` module, which contains functions for diagonalizing QWC -# terms: - -rotations, new_obs = qml.pauli.diagonalize_qwc_pauli_words(obs) - -print(rotations) -print(new_obs) - - -############################################################################## -# Here, the first line corresponds to the basis rotations that were discussed above, written in -# terms of ``RX`` and ``RY`` rotations. Check out the :mod:`qml.pauli ` -# documentation for more details on its provided functionality and how it works. -# -# Given a Hamiltonian containing a large number of Pauli terms, -# there is a high likelihood of there being a significant number of terms that qubit-wise commute. Can -# we somehow partition the terms into **fewest** number of QWC groups to minimize the number of measurements -# we need to take? - -############################################################################## -# Grouping QWC terms -# ------------------ -# -# A nice example is provided in [#verteletskyi2020]_ showing how we might tackle this. Say we have -# the following Hamiltonian defined over four qubits: -# -# .. math:: H = Z_0 + Z_0 Z_1 + Z_0 Z_1 Z_2 + Z_0 Z_1 Z_2 Z_3 + X_2 X_3 + Y_0 X_2 X_3 + Y_0 Y_1 X_2 X_3, -# -# where we are using the shorthand :math:`P_0 P_2 = P\otimes I \otimes P \otimes I` for brevity. -# If we go through and work out which Pauli terms are qubit-wise commuting, we can represent -# this in a neat way using a graph: -# -# .. figure:: /_static/demonstration_assets/measurement_optimize/graph1.png -# :width: 70% -# :align: center -# -# In the above graph, every node represents an individual Pauli term of the Hamiltonian, with -# edges connecting terms that are qubit-wise commuting. Groups of qubit-wise commuting terms are -# represented as **complete subgraphs**. Straight away, we can make an observation: -# there is no unique solution for partitioning the Hamiltonian into groups of qubit-wise commuting -# terms! In fact, there are several solutions: -# -# .. figure:: /_static/demonstration_assets/measurement_optimize/graph2.png -# :width: 90% -# :align: center -# -# Of course, of the potential solutions above, there is one that is more optimal than the others --- -# on the bottom left, we have partitioned the graph into *two* complete subgraphs, as opposed to the -# other solutions that require three complete subgraphs. If we were to go with this solution, -# we would be able to measure the expectation value of the Hamiltonian using two circuit evaluations. -# -# This problem—finding the minimum number of complete subgraphs of a graph—is actually quite well -# known in graph theory, where it is referred to as the `minimum clique cover problem -# `__ (with 'clique' being another term for a complete subgraph). -# -# Unfortunately, that's where our good fortune ends—the minimum clique cover problem is known to -# be `NP-hard `__, meaning there is no known (classical) -# solution to finding the optimum/minimum clique cover in polynomial time. -# -# Thankfully, there is a silver lining: we know of polynomial-time algorithms for finding -# *approximate* solutions to the minimum clique cover problem. These heuristic approaches, while -# not guaranteed to find the optimum solution, scale quadratically with the number of nodes in the -# graph/terms in the Hamiltonian [#yen2020]_, so work reasonably well in practice. -# -# Many of these heuristic approaches have roots in another graph problem known as `graph -# colouring `__; the assignment of colours to -# the graph's vertices such that no adjacent vertices have the same colour. How is this related -# to the minimum clique cover problem, though? If we take our QWC graph above, and generate the -# `complement graph `__ by drawing edges -# between all *non*-adjacent nodes, -# -# .. figure:: /_static/demonstration_assets/measurement_optimize/graph3.png -# :width: 100% -# :align: center -# -# we see that solving the minimum clique cover problem on the QWC graph is equivalent to solving the -# graph colouring problem on the complement graph using the minimum possible number of colours. -# While there are various different heuristic algorithms, a common one is `greedy colouring -# `__; in fact, the open-source graph -# package `NetworkX even provides a function for greedy colouring -# `__, -# ``nx.greedy_color``. -# -# Let's give this a go, using NetworkX to solve the minimum clique problem for observable grouping. -# First, we'll need to generate the QWC graph (with each node corresponding to a Hamiltonian -# term, and edges indicating two terms that are QWC). - -import networkx as nx -from matplotlib import pyplot as plt - -terms = [ - qml.PauliZ(0), - qml.PauliZ(0) @ qml.PauliZ(1), - qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2), - qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3), - qml.PauliX(2) @ qml.PauliX(3), - qml.PauliY(0) @ qml.PauliX(2) @ qml.PauliX(3), - qml.PauliY(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliX(3) -] - -def format_pauli_word(term): - """Convenience function that nicely formats a PennyLane - tensor observable as a Pauli word""" - if isinstance(term, qml.ops.Prod): - return " ".join([format_pauli_word(t) for t in term]) - - return f"{term.name[-1]}{term.wires.tolist()[0]}" - -G = nx.Graph() - -with warnings.catch_warnings(): - # Muting irrelevant warnings - warnings.filterwarnings( - "ignore", - message="The behaviour of operator ", - category=UserWarning, - ) - - # add the terms to the graph - G.add_nodes_from(terms) - - # add QWC edges - G.add_edges_from([ - [terms[0], terms[1]], # Z0 <--> Z0 Z1 - [terms[0], terms[2]], # Z0 <--> Z0 Z1 Z2 - [terms[0], terms[3]], # Z0 <--> Z0 Z1 Z2 Z3 - [terms[1], terms[2]], # Z0 Z1 <--> Z0 Z1 Z2 - [terms[2], terms[3]], # Z0 Z1 Z2 <--> Z0 Z1 Z2 Z3 - [terms[1], terms[3]], # Z0 Z1 <--> Z0 Z1 Z2 Z3 - [terms[0], terms[4]], # Z0 <--> X2 X3 - [terms[1], terms[4]], # Z0 Z1 <--> X2 X3 - [terms[4], terms[5]], # X2 X3 <--> Y0 X2 X3 - [terms[4], terms[6]], # X2 X3 <--> Y0 Y1 X2 X3 - [terms[5], terms[6]], # Y0 X2 X3 <--> Y0 Y1 X2 X3 - ]) - - plt.margins(x=0.1) - coords = nx.spring_layout(G, seed=1) - nx.draw( - G, - coords, - labels={node: format_pauli_word(node) for node in terms}, - with_labels=True, - node_size=500, - font_size=8, - node_color="#9eded1", - edge_color="#c1c1c1", - ) - - ############################################################################## - # We can now generate the complement graph (compare this to our handdrawn - # version above!): - - C = nx.complement(G) - coords = nx.spring_layout(C, seed=1) - - nx.draw( - C, - coords, - labels={node: format_pauli_word(node) for node in terms}, - with_labels=True, - node_size=500, - font_size=8, - node_color="#9eded1", - edge_color="#c1c1c1" - ) - - ############################################################################## - # Now that we have the complement graph, we can perform a greedy coloring to - # determine the minimum number of QWC groups: - - groups = nx.coloring.greedy_color(C, strategy="largest_first") - - # plot the complement graph with the greedy colouring - nx.draw( - C, - coords, - labels={node: format_pauli_word(node) for node in terms}, - with_labels=True, - node_size=500, - font_size=8, - node_color=[("#9eded1", "#aad4f0")[groups[node]] for node in C], - edge_color="#c1c1c1" - ) - - -num_groups = len(set(groups.values())) -print("Minimum number of QWC groupings found:", num_groups) - - -for i in range(num_groups): - print(f"\nGroup {i}:") - - for term, group_id in groups.items(): - if group_id == i: - print(format_pauli_word(term)) - -############################################################################## -# Putting it all together -# ----------------------- -# -# So, we now have a strategy for minimizing the number of measurements we need to perform -# for our VQE problem: -# -# 1. Determine which terms of the Hamiltonian are qubit-wise commuting, and use -# this to construct a graph representing the QWC relationship. -# -# 2. Construct the complement QWC graph. -# -# 3. Use a graph colouring heuristic algorithm to determine a graph colouring for the complement graph -# with a minimum number of colours. Each coloured vertex set corresponds to a -# qubit-wise commuting group of Hamiltonian terms. -# -# 4. Generate and evaluate the circuit ansatz (with additional rotations) per -# QWC grouping, extracting probability distributions. -# -# 5. Finally, post-process the probability distributions with the observable eigenvalues -# to recover the Hamiltonian expectation value. -# -# Luckily, the PennyLane ``pauli`` module makes this relatively easy. Let's walk through -# the entire process using the provided grouping functions. -# -# Steps 1-3 (finding and grouping QWC terms in the Hamiltonian) can be done via the -# :func:`qml.pauli.group_observables ` function: - -obs_groupings = qml.pauli.group_observables(terms, grouping_type='qwc', method='rlf') - - -############################################################################## -# The ``grouping_type`` argument allows us to choose how the commuting terms -# are determined (more on that later!) whereas ``method`` determines the colouring -# heuristic (in this case, ``"rlf"`` refers to Recursive Largest First, a variant of largest first colouring heuristic). -# -# If we want to see what the required rotations and measurements are, we can use the -# :func:`qml.pauli.diagonalize_qwc_groupings ` -# function: - -rotations, measurements = qml.pauli.diagonalize_qwc_groupings(obs_groupings) - -############################################################################## -# However, this isn't strictly necessary—recall previously that the QNode -# has the capability to *automatically* measure qubit-wise commuting observables! - -dev = qml.device("lightning.qubit", wires=4) - -@qml.qnode(dev, interface="jax") -def circuit(weights, group=None, **kwargs): - qml.StronglyEntanglingLayers(weights, wires=range(4)) - return [qml.expval(o) for o in group] - -param_shape = qml.templates.StronglyEntanglingLayers.shape(n_layers=3, n_wires=4) -key = random.PRNGKey(1) -weights = random.normal(key, shape=param_shape) * 0.1 -result = [jnp.array(circuit(weights, group=g)) for g in obs_groupings] - -print("Term expectation values:") -for group, expvals in enumerate(result): - print(f"Group {group} expectation values:", expvals) - -# Since all the coefficients of the Hamiltonian are unity, -# we can simply sum the expectation values. -print(" = ", jnp.sum(jnp.hstack(result))) - - -############################################################################## -# Finally, we don't need to go through this process manually every time; if our cost function can be -# written in the form of an expectation value of a Hamiltonian (as is the case for most VQE and QAOA -# problems), we can use the option ``grouping_type="qwc"`` in :class:`~.pennylane.Hamiltonian` to -# automatically optimize the measurements. - -H = qml.Hamiltonian(coeffs=jnp.ones(len(terms)), observables=terms, grouping_type="qwc") -_, H_ops = H.terms() -@qml.qnode(dev, interface="jax") -def cost_fn(weights): - qml.StronglyEntanglingLayers(weights, wires=range(4)) - return qml.expval(H) -print(cost_fn(weights)) - -############################################################################## -# Beyond VQE -# ---------- -# -# Wait, hang on. We dove so deeply into measurement grouping and optimization, we forgot to check -# how this affects the number of measurements required to perform the VQE on :math:`\text{H}_2 \text{O}!` -# Let's use our new-found knowledge to see what happens. - -dataset = qml.data.load('qchem', molname="H2O")[0] -H, num_qubits = dataset.hamiltonian, len(dataset.hamiltonian.wires) -print("Number of Hamiltonian terms/required measurements:", len(H_ops)) - -# grouping -groups = qml.pauli.group_observables(H_ops, grouping_type='qwc', method='rlf') -print("Number of required measurements after optimization:", len(groups)) - -############################################################################## -# We went from 1086 required measurements/circuit evaluations to 320 (just over *one thousand* -# down to *three hundred* 😱😱😱). -# -# As impressive as this is, however, this is just the beginning of the optimization. -# -# While finding qubit-wise commutating terms is relatively straightforward, with a little -# extra computation we can push this number down even further. Recent work has explored -# the savings that can be made by considering *full* commutativity [#yen2020]_, unitary -# partitioning [#izmaylov2019]_, and Fermionic basis rotation grouping [#huggins2019]_. -# Work has also been performed to reduce the classical overhead associated with measurement -# optimization, allowing the classical measurement grouping to be performed in linear time -# [#gokhale2020]_. For example, recall that qubit-wise commutativity is only a subset of -# full commutativity; if we consider full commutativity instead, we can further reduce the -# number of groups required. -# -# Finally, it is worth pointing out that, as the field of variational quantum algorithms grows, this -# problem of measurement optimization no longer just applies to the VQE algorithm (the algorithm it -# was born from). Instead, there are a multitude of algorithms that could benefit from these -# measurement optimization techniques (QAOA being a prime example). -# -# So the next time you are working on a variational quantum algorithm and the number -# of measurements required begins to explode—stop, take a deep breath 😤, and consider grouping -# and optimizing your measurements. -# -# .. note:: -# -# Qubit-wise commuting group information for a wide variety of molecules has been -# pre-computed, and is available for download in -# in the `PennyLane Datasets library `__. - -############################################################################## -# References -# ---------- -# -# .. [#peruzzo2014] -# -# Alberto Peruzzo, Jarrod McClean *et al.*, "A variational eigenvalue solver on a photonic -# quantum processor". `Nature Communications 5, 4213 (2014). -# `__ -# -# .. [#yen2020] -# -# Tzu-Ching Yen, Vladyslav Verteletskyi, and Artur F. Izmaylov. "Measuring all compatible -# operators in one series of single-qubit measurements using unitary transformations." `Journal of -# Chemical Theory and Computation 16.4 (2020): 2400-2409. -# `__ -# -# .. [#izmaylov2019] -# -# Artur F. Izmaylov, *et al.* "Unitary partitioning approach to the measurement problem in the -# variational quantum eigensolver method." `Journal of Chemical Theory and Computation 16.1 (2019): -# 190-195. `__ -# -# .. [#huggins2019] -# -# William J. Huggins, *et al.* "Efficient and noise resilient measurements for quantum chemistry -# on near-term quantum computers." `arXiv preprint arXiv:1907.13117 (2019). -# `__ -# -# .. [#gokhale2020] -# -# Pranav Gokhale, *et al.* "Minimizing state preparations in variational quantum eigensolver by -# partitioning into commuting families." `arXiv preprint arXiv:1907.13623 (2019). -# `__ -# -# .. [#verteletskyi2020] -# -# Vladyslav Verteletskyi, Tzu-Ching Yen, and Artur F. Izmaylov. "Measurement optimization in the -# variational quantum eigensolver using a minimum clique cover." `The Journal of Chemical Physics -# 152.12 (2020): 124114. `__ -# -# -# About the author -# ---------------- -# +r""" +Measurement optimization +======================== + +.. meta:: + :property="og:description": Optimize and reduce the number of measurements required to evaluate a variational algorithm cost function. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/grouping.png + +.. related:: + + tutorial_vqe A brief overview of VQE + tutorial_quantum_chemistry Building molecular Hamiltonians + tutorial_qaoa_intro Intro to QAOA + +*Author: Josh Izaac — Posted: 18 January 2021. Last updated: 29 August 2023.* + +The variational quantum eigensolver (VQE) is the OG variational quantum algorithm. Harnessing +near-term quantum hardware to solve for the electronic structure of molecules, VQE is *the* +algorithm that sparked the variational circuit craze of the last 5 years, and holds great +promise for showcasing a quantum advantage on near-term quantum hardware. It has also inspired +other quantum algorithms such as the :doc:`Quantum Approximate Optimization Algorithm (QAOA) +`. + +To scale VQE beyond the regime of classical computation, however, we need to solve for the +ground state of increasingly larger molecules. A consequence is that the number of +measurements we need to make on the quantum hardware also grows polynomially—a huge bottleneck, +especially when quantum hardware access is limited and expensive. + +To mitigate this 'measurement problem', a plethora of recent research dropped over the course of +2019 and 2020 [#yen2020]_ [#izmaylov2019]_ [#huggins2019]_ [#gokhale2020]_ [#verteletskyi2020]_ , +exploring potential strategies to minimize the number of measurements required. In fact, by grouping +commuting terms of the Hamiltonian, we can significantly reduce the number of +measurements needed—in some cases, reducing the number of measurements by up to 90%! + +.. figure:: /_static/demonstration_assets/measurement_optimize/grouping.png + :width: 90% + :align: center + +In this demonstration, we revisit the VQE algorithm, see first-hand how the required number of +measurements scales as molecule size increases, and finally use these measurement optimization +strategies to minimize the number of measurements we need to make. These techniques are valuable +beyond just VQE, allowing you to add measurement optimization to your toolkit of techniques to +perform variational algorithms more efficiently. + +Revisiting VQE +-------------- + +The study of :doc:`variational quantum algorithms ` was spearheaded +by the introduction of the :doc:`variational quantum eigensolver ` (VQE) algorithm in +2014 [#peruzzo2014]_. While classical variational techniques have been known for decades to estimate +the ground state energy of a molecule, VQE allowed this variational technique to be applied using +quantum computers. Since then, the field of variational quantum algorithms has evolved +significantly, with larger and more complex models being proposed (such as +:doc:`quantum neural networks `, :doc:`QGANs `, and +:doc:`variational classifiers `). However, quantum chemistry +remains one of the flagship use-cases for variational quantum algorithms, and VQE the standard-bearer. + +Part of the appeal of VQE lies within its simplicity. A circuit ansatz :math:`U(\theta)` is chosen +(typically the Unitary Coupled-Cluster Singles and Doubles +(:func:`~pennylane.templates.subroutines.UCCSD`) ansatz), and the qubit representation of the +molecular Hamiltonian is computed: + +.. math:: H = \sum_i c_i h_i, + +where :math:`h_i` are the terms of the Hamiltonian written as a tensor product of Pauli operators or the identity +acting on wire :math:`n,` :math:`P_n \in \{I, \sigma_x, \sigma_y, \sigma_z\}:` + +.. math:: h_i = \bigotimes_{n=0}^{N-1} P_n. + +(The :math:`h_i` product of Pauli terms is often referred to as a 'Pauli word' in the literature.) The cost +function of the VQE is then simply the expectation value of this Hamiltonian on the state obtained +after running the variational quantum circuit: + +.. math:: \text{cost}(\theta) = \langle 0 | U(\theta)^\dagger H U(\theta) | 0 \rangle. + +By using a classical optimizer to *minimize* this quantity, we can estimate +the ground state energy of the Hamiltonian :math:`H:` + +.. math:: H U(\theta_{min}) |0\rangle = E_{min} U(\theta_{min}) |0\rangle. + +In practice, when we are using quantum hardware to compute these expectation values we expand out +the Hamiltonian as its summation, resulting in separate expectation values that need to be calculated for each term: + +.. math:: + + \text{cost}(\theta) = \langle 0 | U(\theta)^\dagger \left(\sum_i c_i h_i\right) U(\theta) | 0 \rangle + = \sum_i c_i \langle 0 | U(\theta)^\dagger h_i U(\theta) | 0 \rangle. + +.. note:: + + How do we compute the qubit representation of the molecular Hamiltonian? This is a more + complicated story that involves applying a self-consistent field method (such as Hartree-Fock), + and then performing a fermionic-to-qubit mapping such as the Jordan-Wigner or Bravyi-Kitaev + transformations. + + For more details on this process, check out the :doc:`/demos/tutorial_quantum_chemistry` + tutorial. + +The measurement problem +----------------------- + +For small molecules, the VQE algorithm scales and performs exceedingly well. For example, for the +Hydrogen molecule :math:`\text{H}_2,` the final Hamiltonian in its qubit representation +has 15 terms that need to be measured. Let's obtain the Hamiltonian from +`PennyLane's dataset library `__ +to verify the number of terms. In this tutorial, we use the :func:`~.pennylane.data.load` +function to download the dataset of the molecule. + +""" + +import functools +import warnings +import jax +from jax import numpy as jnp +import pennylane as qml + +jax.config.update("jax_enable_x64", True) + +dataset = qml.data.load("qchem", molname="H2", bondlength=0.7)[0] +H, num_qubits = dataset.hamiltonian, len(dataset.hamiltonian.wires) + +print("Required number of qubits:", num_qubits) +print(H) + +############################################################################### +# Here, we can see that the Hamiltonian involves 15 terms, so we expect to compute 15 expectation values +# on hardware. Let's generate the cost function to check this. + +# Create a 4 qubit simulator +dev = qml.device("default.qubit", shots=1000, seed=904932) + +# number of electrons +electrons = 2 + +# Define the Hartree-Fock initial state for our variational circuit +initial_state = qml.qchem.hf_state(electrons, num_qubits) + +# Construct the UCCSD ansatz +singles, doubles = qml.qchem.excitations(electrons, num_qubits) +s_wires, d_wires = qml.qchem.excitations_to_wires(singles, doubles) +ansatz = functools.partial( + qml.UCCSD, init_state=initial_state, s_wires=s_wires, d_wires=d_wires +) + +# generate the cost function +@qml.qnode(dev, interface="jax") +def cost_circuit(params): + ansatz(params, wires=range(num_qubits)) + return qml.expval(H) + +############################################################################## +# If we evaluate this cost function, we can see that it corresponds to 15 different +# executions under the hood—one per expectation value: + +from jax import random as random +key, scale = random.PRNGKey(0), jnp.pi +params = random.normal(key, shape=(len(singles) + len(doubles),)) * scale +with qml.Tracker(dev) as tracker: # track the number of executions + print("Cost function value:", cost_circuit(params)) + +print("Number of quantum evaluations:", tracker.totals['executions']) + +############################################################################## +# How about a larger molecule? Let's try the +# `water molecule `__: + +dataset = qml.data.load('qchem', molname="H2O")[0] +H, num_qubits = dataset.hamiltonian, len(dataset.hamiltonian.wires) + +print("Required number of qubits:", num_qubits) +print("Number of Hamiltonian terms/required measurements:", len(H.terms()[0])) + +print("\n", H) + + +############################################################################## +# Simply going from two atoms in :math:`\text{H}_2` to three in :math:`\text{H}_2 \text{O}` +# resulted in over triple the number of qubits required and 1086 measurements that must be made! +# +# We can see that as the size of our molecule increases, we run into a problem: larger molecules +# result in Hamiltonians that not only require a larger number of qubits :math:`N` in their +# representation, but the number of terms in the Hamiltonian scales like +# :math:`\mathcal{O}(N^4)!` 😱😱😱 +# +# We can mitigate this somewhat by choosing smaller `basis sets +# `__ to represent the electronic structure +# wavefunction, however this would be done at the cost of solution accuracy, and doesn't reduce the number of +# measurements significantly enough to allow us to scale to classically intractable problems. +# +# .. figure:: /_static/demonstration_assets/measurement_optimize/n4.png +# :width: 70% +# :align: center +# +# The number of qubit Hamiltonian terms required to represent various molecules in the specified +# basis sets (adapted from `Minimizing State Preparations for VQE by Gokhale et al. +# `__) + + +############################################################################## +# Simultaneously measuring observables +# ------------------------------------ +# +# One of the assumptions we made above was that every term in the Hamiltonian must be measured independently. +# However, this might not be the case. From the `Heisenberg uncertainty relationship +# `__ for two +# observables :math:`\hat{A}` and :math:`\hat{B},` we know that +# +# .. math:: \sigma_A^2 \sigma_B^2 \geq \frac{1}{2}\left|\left\langle [\hat{A}, \hat{B}] \right\rangle\right|, +# +# where :math:`\sigma^2_A` and :math:`\sigma^2_B` are the variances of measuring the expectation value of the +# associated observables, and +# +# .. math:: [\hat{A}, \hat{B}] = \hat{A}\hat{B}-\hat{B}\hat{A} +# +# is the commutator. Therefore, +# +# - If the two observables :math:`\hat{A}` and :math:`\hat{B}` do not commute (:math:`[\hat{A}, +# \hat{B}] \neq 0`), then :math:`\sigma_A^2 +# \sigma_B^2 > 0` and we cannot simultaneously measure the expectation values of the two +# observables. +# +# .. +# +# - If :math:`\hat{A}` and :math:`\hat{B}` **do** commute (:math:`[\hat{A}, +# \hat{B}] = 0`), then :math:`\sigma_A^2 +# \sigma_B^2 \geq 0` and there exists a measurement basis where we can **simultaneously measure** the +# expectation value of both observables on the same state. +# +# To explore why commutativity and simultaneous measurement are related, let's assume that there +# is a complete, orthonormal eigenbasis :math:`|\phi_n\rangle` that *simultaneously +# diagonalizes* both :math:`\hat{A}` and :math:`\hat{B}:` +# +# .. math:: +# +# ① ~~ \hat{A} |\phi_n\rangle &= \lambda_{A,n} |\phi_n\rangle,\\ +# ② ~~ \hat{B} |\phi_n\rangle &= \lambda_{B,n} |\phi_n\rangle. +# +# where :math:`\lambda_{A,n}` and :math:`\lambda_{B,n}` are the corresponding eigenvalues. +# If we pre-multiply the first equation by :math:`\hat{B},` and the second by :math:`\hat{A}` +# (both denoted in blue): +# +# .. math:: +# +# \color{blue}{\hat{B}}\hat{A} |\phi_n\rangle &= \lambda_{A,n} \color{blue}{\hat{B}} +# |\phi_n\rangle = \lambda_{A,n} \color{blue}{\lambda_{B,n}} |\phi_n\rangle,\\ +# \color{blue}{\hat{A}}\hat{B} |\phi_n\rangle &= \lambda_{B,n} \color{blue}{\hat{A}} +# |\phi_n\rangle = \lambda_{A,n} \color{blue}{\lambda_{B,n}} |\phi_n\rangle. +# +# We can see that assuming a simultaneous eigenbasis requires that +# :math:`\hat{A}\hat{B}|\phi_n\rangle = \hat{B}\hat{A}|\phi_n\rangle.` Or, rearranging, +# +# .. math:: (\hat{A}\hat{B} - \hat{B}\hat{A}) |\phi_n\rangle = [\hat{A}, \hat{B}]|\phi_n\rangle = 0. +# +# Our assumption that :math:`|\phi_n\rangle` simultaneously diagonalizes both :math:`\hat{A}` and +# :math:`\hat{B}` only holds true if the two observables commute. +# +# So far, this seems awfully theoretical. What does this mean in practice? +# +# In the realm of variational circuits, we typically want to compute expectation values of an +# observable on a given state :math:`|\psi\rangle.` If we have two commuting observables, we now know that +# they share a simultaneous eigenbasis: +# +# .. math:: +# +# \hat{A} &= \sum_n \lambda_{A, n} |\phi_n\rangle\langle \phi_n|,\\ +# \hat{B} &= \sum_n \lambda_{B, n} |\phi_n\rangle\langle \phi_n|. +# +# Substituting this into the expression for the expectation values: +# +# .. math:: +# +# \langle\hat{A}\rangle &= \langle \psi | \hat{A} | \psi \rangle = \langle \psi | \left( \sum_n +# \lambda_{A, n} |\phi_n\rangle\langle \phi_n| \right) | \psi \rangle = \sum_n \lambda_{A,n} +# |\langle \phi_n|\psi\rangle|^2,\\ +# \langle\hat{B}\rangle &= \langle \psi | \hat{B} | \psi \rangle = \langle \psi | \left( \sum_n +# \lambda_{B, n} |\phi_n\rangle\langle \phi_n| \right) | \psi \rangle = \sum_n \lambda_{B,n} +# |\langle \phi_n|\psi\rangle|^2. +# +# So, assuming we know the eigenvalues of the commuting observables in advance, if we perform a +# measurement in their shared eigenbasis (the :math:`|\phi_n\rangle`), we only need to perform a **single measurement** of the +# probabilities :math:`|\langle \phi_n|\psi\rangle|^2` in order to recover both expectation values! 😍 +# +# Fantastic! But, can we use this to reduce the number of measurements we need to perform in the VQE algorithm? +# To do so, we must find the answer to two questions: +# +# 1. How do we determine which terms of the cost Hamiltonian commute? +# +# 2. How do we rotate the circuit into the shared eigenbasis prior to measurement? +# +# The answers to these questions aren't necessarily easy nor straightforward. Thankfully, there are +# some recent techniques we can harness to address both. + +############################################################################## +# Qubit-wise commuting Pauli terms +# -------------------------------- +# +# Back when we summarized the VQE algorithm, we saw that each term of the Hamiltonian is generally represented +# as a tensor product of Pauli operators: +# +# .. math:: h_i = \bigotimes_{n=0}^{N-1} P_n. +# +# Luckily, this tensor product structure allows us to take a bit of a shortcut. Rather than consider +# **full commutativity**, we can consider a more strict condition known as **qubit-wise +# commutativity** (QWC). +# +# To start with, let's consider single-qubit Pauli operators and the identity. We know that the Pauli operators +# commute with themselves as well as the identity, but they do *not* commute with +# each other: +# +# .. math:: +# +# [\sigma_i, I] = 0, ~~~ [\sigma_i, \sigma_i] = 0, ~~~ [\sigma_i, \sigma_j] = c \sigma_k \delta_{ij}. +# +# Now consider two tensor products of Pauli terms, for example :math:`X\otimes Y \otimes I` and +# :math:`X\otimes I \otimes Z.` We say that these two terms are qubit-wise commuting, since, if +# we compare each subsystem in the tensor product, we see that every one commutes: +# +# .. math:: +# +# \begin{array}{ | *1c | *1c | *1c | *1c | *1c |} +# X &\otimes &Y &\otimes &I\\ +# X &\otimes &I &\otimes &Z +# \end{array} ~~~~ \Rightarrow ~~~~ [X, X] = 0, ~~~ [Y, I] = 0, ~~~ [I, Z] = 0. +# +# As a consequence, both terms must commute: +# +# .. math:: [X\otimes Y \otimes I, X\otimes I \otimes Z] = 0. +# +# .. important:: +# +# Qubit-wise commutativity is a **sufficient** but not **necessary** condition +# for full commutativity. For example, the two Pauli terms :math:`Y\otimes Y` and +# :math:`X\otimes X` are not qubit-wise commuting, but do commute (have a go verifying this!). +# +# Once we have identified a qubit-wise commuting pair of Pauli terms, it is also straightforward to +# find the gates to rotate the circuit into the shared eigenbasis. To do so, we simply rotate +# each qubit one-by-one depending on the Pauli operator we are measuring on that wire: +# +# .. raw:: html +# +# +#
+# +# .. rst-class:: docstable +# +# +------------------+-------------------------------+ +# | Observable | Rotation gate | +# +==================+===============================+ +# | :math:`X` | :math:`RY(-\pi/2) = H` | +# +------------------+-------------------------------+ +# | :math:`Y` | :math:`RX(\pi/2)=HS^{-1}=HSZ` | +# +------------------+-------------------------------+ +# | :math:`Z` | :math:`I` | +# +------------------+-------------------------------+ +# | :math:`I` | :math:`I` | +# +------------------+-------------------------------+ +# +# .. raw:: html +# +#
+# +# Therefore, in this particular example: +# +# * Wire 0: we are measuring both terms in the :math:`X` basis, apply the Hadamard gate +# * Wire 1: we are measuring both terms in the :math:`Y` basis, apply a :math:`RX(\pi/2)` gate +# * Wire 2: we are measuring both terms in the :math:`Z` basis (the computational basis), no gate needs to be applied. +# +# Let's use PennyLane to verify this. + + +obs = [ + qml.PauliX(0) @ qml.PauliY(1), + qml.PauliX(0) @ qml.PauliZ(2) +] + + +############################################################################## +# First, let's naively use two separate circuit evaluations to measure +# the two QWC terms. + + +dev = qml.device("default.qubit", wires=3) + +@qml.qnode(dev, interface="jax") +def circuit1(weights): + qml.StronglyEntanglingLayers(weights, wires=range(3)) + return qml.expval(obs[0]) + + +@qml.qnode(dev, interface="jax") +def circuit2(weights): + qml.StronglyEntanglingLayers(weights, wires=range(3)) + return qml.expval(obs[1]) + +param_shape = qml.templates.StronglyEntanglingLayers.shape(n_layers=3, n_wires=3) +key, scale = random.PRNGKey(192933), 0.1 +weights = scale * random.normal(key, shape=param_shape) + +print("Expectation value of XYI = ", circuit1(weights)) +print("Expectation value of XIZ = ", circuit2(weights)) + +############################################################################## +# Now, let's use our QWC approach to reduce this down to a *single* measurement +# of the probabilities in the shared eigenbasis of both QWC observables: + +@qml.qnode(dev, interface="jax") +def circuit_qwc(weights): + qml.StronglyEntanglingLayers(weights, wires=range(3)) + + # rotate wire 0 into the shared eigenbasis + qml.RY(-jnp.pi / 2, wires=0) + + # rotate wire 1 into the shared eigenbasis + qml.RX(jnp.pi / 2, wires=1) + + # wire 2 does not require a rotation + + # measure probabilities in the computational basis + return qml.probs(wires=range(3)) + + +rotated_probs = circuit_qwc(weights) +print(rotated_probs) + +############################################################################## +# We're not quite there yet; we have only calculated the probabilities of the variational circuit +# rotated into the shared eigenbasis—the :math:`|\langle \phi_n |\psi\rangle|^2.` To recover the +# *expectation values* of the two QWC observables from the probabilities, recall that we need one +# final piece of information: their eigenvalues :math:`\lambda_{A, n}` and :math:`\lambda_{B, n}.` +# +# We know that the single-qubit Pauli operators each have eigenvalues :math:`(1, -1),` while the identity +# operator has eigenvalues :math:`(1, 1).` We can make use of ``np.kron`` to quickly +# generate the eigenvalues of the full Pauli terms, making sure that the order +# of the eigenvalues in the Kronecker product corresponds to the tensor product. + +eigenvalues_XYI = jnp.kron(jnp.kron(jnp.array([1, -1]), jnp.array([1, -1])), jnp.array([1, 1])) +eigenvalues_XIZ = jnp.kron(jnp.kron(jnp.array([1, -1]), jnp.array([1, 1])), jnp.array([1, -1])) + +# Taking the linear combination of the eigenvalues and the probabilities +print("Expectation value of XYI = ", jnp.dot(eigenvalues_XYI, rotated_probs)) +print("Expectation value of XIZ = ", jnp.dot(eigenvalues_XIZ, rotated_probs)) + + +############################################################################## +# Compare this to the result when we used two circuit evaluations. We have successfully used a +# single circuit evaluation to recover both expectation values! +# +# Luckily, PennyLane automatically performs this QWC grouping under the hood. We simply +# return the two QWC Pauli terms from the QNode: + +@qml.qnode(dev, interface="jax") +def circuit(weights): + qml.StronglyEntanglingLayers(weights, wires=range(3)) + return [ + qml.expval(qml.PauliX(0) @ qml.PauliY(1)), + qml.expval(qml.PauliX(0) @ qml.PauliZ(2)) + ] + + +print(circuit(weights)) + + +############################################################################## +# Behind the scenes, PennyLane is making use of our built-in +# :mod:`qml.pauli ` module, which contains functions for diagonalizing QWC +# terms: + +rotations, new_obs = qml.pauli.diagonalize_qwc_pauli_words(obs) + +print(rotations) +print(new_obs) + + +############################################################################## +# Here, the first line corresponds to the basis rotations that were discussed above, written in +# terms of ``RX`` and ``RY`` rotations. Check out the :mod:`qml.pauli ` +# documentation for more details on its provided functionality and how it works. +# +# Given a Hamiltonian containing a large number of Pauli terms, +# there is a high likelihood of there being a significant number of terms that qubit-wise commute. Can +# we somehow partition the terms into **fewest** number of QWC groups to minimize the number of measurements +# we need to take? + +############################################################################## +# Grouping QWC terms +# ------------------ +# +# A nice example is provided in [#verteletskyi2020]_ showing how we might tackle this. Say we have +# the following Hamiltonian defined over four qubits: +# +# .. math:: H = Z_0 + Z_0 Z_1 + Z_0 Z_1 Z_2 + Z_0 Z_1 Z_2 Z_3 + X_2 X_3 + Y_0 X_2 X_3 + Y_0 Y_1 X_2 X_3, +# +# where we are using the shorthand :math:`P_0 P_2 = P\otimes I \otimes P \otimes I` for brevity. +# If we go through and work out which Pauli terms are qubit-wise commuting, we can represent +# this in a neat way using a graph: +# +# .. figure:: /_static/demonstration_assets/measurement_optimize/graph1.png +# :width: 70% +# :align: center +# +# In the above graph, every node represents an individual Pauli term of the Hamiltonian, with +# edges connecting terms that are qubit-wise commuting. Groups of qubit-wise commuting terms are +# represented as **complete subgraphs**. Straight away, we can make an observation: +# there is no unique solution for partitioning the Hamiltonian into groups of qubit-wise commuting +# terms! In fact, there are several solutions: +# +# .. figure:: /_static/demonstration_assets/measurement_optimize/graph2.png +# :width: 90% +# :align: center +# +# Of course, of the potential solutions above, there is one that is more optimal than the others --- +# on the bottom left, we have partitioned the graph into *two* complete subgraphs, as opposed to the +# other solutions that require three complete subgraphs. If we were to go with this solution, +# we would be able to measure the expectation value of the Hamiltonian using two circuit evaluations. +# +# This problem—finding the minimum number of complete subgraphs of a graph—is actually quite well +# known in graph theory, where it is referred to as the `minimum clique cover problem +# `__ (with 'clique' being another term for a complete subgraph). +# +# Unfortunately, that's where our good fortune ends—the minimum clique cover problem is known to +# be `NP-hard `__, meaning there is no known (classical) +# solution to finding the optimum/minimum clique cover in polynomial time. +# +# Thankfully, there is a silver lining: we know of polynomial-time algorithms for finding +# *approximate* solutions to the minimum clique cover problem. These heuristic approaches, while +# not guaranteed to find the optimum solution, scale quadratically with the number of nodes in the +# graph/terms in the Hamiltonian [#yen2020]_, so work reasonably well in practice. +# +# Many of these heuristic approaches have roots in another graph problem known as `graph +# colouring `__; the assignment of colours to +# the graph's vertices such that no adjacent vertices have the same colour. How is this related +# to the minimum clique cover problem, though? If we take our QWC graph above, and generate the +# `complement graph `__ by drawing edges +# between all *non*-adjacent nodes, +# +# .. figure:: /_static/demonstration_assets/measurement_optimize/graph3.png +# :width: 100% +# :align: center +# +# we see that solving the minimum clique cover problem on the QWC graph is equivalent to solving the +# graph colouring problem on the complement graph using the minimum possible number of colours. +# While there are various different heuristic algorithms, a common one is `greedy colouring +# `__; in fact, the open-source graph +# package `NetworkX even provides a function for greedy colouring +# `__, +# ``nx.greedy_color``. +# +# Let's give this a go, using NetworkX to solve the minimum clique problem for observable grouping. +# First, we'll need to generate the QWC graph (with each node corresponding to a Hamiltonian +# term, and edges indicating two terms that are QWC). + +import networkx as nx +from matplotlib import pyplot as plt + +terms = [ + qml.PauliZ(0), + qml.PauliZ(0) @ qml.PauliZ(1), + qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2), + qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3), + qml.PauliX(2) @ qml.PauliX(3), + qml.PauliY(0) @ qml.PauliX(2) @ qml.PauliX(3), + qml.PauliY(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliX(3) +] + +def format_pauli_word(term): + """Convenience function that nicely formats a PennyLane + tensor observable as a Pauli word""" + if isinstance(term, qml.ops.Prod): + return " ".join([format_pauli_word(t) for t in term]) + + return f"{term.name[-1]}{term.wires.tolist()[0]}" + +G = nx.Graph() + +with warnings.catch_warnings(): + # Muting irrelevant warnings + warnings.filterwarnings( + "ignore", + message="The behaviour of operator ", + category=UserWarning, + ) + + # add the terms to the graph + G.add_nodes_from(terms) + + # add QWC edges + G.add_edges_from([ + [terms[0], terms[1]], # Z0 <--> Z0 Z1 + [terms[0], terms[2]], # Z0 <--> Z0 Z1 Z2 + [terms[0], terms[3]], # Z0 <--> Z0 Z1 Z2 Z3 + [terms[1], terms[2]], # Z0 Z1 <--> Z0 Z1 Z2 + [terms[2], terms[3]], # Z0 Z1 Z2 <--> Z0 Z1 Z2 Z3 + [terms[1], terms[3]], # Z0 Z1 <--> Z0 Z1 Z2 Z3 + [terms[0], terms[4]], # Z0 <--> X2 X3 + [terms[1], terms[4]], # Z0 Z1 <--> X2 X3 + [terms[4], terms[5]], # X2 X3 <--> Y0 X2 X3 + [terms[4], terms[6]], # X2 X3 <--> Y0 Y1 X2 X3 + [terms[5], terms[6]], # Y0 X2 X3 <--> Y0 Y1 X2 X3 + ]) + + plt.margins(x=0.1) + coords = nx.spring_layout(G, seed=1) + nx.draw( + G, + coords, + labels={node: format_pauli_word(node) for node in terms}, + with_labels=True, + node_size=500, + font_size=8, + node_color="#9eded1", + edge_color="#c1c1c1", + ) + + ############################################################################## + # We can now generate the complement graph (compare this to our handdrawn + # version above!): + + C = nx.complement(G) + coords = nx.spring_layout(C, seed=1) + + nx.draw( + C, + coords, + labels={node: format_pauli_word(node) for node in terms}, + with_labels=True, + node_size=500, + font_size=8, + node_color="#9eded1", + edge_color="#c1c1c1" + ) + + ############################################################################## + # Now that we have the complement graph, we can perform a greedy coloring to + # determine the minimum number of QWC groups: + + groups = nx.coloring.greedy_color(C, strategy="largest_first") + + # plot the complement graph with the greedy colouring + nx.draw( + C, + coords, + labels={node: format_pauli_word(node) for node in terms}, + with_labels=True, + node_size=500, + font_size=8, + node_color=[("#9eded1", "#aad4f0")[groups[node]] for node in C], + edge_color="#c1c1c1" + ) + + +num_groups = len(set(groups.values())) +print("Minimum number of QWC groupings found:", num_groups) + + +for i in range(num_groups): + print(f"\nGroup {i}:") + + for term, group_id in groups.items(): + if group_id == i: + print(format_pauli_word(term)) + +############################################################################## +# Putting it all together +# ----------------------- +# +# So, we now have a strategy for minimizing the number of measurements we need to perform +# for our VQE problem: +# +# 1. Determine which terms of the Hamiltonian are qubit-wise commuting, and use +# this to construct a graph representing the QWC relationship. +# +# 2. Construct the complement QWC graph. +# +# 3. Use a graph colouring heuristic algorithm to determine a graph colouring for the complement graph +# with a minimum number of colours. Each coloured vertex set corresponds to a +# qubit-wise commuting group of Hamiltonian terms. +# +# 4. Generate and evaluate the circuit ansatz (with additional rotations) per +# QWC grouping, extracting probability distributions. +# +# 5. Finally, post-process the probability distributions with the observable eigenvalues +# to recover the Hamiltonian expectation value. +# +# Luckily, the PennyLane ``pauli`` module makes this relatively easy. Let's walk through +# the entire process using the provided grouping functions. +# +# Steps 1-3 (finding and grouping QWC terms in the Hamiltonian) can be done via the +# :func:`qml.pauli.group_observables ` function: + +obs_groupings = qml.pauli.group_observables(terms, grouping_type='qwc', method='rlf') + + +############################################################################## +# The ``grouping_type`` argument allows us to choose how the commuting terms +# are determined (more on that later!) whereas ``method`` determines the colouring +# heuristic (in this case, ``"rlf"`` refers to Recursive Largest First, a variant of largest first colouring heuristic). +# +# If we want to see what the required rotations and measurements are, we can use the +# :func:`qml.pauli.diagonalize_qwc_groupings ` +# function: + +rotations, measurements = qml.pauli.diagonalize_qwc_groupings(obs_groupings) + +############################################################################## +# However, this isn't strictly necessary—recall previously that the QNode +# has the capability to *automatically* measure qubit-wise commuting observables! + +dev = qml.device("lightning.qubit", wires=4) + +@qml.qnode(dev, interface="jax") +def circuit(weights, group=None, **kwargs): + qml.StronglyEntanglingLayers(weights, wires=range(4)) + return [qml.expval(o) for o in group] + +param_shape = qml.templates.StronglyEntanglingLayers.shape(n_layers=3, n_wires=4) +key = random.PRNGKey(1) +weights = random.normal(key, shape=param_shape) * 0.1 +result = [jnp.array(circuit(weights, group=g)) for g in obs_groupings] + +print("Term expectation values:") +for group, expvals in enumerate(result): + print(f"Group {group} expectation values:", expvals) + +# Since all the coefficients of the Hamiltonian are unity, +# we can simply sum the expectation values. +print(" = ", jnp.sum(jnp.hstack(result))) + + +############################################################################## +# Finally, we don't need to go through this process manually every time; if our cost function can be +# written in the form of an expectation value of a Hamiltonian (as is the case for most VQE and QAOA +# problems), we can use the option ``grouping_type="qwc"`` in :class:`~.pennylane.Hamiltonian` to +# automatically optimize the measurements. + +H = qml.Hamiltonian(coeffs=jnp.ones(len(terms)), observables=terms, grouping_type="qwc") +_, H_ops = H.terms() +@qml.qnode(dev, interface="jax") +def cost_fn(weights): + qml.StronglyEntanglingLayers(weights, wires=range(4)) + return qml.expval(H) +print(cost_fn(weights)) + +############################################################################## +# Beyond VQE +# ---------- +# +# Wait, hang on. We dove so deeply into measurement grouping and optimization, we forgot to check +# how this affects the number of measurements required to perform the VQE on :math:`\text{H}_2 \text{O}!` +# Let's use our new-found knowledge to see what happens. + +dataset = qml.data.load('qchem', molname="H2O")[0] +H, num_qubits = dataset.hamiltonian, len(dataset.hamiltonian.wires) +print("Number of Hamiltonian terms/required measurements:", len(H_ops)) + +# grouping +groups = qml.pauli.group_observables(H_ops, grouping_type='qwc', method='rlf') +print("Number of required measurements after optimization:", len(groups)) + +############################################################################## +# We went from 1086 required measurements/circuit evaluations to 320 (just over *one thousand* +# down to *three hundred* 😱😱😱). +# +# As impressive as this is, however, this is just the beginning of the optimization. +# +# While finding qubit-wise commutating terms is relatively straightforward, with a little +# extra computation we can push this number down even further. Recent work has explored +# the savings that can be made by considering *full* commutativity [#yen2020]_, unitary +# partitioning [#izmaylov2019]_, and Fermionic basis rotation grouping [#huggins2019]_. +# Work has also been performed to reduce the classical overhead associated with measurement +# optimization, allowing the classical measurement grouping to be performed in linear time +# [#gokhale2020]_. For example, recall that qubit-wise commutativity is only a subset of +# full commutativity; if we consider full commutativity instead, we can further reduce the +# number of groups required. +# +# Finally, it is worth pointing out that, as the field of variational quantum algorithms grows, this +# problem of measurement optimization no longer just applies to the VQE algorithm (the algorithm it +# was born from). Instead, there are a multitude of algorithms that could benefit from these +# measurement optimization techniques (QAOA being a prime example). +# +# So the next time you are working on a variational quantum algorithm and the number +# of measurements required begins to explode—stop, take a deep breath 😤, and consider grouping +# and optimizing your measurements. +# +# .. note:: +# +# Qubit-wise commuting group information for a wide variety of molecules has been +# pre-computed, and is available for download in +# in the `PennyLane Datasets library `__. + +############################################################################## +# References +# ---------- +# +# .. [#peruzzo2014] +# +# Alberto Peruzzo, Jarrod McClean *et al.*, "A variational eigenvalue solver on a photonic +# quantum processor". `Nature Communications 5, 4213 (2014). +# `__ +# +# .. [#yen2020] +# +# Tzu-Ching Yen, Vladyslav Verteletskyi, and Artur F. Izmaylov. "Measuring all compatible +# operators in one series of single-qubit measurements using unitary transformations." `Journal of +# Chemical Theory and Computation 16.4 (2020): 2400-2409. +# `__ +# +# .. [#izmaylov2019] +# +# Artur F. Izmaylov, *et al.* "Unitary partitioning approach to the measurement problem in the +# variational quantum eigensolver method." `Journal of Chemical Theory and Computation 16.1 (2019): +# 190-195. `__ +# +# .. [#huggins2019] +# +# William J. Huggins, *et al.* "Efficient and noise resilient measurements for quantum chemistry +# on near-term quantum computers." `arXiv preprint arXiv:1907.13117 (2019). +# `__ +# +# .. [#gokhale2020] +# +# Pranav Gokhale, *et al.* "Minimizing state preparations in variational quantum eigensolver by +# partitioning into commuting families." `arXiv preprint arXiv:1907.13623 (2019). +# `__ +# +# .. [#verteletskyi2020] +# +# Vladyslav Verteletskyi, Tzu-Ching Yen, and Artur F. Izmaylov. "Measurement optimization in the +# variational quantum eigensolver using a minimum clique cover." `The Journal of Chemical Physics +# 152.12 (2020): 124114. `__ +# +# +# About the author +# ---------------- +# diff --git a/demonstrations/tutorial_pasqal.py b/demonstrations/tutorial_pasqal.py index b49898f392..8618d05f4b 100644 --- a/demonstrations/tutorial_pasqal.py +++ b/demonstrations/tutorial_pasqal.py @@ -1,362 +1,362 @@ -r""" -Quantum computation with neutral atoms -====================================== - -.. meta:: - :property="og:description": Neutral atom quantum devices allow you to place - qubits within interesting three-dimensional configurations. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/pasqal_thumbnail.png - -.. related:: - ahs_aquila Pulse programming on neutral atom hardware - -*Author: Nathan Killoran — Posted: 13 October 2020. Last updated: 21 January 2021.* - -Quantum computing architectures come in many flavours: superconducting qubits, ion traps, -photonics, silicon, and more. One very interesting physical substrate is *neutral atoms*. These -quantum devices have some basic similarities to ion traps. Ion-trap devices make use of atoms -that have an imbalance between protons (positively charged) and electrons (negatively charged). -Neutral atoms, on the other hand, have an equal number of protons and electrons. - -In neutral-atom systems, the individual atoms can be easily programmed into various two- or -three-dimensional configurations. Like ion-trap systems, individual qubits can be encoded into -the energy levels of the atoms. The qubits thus retain the three-dimensional geometry of their -host atoms. Qubits that are nearby in space can be programmed to interact with one another -via two-qubit gates. This opens up some tantalizing possibilities for exotic quantum-computing -circuit topologies. - -.. figure:: https://raw.githubusercontent.com/lhenriet/cirq-pasqal/fc4f9c7792a8737fde76d4c05828aa538be8452e/pasqal-tutorials/files/eiffel_tower.png - :align: center - :width: 50% - - .. - - Neutral atoms (green dots) arranged in various configurations. These atoms can be - used to encode qubits and carry out quantum computations. Image originally from [#barredo2017]_. - -The startup `Pasqal `_ is one of the companies working to bring -neutral-atom quantum computing devices to the world. To support this new class of devices, -Pasqal has contributed some new features to the quantum software library `Cirq `_. - -In this demo, we will use PennyLane, Cirq, and TensorFlow to show off the unique abilities of -neutral atom devices, leveraging them to make a variational quantum circuit which has a -very unique topology: *the Eiffel tower*. Specifically, we will build a simple toy -circuit whose qubits are arranged like the Eiffel tower. The girders between -the points on the tower will represent two-qubit gates, with the final output of our -variational circuit coming at the very peak of the tower. - -Let's get to it! - -.. note:: - - To run this demo locally, you will need to install `Cirq - `_, (version >= 0.9.1), and the - `PennyLane-cirq plugin `_ - (version >= 0.13). You will also need to download a copy of the data, which - is available `here - `_. - -""" - -############################################################################## -# Building the Eiffel tower -# ------------------------- -# -# Our first step will be to load and visualize the data for the Eiffel tower -# configuration, which was generously provided by the team at Pasqal. -# (If running locally, the line below should be updated with the local -# path where you have saved the downloaded data). - -import numpy as np -coords = np.loadtxt("../_static/demonstration_assets/pasqal/Eiffel_tower_data.dat") -xs = coords[:,0] -ys = coords[:,1] -zs = coords[:,2] - -import matplotlib.pyplot as plt -fig = plt.figure() -ax = fig.add_subplot(111, projection='3d') -ax.view_init(40, 15) -fig.subplots_adjust(left=0, right=1, bottom=0, top=1) -ax.set_xlim(-20, 20) -ax.set_ylim(-20, 20) -ax.set_zlim(-40, 10) -plt.axis('off') -ax.scatter(xs, ys, zs, c='g',alpha=0.3) -plt.show() - -############################################################################## -# This dataset contains 126 points. Each point represents a distinct -# neutral-atom qubit. Simulating this many qubits would be outside the -# reach of Cirq's built-in simulators, so for this demo, -# we will pare down to just 9 points, evenly spaced around the tower. -# These are highlighted in red below. -# - -fig = plt.figure() -ax = fig.add_subplot(111, projection='3d') -ax.view_init(40, 15) -fig.subplots_adjust(left=0, right=1, bottom=0, top=1) -ax.set_xlim(-20, 20) -ax.set_ylim(-20, 20) -ax.set_zlim(-40, 10) -plt.axis('off') -ax.scatter(xs, ys, zs, c='g', alpha=0.3) - -base_mask = [3, 7, 11, 15] -qubit_mask = [48, 51, 60, 63, 96, 97, 98, 99, 125] -input_coords = coords[base_mask] # we'll need this for a plot later -qubit_coords = coords[qubit_mask] - -subset_xs = qubit_coords[:, 0] -subset_ys = qubit_coords[:, 1] -subset_zs = qubit_coords[:, 2] -ax.scatter(subset_xs, subset_ys, subset_zs, c='r', alpha=1.0) -plt.show() - -############################################################################## -# Converting to Cirq qubits -# ------------------------- -# -# Our next step will be to convert these datapoints into objects that -# Cirq understands as qubits. For neutral-atom devices in Cirq, we can use the -# ``ThreeDQubit`` class, which carries information about the three-dimensional -# arrangement of the qubits. -# -# Now, neutral-atom devices come with some physical restrictions. -# Specifically, in a particular three-dimensional configuration, qubits that -# are too distant from one another can't easily interact. Instead, there is -# a notion of a *control radius;* any atoms which are within the system's -# control radius can interact with one another. Qubits separated by a -# distance larger than the control radius cannot interact. -# -# In order to allow our Eiffel tower qubits to interact with -# one another more easily, we will artificially scale some dimensions -# when placing the atoms. - -from cirq_pasqal import ThreeDQubit -xy_scale = 1.5 -z_scale = 0.75 -qubits = [ThreeDQubit(xy_scale * x, xy_scale * y, z_scale * z) - for x, y, z in qubit_coords] - -############################################################################## -# To simulate a neutral-atom quantum computation, we can use the -# ``"cirq.pasqal"`` device, available via the -# `PennyLane-Cirq plugin `_. -# We will need to provide this device with the ``ThreeDQubit`` object that we created -# above. We also need to instantiate the device with a fixed control radius. - -import pennylane as qml - -num_wires = len(qubits) -control_radius = 32.4 -dev = qml.device("cirq.pasqal", control_radius=control_radius, - qubits=qubits, wires=num_wires) - -############################################################################## -# Creating a quantum circuit -# -------------------------- -# -# We will now make a variational circuit out of the Eiffel tower configuration -# from above. Each of the 9 qubits we are using can be thought of -# as a single wire in a quantum circuit. We will cause these qubits to interact by applying -# a sequence of two-qubit gates. Specifically, the circuit consists of several -# stages: -# -# i. Input classical data is converted into quantum information at the first -# (lowest) vertical level of qubits. In this example, our classical data -# will be simple bit strings, which we can embed by using single-qubit -# bit flips (a simple -# `data-embedding `_ -# strategy). -# -# ii. For each corner of the tower, CNOTs are enacted between the first- -# and second-level qubits. -# -# iii. All qubits from the second level interact with a single "peak" qubit -# using a parametrized controlled-rotation operation. The free parameters -# of our variational circuit enter here. -# -# The output of our circuit is determined via a Pauli-Z measurement on -# the final "peak" qubit. -# -# That's a few things to keep track of, so let's show the circuit via a -# three-dimensional image: - -first_lvl_coords = qubit_coords[:4] -second_lvl_coords = qubit_coords[4:8] -peak_coords = qubit_coords[8] - -input_x, input_y, input_z = [input_coords[:, idx] - for idx in range(3)] -second_x, second_y, second_z = [first_lvl_coords[:, idx] - for idx in range(3)] -third_x, third_y, third_z = [second_lvl_coords[:, idx] - for idx in range(3)] -peak_x, peak_y, peak_z = peak_coords - -fig = plt.figure() -ax = fig.add_subplot(111, projection='3d') -ax.view_init(40, 15) -fig.subplots_adjust(left=0, right=1, bottom=0, top=1) -ax.set_xlim(-20, 20) -ax.set_ylim(-20, 20) -ax.set_zlim(-40, 10) -plt.axis('off') - -ax.scatter(xs, ys, zs, c='g', alpha=0.3) -ax.scatter(subset_xs, subset_ys, subset_zs, c='r', alpha=1.0); - -# Two-qubit gates between second and third levels -for corner in range(4): - ax.plot(xs=[second_x[corner], third_x[corner]], - ys=[second_y[corner], third_y[corner]], - zs=[second_z[corner], third_z[corner]], - c='k'); - -# Two-qubit gates between third level and peak -for corner in range(4): - ax.plot(xs=[third_x[corner], peak_x], - ys=[third_y[corner], peak_y], - zs=[third_z[corner], peak_z], - c='k'); - -# Additional lines to guide the eye -for corner in range(4): - ax.plot(xs=[input_x[corner], second_x[corner]], - ys=[input_y[corner], second_y[corner]], - zs=[input_z[corner], second_z[corner]], - c='grey', linestyle='--'); - ax.plot(xs=[second_x[corner], second_x[(corner + 1) % 4]], - ys=[second_y[corner], second_y[(corner + 1) % 4]], - zs=[second_z[corner], second_z[(corner + 1) % 4]], - c='grey', linestyle='--'); - ax.plot(xs=[third_x[corner], third_x[(corner + 1) % 4]], - ys=[third_y[corner], third_y[(corner + 1) % 4]], - zs=[third_z[corner], third_z[(corner + 1) % 4]], - c='grey', linestyle='--'); - -plt.show() - -############################################################################## -# In this figure, the red dots represent the specific qubits we will use in -# our circuit (the green dots are not used in this demo). -# -# The solid black lines indicate two-qubit gates between these qubits. -# The dashed grey lines are meant to guide the eye, but could also be -# used to make a more complex model by adding further two-qubit gates. -# -# Classical data is loaded in at the bottom qubits (the "tower legs") and -# the final measurement result is read out from the top "peak" qubit. -# The order of gate execution proceeds vertically from bottom to top, and -# clockwise at each level. -# -# The code below creates this particular quantum circuit configuration in -# PennyLane: - -peak_qubit = 8 - -def controlled_rotation(phi, wires): - qml.RY(phi, wires=wires[1]) - qml.CNOT(wires=wires) - qml.RY(-phi, wires=wires[1]) - qml.CNOT(wires=wires) - -@qml.qnode(dev, interface="tf") -def circuit(weights, data): - - # Input classical data loaded into qubits at second level - for idx in range(4): - if data[idx]: - qml.PauliX(wires=idx) - - # Interact qubits from second and third levels - for idx in range(4): - qml.CNOT(wires=[idx, idx + 4]) - - # Interact qubits from third level with peak using parameterized gates - for idx, wire in enumerate(range(4, 8)): - controlled_rotation(weights[idx], wires=[wire, peak_qubit]) - - return qml.expval(qml.PauliZ(wires=peak_qubit)) - - -############################################################################## -# Training the circuit -# -------------------- -# -# Let's now leverage this variational circuit to tackle a toy classification -# problem. -# For the purposes of this demo, we will consider a very simple classifier: -# -# * if the first input qubit is in the state :math:`\mid 0 \rangle,` the model -# should make the prediction "0", and -# -# * if the first input qubit is in the state :math:`\mid 1 \rangle,` the model -# should predict "1" (independent of the states of all other qubits). -# -# In other words, the idealized trained model should learn an -# identity transformation between the first qubit and the final one, while -# ignoring the states of all other qubits. -# -# With this goal in mind, we can create a basic cost function. This cost -# function randomly samples possible 4-bit input bitstrings, and compares -# the circuit's output with the value of the first bit. The other bits -# can be thought of as noise that we don't want our model to learn. - - -import tensorflow as tf -np.random.seed(143) -init_weights = np.pi * np.random.rand(4) - -weights = tf.Variable(init_weights, dtype=tf.float64) - -data = np.random.randint(0, 2, size=4) - -def cost(): - data = np.random.randint(0, 2, size=4) - label = data[0] - output = (-circuit(weights, data) + 1) / 2 - return tf.abs(output - label) ** 2 - -opt = tf.keras.optimizers.Adam(learning_rate=0.1) - -for step in range(100): - opt.minimize(cost, [weights]) - if step % 5 == 0: - print("Step {}: cost={}".format(step, cost())) - -print("Final cost value: {}".format(cost())) - -############################################################################## -# Success! The circuit has learned to transfer the state of the first qubit -# to the state of the last qubit, while ignoring the state of all other input -# qubits. -# -# The programmable three-dimensional configurations of neutral-atom quantum -# computers provide a special tool that is hard to replicate in other -# platforms. Could the physical -# arrangement of qubits, in particular the third dimension, be leveraged to -# make quantum algorithms more sparse or efficient? Could neutral-atom -# systems—with their unique programmability of the geometry—allow us to -# rapidly prototype and experiment with new circuit topologies? What -# possibilities could this open up for quantum computing, quantum chemistry, -# or quantum machine learning? -# - -############################################################################## -# References -# ---------- -# -# .. [#barredo2017] -# -# Daniel Barredo, Vincent Lienhard, Sylvain de Leseleuc, Thierry Lahaye, and Antoine Browaeys. -# "Synthetic three-dimensional atomic structures assembled atom by atom." -# `arXiv:1712.02727 -# `__, 2017. -# -# -# About the author -# ---------------- +r""" +Quantum computation with neutral atoms +====================================== + +.. meta:: + :property="og:description": Neutral atom quantum devices allow you to place + qubits within interesting three-dimensional configurations. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/pasqal_thumbnail.png + +.. related:: + ahs_aquila Pulse programming on neutral atom hardware + +*Author: Nathan Killoran — Posted: 13 October 2020. Last updated: 21 January 2021.* + +Quantum computing architectures come in many flavours: superconducting qubits, ion traps, +photonics, silicon, and more. One very interesting physical substrate is *neutral atoms*. These +quantum devices have some basic similarities to ion traps. Ion-trap devices make use of atoms +that have an imbalance between protons (positively charged) and electrons (negatively charged). +Neutral atoms, on the other hand, have an equal number of protons and electrons. + +In neutral-atom systems, the individual atoms can be easily programmed into various two- or +three-dimensional configurations. Like ion-trap systems, individual qubits can be encoded into +the energy levels of the atoms. The qubits thus retain the three-dimensional geometry of their +host atoms. Qubits that are nearby in space can be programmed to interact with one another +via two-qubit gates. This opens up some tantalizing possibilities for exotic quantum-computing +circuit topologies. + +.. figure:: https://raw.githubusercontent.com/lhenriet/cirq-pasqal/fc4f9c7792a8737fde76d4c05828aa538be8452e/pasqal-tutorials/files/eiffel_tower.png + :align: center + :width: 50% + + .. + + Neutral atoms (green dots) arranged in various configurations. These atoms can be + used to encode qubits and carry out quantum computations. Image originally from [#barredo2017]_. + +The startup `Pasqal `_ is one of the companies working to bring +neutral-atom quantum computing devices to the world. To support this new class of devices, +Pasqal has contributed some new features to the quantum software library `Cirq `_. + +In this demo, we will use PennyLane, Cirq, and TensorFlow to show off the unique abilities of +neutral atom devices, leveraging them to make a variational quantum circuit which has a +very unique topology: *the Eiffel tower*. Specifically, we will build a simple toy +circuit whose qubits are arranged like the Eiffel tower. The girders between +the points on the tower will represent two-qubit gates, with the final output of our +variational circuit coming at the very peak of the tower. + +Let's get to it! + +.. note:: + + To run this demo locally, you will need to install `Cirq + `_, (version >= 0.9.1), and the + `PennyLane-cirq plugin `_ + (version >= 0.13). You will also need to download a copy of the data, which + is available `here + `_. + +""" + +############################################################################## +# Building the Eiffel tower +# ------------------------- +# +# Our first step will be to load and visualize the data for the Eiffel tower +# configuration, which was generously provided by the team at Pasqal. +# (If running locally, the line below should be updated with the local +# path where you have saved the downloaded data). + +import numpy as np +coords = np.loadtxt("../_static/demonstration_assets/pasqal/Eiffel_tower_data.dat") +xs = coords[:,0] +ys = coords[:,1] +zs = coords[:,2] + +import matplotlib.pyplot as plt +fig = plt.figure() +ax = fig.add_subplot(111, projection='3d') +ax.view_init(40, 15) +fig.subplots_adjust(left=0, right=1, bottom=0, top=1) +ax.set_xlim(-20, 20) +ax.set_ylim(-20, 20) +ax.set_zlim(-40, 10) +plt.axis('off') +ax.scatter(xs, ys, zs, c='g',alpha=0.3) +plt.show() + +############################################################################## +# This dataset contains 126 points. Each point represents a distinct +# neutral-atom qubit. Simulating this many qubits would be outside the +# reach of Cirq's built-in simulators, so for this demo, +# we will pare down to just 9 points, evenly spaced around the tower. +# These are highlighted in red below. +# + +fig = plt.figure() +ax = fig.add_subplot(111, projection='3d') +ax.view_init(40, 15) +fig.subplots_adjust(left=0, right=1, bottom=0, top=1) +ax.set_xlim(-20, 20) +ax.set_ylim(-20, 20) +ax.set_zlim(-40, 10) +plt.axis('off') +ax.scatter(xs, ys, zs, c='g', alpha=0.3) + +base_mask = [3, 7, 11, 15] +qubit_mask = [48, 51, 60, 63, 96, 97, 98, 99, 125] +input_coords = coords[base_mask] # we'll need this for a plot later +qubit_coords = coords[qubit_mask] + +subset_xs = qubit_coords[:, 0] +subset_ys = qubit_coords[:, 1] +subset_zs = qubit_coords[:, 2] +ax.scatter(subset_xs, subset_ys, subset_zs, c='r', alpha=1.0) +plt.show() + +############################################################################## +# Converting to Cirq qubits +# ------------------------- +# +# Our next step will be to convert these datapoints into objects that +# Cirq understands as qubits. For neutral-atom devices in Cirq, we can use the +# ``ThreeDQubit`` class, which carries information about the three-dimensional +# arrangement of the qubits. +# +# Now, neutral-atom devices come with some physical restrictions. +# Specifically, in a particular three-dimensional configuration, qubits that +# are too distant from one another can't easily interact. Instead, there is +# a notion of a *control radius;* any atoms which are within the system's +# control radius can interact with one another. Qubits separated by a +# distance larger than the control radius cannot interact. +# +# In order to allow our Eiffel tower qubits to interact with +# one another more easily, we will artificially scale some dimensions +# when placing the atoms. + +from cirq_pasqal import ThreeDQubit +xy_scale = 1.5 +z_scale = 0.75 +qubits = [ThreeDQubit(xy_scale * x, xy_scale * y, z_scale * z) + for x, y, z in qubit_coords] + +############################################################################## +# To simulate a neutral-atom quantum computation, we can use the +# ``"cirq.pasqal"`` device, available via the +# `PennyLane-Cirq plugin `_. +# We will need to provide this device with the ``ThreeDQubit`` object that we created +# above. We also need to instantiate the device with a fixed control radius. + +import pennylane as qml + +num_wires = len(qubits) +control_radius = 32.4 +dev = qml.device("cirq.pasqal", control_radius=control_radius, + qubits=qubits, wires=num_wires) + +############################################################################## +# Creating a quantum circuit +# -------------------------- +# +# We will now make a variational circuit out of the Eiffel tower configuration +# from above. Each of the 9 qubits we are using can be thought of +# as a single wire in a quantum circuit. We will cause these qubits to interact by applying +# a sequence of two-qubit gates. Specifically, the circuit consists of several +# stages: +# +# i. Input classical data is converted into quantum information at the first +# (lowest) vertical level of qubits. In this example, our classical data +# will be simple bit strings, which we can embed by using single-qubit +# bit flips (a simple +# `data-embedding `_ +# strategy). +# +# ii. For each corner of the tower, CNOTs are enacted between the first- +# and second-level qubits. +# +# iii. All qubits from the second level interact with a single "peak" qubit +# using a parametrized controlled-rotation operation. The free parameters +# of our variational circuit enter here. +# +# The output of our circuit is determined via a Pauli-Z measurement on +# the final "peak" qubit. +# +# That's a few things to keep track of, so let's show the circuit via a +# three-dimensional image: + +first_lvl_coords = qubit_coords[:4] +second_lvl_coords = qubit_coords[4:8] +peak_coords = qubit_coords[8] + +input_x, input_y, input_z = [input_coords[:, idx] + for idx in range(3)] +second_x, second_y, second_z = [first_lvl_coords[:, idx] + for idx in range(3)] +third_x, third_y, third_z = [second_lvl_coords[:, idx] + for idx in range(3)] +peak_x, peak_y, peak_z = peak_coords + +fig = plt.figure() +ax = fig.add_subplot(111, projection='3d') +ax.view_init(40, 15) +fig.subplots_adjust(left=0, right=1, bottom=0, top=1) +ax.set_xlim(-20, 20) +ax.set_ylim(-20, 20) +ax.set_zlim(-40, 10) +plt.axis('off') + +ax.scatter(xs, ys, zs, c='g', alpha=0.3) +ax.scatter(subset_xs, subset_ys, subset_zs, c='r', alpha=1.0); + +# Two-qubit gates between second and third levels +for corner in range(4): + ax.plot(xs=[second_x[corner], third_x[corner]], + ys=[second_y[corner], third_y[corner]], + zs=[second_z[corner], third_z[corner]], + c='k'); + +# Two-qubit gates between third level and peak +for corner in range(4): + ax.plot(xs=[third_x[corner], peak_x], + ys=[third_y[corner], peak_y], + zs=[third_z[corner], peak_z], + c='k'); + +# Additional lines to guide the eye +for corner in range(4): + ax.plot(xs=[input_x[corner], second_x[corner]], + ys=[input_y[corner], second_y[corner]], + zs=[input_z[corner], second_z[corner]], + c='grey', linestyle='--'); + ax.plot(xs=[second_x[corner], second_x[(corner + 1) % 4]], + ys=[second_y[corner], second_y[(corner + 1) % 4]], + zs=[second_z[corner], second_z[(corner + 1) % 4]], + c='grey', linestyle='--'); + ax.plot(xs=[third_x[corner], third_x[(corner + 1) % 4]], + ys=[third_y[corner], third_y[(corner + 1) % 4]], + zs=[third_z[corner], third_z[(corner + 1) % 4]], + c='grey', linestyle='--'); + +plt.show() + +############################################################################## +# In this figure, the red dots represent the specific qubits we will use in +# our circuit (the green dots are not used in this demo). +# +# The solid black lines indicate two-qubit gates between these qubits. +# The dashed grey lines are meant to guide the eye, but could also be +# used to make a more complex model by adding further two-qubit gates. +# +# Classical data is loaded in at the bottom qubits (the "tower legs") and +# the final measurement result is read out from the top "peak" qubit. +# The order of gate execution proceeds vertically from bottom to top, and +# clockwise at each level. +# +# The code below creates this particular quantum circuit configuration in +# PennyLane: + +peak_qubit = 8 + +def controlled_rotation(phi, wires): + qml.RY(phi, wires=wires[1]) + qml.CNOT(wires=wires) + qml.RY(-phi, wires=wires[1]) + qml.CNOT(wires=wires) + +@qml.qnode(dev, interface="tf") +def circuit(weights, data): + + # Input classical data loaded into qubits at second level + for idx in range(4): + if data[idx]: + qml.PauliX(wires=idx) + + # Interact qubits from second and third levels + for idx in range(4): + qml.CNOT(wires=[idx, idx + 4]) + + # Interact qubits from third level with peak using parameterized gates + for idx, wire in enumerate(range(4, 8)): + controlled_rotation(weights[idx], wires=[wire, peak_qubit]) + + return qml.expval(qml.PauliZ(wires=peak_qubit)) + + +############################################################################## +# Training the circuit +# -------------------- +# +# Let's now leverage this variational circuit to tackle a toy classification +# problem. +# For the purposes of this demo, we will consider a very simple classifier: +# +# * if the first input qubit is in the state :math:`\mid 0 \rangle,` the model +# should make the prediction "0", and +# +# * if the first input qubit is in the state :math:`\mid 1 \rangle,` the model +# should predict "1" (independent of the states of all other qubits). +# +# In other words, the idealized trained model should learn an +# identity transformation between the first qubit and the final one, while +# ignoring the states of all other qubits. +# +# With this goal in mind, we can create a basic cost function. This cost +# function randomly samples possible 4-bit input bitstrings, and compares +# the circuit's output with the value of the first bit. The other bits +# can be thought of as noise that we don't want our model to learn. + + +import tensorflow as tf +np.random.seed(143) +init_weights = np.pi * np.random.rand(4) + +weights = tf.Variable(init_weights, dtype=tf.float64) + +data = np.random.randint(0, 2, size=4) + +def cost(): + data = np.random.randint(0, 2, size=4) + label = data[0] + output = (-circuit(weights, data) + 1) / 2 + return tf.abs(output - label) ** 2 + +opt = tf.keras.optimizers.Adam(learning_rate=0.1) + +for step in range(100): + opt.minimize(cost, [weights]) + if step % 5 == 0: + print("Step {}: cost={}".format(step, cost())) + +print("Final cost value: {}".format(cost())) + +############################################################################## +# Success! The circuit has learned to transfer the state of the first qubit +# to the state of the last qubit, while ignoring the state of all other input +# qubits. +# +# The programmable three-dimensional configurations of neutral-atom quantum +# computers provide a special tool that is hard to replicate in other +# platforms. Could the physical +# arrangement of qubits, in particular the third dimension, be leveraged to +# make quantum algorithms more sparse or efficient? Could neutral-atom +# systems—with their unique programmability of the geometry—allow us to +# rapidly prototype and experiment with new circuit topologies? What +# possibilities could this open up for quantum computing, quantum chemistry, +# or quantum machine learning? +# + +############################################################################## +# References +# ---------- +# +# .. [#barredo2017] +# +# Daniel Barredo, Vincent Lienhard, Sylvain de Leseleuc, Thierry Lahaye, and Antoine Browaeys. +# "Synthetic three-dimensional atomic structures assembled atom by atom." +# `arXiv:1712.02727 +# `__, 2017. +# +# +# About the author +# ---------------- # \ No newline at end of file diff --git a/demonstrations/tutorial_qchem_external.py b/demonstrations/tutorial_qchem_external.py index ae25f561bf..36bc3094f3 100644 --- a/demonstrations/tutorial_qchem_external.py +++ b/demonstrations/tutorial_qchem_external.py @@ -1,230 +1,230 @@ -r""" - -Using PennyLane with PySCF and OpenFermion -========================================== - -.. meta:: - :property="og:description": Learn how to integrate external quantum chemistry libraries with PennyLane. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_external_libs.png - - -.. related:: - tutorial_quantum_chemistry Quantum chemistry with PennyLane - tutorial_vqe A brief overview of VQE - tutorial_givens_rotations Givens rotations for quantum chemistry - tutorial_adaptive_circuits Adaptive circuits for quantum chemistry - -*Author: Soran Jahangiri — Posted: 3 January 2023.* - -The quantum chemistry module in PennyLane, :mod:`qchem `, provides built-in -methods to compute molecular integrals, solve Hartree-Fock equations, and construct -`fully-differentiable `_ molecular -Hamiltonians. PennyLane also lets you take advantage of various -external resources and libraries to build upon existing tools. In this demo we will show you how -to integrate PennyLane with `PySCF `_ and -`OpenFermion `_ to compute molecular integrals, -construct molecular Hamiltonians, and import initial states. - -Building molecular Hamiltonians -------------------------------- -In PennyLane, Hamiltonians for quantum chemistry are built with the -:func:`~.pennylane.qchem.molecular_hamiltonian` function by specifying a backend for solving the -Hartree–Fock equations. The default backend is the differentiable Hartree–Fock solver of the -:mod:`qchem ` module. A molecular Hamiltonian can also be constructed with -non-differentiable backends that use the electronic structure package -`PySCF `_ or the -`OpenFermion-PySCF `_ plugin. These -backends can be selected by setting the keyword argument ``method='pyscf'`` or -``method='openfermion'`` in :func:`~.pennylane.qchem.molecular_hamiltonian`. This requires -``PySCF`` or ``OpenFermion-PySCF`` to be installed by the user depending on the desired backend: - -.. code-block:: bash - - pip install pyscf # for method='pyscf` - pip install openfermionpyscf # for method='openfermion` - -For example, the molecular Hamiltonian for a water molecule can be constructed with the ``pyscf`` -backend as: -""" - -import pennylane as qml -import numpy as np - -symbols = ["H", "O", "H"] -geometry = np.array([[-0.0399, -0.0038, 0.0000], - [ 1.5780, 0.8540, 0.0000], - [ 2.7909, -0.5159, 0.0000]]) -molecule = qml.qchem.Molecule(symbols, geometry) - -H, qubits = qml.qchem.molecular_hamiltonian(molecule, method="pyscf") -print(H) - -############################################################################## -# This generates a PennyLane :class:`~.pennylane.Hamiltonian` that can be used in a VQE workflow or -# converted to a -# `sparse matrix `_ -# in the computational basis. -# -# Additionally, if you have built your electronic Hamiltonian independently using -# `OpenFermion `_ tools, it can -# be readily converted to a PennyLane observable using the -# :func:`~.pennylane.import_operator` function. Here is an example: - -from openfermion.ops import QubitOperator - -H = 0.1 * QubitOperator('X0 X1') + 0.2 * QubitOperator('Z0') -H = qml.qchem.import_operator(H) - -print(f'Type: \n {type(H)} \n') -print(f'Hamiltonian: \n {H}') - -############################################################################## -# Computing molecular integrals -# ----------------------------- -# In order to build a -# `molecular Hamiltonian `_, we need -# one- and two-electron integrals in the molecular orbital basis. These integrals are used to -# construct a fermionic Hamiltonian which is then mapped onto the qubit basis. These molecular -# integrals can be computed with the -# :func:`~.pennylane.qchem.electron_integrals` function of PennyLane. Alternatively, the integrals -# can be computed with the `PySCF `_ package and used in PennyLane -# workflows such as building a -# `fermionic Hamiltonian `_ or -# quantum `resource estimation `_. -# Let's use water as an example. -# -# First, we define the PySCF molecule object and run a restricted Hartree-Fock -# calculation: - -from pyscf import gto, ao2mo, scf - -mol_pyscf = gto.M(atom = '''H -0.02111417 -0.00201087 0.; - O 0.83504162 0.45191733 0.; - H 1.47688065 -0.27300252 0.''') -rhf = scf.RHF(mol_pyscf) -energy = rhf.kernel() - -############################################################################## -# We obtain the molecular integrals ``one_ao`` and ``two_ao`` in the basis of atomic orbitals -# by following the example `here `_: - -one_ao = mol_pyscf.intor_symmetric('int1e_kin') + mol_pyscf.intor_symmetric('int1e_nuc') -two_ao = mol_pyscf.intor('int2e_sph') - -############################################################################## -# These integrals are then mapped to the basis of molecular orbitals: - -one_mo = np.einsum('pi,pq,qj->ij', rhf.mo_coeff, one_ao, rhf.mo_coeff) -two_mo = ao2mo.incore.full(two_ao, rhf.mo_coeff) - -############################################################################## -# Note that the two-electron integral tensor is represented in -# `chemists' notation `_. To use it -# in PennyLane, we need to convert it into the so-called -# *physicists' notation*: - -two_mo = np.swapaxes(two_mo, 1, 3) - -############################################################################## -# Let's now look at an example where these molecular integrals are used to build the fermionic -# Hamiltonian of water. To do that we also need to compute the nuclear energy contribution: - -core_constant = np.array([rhf.energy_nuc()]) - -############################################################################## -# We now use the integrals to construct a fermionic Hamiltonian with PennyLane's powerful tools -# for creating and manipulating -# `fermionic operators `_: - -H_fermionic = qml.qchem.fermionic_observable(core_constant, one_mo, two_mo) - -############################################################################## -# The Hamiltonian can be mapped to the qubit basis with the :func:`~.pennylane.jordan_wigner` -# function: - -H = qml.jordan_wigner(H_fermionic) - -############################################################################## -# Importing initial states -# ------------------------ -# Simulating molecules with quantum algorithms requires defining an initial state that should have -# non-zero overlap with the molecular ground state. A trivial choice for the initial state is the -# Hartree-Fock state which is obtained by putting the electrons in the lowest-energy molecular -# orbitals. For molecules with a complicated electronic structure, the Hartree-Fock state has -# only a small overlap with the ground state, which makes executing quantum algorithms -# inefficient. -# -# Initial states obtained from affordable post-Hartree-Fock calculations can be used to make the -# quantum workflow more performant. For instance, configuration interaction (CI) and coupled cluster -# (CC) calculations with single and double (SD) excitations can be performed using PySCF and the -# resulting wave function can be used as the initial state in the quantum algorithm. PennyLane -# provides the :func:`~.pennylane.qchem.import_state` function that takes a PySCF solver object, -# extracts the wave function and returns a state vector in the computational basis that can be used -# in a quantum circuit. Let’s look at an example. -# -# First, we run CCSD calculations for the hydrogen molecule to obtain the solver object. - -from pyscf import gto, scf, cc - -mol = gto.M(atom=[['H', (0, 0, 0)], ['H', (0, 0, 0.7)]]) -myhf = scf.RHF(mol).run() -mycc = cc.CCSD(myhf).run() - -############################################################################## -# Then, we use the :func:`~.pennylane.qchem.import_state` function to obtain the -# state vector. - -state = qml.qchem.import_state(mycc) -print(state) - -############################################################################## -# You can verify that this state is a superposition of the Hartree-Fock state and a doubly-excited -# state. -# -# Converting fermionic operators -# ------------------------------ -# Fermionic operators are commonly used to construct observables for molecules and spin systems. -# You can easily convert between fermionic operators created with PennyLane and OpenFermion by using -# the :func:`~.pennylane.from_openfermion` and :func:`~.pennylane.to_openfermion` functions. Let's -# look at some examples. First, we create a fermionic operator with OpenFermion and convert it to a -# PennyLane fermionic operator. - -from openfermion import FermionOperator -openfermion_op = 0.5 * FermionOperator('0^ 2') + FermionOperator('0 2^') -pennylane_op = qml.from_openfermion(openfermion_op) -print(pennylane_op) - -############################################################################## -# The resulting operator can be used in PennyLane like any other fermionic object. We now take this -# PennyLane fermionic operator and convert it back to an OpenFermion operator. - -openfermion_op = qml.to_openfermion(pennylane_op) -print(openfermion_op) - -############################################################################## -# The :func:`~.pennylane.from_openfermion` and :func:`~.pennylane.to_openfermion` functions support -# converting several operator types. You can look at the function documentations for more details -# and examples. - -############################################################################## -# Conclusions -# ----------- -# This tutorial demonstrates how to use PennyLane with external quantum chemistry libraries such as -# `PySCF `_ and -# `OpenFermion `_. -# -# To summarize: -# -# 1. We can construct molecular Hamiltonians in PennyLane by using a user-installed version of PySCF -# by passing the argument ``method=pyscf`` to the :func:`~.pennylane.qchem.molecular_hamiltonian` -# function. -# 2. We can directly use one- and two-electron integrals from PySCF, but we need to convert the -# tensor containing the two-electron integrals from chemists' notation to physicists' notation. -# 3. We can easily convert between OpenFermion operators and PennyLane operators using the -# :func:`~.pennylane.from_openfermion` and :func:`~.pennylane.to_openfermion` functions. -# 4. Finally, we can convert PySCF wave functions to PennyLane state vectors using the -# :func:`~.pennylane.qchem.import_state` function. -# -# About the author -# ---------------- -# +r""" + +Using PennyLane with PySCF and OpenFermion +========================================== + +.. meta:: + :property="og:description": Learn how to integrate external quantum chemistry libraries with PennyLane. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_external_libs.png + + +.. related:: + tutorial_quantum_chemistry Quantum chemistry with PennyLane + tutorial_vqe A brief overview of VQE + tutorial_givens_rotations Givens rotations for quantum chemistry + tutorial_adaptive_circuits Adaptive circuits for quantum chemistry + +*Author: Soran Jahangiri — Posted: 3 January 2023.* + +The quantum chemistry module in PennyLane, :mod:`qchem `, provides built-in +methods to compute molecular integrals, solve Hartree-Fock equations, and construct +`fully-differentiable `_ molecular +Hamiltonians. PennyLane also lets you take advantage of various +external resources and libraries to build upon existing tools. In this demo we will show you how +to integrate PennyLane with `PySCF `_ and +`OpenFermion `_ to compute molecular integrals, +construct molecular Hamiltonians, and import initial states. + +Building molecular Hamiltonians +------------------------------- +In PennyLane, Hamiltonians for quantum chemistry are built with the +:func:`~.pennylane.qchem.molecular_hamiltonian` function by specifying a backend for solving the +Hartree–Fock equations. The default backend is the differentiable Hartree–Fock solver of the +:mod:`qchem ` module. A molecular Hamiltonian can also be constructed with +non-differentiable backends that use the electronic structure package +`PySCF `_ or the +`OpenFermion-PySCF `_ plugin. These +backends can be selected by setting the keyword argument ``method='pyscf'`` or +``method='openfermion'`` in :func:`~.pennylane.qchem.molecular_hamiltonian`. This requires +``PySCF`` or ``OpenFermion-PySCF`` to be installed by the user depending on the desired backend: + +.. code-block:: bash + + pip install pyscf # for method='pyscf` + pip install openfermionpyscf # for method='openfermion` + +For example, the molecular Hamiltonian for a water molecule can be constructed with the ``pyscf`` +backend as: +""" + +import pennylane as qml +import numpy as np + +symbols = ["H", "O", "H"] +geometry = np.array([[-0.0399, -0.0038, 0.0000], + [ 1.5780, 0.8540, 0.0000], + [ 2.7909, -0.5159, 0.0000]]) +molecule = qml.qchem.Molecule(symbols, geometry) + +H, qubits = qml.qchem.molecular_hamiltonian(molecule, method="pyscf") +print(H) + +############################################################################## +# This generates a PennyLane :class:`~.pennylane.Hamiltonian` that can be used in a VQE workflow or +# converted to a +# `sparse matrix `_ +# in the computational basis. +# +# Additionally, if you have built your electronic Hamiltonian independently using +# `OpenFermion `_ tools, it can +# be readily converted to a PennyLane observable using the +# :func:`~.pennylane.import_operator` function. Here is an example: + +from openfermion.ops import QubitOperator + +H = 0.1 * QubitOperator('X0 X1') + 0.2 * QubitOperator('Z0') +H = qml.qchem.import_operator(H) + +print(f'Type: \n {type(H)} \n') +print(f'Hamiltonian: \n {H}') + +############################################################################## +# Computing molecular integrals +# ----------------------------- +# In order to build a +# `molecular Hamiltonian `_, we need +# one- and two-electron integrals in the molecular orbital basis. These integrals are used to +# construct a fermionic Hamiltonian which is then mapped onto the qubit basis. These molecular +# integrals can be computed with the +# :func:`~.pennylane.qchem.electron_integrals` function of PennyLane. Alternatively, the integrals +# can be computed with the `PySCF `_ package and used in PennyLane +# workflows such as building a +# `fermionic Hamiltonian `_ or +# quantum `resource estimation `_. +# Let's use water as an example. +# +# First, we define the PySCF molecule object and run a restricted Hartree-Fock +# calculation: + +from pyscf import gto, ao2mo, scf + +mol_pyscf = gto.M(atom = '''H -0.02111417 -0.00201087 0.; + O 0.83504162 0.45191733 0.; + H 1.47688065 -0.27300252 0.''') +rhf = scf.RHF(mol_pyscf) +energy = rhf.kernel() + +############################################################################## +# We obtain the molecular integrals ``one_ao`` and ``two_ao`` in the basis of atomic orbitals +# by following the example `here `_: + +one_ao = mol_pyscf.intor_symmetric('int1e_kin') + mol_pyscf.intor_symmetric('int1e_nuc') +two_ao = mol_pyscf.intor('int2e_sph') + +############################################################################## +# These integrals are then mapped to the basis of molecular orbitals: + +one_mo = np.einsum('pi,pq,qj->ij', rhf.mo_coeff, one_ao, rhf.mo_coeff) +two_mo = ao2mo.incore.full(two_ao, rhf.mo_coeff) + +############################################################################## +# Note that the two-electron integral tensor is represented in +# `chemists' notation `_. To use it +# in PennyLane, we need to convert it into the so-called +# *physicists' notation*: + +two_mo = np.swapaxes(two_mo, 1, 3) + +############################################################################## +# Let's now look at an example where these molecular integrals are used to build the fermionic +# Hamiltonian of water. To do that we also need to compute the nuclear energy contribution: + +core_constant = np.array([rhf.energy_nuc()]) + +############################################################################## +# We now use the integrals to construct a fermionic Hamiltonian with PennyLane's powerful tools +# for creating and manipulating +# `fermionic operators `_: + +H_fermionic = qml.qchem.fermionic_observable(core_constant, one_mo, two_mo) + +############################################################################## +# The Hamiltonian can be mapped to the qubit basis with the :func:`~.pennylane.jordan_wigner` +# function: + +H = qml.jordan_wigner(H_fermionic) + +############################################################################## +# Importing initial states +# ------------------------ +# Simulating molecules with quantum algorithms requires defining an initial state that should have +# non-zero overlap with the molecular ground state. A trivial choice for the initial state is the +# Hartree-Fock state which is obtained by putting the electrons in the lowest-energy molecular +# orbitals. For molecules with a complicated electronic structure, the Hartree-Fock state has +# only a small overlap with the ground state, which makes executing quantum algorithms +# inefficient. +# +# Initial states obtained from affordable post-Hartree-Fock calculations can be used to make the +# quantum workflow more performant. For instance, configuration interaction (CI) and coupled cluster +# (CC) calculations with single and double (SD) excitations can be performed using PySCF and the +# resulting wave function can be used as the initial state in the quantum algorithm. PennyLane +# provides the :func:`~.pennylane.qchem.import_state` function that takes a PySCF solver object, +# extracts the wave function and returns a state vector in the computational basis that can be used +# in a quantum circuit. Let’s look at an example. +# +# First, we run CCSD calculations for the hydrogen molecule to obtain the solver object. + +from pyscf import gto, scf, cc + +mol = gto.M(atom=[['H', (0, 0, 0)], ['H', (0, 0, 0.7)]]) +myhf = scf.RHF(mol).run() +mycc = cc.CCSD(myhf).run() + +############################################################################## +# Then, we use the :func:`~.pennylane.qchem.import_state` function to obtain the +# state vector. + +state = qml.qchem.import_state(mycc) +print(state) + +############################################################################## +# You can verify that this state is a superposition of the Hartree-Fock state and a doubly-excited +# state. +# +# Converting fermionic operators +# ------------------------------ +# Fermionic operators are commonly used to construct observables for molecules and spin systems. +# You can easily convert between fermionic operators created with PennyLane and OpenFermion by using +# the :func:`~.pennylane.from_openfermion` and :func:`~.pennylane.to_openfermion` functions. Let's +# look at some examples. First, we create a fermionic operator with OpenFermion and convert it to a +# PennyLane fermionic operator. + +from openfermion import FermionOperator +openfermion_op = 0.5 * FermionOperator('0^ 2') + FermionOperator('0 2^') +pennylane_op = qml.from_openfermion(openfermion_op) +print(pennylane_op) + +############################################################################## +# The resulting operator can be used in PennyLane like any other fermionic object. We now take this +# PennyLane fermionic operator and convert it back to an OpenFermion operator. + +openfermion_op = qml.to_openfermion(pennylane_op) +print(openfermion_op) + +############################################################################## +# The :func:`~.pennylane.from_openfermion` and :func:`~.pennylane.to_openfermion` functions support +# converting several operator types. You can look at the function documentations for more details +# and examples. + +############################################################################## +# Conclusions +# ----------- +# This tutorial demonstrates how to use PennyLane with external quantum chemistry libraries such as +# `PySCF `_ and +# `OpenFermion `_. +# +# To summarize: +# +# 1. We can construct molecular Hamiltonians in PennyLane by using a user-installed version of PySCF +# by passing the argument ``method=pyscf`` to the :func:`~.pennylane.qchem.molecular_hamiltonian` +# function. +# 2. We can directly use one- and two-electron integrals from PySCF, but we need to convert the +# tensor containing the two-electron integrals from chemists' notation to physicists' notation. +# 3. We can easily convert between OpenFermion operators and PennyLane operators using the +# :func:`~.pennylane.from_openfermion` and :func:`~.pennylane.to_openfermion` functions. +# 4. Finally, we can convert PySCF wave functions to PennyLane state vectors using the +# :func:`~.pennylane.qchem.import_state` function. +# +# About the author +# ---------------- +# diff --git a/demonstrations/tutorial_qft_arithmetics.py b/demonstrations/tutorial_qft_arithmetics.py index 3d67048454..92929830f5 100644 --- a/demonstrations/tutorial_qft_arithmetics.py +++ b/demonstrations/tutorial_qft_arithmetics.py @@ -1,433 +1,433 @@ -r""".. _qft_arithmetics: - -Basic arithmetic with the quantum Fourier transform (QFT) -======================================= - -.. meta:: - :property="og:description": Learn how to use the quantum Fourier transform (QFT) to do basic arithmetic - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qft_arithmetics_thumbnail.png - -.. related:: - tutorial_qubit_rotation Basis tutorial: qubit rotation - - - -*Author: Guillermo Alonso-Linaje — Posted: 07 November 2022.* - -Arithmetic is a fundamental branch of mathematics that consists of the study of the main operations with numbers such as -addition, multiplication, subtraction and division. Using arithmetic operations we can understand the world around us -and solve many of our daily tasks. - -Arithmetic is crucial for the implementation of any kind of algorithm in classical computer science, but also in quantum computing. For this reason, in this demo we are going to show -an approach to defining arithmetic operations on quantum computers. The simplest and most direct way to achieve this goal is to use the -quantum Fourier transform (QFT), which we will demonstrate on a basic level. - -In this demo we will not focus on understanding how the QFT is built, -as we can find a great explanation in the -`PennyLane Codebook `__. Instead, we will develop the -intuition for how it works and how we can best take advantage of it. - -Motivation ----------- - -The first question we have to ask ourselves is whether it makes sense to perform these basic operations on a quantum computer. Is the -goal purely academic or is it really something that is needed in certain algorithms? Why implement in a quantum computer -something that we can do with a calculator? - -When it comes to basic quantum computing algorithms like the Deustch–Jozsa or -Grover's algorithm, we might think that we have never needed to apply any arithmetic operations such as addition or multiplication. -However, the reality is different. When we learn about these algorithms from an academic point of view, -we work with a ready-made operator that we never have to worry about, the *oracle*. -Unfortunately, in real-world situations, we will have to build this seemingly magical operator by hand. -As an example, let's imagine that we want to use Grover's algorithm to search for `magic squares `__. -To define the oracle, which determines whether a solution is valid or not, we must perform sums over the rows and -columns to check that they all have the same value. Therefore, to create this oracle, -we will need to define a sum operator within the quantum computer. - -The second question we face is why we should work with the QFT at all. There are other procedures that could be used to perform these basic operations; for example, by -imitating the classical algorithm. But, as we can see in [#Draper2000]_, it has already been proven that the QFT needs fewer qubits to perform these operations, which is -nowadays of vital importance. - -We will organize the demo as follows. Initially, we will talk about the Fourier basis to give an intuitive idea of how -it works, after which we will address different basic arithmetic operations. Finally, we will move on to a practical example -in which we will factor numbers using Grover's algorithm. - - -QFT representation ------------------ - -To apply the QFT to basic arithmetic operations, our objective now is to learn how to add, -subtract and multiply numbers using quantum devices. As we are working with qubits, -—which, like bits, can take the -values :math:`0` or :math:`1`—we will represent the numbers in binary. For the -purposes of this tutorial, we will assume that we are working only with -integers. Therefore, if we have :math:`n` qubits, we will be able to -represent the numbers from :math:`0` to :math:`2^n-1.` - -The first thing we need to know is PennyLane's -standard for encoding numbers in a binary format. A binary number can be -represented as a string of 1s and 0s, which we can represent as the multi-qubit state - -.. math:: \vert m \rangle = \vert \overline{q_0q_1...q_{n-1}}\rangle, - -where the formula to obtain the equivalent decimal number :math:`m` will be: - -.. math:: m= \sum_{i = 0}^{n-1}2^{n-1-i}q_i. - -Note that :math:`\vert m \rangle` refers to the basic state -generated by the binary encoding of the number :math:`m.` -For instance, the natural number :math:`6` -is represented by the quantum state :math:`\vert 110\rangle,` since :math:`\vert 110 \rangle = 1 \cdot 2^2 + 1\cdot 2^1+0\cdot 2^0 = 6.` - -Let’s see how we would represent all the integers from :math:`0` to :math:`7` using the product state of three qubits, visualized by separate Bloch spheres for each qubit. - -.. figure:: /_static/demonstration_assets/qft_arithmetics/3_qubits_computational_basis.gif - :width: 90% - :align: center - - Representation of integers using a computational basis of three qubits. - -.. note:: - - The `Bloch sphere `_ - is a way of graphically representing the state of a qubit. - At the top of the sphere we place the state :math:`\vert 0 \rangle,` at the bottom - :math:`\vert 1 \rangle,` and in the rest of the - sphere we will place the possible states in superposition. It is a very useful - representation that helps better visualize and interpret quantum gates such as rotations. - -We can use -the :class:`qml.BasisEmbedding ` -template to obtain the binary representation in a simple way. -Let's see how we would code the number :math:`6.` -""" - -import pennylane as qml -import matplotlib.pyplot as plt - -dev = qml.device("default.qubit", wires=3) - -@qml.compile -@qml.qnode(dev) -def basis_embedding_circuit(m): - qml.BasisEmbedding(m, wires=range(3)) - return qml.state() - -m = 6 # number to be encoded - -qml.draw_mpl(basis_embedding_circuit, show_all_wires=True)(m) -plt.show() - -###################################################################### -# -# As we can see, the first qubit—the :math:`0`-th wire—is placed on top and the rest of the qubits are -# below it. However, this is not the only way we could represent numbers. -# We can also represent them in different bases, such as the so-called *Fourier base*. -# -# In this case, all the states of the basis will be represented via qubits in -# the XY-plane of the Bloch sphere, each rotated by a certain -# amount. -# -# -# How do we know how much we must rotate each qubit to represent a certain number? -# It is actually very easy! Suppose we are working with -# :math:`n` qubits and we want to represent the number :math:`m` in the -# Fourier basis. Then the :math:`j`-th qubit will have the phase: -# -# .. math:: \alpha_j = \frac{2m\pi}{2^{j}}. -# -# Now we can represent numbers in the Fourier basis using three qubits: -# -# .. figure:: /_static/demonstration_assets/qft_arithmetics/3_qubits_fourier_basis.gif -# :width: 90% -# :align: center -# -# Representation of integers using the Fourier basis with three qubits -# -# As we can see, the third qubit will rotate -# :math:`\frac{1}{8}` of a turn counterclockwise with each number. The next qubit -# rotates :math:`\frac{1}{4}` of a full turn and, finally, the first qubit rotates -# half a turn for each increase in number. -# -# Adding a number to a register -# ------------------------------ -# -# The fact that the states encoding the numbers are now in phase gives us great -# flexibility in carrying out our arithmetic operations. To see this in practice, -# let’s look at the situation in which want to create an operator Sum -# such that: -# -# .. math:: \text{Sum(k)}\vert m \rangle = \vert m + k \rangle. -# -# The procedure to implement this unitary operation is the following: -# -# #. We convert the state from the computational basis into the Fourier basis by applying the QFT to the :math:`\vert m \rangle` state via the :class:`~pennylane.QFT` operator. -# -# #. We rotate the :math:`j`-th qubit by the angle :math:`\frac{2k\pi}{2^{j}}` using the :math:`R_Z` gate, which leads to the new phases, :math:`\frac{2(m + k)\pi}{2^{j}}.` -# -# #. We apply the QFT inverse to return to the computational basis and obtain :math:`m+k.` -# -# -# Let's see how this process would look in PennyLane. -# - -import pennylane as qml -import numpy as np - -n_wires = 4 -dev = qml.device("default.qubit", wires=n_wires, shots=1) - -def add_k_fourier(k, wires): - for j in range(len(wires)): - qml.RZ(k * np.pi / (2**j), wires=wires[j]) - -@qml.qnode(dev) -def sum(m, k): - qml.BasisEmbedding(m, wires=range(n_wires)) # m encoding - - qml.QFT(wires=range(n_wires)) # step 1 - - add_k_fourier(k, range(n_wires)) # step 2 - - qml.adjoint(qml.QFT)(wires=range(n_wires)) # step 3 - - return qml.sample() - - -print(f"The ket representation of the sum of 3 and 4 is {sum(3,4)}") - -###################################################################### -# Perfect, we have obtained :math:`\vert 0111 \rangle,` which is equivalent to the number :math:`7` in binary! -# -# Note that this is a deterministic algorithm, which means that we have obtained the desired solution by executing a single shot. -# On the other hand, if the result of an operation is greater than the maximum -# value :math:`2^n-1,` we will start again from zero, that is to say, we -# will calculate the sum modulo :math:`2^n-1.` For instance, in our three-qubit example, suppose that -# we want to calculate :math:`6+3.` We see that we do not have -# enough memory space, as :math:`6+3 = 9 > 2^3-1.` The result we will get will -# be :math:`9 \pmod 8 = 1,` or :math:`\vert 001 \rangle` in binary. Make sure to use -# enough qubits to represent your solutions! -# Finally, it is important to point out that it is not necessary to know how the -# QFT is constructed in order to use it. By knowing the properties of the -# new basis, we can use it in a simple way. -# -# Adding two different registers -# ------------------------------ -# -# In this previous algorithm, we had to pass to the operator the value of :math:`k` that we wanted to add to the starting state. -# But at other times, instead of passing that value to the operator, we would like it to pull the information from another register. -# That is, we are looking for a new operator :math:`\text{Sum}_2` such that -# -# .. math:: \text{Sum}_2\vert m \rangle \vert k \rangle \vert 0 \rangle = \vert m \rangle \vert k \rangle \vert m+k \rangle. -# -# In this case, we can understand the third register (which is initially -# at :math:`0`) as a counter that will tally as many units as :math:`m` and -# :math:`k` combined. The binary decomposition will -# make this simple. If we have :math:`\vert m \rangle = \vert \overline{q_0q_1q_2} \rangle,` we will -# have to add :math:`1` to the counter if :math:`q_2 = 1` and nothing -# otherwise. In general, we should add :math:`2^{n-i-1}` units if the :math:`i`-th -# qubit is in state :math:`\vert 1 \rangle` and 0 otherwise. As we can see, this is the same idea that is also -# behind the concept of a controlled gate. Indeed, observe that we will, indeed, apply a corresponding -# phase if indeed the control qubit is in the state :math:`\vert 1\rangle.` -# Let us now code the :math:`\text{Sum}_2` operator. - -wires_m = [0, 1, 2] # qubits needed to encode m -wires_k = [3, 4, 5] # qubits needed to encode k -wires_solution = [6, 7, 8, 9] # qubits needed to encode the solution - -dev = qml.device("default.qubit", wires=wires_m + wires_k + wires_solution, shots=1) - -n_wires = len(dev.wires) # total number of qubits used - -def addition(wires_m, wires_k, wires_solution): - # prepare solution qubits to counting - qml.QFT(wires=wires_solution) - - # add m to the counter - for i in range(len(wires_m)): - qml.ctrl(add_k_fourier, control=wires_m[i])(2 **(len(wires_m) - i - 1), wires_solution) - - # add k to the counter - for i in range(len(wires_k)): - qml.ctrl(add_k_fourier, control=wires_k[i])(2 **(len(wires_k) - i - 1), wires_solution) - - # return to computational basis - qml.adjoint(qml.QFT)(wires=wires_solution) - -@qml.qnode(dev) -def sum2(m, k, wires_m, wires_k, wires_solution): - # m and k codification - qml.BasisEmbedding(m, wires=wires_m) - qml.BasisEmbedding(k, wires=wires_k) - - # apply the addition circuit - addition(wires_m, wires_k, wires_solution) - - return qml.sample(wires=wires_solution) - -print(f"The ket representation of the sum of 7 and 3 is " - f"{sum2(7, 3, wires_m, wires_k, wires_solution)}") - -qml.draw_mpl(sum2, show_all_wires=True)(7, 3, wires_m, wires_k, wires_solution) -plt.show() - -###################################################################### -# Great! We have just seen how to add a number to a counter. In the example above, -# we added :math:`3 + 7` to get :math:`10,` which in binary -# is :math:`\vert 1010 \rangle.` -# -# Multiplying qubits -# ------------------- -# -# Following the same idea, we will see how easily we can -# implement multiplication. For this purpose we'll take two arbitrary numbers :math:`m` and :math:`k` -# to carry out the operation. This time, we look for an operator Mul such that -# -# .. math:: \text{Mul}\vert m \rangle \vert k \rangle \vert 0 \rangle = \vert m \rangle \vert k \rangle \vert m\cdot k \rangle. -# -# To understand the multiplication process, let's work with the binary decomposition of -# :math:`k:=\sum_{i=0}^{n-1}2^{n-i-1}k_i` and -# :math:`m:=\sum_{j=0}^{l-1}2^{l-j-1}m_i.` In this case, the product would -# be: -# -# .. math:: k \cdot m = \sum_{i=0}^{n-1}\sum_{j = 0}^{l-1}m_ik_i (2^{n-i-1} \cdot 2^{l-j-1}). -# -# In other words, if :math:`k_i = 1` and :math:`m_i = 1,` we would add -# :math:`2^{n-i-1} \cdot 2^{l-j-1}` units to the counter, where :math:`n` and :math:`l` -# are the number of qubits with which we encode :math:`m` and :math:`k` respectively. -# Let's code to see how it works! - -wires_m = [0, 1, 2] # qubits needed to encode m -wires_k = [3, 4, 5] # qubits needed to encode k -wires_solution = [6, 7, 8, 9, 10] # qubits needed to encode the solution - -dev = qml.device("default.qubit", wires=wires_m + wires_k + wires_solution, shots=1) - -n_wires = len(dev.wires) - -def multiplication(wires_m, wires_k, wires_solution): - # prepare sol-qubits to counting - qml.QFT(wires=wires_solution) - - # add m to the counter - for i in range(len(wires_k)): - for j in range(len(wires_m)): - coeff = 2 ** (len(wires_m) + len(wires_k) - i - j - 2) - qml.ctrl(add_k_fourier, control=[wires_k[i], wires_m[j]])(coeff, wires_solution) - - # return to computational basis - qml.adjoint(qml.QFT)(wires=wires_solution) - -@qml.qnode(dev) -def mul(m, k): - # m and k codification - qml.BasisEmbedding(m, wires=wires_m) - qml.BasisEmbedding(k, wires=wires_k) - - # Apply multiplication - multiplication(wires_m, wires_k, wires_solution) - - return qml.sample(wires=wires_solution) - - -print(f"The ket representation of the multiplication of 3 and 7 is {mul(3,7)}") - -qml.draw_mpl(mul, show_all_wires=True)(3, 7) -plt.show() - - -###################################################################### -# Awesome! We have multiplied :math:`7 \cdot 3` and, as a result, we have -# :math:`\vert 10101 \rangle,` which is :math:`21` in binary. -# -# -# Factorization with Grover -# ------------------------- -# -# With this, we have already gained a large repertoire of interesting -# operations that we can do, but we can give the idea one more twist and -# apply what we have learned in an example. -# -# Let’s imagine now that we want just the opposite: to factor the -# number :math:`21` as a product of two terms. Is this something we could do -# using our previous reasoning? The answer is yes! We can make use of -# `Grover's algorithm `_ to -# amplify the states whose product is the number we -# are looking for. All we would need is to construct the oracle :math:`U,` i.e., an -# operator such that -# -# .. math:: U\vert m \rangle \vert k \rangle = \vert m \rangle \vert k \rangle \text{ if }m\cdot k \not = 21, -# -# .. math:: U\vert m \rangle \vert k \rangle = -\vert m \rangle \vert k \rangle \text{ if }m\cdot k = 21 -# -# The idea of the oracle is as simple as this: -# -# #. use auxiliary registers to store the product, -# #. check if the product state is :math:`\vert 10101 \rangle` and, in that case, change the sign, -# #. execute the inverse of the circuit to clear the auxiliary qubits. -# #. calculate the probabilities and see which states have been amplified. -# -# Let's go back to PennyLane to implement this idea. - -n = 21 # number we want to factor - -wires_m = [0, 1, 2] # qubits needed to encode m -wires_k = [3, 4, 5] # qubits needed to encode k -wires_solution = [6, 7, 8, 9, 10] # qubits needed to encode the solution - -dev = qml.device("default.qubit", wires=wires_m + wires_k + wires_solution) - -n_wires = len(dev.wires) - -@qml.qnode(dev) -def factorization(n, wires_m, wires_k, wires_solution): - # Superposition of the input - for wire in wires_m: - qml.Hadamard(wires=wire) - - for wire in wires_k: - qml.Hadamard(wires=wire) - - # Apply the multiplication - multiplication(wires_m, wires_k, wires_solution) - - # Change sign of n - qml.FlipSign(n, wires=wires_solution) - - # Uncompute multiplication - qml.adjoint(multiplication)(wires_m, wires_k, wires_solution) - - # Apply Grover operator - qml.GroverOperator(wires=wires_m + wires_k) - - return qml.probs(wires=wires_m) - - -plt.bar(range(2 ** len(wires_m)), factorization(n, wires_m, wires_k, wires_solution)) -plt.xlabel("Basic states") -plt.ylabel("Probability") -plt.show() - -###################################################################### -# By plotting the probabilities of obtaining each basic state we see that -# prime factors have been amplified! Factorization via Grover’s algorithm -# does not achieve exponential improvement that -# `Shor's algorithm `_ does, but we -# can see that this construction is simple and a great example to -# illustrate basic arithmetic! -# -# I hope we can now all see that oracles are not something magical and that there -# is a lot of work behind their construction! This will help us in the future to build -# more complicated operators, but until then, let’s keep on learning. 🚀 -# -# References -# ---------- -# -# .. [#Draper2000] -# -# Thomas G. Draper, "Addition on a Quantum Computer". `arXiv:quant-ph/0008033 `__. -# -# -# About the author -# ---------------- -# +r""".. _qft_arithmetics: + +Basic arithmetic with the quantum Fourier transform (QFT) +======================================= + +.. meta:: + :property="og:description": Learn how to use the quantum Fourier transform (QFT) to do basic arithmetic + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qft_arithmetics_thumbnail.png + +.. related:: + tutorial_qubit_rotation Basis tutorial: qubit rotation + + + +*Author: Guillermo Alonso-Linaje — Posted: 07 November 2022.* + +Arithmetic is a fundamental branch of mathematics that consists of the study of the main operations with numbers such as +addition, multiplication, subtraction and division. Using arithmetic operations we can understand the world around us +and solve many of our daily tasks. + +Arithmetic is crucial for the implementation of any kind of algorithm in classical computer science, but also in quantum computing. For this reason, in this demo we are going to show +an approach to defining arithmetic operations on quantum computers. The simplest and most direct way to achieve this goal is to use the +quantum Fourier transform (QFT), which we will demonstrate on a basic level. + +In this demo we will not focus on understanding how the QFT is built, +as we can find a great explanation in the +`PennyLane Codebook `__. Instead, we will develop the +intuition for how it works and how we can best take advantage of it. + +Motivation +---------- + +The first question we have to ask ourselves is whether it makes sense to perform these basic operations on a quantum computer. Is the +goal purely academic or is it really something that is needed in certain algorithms? Why implement in a quantum computer +something that we can do with a calculator? + +When it comes to basic quantum computing algorithms like the Deustch–Jozsa or +Grover's algorithm, we might think that we have never needed to apply any arithmetic operations such as addition or multiplication. +However, the reality is different. When we learn about these algorithms from an academic point of view, +we work with a ready-made operator that we never have to worry about, the *oracle*. +Unfortunately, in real-world situations, we will have to build this seemingly magical operator by hand. +As an example, let's imagine that we want to use Grover's algorithm to search for `magic squares `__. +To define the oracle, which determines whether a solution is valid or not, we must perform sums over the rows and +columns to check that they all have the same value. Therefore, to create this oracle, +we will need to define a sum operator within the quantum computer. + +The second question we face is why we should work with the QFT at all. There are other procedures that could be used to perform these basic operations; for example, by +imitating the classical algorithm. But, as we can see in [#Draper2000]_, it has already been proven that the QFT needs fewer qubits to perform these operations, which is +nowadays of vital importance. + +We will organize the demo as follows. Initially, we will talk about the Fourier basis to give an intuitive idea of how +it works, after which we will address different basic arithmetic operations. Finally, we will move on to a practical example +in which we will factor numbers using Grover's algorithm. + + +QFT representation +----------------- + +To apply the QFT to basic arithmetic operations, our objective now is to learn how to add, +subtract and multiply numbers using quantum devices. As we are working with qubits, +—which, like bits, can take the +values :math:`0` or :math:`1`—we will represent the numbers in binary. For the +purposes of this tutorial, we will assume that we are working only with +integers. Therefore, if we have :math:`n` qubits, we will be able to +represent the numbers from :math:`0` to :math:`2^n-1.` + +The first thing we need to know is PennyLane's +standard for encoding numbers in a binary format. A binary number can be +represented as a string of 1s and 0s, which we can represent as the multi-qubit state + +.. math:: \vert m \rangle = \vert \overline{q_0q_1...q_{n-1}}\rangle, + +where the formula to obtain the equivalent decimal number :math:`m` will be: + +.. math:: m= \sum_{i = 0}^{n-1}2^{n-1-i}q_i. + +Note that :math:`\vert m \rangle` refers to the basic state +generated by the binary encoding of the number :math:`m.` +For instance, the natural number :math:`6` +is represented by the quantum state :math:`\vert 110\rangle,` since :math:`\vert 110 \rangle = 1 \cdot 2^2 + 1\cdot 2^1+0\cdot 2^0 = 6.` + +Let’s see how we would represent all the integers from :math:`0` to :math:`7` using the product state of three qubits, visualized by separate Bloch spheres for each qubit. + +.. figure:: /_static/demonstration_assets/qft_arithmetics/3_qubits_computational_basis.gif + :width: 90% + :align: center + + Representation of integers using a computational basis of three qubits. + +.. note:: + + The `Bloch sphere `_ + is a way of graphically representing the state of a qubit. + At the top of the sphere we place the state :math:`\vert 0 \rangle,` at the bottom + :math:`\vert 1 \rangle,` and in the rest of the + sphere we will place the possible states in superposition. It is a very useful + representation that helps better visualize and interpret quantum gates such as rotations. + +We can use +the :class:`qml.BasisEmbedding ` +template to obtain the binary representation in a simple way. +Let's see how we would code the number :math:`6.` +""" + +import pennylane as qml +import matplotlib.pyplot as plt + +dev = qml.device("default.qubit", wires=3) + +@qml.compile +@qml.qnode(dev) +def basis_embedding_circuit(m): + qml.BasisEmbedding(m, wires=range(3)) + return qml.state() + +m = 6 # number to be encoded + +qml.draw_mpl(basis_embedding_circuit, show_all_wires=True)(m) +plt.show() + +###################################################################### +# +# As we can see, the first qubit—the :math:`0`-th wire—is placed on top and the rest of the qubits are +# below it. However, this is not the only way we could represent numbers. +# We can also represent them in different bases, such as the so-called *Fourier base*. +# +# In this case, all the states of the basis will be represented via qubits in +# the XY-plane of the Bloch sphere, each rotated by a certain +# amount. +# +# +# How do we know how much we must rotate each qubit to represent a certain number? +# It is actually very easy! Suppose we are working with +# :math:`n` qubits and we want to represent the number :math:`m` in the +# Fourier basis. Then the :math:`j`-th qubit will have the phase: +# +# .. math:: \alpha_j = \frac{2m\pi}{2^{j}}. +# +# Now we can represent numbers in the Fourier basis using three qubits: +# +# .. figure:: /_static/demonstration_assets/qft_arithmetics/3_qubits_fourier_basis.gif +# :width: 90% +# :align: center +# +# Representation of integers using the Fourier basis with three qubits +# +# As we can see, the third qubit will rotate +# :math:`\frac{1}{8}` of a turn counterclockwise with each number. The next qubit +# rotates :math:`\frac{1}{4}` of a full turn and, finally, the first qubit rotates +# half a turn for each increase in number. +# +# Adding a number to a register +# ------------------------------ +# +# The fact that the states encoding the numbers are now in phase gives us great +# flexibility in carrying out our arithmetic operations. To see this in practice, +# let’s look at the situation in which want to create an operator Sum +# such that: +# +# .. math:: \text{Sum(k)}\vert m \rangle = \vert m + k \rangle. +# +# The procedure to implement this unitary operation is the following: +# +# #. We convert the state from the computational basis into the Fourier basis by applying the QFT to the :math:`\vert m \rangle` state via the :class:`~pennylane.QFT` operator. +# +# #. We rotate the :math:`j`-th qubit by the angle :math:`\frac{2k\pi}{2^{j}}` using the :math:`R_Z` gate, which leads to the new phases, :math:`\frac{2(m + k)\pi}{2^{j}}.` +# +# #. We apply the QFT inverse to return to the computational basis and obtain :math:`m+k.` +# +# +# Let's see how this process would look in PennyLane. +# + +import pennylane as qml +import numpy as np + +n_wires = 4 +dev = qml.device("default.qubit", wires=n_wires, shots=1) + +def add_k_fourier(k, wires): + for j in range(len(wires)): + qml.RZ(k * np.pi / (2**j), wires=wires[j]) + +@qml.qnode(dev) +def sum(m, k): + qml.BasisEmbedding(m, wires=range(n_wires)) # m encoding + + qml.QFT(wires=range(n_wires)) # step 1 + + add_k_fourier(k, range(n_wires)) # step 2 + + qml.adjoint(qml.QFT)(wires=range(n_wires)) # step 3 + + return qml.sample() + + +print(f"The ket representation of the sum of 3 and 4 is {sum(3,4)}") + +###################################################################### +# Perfect, we have obtained :math:`\vert 0111 \rangle,` which is equivalent to the number :math:`7` in binary! +# +# Note that this is a deterministic algorithm, which means that we have obtained the desired solution by executing a single shot. +# On the other hand, if the result of an operation is greater than the maximum +# value :math:`2^n-1,` we will start again from zero, that is to say, we +# will calculate the sum modulo :math:`2^n-1.` For instance, in our three-qubit example, suppose that +# we want to calculate :math:`6+3.` We see that we do not have +# enough memory space, as :math:`6+3 = 9 > 2^3-1.` The result we will get will +# be :math:`9 \pmod 8 = 1,` or :math:`\vert 001 \rangle` in binary. Make sure to use +# enough qubits to represent your solutions! +# Finally, it is important to point out that it is not necessary to know how the +# QFT is constructed in order to use it. By knowing the properties of the +# new basis, we can use it in a simple way. +# +# Adding two different registers +# ------------------------------ +# +# In this previous algorithm, we had to pass to the operator the value of :math:`k` that we wanted to add to the starting state. +# But at other times, instead of passing that value to the operator, we would like it to pull the information from another register. +# That is, we are looking for a new operator :math:`\text{Sum}_2` such that +# +# .. math:: \text{Sum}_2\vert m \rangle \vert k \rangle \vert 0 \rangle = \vert m \rangle \vert k \rangle \vert m+k \rangle. +# +# In this case, we can understand the third register (which is initially +# at :math:`0`) as a counter that will tally as many units as :math:`m` and +# :math:`k` combined. The binary decomposition will +# make this simple. If we have :math:`\vert m \rangle = \vert \overline{q_0q_1q_2} \rangle,` we will +# have to add :math:`1` to the counter if :math:`q_2 = 1` and nothing +# otherwise. In general, we should add :math:`2^{n-i-1}` units if the :math:`i`-th +# qubit is in state :math:`\vert 1 \rangle` and 0 otherwise. As we can see, this is the same idea that is also +# behind the concept of a controlled gate. Indeed, observe that we will, indeed, apply a corresponding +# phase if indeed the control qubit is in the state :math:`\vert 1\rangle.` +# Let us now code the :math:`\text{Sum}_2` operator. + +wires_m = [0, 1, 2] # qubits needed to encode m +wires_k = [3, 4, 5] # qubits needed to encode k +wires_solution = [6, 7, 8, 9] # qubits needed to encode the solution + +dev = qml.device("default.qubit", wires=wires_m + wires_k + wires_solution, shots=1) + +n_wires = len(dev.wires) # total number of qubits used + +def addition(wires_m, wires_k, wires_solution): + # prepare solution qubits to counting + qml.QFT(wires=wires_solution) + + # add m to the counter + for i in range(len(wires_m)): + qml.ctrl(add_k_fourier, control=wires_m[i])(2 **(len(wires_m) - i - 1), wires_solution) + + # add k to the counter + for i in range(len(wires_k)): + qml.ctrl(add_k_fourier, control=wires_k[i])(2 **(len(wires_k) - i - 1), wires_solution) + + # return to computational basis + qml.adjoint(qml.QFT)(wires=wires_solution) + +@qml.qnode(dev) +def sum2(m, k, wires_m, wires_k, wires_solution): + # m and k codification + qml.BasisEmbedding(m, wires=wires_m) + qml.BasisEmbedding(k, wires=wires_k) + + # apply the addition circuit + addition(wires_m, wires_k, wires_solution) + + return qml.sample(wires=wires_solution) + +print(f"The ket representation of the sum of 7 and 3 is " + f"{sum2(7, 3, wires_m, wires_k, wires_solution)}") + +qml.draw_mpl(sum2, show_all_wires=True)(7, 3, wires_m, wires_k, wires_solution) +plt.show() + +###################################################################### +# Great! We have just seen how to add a number to a counter. In the example above, +# we added :math:`3 + 7` to get :math:`10,` which in binary +# is :math:`\vert 1010 \rangle.` +# +# Multiplying qubits +# ------------------- +# +# Following the same idea, we will see how easily we can +# implement multiplication. For this purpose we'll take two arbitrary numbers :math:`m` and :math:`k` +# to carry out the operation. This time, we look for an operator Mul such that +# +# .. math:: \text{Mul}\vert m \rangle \vert k \rangle \vert 0 \rangle = \vert m \rangle \vert k \rangle \vert m\cdot k \rangle. +# +# To understand the multiplication process, let's work with the binary decomposition of +# :math:`k:=\sum_{i=0}^{n-1}2^{n-i-1}k_i` and +# :math:`m:=\sum_{j=0}^{l-1}2^{l-j-1}m_i.` In this case, the product would +# be: +# +# .. math:: k \cdot m = \sum_{i=0}^{n-1}\sum_{j = 0}^{l-1}m_ik_i (2^{n-i-1} \cdot 2^{l-j-1}). +# +# In other words, if :math:`k_i = 1` and :math:`m_i = 1,` we would add +# :math:`2^{n-i-1} \cdot 2^{l-j-1}` units to the counter, where :math:`n` and :math:`l` +# are the number of qubits with which we encode :math:`m` and :math:`k` respectively. +# Let's code to see how it works! + +wires_m = [0, 1, 2] # qubits needed to encode m +wires_k = [3, 4, 5] # qubits needed to encode k +wires_solution = [6, 7, 8, 9, 10] # qubits needed to encode the solution + +dev = qml.device("default.qubit", wires=wires_m + wires_k + wires_solution, shots=1) + +n_wires = len(dev.wires) + +def multiplication(wires_m, wires_k, wires_solution): + # prepare sol-qubits to counting + qml.QFT(wires=wires_solution) + + # add m to the counter + for i in range(len(wires_k)): + for j in range(len(wires_m)): + coeff = 2 ** (len(wires_m) + len(wires_k) - i - j - 2) + qml.ctrl(add_k_fourier, control=[wires_k[i], wires_m[j]])(coeff, wires_solution) + + # return to computational basis + qml.adjoint(qml.QFT)(wires=wires_solution) + +@qml.qnode(dev) +def mul(m, k): + # m and k codification + qml.BasisEmbedding(m, wires=wires_m) + qml.BasisEmbedding(k, wires=wires_k) + + # Apply multiplication + multiplication(wires_m, wires_k, wires_solution) + + return qml.sample(wires=wires_solution) + + +print(f"The ket representation of the multiplication of 3 and 7 is {mul(3,7)}") + +qml.draw_mpl(mul, show_all_wires=True)(3, 7) +plt.show() + + +###################################################################### +# Awesome! We have multiplied :math:`7 \cdot 3` and, as a result, we have +# :math:`\vert 10101 \rangle,` which is :math:`21` in binary. +# +# +# Factorization with Grover +# ------------------------- +# +# With this, we have already gained a large repertoire of interesting +# operations that we can do, but we can give the idea one more twist and +# apply what we have learned in an example. +# +# Let’s imagine now that we want just the opposite: to factor the +# number :math:`21` as a product of two terms. Is this something we could do +# using our previous reasoning? The answer is yes! We can make use of +# `Grover's algorithm `_ to +# amplify the states whose product is the number we +# are looking for. All we would need is to construct the oracle :math:`U,` i.e., an +# operator such that +# +# .. math:: U\vert m \rangle \vert k \rangle = \vert m \rangle \vert k \rangle \text{ if }m\cdot k \not = 21, +# +# .. math:: U\vert m \rangle \vert k \rangle = -\vert m \rangle \vert k \rangle \text{ if }m\cdot k = 21 +# +# The idea of the oracle is as simple as this: +# +# #. use auxiliary registers to store the product, +# #. check if the product state is :math:`\vert 10101 \rangle` and, in that case, change the sign, +# #. execute the inverse of the circuit to clear the auxiliary qubits. +# #. calculate the probabilities and see which states have been amplified. +# +# Let's go back to PennyLane to implement this idea. + +n = 21 # number we want to factor + +wires_m = [0, 1, 2] # qubits needed to encode m +wires_k = [3, 4, 5] # qubits needed to encode k +wires_solution = [6, 7, 8, 9, 10] # qubits needed to encode the solution + +dev = qml.device("default.qubit", wires=wires_m + wires_k + wires_solution) + +n_wires = len(dev.wires) + +@qml.qnode(dev) +def factorization(n, wires_m, wires_k, wires_solution): + # Superposition of the input + for wire in wires_m: + qml.Hadamard(wires=wire) + + for wire in wires_k: + qml.Hadamard(wires=wire) + + # Apply the multiplication + multiplication(wires_m, wires_k, wires_solution) + + # Change sign of n + qml.FlipSign(n, wires=wires_solution) + + # Uncompute multiplication + qml.adjoint(multiplication)(wires_m, wires_k, wires_solution) + + # Apply Grover operator + qml.GroverOperator(wires=wires_m + wires_k) + + return qml.probs(wires=wires_m) + + +plt.bar(range(2 ** len(wires_m)), factorization(n, wires_m, wires_k, wires_solution)) +plt.xlabel("Basic states") +plt.ylabel("Probability") +plt.show() + +###################################################################### +# By plotting the probabilities of obtaining each basic state we see that +# prime factors have been amplified! Factorization via Grover’s algorithm +# does not achieve exponential improvement that +# `Shor's algorithm `_ does, but we +# can see that this construction is simple and a great example to +# illustrate basic arithmetic! +# +# I hope we can now all see that oracles are not something magical and that there +# is a lot of work behind their construction! This will help us in the future to build +# more complicated operators, but until then, let’s keep on learning. 🚀 +# +# References +# ---------- +# +# .. [#Draper2000] +# +# Thomas G. Draper, "Addition on a Quantum Computer". `arXiv:quant-ph/0008033 `__. +# +# +# About the author +# ---------------- +# diff --git a/demonstrations/tutorial_quantum_chemistry.py b/demonstrations/tutorial_quantum_chemistry.py index b015fb33b7..6925c9fe1b 100644 --- a/demonstrations/tutorial_quantum_chemistry.py +++ b/demonstrations/tutorial_quantum_chemistry.py @@ -1,331 +1,331 @@ -r""" -Building molecular Hamiltonians -=============================== - - -.. meta:: - :property="og:description": Learn how to build electronic Hamiltonians of molecules. - - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/water_structure.png - -.. related:: - tutorial_vqe A brief overview of VQE - -*Author: Alain Delgado — Posted: 02 August 2020. Last updated: 29 August 2023.* - -.. note:: - - A wide variety of molecular data, including Hamiltonians, is - available on the `PennyLane Datasets service `__. - -The ultimate goal of computational quantum chemistry is to unravel the -quantum effects that determine the structure and properties of molecules. Reaching -this goal is challenging since the characteristic energies associated with -these effects, e.g., the electronic correlation energy, are typically a tiny fraction -of the total energy of the molecule. - -Accurate molecular properties can be computed from the wave function describing the -interacting electrons in a molecule. The **electronic** wave function -:math:`\Psi(r)` satisfies the `Schrödinger equation -`_ - -.. math:: - H_e \Psi(r) = E \Psi(r), - -where :math:`H_e` and :math:`E` denote the electronic Hamiltonian and the -total energy of the molecule, respectively. When solving the latter equation, -the nuclei of the molecule can be treated as point particles whose coordinates -are fixed [#BornOpp1927]_. In this approximation, both the total energy and -the electronic Hamiltonian depend parametrically on the nuclear coordinates. - - -In this tutorial, you will learn how to use PennyLane to build a -representation of the electronic Hamiltonian :math:`H_e` that can be used to perform -**quantum** simulations of molecules [#yudong2019]_. First, we show how to define -the structure of the molecule in terms of the symbols and the coordinates of -the atoms. Next, we describe how to solve the `Hartree-Fock -equations `_ for the target -molecule. Finally, we discuss some advanced features that can be used to simulate -more complicated systems. - -Let's get started! - -Defining the molecular structure --------------------------------- -In this example we construct the electronic Hamiltonian of the water molecule. - - -.. figure:: ../_static/demonstration_assets/quantum_chemistry/water_structure.png - :width: 30% - :align: center - -The structure of a molecule is defined by the symbols and the nuclear coordinates of -its constituent atoms. It can be specified using different `chemical file formats -`_. Within PennyLane, the molecular -structure is defined by providing a list with the atomic symbols and a one-dimensional -array with the nuclear coordinates in -`atomic units `_. -""" -import numpy as np - -symbols = ["H", "O", "H"] -coordinates = np.array([[-0.0399, -0.0038, 0.0], [1.5780, 0.8540, 0.0], [2.7909, -0.5159, 0.0]]) - -############################################################################## -# The :func:`~.pennylane.qchem.read_structure` function can also be used to read the -# molecular geometry from an external file. - - -from pennylane import qchem - -symbols, coordinates = qchem.read_structure("quantum_chemistry/h2o.xyz") - -############################################################################## -# The xyz format is supported. -# -# Solving the Hartree-Fock equations -# ---------------------------------- -# The molecule's electronic Hamiltonian is commonly represented using the -# `second-quantization `_ formalism, -# which we will explore in more detail in the -# next section. To that aim, a basis of **single-particle** states needs to be chosen. -# In quantum chemistry these states are the -# `molecular orbitals `_ -# which describe the wave function of a single electron in the molecule. -# -# Molecular orbitals are typically represented as a linear combination of **atomic orbitals**. -# The expansion coefficients in the atomic basis are calculated using the -# `Hartree-Fock (HF) method `_. -# In the HF approximation, each electron in the molecule is treated as an **independent** -# particle that moves under the influence of the Coulomb potential due to the nuclei, and a mean -# field generated by all other electrons [#pople1977]_. The optimized coefficients are precisely -# what we need to build the second-quantized Hamiltonian. -# -# PennyLane provides a differentiable Hartree-Fock solver and the functionality to construct a -# fully-differentiable molecular Hamiltonian. -# -# Building the Hamiltonian -# ------------------------ -# In the second quantization formalism, the electronic wave function of the molecule -# is represented in the occupation number basis. For :math:`M` *spin* molecular -# orbitals, the elements of this basis are labelled as -# :math:`\vert n_0, n_1, \dots, n_{M-1} \rangle,` where :math:`n_i = 0,1` -# indicates the occupation of each orbital. In this representation, the electronic -# Hamiltonian is given by -# -# .. math:: -# H = \sum_{p,q} h_{pq} c_p^\dagger c_q + -# \frac{1}{2} \sum_{p,q,r,s} h_{pqrs} c_p^\dagger c_q^\dagger c_r c_s, -# -# where :math:`c^\dagger` and :math:`c` are the electron creation -# and annihilation operators, respectively, and the coefficients -# :math:`h_{pq}` and :math:`h_{pqrs}` denote the one- and two-electron -# Coulomb integrals [#ref_integrals]_ evaluated using the Hartree-Fock -# orbitals. -# -# We can use the states of :math:`M` qubits to encode any element -# of the occupation number basis -# -# .. math:: -# \vert n_0, n_1, \dots, n_{M-1} \rangle \rightarrow \vert q_0 q_1 \cdots q_{M-1} \rangle. -# -# This implies that we need to map the fermionic operators onto operators -# that act on qubits. This can be done by using the -# `Jordan-Wigner `_ -# transformation [#seeley2012]_ which allows us to decompose the fermionic Hamiltonian -# into a linear combination of the tensor product of Pauli operators -# -# .. math:: -# H = \sum_j C_j \otimes_i \sigma_i^{(j)}, -# -# where :math:`C_j` is a scalar coefficient and :math:`\sigma_i` represents an -# element of the Pauli group :math:`\{ I, X, Y, Z \}.` -# -# In PennyLane we have the :func:`~.pennylane.qchem.molecular_hamiltonian` -# function which encapsulates all the steps explained above. It simplifies the process of building -# the electronic Hamiltonian to a single line of code. We just need to input -# the molecule, as shown below: - -molecule = qchem.Molecule(symbols, coordinates) -H, qubits = qchem.molecular_hamiltonian(molecule) -print("Number of qubits: {:}".format(qubits)) -print("Qubit Hamiltonian") -print(H) - -############################################################################## -# Advanced features -# ----------------- -# The :class:`~.pennylane.qchem.Molecule` object allows us to define additional -# keyword arguments to solve the Hartree-Fock equations of more complicated systems. -# The net charge of the molecule may be specified to simulate positively or negatively -# charged molecules. For a neutral system we choose - -charge = 0 - -############################################################################## -# We can also specify the -# `spin multiplicity `_. For the -# water molecule, which contains ten electrons, the `Slater determinant -# `_ resulting from occupying the five -# lowest-energy orbitals with two *paired* electrons per orbital has spin multiplicity one. -# Alternatively, if we define an occupation where the first four orbitals are doubly occupied -# and the next two are singly occupied by *unpaired* electrons, the HF state will have -# multiplicity three. -# -# | -# -# .. figure:: ../_static/demonstration_assets/quantum_chemistry/hf_references.png -# :width: 50% -# :align: center -# -# | -# -# For the neutral water molecule we have, - -multiplicity = 1 - -############################################################################## -# As mentioned above, molecular orbitals are represented as a linear combination -# of atomic orbitals which are typically modeled as `Gaussian-type orbitals -# `_. We can specify different types -# of `Gaussian atomic bases `_. In this example we -# choose a `minimal basis set -# `_. - -basis_set = "sto-3g" - -############################################################################## -# PennyLane also allows us to define an active space [#truhlar2018]_ to perform quantum -# simulations with a reduced number of qubits. This is done by classifying the molecular -# orbitals as core, active, and external orbitals: -# -# * Core orbitals are always occupied by two electrons. -# * Active orbitals can be occupied by zero, one, or two electrons. -# * The external orbitals are never occupied. -# -# Within this approximation, a certain number of **active electrons** are allowed to -# populate a finite set of **active orbitals**. -# -# .. figure:: ../_static/demonstration_assets/quantum_chemistry/sketch_active_space.png -# :width: 40% -# :align: center -# -# .. note:: -# The number of active **spin-orbitals** determines the **number of qubits** required -# to perform the quantum simulations. -# -# For the water molecule in a minimal basis set we have a total of ten electrons -# and seven molecular orbitals. In this example we define a symmetric active space with -# four electrons and four active orbitals using -# the :func:`~.pennylane.qchem.active_space` function: - -electrons = 10 -orbitals = 7 -core, active = qchem.active_space(electrons, orbitals, active_electrons=4, active_orbitals=4) - -############################################################################## -# Viewing the results: - -print("List of core orbitals: {:}".format(core)) -print("List of active orbitals: {:}".format(active)) -print("Number of qubits: {:}".format(2 * len(active))) - -############################################################################## -# Finally, we use the :func:`~.pennylane.qchem.molecular_hamiltonian` function to -# build the resulting Hamiltonian of the water molecule: - -molecule = qchem.Molecule( - symbols, - coordinates, - charge=charge, - mult=multiplicity, - basis_name=basis_set -) - -H, qubits = qchem.molecular_hamiltonian( - molecule, - active_electrons=4, - active_orbitals=4, -) - -print("Number of qubits required to perform quantum simulations: {:}".format(qubits)) -print("Hamiltonian of the water molecule") -print(H) - -############################################################################## -# In this case, since we have truncated the basis of molecular orbitals, the resulting -# observable is an approximation of the Hamiltonian generated in the -# section `Building the Hamiltonian `__. -# -# OpenFermion-PySCF backend -# ------------------------- -# The :func:`~.pennylane.qchem.molecular_hamiltonian` function can also be used to construct the -# molecular Hamiltonian with a non-differentiable backend that uses the -# `OpenFermion-PySCF `_ plugin interfaced with the -# electronic structure package `PySCF `_. This -# backend can be selected by setting ``method='pyscf'`` in -# :func:`~.pennylane.qchem.molecular_hamiltonian`: - -molecule = qchem.Molecule(symbols, coordinates) -H, qubits = qchem.molecular_hamiltonian(molecule, method="pyscf") -print(H) - -############################################################################## -# This backend requires the ``OpenFermion-PySCF`` plugin to be installed by the user with -# -# .. code-block:: bash -# -# pip install openfermionpyscf -# -# Additionally, if you have built your electronic Hamiltonian independently using -# `OpenFermion `_ tools, it can -# be readily converted to a PennyLane observable using the -# :func:`~.pennylane.qchem.import_operator` function. -# -# You have completed the tutorial! Now, select your favorite molecule and build its electronic -# Hamiltonian. -# To see how simple it is to implement the VQE algorithm to compute the ground-state energy of -# your molecule using PennyLane, take a look at the tutorial :doc:`tutorial_vqe`. -# -# References -# ---------- -# -# .. [#yudong2019] -# -# Yudong Cao, Jonathan Romero, *et al.*, "Quantum Chemistry in the Age of Quantum Computing". -# `Chem. Rev. 2019, 119, 19, 10856-10915. -# `_ -# -# .. [#BornOpp1927] -# -# M. Born, J.R. Oppenheimer, "Quantum Theory of the Molecules". -# `Annalen der Physik 84, 457-484 (1927) -# `_ -# -# .. [#pople1977] -# -# Rolf Seeger, John Pople. "Self‐consistent molecular orbital methods. XVIII. Constraints and -# stability in Hartree–Fock theory". `Journal of Chemical Physics 66, -# 3045 (1977). `_ -# -# .. [#ref_integrals] -# -# J.T. Fermann, E.F. Valeev, "Fundamentals of Molecular Integrals Evaluation". -# `arXiv:2007.12057 `_ -# -# .. [#seeley2012] -# -# Jacob T. Seeley, Martin J. Richard, Peter J. Love. "The Bravyi-Kitaev transformation for -# quantum computation of electronic structure". `Journal of Chemical Physics 137, 224109 (2012). -# `_ -# -# .. [#truhlar2018] -# -# J.J. Bao, S.S. Dong, L. Gagliardi, D.G. Truhlar. "Automatic Selection of an -# Active Space for Calculating Electronic Excitation Spectra by MS-CASPT2 or MC-PDFT". -# `Journal of Chemical Theory and Computation 14, 2017 (2018). -# `_ -# -# About the author -# ---------------- -# +r""" +Building molecular Hamiltonians +=============================== + + +.. meta:: + :property="og:description": Learn how to build electronic Hamiltonians of molecules. + + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/water_structure.png + +.. related:: + tutorial_vqe A brief overview of VQE + +*Author: Alain Delgado — Posted: 02 August 2020. Last updated: 29 August 2023.* + +.. note:: + + A wide variety of molecular data, including Hamiltonians, is + available on the `PennyLane Datasets service `__. + +The ultimate goal of computational quantum chemistry is to unravel the +quantum effects that determine the structure and properties of molecules. Reaching +this goal is challenging since the characteristic energies associated with +these effects, e.g., the electronic correlation energy, are typically a tiny fraction +of the total energy of the molecule. + +Accurate molecular properties can be computed from the wave function describing the +interacting electrons in a molecule. The **electronic** wave function +:math:`\Psi(r)` satisfies the `Schrödinger equation +`_ + +.. math:: + H_e \Psi(r) = E \Psi(r), + +where :math:`H_e` and :math:`E` denote the electronic Hamiltonian and the +total energy of the molecule, respectively. When solving the latter equation, +the nuclei of the molecule can be treated as point particles whose coordinates +are fixed [#BornOpp1927]_. In this approximation, both the total energy and +the electronic Hamiltonian depend parametrically on the nuclear coordinates. + + +In this tutorial, you will learn how to use PennyLane to build a +representation of the electronic Hamiltonian :math:`H_e` that can be used to perform +**quantum** simulations of molecules [#yudong2019]_. First, we show how to define +the structure of the molecule in terms of the symbols and the coordinates of +the atoms. Next, we describe how to solve the `Hartree-Fock +equations `_ for the target +molecule. Finally, we discuss some advanced features that can be used to simulate +more complicated systems. + +Let's get started! + +Defining the molecular structure +-------------------------------- +In this example we construct the electronic Hamiltonian of the water molecule. + + +.. figure:: ../_static/demonstration_assets/quantum_chemistry/water_structure.png + :width: 30% + :align: center + +The structure of a molecule is defined by the symbols and the nuclear coordinates of +its constituent atoms. It can be specified using different `chemical file formats +`_. Within PennyLane, the molecular +structure is defined by providing a list with the atomic symbols and a one-dimensional +array with the nuclear coordinates in +`atomic units `_. +""" +import numpy as np + +symbols = ["H", "O", "H"] +coordinates = np.array([[-0.0399, -0.0038, 0.0], [1.5780, 0.8540, 0.0], [2.7909, -0.5159, 0.0]]) + +############################################################################## +# The :func:`~.pennylane.qchem.read_structure` function can also be used to read the +# molecular geometry from an external file. + + +from pennylane import qchem + +symbols, coordinates = qchem.read_structure("quantum_chemistry/h2o.xyz") + +############################################################################## +# The xyz format is supported. +# +# Solving the Hartree-Fock equations +# ---------------------------------- +# The molecule's electronic Hamiltonian is commonly represented using the +# `second-quantization `_ formalism, +# which we will explore in more detail in the +# next section. To that aim, a basis of **single-particle** states needs to be chosen. +# In quantum chemistry these states are the +# `molecular orbitals `_ +# which describe the wave function of a single electron in the molecule. +# +# Molecular orbitals are typically represented as a linear combination of **atomic orbitals**. +# The expansion coefficients in the atomic basis are calculated using the +# `Hartree-Fock (HF) method `_. +# In the HF approximation, each electron in the molecule is treated as an **independent** +# particle that moves under the influence of the Coulomb potential due to the nuclei, and a mean +# field generated by all other electrons [#pople1977]_. The optimized coefficients are precisely +# what we need to build the second-quantized Hamiltonian. +# +# PennyLane provides a differentiable Hartree-Fock solver and the functionality to construct a +# fully-differentiable molecular Hamiltonian. +# +# Building the Hamiltonian +# ------------------------ +# In the second quantization formalism, the electronic wave function of the molecule +# is represented in the occupation number basis. For :math:`M` *spin* molecular +# orbitals, the elements of this basis are labelled as +# :math:`\vert n_0, n_1, \dots, n_{M-1} \rangle,` where :math:`n_i = 0,1` +# indicates the occupation of each orbital. In this representation, the electronic +# Hamiltonian is given by +# +# .. math:: +# H = \sum_{p,q} h_{pq} c_p^\dagger c_q + +# \frac{1}{2} \sum_{p,q,r,s} h_{pqrs} c_p^\dagger c_q^\dagger c_r c_s, +# +# where :math:`c^\dagger` and :math:`c` are the electron creation +# and annihilation operators, respectively, and the coefficients +# :math:`h_{pq}` and :math:`h_{pqrs}` denote the one- and two-electron +# Coulomb integrals [#ref_integrals]_ evaluated using the Hartree-Fock +# orbitals. +# +# We can use the states of :math:`M` qubits to encode any element +# of the occupation number basis +# +# .. math:: +# \vert n_0, n_1, \dots, n_{M-1} \rangle \rightarrow \vert q_0 q_1 \cdots q_{M-1} \rangle. +# +# This implies that we need to map the fermionic operators onto operators +# that act on qubits. This can be done by using the +# `Jordan-Wigner `_ +# transformation [#seeley2012]_ which allows us to decompose the fermionic Hamiltonian +# into a linear combination of the tensor product of Pauli operators +# +# .. math:: +# H = \sum_j C_j \otimes_i \sigma_i^{(j)}, +# +# where :math:`C_j` is a scalar coefficient and :math:`\sigma_i` represents an +# element of the Pauli group :math:`\{ I, X, Y, Z \}.` +# +# In PennyLane we have the :func:`~.pennylane.qchem.molecular_hamiltonian` +# function which encapsulates all the steps explained above. It simplifies the process of building +# the electronic Hamiltonian to a single line of code. We just need to input +# the molecule, as shown below: + +molecule = qchem.Molecule(symbols, coordinates) +H, qubits = qchem.molecular_hamiltonian(molecule) +print("Number of qubits: {:}".format(qubits)) +print("Qubit Hamiltonian") +print(H) + +############################################################################## +# Advanced features +# ----------------- +# The :class:`~.pennylane.qchem.Molecule` object allows us to define additional +# keyword arguments to solve the Hartree-Fock equations of more complicated systems. +# The net charge of the molecule may be specified to simulate positively or negatively +# charged molecules. For a neutral system we choose + +charge = 0 + +############################################################################## +# We can also specify the +# `spin multiplicity `_. For the +# water molecule, which contains ten electrons, the `Slater determinant +# `_ resulting from occupying the five +# lowest-energy orbitals with two *paired* electrons per orbital has spin multiplicity one. +# Alternatively, if we define an occupation where the first four orbitals are doubly occupied +# and the next two are singly occupied by *unpaired* electrons, the HF state will have +# multiplicity three. +# +# | +# +# .. figure:: ../_static/demonstration_assets/quantum_chemistry/hf_references.png +# :width: 50% +# :align: center +# +# | +# +# For the neutral water molecule we have, + +multiplicity = 1 + +############################################################################## +# As mentioned above, molecular orbitals are represented as a linear combination +# of atomic orbitals which are typically modeled as `Gaussian-type orbitals +# `_. We can specify different types +# of `Gaussian atomic bases `_. In this example we +# choose a `minimal basis set +# `_. + +basis_set = "sto-3g" + +############################################################################## +# PennyLane also allows us to define an active space [#truhlar2018]_ to perform quantum +# simulations with a reduced number of qubits. This is done by classifying the molecular +# orbitals as core, active, and external orbitals: +# +# * Core orbitals are always occupied by two electrons. +# * Active orbitals can be occupied by zero, one, or two electrons. +# * The external orbitals are never occupied. +# +# Within this approximation, a certain number of **active electrons** are allowed to +# populate a finite set of **active orbitals**. +# +# .. figure:: ../_static/demonstration_assets/quantum_chemistry/sketch_active_space.png +# :width: 40% +# :align: center +# +# .. note:: +# The number of active **spin-orbitals** determines the **number of qubits** required +# to perform the quantum simulations. +# +# For the water molecule in a minimal basis set we have a total of ten electrons +# and seven molecular orbitals. In this example we define a symmetric active space with +# four electrons and four active orbitals using +# the :func:`~.pennylane.qchem.active_space` function: + +electrons = 10 +orbitals = 7 +core, active = qchem.active_space(electrons, orbitals, active_electrons=4, active_orbitals=4) + +############################################################################## +# Viewing the results: + +print("List of core orbitals: {:}".format(core)) +print("List of active orbitals: {:}".format(active)) +print("Number of qubits: {:}".format(2 * len(active))) + +############################################################################## +# Finally, we use the :func:`~.pennylane.qchem.molecular_hamiltonian` function to +# build the resulting Hamiltonian of the water molecule: + +molecule = qchem.Molecule( + symbols, + coordinates, + charge=charge, + mult=multiplicity, + basis_name=basis_set +) + +H, qubits = qchem.molecular_hamiltonian( + molecule, + active_electrons=4, + active_orbitals=4, +) + +print("Number of qubits required to perform quantum simulations: {:}".format(qubits)) +print("Hamiltonian of the water molecule") +print(H) + +############################################################################## +# In this case, since we have truncated the basis of molecular orbitals, the resulting +# observable is an approximation of the Hamiltonian generated in the +# section `Building the Hamiltonian `__. +# +# OpenFermion-PySCF backend +# ------------------------- +# The :func:`~.pennylane.qchem.molecular_hamiltonian` function can also be used to construct the +# molecular Hamiltonian with a non-differentiable backend that uses the +# `OpenFermion-PySCF `_ plugin interfaced with the +# electronic structure package `PySCF `_. This +# backend can be selected by setting ``method='pyscf'`` in +# :func:`~.pennylane.qchem.molecular_hamiltonian`: + +molecule = qchem.Molecule(symbols, coordinates) +H, qubits = qchem.molecular_hamiltonian(molecule, method="pyscf") +print(H) + +############################################################################## +# This backend requires the ``OpenFermion-PySCF`` plugin to be installed by the user with +# +# .. code-block:: bash +# +# pip install openfermionpyscf +# +# Additionally, if you have built your electronic Hamiltonian independently using +# `OpenFermion `_ tools, it can +# be readily converted to a PennyLane observable using the +# :func:`~.pennylane.qchem.import_operator` function. +# +# You have completed the tutorial! Now, select your favorite molecule and build its electronic +# Hamiltonian. +# To see how simple it is to implement the VQE algorithm to compute the ground-state energy of +# your molecule using PennyLane, take a look at the tutorial :doc:`tutorial_vqe`. +# +# References +# ---------- +# +# .. [#yudong2019] +# +# Yudong Cao, Jonathan Romero, *et al.*, "Quantum Chemistry in the Age of Quantum Computing". +# `Chem. Rev. 2019, 119, 19, 10856-10915. +# `_ +# +# .. [#BornOpp1927] +# +# M. Born, J.R. Oppenheimer, "Quantum Theory of the Molecules". +# `Annalen der Physik 84, 457-484 (1927) +# `_ +# +# .. [#pople1977] +# +# Rolf Seeger, John Pople. "Self‐consistent molecular orbital methods. XVIII. Constraints and +# stability in Hartree–Fock theory". `Journal of Chemical Physics 66, +# 3045 (1977). `_ +# +# .. [#ref_integrals] +# +# J.T. Fermann, E.F. Valeev, "Fundamentals of Molecular Integrals Evaluation". +# `arXiv:2007.12057 `_ +# +# .. [#seeley2012] +# +# Jacob T. Seeley, Martin J. Richard, Peter J. Love. "The Bravyi-Kitaev transformation for +# quantum computation of electronic structure". `Journal of Chemical Physics 137, 224109 (2012). +# `_ +# +# .. [#truhlar2018] +# +# J.J. Bao, S.S. Dong, L. Gagliardi, D.G. Truhlar. "Automatic Selection of an +# Active Space for Calculating Electronic Excitation Spectra by MS-CASPT2 or MC-PDFT". +# `Journal of Chemical Theory and Computation 14, 2017 (2018). +# `_ +# +# About the author +# ---------------- +# diff --git a/demonstrations/tutorial_quantum_dropout.py b/demonstrations/tutorial_quantum_dropout.py index cebca15758..e0ea5e2ba2 100644 --- a/demonstrations/tutorial_quantum_dropout.py +++ b/demonstrations/tutorial_quantum_dropout.py @@ -1,702 +1,702 @@ -r"""Dropout for Quantum Neural Networks -=================================== -""" - -###################################################################### -# Are you struggling with overfitting while training Quantum Neural Networks (QNNs)? -# -# In this demo, we show how to exploit the quantum version of the dropout technique to avoid the problem of -# overfitting in overparametrized QNNs. What follows is based on the paper “A General -# Approach to Dropout in Quantum Neural Networks” by F. Scala, et al. [#dropout]_. -# -# .. figure:: ../_static/demonstration_assets/quantum_dropout/socialthumbnail_large_QuantumDropout_2024-03-07.png -# :align: center -# :width: 60% -# :target: javascript:void(0) -# -# -# What is overfitting and dropout? -# --------------------------------- -# -# Neural Networks (NNs) usually require highly flexible models with lots of trainable parameters in -# order to *learn* a certain underlying function (or data distribution). -# However, being able to learn with low in-sample error is not enough; *generalization* — the ability to provide -# good predictions on previously unseen data — is also desirable. -# -# Highly expressive models may suffer from **overfitting**, which means that -# they are trained too well on the training data, and as a result perform poorly on new, unseen -# data. This happens because the model has learned the noise in the training data, rather than the -# underlying pattern that is generalizable to new data. -# -# **Dropout** is a common technique for classical Deep Neural Networks (DNNs) preventing computational units -# from becoming too specialized and reducing the risk of overfitting [#Hinton2012]_, [#Srivastava2014]_. It consists of randomly removing -# neurons or connections *only during training* to block the flow of information. Once the -# model is trained, the DNN is employed in its original form. -# -# Why dropout for Quantum Neural Networks? -# ---------------------------------------- -# -# Recently, it has been shown that the use of overparametrized QNN models -# changes the optimization landscape by removing lots of local minima [#Kiani2020]_, [#Larocca2023]_. On the one hand, this increased number of -# parameters leads to faster and easier training, but on the other hand, it may drive -# the model to overfit the data. This is also strictly related to the `repeated encoding `__ of classical -# data to achieve nonlinearity in the computation. This is why, inspired from classical DNNs, one -# can think of applying some sort of dropout to QNNs. This would correspond to randomly dropping some -# (groups of) parameterized gates during training to achieve better generalization. -# -# Quantum dropout of rotations in a sine regression -# -------------------------------------------------- -# -# In this demo we will exploit quantum dropout to avoid overfitting during the regression of noisy -# data originally coming from the *sinusoidal* function. In particular, we will randomly “drop” -# rotations during the training phase. In practice, this will correspond to temporarily setting parameters to a value of 0. -# -# Let’s start by importing Pennylane and ``numpy`` and fixing the random seed for reproducibility: -# - -import numpy as np -import pennylane as qml - -seed = 12345 -np.random.seed(seed=seed) - -###################################################################### -# The circuit -# ~~~~~~~~~~~ -# -# Now we define the embedding of classical data and the variational ansatz that will then be combined -# to construct our QNN. Dropout will happen inside the variational ansatz. Obtaining dropout with standard -# Pennylane would be quite straightforward by means of some "if statements", but the training procedure -# will take ages. Here we will leverage JAX in order to speed up the training process with -# Just In Time (JIT) compilation. The drawback is that the definition of the variational ansatz becomes a -# little elaborated, since JAX has its own language for conditional statements. For this purpose we -# define two functions ``true_cond`` and ``false_cond`` to work with ``jax.lax.cond```, which is the JAX -# conditional statement. See this `demo `__ -# for additional insights on how to optimize QNNs with JAX. -# -# Practically speaking, rotation dropout will be performed by passing a list to the ansatz. -# The single qubit rotations are applied depending on the values stored in this list: -# if the value is negative the rotation is dropped (rotation dropout), otherwise it is applied. -# How to produce this list will be explained later in this demo (see the ``make_dropout`` function). - -import jax # require for Just In Time (JIT) compilation -import jax.numpy as jnp - -jax.config.update("jax_platform_name", "cpu") -jax.config.update("jax_enable_x64", True) - - -def embedding(x, wires): - # Encodes the datum multiple times in the register, - # employing also nonlinear functions - assert len(x) == 1 # check feature is 1-D - for i in wires: - qml.RY(jnp.arcsin(x), wires=i) - for i in wires: - qml.RZ(jnp.arccos(x ** 2), wires=i) - - -def true_cond(angle): - # necessary for using an if statement within jitted function - # exploiting jax.lax.cond - # if this function is assessed the rotation is dropped - return 0.0 - - -def false_cond(angle): - # necessary for using an if statement within jitted function - # exploiting jax.lax.cond - # if this function is assessed the rotation is kept - return angle - - -def var_ansatz( - theta, wires, rotations=[qml.RX, qml.RZ, qml.RX], entangler=qml.CNOT, keep_rotation=None -): - - """Single layer of the variational ansatz for our QNN. - We have a single qubit rotation per each qubit (wire) followed by - a linear chain of entangling gates (entangler). This structure is repeated per each rotation in `rotations` - (defining `inner_layers`). - The single qubit rotations are applied depending on the values stored in `keep_rotation`: - if the value is negative the rotation is dropped (rotation dropout), otherwise it is applied. - - Params: - - theta: variational angles that will undergo optimization - - wires: list of qubits (wires) - - rotations: list of rotation kind per each `inner_layer` - - entangler: entangling gate - - keep_rotation: list of lists. There is one list per each `inner_layer`. - In each list there are indexes of the rotations that we want to apply. - Some of these values may be substituted by -1 value - which means that the rotation gate wont be applied (dropout). - """ - - # the length of `rotations` defines the number of inner layers - N = len(wires) - assert len(theta) == 3 * N - wires = list(wires) - - counter = 0 - # keep_rotations contains a list per each inner_layer - for rots in keep_rotation: - # we cicle over the elements of the lists inside keep_rotation - for qb, keep_or_drop in enumerate(rots): - rot = rotations[counter] # each inner layer can have a different rotation - - angle = theta[counter * N + qb] - # conditional statement implementing dropout - # if `keep_or_drop` is negative the rotation is dropped - angle_drop = jax.lax.cond(keep_or_drop < 0, true_cond, false_cond, angle) - rot(angle_drop, wires=wires[qb]) - for qb in wires[:-1]: - entangler(wires=[wires[qb], wires[qb + 1]]) - counter += 1 - - -###################################################################### -# And then we define the hyperparameters of our QNN, namely the number of qubits, -# the number of sublayers in the variational ansatz (``inner_layers``) and the resulting -# number of parameters per layer: -# - -n_qubits = 5 -inner_layers = 3 -params_per_layer = n_qubits * inner_layers - -###################################################################### -# Now we actually build the QNN: -# - - -def create_circuit(n_qubits, layers): - device = qml.device("default.qubit", wires=n_qubits) - - @qml.qnode(device) - def circuit(x, theta, keep_rot): - # print(x) - # print(theta) - - for i in range(layers): - embedding(x, wires=range(n_qubits)) - - keep_rotation = keep_rot[i] - - var_ansatz( - theta[i * params_per_layer : (i + 1) * params_per_layer], - wires=range(n_qubits), - entangler=qml.CNOT, - keep_rotation=keep_rotation, - ) - - return qml.expval(qml.PauliZ(wires=0)) # we measure only the first qubit - - return circuit - - -###################################################################### -# Let’s have a look at a single layer of our QNN: -# -import matplotlib.pyplot as plt - - -plt.style.use("pennylane.drawer.plot") # set pennylane theme, which is nice to see - -# create the circuit with given number of qubits and layers -layers = 1 -circ = create_circuit(n_qubits, layers=layers) - -# for the moment let's keep all the rotations in all sublayers -keep_all_rot = [ - [list(range((n_qubits))) for j in range(1, inner_layers + 1)], -] -# we count the parameters -numbered_params = np.array(range(params_per_layer * layers), dtype=float) -# we encode a single coordinate -single_sample = np.array([0]) - -qml.draw_mpl(circ, decimals=2,)(single_sample, numbered_params, keep_all_rot) - -plt.show() - -###################################################################### -# We now build the model that we will employ for the regression task. -# Since we want to have an overparametrized QNN, we will add 10 layers and we exploit -# ``JAX`` to speed the training up: -# - -layers = 10 -qnn_tmp = create_circuit(n_qubits, layers) -qnn_tmp = jax.jit(qnn_tmp) -qnn_batched = jax.vmap( - qnn_tmp, (0, None, None) -) # we want to vmap on 0-axis of the first circuit param -# in this way we process in parallel all the inputs -# We jit for faster execution -qnn = jax.jit(qnn_batched) - - -###################################################################### -# Dropping rotations -# ~~~~~~~~~~~~~~~~~~ -# -# As anticipated, we need to set some random parameters to 0 at each optimization step. Given a layer -# dropout rate :math:`p_L` (this will be called ``layer_drop_rate``) and the gate dropout rate :math:`p_G` -# (this will be called ``rot_drop_rate``), the probability :math:`p` that a -# gate is dropped in a layer can be calculated with the conditioned probability law: -# -# .. math:: -# -# -# p=p(A\cap B)=p(A|B)p(B)=p_Gp_L -# -# where :math:`B` represents the selection of a specific layer and -# :math:`A` the selection of a specific gate within the chosen layer. -# -# In the following cell we define a function that produces the list of the indices of rotation gates that -# are kept. For gates which are dropped, the value ``-1`` is assigned to the corresponding index. The structure of the list -# is nested; we have one list per ``inner_layer`` inside one list per each layer, all contained in another list. -# This function will be called at each iteration. -# - - -def make_dropout(key): - drop_layers = [] - - for lay in range(layers): - # each layer has prob p_L=layer_drop_rate of being dropped - # according to that for every layer we sample - # if we have to appy dropout in it or not - out = jax.random.choice( - key, jnp.array(range(2)), p=jnp.array([1 - layer_drop_rate, layer_drop_rate]) - ) - key = jax.random.split(key)[0] # update the random key - - if out == 1: # if it has to be dropped - drop_layers.append(lay) - - keep_rot = [] - # we make list of indexes corresponding to the rotations gates - # that are kept in the computation during a single train step - for i in range(layers): - # each list is divded in layers and then in "inner layers" - # this is strictly related to the QNN architecture that we use - keep_rot_layer = [list(range((n_qubits))) for j in range(1, inner_layers + 1)] - - if i in drop_layers: # if dropout has to be applied in this layer - keep_rot_layer = [] # list of indexes for a single layer - inner_keep_r = [] # list of indexes for a single inner layer - for param in range(params_per_layer): - # each rotation within the layer has prob p=rot_drop_rate of being dropped - # according to that for every parameter (rotation) we sample - # if we have to drop it or not - out = jax.random.choice( - key, jnp.array(range(2)), p=jnp.array([1 - rot_drop_rate, rot_drop_rate]) - ) - key = jax.random.split(key)[0] # update the random key - - if out == 0: # if we have to keep it - inner_keep_r.append(param % n_qubits) # % is required because we work - # inner layer by inner layer - else: # if the rotation has to be dropped - inner_keep_r.append(-1) # we assign the value -1 - - if param % n_qubits == n_qubits - 1: # if it's the last qubit of the register - # append the inner layer list - keep_rot_layer.append(inner_keep_r) - # and reset it - inner_keep_r = [] - - keep_rot.append(keep_rot_layer) - - return jnp.array(keep_rot) - - -###################################################################### -# We can check the output of the ``make_dropout`` function: -# - -# setting the drop probability -layer_drop_rate, rot_drop_rate = 0.5, 0.3 # 15% probability of dropping a gate - -# JAX random key -key = jax.random.PRNGKey(12345) -# create the list of indexes, -# -1 implies we are dropping a gate -keep_rot = make_dropout(key) - -# let's just print the list for first layer -print(keep_rot[0]) - -###################################################################### -# Noisy sinusoidal function -# ~~~~~~~~~~~~~~~~~~~~~~~~~ -# -# To test the effectiveness of the dropout technique, we will use a prototypical dataset -# with which it is very easy to overfit: the sinusoidal function. We produce some -# points according to the :math:`\sin` function and then we add some white Gaussian noise -# (noise that follows a normal distribution) :math:`\epsilon.` The noise is essential to obtain overfitting; -# when our model is extremely expressive, it is capable of exactly fit each point and some parameters -# become hyper-specialized in recognizing the noisy features. This makes predictions on new unseen -# data difficult, since the overfitting model did not learn the true underlying data distribution. -# The dropout technique will help in avoiding co-adaptation and hyper-specialization, -# effectively reducing overfitting. -# - -from sklearn.model_selection import train_test_split - - -def make_sin_dataset(dataset_size=100, test_size=0.4, noise_value=0.4, plot=False): - """1D regression problem y=sin(x*\pi)""" - # x-axis - x_ax = np.linspace(-1, 1, dataset_size) - y = [[np.sin(x * np.pi)] for x in x_ax] - np.random.seed(123) - # noise vector - noise = np.array([np.random.normal(0, 0.5, 1) for i in y]) * noise_value - X = np.array(x_ax) - y = np.array(y + noise) # apply noise - - # split the dataset - X_train, X_test, y_train, y_test = train_test_split( - X, y, test_size=test_size, random_state=40, shuffle=True - ) - - X_train = X_train.reshape(-1, 1) - X_test = X_test.reshape(-1, 1) - - y_train = y_train.reshape(-1, 1) - y_test = y_test.reshape(-1, 1) - - return X_train, X_test, y_train, y_test - - -from matplotlib import ticker - -X, X_test, y, y_test = make_sin_dataset(dataset_size=20, test_size=0.25) - - -fig, ax = plt.subplots() -plt.plot(X, y, "o", label="Training") -plt.plot(X_test, y_test, "o", label="Test") - -plt.plot( - np.linspace(-1, 1, 100), - [np.sin(x * np.pi) for x in np.linspace(-1, 1, 100)], - linestyle="dotted", - label=r"$\sin(x)$", -) -plt.ylabel(r"$y = \sin(\pi\cdot x) + \epsilon$") -plt.xlabel(r"$x$") -ax.xaxis.set_major_locator(ticker.MultipleLocator(0.5)) -ax.yaxis.set_major_locator(ticker.MultipleLocator(0.5)) -plt.legend() - -plt.show() - -###################################################################### -# Since our circuit is only able to provide outputs in the range :math:`[-1,1],` we rescale all the -# noisy data within this range. To do this we leverage the `MinMaxScaler` from `sklearn`. -# It is common practice to fit the scaler only from training data and then apply it also to the -# test. The reason behind this is that in general one only has knowledge about the training dataset. -# (If the training dataset is not exhaustively representative of the underlying distribution, -# this preprocessing may lead to some outliers in the test set to be scaled out of the desired range.) -# - -from sklearn.preprocessing import MinMaxScaler - -scaler = MinMaxScaler(feature_range=(-1, 1)) -y = scaler.fit_transform(y) -y_test = scaler.transform(y_test) - -# reshaping for computation -y = y.reshape(-1,) -y_test = y_test.reshape(-1,) - -###################################################################### -# Optimization -# ~~~~~~~~~~~~ -# -# At this point we have to set the hyperparameters of the optimization, namely the number of epochs, the -# learning rate, and the optimizer: -# - -import optax # optimization using jax - -epochs = 700 -optimizer = optax.adam(learning_rate=0.01) - -###################################################################### -# We define the cost function as the Mean Square Error: -# - - -@jax.jit -def calculate_mse_cost(X, y, theta, keep_rot): - yp = qnn(X, theta, keep_rot) - # depending on your version of Pennylane you may require the following line - ##### - yp = jnp.array(yp).T - ##### - cost = jnp.mean((yp - y) ** 2) - - return cost - - -# Optimization update step -@jax.jit -def optimizer_update(opt_state, params, x, y, keep_rot): - loss, grads = jax.value_and_grad(lambda theta: calculate_mse_cost(x, y, theta, keep_rot))( - params - ) - updates, opt_state = optimizer.update(grads, opt_state) - - params = optax.apply_updates(params, updates) - return params, opt_state, loss - - -###################################################################### -# Training the model -# ------------------ -# -# And now we can try to train the model. We execute different runs of the training to understand the -# average behaviour of quantum dropout. To see the effect of dropout we can set different values of -# ``layer_drop_rate`` and ``rot_drop_rate``: -# - -n_run = 3 -drop_rates = [(0.0, 0.0), (0.3, 0.2), (0.7, 0.7)] - -train_history = {} -test_history = {} -opt_params = {} - - -for layer_drop_rate, rot_drop_rate in drop_rates: - # initialization of some lists to store data - costs_per_comb = [] - test_costs_per_comb = [] - opt_params_per_comb = [] - # we execute multiple runs in order to see the average behaviour - for tmp_seed in range(seed, seed + n_run): - key = jax.random.PRNGKey(tmp_seed) - assert len(X.shape) == 2 # X must be a matrix - assert len(y.shape) == 1 # y must be an array - assert X.shape[0] == y.shape[0] # compatibility check - - # parameters initialization with gaussian ditribution - initial_params = jax.random.normal(key, shape=(layers * params_per_layer,)) - # update the random key - key = jax.random.split(key)[0] - - params = jnp.copy(initial_params) - - # optimizer initialization - opt_state = optimizer.init(initial_params) - - # lists for saving single run training and test cost trend - costs = [] - test_costs = [] - - for epoch in range(epochs): - # generate the list for dropout - keep_rot = make_dropout(key) - # update the random key - key = jax.random.split(key)[0] - - # optimization step - params, opt_state, cost = optimizer_update(opt_state, params, X, y, keep_rot) - - ############## performance evaluation ############# - # inference is done with the original model - # with all the gates - keep_rot = jnp.array( - [ - [list(range((n_qubits))) for j in range(1, inner_layers + 1)] - for i in range(layers) - ] - ) - # inference on train set - cost = calculate_mse_cost(X, y, params, keep_rot) - - costs.append(cost) - - # inference on test set - test_cost = calculate_mse_cost(X_test, y_test, params, keep_rot) - test_costs.append(test_cost) - - # we print updates every 5 iterations - if epoch % 5 == 0: - print( - f"{layer_drop_rate:.1f}-{rot_drop_rate:.1f}", - f"run {tmp_seed-seed} - epoch {epoch}/{epochs}", - f"--- Train cost:{cost:.5f}", - f"--- Test cost:{test_cost:.5f}", - end="\r", - ) - - costs_per_comb.append(costs) - test_costs_per_comb.append(test_costs) - opt_params_per_comb.append(params) - print() - costs_per_comb = np.array(costs_per_comb) - test_costs_per_comb = np.array(test_costs_per_comb) - opt_params_per_comb = np.array(opt_params_per_comb) - - train_history[(layer_drop_rate, rot_drop_rate)] = costs_per_comb - test_history[(layer_drop_rate, rot_drop_rate)] = test_costs_per_comb - opt_params[(layer_drop_rate, rot_drop_rate)] = opt_params_per_comb - -###################################################################### -# Performance evaluation -# ---------------------- -# -# Let’s compare the difference in performance with a plot: -# - -fig, axs = plt.subplots(1, 2, figsize=(12, 4)) -plt.subplots_adjust(wspace=0.05) -axs[0].set_title("MSE train") -for k, v in train_history.items(): - train_losses = np.array(v) - mean_train_history = np.mean(train_losses, axis=0) - std_train_history = np.std(train_losses, axis=0,) - - mean_train_history = mean_train_history.reshape((epochs,)) - std_train_history = std_train_history.reshape((epochs,)) - - # shadow standard deviation - axs[0].fill_between( - range(epochs), - mean_train_history - std_train_history, - mean_train_history + std_train_history, - alpha=0.2, - ) - # average trend - axs[0].plot(range(epochs), mean_train_history, label=f"{k}") # Avg Loss - -axs[1].set_title("MSE test") -for k, v in test_history.items(): - test_losses = np.array(v) - mean_test_history = np.mean(test_losses, axis=0) - std_test_history = np.std(test_losses, axis=0,) - - mean_test_history = mean_test_history.reshape((epochs,)) - std_test_history = std_test_history.reshape((epochs,)) - - # shadow standard deviation - axs[1].fill_between( - range(epochs), - mean_test_history - std_test_history, - mean_test_history + std_test_history, - alpha=0.2, - ) - # averange trend - axs[1].plot(range(epochs), mean_test_history, label=f"{k}") # Avg Loss - -axs[0].legend(loc="upper center", bbox_to_anchor=(1.01, 1.25), ncol=4, fancybox=True, shadow=True) - -for ax in axs.flat: - ax.set_xlabel("Epochs") - ax.set_ylabel("MSE") - ax.set_yscale("log") - ax.set_ylim([1e-3, 0.6]) - ax.label_outer() - -plt.subplots_adjust(bottom=0.3) - -plt.show() - -###################################################################### -# On the left you can see that without dropout there is a deep minimization of the training loss, -# moderate values of dropout converge, whereas high drop probabilities impede any learning. On -# the right, we can see the difference in generalization during the optimization process. Standard -# training without dropout initially reaches a low value of generalization error, but as the -# model starts to learn the noise in the training data (overfitting), the generalization error grows -# back. Oppositely, moderate values of dropout enable generalization errors comparable to the respective -# training ones. As the learning is not successful for elevated drop probabilities, the generalization -# error is huge. It is interesting to notice that the “not-learning” error is very close to the final -# error of the QNN trained without dropout. -# -# Hence, one can conclude that low values of dropout greatly improve the generalization performance of -# the model and remove overfitting, even if the randomness of the technique inevitably makes the -# training a little noisy. On the other hand, high drop probabilities only hinder the training -# process. -# -# Validation -# ~~~~~~~~~~ -# -# To validate the technique we can also check how the model predicts in the whole :math:`[-1,1]` range -# with and without quantum dropout. -# - -X, X_test, y, y_test = make_sin_dataset(dataset_size=20, test_size=0.25) - -# spanning the whole range -x_ax = jnp.linspace(-1, 1, 100).reshape(100, 1) - -# selecting which run we want to plot -run = 1 - -fig, ax = plt.subplots() -styles = ["dashed", "-.", "solid", "-."] -for i, k in enumerate(train_history.keys()): - if k[0] == 0.3: - alpha = 1 - else: - alpha = 0.5 - # predicting and rescaling - yp = scaler.inverse_transform(qnn(x_ax, opt_params[k][run], keep_rot).reshape(-1, 1)) - plt.plot([[i] for i in np.linspace(-1, 1, 100)], yp, label=k, alpha=alpha, linestyle=styles[i]) - -plt.scatter(X, y, label="Training", zorder=10) -plt.scatter(X_test, y_test, label="Test", zorder=10) - -ylabel = r"$y = \sin(\pi\cdot x) + \epsilon$" -plt.xlabel("x", fontsize="medium") -plt.ylabel(ylabel, fontsize="medium") -plt.legend() -ax.xaxis.set_major_locator(ticker.MultipleLocator(0.5)) -ax.yaxis.set_major_locator(ticker.MultipleLocator(0.5)) - -plt.show() - -###################################################################### -# The model without dropout overfits the noisy data by trying to exactly predict each of them, -# whereas dropout actually mitigates overfitting and makes the approximation of the underlying sinusoidal -# function way smoother. -# -# Conclusion -# ---------------------- -# In this demo, we explained the basic idea behind quantum dropout and -# how to avoid overfitting by randomly "dropping" some rotation gates -# of a QNN during the training phase. We invite you to check out the paper [#dropout]_ -# for more dropout techniques and additional analysis. Try it yourself and develop new -# dropout strategies. -# -# -# References -# ---------- -# -# .. [#dropout] Scala, F., Ceschini, A., Panella, M., & Gerace, D. (2023). -# *A General Approach to Dropout in Quantum Neural Networks*. -# `Adv. Quantum Technol., 2300220 `__. -# -# .. [#Hinton2012] Hinton, G., Srivastava, N., Krizhevsky, A., Sutskever, I., & Salakhutdinov, R. (2012). -# *Improving neural networks by preventing co-adaptation of feature detectors*. -# `arXiv:1207.0580. `__. -# -# .. [#Srivastava2014] Srivastava, N., Hinton, G., Krizhevsky, A., Sutskever, I., & Salakhutdinov, R. (2014). -# *Dropout: A Simple Way to Prevent Neural Networks from Overfitting*. -# `Journal of Machine Learning Research, 15(56):1929−1958. `__. -# -# .. [#Kiani2020] Kiani,B. T., Lloyd, S., & Maity, R. (2020). -# *Learning Unitaries by Gradient Descent*. -# `arXiv: 2001.11897. `__. -# -# .. [#Larocca2023] Larocca, M., Ju, N., García-Martín, D., Coles, P. J., & Cerezo, M. (2023). -# *Theory of overparametrization in quantum neural networks*. -# `Nat. Comp. Science, 3, 542–551. `__. -# -# About the author -# ---------------- +r"""Dropout for Quantum Neural Networks +=================================== +""" + +###################################################################### +# Are you struggling with overfitting while training Quantum Neural Networks (QNNs)? +# +# In this demo, we show how to exploit the quantum version of the dropout technique to avoid the problem of +# overfitting in overparametrized QNNs. What follows is based on the paper “A General +# Approach to Dropout in Quantum Neural Networks” by F. Scala, et al. [#dropout]_. +# +# .. figure:: ../_static/demonstration_assets/quantum_dropout/socialthumbnail_large_QuantumDropout_2024-03-07.png +# :align: center +# :width: 60% +# :target: javascript:void(0) +# +# +# What is overfitting and dropout? +# --------------------------------- +# +# Neural Networks (NNs) usually require highly flexible models with lots of trainable parameters in +# order to *learn* a certain underlying function (or data distribution). +# However, being able to learn with low in-sample error is not enough; *generalization* — the ability to provide +# good predictions on previously unseen data — is also desirable. +# +# Highly expressive models may suffer from **overfitting**, which means that +# they are trained too well on the training data, and as a result perform poorly on new, unseen +# data. This happens because the model has learned the noise in the training data, rather than the +# underlying pattern that is generalizable to new data. +# +# **Dropout** is a common technique for classical Deep Neural Networks (DNNs) preventing computational units +# from becoming too specialized and reducing the risk of overfitting [#Hinton2012]_, [#Srivastava2014]_. It consists of randomly removing +# neurons or connections *only during training* to block the flow of information. Once the +# model is trained, the DNN is employed in its original form. +# +# Why dropout for Quantum Neural Networks? +# ---------------------------------------- +# +# Recently, it has been shown that the use of overparametrized QNN models +# changes the optimization landscape by removing lots of local minima [#Kiani2020]_, [#Larocca2023]_. On the one hand, this increased number of +# parameters leads to faster and easier training, but on the other hand, it may drive +# the model to overfit the data. This is also strictly related to the `repeated encoding `__ of classical +# data to achieve nonlinearity in the computation. This is why, inspired from classical DNNs, one +# can think of applying some sort of dropout to QNNs. This would correspond to randomly dropping some +# (groups of) parameterized gates during training to achieve better generalization. +# +# Quantum dropout of rotations in a sine regression +# -------------------------------------------------- +# +# In this demo we will exploit quantum dropout to avoid overfitting during the regression of noisy +# data originally coming from the *sinusoidal* function. In particular, we will randomly “drop” +# rotations during the training phase. In practice, this will correspond to temporarily setting parameters to a value of 0. +# +# Let’s start by importing Pennylane and ``numpy`` and fixing the random seed for reproducibility: +# + +import numpy as np +import pennylane as qml + +seed = 12345 +np.random.seed(seed=seed) + +###################################################################### +# The circuit +# ~~~~~~~~~~~ +# +# Now we define the embedding of classical data and the variational ansatz that will then be combined +# to construct our QNN. Dropout will happen inside the variational ansatz. Obtaining dropout with standard +# Pennylane would be quite straightforward by means of some "if statements", but the training procedure +# will take ages. Here we will leverage JAX in order to speed up the training process with +# Just In Time (JIT) compilation. The drawback is that the definition of the variational ansatz becomes a +# little elaborated, since JAX has its own language for conditional statements. For this purpose we +# define two functions ``true_cond`` and ``false_cond`` to work with ``jax.lax.cond```, which is the JAX +# conditional statement. See this `demo `__ +# for additional insights on how to optimize QNNs with JAX. +# +# Practically speaking, rotation dropout will be performed by passing a list to the ansatz. +# The single qubit rotations are applied depending on the values stored in this list: +# if the value is negative the rotation is dropped (rotation dropout), otherwise it is applied. +# How to produce this list will be explained later in this demo (see the ``make_dropout`` function). + +import jax # require for Just In Time (JIT) compilation +import jax.numpy as jnp + +jax.config.update("jax_platform_name", "cpu") +jax.config.update("jax_enable_x64", True) + + +def embedding(x, wires): + # Encodes the datum multiple times in the register, + # employing also nonlinear functions + assert len(x) == 1 # check feature is 1-D + for i in wires: + qml.RY(jnp.arcsin(x), wires=i) + for i in wires: + qml.RZ(jnp.arccos(x ** 2), wires=i) + + +def true_cond(angle): + # necessary for using an if statement within jitted function + # exploiting jax.lax.cond + # if this function is assessed the rotation is dropped + return 0.0 + + +def false_cond(angle): + # necessary for using an if statement within jitted function + # exploiting jax.lax.cond + # if this function is assessed the rotation is kept + return angle + + +def var_ansatz( + theta, wires, rotations=[qml.RX, qml.RZ, qml.RX], entangler=qml.CNOT, keep_rotation=None +): + + """Single layer of the variational ansatz for our QNN. + We have a single qubit rotation per each qubit (wire) followed by + a linear chain of entangling gates (entangler). This structure is repeated per each rotation in `rotations` + (defining `inner_layers`). + The single qubit rotations are applied depending on the values stored in `keep_rotation`: + if the value is negative the rotation is dropped (rotation dropout), otherwise it is applied. + + Params: + - theta: variational angles that will undergo optimization + - wires: list of qubits (wires) + - rotations: list of rotation kind per each `inner_layer` + - entangler: entangling gate + - keep_rotation: list of lists. There is one list per each `inner_layer`. + In each list there are indexes of the rotations that we want to apply. + Some of these values may be substituted by -1 value + which means that the rotation gate wont be applied (dropout). + """ + + # the length of `rotations` defines the number of inner layers + N = len(wires) + assert len(theta) == 3 * N + wires = list(wires) + + counter = 0 + # keep_rotations contains a list per each inner_layer + for rots in keep_rotation: + # we cicle over the elements of the lists inside keep_rotation + for qb, keep_or_drop in enumerate(rots): + rot = rotations[counter] # each inner layer can have a different rotation + + angle = theta[counter * N + qb] + # conditional statement implementing dropout + # if `keep_or_drop` is negative the rotation is dropped + angle_drop = jax.lax.cond(keep_or_drop < 0, true_cond, false_cond, angle) + rot(angle_drop, wires=wires[qb]) + for qb in wires[:-1]: + entangler(wires=[wires[qb], wires[qb + 1]]) + counter += 1 + + +###################################################################### +# And then we define the hyperparameters of our QNN, namely the number of qubits, +# the number of sublayers in the variational ansatz (``inner_layers``) and the resulting +# number of parameters per layer: +# + +n_qubits = 5 +inner_layers = 3 +params_per_layer = n_qubits * inner_layers + +###################################################################### +# Now we actually build the QNN: +# + + +def create_circuit(n_qubits, layers): + device = qml.device("default.qubit", wires=n_qubits) + + @qml.qnode(device) + def circuit(x, theta, keep_rot): + # print(x) + # print(theta) + + for i in range(layers): + embedding(x, wires=range(n_qubits)) + + keep_rotation = keep_rot[i] + + var_ansatz( + theta[i * params_per_layer : (i + 1) * params_per_layer], + wires=range(n_qubits), + entangler=qml.CNOT, + keep_rotation=keep_rotation, + ) + + return qml.expval(qml.PauliZ(wires=0)) # we measure only the first qubit + + return circuit + + +###################################################################### +# Let’s have a look at a single layer of our QNN: +# +import matplotlib.pyplot as plt + + +plt.style.use("pennylane.drawer.plot") # set pennylane theme, which is nice to see + +# create the circuit with given number of qubits and layers +layers = 1 +circ = create_circuit(n_qubits, layers=layers) + +# for the moment let's keep all the rotations in all sublayers +keep_all_rot = [ + [list(range((n_qubits))) for j in range(1, inner_layers + 1)], +] +# we count the parameters +numbered_params = np.array(range(params_per_layer * layers), dtype=float) +# we encode a single coordinate +single_sample = np.array([0]) + +qml.draw_mpl(circ, decimals=2,)(single_sample, numbered_params, keep_all_rot) + +plt.show() + +###################################################################### +# We now build the model that we will employ for the regression task. +# Since we want to have an overparametrized QNN, we will add 10 layers and we exploit +# ``JAX`` to speed the training up: +# + +layers = 10 +qnn_tmp = create_circuit(n_qubits, layers) +qnn_tmp = jax.jit(qnn_tmp) +qnn_batched = jax.vmap( + qnn_tmp, (0, None, None) +) # we want to vmap on 0-axis of the first circuit param +# in this way we process in parallel all the inputs +# We jit for faster execution +qnn = jax.jit(qnn_batched) + + +###################################################################### +# Dropping rotations +# ~~~~~~~~~~~~~~~~~~ +# +# As anticipated, we need to set some random parameters to 0 at each optimization step. Given a layer +# dropout rate :math:`p_L` (this will be called ``layer_drop_rate``) and the gate dropout rate :math:`p_G` +# (this will be called ``rot_drop_rate``), the probability :math:`p` that a +# gate is dropped in a layer can be calculated with the conditioned probability law: +# +# .. math:: +# +# +# p=p(A\cap B)=p(A|B)p(B)=p_Gp_L +# +# where :math:`B` represents the selection of a specific layer and +# :math:`A` the selection of a specific gate within the chosen layer. +# +# In the following cell we define a function that produces the list of the indices of rotation gates that +# are kept. For gates which are dropped, the value ``-1`` is assigned to the corresponding index. The structure of the list +# is nested; we have one list per ``inner_layer`` inside one list per each layer, all contained in another list. +# This function will be called at each iteration. +# + + +def make_dropout(key): + drop_layers = [] + + for lay in range(layers): + # each layer has prob p_L=layer_drop_rate of being dropped + # according to that for every layer we sample + # if we have to appy dropout in it or not + out = jax.random.choice( + key, jnp.array(range(2)), p=jnp.array([1 - layer_drop_rate, layer_drop_rate]) + ) + key = jax.random.split(key)[0] # update the random key + + if out == 1: # if it has to be dropped + drop_layers.append(lay) + + keep_rot = [] + # we make list of indexes corresponding to the rotations gates + # that are kept in the computation during a single train step + for i in range(layers): + # each list is divded in layers and then in "inner layers" + # this is strictly related to the QNN architecture that we use + keep_rot_layer = [list(range((n_qubits))) for j in range(1, inner_layers + 1)] + + if i in drop_layers: # if dropout has to be applied in this layer + keep_rot_layer = [] # list of indexes for a single layer + inner_keep_r = [] # list of indexes for a single inner layer + for param in range(params_per_layer): + # each rotation within the layer has prob p=rot_drop_rate of being dropped + # according to that for every parameter (rotation) we sample + # if we have to drop it or not + out = jax.random.choice( + key, jnp.array(range(2)), p=jnp.array([1 - rot_drop_rate, rot_drop_rate]) + ) + key = jax.random.split(key)[0] # update the random key + + if out == 0: # if we have to keep it + inner_keep_r.append(param % n_qubits) # % is required because we work + # inner layer by inner layer + else: # if the rotation has to be dropped + inner_keep_r.append(-1) # we assign the value -1 + + if param % n_qubits == n_qubits - 1: # if it's the last qubit of the register + # append the inner layer list + keep_rot_layer.append(inner_keep_r) + # and reset it + inner_keep_r = [] + + keep_rot.append(keep_rot_layer) + + return jnp.array(keep_rot) + + +###################################################################### +# We can check the output of the ``make_dropout`` function: +# + +# setting the drop probability +layer_drop_rate, rot_drop_rate = 0.5, 0.3 # 15% probability of dropping a gate + +# JAX random key +key = jax.random.PRNGKey(12345) +# create the list of indexes, +# -1 implies we are dropping a gate +keep_rot = make_dropout(key) + +# let's just print the list for first layer +print(keep_rot[0]) + +###################################################################### +# Noisy sinusoidal function +# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# To test the effectiveness of the dropout technique, we will use a prototypical dataset +# with which it is very easy to overfit: the sinusoidal function. We produce some +# points according to the :math:`\sin` function and then we add some white Gaussian noise +# (noise that follows a normal distribution) :math:`\epsilon.` The noise is essential to obtain overfitting; +# when our model is extremely expressive, it is capable of exactly fit each point and some parameters +# become hyper-specialized in recognizing the noisy features. This makes predictions on new unseen +# data difficult, since the overfitting model did not learn the true underlying data distribution. +# The dropout technique will help in avoiding co-adaptation and hyper-specialization, +# effectively reducing overfitting. +# + +from sklearn.model_selection import train_test_split + + +def make_sin_dataset(dataset_size=100, test_size=0.4, noise_value=0.4, plot=False): + """1D regression problem y=sin(x*\pi)""" + # x-axis + x_ax = np.linspace(-1, 1, dataset_size) + y = [[np.sin(x * np.pi)] for x in x_ax] + np.random.seed(123) + # noise vector + noise = np.array([np.random.normal(0, 0.5, 1) for i in y]) * noise_value + X = np.array(x_ax) + y = np.array(y + noise) # apply noise + + # split the dataset + X_train, X_test, y_train, y_test = train_test_split( + X, y, test_size=test_size, random_state=40, shuffle=True + ) + + X_train = X_train.reshape(-1, 1) + X_test = X_test.reshape(-1, 1) + + y_train = y_train.reshape(-1, 1) + y_test = y_test.reshape(-1, 1) + + return X_train, X_test, y_train, y_test + + +from matplotlib import ticker + +X, X_test, y, y_test = make_sin_dataset(dataset_size=20, test_size=0.25) + + +fig, ax = plt.subplots() +plt.plot(X, y, "o", label="Training") +plt.plot(X_test, y_test, "o", label="Test") + +plt.plot( + np.linspace(-1, 1, 100), + [np.sin(x * np.pi) for x in np.linspace(-1, 1, 100)], + linestyle="dotted", + label=r"$\sin(x)$", +) +plt.ylabel(r"$y = \sin(\pi\cdot x) + \epsilon$") +plt.xlabel(r"$x$") +ax.xaxis.set_major_locator(ticker.MultipleLocator(0.5)) +ax.yaxis.set_major_locator(ticker.MultipleLocator(0.5)) +plt.legend() + +plt.show() + +###################################################################### +# Since our circuit is only able to provide outputs in the range :math:`[-1,1],` we rescale all the +# noisy data within this range. To do this we leverage the `MinMaxScaler` from `sklearn`. +# It is common practice to fit the scaler only from training data and then apply it also to the +# test. The reason behind this is that in general one only has knowledge about the training dataset. +# (If the training dataset is not exhaustively representative of the underlying distribution, +# this preprocessing may lead to some outliers in the test set to be scaled out of the desired range.) +# + +from sklearn.preprocessing import MinMaxScaler + +scaler = MinMaxScaler(feature_range=(-1, 1)) +y = scaler.fit_transform(y) +y_test = scaler.transform(y_test) + +# reshaping for computation +y = y.reshape(-1,) +y_test = y_test.reshape(-1,) + +###################################################################### +# Optimization +# ~~~~~~~~~~~~ +# +# At this point we have to set the hyperparameters of the optimization, namely the number of epochs, the +# learning rate, and the optimizer: +# + +import optax # optimization using jax + +epochs = 700 +optimizer = optax.adam(learning_rate=0.01) + +###################################################################### +# We define the cost function as the Mean Square Error: +# + + +@jax.jit +def calculate_mse_cost(X, y, theta, keep_rot): + yp = qnn(X, theta, keep_rot) + # depending on your version of Pennylane you may require the following line + ##### + yp = jnp.array(yp).T + ##### + cost = jnp.mean((yp - y) ** 2) + + return cost + + +# Optimization update step +@jax.jit +def optimizer_update(opt_state, params, x, y, keep_rot): + loss, grads = jax.value_and_grad(lambda theta: calculate_mse_cost(x, y, theta, keep_rot))( + params + ) + updates, opt_state = optimizer.update(grads, opt_state) + + params = optax.apply_updates(params, updates) + return params, opt_state, loss + + +###################################################################### +# Training the model +# ------------------ +# +# And now we can try to train the model. We execute different runs of the training to understand the +# average behaviour of quantum dropout. To see the effect of dropout we can set different values of +# ``layer_drop_rate`` and ``rot_drop_rate``: +# + +n_run = 3 +drop_rates = [(0.0, 0.0), (0.3, 0.2), (0.7, 0.7)] + +train_history = {} +test_history = {} +opt_params = {} + + +for layer_drop_rate, rot_drop_rate in drop_rates: + # initialization of some lists to store data + costs_per_comb = [] + test_costs_per_comb = [] + opt_params_per_comb = [] + # we execute multiple runs in order to see the average behaviour + for tmp_seed in range(seed, seed + n_run): + key = jax.random.PRNGKey(tmp_seed) + assert len(X.shape) == 2 # X must be a matrix + assert len(y.shape) == 1 # y must be an array + assert X.shape[0] == y.shape[0] # compatibility check + + # parameters initialization with gaussian ditribution + initial_params = jax.random.normal(key, shape=(layers * params_per_layer,)) + # update the random key + key = jax.random.split(key)[0] + + params = jnp.copy(initial_params) + + # optimizer initialization + opt_state = optimizer.init(initial_params) + + # lists for saving single run training and test cost trend + costs = [] + test_costs = [] + + for epoch in range(epochs): + # generate the list for dropout + keep_rot = make_dropout(key) + # update the random key + key = jax.random.split(key)[0] + + # optimization step + params, opt_state, cost = optimizer_update(opt_state, params, X, y, keep_rot) + + ############## performance evaluation ############# + # inference is done with the original model + # with all the gates + keep_rot = jnp.array( + [ + [list(range((n_qubits))) for j in range(1, inner_layers + 1)] + for i in range(layers) + ] + ) + # inference on train set + cost = calculate_mse_cost(X, y, params, keep_rot) + + costs.append(cost) + + # inference on test set + test_cost = calculate_mse_cost(X_test, y_test, params, keep_rot) + test_costs.append(test_cost) + + # we print updates every 5 iterations + if epoch % 5 == 0: + print( + f"{layer_drop_rate:.1f}-{rot_drop_rate:.1f}", + f"run {tmp_seed-seed} - epoch {epoch}/{epochs}", + f"--- Train cost:{cost:.5f}", + f"--- Test cost:{test_cost:.5f}", + end="\r", + ) + + costs_per_comb.append(costs) + test_costs_per_comb.append(test_costs) + opt_params_per_comb.append(params) + print() + costs_per_comb = np.array(costs_per_comb) + test_costs_per_comb = np.array(test_costs_per_comb) + opt_params_per_comb = np.array(opt_params_per_comb) + + train_history[(layer_drop_rate, rot_drop_rate)] = costs_per_comb + test_history[(layer_drop_rate, rot_drop_rate)] = test_costs_per_comb + opt_params[(layer_drop_rate, rot_drop_rate)] = opt_params_per_comb + +###################################################################### +# Performance evaluation +# ---------------------- +# +# Let’s compare the difference in performance with a plot: +# + +fig, axs = plt.subplots(1, 2, figsize=(12, 4)) +plt.subplots_adjust(wspace=0.05) +axs[0].set_title("MSE train") +for k, v in train_history.items(): + train_losses = np.array(v) + mean_train_history = np.mean(train_losses, axis=0) + std_train_history = np.std(train_losses, axis=0,) + + mean_train_history = mean_train_history.reshape((epochs,)) + std_train_history = std_train_history.reshape((epochs,)) + + # shadow standard deviation + axs[0].fill_between( + range(epochs), + mean_train_history - std_train_history, + mean_train_history + std_train_history, + alpha=0.2, + ) + # average trend + axs[0].plot(range(epochs), mean_train_history, label=f"{k}") # Avg Loss + +axs[1].set_title("MSE test") +for k, v in test_history.items(): + test_losses = np.array(v) + mean_test_history = np.mean(test_losses, axis=0) + std_test_history = np.std(test_losses, axis=0,) + + mean_test_history = mean_test_history.reshape((epochs,)) + std_test_history = std_test_history.reshape((epochs,)) + + # shadow standard deviation + axs[1].fill_between( + range(epochs), + mean_test_history - std_test_history, + mean_test_history + std_test_history, + alpha=0.2, + ) + # averange trend + axs[1].plot(range(epochs), mean_test_history, label=f"{k}") # Avg Loss + +axs[0].legend(loc="upper center", bbox_to_anchor=(1.01, 1.25), ncol=4, fancybox=True, shadow=True) + +for ax in axs.flat: + ax.set_xlabel("Epochs") + ax.set_ylabel("MSE") + ax.set_yscale("log") + ax.set_ylim([1e-3, 0.6]) + ax.label_outer() + +plt.subplots_adjust(bottom=0.3) + +plt.show() + +###################################################################### +# On the left you can see that without dropout there is a deep minimization of the training loss, +# moderate values of dropout converge, whereas high drop probabilities impede any learning. On +# the right, we can see the difference in generalization during the optimization process. Standard +# training without dropout initially reaches a low value of generalization error, but as the +# model starts to learn the noise in the training data (overfitting), the generalization error grows +# back. Oppositely, moderate values of dropout enable generalization errors comparable to the respective +# training ones. As the learning is not successful for elevated drop probabilities, the generalization +# error is huge. It is interesting to notice that the “not-learning” error is very close to the final +# error of the QNN trained without dropout. +# +# Hence, one can conclude that low values of dropout greatly improve the generalization performance of +# the model and remove overfitting, even if the randomness of the technique inevitably makes the +# training a little noisy. On the other hand, high drop probabilities only hinder the training +# process. +# +# Validation +# ~~~~~~~~~~ +# +# To validate the technique we can also check how the model predicts in the whole :math:`[-1,1]` range +# with and without quantum dropout. +# + +X, X_test, y, y_test = make_sin_dataset(dataset_size=20, test_size=0.25) + +# spanning the whole range +x_ax = jnp.linspace(-1, 1, 100).reshape(100, 1) + +# selecting which run we want to plot +run = 1 + +fig, ax = plt.subplots() +styles = ["dashed", "-.", "solid", "-."] +for i, k in enumerate(train_history.keys()): + if k[0] == 0.3: + alpha = 1 + else: + alpha = 0.5 + # predicting and rescaling + yp = scaler.inverse_transform(qnn(x_ax, opt_params[k][run], keep_rot).reshape(-1, 1)) + plt.plot([[i] for i in np.linspace(-1, 1, 100)], yp, label=k, alpha=alpha, linestyle=styles[i]) + +plt.scatter(X, y, label="Training", zorder=10) +plt.scatter(X_test, y_test, label="Test", zorder=10) + +ylabel = r"$y = \sin(\pi\cdot x) + \epsilon$" +plt.xlabel("x", fontsize="medium") +plt.ylabel(ylabel, fontsize="medium") +plt.legend() +ax.xaxis.set_major_locator(ticker.MultipleLocator(0.5)) +ax.yaxis.set_major_locator(ticker.MultipleLocator(0.5)) + +plt.show() + +###################################################################### +# The model without dropout overfits the noisy data by trying to exactly predict each of them, +# whereas dropout actually mitigates overfitting and makes the approximation of the underlying sinusoidal +# function way smoother. +# +# Conclusion +# ---------------------- +# In this demo, we explained the basic idea behind quantum dropout and +# how to avoid overfitting by randomly "dropping" some rotation gates +# of a QNN during the training phase. We invite you to check out the paper [#dropout]_ +# for more dropout techniques and additional analysis. Try it yourself and develop new +# dropout strategies. +# +# +# References +# ---------- +# +# .. [#dropout] Scala, F., Ceschini, A., Panella, M., & Gerace, D. (2023). +# *A General Approach to Dropout in Quantum Neural Networks*. +# `Adv. Quantum Technol., 2300220 `__. +# +# .. [#Hinton2012] Hinton, G., Srivastava, N., Krizhevsky, A., Sutskever, I., & Salakhutdinov, R. (2012). +# *Improving neural networks by preventing co-adaptation of feature detectors*. +# `arXiv:1207.0580. `__. +# +# .. [#Srivastava2014] Srivastava, N., Hinton, G., Krizhevsky, A., Sutskever, I., & Salakhutdinov, R. (2014). +# *Dropout: A Simple Way to Prevent Neural Networks from Overfitting*. +# `Journal of Machine Learning Research, 15(56):1929−1958. `__. +# +# .. [#Kiani2020] Kiani,B. T., Lloyd, S., & Maity, R. (2020). +# *Learning Unitaries by Gradient Descent*. +# `arXiv: 2001.11897. `__. +# +# .. [#Larocca2023] Larocca, M., Ju, N., García-Martín, D., Coles, P. J., & Cerezo, M. (2023). +# *Theory of overparametrization in quantum neural networks*. +# `Nat. Comp. Science, 3, 542–551. `__. +# +# About the author +# ---------------- diff --git a/demonstrations/tutorial_quantum_natural_gradient.py b/demonstrations/tutorial_quantum_natural_gradient.py index f693b3d3aa..d65bd9e2d5 100644 --- a/demonstrations/tutorial_quantum_natural_gradient.py +++ b/demonstrations/tutorial_quantum_natural_gradient.py @@ -1,499 +1,499 @@ -r""" - -.. _quantum_natural_gradient: - -Quantum natural gradient -======================== - -.. meta:: - :property="og:description": The quantum natural gradient method can achieve faster convergence for quantum machine - learning problems by taking into account the intrinsic geometry of qubits. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qng_optimization.png - -.. related:: - - tutorial_backprop Quantum gradients with backpropagation - tutorial_vqe_qng Accelerating VQE with quantum natural gradient - -*Author: Josh Izaac — Posted: 11 October 2019. Last updated: 25 January 2021.* - -This example demonstrates the quantum natural gradient optimization technique -for variational quantum circuits, originally proposed in -`Stokes et al. (2019) `__. - -Background ----------- - -The most successful class of quantum algorithms for use on near-term noisy quantum hardware -is the so-called variational quantum algorithm. As laid out in the -`Concepts section `__, in variational quantum algorithms -a low-depth parametrized quantum circuit ansatz is chosen, and a problem-specific -observable measured. A classical optimization loop is then used to find -the set of quantum parameters that *minimize* a particular measurement expectation value -of the quantum device. Examples of such algorithms include the :doc:`variational quantum -eigensolver (VQE) `, the -`quantum approximate optimization algorithm (QAOA) `__, -and :ref:`quantum neural networks (QNN) `. - -Most recent demonstrations -of variational quantum algorithms have used gradient-free classical optimization -methods, such as the Nelder-Mead algorithm. However, the parameter-shift rule -(as implemented in PennyLane) allows the user to automatically compute -analytic gradients of quantum circuits. This opens up the possibility to train -quantum computing hardware using gradient descent—the same method used to train -deep learning models. -Though one caveat has surfaced with gradient descent — how do we choose the optimal -step size for our variational quantum algorithms, to ensure successful and -efficient optimization? - -The natural gradient -^^^^^^^^^^^^^^^^^^^^ - -In standard gradient descent, each optimization step is given by - -.. math:: \theta_{t+1} = \theta_t -\eta \nabla \mathcal{L}(\theta), - -where :math:`\mathcal{L}(\theta)` is the cost as a function of -the parameters :math:`\theta,` and :math:`\eta` is the learning rate -or step size. In essence, each optimization step calculates the -steepest descent direction around the local value of :math:`\theta_t` -in the parameter space, and updates :math:`\theta_t\rightarrow \theta_{t+1}` -by this vector. - -The problem with the above approach is that each optimization step -is strongly connected to a *Euclidean geometry* on the parameter space. -The parametrization is not unique, and different parametrizations can distort -distances within the optimization landscape. - -For example, consider the following cost function :math:`\mathcal{L},` parametrized -using two different coordinate systems, :math:`(\theta_0, \theta_1),` and -:math:`(\phi_0, \phi_1):` - -| - -.. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng7.png - :align: center - :width: 90% - :target: javascript:void(0) - -| - -Performing gradient descent in the :math:`(\theta_0, \theta_1)` parameter -space, we are updating each parameter by the same Euclidean distance, -and not taking into account the fact that the cost function might vary at a different -rate with respect to each parameter. - -Instead, if we perform a change of coordinate system (re-parametrization) -of the cost function, we might find a parameter space where variations in :math:`\mathcal{L}` -are similar across different parameters. This is the case with the new parametrization -:math:`(\phi_0, \phi_1);` the cost function is unchanged, -but we now have a nicer geometry in which to perform gradient descent, and a more -informative stepsize. This leads to faster convergence, and can help avoid optimization -becoming stuck in local minima. For a more in-depth explanation, -including why the parameter space might not be best represented by a Euclidean space, -see `Yamamoto (2019) `__. - -However, what if we avoid gradient descent in the parameter space altogether? -If we instead consider the optimization problem as a -probability distribution of possible output values given an input -(i.e., `maximum likelihood estimation `_), -a better approach is to perform the gradient descent in the *distribution space*, which is -dimensionless and invariant with respect to the parametrization. As a result, -each optimization step will always choose the optimum step-size for every -parameter, regardless of the parametrization. - -In classical neural networks, the above process is known as -*natural gradient descent*, and was first introduced by -`Amari (1998) `__. -The standard gradient descent is modified as follows: - -.. math:: \theta_{t+1} = \theta_t - \eta F^{-1}\nabla \mathcal{L}(\theta), - -where :math:`F` is the `Fisher information matrix `__. -The Fisher information matrix acts as a metric tensor, transforming the -steepest descent in the Euclidean parameter space to the steepest descent in the -distribution space. - -The quantum analog -^^^^^^^^^^^^^^^^^^ - -In a similar vein, it has been shown that the standard Euclidean geometry -is sub-optimal for optimization of quantum variational algorithms -`(Harrow and Napp, 2019) `__. -The space of quantum states instead possesses a unique invariant metric -tensor known as the Fubini-Study metric tensor :math:`g_{ij},` which can be used to -construct a quantum analog to natural gradient descent: - -.. math:: \theta_{t+1} = \theta_t - \eta g^{+}(\theta_t)\nabla \mathcal{L}(\theta), - -where :math:`g^{+}` refers to the pseudo-inverse. - -.. note:: - - It can be shown that the Fubini-Study metric tensor reduces - to the Fisher information matrix in the classical limit. - - Furthermore, in the limit where :math:`\eta\rightarrow 0,` - the dynamics of the system are equivalent to imaginary-time - evolution within the variational subspace, as proposed in - `McArdle et al. (2018) `__. - -""" - -############################################################################## -# Block-diagonal metric tensor -# ---------------------------- -# -# A block-diagonal approximation to the Fubini-Study metric tensor -# of a variational quantum circuit can be evaluated on quantum hardware. -# -# Consider a variational quantum circuit -# -# .. math:: -# -# U(\mathbf{\theta})|\psi_0\rangle = V_L(\theta_L) W_L V_{L-1}(\theta_{L-1}) W_{L-1} -# \cdots V_{\ell}(\theta_{\ell}) W_{\ell} \cdots V_{0}(\theta_{0}) W_{0} |\psi_0\rangle -# -# where -# -# * :math:`|\psi_0\rangle` is the initial state, -# * :math:`W_\ell` are layers of non-parametrized quantum gates, -# * :math:`V_\ell(\theta_\ell)` are layers of parametrized quantum gates -# with :math:`n_\ell` parameters :math:`\theta_\ell = \{\theta^{(\ell)}_0, \dots, \theta^{(\ell)}_n\}.` -# -# Further, assume all parametrized gates can be written in the form -# :math:`X(\theta^{(\ell)}_{i}) = e^{i\theta^{(\ell)}_{i} K^{(\ell)}_i},` -# where :math:`K^{(\ell)}_i` is the *generator* of the parametrized operation. -# -# For each parametric layer :math:`\ell` in the variational quantum circuit -# the :math:`n_\ell\times n_\ell` block-diagonal submatrix -# of the Fubini-Study tensor :math:`g_{ij}^{(\ell)}` is calculated by: -# -# .. math:: -# -# g_{ij}^{(\ell)} = \langle \psi_{\ell-1} | K_i K_j | \psi_{\ell-1} \rangle -# - \langle \psi_{\ell-1} | K_i | \psi_{\ell-1}\rangle -# \langle \psi_{\ell-1} |K_j | \psi_{\ell-1}\rangle -# -# where -# -# .. math:: -# -# | \psi_{\ell-1}\rangle = V_{\ell-1}(\theta_{\ell-1}) W_{\ell-1} \cdots V_{0}(\theta_{0}) W_{0} |\psi_0\rangle. -# -# (that is, :math:`|\psi_{\ell-1}\rangle` is the quantum state prior to the application -# of parameterized layer :math:`\ell`), and we have :math:`K_i \equiv K_i^{(\ell)}` for brevity. -# -# Let's consider a small variational quantum circuit example coded in PennyLane: - -import numpy as np -import pennylane as qml -from pennylane import numpy as pnp - -dev = qml.device("lightning.qubit", wires=3) - - -@qml.qnode(dev, interface="autograd", diff_method="parameter-shift") -def circuit(params): - # |psi_0>: state preparation - qml.RY(np.pi / 4, wires=0) - qml.RY(np.pi / 3, wires=1) - qml.RY(np.pi / 7, wires=2) - - # V0(theta0, theta1): Parametrized layer 0 - qml.RZ(params[0], wires=0) - qml.RZ(params[1], wires=1) - - # W1: non-parametrized gates - qml.CNOT(wires=[0, 1]) - qml.CNOT(wires=[1, 2]) - - # V_1(theta2, theta3): Parametrized layer 1 - qml.RY(params[2], wires=1) - qml.RX(params[3], wires=2) - - # W2: non-parametrized gates - qml.CNOT(wires=[0, 1]) - qml.CNOT(wires=[1, 2]) - - return qml.expval(qml.PauliY(0)) - -# Use pennylane.numpy for trainable parameters -params = pnp.array([0.432, -0.123, 0.543, 0.233]) - -############################################################################## -# The above circuit consists of 4 parameters, with two distinct parametrized -# layers of 2 parameters each. -# -# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng1.png -# :align: center -# :width: 90% -# :target: javascript:void(0) -# -# | -# -# (Note that in this example, the first non-parametrized layer :math:`W_0` -# is simply the identity.) Since there are two layers, each with two parameters, -# the block-diagonal approximation consists of two -# :math:`2\times 2` matrices, :math:`g^{(0)}` and :math:`g^{(1)}.` -# -# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng2.png -# :align: center -# :width: 30% -# :target: javascript:void(0) -# -# To compute the first block-diagonal :math:`g^{(0)},` we create subcircuits consisting -# of all gates prior to the layer, and observables corresponding to -# the *generators* of the gates in the layer: -# -# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng3.png -# :align: center -# :width: 30% -# :target: javascript:void(0) - -g0 = np.zeros([2, 2]) - - -def layer0_subcircuit(params): - """This function contains all gates that - precede parametrized layer 0""" - qml.RY(np.pi / 4, wires=0) - qml.RY(np.pi / 3, wires=1) - qml.RY(np.pi / 7, wires=2) - - -############################################################################## -# We then post-process the measurement results in order to determine :math:`g^{(0)},` -# as follows. -# -# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng4.png -# :align: center -# :width: 50% -# :target: javascript:void(0) -# -# We can see that the diagonal terms are simply given by the variance: - - -@qml.qnode(dev, interface="autograd") -def layer0_diag(params): - layer0_subcircuit(params) - return qml.var(qml.PauliZ(0)), qml.var(qml.PauliZ(1)) - - -# calculate the diagonal terms -varK0, varK1 = layer0_diag(params) -g0[0, 0] = varK0 / 4 -g0[1, 1] = varK1 / 4 - -############################################################################## -# The following two subcircuits are then used to calculate the -# off-diagonal covariance terms of :math:`g^{(0)}:` - - -@qml.qnode(dev, interface="autograd") -def layer0_off_diag_single(params): - layer0_subcircuit(params) - return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)) - - -@qml.qnode(dev, interface="autograd") -def layer0_off_diag_double(params): - layer0_subcircuit(params) - ZZ = np.kron(np.diag([1, -1]), np.diag([1, -1])) - return qml.expval(qml.Hermitian(ZZ, wires=[0, 1])) - - -# calculate the off-diagonal terms -exK0, exK1 = layer0_off_diag_single(params) -exK0K1 = layer0_off_diag_double(params) - -g0[0, 1] = (exK0K1 - exK0 * exK1) / 4 -g0[1, 0] = (exK0K1 - exK0 * exK1) / 4 - -############################################################################## -# Note that, by definition, the block-diagonal matrices must be real and -# symmetric. -# -# We can repeat the above process to compute :math:`g^{(1)}.` The subcircuit -# required is given by -# -# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng8.png -# :align: center -# :width: 50% -# :target: javascript:void(0) - -g1 = np.zeros([2, 2]) - - -def layer1_subcircuit(params): - """This function contains all gates that - precede parametrized layer 1""" - # |psi_0>: state preparation - qml.RY(np.pi / 4, wires=0) - qml.RY(np.pi / 3, wires=1) - qml.RY(np.pi / 7, wires=2) - - # V0(theta0, theta1): Parametrized layer 0 - qml.RZ(params[0], wires=0) - qml.RZ(params[1], wires=1) - - # W1: non-parametrized gates - qml.CNOT(wires=[0, 1]) - qml.CNOT(wires=[1, 2]) - - -############################################################################## -# Using this subcircuit, we can now generate the submatrix :math:`g^{(1)}.` -# -# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng5.png -# :align: center -# :width: 50% -# :target: javascript:void(0) - - -@qml.qnode(dev, interface="autograd") -def layer1_diag(params): - layer1_subcircuit(params) - return qml.var(qml.PauliY(1)), qml.var(qml.PauliX(2)) - - -############################################################################## -# As previously, the diagonal terms are simply given by the variance, - -varK0, varK1 = layer1_diag(params) -g1[0, 0] = varK0 / 4 -g1[1, 1] = varK1 / 4 - - -############################################################################## -# while the off-diagonal terms require covariance between the two -# observables to be computed. - - -@qml.qnode(dev, interface="autograd") -def layer1_off_diag_single(params): - layer1_subcircuit(params) - return qml.expval(qml.PauliY(1)), qml.expval(qml.PauliX(2)) - - -@qml.qnode(dev, interface="autograd") -def layer1_off_diag_double(params): - layer1_subcircuit(params) - X = np.array([[0, 1], [1, 0]]) - Y = np.array([[0, -1j], [1j, 0]]) - YX = np.kron(Y, X) - return qml.expval(qml.Hermitian(YX, wires=[1, 2])) - - -# calculate the off-diagonal terms -exK0, exK1 = layer1_off_diag_single(params) -exK0K1 = layer1_off_diag_double(params) - -g1[0, 1] = (exK0K1 - exK0 * exK1) / 4 -g1[1, 0] = g1[0, 1] - - -############################################################################## -# Putting this altogether, the block-diagonal approximation to the Fubini-Study -# metric tensor for this variational quantum circuit is -from scipy.linalg import block_diag - -g = block_diag(g0, g1) -print(np.round(g, 8)) - - -############################################################################## -# PennyLane contains a built-in function for computing the Fubini-Study metric -# tensor, :func:`~.pennylane.metric_tensor`, which -# we can use to verify this result: -print(np.round(qml.metric_tensor(circuit, approx="block-diag")(params), 8)) - -############################################################################## -# As opposed to our manual computation, which required 6 different quantum -# evaluations, the PennyLane Fubini-Study metric tensor implementation -# requires only 2 quantum evaluations, one per layer. This is done by -# automatically detecting the layer structure, and noting that every -# observable that must be measured commutes, allowing for simultaneous measurement. -# -# Therefore, by combining the quantum natural gradient optimizer with the analytic -# parameter-shift rule to optimize a variational circuit with :math:`d` parameters -# and :math:`L` parametrized layers, a total of :math:`2d+L` quantum evaluations -# are required per optimization step. -# -# Note that the :func:`~.pennylane.metric_tensor` function also supports computing the diagonal -# approximation to the metric tensor: -print(qml.metric_tensor(circuit, approx='diag')(params)) - -############################################################################## -# Furthermore, the returned metric tensor is **full differentiable**; include it -# in your cost function, and train or optimize its value! - -############################################################################## -# Quantum natural gradient optimization -# ------------------------------------- -# -# PennyLane provides an implementation of the quantum natural gradient -# optimizer, :class:`~.pennylane.QNGOptimizer`. Let's compare the optimization convergence -# of the QNG Optimizer and the :class:`~.pennylane.GradientDescentOptimizer` for the simple variational -# circuit above. - -steps = 200 -init_params = pnp.array([0.432, -0.123, 0.543, 0.233], requires_grad=True) - -############################################################################## -# Performing vanilla gradient descent: - -gd_cost = [] -opt = qml.GradientDescentOptimizer(0.01) - -theta = init_params -for _ in range(steps): - theta = opt.step(circuit, theta) - gd_cost.append(circuit(theta)) - -############################################################################## -# Performing quantum natural gradient descent: - -qng_cost = [] -opt = qml.QNGOptimizer(0.01) - -theta = init_params -for _ in range(steps): - theta = opt.step(circuit, theta) - qng_cost.append(circuit(theta)) - - -############################################################################## -# Plotting the cost vs optimization step for both optimization strategies: -from matplotlib import pyplot as plt - -plt.style.use("seaborn") -plt.plot(gd_cost, "b", label="Vanilla gradient descent") -plt.plot(qng_cost, "g", label="Quantum natural gradient descent") - -plt.ylabel("Cost function value") -plt.xlabel("Optimization steps") -plt.legend() -plt.show() - -############################################################################## -# References -# ---------- -# -# 1. Shun-Ichi Amari. "Natural gradient works efficiently in learning." -# `Neural computation 10.2, 251-276 `__, 1998. -# -# 2. James Stokes, Josh Izaac, Nathan Killoran, Giuseppe Carleo. -# "Quantum Natural Gradient." `arXiv:1909.02108 `__, 2019. -# -# 3. Aram Harrow and John Napp. "Low-depth gradient measurements can improve -# convergence in variational hybrid quantum-classical algorithms." -# `arXiv:1901.05374 `__, 2019. -# -# 4. Naoki Yamamoto. "On the natural gradient for variational quantum eigensolver." -# `arXiv:1909.05074 `__, 2019. -# -# -# About the author -# ---------------- +r""" + +.. _quantum_natural_gradient: + +Quantum natural gradient +======================== + +.. meta:: + :property="og:description": The quantum natural gradient method can achieve faster convergence for quantum machine + learning problems by taking into account the intrinsic geometry of qubits. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qng_optimization.png + +.. related:: + + tutorial_backprop Quantum gradients with backpropagation + tutorial_vqe_qng Accelerating VQE with quantum natural gradient + +*Author: Josh Izaac — Posted: 11 October 2019. Last updated: 25 January 2021.* + +This example demonstrates the quantum natural gradient optimization technique +for variational quantum circuits, originally proposed in +`Stokes et al. (2019) `__. + +Background +---------- + +The most successful class of quantum algorithms for use on near-term noisy quantum hardware +is the so-called variational quantum algorithm. As laid out in the +`Concepts section `__, in variational quantum algorithms +a low-depth parametrized quantum circuit ansatz is chosen, and a problem-specific +observable measured. A classical optimization loop is then used to find +the set of quantum parameters that *minimize* a particular measurement expectation value +of the quantum device. Examples of such algorithms include the :doc:`variational quantum +eigensolver (VQE) `, the +`quantum approximate optimization algorithm (QAOA) `__, +and :ref:`quantum neural networks (QNN) `. + +Most recent demonstrations +of variational quantum algorithms have used gradient-free classical optimization +methods, such as the Nelder-Mead algorithm. However, the parameter-shift rule +(as implemented in PennyLane) allows the user to automatically compute +analytic gradients of quantum circuits. This opens up the possibility to train +quantum computing hardware using gradient descent—the same method used to train +deep learning models. +Though one caveat has surfaced with gradient descent — how do we choose the optimal +step size for our variational quantum algorithms, to ensure successful and +efficient optimization? + +The natural gradient +^^^^^^^^^^^^^^^^^^^^ + +In standard gradient descent, each optimization step is given by + +.. math:: \theta_{t+1} = \theta_t -\eta \nabla \mathcal{L}(\theta), + +where :math:`\mathcal{L}(\theta)` is the cost as a function of +the parameters :math:`\theta,` and :math:`\eta` is the learning rate +or step size. In essence, each optimization step calculates the +steepest descent direction around the local value of :math:`\theta_t` +in the parameter space, and updates :math:`\theta_t\rightarrow \theta_{t+1}` +by this vector. + +The problem with the above approach is that each optimization step +is strongly connected to a *Euclidean geometry* on the parameter space. +The parametrization is not unique, and different parametrizations can distort +distances within the optimization landscape. + +For example, consider the following cost function :math:`\mathcal{L},` parametrized +using two different coordinate systems, :math:`(\theta_0, \theta_1),` and +:math:`(\phi_0, \phi_1):` + +| + +.. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng7.png + :align: center + :width: 90% + :target: javascript:void(0) + +| + +Performing gradient descent in the :math:`(\theta_0, \theta_1)` parameter +space, we are updating each parameter by the same Euclidean distance, +and not taking into account the fact that the cost function might vary at a different +rate with respect to each parameter. + +Instead, if we perform a change of coordinate system (re-parametrization) +of the cost function, we might find a parameter space where variations in :math:`\mathcal{L}` +are similar across different parameters. This is the case with the new parametrization +:math:`(\phi_0, \phi_1);` the cost function is unchanged, +but we now have a nicer geometry in which to perform gradient descent, and a more +informative stepsize. This leads to faster convergence, and can help avoid optimization +becoming stuck in local minima. For a more in-depth explanation, +including why the parameter space might not be best represented by a Euclidean space, +see `Yamamoto (2019) `__. + +However, what if we avoid gradient descent in the parameter space altogether? +If we instead consider the optimization problem as a +probability distribution of possible output values given an input +(i.e., `maximum likelihood estimation `_), +a better approach is to perform the gradient descent in the *distribution space*, which is +dimensionless and invariant with respect to the parametrization. As a result, +each optimization step will always choose the optimum step-size for every +parameter, regardless of the parametrization. + +In classical neural networks, the above process is known as +*natural gradient descent*, and was first introduced by +`Amari (1998) `__. +The standard gradient descent is modified as follows: + +.. math:: \theta_{t+1} = \theta_t - \eta F^{-1}\nabla \mathcal{L}(\theta), + +where :math:`F` is the `Fisher information matrix `__. +The Fisher information matrix acts as a metric tensor, transforming the +steepest descent in the Euclidean parameter space to the steepest descent in the +distribution space. + +The quantum analog +^^^^^^^^^^^^^^^^^^ + +In a similar vein, it has been shown that the standard Euclidean geometry +is sub-optimal for optimization of quantum variational algorithms +`(Harrow and Napp, 2019) `__. +The space of quantum states instead possesses a unique invariant metric +tensor known as the Fubini-Study metric tensor :math:`g_{ij},` which can be used to +construct a quantum analog to natural gradient descent: + +.. math:: \theta_{t+1} = \theta_t - \eta g^{+}(\theta_t)\nabla \mathcal{L}(\theta), + +where :math:`g^{+}` refers to the pseudo-inverse. + +.. note:: + + It can be shown that the Fubini-Study metric tensor reduces + to the Fisher information matrix in the classical limit. + + Furthermore, in the limit where :math:`\eta\rightarrow 0,` + the dynamics of the system are equivalent to imaginary-time + evolution within the variational subspace, as proposed in + `McArdle et al. (2018) `__. + +""" + +############################################################################## +# Block-diagonal metric tensor +# ---------------------------- +# +# A block-diagonal approximation to the Fubini-Study metric tensor +# of a variational quantum circuit can be evaluated on quantum hardware. +# +# Consider a variational quantum circuit +# +# .. math:: +# +# U(\mathbf{\theta})|\psi_0\rangle = V_L(\theta_L) W_L V_{L-1}(\theta_{L-1}) W_{L-1} +# \cdots V_{\ell}(\theta_{\ell}) W_{\ell} \cdots V_{0}(\theta_{0}) W_{0} |\psi_0\rangle +# +# where +# +# * :math:`|\psi_0\rangle` is the initial state, +# * :math:`W_\ell` are layers of non-parametrized quantum gates, +# * :math:`V_\ell(\theta_\ell)` are layers of parametrized quantum gates +# with :math:`n_\ell` parameters :math:`\theta_\ell = \{\theta^{(\ell)}_0, \dots, \theta^{(\ell)}_n\}.` +# +# Further, assume all parametrized gates can be written in the form +# :math:`X(\theta^{(\ell)}_{i}) = e^{i\theta^{(\ell)}_{i} K^{(\ell)}_i},` +# where :math:`K^{(\ell)}_i` is the *generator* of the parametrized operation. +# +# For each parametric layer :math:`\ell` in the variational quantum circuit +# the :math:`n_\ell\times n_\ell` block-diagonal submatrix +# of the Fubini-Study tensor :math:`g_{ij}^{(\ell)}` is calculated by: +# +# .. math:: +# +# g_{ij}^{(\ell)} = \langle \psi_{\ell-1} | K_i K_j | \psi_{\ell-1} \rangle +# - \langle \psi_{\ell-1} | K_i | \psi_{\ell-1}\rangle +# \langle \psi_{\ell-1} |K_j | \psi_{\ell-1}\rangle +# +# where +# +# .. math:: +# +# | \psi_{\ell-1}\rangle = V_{\ell-1}(\theta_{\ell-1}) W_{\ell-1} \cdots V_{0}(\theta_{0}) W_{0} |\psi_0\rangle. +# +# (that is, :math:`|\psi_{\ell-1}\rangle` is the quantum state prior to the application +# of parameterized layer :math:`\ell`), and we have :math:`K_i \equiv K_i^{(\ell)}` for brevity. +# +# Let's consider a small variational quantum circuit example coded in PennyLane: + +import numpy as np +import pennylane as qml +from pennylane import numpy as pnp + +dev = qml.device("lightning.qubit", wires=3) + + +@qml.qnode(dev, interface="autograd", diff_method="parameter-shift") +def circuit(params): + # |psi_0>: state preparation + qml.RY(np.pi / 4, wires=0) + qml.RY(np.pi / 3, wires=1) + qml.RY(np.pi / 7, wires=2) + + # V0(theta0, theta1): Parametrized layer 0 + qml.RZ(params[0], wires=0) + qml.RZ(params[1], wires=1) + + # W1: non-parametrized gates + qml.CNOT(wires=[0, 1]) + qml.CNOT(wires=[1, 2]) + + # V_1(theta2, theta3): Parametrized layer 1 + qml.RY(params[2], wires=1) + qml.RX(params[3], wires=2) + + # W2: non-parametrized gates + qml.CNOT(wires=[0, 1]) + qml.CNOT(wires=[1, 2]) + + return qml.expval(qml.PauliY(0)) + +# Use pennylane.numpy for trainable parameters +params = pnp.array([0.432, -0.123, 0.543, 0.233]) + +############################################################################## +# The above circuit consists of 4 parameters, with two distinct parametrized +# layers of 2 parameters each. +# +# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng1.png +# :align: center +# :width: 90% +# :target: javascript:void(0) +# +# | +# +# (Note that in this example, the first non-parametrized layer :math:`W_0` +# is simply the identity.) Since there are two layers, each with two parameters, +# the block-diagonal approximation consists of two +# :math:`2\times 2` matrices, :math:`g^{(0)}` and :math:`g^{(1)}.` +# +# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng2.png +# :align: center +# :width: 30% +# :target: javascript:void(0) +# +# To compute the first block-diagonal :math:`g^{(0)},` we create subcircuits consisting +# of all gates prior to the layer, and observables corresponding to +# the *generators* of the gates in the layer: +# +# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng3.png +# :align: center +# :width: 30% +# :target: javascript:void(0) + +g0 = np.zeros([2, 2]) + + +def layer0_subcircuit(params): + """This function contains all gates that + precede parametrized layer 0""" + qml.RY(np.pi / 4, wires=0) + qml.RY(np.pi / 3, wires=1) + qml.RY(np.pi / 7, wires=2) + + +############################################################################## +# We then post-process the measurement results in order to determine :math:`g^{(0)},` +# as follows. +# +# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng4.png +# :align: center +# :width: 50% +# :target: javascript:void(0) +# +# We can see that the diagonal terms are simply given by the variance: + + +@qml.qnode(dev, interface="autograd") +def layer0_diag(params): + layer0_subcircuit(params) + return qml.var(qml.PauliZ(0)), qml.var(qml.PauliZ(1)) + + +# calculate the diagonal terms +varK0, varK1 = layer0_diag(params) +g0[0, 0] = varK0 / 4 +g0[1, 1] = varK1 / 4 + +############################################################################## +# The following two subcircuits are then used to calculate the +# off-diagonal covariance terms of :math:`g^{(0)}:` + + +@qml.qnode(dev, interface="autograd") +def layer0_off_diag_single(params): + layer0_subcircuit(params) + return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)) + + +@qml.qnode(dev, interface="autograd") +def layer0_off_diag_double(params): + layer0_subcircuit(params) + ZZ = np.kron(np.diag([1, -1]), np.diag([1, -1])) + return qml.expval(qml.Hermitian(ZZ, wires=[0, 1])) + + +# calculate the off-diagonal terms +exK0, exK1 = layer0_off_diag_single(params) +exK0K1 = layer0_off_diag_double(params) + +g0[0, 1] = (exK0K1 - exK0 * exK1) / 4 +g0[1, 0] = (exK0K1 - exK0 * exK1) / 4 + +############################################################################## +# Note that, by definition, the block-diagonal matrices must be real and +# symmetric. +# +# We can repeat the above process to compute :math:`g^{(1)}.` The subcircuit +# required is given by +# +# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng8.png +# :align: center +# :width: 50% +# :target: javascript:void(0) + +g1 = np.zeros([2, 2]) + + +def layer1_subcircuit(params): + """This function contains all gates that + precede parametrized layer 1""" + # |psi_0>: state preparation + qml.RY(np.pi / 4, wires=0) + qml.RY(np.pi / 3, wires=1) + qml.RY(np.pi / 7, wires=2) + + # V0(theta0, theta1): Parametrized layer 0 + qml.RZ(params[0], wires=0) + qml.RZ(params[1], wires=1) + + # W1: non-parametrized gates + qml.CNOT(wires=[0, 1]) + qml.CNOT(wires=[1, 2]) + + +############################################################################## +# Using this subcircuit, we can now generate the submatrix :math:`g^{(1)}.` +# +# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng5.png +# :align: center +# :width: 50% +# :target: javascript:void(0) + + +@qml.qnode(dev, interface="autograd") +def layer1_diag(params): + layer1_subcircuit(params) + return qml.var(qml.PauliY(1)), qml.var(qml.PauliX(2)) + + +############################################################################## +# As previously, the diagonal terms are simply given by the variance, + +varK0, varK1 = layer1_diag(params) +g1[0, 0] = varK0 / 4 +g1[1, 1] = varK1 / 4 + + +############################################################################## +# while the off-diagonal terms require covariance between the two +# observables to be computed. + + +@qml.qnode(dev, interface="autograd") +def layer1_off_diag_single(params): + layer1_subcircuit(params) + return qml.expval(qml.PauliY(1)), qml.expval(qml.PauliX(2)) + + +@qml.qnode(dev, interface="autograd") +def layer1_off_diag_double(params): + layer1_subcircuit(params) + X = np.array([[0, 1], [1, 0]]) + Y = np.array([[0, -1j], [1j, 0]]) + YX = np.kron(Y, X) + return qml.expval(qml.Hermitian(YX, wires=[1, 2])) + + +# calculate the off-diagonal terms +exK0, exK1 = layer1_off_diag_single(params) +exK0K1 = layer1_off_diag_double(params) + +g1[0, 1] = (exK0K1 - exK0 * exK1) / 4 +g1[1, 0] = g1[0, 1] + + +############################################################################## +# Putting this altogether, the block-diagonal approximation to the Fubini-Study +# metric tensor for this variational quantum circuit is +from scipy.linalg import block_diag + +g = block_diag(g0, g1) +print(np.round(g, 8)) + + +############################################################################## +# PennyLane contains a built-in function for computing the Fubini-Study metric +# tensor, :func:`~.pennylane.metric_tensor`, which +# we can use to verify this result: +print(np.round(qml.metric_tensor(circuit, approx="block-diag")(params), 8)) + +############################################################################## +# As opposed to our manual computation, which required 6 different quantum +# evaluations, the PennyLane Fubini-Study metric tensor implementation +# requires only 2 quantum evaluations, one per layer. This is done by +# automatically detecting the layer structure, and noting that every +# observable that must be measured commutes, allowing for simultaneous measurement. +# +# Therefore, by combining the quantum natural gradient optimizer with the analytic +# parameter-shift rule to optimize a variational circuit with :math:`d` parameters +# and :math:`L` parametrized layers, a total of :math:`2d+L` quantum evaluations +# are required per optimization step. +# +# Note that the :func:`~.pennylane.metric_tensor` function also supports computing the diagonal +# approximation to the metric tensor: +print(qml.metric_tensor(circuit, approx='diag')(params)) + +############################################################################## +# Furthermore, the returned metric tensor is **full differentiable**; include it +# in your cost function, and train or optimize its value! + +############################################################################## +# Quantum natural gradient optimization +# ------------------------------------- +# +# PennyLane provides an implementation of the quantum natural gradient +# optimizer, :class:`~.pennylane.QNGOptimizer`. Let's compare the optimization convergence +# of the QNG Optimizer and the :class:`~.pennylane.GradientDescentOptimizer` for the simple variational +# circuit above. + +steps = 200 +init_params = pnp.array([0.432, -0.123, 0.543, 0.233], requires_grad=True) + +############################################################################## +# Performing vanilla gradient descent: + +gd_cost = [] +opt = qml.GradientDescentOptimizer(0.01) + +theta = init_params +for _ in range(steps): + theta = opt.step(circuit, theta) + gd_cost.append(circuit(theta)) + +############################################################################## +# Performing quantum natural gradient descent: + +qng_cost = [] +opt = qml.QNGOptimizer(0.01) + +theta = init_params +for _ in range(steps): + theta = opt.step(circuit, theta) + qng_cost.append(circuit(theta)) + + +############################################################################## +# Plotting the cost vs optimization step for both optimization strategies: +from matplotlib import pyplot as plt + +plt.style.use("seaborn") +plt.plot(gd_cost, "b", label="Vanilla gradient descent") +plt.plot(qng_cost, "g", label="Quantum natural gradient descent") + +plt.ylabel("Cost function value") +plt.xlabel("Optimization steps") +plt.legend() +plt.show() + +############################################################################## +# References +# ---------- +# +# 1. Shun-Ichi Amari. "Natural gradient works efficiently in learning." +# `Neural computation 10.2, 251-276 `__, 1998. +# +# 2. James Stokes, Josh Izaac, Nathan Killoran, Giuseppe Carleo. +# "Quantum Natural Gradient." `arXiv:1909.02108 `__, 2019. +# +# 3. Aram Harrow and John Napp. "Low-depth gradient measurements can improve +# convergence in variational hybrid quantum-classical algorithms." +# `arXiv:1901.05374 `__, 2019. +# +# 4. Naoki Yamamoto. "On the natural gradient for variational quantum eigensolver." +# `arXiv:1909.05074 `__, 2019. +# +# +# About the author +# ---------------- # \ No newline at end of file diff --git a/demonstrations/tutorial_quantum_phase_transitions.metadata.json b/demonstrations/tutorial_quantum_phase_transitions.metadata.json new file mode 100644 index 0000000000..5dd1804b01 --- /dev/null +++ b/demonstrations/tutorial_quantum_phase_transitions.metadata.json @@ -0,0 +1,138 @@ +{ + "title": "Seeing Quantum Phase Transitions with Quantum Computers", + "authors": [ + { + "username": "Damian_Pope" + }, + { + "username": "Tirth_Shah" + } + ], + "dateOfPublication": "", + "dateOfLastModification": "", + "categories": ["Optimization","Quantum Machine Learning"], + "tags": ["quantum phase transition", "Ising model"], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_3qubit_Ising_model_PyTorch.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_3qubit_Ising_model_PyTorch.png" + } + ], + "seoDescription": "Learn how to simulate and observe quantum phase transitions on a quantum computer", + "doi": "", + "references": [ + { + "id": "Vojta2002", + "type": "chapter-book", + "title": "Computational Statistical Physics", + "authors": "Thomas Vojta", + "year": "2002", + "doi": "", + "url": "https://link.springer.com/book/10.1007/978-3-662-04804-7" + }, + { + "id": "Mazumdar2019", + "type": "article-journal", + "title": "Cosmic phase transitions: their applications and experimental signatures", + "authors": ["Anupam Mazumdar","Graham White"], + "year": "2019", + "doi": "", + "url": "https://iopscience.iop.org/article/10.1088/1361-6633/ab1f55" + }, + { + "id": "Mueller2023", + "type": "article-journal", + "title": "Quantum Computation of Dynamical Quantum Phase Transitions and Entanglement Tomography in a Lattice Gauge Theory", + "authors": ["Niklas Mueller","Joseph A. Carolan","Andrew Connelly","Zohreh Davoudi","Eugene F. Dumitrescu","Kübra Yeter-Aydeniz"], + "year": "2023", + "doi": "", + "url": "https://journals.aps.org/prxquantum/abstract/10.1103/PRXQuantum.4.030323" + }, + { + "id": "Smith2019", + "type": "preprint", + "title": "Quantum Computation of Dynamical Quantum Phase Transitions and Entanglement Tomography in a Lattice Gauge Theory", + "authors": ["Adam Smith","Bernhard Jobst","Andrew G. Green","Frank Pollmann"], + "year": "2019", + "doi": "", + "url": "https://arxiv.org/abs/1910.05351" + }, + { + "id": "Haghshenas2024", + "type": "preprint", + "title": "Probing critical states of matter on a digital quantum computer", + "authors": ["Reza Haghshenas","Eli Chertkov","Matthew DeCross","Thomas M. Gatterman","Justin A. Gerber","Kevin Gilmore","Dan Gresh","Nathan Hewitt","Chandler V. Horst","Mitchell Matheny","Tanner Mengle","Brian Neyenhuis","David Hayes","Michael Foss-Feig"], + "year": "2024", + "doi": "", + "url": "https://arxiv.org/abs/2305.01650" + }, + { + "id": "Chertkov2022", + "type": "preprint", + "title": "Characterizing a non-equilibrium phase transition on a quantum computer", + "authors": ["Eli Chertkov","Zihan Cheng","Andrew C. Potter","Sarang Gopalakrishnan","Thomas M. Gatterman", "Justin A. Gerber","Kevin Gilmore","Dan Gresh","Alex Hall","Aaron Hankin","Mitchell Matheny","Tanner Mengle","David Hayes","Brian Neyenhuis","Russell Stutz","Michael Foss-Feig"], + "year": "2022", + "doi": "", + "url": "https://arxiv.org/abs/2305.01650" + }, + { + "id": "Thompson2023", + "type": "preprint", + "title": "Quantum Computation of Phase Transition in Interacting Scalar Quantum Field Theory", + "authors": ["Shane Thompson","George Siopsis"], + "year": "2023", + "doi": "", + "url": "https://arxiv.org/abs/2303.02425" + }, + { + "id": "Vodeb2025", + "type": "article-journal", + "title": "Stirring the false vacuum via interacting quantized bubbles on a 5,564-qubit quantum annealer", + "authors": ["Jaka Vodeb","Jean-Yves Desaules","Andrew Hallam","Andrea Rava","Gregor Humar","Dennis Willsch","Fengping Jin","Madita Willsch","Kristel Michielsen","Zlatko Papić"], + "year": "2024", + "doi": "", + "url": "https://www.nature.com/articles/s41567-024-02765-w" + }, + { + "id": "Kandala2017", + "type": "preprint", + "title": "Hardware-efficient Variational Quantum Eigensolver for Small Molecules and Quantum Magnets", + "authors": ["Abhinav Kandala","Antonio Mezzacapo","Kristan Temme","Maika Takita","Markus Brink","Jerry M. Chow","Jay M. Gambetta"], + "year": "2017", + "doi": "", + "url": "https://arxiv.org/abs/1704.05018" + }, + { + "id": "Blote2002", + "type": "article-journal", + "title": "Cluster Monte Carlo simulation of the transverse Ising model", + "authors": ["Henk W. J. Blote","Youjin Deng"], + "year": "2002", + "doi": "", + "url": "https://journals.aps.org/pre/abstract/10.1103/PhysRevE.66.066110" + }, + { + "id": "Hashizume2022", + "type": "article-journal", + "title": "Dynamical phase transitions in the two-dimensional transverse-field Ising model", + "authors": ["Tomohiro Hashizume","Ian P. McCulloch","Jad C. Halimeh"], + "year": "2022", + "doi": "", + "url": "https://journals.aps.org/prresearch/abstract/10.1103/PhysRevResearch.4.013250" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_isingmodel_PyTorch", + "weight": 1.0 + } + ], + "hardware": [] +} \ No newline at end of file diff --git a/demonstrations/tutorial_quantum_phase_transitions.py b/demonstrations/tutorial_quantum_phase_transitions.py new file mode 100644 index 0000000000..3703eb3aa3 --- /dev/null +++ b/demonstrations/tutorial_quantum_phase_transitions.py @@ -0,0 +1,1156 @@ +r""" +Seeing Phase Transitions with Quantum Computers +=============================================== + + +By Damian Pope and Tirth Shah + + + +Introduction +------------ + + +This tutorial introduces three quantum phase transitions related to condensed matter physics. It walks through how to simulate them on a quantum computer. The phase transitions it covers involve: + + + +.. raw:: html + + + +
    + + + +.. raw:: html + + + +
  • + + + +the 1D quantum Ising model + + + +.. raw:: html + + + +
  • + + + +.. raw:: html + + + +
  • + + + +the 2D quantum Ising model + + + +.. raw:: html + + + +
  • + + + +.. raw:: html + + + +
  • + + + +`dynamical quantum phase transitions`_ (i.e., phase transitions in time evolution) + + + +.. raw:: html + + + +
  • + + + +.. raw:: html + + + +
+ + + + +A phase transition happens when there's an abrupt change in some property of a system. For example, when liquid water freezes and turns into ice. Phase transitions are important to many areas of physics including: + + + +.. raw:: html + + + +
    + + + +.. raw:: html + + + +
  • + + + +condensed matter physics, e.g., [#Vojta2002]_ + + + +.. raw:: html + + + +
  • + + + +.. raw:: html + + + +
  • + + + +cosmology, e.g., [#Mazumdar2019]_ + + + +.. raw:: html + + + +
  • + + + +.. raw:: html + + + +
  • + + + +high-energy physics, e.g., [#Mueller2023]_ + + + +.. raw:: html + + + +
  • + + + +.. raw:: html + + + +
+ + + +They're important as they can: + + + +.. raw:: html + + + +
    + + + +.. raw:: html + + + +
  • + + + +help us find new quantum states of matter + + + +.. raw:: html + + + +
  • + + + +.. raw:: html + + + +
  • + + + +help to shed light on entanglement and long-range correlations in quantum systems + + + +.. raw:: html + + + +
  • + + + +.. raw:: html + + + +
  • + + + +help us understand the behaviour of many different quantum systems at the same time (due to the + +property of universality) + + + +.. raw:: html + + + +
  • + + + +.. raw:: html + + + +
+ + + +Note: *Quantum* phase transitions are different from *classical* phase transitions. Classical phase + +transitions are caused by thermal fluctuations. Quantum phase transitions can occur at zero + +temperature and are caused by quantum fluctuations (i.e., Heisenberg's uncertainty principle). + +.. raw:: html + +
+ +Phase transitions can be hard to study analytically. Due to discontinuities, mathematical models can break + +down. Phase transitions have been widely studied numerically with classical computers. However, in + +some cases, the amount of computational resources needed is prohibitive. But there's another way + +to study phase transitions: using a quantum computer. Potentially, they can compute aspects of phase + +transitions more efficiently than any conventional technique. + + + +To date, quantum computers have been used to study quantum phase transitions related to: + + + +.. raw:: html + + + +
    + + + +.. raw:: html + + + +
  • + + + +the early universe and high-energy particle colliders [#Mueller2023]_ + + + +.. raw:: html + + + +
  • + + + +.. raw:: html + + + +
  • + + + +a topological transition in an Ising-like model [#Smith2019]_ + + + +.. raw:: html + + + +
  • + + + +.. raw:: html + + + +
  • + + + +the transverse Ising model [#Haghshenas2024]_ + + + +.. raw:: html + + + +
  • + + + +.. raw:: html + + + +
  • + + + +noisy quantum systems [#Chertkov2022]_ + + + +.. raw:: html + + + +
  • + + + +.. raw:: html + + + +
  • + + + +scalar quantum field theory [#Thompson2023]_ + + + +.. raw:: html + + + +
  • + + + +.. raw:: html + + + +
  • + + + +the evolution of the universe [#Vodeb2025]_ + + + +.. raw:: html + + + +
  • + + + +.. raw:: html + + + +
+ + + +Note: This tutorial focuses on the *quantum* Ising model. It complements existing content on this + +model: + +`3-qubit Ising model in PyTorch `_ + +`Transverse-field Ising model `_ + +`Ising Uprising Challenge `_ + +`How to Solve a QUBO problem `_ + +`Quadratic Unconstrained Binary Optimization (QUBO) `_ + +`Quantum Dataset How to build spin Hamiltonians `_ + + + +What is the Ising model? +------------------------ + + + +The simplest Ising model consists of :math:`N` qubits arranged along a line. + +""" + +############################################################################## +# .. figure:: ../_static/demonstration_assets/quantum_phase_transitions/Fig_1_Ising_chain.png +# :align: center +# :width: 50% + +###################################################################### +# Each qubit interacts with the qubits on either side of it. For example, the second qubit interacts with the first and third qubits. + +###################################################################### +# The system’s Hamiltonian is +# +# .. math:: +# \begin{equation} +# H = -J \,\, \Sigma_{i=1}^{N-1} \sigma_{z}^{(i)} \sigma^{(i+1)}_{z} +# \end{equation} +# +# where +# +# .. math:: +# \sigma_{z}^{(i)} = \left[ {\begin{array}{cc} +# 1 & 0 \\ +# 0 & -1 \\ +# \end{array} } \right] +# +# is the Pauli Z operator for the :math:`i^{th}` qubit and :math:`J` is the interaction strength +# between neighbouring qubits. +# +# The code below creates this Hamiltonian: +# +import pennylane as qml + +from pennylane import numpy as np + +N = 3 +J = 2 +wires = range(N) + +dev = qml.device("lightning.qubit", wires=N) + +coeffs = [-J] * (N - 1) + +obs = [] +for i in range(N - 1): + obs.append(qml.Z(i) @ qml.Z(i + 1)) +H = qml.Hamiltonian(coeffs, obs) + +print(f"H={H}") + +###################################################################### +# Why is the Ising Model Important? +# --------------------------------- +# At first glance, the Ising model looks like it's simple and unrealistic. However, it correctly +# models many properties of real-world magnets. Also, its simplicity allows us to actually solve it. +# You can think of the Ising model as a sandbox to play in and quickly learn about the essence of +# various complex real-world phenomena. +# +# The Ising model exhibits a wide range of interesting emergent properties, such as phase transitions. +# One calculation that gives us insight into their behaviour is finding the ground state of the Ising +# model and seeing how it changes as the interactions change. Often, we're looking to see if a phase +# transition happens. +# +# Let's look at an example. + + +###################################################################### +# Seeing Phase Transitions with Quantum Computers +# ----------------------------------------------- +# To do this, we'll use the well-known variational quantum eigensolver (VQE) algorithm to find the +# ground state. You can find an introduction to it `here `_. +# +# Let's start by finding the ground state of the Ising model for a fixed value of :math:`J`. +# We'll use the well-known Hardware Efficient Ansatz (HEA) [#Kandala2017]_ to do this. It's a +# general-purpose ansatz that efficiently represents a wide range of quantum states. It consists of: +# +# 1. Applying three single-qubit rotations to each qubit. Each one is parameterized by a different rotation angle. +# +# 2. Applying a CNOT gate to each neighbouring pair of qubits. +# +# 3. Applying three single-qubit rotations to each qubit. Again, each one is parameterized by a different rotation angle. + + + +import random + +random.seed(a=10) + +# params is an array that stores the parameter values of the statevector that we use in VQE. +# Generate some initial random angle values. +params = np.array([2 * np.pi * random.uniform(0, 1)] * (6 * N), requires_grad=True) + +# create an ansatz using the hardware efficiency ansatz (HEA) +def create_ansatz(params, N): + # STEP 1: perform single-qubit rotations on all the qubits + for i in range(N): + qml.RZ(phi=params[i], wires=i) + qml.RX(phi=params[N + i], wires=i) + qml.RZ(phi=params[2 * N + i], wires=i) + + # STEP 2: perform a CNOT gate on each pair of neighbouring qubits + for i in range(N - 1): + qml.CNOT(wires=[i, i + 1]) + + # STEP 3: perform single-qubit rotations on all the qubits + for i in range(N): + qml.RZ(phi=params[3 * N + i], wires=i) + qml.RX(phi=params[4 * N + i], wires=i) + qml.RZ(phi=params[5 * N + i], wires=i) + +@qml.qnode(dev) +def quantum_circuit(params): + # Create a quantum state using params + create_ansatz(params, N) + return qml.expval(H) + +max_iters = 200 +tolerance = 1e-04 + +# create an optimizer +opt = qml.GradientDescentOptimizer(stepsize=0.1) + +# energy is a list that stores all the estimates for the ground-state energy +energy = [] + +# execute the VQE optimization loop +for i in range(max_iters): + params, prev_energy = opt.step_and_cost(quantum_circuit, params) + energy.append(prev_energy) + + if i > 1: + if np.abs(energy[-2] - energy[-1]) < tolerance: + break + +# graph the energy as a function of the number of iterations +import matplotlib.pyplot as plt + +plt.plot(list(range(len(energy))), energy) +plt.xlabel("Iteration") +plt.ylabel("Energy") +plt.show() + +###################################################################### +# The graph above shows that the energy :math:`E` gradually decreases until it reaches :math:`E = - 4`. +# To check that this result makes sense, let's think about the Hamiltonian. Consider the first term, :math:`-2 * Z(0) @ Z(1)`. +# When the first and second qubits are in the computational basis state +# :math:`| 0 \rangle` , the product :math:`Z(0) @ Z(1)` is :math:`(+1)(+1) = +1`. Multiplying this by :math:`J = -2` +# gives an energy of -2. The second term :math:`-2 * Z(1) @ Z(2)` also gives :math:`E = -2`. Combining +# these results gives :math:`E = -2 -2 = -4`. When all the qubits are in the other basis state +# (:math:`| 1 \rangle`), we also get :math:`E = -4`. These two calculations agree with the numerical result from VQE. So far, so good. +# +# +# +# Let's now introduce an extra energy term that's proportional to the sum of all the Pauli X operators: +# +# .. math:: +# - h_{x}\Sigma_{i=1}^{N} \sigma_{x}^{(i)} +# +# If our qubits are actually spin-1/2 particles (e.g., electrons), :math:`h_{x}` is a horizontal +# magnetic field. Often, it's called a :math:`{\it transverse}` :math:`{\it field}`. +# + +############################################################################## +# .. figure:: ../_static/demonstration_assets/quantum_phase_transitions/Fig_2_transverse_Ising.png +# :align: center +# :width: 50% +# + +###################################################################### +# The system's Hamiltonian becomes +# +# .. math:: +# H = -J \,\, \Sigma_{i=1}^{N-1} \sigma_{z}^{(i)} \sigma^{(i+1)}_{z} - h_{x}\Sigma_{i=1}^{N} \sigma_{x}^{(i)} +# +# A quantum phase transitions happens when we change the ratio :math:`J/h_x`. Physically, this +# corresponds to changing the relative strengths of the coupling interaction and the horizontal +# magnetic field. When :math:`J` is much larger than :math:`h_{x}`, the ground state corresponds to +# all the spins (i.e., the qubits) being aligned vertically (parallel to the :math:`z` axis). +# + +############################################################################## +# .. figure:: ../_static/demonstration_assets/quantum_phase_transitions/Fig_3_ground_state_J_large.png +# :align: center +# :width: 75% +# + +###################################################################### +# But, when :math:`h_{x}` is much greater than :math:`J`, the ground state corresponds to +# all the spins being aligned along the :math:`x` axis parallel to the magnetic field: +# + +############################################################################## +# .. figure:: ../_static/demonstration_assets/quantum_phase_transitions/Fig_4_ground_state_h_large.png +# :align: center +# :width: 75% +# + +###################################################################### +# When :math:`J/h_{x} = 1`, the ground state suddenly switches from the first state (all +# vertical) to the second one (all horizontal). This is a quantum phase transition. The interplay +# between :math:`J` and :math:`h_{x}` is like a tug of war. The coupling constant :math:`J` tries to +# align all the qubits vertically, in the computational basis. The magnetic field :math:`h_{x}` tries +# to align them horizontally, in the Pauli X basis. Depending on value of :math:`J/h_{x}`, one of the two constants +# will dominate. +# +# To see the phase transition, let's introduce the total magnetization observable :math:`M` of all +# the qubits: +# +# .. math:: +# M =\frac{1}{N} \Sigma_{i} \sigma_{Z}^{(i)} +# +# It's just the sum of all the Pauli :math:`Z` operators, scaled by the number of qubits. For example, +# for the state :math:`| \psi \rangle = |0 \rangle |0\rangle`, +# :math:`M = \frac{1}{2} \left( 1 + 1 \right) = 1`. The total magnetization tracks the phase change as +# follows: +# +# - When :math:`h_{x} \gg J`, :math:`M = 0` as each qubit is in an equal superposition of :math:`|0 \rangle` and :math:`|1 \rangle`. +# +# - When :math:`J \gg h_{x}`, :math:`| M | = 1` as the qubits are either all in :math:`|0 \rangle` or all in :math:`|1 \rangle`. +# + +############################################################################## +# Let's now calculate :math:`M` for a range of :math:`J/h_{x}` values. +# +N = 5 +wires = range(N) + +# h_x is the strength of the transverse magnetic field +h_x = 1 + +# Vary the value of the coupling constant J in order to see a phase transition as we change J/h_x +J_list = [0.0, 0.25, 0.75, 0.9, 1.0, 1.1, 2.0, 5.0, 7.5] + +# This variable stores the values of the magnetization observable M for different values of J/h_x +magnetization_list = [] + +dev_2 = qml.device("lightning.qubit", wires=N) + +# This function prepares an estimate of the ground state & calculates its energy. +@qml.qnode(dev_2) +def quantum_circuit_2(params): + # Generate an estimate of the ground state + create_ansatz(params, N) + return qml.expval(H) + +# A function that returns the magnetization operator of N qubits. +def magnetization_op(N): + total_op = qml.PauliZ(0) + + if N > 1: + for i in range(1, N): + total_op = total_op + qml.PauliZ(i) + + return total_op / N + +#Prepare a parameterized state & return the value of the magnetization operator. +@qml.qnode(dev_2) +def calculate_magnetization(params): + create_ansatz(params, N) + return qml.expval(magnetization_op(N)) + +# Loop through all the different values of J +for i in range(len(J_list)): + + # Build the Hamiltonian + + # Add Pauli Z-Pauli Z interaction terms to the Hamiltonian + coeffs = [-J_list[i]] * (N - 1) + + obs = [] + for j in range(N - 1): + obs.append(qml.Z(j) @ qml.Z(j + 1)) + + # Add Pauli X terms to the Hamiltonian + for j in range(N): + obs.append(qml.X(j)) + coeffs.append(-h_x) + + H = qml.Hamiltonian(coeffs, obs) + + params = np.array([2 * np.pi * random.uniform(0, 1)] * (6 * N), requires_grad=True) + + max_iters = 200 + tolerance = 1e-04 + + # create an optimizer + opt = qml.MomentumOptimizer(stepsize=0.02, momentum=0.9) + + energy = [] + + # Run the VQE optimization loop + for j in range(max_iters): + params, prev_energy = opt.step_and_cost(quantum_circuit_2, params) + energy.append(prev_energy) + + if j > 1: + if np.abs(energy[-2] - energy[-1]) < tolerance: + break + + magnetization_list.append(calculate_magnetization(params)) + +############################################################################## +# Now that we've calculated :math:`M`, let's plot the results. +# + +# Plot |magnetization| versus J +plt.plot(J_list, np.abs(magnetization_list), marker="x") +plt.xlabel("J") +plt.ylabel("|Magnetization|") +plt.title("|Magnetization| vs. J for N=" + str(N)) +plt.show() + +###################################################################### +#Notice how the magnetization increases sharply around :math:`J/h_{x} = 1`. This suggests that a phase transition is happening. +#(It's also well known that a phase transition does happen at this value.) Why the graph doesn't have a sharp and discontinuous increase at +#exactly :math:`J/h_x=1`? There are two reasons: +# +#- Like all other numerical results, this result is just approximate. +#- The phase transition happens at :math:`J/h_x=1` in the asymptotic limit of large :math:`N`, i.e., as the number of qubits goes to infinity. You can see this by plotting how :math:`M` changes for three different values of :math:`N`, :math:`N = 4, 5, 6`. +# + +# magnetization values for N = 4 +magnetization_4 = [ + 0.01705303, + -0.05617393, + 0.34882499, + 0.38068118, + 0.74856645, + 0.90577316, + 0.9872206, +] +J_list_4 = [0.0, 0.25, 0.75, 0.9, 1.1, 2.0, 5.0] + +# magnetization values for N = 6 +magnetization_6 = [ + -0.11958867, + 0.00284093, + 0.01237123, + 0.00255386, + 0.81125517, + 0.92437233, + 0.99013448, +] +J_list_6 = J_list_4[:] + +# Plot |M| for multiple N values versus J +plt.plot(J_list_4, np.abs(magnetization_4), "xk-", label="N=4") +plt.plot(J_list[0:8], np.abs(magnetization_list[0:8]), "xb--", label="N=5") +plt.plot(J_list_6, np.abs(magnetization_6), "sg:", label="N=6") + +plt.xlabel("J") +plt.ylabel("|Magnetization|") +plt.title("|Magnetization| vs. J for N=4, 5, 6") +plt.legend(loc="lower right") +plt.show() + +###################################################################### +# Notice how the increase in :math:`|M|` gets steeper as we increase :math:`N`. You can +# think of this as showing that we're getting closer and closer to the asymptotic behaviour of a truly +# discontinuous phase transition. +# + +###################################################################### +# Two-dimensional Ising Model +# --------------------------- +# In the 2D quantum Ising model, the qubits are arranged in a 2D grid. +# + +###################################################################### +# .. figure:: ../_static/demonstration_assets/quantum_phase_transitions/Fig_5_2D_Ising_model.png +# :align: center +# :width: 25% + +############################################################################## +# Compared to the 1D model, it's richer, harder to solve mathematically, and harder to simulate on +# classical computers. It's also more realistic and is used by physicists to study +# low-dimensional quantum systems. In this section, we'll explore phase transitions +# in the 2D quantum Ising model. The Hamiltonian for the model is +# +# .. math:: +# H = -J \,\, \Sigma_{\langle i,j \rangle} \sigma_{z}^{(i)} \sigma^{(j)}_{z} - h_{x} \Sigma_{ i } \sigma_{x}^{(i)} +# +# The expression :math:`\langle i,j \rangle` includes every pair of neighbouring qubits in the lattice. The +# :math:`\Sigma_{i}` term sums over every qubit in the lattice. The code below creates the Hamiltonian +# using `PennyLane's spin module `_. +# + +N = 2 + +H = qml.spin.transverse_ising(lattice="square", n_cells=[N, N], h=1.0, boundary_condition=True) + +print(f"H={H}") + +###################################################################### +# Like we did for the 1D model, let's find the ground state using VQE. +# + +wires_2D = range(N**2) +dev_2D = qml.device("lightning.qubit", wires=wires_2D) + +random.seed(a=10) + +# generate random parameter values for the initial statevector +params = np.array([2 * np.pi * random.uniform(0, 1)] * (6 * N), requires_grad=True) + +@qml.qnode(dev_2D) +def quantum_circuit_2D(params): + create_ansatz(params, N) + return qml.expval(H) + +max_iters = 500 +tolerance = 3e-04 + +# create an optimizer +opt = qml.GradientDescentOptimizer(stepsize=0.015) + +energy = [] + +# execute the optimization loop +for i in range(max_iters): + params, prev_energy = opt.step_and_cost(quantum_circuit_2D, params) + energy.append(prev_energy) + + if i > 1: + if np.abs(energy[-2] - energy[-1]) < tolerance: + break + +# print out the results +plt.plot(list(range(len(energy))), energy) +plt.xlabel("Iteration") +plt.ylabel("Energy") +plt.show() + +############################################################################## +# The energy in the graph approaches -4.5, which makes sense. The Hamiltonian has four two-qubit interaction terms and the +# smallest that each one can be is -1. So, the ground-state energy must be less than -4. Let's vary the ratio :math:`J/h_{x}` again and calculate the +# magnetization :math:`M` each time. Finally, let's plot the results and see if there's a quantum +# phase transition. +# + +N = 3 +dev_2D_varying_J = qml.device("lightning.qubit", wires=N**2) + +# strength of transverse magnetic field +h_x = 1 + +# Vary J in order to see a phase transition in the magnetization as we change J/h_x +J_list = [0.035, 0.05, 0.1, 0.25, 0.375, 0.5, 0.75, 1.0, 5, 10] + +magnetization_list = [] + +# Prepare a parameterized state & calculate the value of the magnetization operator. +@qml.qnode(dev_2D_varying_J) +def calculate_magnetization_2D(params): + create_ansatz(params, N) + return qml.expval(magnetization_op(N)) + +@qml.qnode(dev_2D_varying_J) +def quantum_circuit_2D_varying_J(params): + create_ansatz(params, N) + return qml.expval(H) + +# Loop through all values of J +for i in range(len(J_list)): + H = qml.spin.transverse_ising( + lattice="square", coupling=J_list[i], n_cells=[N, N], boundary_condition=True + ) + #Set the initial values of the rotation angle parameters. + #The values below were chosen as, through trial and error, we discovered that they worked well. + params = np.zeros(6 * N, requires_grad=True) + for i in range(N): + params[i] = 0 + params[N + i] = np.pi / 2 + params[2 * N + i] = np.pi / 2 + + max_iters = 500 + + # create an optimizer + opt = qml.MomentumOptimizer(stepsize=0.03, momentum=0.9) + + energy = [] + + # execute the optimization loop + for j in range(max_iters): + params, prev_energy = opt.step_and_cost(quantum_circuit_2D_varying_J, params) + energy.append(prev_energy) + + if j > 1: + if np.abs(energy[-2] - energy[-1]) < tolerance: + break + + magnetization_list.append(calculate_magnetization_2D(params)) + +###################################################################### +# Let's plot the results. + +# Plot |magnetization| versus J +plt.plot(J_list, np.abs(magnetization_list), marker="x") +plt.xlabel("J") +plt.ylabel("|Magnetization|") +plt.title("|Magnetization| vs. J for N=" + str(N)) +plt.show() + +###################################################################### +# From the graph, it's unclear if there's a phase transition. Looking at it, multiple data points are +# bunched up on the left. To spread them out, let's change the scale by ignoring the last two +# points. +# + +plt.plot(J_list[0:8], np.abs(magnetization_list[0:8]), marker="x") +plt.xlabel("J") +plt.ylabel("|Magnetization|") +plt.title("|Magnetization| vs. J for N=" + str(N)) +plt.show() + +###################################################################### +# Like in the 1D case, the magnetization displays a rapid increase. This is consistent with a phase change +# but it's not conclusive as the increase is somewhat gradual. This is because :math:`N` is so small. +# Note that the result is consistent with where the phase change is known to occur [#Blote2002]_, [#Hashizume2022]_. +# + +############################################################################## +#.. _dynamical quantum phase transitions: +#Time Evolution & Dynamical Phase Transitions +#-------------------------------------------- +# +#Another important aspect of quantum systems is how they evolve over time. +#Sometimes, this evolution is hard to simulate on classical computers. So, researchers are +#interested in modelling it on quantum computers. Occasionally, +#some property of a quantum system changes abruptly. This is called a *dynamical quantum +#phase transition*: a phase transition that happens during the time evolution +#of a quantum system [#Heyl2013]_. +# +#To evolve the Ising model in time, we'll use the well-known Suzuki-Trotter product approximation. The code below does this. + +import math + +N = 5 +wires = range(N) + +# create the Hamiltonian for a 1D Ising model with transverse & longitudinal magnetic fields +# we do this to copy what was done in Reference 13: https://arxiv.org/abs/2008.04894 +obs = [] +for j in range(N - 1): + obs.append(qml.Z(j) @ qml.Z(j + 1)) + +# add Pauli X terms to Hamiltonian (transverse field) +for j in range(N): + obs.append(qml.X(j)) + +# add Pauli Z terms to Hamiltonian (longitudinal field) +for j in range(N): + obs.append(qml.Z(j)) + +dev = qml.device("lightning.qubit", wires=N) + +J = -0.1 + +# strength of transverse field interaction +h_x = 1 + +# strength of longitudinal field interaction +h_z = -0.15 + +J_coeffs = [-J] * (N - 1) + +X_coeffs = [h_x] * N + +Z_coeffs = [h_z] * N + +coeffs = J_coeffs + X_coeffs + Z_coeffs + +H = qml.Hamiltonian(coeffs, obs) + +# create the circuit that evolves the system in time +@qml.qnode(dev) +def time_evolution_circuit(H, T): + #Evolve the system via a sequence of short approximate Trotter time steps + #https://docs.pennylane.ai/en/stable/code/api/pennylane.TrotterProduct.html + qml.TrotterProduct(H, time=T, n=math.ceil(T / 0.1)+1, order=2) + + # return the final probabilities + return qml.probs(wires=range(N)) + + +############################################################################## +#To see if a dynamical phase transition happens, let's consider a observable called the :math:`\it{rate \; function}` +#:math:`\gamma`. It depends on the overlap between the quantum state that we start with and the final state at +#some time :math:`t`. More specifically, +# +# .. math:: +# \gamma = -\frac{1}{N} \log_{e} (|G|^{2}) +# +#where :math:`G = \langle \psi_{i} | \psi_{f}\rangle`, where :math:`| \psi_{i}\rangle` and :math:`| \psi_{f} \rangle` are the initial and final states +#respectively. As the system evolves, we'll keep calculating :math:`\gamma`. If it changes discontinuously, +#then a dynamical phase transition has happened. +# +# +#The function below calculates :math:`\gamma` at time :math:`T`. +def rate_function(H, T, N): + probability_list = time_evolution_circuit(H, T) + mag_G_squared = probability_list[0] + return -1 / N * np.log(mag_G_squared) + +###################################################################### +#Let's now calculate :math:`\gamma` at different times to see how it evolves. Finally, let's graph the value of :math:`\gamma` versus time to see if a dynamical phase transition happens. +# + +rate_function_list = [] + +# time step size for time evolution +deltaT = 0.05 + +num_time_steps = 50 + +for i in range(num_time_steps): + rate_function_list.append(rate_function(H, i * deltaT, N)) + +plt.plot(np.linspace(0, deltaT * (num_time_steps-1), num_time_steps), rate_function_list) +plt.xlabel("time") +plt.ylabel(r"Rate function, $\lambda$") +plt.title("Rate Function versus time") +plt.legend(["N=" + str(N)]) +plt.show() + +############################################################################## +# The sharp change in :math:`\Gamma` at :math:`t = 1.5` suggests that a dynamical phase transition has happened. This +# conclusion is supported by classical numerical simulations that show a phase +# transition at the same time [#Nicola2021]_. +# + +############################################################################## +#Summary +#------- +# In this demo, we have shown how you can use quantum computers to simulate quantum phase +# transitions in the 1D and 2D quantum Ising models. We've also shown how to use quantum computers to see dynamical quantum phase transitions. +# +#Acknowledgement +#--------------- +#Damian Pope would like to thank Associate Professor Matthew Johnson (Perimeter Institute for Theoretical Physics and +#York University) for insightful discussions on quantum phase transitions in cosmology and quantum computing. + +###################################################################### +#References +#------------ +# +# .. [#Vojta2002] +# T. Vojta, in K.H. Hoffmann and M. Schreiber (Eds): Computational Statistical Physics, Springer, Berlin (2002) +# +# .. [#Mazumdar2019] +# +# Anupam Mazumdar and Graham White. "Cosmic phase transitions: their applications and experimental signatures" Rep. Prog. Phys. 82, 076901, 2019 +# +# +# .. [#Mueller2023] +# +# Niklas Mueller et al. "Quantum Computation of Dynamical Quantum Phase Transitions and Entanglement Tomography in a Lattice Gauge Theory" PRX Quantum 4, 030323, 2023 +# +# +# .. [#Smith2019] +# +# Adam Smith, Bernhard Jobst, Andrew G. Green, and Frank Pollmann. "Crossing a topological phase transition with a quantum computer" `arXiv:1910.05351 [cond-mat.str-el] `__, 2019 +# +# +# .. [#Haghshenas2024] +# +# Reza Haghshenas et al. "Probing critical states of matter on a digital quantum computer" `arXiv:2305.01650 [quant-ph] `__, 2024 +# +# +# +# .. [#Chertkov2022] +# +# Eli Chertkov, et al., "Characterizing a non-equilibrium phase transition on a quantum computer", `arXiv:2209.12889 [quant-ph] `__, 2022 +# +# +# +# .. [#Thompson2023] +# +# Shane Thompson and George Siopsis. "Quantum Computation of Phase Transition in Interacting Scalar Quantum Field Theory" `arXiv:2303.02425 [quant-ph] `__, 2023 +# +# +# .. [#Vodeb2025] +# +# Jaka Vodeb et al., "Stirring the false vacuum via interacting quantized bubbles on a 5,564-qubit quantum annealer", Nature Physics, 21, 386, 2025 `https://www.nature.com/articles/s41567-024-02765-w `__ +# +# +# .. [#Kandala2017] +# +# Abhinav Kandala et al., "Hardware-efficient Variational Quantum Eigensolver for Small Molecules and Quantum Magnets", `arXiv:1704.05018 [quant-ph] `__ 2017 +# +# +# .. [#Blote2002] +# +# Henk W. J. Blöte and Youjin Deng. "Cluster Monte Carlo simulation of the transverse Ising model", Phys. Rev. E 66, 066110, 2002. (See Table II, row labelled 'square lattice'); +# +# +# .. [#Hashizume2022] +# +# Tomohiro Hashizume, Ian P. McCulloch, and Jad C. Halimeh. "Dynamical phase transitions in the two-dimensional transverse-field Ising model", Phys. Rev. Research 4, 013250, 2022 (See Figure 1 and Section II) +# +# .. [#Heyl2013] +# +# M. Heyl, A. Polkovnikov, S. Kehrein. "Dynamical Quantum Phase Transitions in the Transverse-Field Ising Model", Phys. Rev. Lett. 110 135704 (2013) +# +# .. [#Nicola2021] +# +# S. De Nicola , A. A. Michailidis , M. Serbyn. "Entanglement View of Dynamical Quantum Phase Transitions", Phys. Rev. Lett. 126 040602 (2021), Figure 1 (d) +# +############################################################################## +# About the author +# ---------------- +# + \ No newline at end of file diff --git a/demonstrations/tutorial_quantum_transfer_learning.py b/demonstrations/tutorial_quantum_transfer_learning.py index a8e584f07c..8991710e41 100644 --- a/demonstrations/tutorial_quantum_transfer_learning.py +++ b/demonstrations/tutorial_quantum_transfer_learning.py @@ -1,612 +1,612 @@ -r""" -.. _quantum_transfer_learning: - -Quantum transfer learning -========================= - -.. meta:: - :property="og:description": Combine PyTorch and PennyLane to train a hybrid quantum-classical image - classifier using transfer learning. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/transfer_images.png - -*Author: Andrea Mari — Posted: 19 December 2019. Last updated: 28 January 2021.* - -In this tutorial we apply a machine learning method, known as *transfer learning*, to an -image classifier based on a hybrid classical-quantum network. - -This example follows the general structure of the PyTorch -`tutorial on transfer learning `_ -by Sasank Chilamkurthy, with the crucial difference of using a quantum circuit to perform the -final classification task. - -More details on this topic can be found in the research paper [1] (`Mari et al. (2019) `_). - - -Introduction ------------- - -Transfer learning is a well-established technique for training artificial neural networks (see e.g., Ref. [2]), -which is based on the general intuition that if a pre-trained network is good at solving a -given problem, then, with just a bit of additional training, it can be used to also solve a different -but related problem. - -As discussed in Ref. [1], this idea can be formalized in terms of two abstract networks :math:`A` -and :math:`B,` independently from their quantum or classical physical nature. - -| - - -.. figure:: ../_static/demonstration_assets/quantum_transfer_learning/transfer_learning_general.png - :scale: 45% - :alt: transfer_general - :align: center - -| - -As sketched in the above figure, one can give the following **general definition of the -transfer learning method**: - -1. Take a network :math:`A` that has been pre-trained on a dataset :math:`D_A` and for a given - task :math:`T_A.` - -2. Remove some of the final layers. In this way, the resulting truncated network :math:`A'` - can be used as a feature extractor. - -3. Connect a new trainable network :math:`B` at the end of the pre-trained network :math:`A'.` - -4. Keep the weights of :math:`A'` constant, and train the final block :math:`B` with a - new dataset :math:`D_B` and/or for a new task of interest :math:`T_B.` - -When dealing with hybrid systems, depending on the physical nature (classical or quantum) of the -networks :math:`A` and :math:`B,` one can have different implementations of transfer learning as - -summarized in following table: - -| - -.. rst-class:: docstable - -+-----------+-----------+-----------------------------------------------------+ -| Network A | Network B | Transfer learning scheme | -+===========+===========+=====================================================+ -| Classical | Classical | CC - Standard classical method. See e.g., Ref. [2]. | -+-----------+-----------+-----------------------------------------------------+ -| Classical | Quantum | CQ - **Hybrid model presented in this tutorial.** | -+-----------+-----------+-----------------------------------------------------+ -| Quantum | Classical | QC - Model studied in Ref. [1]. | -+-----------+-----------+-----------------------------------------------------+ -| Quantum | Quantum | QQ - Model studied in Ref. [1]. | -+-----------+-----------+-----------------------------------------------------+ - -Classical-to-quantum transfer learning --------------------------------------- - -We focus on the CQ transfer learning scheme discussed in the previous section and we give a specific example. - -1. As pre-trained network :math:`A` we use **ResNet18**, a deep residual neural network introduced by - Microsoft in Ref. [3], which is pre-trained on the *ImageNet* dataset. - -2. After removing its final layer we obtain :math:`A',` a pre-processing block which maps any - input high-resolution image into 512 abstract features. - -3. Such features are classified by a 4-qubit "dressed quantum circuit" :math:`B,` i.e., a - variational quantum circuit sandwiched between two classical layers. - -4. The hybrid model is trained, keeping :math:`A'` constant, on the *Hymenoptera* dataset - (a small subclass of ImageNet) containing images of *ants* and *bees*. - -A graphical representation of the full data processing pipeline is given in the figure below. - -.. figure:: ../_static/demonstration_assets/quantum_transfer_learning/transfer_learning_c2q.png - :scale: 55% - :alt: transfer_c2q - :align: center - -""" - -############################################################################## -# General setup -# ------------------------ -# -# .. note:: -# -# To use the PyTorch interface in PennyLane, you must first -# `install PyTorch `_. -# -# In addition to *PennyLane*, we will also need some standard *PyTorch* libraries and the -# plotting library *matplotlib*. - -# Some parts of this code are based on the Python script: -# https://github.com/pytorch/tutorials/blob/master/beginner_source/transfer_learning_tutorial.py -# License: BSD - -import time -import os -import copy - -# PyTorch -import torch -import torch.nn as nn -import torch.optim as optim -from torch.optim import lr_scheduler -import torchvision -from torchvision import datasets, transforms - -# Pennylane -import pennylane as qml -from pennylane import numpy as np - -torch.manual_seed(42) -np.random.seed(42) - -# Plotting -import matplotlib.pyplot as plt - -# OpenMP: number of parallel threads. -os.environ["OMP_NUM_THREADS"] = "1" - - -############################################################################## -# Setting of the main hyper-parameters of the model -# ------------------------------------------------------------ -# -# .. note:: -# To reproduce the results of Ref. [1], ``num_epochs`` should be set to ``30`` which may take a long time. -# We suggest to first try with ``num_epochs=1`` and, if everything runs smoothly, increase it to a larger value. - - -n_qubits = 4 # Number of qubits -step = 0.0004 # Learning rate -batch_size = 4 # Number of samples for each training step -num_epochs = 3 # Number of training epochs -q_depth = 6 # Depth of the quantum circuit (number of variational layers) -gamma_lr_scheduler = 0.1 # Learning rate reduction applied every 10 epochs. -q_delta = 0.01 # Initial spread of random quantum weights -start_time = time.time() # Start of the computation timer - -############################################################################## -# We initialize a PennyLane device with a ``default.qubit`` backend. - -dev = qml.device("default.qubit", wires=n_qubits) - -############################################################################## -# We configure PyTorch to use CUDA only if available. Otherwise the CPU is used. - -device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") - -############################################################################## -# Dataset loading -# ------------------------------------------------------------ -# -# .. note:: -# The dataset containing images of *ants* and *bees* can be downloaded -# `here `_ and -# should be extracted in the subfolder ``../_data/hymenoptera_data``. -# -# This is a very small dataset (roughly 250 images), too small for training from scratch a -# classical or quantum model, however it is enough when using *transfer learning* approach. -# -# The PyTorch packages ``torchvision`` and ``torch.utils.data`` are used for loading the dataset -# and performing standard preliminary image operations: resize, center, crop, normalize, *etc.* - -data_transforms = { - "train": transforms.Compose( - [ - # transforms.RandomResizedCrop(224), # uncomment for data augmentation - # transforms.RandomHorizontalFlip(), # uncomment for data augmentation - transforms.Resize(256), - transforms.CenterCrop(224), - transforms.ToTensor(), - # Normalize input channels using mean values and standard deviations of ImageNet. - transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), - ] - ), - "val": transforms.Compose( - [ - transforms.Resize(256), - transforms.CenterCrop(224), - transforms.ToTensor(), - transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), - ] - ), -} - -data_dir = "../_data/hymenoptera_data" -image_datasets = { - x if x == "train" else "validation": datasets.ImageFolder( - os.path.join(data_dir, x), data_transforms[x] - ) - for x in ["train", "val"] -} -dataset_sizes = {x: len(image_datasets[x]) for x in ["train", "validation"]} -class_names = image_datasets["train"].classes - -# Initialize dataloader -dataloaders = { - x: torch.utils.data.DataLoader(image_datasets[x], batch_size=batch_size, shuffle=True) - for x in ["train", "validation"] -} - -# function to plot images -def imshow(inp, title=None): - """Display image from tensor.""" - inp = inp.numpy().transpose((1, 2, 0)) - # Inverse of the initial normalization operation. - mean = np.array([0.485, 0.456, 0.406]) - std = np.array([0.229, 0.224, 0.225]) - inp = std * inp + mean - inp = np.clip(inp, 0, 1) - plt.imshow(inp) - if title is not None: - plt.title(title) - - -############################################################################## -# Let us show a batch of the test data, just to have an idea of the classification problem. - -# Get a batch of training data -inputs, classes = next(iter(dataloaders["validation"])) - -# Make a grid from batch -out = torchvision.utils.make_grid(inputs) - -imshow(out, title=[class_names[x] for x in classes]) - -dataloaders = { - x: torch.utils.data.DataLoader(image_datasets[x], batch_size=batch_size, shuffle=True) - for x in ["train", "validation"] -} - - -############################################################################## -# Variational quantum circuit -# ------------------------------------ -# We first define some quantum layers that will compose the quantum circuit. - - -def H_layer(nqubits): - """Layer of single-qubit Hadamard gates. - """ - for idx in range(nqubits): - qml.Hadamard(wires=idx) - - -def RY_layer(w): - """Layer of parametrized qubit rotations around the y axis. - """ - for idx, element in enumerate(w): - qml.RY(element, wires=idx) - - -def entangling_layer(nqubits): - """Layer of CNOTs followed by another shifted layer of CNOT. - """ - # In other words it should apply something like : - # CNOT CNOT CNOT CNOT... CNOT - # CNOT CNOT CNOT... CNOT - for i in range(0, nqubits - 1, 2): # Loop over even indices: i=0,2,...N-2 - qml.CNOT(wires=[i, i + 1]) - for i in range(1, nqubits - 1, 2): # Loop over odd indices: i=1,3,...N-3 - qml.CNOT(wires=[i, i + 1]) - - -############################################################################## -# Now we define the quantum circuit through the PennyLane `qnode` decorator . -# -# The structure is that of a typical variational quantum circuit: -# -# * **Embedding layer:** All qubits are first initialized in a balanced superposition -# of *up* and *down* states, then they are rotated according to the input parameters -# (local embedding). -# -# * **Variational layers:** A sequence of trainable rotation layers and constant -# entangling layers is applied. -# -# * **Measurement layer:** For each qubit, the local expectation value of the :math:`Z` -# operator is measured. This produces a classical output vector, suitable for -# additional post-processing. - - -@qml.qnode(dev) -def quantum_net(q_input_features, q_weights_flat): - """ - The variational quantum circuit. - """ - - # Reshape weights - q_weights = q_weights_flat.reshape(q_depth, n_qubits) - - # Start from state |+> , unbiased w.r.t. |0> and |1> - H_layer(n_qubits) - - # Embed features in the quantum node - RY_layer(q_input_features) - - # Sequence of trainable variational layers - for k in range(q_depth): - entangling_layer(n_qubits) - RY_layer(q_weights[k]) - - # Expectation values in the Z basis - exp_vals = [qml.expval(qml.PauliZ(position)) for position in range(n_qubits)] - return tuple(exp_vals) - - -############################################################################## -# Dressed quantum circuit -# ------------------------ -# -# We can now define a custom ``torch.nn.Module`` representing a *dressed* quantum circuit. -# -# This is a concatenation of: -# -# * A classical pre-processing layer (``nn.Linear``). -# * A classical activation function (``torch.tanh``). -# * A constant ``np.pi/2.0`` scaling. -# * The previously defined quantum circuit (``quantum_net``). -# * A classical post-processing layer (``nn.Linear``). -# -# The input of the module is a batch of vectors with 512 real parameters (features) and -# the output is a batch of vectors with two real outputs (associated with the two classes -# of images: *ants* and *bees*). - - -class DressedQuantumNet(nn.Module): - """ - Torch module implementing the *dressed* quantum net. - """ - - def __init__(self): - """ - Definition of the *dressed* layout. - """ - - super().__init__() - self.pre_net = nn.Linear(512, n_qubits) - self.q_params = nn.Parameter(q_delta * torch.randn(q_depth * n_qubits)) - self.post_net = nn.Linear(n_qubits, 2) - - def forward(self, input_features): - """ - Defining how tensors are supposed to move through the *dressed* quantum - net. - """ - - # obtain the input features for the quantum circuit - # by reducing the feature dimension from 512 to 4 - pre_out = self.pre_net(input_features) - q_in = torch.tanh(pre_out) * np.pi / 2.0 - - # Apply the quantum circuit to each element of the batch and append to q_out - q_out = torch.Tensor(0, n_qubits) - q_out = q_out.to(device) - for elem in q_in: - q_out_elem = torch.hstack(quantum_net(elem, self.q_params)).float().unsqueeze(0) - q_out = torch.cat((q_out, q_out_elem)) - - # return the two-dimensional prediction from the postprocessing layer - return self.post_net(q_out) - - -############################################################################## -# Hybrid classical-quantum model -# ------------------------------------ -# -# We are finally ready to build our full hybrid classical-quantum network. -# We follow the *transfer learning* approach: -# -# 1. First load the classical pre-trained network *ResNet18* from the ``torchvision.models`` zoo. -# 2. Freeze all the weights since they should not be trained. -# 3. Replace the last fully connected layer with our trainable dressed quantum circuit (``DressedQuantumNet``). -# -# .. note:: -# The *ResNet18* model is automatically downloaded by PyTorch and it may take several minutes (only the first time). -# - -weights = torchvision.models.ResNet18_Weights.IMAGENET1K_V1 -model_hybrid = torchvision.models.resnet18(weights=weights) - -for param in model_hybrid.parameters(): - param.requires_grad = False - - -# Notice that model_hybrid.fc is the last layer of ResNet18 -model_hybrid.fc = DressedQuantumNet() - -# Use CUDA or CPU according to the "device" object. -model_hybrid = model_hybrid.to(device) - -############################################################################## -# Training and results -# ------------------------ -# -# Before training the network we need to specify the *loss* function. -# -# We use, as usual in classification problem, the *cross-entropy* which is -# directly available within ``torch.nn``. - - -criterion = nn.CrossEntropyLoss() - -############################################################################## -# We also initialize the *Adam optimizer* which is called at each training step -# in order to update the weights of the model. - - -optimizer_hybrid = optim.Adam(model_hybrid.fc.parameters(), lr=step) - -############################################################################## -# We schedule to reduce the learning rate by a factor of ``gamma_lr_scheduler`` -# every 10 epochs. - - -exp_lr_scheduler = lr_scheduler.StepLR( - optimizer_hybrid, step_size=10, gamma=gamma_lr_scheduler -) - -############################################################################## -# What follows is a training function that will be called later. -# This function should return a trained model that can be used to make predictions -# (classifications). - - -def train_model(model, criterion, optimizer, scheduler, num_epochs): - since = time.time() - best_model_wts = copy.deepcopy(model.state_dict()) - best_acc = 0.0 - best_loss = 10000.0 # Large arbitrary number - best_acc_train = 0.0 - best_loss_train = 10000.0 # Large arbitrary number - print("Training started:") - - for epoch in range(num_epochs): - - # Each epoch has a training and validation phase - for phase in ["train", "validation"]: - if phase == "train": - # Set model to training mode - model.train() - else: - # Set model to evaluate mode - model.eval() - running_loss = 0.0 - running_corrects = 0 - - # Iterate over data. - n_batches = dataset_sizes[phase] // batch_size - it = 0 - for inputs, labels in dataloaders[phase]: - since_batch = time.time() - batch_size_ = len(inputs) - inputs = inputs.to(device) - labels = labels.to(device) - optimizer.zero_grad() - - # Track/compute gradient and make an optimization step only when training - with torch.set_grad_enabled(phase == "train"): - outputs = model(inputs) - _, preds = torch.max(outputs, 1) - loss = criterion(outputs, labels) - if phase == "train": - loss.backward() - optimizer.step() - - # Print iteration results - running_loss += loss.item() * batch_size_ - batch_corrects = torch.sum(preds == labels.data).item() - running_corrects += batch_corrects - print( - "Phase: {} Epoch: {}/{} Iter: {}/{} Batch time: {:.4f}".format( - phase, - epoch + 1, - num_epochs, - it + 1, - n_batches + 1, - time.time() - since_batch, - ), - end="\r", - flush=True, - ) - it += 1 - - # Print epoch results - epoch_loss = running_loss / dataset_sizes[phase] - epoch_acc = running_corrects / dataset_sizes[phase] - print( - "Phase: {} Epoch: {}/{} Loss: {:.4f} Acc: {:.4f} ".format( - "train" if phase == "train" else "validation ", - epoch + 1, - num_epochs, - epoch_loss, - epoch_acc, - ) - ) - - # Check if this is the best model wrt previous epochs - if phase == "validation" and epoch_acc > best_acc: - best_acc = epoch_acc - best_model_wts = copy.deepcopy(model.state_dict()) - if phase == "validation" and epoch_loss < best_loss: - best_loss = epoch_loss - if phase == "train" and epoch_acc > best_acc_train: - best_acc_train = epoch_acc - if phase == "train" and epoch_loss < best_loss_train: - best_loss_train = epoch_loss - - # Update learning rate - if phase == "train": - scheduler.step() - - # Print final results - model.load_state_dict(best_model_wts) - time_elapsed = time.time() - since - print( - "Training completed in {:.0f}m {:.0f}s".format(time_elapsed // 60, time_elapsed % 60) - ) - print("Best test loss: {:.4f} | Best test accuracy: {:.4f}".format(best_loss, best_acc)) - return model - - -############################################################################## -# We are ready to perform the actual training process. - -model_hybrid = train_model( - model_hybrid, criterion, optimizer_hybrid, exp_lr_scheduler, num_epochs=num_epochs -) - -############################################################################## -# Visualizing the model predictions -# ------------------------------------ - -############################################################################## -# We first define a visualization function for a batch of test data. - - -def visualize_model(model, num_images=6, fig_name="Predictions"): - images_so_far = 0 - _fig = plt.figure(fig_name) - model.eval() - with torch.no_grad(): - for _i, (inputs, labels) in enumerate(dataloaders["validation"]): - inputs = inputs.to(device) - labels = labels.to(device) - outputs = model(inputs) - _, preds = torch.max(outputs, 1) - for j in range(inputs.size()[0]): - images_so_far += 1 - ax = plt.subplot(num_images // 2, 2, images_so_far) - ax.axis("off") - ax.set_title("[{}]".format(class_names[preds[j]])) - imshow(inputs.cpu().data[j]) - if images_so_far == num_images: - return - - -############################################################################## -# Finally, we can run the previous function to see a batch of images -# with the corresponding predictions. -# -visualize_model(model_hybrid, num_images=batch_size) -plt.show() - -############################################################################## -# References -# ------------ -# -# [1] Andrea Mari, Thomas R. Bromley, Josh Izaac, Maria Schuld, and Nathan Killoran. -# *Transfer learning in hybrid classical-quantum neural networks*. arXiv:1912.08278 (2019). -# -# [2] Rajat Raina, Alexis Battle, Honglak Lee, Benjamin Packer, and Andrew Y Ng. -# *Self-taught learning: transfer learning from unlabeled data*. -# Proceedings of the 24th International Conference on Machine Learning*, 759–766 (2007). -# -# [3] Kaiming He, Xiangyu Zhang, Shaoqing ren and Jian Sun. *Deep residual learning for image recognition*. -# Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition, 770-778 (2016). -# -# [4] Ville Bergholm, Josh Izaac, Maria Schuld, Christian Gogolin, Carsten Blank, Keri McKiernan, and Nathan Killoran. -# *PennyLane: Automatic differentiation of hybrid quantum-classical computations*. arXiv:1811.04968 (2018). -# -# -# About the author -# ---------------- +r""" +.. _quantum_transfer_learning: + +Quantum transfer learning +========================= + +.. meta:: + :property="og:description": Combine PyTorch and PennyLane to train a hybrid quantum-classical image + classifier using transfer learning. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/transfer_images.png + +*Author: Andrea Mari — Posted: 19 December 2019. Last updated: 28 January 2021.* + +In this tutorial we apply a machine learning method, known as *transfer learning*, to an +image classifier based on a hybrid classical-quantum network. + +This example follows the general structure of the PyTorch +`tutorial on transfer learning `_ +by Sasank Chilamkurthy, with the crucial difference of using a quantum circuit to perform the +final classification task. + +More details on this topic can be found in the research paper [1] (`Mari et al. (2019) `_). + + +Introduction +------------ + +Transfer learning is a well-established technique for training artificial neural networks (see e.g., Ref. [2]), +which is based on the general intuition that if a pre-trained network is good at solving a +given problem, then, with just a bit of additional training, it can be used to also solve a different +but related problem. + +As discussed in Ref. [1], this idea can be formalized in terms of two abstract networks :math:`A` +and :math:`B,` independently from their quantum or classical physical nature. + +| + + +.. figure:: ../_static/demonstration_assets/quantum_transfer_learning/transfer_learning_general.png + :scale: 45% + :alt: transfer_general + :align: center + +| + +As sketched in the above figure, one can give the following **general definition of the +transfer learning method**: + +1. Take a network :math:`A` that has been pre-trained on a dataset :math:`D_A` and for a given + task :math:`T_A.` + +2. Remove some of the final layers. In this way, the resulting truncated network :math:`A'` + can be used as a feature extractor. + +3. Connect a new trainable network :math:`B` at the end of the pre-trained network :math:`A'.` + +4. Keep the weights of :math:`A'` constant, and train the final block :math:`B` with a + new dataset :math:`D_B` and/or for a new task of interest :math:`T_B.` + +When dealing with hybrid systems, depending on the physical nature (classical or quantum) of the +networks :math:`A` and :math:`B,` one can have different implementations of transfer learning as + +summarized in following table: + +| + +.. rst-class:: docstable + ++-----------+-----------+-----------------------------------------------------+ +| Network A | Network B | Transfer learning scheme | ++===========+===========+=====================================================+ +| Classical | Classical | CC - Standard classical method. See e.g., Ref. [2]. | ++-----------+-----------+-----------------------------------------------------+ +| Classical | Quantum | CQ - **Hybrid model presented in this tutorial.** | ++-----------+-----------+-----------------------------------------------------+ +| Quantum | Classical | QC - Model studied in Ref. [1]. | ++-----------+-----------+-----------------------------------------------------+ +| Quantum | Quantum | QQ - Model studied in Ref. [1]. | ++-----------+-----------+-----------------------------------------------------+ + +Classical-to-quantum transfer learning +-------------------------------------- + +We focus on the CQ transfer learning scheme discussed in the previous section and we give a specific example. + +1. As pre-trained network :math:`A` we use **ResNet18**, a deep residual neural network introduced by + Microsoft in Ref. [3], which is pre-trained on the *ImageNet* dataset. + +2. After removing its final layer we obtain :math:`A',` a pre-processing block which maps any + input high-resolution image into 512 abstract features. + +3. Such features are classified by a 4-qubit "dressed quantum circuit" :math:`B,` i.e., a + variational quantum circuit sandwiched between two classical layers. + +4. The hybrid model is trained, keeping :math:`A'` constant, on the *Hymenoptera* dataset + (a small subclass of ImageNet) containing images of *ants* and *bees*. + +A graphical representation of the full data processing pipeline is given in the figure below. + +.. figure:: ../_static/demonstration_assets/quantum_transfer_learning/transfer_learning_c2q.png + :scale: 55% + :alt: transfer_c2q + :align: center + +""" + +############################################################################## +# General setup +# ------------------------ +# +# .. note:: +# +# To use the PyTorch interface in PennyLane, you must first +# `install PyTorch `_. +# +# In addition to *PennyLane*, we will also need some standard *PyTorch* libraries and the +# plotting library *matplotlib*. + +# Some parts of this code are based on the Python script: +# https://github.com/pytorch/tutorials/blob/master/beginner_source/transfer_learning_tutorial.py +# License: BSD + +import time +import os +import copy + +# PyTorch +import torch +import torch.nn as nn +import torch.optim as optim +from torch.optim import lr_scheduler +import torchvision +from torchvision import datasets, transforms + +# Pennylane +import pennylane as qml +from pennylane import numpy as np + +torch.manual_seed(42) +np.random.seed(42) + +# Plotting +import matplotlib.pyplot as plt + +# OpenMP: number of parallel threads. +os.environ["OMP_NUM_THREADS"] = "1" + + +############################################################################## +# Setting of the main hyper-parameters of the model +# ------------------------------------------------------------ +# +# .. note:: +# To reproduce the results of Ref. [1], ``num_epochs`` should be set to ``30`` which may take a long time. +# We suggest to first try with ``num_epochs=1`` and, if everything runs smoothly, increase it to a larger value. + + +n_qubits = 4 # Number of qubits +step = 0.0004 # Learning rate +batch_size = 4 # Number of samples for each training step +num_epochs = 3 # Number of training epochs +q_depth = 6 # Depth of the quantum circuit (number of variational layers) +gamma_lr_scheduler = 0.1 # Learning rate reduction applied every 10 epochs. +q_delta = 0.01 # Initial spread of random quantum weights +start_time = time.time() # Start of the computation timer + +############################################################################## +# We initialize a PennyLane device with a ``default.qubit`` backend. + +dev = qml.device("default.qubit", wires=n_qubits) + +############################################################################## +# We configure PyTorch to use CUDA only if available. Otherwise the CPU is used. + +device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") + +############################################################################## +# Dataset loading +# ------------------------------------------------------------ +# +# .. note:: +# The dataset containing images of *ants* and *bees* can be downloaded +# `here `_ and +# should be extracted in the subfolder ``../_data/hymenoptera_data``. +# +# This is a very small dataset (roughly 250 images), too small for training from scratch a +# classical or quantum model, however it is enough when using *transfer learning* approach. +# +# The PyTorch packages ``torchvision`` and ``torch.utils.data`` are used for loading the dataset +# and performing standard preliminary image operations: resize, center, crop, normalize, *etc.* + +data_transforms = { + "train": transforms.Compose( + [ + # transforms.RandomResizedCrop(224), # uncomment for data augmentation + # transforms.RandomHorizontalFlip(), # uncomment for data augmentation + transforms.Resize(256), + transforms.CenterCrop(224), + transforms.ToTensor(), + # Normalize input channels using mean values and standard deviations of ImageNet. + transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), + ] + ), + "val": transforms.Compose( + [ + transforms.Resize(256), + transforms.CenterCrop(224), + transforms.ToTensor(), + transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), + ] + ), +} + +data_dir = "../_data/hymenoptera_data" +image_datasets = { + x if x == "train" else "validation": datasets.ImageFolder( + os.path.join(data_dir, x), data_transforms[x] + ) + for x in ["train", "val"] +} +dataset_sizes = {x: len(image_datasets[x]) for x in ["train", "validation"]} +class_names = image_datasets["train"].classes + +# Initialize dataloader +dataloaders = { + x: torch.utils.data.DataLoader(image_datasets[x], batch_size=batch_size, shuffle=True) + for x in ["train", "validation"] +} + +# function to plot images +def imshow(inp, title=None): + """Display image from tensor.""" + inp = inp.numpy().transpose((1, 2, 0)) + # Inverse of the initial normalization operation. + mean = np.array([0.485, 0.456, 0.406]) + std = np.array([0.229, 0.224, 0.225]) + inp = std * inp + mean + inp = np.clip(inp, 0, 1) + plt.imshow(inp) + if title is not None: + plt.title(title) + + +############################################################################## +# Let us show a batch of the test data, just to have an idea of the classification problem. + +# Get a batch of training data +inputs, classes = next(iter(dataloaders["validation"])) + +# Make a grid from batch +out = torchvision.utils.make_grid(inputs) + +imshow(out, title=[class_names[x] for x in classes]) + +dataloaders = { + x: torch.utils.data.DataLoader(image_datasets[x], batch_size=batch_size, shuffle=True) + for x in ["train", "validation"] +} + + +############################################################################## +# Variational quantum circuit +# ------------------------------------ +# We first define some quantum layers that will compose the quantum circuit. + + +def H_layer(nqubits): + """Layer of single-qubit Hadamard gates. + """ + for idx in range(nqubits): + qml.Hadamard(wires=idx) + + +def RY_layer(w): + """Layer of parametrized qubit rotations around the y axis. + """ + for idx, element in enumerate(w): + qml.RY(element, wires=idx) + + +def entangling_layer(nqubits): + """Layer of CNOTs followed by another shifted layer of CNOT. + """ + # In other words it should apply something like : + # CNOT CNOT CNOT CNOT... CNOT + # CNOT CNOT CNOT... CNOT + for i in range(0, nqubits - 1, 2): # Loop over even indices: i=0,2,...N-2 + qml.CNOT(wires=[i, i + 1]) + for i in range(1, nqubits - 1, 2): # Loop over odd indices: i=1,3,...N-3 + qml.CNOT(wires=[i, i + 1]) + + +############################################################################## +# Now we define the quantum circuit through the PennyLane `qnode` decorator . +# +# The structure is that of a typical variational quantum circuit: +# +# * **Embedding layer:** All qubits are first initialized in a balanced superposition +# of *up* and *down* states, then they are rotated according to the input parameters +# (local embedding). +# +# * **Variational layers:** A sequence of trainable rotation layers and constant +# entangling layers is applied. +# +# * **Measurement layer:** For each qubit, the local expectation value of the :math:`Z` +# operator is measured. This produces a classical output vector, suitable for +# additional post-processing. + + +@qml.qnode(dev) +def quantum_net(q_input_features, q_weights_flat): + """ + The variational quantum circuit. + """ + + # Reshape weights + q_weights = q_weights_flat.reshape(q_depth, n_qubits) + + # Start from state |+> , unbiased w.r.t. |0> and |1> + H_layer(n_qubits) + + # Embed features in the quantum node + RY_layer(q_input_features) + + # Sequence of trainable variational layers + for k in range(q_depth): + entangling_layer(n_qubits) + RY_layer(q_weights[k]) + + # Expectation values in the Z basis + exp_vals = [qml.expval(qml.PauliZ(position)) for position in range(n_qubits)] + return tuple(exp_vals) + + +############################################################################## +# Dressed quantum circuit +# ------------------------ +# +# We can now define a custom ``torch.nn.Module`` representing a *dressed* quantum circuit. +# +# This is a concatenation of: +# +# * A classical pre-processing layer (``nn.Linear``). +# * A classical activation function (``torch.tanh``). +# * A constant ``np.pi/2.0`` scaling. +# * The previously defined quantum circuit (``quantum_net``). +# * A classical post-processing layer (``nn.Linear``). +# +# The input of the module is a batch of vectors with 512 real parameters (features) and +# the output is a batch of vectors with two real outputs (associated with the two classes +# of images: *ants* and *bees*). + + +class DressedQuantumNet(nn.Module): + """ + Torch module implementing the *dressed* quantum net. + """ + + def __init__(self): + """ + Definition of the *dressed* layout. + """ + + super().__init__() + self.pre_net = nn.Linear(512, n_qubits) + self.q_params = nn.Parameter(q_delta * torch.randn(q_depth * n_qubits)) + self.post_net = nn.Linear(n_qubits, 2) + + def forward(self, input_features): + """ + Defining how tensors are supposed to move through the *dressed* quantum + net. + """ + + # obtain the input features for the quantum circuit + # by reducing the feature dimension from 512 to 4 + pre_out = self.pre_net(input_features) + q_in = torch.tanh(pre_out) * np.pi / 2.0 + + # Apply the quantum circuit to each element of the batch and append to q_out + q_out = torch.Tensor(0, n_qubits) + q_out = q_out.to(device) + for elem in q_in: + q_out_elem = torch.hstack(quantum_net(elem, self.q_params)).float().unsqueeze(0) + q_out = torch.cat((q_out, q_out_elem)) + + # return the two-dimensional prediction from the postprocessing layer + return self.post_net(q_out) + + +############################################################################## +# Hybrid classical-quantum model +# ------------------------------------ +# +# We are finally ready to build our full hybrid classical-quantum network. +# We follow the *transfer learning* approach: +# +# 1. First load the classical pre-trained network *ResNet18* from the ``torchvision.models`` zoo. +# 2. Freeze all the weights since they should not be trained. +# 3. Replace the last fully connected layer with our trainable dressed quantum circuit (``DressedQuantumNet``). +# +# .. note:: +# The *ResNet18* model is automatically downloaded by PyTorch and it may take several minutes (only the first time). +# + +weights = torchvision.models.ResNet18_Weights.IMAGENET1K_V1 +model_hybrid = torchvision.models.resnet18(weights=weights) + +for param in model_hybrid.parameters(): + param.requires_grad = False + + +# Notice that model_hybrid.fc is the last layer of ResNet18 +model_hybrid.fc = DressedQuantumNet() + +# Use CUDA or CPU according to the "device" object. +model_hybrid = model_hybrid.to(device) + +############################################################################## +# Training and results +# ------------------------ +# +# Before training the network we need to specify the *loss* function. +# +# We use, as usual in classification problem, the *cross-entropy* which is +# directly available within ``torch.nn``. + + +criterion = nn.CrossEntropyLoss() + +############################################################################## +# We also initialize the *Adam optimizer* which is called at each training step +# in order to update the weights of the model. + + +optimizer_hybrid = optim.Adam(model_hybrid.fc.parameters(), lr=step) + +############################################################################## +# We schedule to reduce the learning rate by a factor of ``gamma_lr_scheduler`` +# every 10 epochs. + + +exp_lr_scheduler = lr_scheduler.StepLR( + optimizer_hybrid, step_size=10, gamma=gamma_lr_scheduler +) + +############################################################################## +# What follows is a training function that will be called later. +# This function should return a trained model that can be used to make predictions +# (classifications). + + +def train_model(model, criterion, optimizer, scheduler, num_epochs): + since = time.time() + best_model_wts = copy.deepcopy(model.state_dict()) + best_acc = 0.0 + best_loss = 10000.0 # Large arbitrary number + best_acc_train = 0.0 + best_loss_train = 10000.0 # Large arbitrary number + print("Training started:") + + for epoch in range(num_epochs): + + # Each epoch has a training and validation phase + for phase in ["train", "validation"]: + if phase == "train": + # Set model to training mode + model.train() + else: + # Set model to evaluate mode + model.eval() + running_loss = 0.0 + running_corrects = 0 + + # Iterate over data. + n_batches = dataset_sizes[phase] // batch_size + it = 0 + for inputs, labels in dataloaders[phase]: + since_batch = time.time() + batch_size_ = len(inputs) + inputs = inputs.to(device) + labels = labels.to(device) + optimizer.zero_grad() + + # Track/compute gradient and make an optimization step only when training + with torch.set_grad_enabled(phase == "train"): + outputs = model(inputs) + _, preds = torch.max(outputs, 1) + loss = criterion(outputs, labels) + if phase == "train": + loss.backward() + optimizer.step() + + # Print iteration results + running_loss += loss.item() * batch_size_ + batch_corrects = torch.sum(preds == labels.data).item() + running_corrects += batch_corrects + print( + "Phase: {} Epoch: {}/{} Iter: {}/{} Batch time: {:.4f}".format( + phase, + epoch + 1, + num_epochs, + it + 1, + n_batches + 1, + time.time() - since_batch, + ), + end="\r", + flush=True, + ) + it += 1 + + # Print epoch results + epoch_loss = running_loss / dataset_sizes[phase] + epoch_acc = running_corrects / dataset_sizes[phase] + print( + "Phase: {} Epoch: {}/{} Loss: {:.4f} Acc: {:.4f} ".format( + "train" if phase == "train" else "validation ", + epoch + 1, + num_epochs, + epoch_loss, + epoch_acc, + ) + ) + + # Check if this is the best model wrt previous epochs + if phase == "validation" and epoch_acc > best_acc: + best_acc = epoch_acc + best_model_wts = copy.deepcopy(model.state_dict()) + if phase == "validation" and epoch_loss < best_loss: + best_loss = epoch_loss + if phase == "train" and epoch_acc > best_acc_train: + best_acc_train = epoch_acc + if phase == "train" and epoch_loss < best_loss_train: + best_loss_train = epoch_loss + + # Update learning rate + if phase == "train": + scheduler.step() + + # Print final results + model.load_state_dict(best_model_wts) + time_elapsed = time.time() - since + print( + "Training completed in {:.0f}m {:.0f}s".format(time_elapsed // 60, time_elapsed % 60) + ) + print("Best test loss: {:.4f} | Best test accuracy: {:.4f}".format(best_loss, best_acc)) + return model + + +############################################################################## +# We are ready to perform the actual training process. + +model_hybrid = train_model( + model_hybrid, criterion, optimizer_hybrid, exp_lr_scheduler, num_epochs=num_epochs +) + +############################################################################## +# Visualizing the model predictions +# ------------------------------------ + +############################################################################## +# We first define a visualization function for a batch of test data. + + +def visualize_model(model, num_images=6, fig_name="Predictions"): + images_so_far = 0 + _fig = plt.figure(fig_name) + model.eval() + with torch.no_grad(): + for _i, (inputs, labels) in enumerate(dataloaders["validation"]): + inputs = inputs.to(device) + labels = labels.to(device) + outputs = model(inputs) + _, preds = torch.max(outputs, 1) + for j in range(inputs.size()[0]): + images_so_far += 1 + ax = plt.subplot(num_images // 2, 2, images_so_far) + ax.axis("off") + ax.set_title("[{}]".format(class_names[preds[j]])) + imshow(inputs.cpu().data[j]) + if images_so_far == num_images: + return + + +############################################################################## +# Finally, we can run the previous function to see a batch of images +# with the corresponding predictions. +# +visualize_model(model_hybrid, num_images=batch_size) +plt.show() + +############################################################################## +# References +# ------------ +# +# [1] Andrea Mari, Thomas R. Bromley, Josh Izaac, Maria Schuld, and Nathan Killoran. +# *Transfer learning in hybrid classical-quantum neural networks*. arXiv:1912.08278 (2019). +# +# [2] Rajat Raina, Alexis Battle, Honglak Lee, Benjamin Packer, and Andrew Y Ng. +# *Self-taught learning: transfer learning from unlabeled data*. +# Proceedings of the 24th International Conference on Machine Learning*, 759–766 (2007). +# +# [3] Kaiming He, Xiangyu Zhang, Shaoqing ren and Jian Sun. *Deep residual learning for image recognition*. +# Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition, 770-778 (2016). +# +# [4] Ville Bergholm, Josh Izaac, Maria Schuld, Christian Gogolin, Carsten Blank, Keri McKiernan, and Nathan Killoran. +# *PennyLane: Automatic differentiation of hybrid quantum-classical computations*. arXiv:1811.04968 (2018). +# +# +# About the author +# ---------------- # \ No newline at end of file diff --git a/demonstrations/tutorial_qubit_tapering.py b/demonstrations/tutorial_qubit_tapering.py index 5c55be064e..a09519630b 100644 --- a/demonstrations/tutorial_qubit_tapering.py +++ b/demonstrations/tutorial_qubit_tapering.py @@ -1,330 +1,330 @@ -r""" - -Qubit tapering -============== - -.. meta:: - :property="og:description": Learn how to taper off qubits - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qubit_tapering.png - -.. related:: - tutorial_quantum_chemistry Building molecular Hamiltonians - tutorial_vqe A brief overview of VQE - tutorial_givens_rotations Givens rotations for quantum chemistry - tutorial_adaptive_circuits Adaptive circuits for quantum chemistry - tutorial_differentiable_HF Differentiable Hartree-Fock - -*Authors: Utkarsh Azad and Soran Jahangiri. Posted: 16 May 2022. Last updated: 08 Nov 2022* - -The performance of variational quantum algorithms is considerably limited by the number of qubits -required to represent wave functions. In the context of quantum chemistry, this -limitation hinders the treatment of large molecules with algorithms such as the :doc:`variational quantum -eigensolver (VQE) `. Several approaches have been developed to reduce the qubit requirements for -quantum chemistry calculations. In this tutorial, we demonstrate the symmetry-based qubit -tapering approach which allows reducing the number of qubits required to perform molecular quantum -simulations based on the :math:`\mathbb{Z}_2` symmetries present in molecular Hamiltonians -[#bravyi2017]_ [#setia2019]_. - -A molecular Hamiltonian in the qubit basis can be expressed as a linear combination of Pauli words -as - -.. math:: H = \sum_{i=1}^r h_i P_i, - -where :math:`h_i` is a real coefficient and :math:`P_i` is a tensor product of Pauli and -identity operators acting on :math:`M` qubits - -.. math:: P_i \in \pm \left \{ I, X, Y, Z \right \} ^ {\bigotimes M}. - -The main idea in the symmetry-based qubit tapering approach is to find a unitary operator :math:`U` -that transforms :math:`H` to a new Hamiltonian :math:`H'` which has the same eigenvalues as -:math:`H` - -.. math:: H' = U^{\dagger} H U = \sum_{i=1}^r c_i \mu_i, - -such that each :math:`\mu_i` term in the new Hamiltonian always acts trivially, e.g., with an -identity or a Pauli operator, on a set of qubits. This allows tapering-off those qubits from the -Hamiltonian. - -For instance, consider the following Hamiltonian - -.. math:: H' = Z_0 X_1 - X_1 + Y_0 X_1, - -where all terms in the Hamiltonian act on the second qubit with the :math:`X` operator. It is -straightforward to show that each term in the Hamiltonian commutes with :math:`I_0 X_1` and the -ground-state eigenvector of :math:`H'` is also an eigenvector of :math:`I_0 X_1` with eigenvalues -:math:`\pm 1.` We can also rewrite the Hamiltonian as - -.. math:: H' = (Z_0 I_1 - I_0 I_1 + Y_0 I_1) I_0 X_1, - -which gives us - -.. math:: H'|\psi \rangle = \pm1 (Z_0 I_1 - I_0 I_1 + Y_0 I_1)|\psi \rangle, - -where :math:`|\psi \rangle` is an eigenvector of :math:`H'.` This means that the Hamiltonian -:math:`H` can be simplified as - -.. math:: H_{tapered} = \pm1 (Z_0 - I_0 + Y_0). - -The tapered Hamiltonian :math:`H_{tapered}` has the eigenvalues - -.. math:: [-2.41421, 0.41421], - -and - -.. math:: [2.41421, -0.41421], - -depending on the value of the :math:`\pm 1` prefactor. The eigenvalues of the original Hamiltonian -:math:`H` are - -.. math:: [2.41421, -2.41421, 0.41421, -0.41421], - -which are thus reproduced by the tapered Hamiltonian. - -More generally, we can construct the unitary :math:`U` such that each :math:`\mu_i` term acts with a -Pauli-X operator on a set of qubits -:math:`\left \{ j \right \}, j \in \left \{ l, ..., k \right \}` where :math:`j` is the qubit label. -This guarantees that each term of the transformed Hamiltonian commutes with each of the Pauli-X -operators applied to the :math:`j`-th qubit: - -.. math:: [H', X^j] = 0, - -and the eigenvectors of the transformed Hamiltonian :math:`H'` are also eigenvectors of each of the -:math:`X^{j}` operators. Then we can factor out all of the :math:`X^{j}` operators from the -transformed Hamiltonian and replace them with their eigenvalues :math:`\pm 1.` This gives us a -set of tapered Hamiltonians depending on which eigenvalue :math:`\pm 1` we chose for each of the -:math:`X^{j}` operators. For instance, in the case of two tapered qubits, we have four eigenvalue -sectors: :math:`[+1, +1]`, :math:`[-1, +1]`, :math:`[+1, -1],` :math:`[-1, -1].` In these tapered -Hamiltonians, the set of :math:`\left \{ j \right \}, j \in \left \{ l, ..., k \right \}` qubits -are eliminated. For tapered molecular Hamiltonians, it is possible to determine the optimal sector -of the eigenvalues that corresponds to the ground state. This is explained in more detail in the -following sections. - -The unitary operator :math:`U` can be constructed as a -`Clifford `__ operator [#bravyi2017]_ - -.. math:: U = \Pi_j \left [\frac{1}{\sqrt{2}} \left (X^{q(j)} + \tau_j \right) \right], - -where :math:`\tau` denotes the generators of the symmetry group of :math:`H` and -:math:`X^{q}` operators act on those qubits that will be ultimately tapered off from -the Hamiltonian. The symmetry group of the Hamiltonian is defined as an Abelian group of Pauli words that commute -with each term in the Hamiltonian (excluding :math:`−I`). The -`generators `__ of the symmetry group are -those elements of the group that can be combined, along with their inverses, to create any other -member of the group. - -Let's use the qubit tapering method and obtain the ground state energy of the `Helium hydride -cation `__ :math:`\textrm{HeH}^+.` - -Tapering the molecular Hamiltonian ----------------------------------- - -In PennyLane, a :doc:`molecular Hamiltonian ` can be created by specifying the atomic symbols and -coordinates. -""" -import pennylane as qml -from jax import numpy as jnp -import jax - -jax.config.update("jax_enable_x64", True) -symbols = ["He", "H"] -geometry = jnp.array([[0.00000000, 0.00000000, -0.87818361], - [0.00000000, 0.00000000, 0.87818362]]) - -molecule = qml.qchem.Molecule(symbols, geometry, charge=1) -H, qubits = qml.qchem.molecular_hamiltonian(molecule) -H - -############################################################################## -# This Hamiltonian contains 27 terms where each term acts on up to four qubits. -# -# We can now obtain the symmetry generators and the :math:`X^{j}` operators that are -# used to construct the unitary :math:`U` operator that transforms the :math:`\textrm{HeH}^+` -# Hamiltonian. In PennyLane, these are constructed by using the -# :func:`~.pennylane.symmetry_generators` and :func:`~.pennylane.paulix_ops` functions. - -generators = qml.symmetry_generators(H) -paulixops = qml.paulix_ops(generators, qubits) - -for idx, generator in enumerate(generators): - print(f"generator {idx+1}: {generator}, paulix_op: {paulixops[idx]}") - -############################################################################## -# Once the operator :math:`U` is applied, each of the Hamiltonian terms will act on the qubits -# :math:`q_2, q_3` either with the identity or with a Pauli-X operator. For each of these qubits, -# we can simply replace the Pauli-X operator with one of its eigenvalues :math:`+1` or :math:`-1.` -# This results in a total number of :math:`2^k` Hamiltonians, where :math:`k` is the number of -# tapered-off qubits and each Hamiltonian corresponds to one eigenvalue sector. The optimal sector -# corresponding to the ground-state energy of the molecule can be obtained by using the -# :func:`~.pennylane.qchem.optimal_sector` function. - - -n_electrons = 2 -paulix_sector = qml.qchem.optimal_sector(H, generators, n_electrons) -print(paulix_sector) - -############################################################################## -# The optimal eigenvalues are :math:`-1, -1` for qubits :math:`q_2, q_3,` respectively. We can now -# build the tapered Hamiltonian with the :func:`~.pennylane.taper` function which -# constructs the operator :math:`U,` applies it to the Hamiltonian and finally tapers off the -# qubits :math:`q_2, q_3` by replacing the Pauli-X operators acting on those qubits with the optimal -# eigenvalues. - -H_tapered = qml.taper(H, generators, paulixops, paulix_sector) -H_tapered_coeffs, H_tapered_ops = H_tapered.terms() -H_tapered = qml.Hamiltonian(jnp.real(jnp.array(H_tapered_coeffs)), H_tapered_ops) -print(H_tapered) - -############################################################################## -# The new Hamiltonian has only 9 non-zero terms acting on only 2 qubits! We can verify that the -# original and the tapered Hamiltonian both give the correct ground state energy of the -# :math:`\textrm{HeH}^+` cation, which is :math:`-2.862595242378` Ha computed with the full -# configuration interaction (FCI) method. In PennyLane, it's possible to build a sparse matrix -# representation of Hamiltonians. This allows us to directly diagonalize them to obtain exact values -# of the ground-state energies. - -H_sparse = qml.SparseHamiltonian(H.sparse_matrix(), wires=H.wires) -H_tapered_sparse = qml.SparseHamiltonian(H_tapered.sparse_matrix(), wires=H_tapered.wires) - -print("Eigenvalues of H:\n", qml.eigvals(H_sparse, k=16)) -print("\nEigenvalues of H_tapered:\n", qml.eigvals(H_tapered_sparse, k=4)) - -############################################################################## -# Note that a second-quantized Hamiltonian is independent of the number of electrons and its -# eigenspectrum contains the energies of the neutral and charged molecules. Therefore, the -# smallest eigenvalue returned by :func:`~.pennylane.eigvals` for a molecular Hamiltonian -# might correspond to the neutral or charged molecule. While in the case of :math:`\textrm{HeH}^+,` -# qubit tapering allows specifying the optimal sector of the eigenvectors corresponding only to the -# correct number of electrons, it is generally guaranteed that the optimal sector covers all -# eigenvectors with the correct number of electrons, but may contain additional eigenvectors of -# different charge. Therefore, the ground-state energy of the :math:`\textrm{HeH}^+` cation is -# the smallest eigenvalue of the tapered Hamiltonian. -# -# Tapering the reference state -# ---------------------------- -# The ground state Hartree-Fock energy of :math:`\textrm{HeH}^+` can be computed by directly -# applying the Hamiltonians to the Hartree-Fock state. For the tapered Hamiltonian, this requires -# transforming the Hartree-Fock state with the same symmetries obtained for the original -# Hamiltonian. This reduces the number of qubits in the Hartree-Fock state to match that of the -# tapered Hamiltonian. It can be done with the :func:`~.pennylane.qchem.taper_hf` function. - -state_tapered = qml.qchem.taper_hf(generators, paulixops, paulix_sector, - num_electrons=n_electrons, num_wires=len(H.wires)) -print(state_tapered) - -############################################################################## -# Recall that the original Hartree-Fock state for the :math:`\textrm{HeH}^+` cation is -# :math:`[1 1 0 0].` We can now generate the qubit representation of these states and compute the -# Hartree-Fock energies for each Hamiltonian. - -dev = qml.device("default.qubit", wires=H.wires) -@qml.qnode(dev, interface="jax") -def circuit(): - qml.BasisState(jnp.array([1, 1, 0, 0]), wires=H.wires) - return qml.state() - -qubit_state = circuit() -HF_energy = qubit_state.T @ H.sparse_matrix().toarray() @ qubit_state -print(f"HF energy: {jnp.real(HF_energy):.8f} Ha") - -dev = qml.device("lightning.qubit", wires=H_tapered.wires) -@qml.qnode(dev, interface="jax") -def circuit(): - qml.BasisState(jnp.array([1, 1]), wires=H_tapered.wires) - return qml.state() - -qubit_state = circuit() -HF_energy = qubit_state.T @ H_tapered.sparse_matrix().toarray() @ qubit_state -print(f"HF energy (tapered): {jnp.real(HF_energy):.8f} Ha") - -############################################################################## -# These values are identical to the reference Hartree-Fock energy :math:`-2.8543686493` Ha. -# -# VQE simulation -# -------------- -# Finally, we can use the tapered Hamiltonian and the tapered reference state to perform a VQE -# simulation and compute the ground-state energy of the :math:`\textrm{HeH}^+` cation. We build a -# tapered variational ansatz `[3] `__ -# that prepares an entangled state by evolving the tapered Hartree-Fock state using the tapered -# particle-conserving gates, i.e., the :func:`~.pennylane.SingleExcitation` and -# :func:`~.pennylane.DoubleExcitation` operations tapered using -# :func:`~.pennylane.qchem.taper_operation`. - -singles, doubles = qml.qchem.excitations(n_electrons, len(H.wires)) -tapered_doubles = [ - qml.taper_operation(qml.DoubleExcitation, generators, paulixops, paulix_sector, - wire_order=H.wires, op_wires=double) for double in doubles -] -tapered_singles = [ - qml.taper_operation(qml.SingleExcitation, generators, paulixops, paulix_sector, - wire_order=H.wires, op_wires=single) for single in singles -] - -dev = qml.device("lightning.qubit", wires=H_tapered.wires) - -@qml.qnode(dev, interface="jax") -def tapered_circuit(params): - qml.BasisState(state_tapered, wires=H_tapered.wires) - for idx, tapered_op in enumerate(tapered_doubles + tapered_singles): - tapered_op(params[idx]) - return qml.expval(H_tapered) - -############################################################################## -# We define an optimizer and the initial values of the circuit parameters and optimize the circuit -# parameters with respect to the ground state energy. - -import optax -import catalyst - -opt = optax.sgd(learning_rate=0.8) # sgd stands for StochasticGradientDescent -init_params = jnp.zeros(len(doubles) + len(singles)) - -def update_step(i, params, opt_state): - """Perform a single gradient update step""" - grads = catalyst.grad(tapered_circuit)(params) - updates, opt_state = opt.update(grads, opt_state) - params = optax.apply_updates(params, updates) - return (params, opt_state) - -loss_history = [] - -opt_state = opt.init(init_params) -params = init_params - -for i in range(1, 41): - params, opt_state = update_step(i, params, opt_state) - energy = tapered_circuit(params) - if not i % 5: - print(f"n: {i}, E: {energy:.8f} Ha, Params: {params}") - -############################################################################## -# The computed energy matches the FCI energy, :math:`-2.862595242378` Ha, while the number of qubits -# and the number of Hamiltonian terms are significantly reduced with respect to their original -# values. -# -# Conclusions -# ----------- -# Molecular Hamiltonians possess symmetries that can be leveraged to reduce the number of qubits -# required in quantum computing simulations. This tutorial introduces a PennyLane functionality that -# can be used for qubit tapering based on :math:`\mathbb{Z}_2` symmetries. The procedure includes -# obtaining tapered Hamiltonians and tapered reference states that can be used in variational -# quantum algorithms such as VQE. -# -# References -# ---------- -# -# .. [#bravyi2017] -# -# Sergey Bravyi, Jay M. Gambetta, Antonio Mezzacapo, Kristan Temme, "Tapering off qubits to -# simulate fermionic Hamiltonians". `arXiv:1701.08213 `__ -# -# .. [#setia2019] -# -# Kanav Setia, Richard Chen, Julia E. Rice, Antonio Mezzacapo, Marco Pistoia, James Whitfield, -# "Reducing qubit requirements for quantum simulation using molecular point group symmetries". -# `arXiv:1910.14644 `__ -# -# -# -# About the author -# ---------------- -# +r""" + +Qubit tapering +============== + +.. meta:: + :property="og:description": Learn how to taper off qubits + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qubit_tapering.png + +.. related:: + tutorial_quantum_chemistry Building molecular Hamiltonians + tutorial_vqe A brief overview of VQE + tutorial_givens_rotations Givens rotations for quantum chemistry + tutorial_adaptive_circuits Adaptive circuits for quantum chemistry + tutorial_differentiable_HF Differentiable Hartree-Fock + +*Authors: Utkarsh Azad and Soran Jahangiri. Posted: 16 May 2022. Last updated: 08 Nov 2022* + +The performance of variational quantum algorithms is considerably limited by the number of qubits +required to represent wave functions. In the context of quantum chemistry, this +limitation hinders the treatment of large molecules with algorithms such as the :doc:`variational quantum +eigensolver (VQE) `. Several approaches have been developed to reduce the qubit requirements for +quantum chemistry calculations. In this tutorial, we demonstrate the symmetry-based qubit +tapering approach which allows reducing the number of qubits required to perform molecular quantum +simulations based on the :math:`\mathbb{Z}_2` symmetries present in molecular Hamiltonians +[#bravyi2017]_ [#setia2019]_. + +A molecular Hamiltonian in the qubit basis can be expressed as a linear combination of Pauli words +as + +.. math:: H = \sum_{i=1}^r h_i P_i, + +where :math:`h_i` is a real coefficient and :math:`P_i` is a tensor product of Pauli and +identity operators acting on :math:`M` qubits + +.. math:: P_i \in \pm \left \{ I, X, Y, Z \right \} ^ {\bigotimes M}. + +The main idea in the symmetry-based qubit tapering approach is to find a unitary operator :math:`U` +that transforms :math:`H` to a new Hamiltonian :math:`H'` which has the same eigenvalues as +:math:`H` + +.. math:: H' = U^{\dagger} H U = \sum_{i=1}^r c_i \mu_i, + +such that each :math:`\mu_i` term in the new Hamiltonian always acts trivially, e.g., with an +identity or a Pauli operator, on a set of qubits. This allows tapering-off those qubits from the +Hamiltonian. + +For instance, consider the following Hamiltonian + +.. math:: H' = Z_0 X_1 - X_1 + Y_0 X_1, + +where all terms in the Hamiltonian act on the second qubit with the :math:`X` operator. It is +straightforward to show that each term in the Hamiltonian commutes with :math:`I_0 X_1` and the +ground-state eigenvector of :math:`H'` is also an eigenvector of :math:`I_0 X_1` with eigenvalues +:math:`\pm 1.` We can also rewrite the Hamiltonian as + +.. math:: H' = (Z_0 I_1 - I_0 I_1 + Y_0 I_1) I_0 X_1, + +which gives us + +.. math:: H'|\psi \rangle = \pm1 (Z_0 I_1 - I_0 I_1 + Y_0 I_1)|\psi \rangle, + +where :math:`|\psi \rangle` is an eigenvector of :math:`H'.` This means that the Hamiltonian +:math:`H` can be simplified as + +.. math:: H_{tapered} = \pm1 (Z_0 - I_0 + Y_0). + +The tapered Hamiltonian :math:`H_{tapered}` has the eigenvalues + +.. math:: [-2.41421, 0.41421], + +and + +.. math:: [2.41421, -0.41421], + +depending on the value of the :math:`\pm 1` prefactor. The eigenvalues of the original Hamiltonian +:math:`H` are + +.. math:: [2.41421, -2.41421, 0.41421, -0.41421], + +which are thus reproduced by the tapered Hamiltonian. + +More generally, we can construct the unitary :math:`U` such that each :math:`\mu_i` term acts with a +Pauli-X operator on a set of qubits +:math:`\left \{ j \right \}, j \in \left \{ l, ..., k \right \}` where :math:`j` is the qubit label. +This guarantees that each term of the transformed Hamiltonian commutes with each of the Pauli-X +operators applied to the :math:`j`-th qubit: + +.. math:: [H', X^j] = 0, + +and the eigenvectors of the transformed Hamiltonian :math:`H'` are also eigenvectors of each of the +:math:`X^{j}` operators. Then we can factor out all of the :math:`X^{j}` operators from the +transformed Hamiltonian and replace them with their eigenvalues :math:`\pm 1.` This gives us a +set of tapered Hamiltonians depending on which eigenvalue :math:`\pm 1` we chose for each of the +:math:`X^{j}` operators. For instance, in the case of two tapered qubits, we have four eigenvalue +sectors: :math:`[+1, +1]`, :math:`[-1, +1]`, :math:`[+1, -1],` :math:`[-1, -1].` In these tapered +Hamiltonians, the set of :math:`\left \{ j \right \}, j \in \left \{ l, ..., k \right \}` qubits +are eliminated. For tapered molecular Hamiltonians, it is possible to determine the optimal sector +of the eigenvalues that corresponds to the ground state. This is explained in more detail in the +following sections. + +The unitary operator :math:`U` can be constructed as a +`Clifford `__ operator [#bravyi2017]_ + +.. math:: U = \Pi_j \left [\frac{1}{\sqrt{2}} \left (X^{q(j)} + \tau_j \right) \right], + +where :math:`\tau` denotes the generators of the symmetry group of :math:`H` and +:math:`X^{q}` operators act on those qubits that will be ultimately tapered off from +the Hamiltonian. The symmetry group of the Hamiltonian is defined as an Abelian group of Pauli words that commute +with each term in the Hamiltonian (excluding :math:`−I`). The +`generators `__ of the symmetry group are +those elements of the group that can be combined, along with their inverses, to create any other +member of the group. + +Let's use the qubit tapering method and obtain the ground state energy of the `Helium hydride +cation `__ :math:`\textrm{HeH}^+.` + +Tapering the molecular Hamiltonian +---------------------------------- + +In PennyLane, a :doc:`molecular Hamiltonian ` can be created by specifying the atomic symbols and +coordinates. +""" +import pennylane as qml +from jax import numpy as jnp +import jax + +jax.config.update("jax_enable_x64", True) +symbols = ["He", "H"] +geometry = jnp.array([[0.00000000, 0.00000000, -0.87818361], + [0.00000000, 0.00000000, 0.87818362]]) + +molecule = qml.qchem.Molecule(symbols, geometry, charge=1) +H, qubits = qml.qchem.molecular_hamiltonian(molecule) +H + +############################################################################## +# This Hamiltonian contains 27 terms where each term acts on up to four qubits. +# +# We can now obtain the symmetry generators and the :math:`X^{j}` operators that are +# used to construct the unitary :math:`U` operator that transforms the :math:`\textrm{HeH}^+` +# Hamiltonian. In PennyLane, these are constructed by using the +# :func:`~.pennylane.symmetry_generators` and :func:`~.pennylane.paulix_ops` functions. + +generators = qml.symmetry_generators(H) +paulixops = qml.paulix_ops(generators, qubits) + +for idx, generator in enumerate(generators): + print(f"generator {idx+1}: {generator}, paulix_op: {paulixops[idx]}") + +############################################################################## +# Once the operator :math:`U` is applied, each of the Hamiltonian terms will act on the qubits +# :math:`q_2, q_3` either with the identity or with a Pauli-X operator. For each of these qubits, +# we can simply replace the Pauli-X operator with one of its eigenvalues :math:`+1` or :math:`-1.` +# This results in a total number of :math:`2^k` Hamiltonians, where :math:`k` is the number of +# tapered-off qubits and each Hamiltonian corresponds to one eigenvalue sector. The optimal sector +# corresponding to the ground-state energy of the molecule can be obtained by using the +# :func:`~.pennylane.qchem.optimal_sector` function. + + +n_electrons = 2 +paulix_sector = qml.qchem.optimal_sector(H, generators, n_electrons) +print(paulix_sector) + +############################################################################## +# The optimal eigenvalues are :math:`-1, -1` for qubits :math:`q_2, q_3,` respectively. We can now +# build the tapered Hamiltonian with the :func:`~.pennylane.taper` function which +# constructs the operator :math:`U,` applies it to the Hamiltonian and finally tapers off the +# qubits :math:`q_2, q_3` by replacing the Pauli-X operators acting on those qubits with the optimal +# eigenvalues. + +H_tapered = qml.taper(H, generators, paulixops, paulix_sector) +H_tapered_coeffs, H_tapered_ops = H_tapered.terms() +H_tapered = qml.Hamiltonian(jnp.real(jnp.array(H_tapered_coeffs)), H_tapered_ops) +print(H_tapered) + +############################################################################## +# The new Hamiltonian has only 9 non-zero terms acting on only 2 qubits! We can verify that the +# original and the tapered Hamiltonian both give the correct ground state energy of the +# :math:`\textrm{HeH}^+` cation, which is :math:`-2.862595242378` Ha computed with the full +# configuration interaction (FCI) method. In PennyLane, it's possible to build a sparse matrix +# representation of Hamiltonians. This allows us to directly diagonalize them to obtain exact values +# of the ground-state energies. + +H_sparse = qml.SparseHamiltonian(H.sparse_matrix(), wires=H.wires) +H_tapered_sparse = qml.SparseHamiltonian(H_tapered.sparse_matrix(), wires=H_tapered.wires) + +print("Eigenvalues of H:\n", qml.eigvals(H_sparse, k=16)) +print("\nEigenvalues of H_tapered:\n", qml.eigvals(H_tapered_sparse, k=4)) + +############################################################################## +# Note that a second-quantized Hamiltonian is independent of the number of electrons and its +# eigenspectrum contains the energies of the neutral and charged molecules. Therefore, the +# smallest eigenvalue returned by :func:`~.pennylane.eigvals` for a molecular Hamiltonian +# might correspond to the neutral or charged molecule. While in the case of :math:`\textrm{HeH}^+,` +# qubit tapering allows specifying the optimal sector of the eigenvectors corresponding only to the +# correct number of electrons, it is generally guaranteed that the optimal sector covers all +# eigenvectors with the correct number of electrons, but may contain additional eigenvectors of +# different charge. Therefore, the ground-state energy of the :math:`\textrm{HeH}^+` cation is +# the smallest eigenvalue of the tapered Hamiltonian. +# +# Tapering the reference state +# ---------------------------- +# The ground state Hartree-Fock energy of :math:`\textrm{HeH}^+` can be computed by directly +# applying the Hamiltonians to the Hartree-Fock state. For the tapered Hamiltonian, this requires +# transforming the Hartree-Fock state with the same symmetries obtained for the original +# Hamiltonian. This reduces the number of qubits in the Hartree-Fock state to match that of the +# tapered Hamiltonian. It can be done with the :func:`~.pennylane.qchem.taper_hf` function. + +state_tapered = qml.qchem.taper_hf(generators, paulixops, paulix_sector, + num_electrons=n_electrons, num_wires=len(H.wires)) +print(state_tapered) + +############################################################################## +# Recall that the original Hartree-Fock state for the :math:`\textrm{HeH}^+` cation is +# :math:`[1 1 0 0].` We can now generate the qubit representation of these states and compute the +# Hartree-Fock energies for each Hamiltonian. + +dev = qml.device("default.qubit", wires=H.wires) +@qml.qnode(dev, interface="jax") +def circuit(): + qml.BasisState(jnp.array([1, 1, 0, 0]), wires=H.wires) + return qml.state() + +qubit_state = circuit() +HF_energy = qubit_state.T @ H.sparse_matrix().toarray() @ qubit_state +print(f"HF energy: {jnp.real(HF_energy):.8f} Ha") + +dev = qml.device("lightning.qubit", wires=H_tapered.wires) +@qml.qnode(dev, interface="jax") +def circuit(): + qml.BasisState(jnp.array([1, 1]), wires=H_tapered.wires) + return qml.state() + +qubit_state = circuit() +HF_energy = qubit_state.T @ H_tapered.sparse_matrix().toarray() @ qubit_state +print(f"HF energy (tapered): {jnp.real(HF_energy):.8f} Ha") + +############################################################################## +# These values are identical to the reference Hartree-Fock energy :math:`-2.8543686493` Ha. +# +# VQE simulation +# -------------- +# Finally, we can use the tapered Hamiltonian and the tapered reference state to perform a VQE +# simulation and compute the ground-state energy of the :math:`\textrm{HeH}^+` cation. We build a +# tapered variational ansatz `[3] `__ +# that prepares an entangled state by evolving the tapered Hartree-Fock state using the tapered +# particle-conserving gates, i.e., the :func:`~.pennylane.SingleExcitation` and +# :func:`~.pennylane.DoubleExcitation` operations tapered using +# :func:`~.pennylane.qchem.taper_operation`. + +singles, doubles = qml.qchem.excitations(n_electrons, len(H.wires)) +tapered_doubles = [ + qml.taper_operation(qml.DoubleExcitation, generators, paulixops, paulix_sector, + wire_order=H.wires, op_wires=double) for double in doubles +] +tapered_singles = [ + qml.taper_operation(qml.SingleExcitation, generators, paulixops, paulix_sector, + wire_order=H.wires, op_wires=single) for single in singles +] + +dev = qml.device("lightning.qubit", wires=H_tapered.wires) + +@qml.qnode(dev, interface="jax") +def tapered_circuit(params): + qml.BasisState(state_tapered, wires=H_tapered.wires) + for idx, tapered_op in enumerate(tapered_doubles + tapered_singles): + tapered_op(params[idx]) + return qml.expval(H_tapered) + +############################################################################## +# We define an optimizer and the initial values of the circuit parameters and optimize the circuit +# parameters with respect to the ground state energy. + +import optax +import catalyst + +opt = optax.sgd(learning_rate=0.8) # sgd stands for StochasticGradientDescent +init_params = jnp.zeros(len(doubles) + len(singles)) + +def update_step(i, params, opt_state): + """Perform a single gradient update step""" + grads = catalyst.grad(tapered_circuit)(params) + updates, opt_state = opt.update(grads, opt_state) + params = optax.apply_updates(params, updates) + return (params, opt_state) + +loss_history = [] + +opt_state = opt.init(init_params) +params = init_params + +for i in range(1, 41): + params, opt_state = update_step(i, params, opt_state) + energy = tapered_circuit(params) + if not i % 5: + print(f"n: {i}, E: {energy:.8f} Ha, Params: {params}") + +############################################################################## +# The computed energy matches the FCI energy, :math:`-2.862595242378` Ha, while the number of qubits +# and the number of Hamiltonian terms are significantly reduced with respect to their original +# values. +# +# Conclusions +# ----------- +# Molecular Hamiltonians possess symmetries that can be leveraged to reduce the number of qubits +# required in quantum computing simulations. This tutorial introduces a PennyLane functionality that +# can be used for qubit tapering based on :math:`\mathbb{Z}_2` symmetries. The procedure includes +# obtaining tapered Hamiltonians and tapered reference states that can be used in variational +# quantum algorithms such as VQE. +# +# References +# ---------- +# +# .. [#bravyi2017] +# +# Sergey Bravyi, Jay M. Gambetta, Antonio Mezzacapo, Kristan Temme, "Tapering off qubits to +# simulate fermionic Hamiltonians". `arXiv:1701.08213 `__ +# +# .. [#setia2019] +# +# Kanav Setia, Richard Chen, Julia E. Rice, Antonio Mezzacapo, Marco Pistoia, James Whitfield, +# "Reducing qubit requirements for quantum simulation using molecular point group symmetries". +# `arXiv:1910.14644 `__ +# +# +# +# About the author +# ---------------- +# diff --git a/demonstrations/tutorial_qutrits_bernstein_vazirani.py b/demonstrations/tutorial_qutrits_bernstein_vazirani.py index e2da31eb6e..2664509a1f 100644 --- a/demonstrations/tutorial_qutrits_bernstein_vazirani.py +++ b/demonstrations/tutorial_qutrits_bernstein_vazirani.py @@ -1,409 +1,409 @@ -r""" - -Qutrits and quantum algorithms -============================== - -.. meta:: - :property="og:description": Learn how to interpret the Bernstein-Vazirani algorithm with qutrits - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_qutrits_bernstein_vazirani.png - -.. related:: - - -*Author: Guillermo Alonso-Linaje — Posted: 9 May 2023. Last updated: 9 May 2023.* - -A qutrit is a basic quantum unit that can exist in a superposition of three possible quantum states, represented as :math:`|0\rangle`, :math:`|1\rangle`, and :math:`|2\rangle,` which functions as a generalization of the qubit. -There are many problems to which we can apply these units, among which we can highlight an improved decomposition of the Toffoli gate. -Using only qubits, it would take at least 6 CNOTs to decompose the gate, whereas with qutrits it would be enough to use 3 [#toffoli_qutrits]_. -This is one of the reasons why it is important to develop the intuition behind this basic unit of information, to see where qutrits can provide an advantage. The goal of this demo is to start working with qutrits from an algorithmic point of view. To do so, we will start with the Bernstein–Vazirani algorithm, which we will initially explore using qubits, and later using qutrits. - - - -Bernstein–Vazirani algorithm ------------------------------- - -The Bernstein–Vazirani algorithm is a quantum algorithm developed by Ethan Bernstein and Umesh Vazirani [#bv]_. -It was one of the first examples that demonstrated an exponential advantage of a quantum computer over a traditional one. So, in this first section we will understand the problem that they tackled. - - -Suppose there is some hidden bit string "a" that we are trying to learn, and that we have access to a function :math:`f(\vec{x})` that implements the following scalar product: - -.. math:: - f(\vec{x}) := \vec{a}\cdot\vec{x} \pmod 2, - -where :math:`\vec{a}=(a_0,a_1,...,a_{n-1})` and :math:`\vec{x}=(x_0,x_1,...,x_{n-1})` are bit strings of length :math:`n` with :math:`a_i, x_i \in \{0,1\}`. Our challenge will be to discover the hidden value of :math:`\vec{a}` by using the function :math:`f.` We don't know anything about :math:`\vec{a}` so the only thing we can do is to evaluate :math:`f` at different points :math:`\vec{x}` with the idea of gaining hidden information. - - -To give an example, let's imagine that we take :math:`\vec{x}=(1,0,1)` and get the value :math:`f(\vec{x}) = 0`. Although it may not seem obvious, knowing the structure that :math:`f` has, this gives us some information about :math:`\vec{a}.` In this case, :math:`a_0` and :math:`a_2` have the same value. This is because taking that value of :math:`\vec{x}`, the function will be equivalent to :math:`a_0 + a_2 \pmod 2,` which will only take the value 0 if they are equal. -I invite you to take your time to think of a possible strategy (at the classical level) in order to determine :math:`\vec{a}` with the minimum number of evaluations of the function :math:`f.` - -The optimal solution requires only :math:`n` calls to the function! Let's see how we can do this. -Knowing the form of :math:`\vec{a}` and :math:`\vec{x},` we can rewrite :math:`f` as: - -.. math:: - f(\vec{x})=\sum_{i=0}^{n-1}a_ix_i \pmod 2. - -The strategy will be to deduce one element of :math:`\vec{a}` with each call to the function. Imagine that we want to determine the value :math:`a_i.` We can simply choose :math:`\vec{x}` as a vector of all zeros except a one in the i-th position, since in this case: - - - -.. math:: - f(\vec{x})= 0\cdot a_0 + 0\cdot a_1 + ... + 1\cdot a_i + ... + 0\cdot a_{n-1} \pmod 2 \quad= a_i. - -It is trivial to see, therefore, that :math:`n` evaluations of :math:`f` are needed. The question is: can we do it more efficiently with a quantum computer? The answer is yes, and in fact, we only need to make one call to the function! - -The first step is to see how we can represent this statement in a circuit. In this case, we will assume that we have an oracle :math:`U_f` that encodes the function, as we can see in the picture below. - - -.. figure:: ../_static/demonstration_assets/qutrits_bernstein_vazirani/oracle_qutrit.jpg - :scale: 35% - :alt: Oracle definition. - :align: center - - Oracle representation of the function. - - -In general, :math:`U_f` sends the state :math:`|\vec{x} \rangle |y\rangle` to the state :math:`| \vec{x} \rangle |y + \vec{a} \cdot \vec{x} \pmod{2} \rangle.` - -Suppose, for example, that :math:`\vec{a}=[0,1,0]`. Then :math:`U_f|111\rangle |0\rangle = |111\rangle|1\rangle`, since we are evaluating :math:`f` at the point :math:`\vec{x} = [1,1,1]`. The scalar product between the two values is :math:`1,` so the last qubit of the output will take the value :math:`1.` - -The Bernstein–Vazirani algorithm makes use of this oracle according to the following circuit: - -.. figure:: ../_static/demonstration_assets/qutrits_bernstein_vazirani/bernstein_vazirani_algorithm.jpg - :scale: 35% - :alt: Bernstein-Vazirani's algorithm - :align: center - - Bernstein–Vazirani algorithm. - - -What we can see is that, by simply using Hadamard gates before and after the oracle, after a single run, the output of the circuit is exactly the hidden value of :math:`\vec{a}.` Let's do a little math to verify that this is so. - -First, the input to our circuit is :math:`|0001\rangle.` The second step is to apply Hadamard gates to this state, and for this we must use the following property: - -.. math:: - H^{\otimes n}|\vec{x}\rangle = \frac{1}{\sqrt{2^n}}\sum_{\vec{z} \in \{0,1\}^n}(-1)^{\vec{x}\cdot\vec{z}}|\vec{z}\rangle. - -Taking as input the value :math:`|0001\rangle,` we obtain the state - -.. math:: - |\phi_1\rangle=H^{\otimes 4}|0001\rangle = H^{\otimes 3}|000\rangle\otimes H|1\rangle = \frac{1}{\sqrt{2^3}}\left(\sum_{z \in \{0,1\}^3}|\vec{z}\rangle\right)\left(\frac{|0\rangle-|1\rangle}{\sqrt{2}}\right). - -As you can see, we have separated the first three qubits from the fourth for clarity. -If we now apply our operator :math:`U_f,` - -.. math:: - |\phi_2\rangle= U_f |\phi_1\rangle = \frac{1}{\sqrt{2^3}}\left(\sum_{\vec{z} \in \{0,1\}^3}|\vec{z}\rangle\frac{|\vec{a}\cdot\vec{z} \pmod 2\rangle-|1 + \vec{a}\cdot\vec{z} \pmod 2\rangle}{\sqrt{2}}\right). - -Depending on the value of :math:`f(\vec{x}),` the final part of the expression can take two values and it can be checked that - -.. math:: - |\phi_2\rangle = \frac{1}{\sqrt{2^3}}\left(\sum_{\vec{z} \in \{0,1\}^3}|\vec{z}\rangle(-1)^{\vec{a}\cdot\vec{z}}\frac{|0\rangle-|1\rangle}{\sqrt{2}}\right). - -This is because, if :math:`\vec{a}\cdot\vec{z}` takes the value :math:`0`, we will have the :math:`\frac{|0\rangle - |1\rangle}{\sqrt{2}}`, and if it takes the value :math:`1,` the result will be :math:`\frac{|1\rangle - |0\rangle}{\sqrt{2}} = - \frac{|0\rangle - |1\rangle}{\sqrt{2}}.` Therefore, by calculating :math:`(-1)^{\vec{a}\cdot\vec{z}}` we cover both cases. -After this, we can include the :math:`(-1)^{\vec{a}\cdot\vec{z}}` factor in the :math:`|\vec{z}\rangle` term and disregard the last qubit, since we are not going to use it again: - -.. math:: - |\phi_2\rangle =\frac{1}{\sqrt{2^3}}\sum_{\vec{z} \in \{0,1\}^3}(-1)^{\vec{a}\cdot\vec{z}}|\vec{z}\rangle. - -Note that you cannot always disregard a qubit. In cases where there is entanglement with other qubits that is not possible, but in this case the state is separable. - -Finally, we will reapply the property of the first step to calculate the result after using the Hadamard: - -.. math:: - |\phi_3\rangle = H^{\otimes 3}|\phi_2\rangle = \frac{1}{2^3}\sum_{\vec{z} \in \{0,1\}^3}(-1)^{\vec{a}\cdot\vec{z}}\left(\sum_{\vec{y} \in \{0,1\}^3}(-1)^{\vec{z}\cdot\vec{y}}|\vec{y}\rangle\right). - -Rearranging this expression, we obtain: - -.. math:: - |\phi_3\rangle = \frac{1}{2^3}\sum_{\vec{y} \in \{0,1\}^3}\left(\sum_{\vec{z} \in \{0,1\}^3}(-1)^{\vec{a}\cdot\vec{z}+\vec{y}\cdot\vec{z}}\right)|\vec{y}\rangle. - -Perfect! The only thing left to check is that, in fact, the previous state is exactly :math:`|\vec{a}\rangle`. It may seem complicated, but I invite you to demonstrate it by showing that :math:`\langle \vec{a}|\phi_3\rangle = 1.` Let's go to the code and check that it works. - -Algorithm coding with qubits ------------------------------- - -We will first code the classical solution. We will do this inside a quantum circuit to understand how to use the oracle, but we are really just programming the qubits as bits. - -""" - - -import pennylane as qml - -dev = qml.device("default.qubit", wires = 4, shots = 1) - -def Uf(): - # The oracle in charge of encoding a hidden "a" value. - qml.CNOT(wires=[1, 3]) - qml.CNOT(wires=[2 ,3]) - - -@qml.qnode(dev) -def circuit0(): - """Circuit used to derive a0""" - - - # Initialize x = [1,0,0] - qml.PauliX(wires = 0) - - # Apply our oracle - - Uf() - - # We measure the last qubit - return qml.sample(wires = 3) - -@qml.qnode(dev) -def circuit1(): - # Circuit used to derive a1 - - # Initialize x = [0,1,0] - qml.PauliX(wires = 1) - - # We apply our oracle - Uf() - - # We measure the last qubit - return qml.sample(wires = 3) - -@qml.qnode(dev) -def circuit2(): - # Circuit used to derive a2 - # Initialize x = [0,0,1] - qml.PauliX(wires = 2) - - # We apply our oracle - Uf() - - # We measure the last qubit - return qml.sample(wires = 3) - -# We run for x = [1,0,0] -a0 = circuit0() - -# We run for x = [0,1,0] -a1 = circuit1() - -# We run for x = [0,0,1] -a2 = circuit2() - -print(f"The value of 'a' is [{a0},{a1},{a2}]") - -############################################################################## -# -# In this case, with 3 queries (:math:`n=3`), we have discovered the value of :math:`\vec{a}.` Let's run the Bernstein–Vazirani subroutine (using qubits as qubits this time) to check that one call is enough: - - -@qml.qnode(dev) -def circuit(): - - # We initialize to |0001> - qml.PauliX(wires = 3) - - # We run the Hadamards - for i in range(4): - qml.Hadamard(wires = i) - - # We apply our function - Uf() - - # We run the Hadamards - for i in range(3): - qml.Hadamard(wires = i) - - # We measure the first 3 qubits - return qml.sample(wires = range(3)) - -a = circuit() - -print(f"The value of a is {a}") - - -############################################################################## -# Great! Everything works as expected, and we have successfully executed the Bernstein–Vazirani algorithm. -# It is important to note that, because of how we defined our device, we are only using a single shot to find this value! -# -# Generalization to qutrits -# ------------------------------ -# -# To make things more interesting, let's imagine a new scenario. We are given a function of the form :math:`f(\vec{x}) := \vec{a}\cdot\vec{x} \pmod 3` where, :math:`\vec{a}=(a_0,a_1,...,a_{n-1})` and :math:`\vec{x}=(x_0,x_1,...,x_{n-1})` are strings of length :math:`n` with :math:`a_i, x_i \in \{0,1,2\}`. How can we minimize the number of calls to the function to discover :math:`\vec{a}?` In this case, the classical procedure to detect the value of :math:`\vec{a}` is the same as in the case of qubits: we will evaluate the output of the inputs :math:`[1,0,0],` :math:`[0,1,0]` and :math:`[0,0,1].` -# -# But how can we work with these kinds of functions in a simple way? To do this we must use a qutrit and its operators. -# By using this new unit of information and unlocking the third orthogonal state, we will have states represented with a vector of dimension :math:`3^n` and the operators will be :math:`3^n \times 3^n` matrices where :math:`n` is the number of qutrits. -# Specifically, we will use the :class:`~.pennylane.TShift` gate, which is equivalent to the :class:`~.pennylane.PauliX` gate for qutrits. It has the following property: -# -# .. math:: -# \text{TShift}|0\rangle = |1\rangle -# -# .. math:: -# \text{TShift}|1\rangle = |2\rangle -# -# .. math:: -# \text{TShift}|2\rangle = |0\rangle -# -# This means we can use this gate to initialize each of the states. -# Another gate that we will use for the oracle definition is the :class:`~.pennylane.TAdd` gate, which is the generalization of the :class:`~.pennylane.Toffoli` gate for qutrits. -# These generalizations simply adjust the addition operation to be performed in modulo 3 instead of modulo 2. -# So, with these ingredients, we are ready to go to the code. - -dev = qml.device("default.qutrit", wires=4, shots=1) - -def Uf(): - # The oracle in charge of encoding a hidden "a" value. - qml.TAdd(wires = [1,3]) - qml.TAdd(wires = [1,3]) - qml.TAdd(wires = [2,3]) - -@qml.qnode(dev) -def circuit0(): - - # Initialize x = [1,0,0] - qml.TShift(wires = 0) - - # We apply our oracle - Uf() - - # We measure the last qutrit - return qml.sample(wires = 3) - -@qml.qnode(dev) -def circuit1(): - - # Initialize x = [0,1,0] - qml.TShift(wires = 1) - - # We apply our oracle - Uf() - - # We measure the last qutrit - return qml.sample(wires = 3) - -@qml.qnode(dev) -def circuit2(): - - # Initialize x = [0,0,1] - qml.TShift(wires = 2) - - # We apply our oracle - Uf() - - # We measure the last qutrit - return qml.sample(wires = 3) - -# Run to obtain the three trits of a -a0 = circuit0() -a1 = circuit1() -a2 = circuit2() - - -print(f"The value of a is [{a0},{a1},{a2}]") - -############################################################################## -# -# The question is, can we perform the same procedure as we have done before to find :math:`\vec{a}` using a single shot? The Hadamard gate also generalizes to qutrits (also denoted as :class:`~.pennylane.THadamard`), so we could try to simply substitute it and see what happens! -# -# -# The definition of the Hadamard gate in this space is: -# -# .. math:: -# \text{THadamard}=\frac{-i}{\sqrt{3}}\begin{pmatrix} -# 1 & 1 & 1\\ -# 1 & w & w^2\\ -# 1 & w^2 & w -# \end{pmatrix}, -# -# where :math:`w = e^{\frac{2 \pi i}{3}}.` -# Let's go to the code and see how to run this in PennyLane. - - -@qml.qnode(dev) -def circuit(): - - # We initialize to |0001> - qml.TShift(wires = 3) - - # We run the THadamard - for i in range(4): - qml.THadamard(wires = i) - -# We run the oracle - Uf() - -# We run the THadamard again - for i in range(3): - qml.THadamard(wires = i) - - # We measure the first 3 qutrits - return qml.sample(wires = range(3)) - -a = circuit() - -print(f"The value of a is {a}") - -############################################################################## -# -# Awesome! The Bernstein–Vazirani algorithm generalizes perfectly to qutrits! Let's do the mathematical calculations again to check that it does indeed make sense. -# -# As before, the input of our circuit is :math:`|0001\rangle.` -# We will then use the Hadamard definition applied to qutrits: -# -# .. math:: -# H^{\otimes n}|\vec{x}\rangle = \frac{1}{\sqrt{3^n}}\sum_{\vec{z} \in \{0,1,2\}^n}w^{\vec{x}\cdot\vec{z}}|\vec{z}\rangle. -# -# In this case, we are disregarding the global phase of :math:`-i` for simplicity. -# Applying this to the state :math:`|0001\rangle,` we obtain -# -# .. math:: -# |\phi_1\rangle=H^{\otimes 4}|0001\rangle = H^{\otimes 3}|000\rangle\otimes H|1\rangle = \frac{1}{\sqrt{3^3}}\left(\sum_{z \in \{0,1,2\}^3}|\vec{z}\rangle\frac{|0\rangle+w|1\rangle+w^2|2\rangle}{\sqrt{3}}\right). -# -# After that, we apply the operator :math:`U_f` to obtain -# -# .. math:: -# |\phi_2\rangle= U_f |\phi_1\rangle = \frac{1}{\sqrt{3^3}}\left(\sum_{\vec{z} \in \{0,1,2\}^3}|\vec{z}\rangle\frac{|0 + \vec{a}\cdot\vec{z} \pmod 3 \rangle+w|1+ \vec{a}\cdot\vec{z} \pmod 3 \rangle+w^2|2+ \vec{a}\cdot\vec{z} \pmod 3 \rangle}{\sqrt{3}}\right). -# -# Depending on the value of :math:`f(\vec{x}),` as before, we obtain three possible states: -# -# - If :math:`\vec{a}\cdot\vec{z} = 0,` we have :math:`\frac{1}{\sqrt{3}}\left(|0\rangle+w|1\rangle+w^2|2\rangle\right).` -# - If :math:`\vec{a}\cdot\vec{z} = 1,` we have :math:`\frac{w^2}{\sqrt{3}}\left(|0\rangle+|1\rangle+w|2\rangle\right).` -# - If :math:`\vec{a}\cdot\vec{z} = 2,` we have :math:`\frac{w}{\sqrt{3}}\left(|0\rangle+w^2|1\rangle+|2\rangle\right).` -# -# Based on this, we can group the three states as :math:`\frac{1}{\sqrt{3}}w^{-\vec{a}\cdot\vec{z}}\left(|0\rangle+w|1\rangle+w^2|2\rangle\right).` -# -# After this, we can enter the coefficient in the :math:`|\vec{z}\rangle` term and, as before, disregard the last qutrit, since we are not going to use it again: -# -# .. math:: -# |\phi_2\rangle =\frac{1}{\sqrt{3^3}}\sum_{\vec{z} \in \{0,1,2\}^3}w^{-\vec{a}\cdot\vec{z}}|\vec{z}\rangle. -# -# Finally, we reapply the THadamard: -# -# .. math:: -# |\phi_3\rangle := H^{\otimes 3}|\phi_2\rangle = \frac{1}{3^3}\sum_{\vec{z} \in \{0,1,2\}^3}w^{-\vec{a}\cdot\vec{z}}\left(\sum_{\vec{y} \in \{0,1,2\}^3}w^{\vec{z}\cdot\vec{y}}|\vec{y}\rangle\right). -# -# Rearranging this expression, we obtain: -# -# .. math:: -# |\phi_3\rangle = \frac{1}{3^3}\sum_{\vec{y} \in \{0,1,2\}^3}\left(\sum_{\vec{z} \in \{0,1,2\}^3}w^{-\vec{a}\cdot\vec{z}+\vec{y}\cdot\vec{z}}\right)|\vec{y}\rangle. -# -# -# In the same way as before, it can be easily checked that :math:`\langle \vec{a}|\phi_3\rangle = 1` and therefore, when measuring, one shot will be enough to obtain the value of :math:`\vec{a}!` -# -# Conclusion -# ---------- -# -# In this demo, we have practised the use of basic qutrit gates such as TShift or THadamard by applying the Bernstein–Vazirani algorithm. In this case, the generalization has been straightforward and we have found that it makes mathematical sense, but we cannot always substitute qubit gates for qutrit gates as we have seen in the demo. To give an easy example of this, we know the property that :math:`X = HZH`, but it turns out that this property does not generalize! The general property is actually :math:`X = H^{\dagger}ZH.` In the case of qubits it holds that :math:`H = H^{\dagger},` but in other dimensions it does not. I invite you to continue practising with other types of algorithms. For instance, will the `Deutsch–Jozsa algorithm `__ generalize well? Take a pen and paper and check it out! -# -# References -# ---------- -# -# .. [#bv] -# -# Ethan Bernstein, Umesh Vazirani, "Quantum Complexity Theory". `SIAM Journal on Computing Vol. 26 (1997). -# `__ -# -# .. [#toffoli_qutrits] -# Alexey Galda, Michael Cubeddu, Naoki Kanazawa, Prineha Narang, Nathan Earnest-Noble, `"Implementing a Ternary Decomposition of the Toffoli Gate on Fixed-FrequencyTransmon Qutrits". -# `__ -# About the author -# ---------------- -# - +r""" + +Qutrits and quantum algorithms +============================== + +.. meta:: + :property="og:description": Learn how to interpret the Bernstein-Vazirani algorithm with qutrits + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_qutrits_bernstein_vazirani.png + +.. related:: + + +*Author: Guillermo Alonso-Linaje — Posted: 9 May 2023. Last updated: 9 May 2023.* + +A qutrit is a basic quantum unit that can exist in a superposition of three possible quantum states, represented as :math:`|0\rangle`, :math:`|1\rangle`, and :math:`|2\rangle,` which functions as a generalization of the qubit. +There are many problems to which we can apply these units, among which we can highlight an improved decomposition of the Toffoli gate. +Using only qubits, it would take at least 6 CNOTs to decompose the gate, whereas with qutrits it would be enough to use 3 [#toffoli_qutrits]_. +This is one of the reasons why it is important to develop the intuition behind this basic unit of information, to see where qutrits can provide an advantage. The goal of this demo is to start working with qutrits from an algorithmic point of view. To do so, we will start with the Bernstein–Vazirani algorithm, which we will initially explore using qubits, and later using qutrits. + + + +Bernstein–Vazirani algorithm +------------------------------ + +The Bernstein–Vazirani algorithm is a quantum algorithm developed by Ethan Bernstein and Umesh Vazirani [#bv]_. +It was one of the first examples that demonstrated an exponential advantage of a quantum computer over a traditional one. So, in this first section we will understand the problem that they tackled. + + +Suppose there is some hidden bit string "a" that we are trying to learn, and that we have access to a function :math:`f(\vec{x})` that implements the following scalar product: + +.. math:: + f(\vec{x}) := \vec{a}\cdot\vec{x} \pmod 2, + +where :math:`\vec{a}=(a_0,a_1,...,a_{n-1})` and :math:`\vec{x}=(x_0,x_1,...,x_{n-1})` are bit strings of length :math:`n` with :math:`a_i, x_i \in \{0,1\}`. Our challenge will be to discover the hidden value of :math:`\vec{a}` by using the function :math:`f.` We don't know anything about :math:`\vec{a}` so the only thing we can do is to evaluate :math:`f` at different points :math:`\vec{x}` with the idea of gaining hidden information. + + +To give an example, let's imagine that we take :math:`\vec{x}=(1,0,1)` and get the value :math:`f(\vec{x}) = 0`. Although it may not seem obvious, knowing the structure that :math:`f` has, this gives us some information about :math:`\vec{a}.` In this case, :math:`a_0` and :math:`a_2` have the same value. This is because taking that value of :math:`\vec{x}`, the function will be equivalent to :math:`a_0 + a_2 \pmod 2,` which will only take the value 0 if they are equal. +I invite you to take your time to think of a possible strategy (at the classical level) in order to determine :math:`\vec{a}` with the minimum number of evaluations of the function :math:`f.` + +The optimal solution requires only :math:`n` calls to the function! Let's see how we can do this. +Knowing the form of :math:`\vec{a}` and :math:`\vec{x},` we can rewrite :math:`f` as: + +.. math:: + f(\vec{x})=\sum_{i=0}^{n-1}a_ix_i \pmod 2. + +The strategy will be to deduce one element of :math:`\vec{a}` with each call to the function. Imagine that we want to determine the value :math:`a_i.` We can simply choose :math:`\vec{x}` as a vector of all zeros except a one in the i-th position, since in this case: + + + +.. math:: + f(\vec{x})= 0\cdot a_0 + 0\cdot a_1 + ... + 1\cdot a_i + ... + 0\cdot a_{n-1} \pmod 2 \quad= a_i. + +It is trivial to see, therefore, that :math:`n` evaluations of :math:`f` are needed. The question is: can we do it more efficiently with a quantum computer? The answer is yes, and in fact, we only need to make one call to the function! + +The first step is to see how we can represent this statement in a circuit. In this case, we will assume that we have an oracle :math:`U_f` that encodes the function, as we can see in the picture below. + + +.. figure:: ../_static/demonstration_assets/qutrits_bernstein_vazirani/oracle_qutrit.jpg + :scale: 35% + :alt: Oracle definition. + :align: center + + Oracle representation of the function. + + +In general, :math:`U_f` sends the state :math:`|\vec{x} \rangle |y\rangle` to the state :math:`| \vec{x} \rangle |y + \vec{a} \cdot \vec{x} \pmod{2} \rangle.` + +Suppose, for example, that :math:`\vec{a}=[0,1,0]`. Then :math:`U_f|111\rangle |0\rangle = |111\rangle|1\rangle`, since we are evaluating :math:`f` at the point :math:`\vec{x} = [1,1,1]`. The scalar product between the two values is :math:`1,` so the last qubit of the output will take the value :math:`1.` + +The Bernstein–Vazirani algorithm makes use of this oracle according to the following circuit: + +.. figure:: ../_static/demonstration_assets/qutrits_bernstein_vazirani/bernstein_vazirani_algorithm.jpg + :scale: 35% + :alt: Bernstein-Vazirani's algorithm + :align: center + + Bernstein–Vazirani algorithm. + + +What we can see is that, by simply using Hadamard gates before and after the oracle, after a single run, the output of the circuit is exactly the hidden value of :math:`\vec{a}.` Let's do a little math to verify that this is so. + +First, the input to our circuit is :math:`|0001\rangle.` The second step is to apply Hadamard gates to this state, and for this we must use the following property: + +.. math:: + H^{\otimes n}|\vec{x}\rangle = \frac{1}{\sqrt{2^n}}\sum_{\vec{z} \in \{0,1\}^n}(-1)^{\vec{x}\cdot\vec{z}}|\vec{z}\rangle. + +Taking as input the value :math:`|0001\rangle,` we obtain the state + +.. math:: + |\phi_1\rangle=H^{\otimes 4}|0001\rangle = H^{\otimes 3}|000\rangle\otimes H|1\rangle = \frac{1}{\sqrt{2^3}}\left(\sum_{z \in \{0,1\}^3}|\vec{z}\rangle\right)\left(\frac{|0\rangle-|1\rangle}{\sqrt{2}}\right). + +As you can see, we have separated the first three qubits from the fourth for clarity. +If we now apply our operator :math:`U_f,` + +.. math:: + |\phi_2\rangle= U_f |\phi_1\rangle = \frac{1}{\sqrt{2^3}}\left(\sum_{\vec{z} \in \{0,1\}^3}|\vec{z}\rangle\frac{|\vec{a}\cdot\vec{z} \pmod 2\rangle-|1 + \vec{a}\cdot\vec{z} \pmod 2\rangle}{\sqrt{2}}\right). + +Depending on the value of :math:`f(\vec{x}),` the final part of the expression can take two values and it can be checked that + +.. math:: + |\phi_2\rangle = \frac{1}{\sqrt{2^3}}\left(\sum_{\vec{z} \in \{0,1\}^3}|\vec{z}\rangle(-1)^{\vec{a}\cdot\vec{z}}\frac{|0\rangle-|1\rangle}{\sqrt{2}}\right). + +This is because, if :math:`\vec{a}\cdot\vec{z}` takes the value :math:`0`, we will have the :math:`\frac{|0\rangle - |1\rangle}{\sqrt{2}}`, and if it takes the value :math:`1,` the result will be :math:`\frac{|1\rangle - |0\rangle}{\sqrt{2}} = - \frac{|0\rangle - |1\rangle}{\sqrt{2}}.` Therefore, by calculating :math:`(-1)^{\vec{a}\cdot\vec{z}}` we cover both cases. +After this, we can include the :math:`(-1)^{\vec{a}\cdot\vec{z}}` factor in the :math:`|\vec{z}\rangle` term and disregard the last qubit, since we are not going to use it again: + +.. math:: + |\phi_2\rangle =\frac{1}{\sqrt{2^3}}\sum_{\vec{z} \in \{0,1\}^3}(-1)^{\vec{a}\cdot\vec{z}}|\vec{z}\rangle. + +Note that you cannot always disregard a qubit. In cases where there is entanglement with other qubits that is not possible, but in this case the state is separable. + +Finally, we will reapply the property of the first step to calculate the result after using the Hadamard: + +.. math:: + |\phi_3\rangle = H^{\otimes 3}|\phi_2\rangle = \frac{1}{2^3}\sum_{\vec{z} \in \{0,1\}^3}(-1)^{\vec{a}\cdot\vec{z}}\left(\sum_{\vec{y} \in \{0,1\}^3}(-1)^{\vec{z}\cdot\vec{y}}|\vec{y}\rangle\right). + +Rearranging this expression, we obtain: + +.. math:: + |\phi_3\rangle = \frac{1}{2^3}\sum_{\vec{y} \in \{0,1\}^3}\left(\sum_{\vec{z} \in \{0,1\}^3}(-1)^{\vec{a}\cdot\vec{z}+\vec{y}\cdot\vec{z}}\right)|\vec{y}\rangle. + +Perfect! The only thing left to check is that, in fact, the previous state is exactly :math:`|\vec{a}\rangle`. It may seem complicated, but I invite you to demonstrate it by showing that :math:`\langle \vec{a}|\phi_3\rangle = 1.` Let's go to the code and check that it works. + +Algorithm coding with qubits +------------------------------ + +We will first code the classical solution. We will do this inside a quantum circuit to understand how to use the oracle, but we are really just programming the qubits as bits. + +""" + + +import pennylane as qml + +dev = qml.device("default.qubit", wires = 4, shots = 1) + +def Uf(): + # The oracle in charge of encoding a hidden "a" value. + qml.CNOT(wires=[1, 3]) + qml.CNOT(wires=[2 ,3]) + + +@qml.qnode(dev) +def circuit0(): + """Circuit used to derive a0""" + + + # Initialize x = [1,0,0] + qml.PauliX(wires = 0) + + # Apply our oracle + + Uf() + + # We measure the last qubit + return qml.sample(wires = 3) + +@qml.qnode(dev) +def circuit1(): + # Circuit used to derive a1 + + # Initialize x = [0,1,0] + qml.PauliX(wires = 1) + + # We apply our oracle + Uf() + + # We measure the last qubit + return qml.sample(wires = 3) + +@qml.qnode(dev) +def circuit2(): + # Circuit used to derive a2 + # Initialize x = [0,0,1] + qml.PauliX(wires = 2) + + # We apply our oracle + Uf() + + # We measure the last qubit + return qml.sample(wires = 3) + +# We run for x = [1,0,0] +a0 = circuit0() + +# We run for x = [0,1,0] +a1 = circuit1() + +# We run for x = [0,0,1] +a2 = circuit2() + +print(f"The value of 'a' is [{a0},{a1},{a2}]") + +############################################################################## +# +# In this case, with 3 queries (:math:`n=3`), we have discovered the value of :math:`\vec{a}.` Let's run the Bernstein–Vazirani subroutine (using qubits as qubits this time) to check that one call is enough: + + +@qml.qnode(dev) +def circuit(): + + # We initialize to |0001> + qml.PauliX(wires = 3) + + # We run the Hadamards + for i in range(4): + qml.Hadamard(wires = i) + + # We apply our function + Uf() + + # We run the Hadamards + for i in range(3): + qml.Hadamard(wires = i) + + # We measure the first 3 qubits + return qml.sample(wires = range(3)) + +a = circuit() + +print(f"The value of a is {a}") + + +############################################################################## +# Great! Everything works as expected, and we have successfully executed the Bernstein–Vazirani algorithm. +# It is important to note that, because of how we defined our device, we are only using a single shot to find this value! +# +# Generalization to qutrits +# ------------------------------ +# +# To make things more interesting, let's imagine a new scenario. We are given a function of the form :math:`f(\vec{x}) := \vec{a}\cdot\vec{x} \pmod 3` where, :math:`\vec{a}=(a_0,a_1,...,a_{n-1})` and :math:`\vec{x}=(x_0,x_1,...,x_{n-1})` are strings of length :math:`n` with :math:`a_i, x_i \in \{0,1,2\}`. How can we minimize the number of calls to the function to discover :math:`\vec{a}?` In this case, the classical procedure to detect the value of :math:`\vec{a}` is the same as in the case of qubits: we will evaluate the output of the inputs :math:`[1,0,0],` :math:`[0,1,0]` and :math:`[0,0,1].` +# +# But how can we work with these kinds of functions in a simple way? To do this we must use a qutrit and its operators. +# By using this new unit of information and unlocking the third orthogonal state, we will have states represented with a vector of dimension :math:`3^n` and the operators will be :math:`3^n \times 3^n` matrices where :math:`n` is the number of qutrits. +# Specifically, we will use the :class:`~.pennylane.TShift` gate, which is equivalent to the :class:`~.pennylane.PauliX` gate for qutrits. It has the following property: +# +# .. math:: +# \text{TShift}|0\rangle = |1\rangle +# +# .. math:: +# \text{TShift}|1\rangle = |2\rangle +# +# .. math:: +# \text{TShift}|2\rangle = |0\rangle +# +# This means we can use this gate to initialize each of the states. +# Another gate that we will use for the oracle definition is the :class:`~.pennylane.TAdd` gate, which is the generalization of the :class:`~.pennylane.Toffoli` gate for qutrits. +# These generalizations simply adjust the addition operation to be performed in modulo 3 instead of modulo 2. +# So, with these ingredients, we are ready to go to the code. + +dev = qml.device("default.qutrit", wires=4, shots=1) + +def Uf(): + # The oracle in charge of encoding a hidden "a" value. + qml.TAdd(wires = [1,3]) + qml.TAdd(wires = [1,3]) + qml.TAdd(wires = [2,3]) + +@qml.qnode(dev) +def circuit0(): + + # Initialize x = [1,0,0] + qml.TShift(wires = 0) + + # We apply our oracle + Uf() + + # We measure the last qutrit + return qml.sample(wires = 3) + +@qml.qnode(dev) +def circuit1(): + + # Initialize x = [0,1,0] + qml.TShift(wires = 1) + + # We apply our oracle + Uf() + + # We measure the last qutrit + return qml.sample(wires = 3) + +@qml.qnode(dev) +def circuit2(): + + # Initialize x = [0,0,1] + qml.TShift(wires = 2) + + # We apply our oracle + Uf() + + # We measure the last qutrit + return qml.sample(wires = 3) + +# Run to obtain the three trits of a +a0 = circuit0() +a1 = circuit1() +a2 = circuit2() + + +print(f"The value of a is [{a0},{a1},{a2}]") + +############################################################################## +# +# The question is, can we perform the same procedure as we have done before to find :math:`\vec{a}` using a single shot? The Hadamard gate also generalizes to qutrits (also denoted as :class:`~.pennylane.THadamard`), so we could try to simply substitute it and see what happens! +# +# +# The definition of the Hadamard gate in this space is: +# +# .. math:: +# \text{THadamard}=\frac{-i}{\sqrt{3}}\begin{pmatrix} +# 1 & 1 & 1\\ +# 1 & w & w^2\\ +# 1 & w^2 & w +# \end{pmatrix}, +# +# where :math:`w = e^{\frac{2 \pi i}{3}}.` +# Let's go to the code and see how to run this in PennyLane. + + +@qml.qnode(dev) +def circuit(): + + # We initialize to |0001> + qml.TShift(wires = 3) + + # We run the THadamard + for i in range(4): + qml.THadamard(wires = i) + +# We run the oracle + Uf() + +# We run the THadamard again + for i in range(3): + qml.THadamard(wires = i) + + # We measure the first 3 qutrits + return qml.sample(wires = range(3)) + +a = circuit() + +print(f"The value of a is {a}") + +############################################################################## +# +# Awesome! The Bernstein–Vazirani algorithm generalizes perfectly to qutrits! Let's do the mathematical calculations again to check that it does indeed make sense. +# +# As before, the input of our circuit is :math:`|0001\rangle.` +# We will then use the Hadamard definition applied to qutrits: +# +# .. math:: +# H^{\otimes n}|\vec{x}\rangle = \frac{1}{\sqrt{3^n}}\sum_{\vec{z} \in \{0,1,2\}^n}w^{\vec{x}\cdot\vec{z}}|\vec{z}\rangle. +# +# In this case, we are disregarding the global phase of :math:`-i` for simplicity. +# Applying this to the state :math:`|0001\rangle,` we obtain +# +# .. math:: +# |\phi_1\rangle=H^{\otimes 4}|0001\rangle = H^{\otimes 3}|000\rangle\otimes H|1\rangle = \frac{1}{\sqrt{3^3}}\left(\sum_{z \in \{0,1,2\}^3}|\vec{z}\rangle\frac{|0\rangle+w|1\rangle+w^2|2\rangle}{\sqrt{3}}\right). +# +# After that, we apply the operator :math:`U_f` to obtain +# +# .. math:: +# |\phi_2\rangle= U_f |\phi_1\rangle = \frac{1}{\sqrt{3^3}}\left(\sum_{\vec{z} \in \{0,1,2\}^3}|\vec{z}\rangle\frac{|0 + \vec{a}\cdot\vec{z} \pmod 3 \rangle+w|1+ \vec{a}\cdot\vec{z} \pmod 3 \rangle+w^2|2+ \vec{a}\cdot\vec{z} \pmod 3 \rangle}{\sqrt{3}}\right). +# +# Depending on the value of :math:`f(\vec{x}),` as before, we obtain three possible states: +# +# - If :math:`\vec{a}\cdot\vec{z} = 0,` we have :math:`\frac{1}{\sqrt{3}}\left(|0\rangle+w|1\rangle+w^2|2\rangle\right).` +# - If :math:`\vec{a}\cdot\vec{z} = 1,` we have :math:`\frac{w^2}{\sqrt{3}}\left(|0\rangle+|1\rangle+w|2\rangle\right).` +# - If :math:`\vec{a}\cdot\vec{z} = 2,` we have :math:`\frac{w}{\sqrt{3}}\left(|0\rangle+w^2|1\rangle+|2\rangle\right).` +# +# Based on this, we can group the three states as :math:`\frac{1}{\sqrt{3}}w^{-\vec{a}\cdot\vec{z}}\left(|0\rangle+w|1\rangle+w^2|2\rangle\right).` +# +# After this, we can enter the coefficient in the :math:`|\vec{z}\rangle` term and, as before, disregard the last qutrit, since we are not going to use it again: +# +# .. math:: +# |\phi_2\rangle =\frac{1}{\sqrt{3^3}}\sum_{\vec{z} \in \{0,1,2\}^3}w^{-\vec{a}\cdot\vec{z}}|\vec{z}\rangle. +# +# Finally, we reapply the THadamard: +# +# .. math:: +# |\phi_3\rangle := H^{\otimes 3}|\phi_2\rangle = \frac{1}{3^3}\sum_{\vec{z} \in \{0,1,2\}^3}w^{-\vec{a}\cdot\vec{z}}\left(\sum_{\vec{y} \in \{0,1,2\}^3}w^{\vec{z}\cdot\vec{y}}|\vec{y}\rangle\right). +# +# Rearranging this expression, we obtain: +# +# .. math:: +# |\phi_3\rangle = \frac{1}{3^3}\sum_{\vec{y} \in \{0,1,2\}^3}\left(\sum_{\vec{z} \in \{0,1,2\}^3}w^{-\vec{a}\cdot\vec{z}+\vec{y}\cdot\vec{z}}\right)|\vec{y}\rangle. +# +# +# In the same way as before, it can be easily checked that :math:`\langle \vec{a}|\phi_3\rangle = 1` and therefore, when measuring, one shot will be enough to obtain the value of :math:`\vec{a}!` +# +# Conclusion +# ---------- +# +# In this demo, we have practised the use of basic qutrit gates such as TShift or THadamard by applying the Bernstein–Vazirani algorithm. In this case, the generalization has been straightforward and we have found that it makes mathematical sense, but we cannot always substitute qubit gates for qutrit gates as we have seen in the demo. To give an easy example of this, we know the property that :math:`X = HZH`, but it turns out that this property does not generalize! The general property is actually :math:`X = H^{\dagger}ZH.` In the case of qubits it holds that :math:`H = H^{\dagger},` but in other dimensions it does not. I invite you to continue practising with other types of algorithms. For instance, will the `Deutsch–Jozsa algorithm `__ generalize well? Take a pen and paper and check it out! +# +# References +# ---------- +# +# .. [#bv] +# +# Ethan Bernstein, Umesh Vazirani, "Quantum Complexity Theory". `SIAM Journal on Computing Vol. 26 (1997). +# `__ +# +# .. [#toffoli_qutrits] +# Alexey Galda, Michael Cubeddu, Naoki Kanazawa, Prineha Narang, Nathan Earnest-Noble, `"Implementing a Ternary Decomposition of the Toffoli Gate on Fixed-FrequencyTransmon Qutrits". +# `__ +# About the author +# ---------------- +# + diff --git a/demonstrations/tutorial_resource_estimation.py b/demonstrations/tutorial_resource_estimation.py index 0c033e4dee..8deb382e9c 100644 --- a/demonstrations/tutorial_resource_estimation.py +++ b/demonstrations/tutorial_resource_estimation.py @@ -1,321 +1,321 @@ -r""" - -Resource estimation for quantum chemistry -========================================= - -.. meta:: - :property="og:description": Learn how to estimate the number of qubits and gates needed to - implement quantum algorithms - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/resource_estimation.jpeg - -.. related:: - tutorial_quantum_chemistry Quantum chemistry with PennyLane - tutorial_vqe A brief overview of VQE - - -*Author: Soran Jahangiri — Posted: 21 November 2022.* - -Quantum algorithms such as -`quantum phase estimation `_ -(QPE) and the `variational quantum eigensolver `_ (VQE) -are widely studied in quantum chemistry as potential avenues to tackle problems that are intractable -for conventional computers. However, we currently do not have quantum computers or simulators -capable of implementing large-scale -versions of these algorithms. This makes it difficult to properly explore their accuracy and -efficiency for problem sizes where the actual advantage of quantum algorithms can potentially occur. -Despite these difficulties, it is still possible to perform **resource estimation** -to assess what we need to implement such quantum algorithms. - -In this demo, we describe how to use PennyLane's resource estimation functionality to estimate the total number of logical qubits and gates required to -implement the QPE algorithm for simulating molecular Hamiltonians represented in first and second -quantization. We focus on `non-Clifford gates `_, which -are the most expensive to implement in a fault-tolerant setting. We also explain how to estimate the -total number of measurements needed to compute expectation values using algorithms such as VQE. - -Quantum Phase Estimation ------------------------- -The QPE algorithm can be used to compute the phase associated with an eigenstate of a unitary -operator. For the purpose of quantum simulation, the unitary operator :math:`U` can be chosen to -share eigenvectors with a molecular Hamiltonian :math:`H,` for example by setting -:math:`U = e^{-iH}.` Estimating the phase of such a unitary then permits recovering the -corresponding eigenvalue of the Hamiltonian. A conceptual QPE circuit diagram is shown below. - -.. figure:: /_static/demonstration_assets/resource_estimation/qpe.png - :width: 60% - :align: center - - Circuit representing the quantum phase estimation algorithm. - -For most cases of interest, this algorithm requires more qubits and longer circuit depths than what -can be implemented on existing hardware. The PennyLane functionality in the -:mod:`qml.resource ` module allows us to estimate the number of logical qubits -and the number of non-Clifford gates that are needed to implement the algorithm. We can estimate -these resources by simply defining system specifications and a target error for estimation. Let's -see how! - -QPE cost for simulating molecules -********************************* -We study the double low-rank Hamiltonian factorization algorithm of [#vonburg2021]_ and use its cost -equations as provided in APPENDIX C of [#lee2021]_. -This algorithm requires the one- and two-electron -`integrals `_ -as input. These integrals can be obtained in different ways and here we use PennyLane to compute -them. We first need to define the atomic symbols and coordinates for the given molecule. Let's use -the water molecule at its equilibrium geometry with the -`6-31g basis set `_ as an example. -""" -import pennylane as qml -import numpy as np -import jax - -jax.config.update("jax_enable_x64", True) - -symbols = ['O', 'H', 'H'] -geometry = np.array([[0.00000000, 0.00000000, 0.28377432], - [0.00000000, 1.45278171, -1.00662237], - [0.00000000, -1.45278171, -1.00662237]]) - -############################################################################## -# Then we construct a molecule object and compute the one- and two-electron -# integrals in the molecular orbital basis. - -mol = qml.qchem.Molecule(symbols, geometry, basis_name='6-31g') -core, one, two = qml.qchem.electron_integrals(mol)() - -############################################################################## -# We now create an instance of the :class:`~.pennylane.resource.DoubleFactorization` class - -algo = qml.resource.DoubleFactorization(one, two) - -############################################################################## -# and obtain the estimated number of non-Clifford gates and logical qubits. - -print(f'Estimated gates : {algo.gates:.2e} \nEstimated qubits: {algo.qubits}') - -############################################################################## -# This estimation is for a target error that is set to the chemical accuracy, 0.0016 -# :math:`\text{Ha},` by default. We can change the target error to a larger value which leads to a -# smaller number of non-Clifford gates and logical qubits. - -chemical_accuracy = 0.0016 -error = chemical_accuracy * 10 -algo = qml.resource.DoubleFactorization(one, two, error=error) -print(f'Estimated gates : {algo.gates:.2e} \nEstimated qubits: {algo.qubits}') - -############################################################################## -# We can also estimate the number of non-Clifford gates with respect to the threshold error values -# for discarding the negligible factors in the factorized Hamiltonian [#vonburg2021]_ and plot the -# estimated numbers. - -threshold = [10**-n for n in range(10)] -n_gates = [] -n_qubits = [] - -for tol in threshold: - algo_ = qml.resource.DoubleFactorization(one, two, tol_factor=tol, tol_eigval=tol) - n_gates.append(algo_.gates) - n_qubits.append(algo_.qubits) - -import matplotlib.pyplot as plt - -fig, ax = plt.subplots(figsize=(5, 3)) -ax.plot(threshold, n_gates, ':o', markerfacecolor='none', color='teal') - -ax.set_ylabel('n gates') -ax.set_xlabel('threshold') -ax.set_xscale('log') -fig.tight_layout() - -############################################################################## -# QPE cost for simulating periodic materials -# ****************************************** -# For periodic materials, we estimate the cost of implementing the QPE algorithm of [#zini2023]_ -# using Hamiltonians represented in first quantization and in a plane wave basis. We first need to -# define the number of plane waves, the number of electrons, and the lattice vectors that construct -# the unit cell of the periodic material. Let's use dilithium iron silicate -# :math:`\text{Li}_2\text{FeSiO}_4` as an example taken from [#delgado2022]_. For this material, the -# unit cell contains 156 electrons and has dimensions :math:`9.49 \times 10.20 \times 11.83` in -# `atomic units `_. We also use :math:`10^5` plane waves. - -planewaves = 100000 -electrons = 156 -vectors = np.array([[9.49, 0.00, 0.00], - [0.00, 10.20, 0.00], - [0.00, 0.00, 11.83]]) - -############################################################################## -# We now create an instance of the :class:`~.pennylane.resource.FirstQuantization` class -algo = qml.resource.FirstQuantization(planewaves, electrons, vectors=vectors) - -############################################################################## -# and obtain the estimated number of non-Clifford gates and logical qubits. -print(f'Estimated gates : {algo.gates:.2e} \nEstimated qubits: {algo.qubits}') - -############################################################################## -# We can also plot the estimated numbers as a function of the number of plane waves for different -# target errors - -error = [0.1, 0.01, 0.001] # in atomic units -planewaves = [10 ** n for n in range(1, 10)] -n_gates = [] -n_qubits = [] - -for er in error: - n_gates_ = [] - n_qubits_ = [] - - for pw in planewaves: - algo_ = qml.resource.FirstQuantization(pw, electrons, vectors=vectors, error=er) - n_gates_.append(algo_.gates) - n_qubits_.append(algo_.qubits) - n_gates.append(n_gates_) - n_qubits.append(n_qubits_) - -fig, ax = plt.subplots(2, 1) - -for i in range(len(n_gates)): - ax[0].plot(planewaves, n_gates[i], ':o', markerfacecolor='none', label=error[i]) -ax[1].plot(planewaves, n_qubits[i], ':o', markerfacecolor='none', label=error[-1]) - -ax[0].set_ylabel('n gates') -ax[1].set_ylabel('n qubits') - -for i in [0, 1]: - ax[i].set_xlabel('n planewaves') - ax[i].tick_params(axis='x') - ax[0].set_yscale('log') - ax[i].set_xscale('log') - ax[i].legend(title='error [Ha]') - -fig.tight_layout() - -############################################################################## -# The algorithm uses a decomposition of the Hamiltonian as a linear combination of unitaries, -# -# .. math:: H=\sum_{i} c_i U_i. -# -# The parameter :math:`\lambda=\sum_i c_i,` which can be interpreted as the 1-norm of the -# Hamiltonian, plays an important role in determining the cost of implementing the QPE -# algorithm [#delgado2022]_. In PennyLane, the 1-norm of the Hamiltonian can be obtained with - -print(f'1-norm of the Hamiltonian: {algo.lamb}') - -############################################################################## -# PennyLane allows you to get more detailed information about the cost of the algorithms as -# explained in the documentation of :class:`~.pennylane.resource.FirstQuantization` -# and :class:`~.pennylane.resource.DoubleFactorization` classes. -# -# Variational quantum eigensolver -# ------------------------------------------ -# In variational quantum algorithms such as VQE, the expectation value of an observable is -# typically computed by decomposing the observable into a linear combination of Pauli words, -# which are tensor products of Pauli and Identity operators. The expectation values are calculated -# through linearity by measuring the expectation value for each of these terms and combining the -# results. The number of qubits required for the measurement is trivially determined by -# the number of qubits the observable acts on. The number of gates required to implement the -# variational algorithm is determined by a circuit ansatz that is also known a priori. However, -# estimating the number of circuit evaluations, i.e. the number of shots, required to achieve a -# certain error in computing the expectation value is not as straightforward. Let's now use -# PennyLane to estimate the number of shots needed to compute the expectation value of the water -# Hamiltonian. -# -# First, we construct the molecular Hamiltonian. - -molecule = qml.qchem.Molecule(symbols, geometry) -H = qml.qchem.molecular_hamiltonian(molecule)[0] -H_coeffs, H_ops = H.terms() - -############################################################################## -# The number of measurements needed to compute :math:`\left \langle H \right \rangle` can be -# obtained with the :func:`~.pennylane.resource.estimate_shots` function, which requires the -# Hamiltonian coefficients as input. The number of measurements required to compute -# :math:`\left \langle H \right \rangle` with a target error set to the chemical accuracy, 0.0016 -# :math:`\text{Ha},` is obtained as follows. - -m = qml.resource.estimate_shots(H_coeffs) -print(f'Shots : {m:.2e}') - -############################################################################## -# This number corresponds to the measurement process where each term in the Hamiltonian is measured -# independently. The number can be reduced by using -# :func:`~.pennylane.pauli.group_observables()`, which partitions the Pauli words into -# groups of commuting terms that can be measured simultaneously. - -ops, coeffs = qml.pauli.group_observables(H_ops, H_coeffs) -coeffs = [np.array(c) for c in coeffs] # cast as numpy array - -m = qml.resource.estimate_shots(coeffs) -print(f'Shots : {m:.2e}') - -############################################################################## -# It is also interesting to illustrate how the number of shots depends on the target error. - -error = np.array([0.02, 0.015, 0.01, 0.005, 0.001]) -m = [qml.resource.estimate_shots(H_coeffs, error=er) for er in error] - -e_ = np.linspace(error[0], error[-1], num=50) -m_ = 1.4e4 / np.linspace(error[0], error[-1], num=50)**2 - -fig, ax = plt.subplots(figsize=(5, 3)) -ax.plot(error, m, 'o', markerfacecolor='none', color='teal', label='estimated') -ax.plot(e_, m_, ':', markerfacecolor='none', color='olive', label='$ 1.4e4 * 1/\epsilon^2 $') - -ax.set_ylabel('shots') -ax.set_xlabel('error [Ha]') -ax.set_yscale('log') -ax.tick_params(axis='x', labelrotation = 90) -ax.legend() -fig.tight_layout() - -############################################################################## -# We have added a line showing the dependency of the shots to the error, as -# :math:`\text{shots} = 1.4\text{e}4 \times 1/\epsilon^2,` for comparison. Can you draw any -# interesting information form the plot? -# -# Conclusions -# ----------- -# This tutorial shows how to use the resource estimation functionality in PennyLane to compute the -# total number of non-Clifford gates and logical qubits required to simulate a Hamiltonian with -# quantum phase estimation algorithms. The estimation can be performed for second-quantized -# molecular Hamiltonians obtained with a double low-rank factorization algorithm, -# and first-quantized Hamiltonians of periodic materials in the plane wave basis. We also discussed -# the estimation of the total number of shots required to obtain the expectation value of an -# observable using the variational quantum eigensolver algorithm. The functionality allows one to -# obtain interesting results about the cost of implementing important quantum algorithms. For -# instance, we estimated the costs with respect to factors such as the target error in obtaining -# energies and the number of basis functions used to simulate a system. Can you think of other -# interesting information that can be obtained using this PennyLane functionality? -# -# References -# ---------- -# -# .. [#vonburg2021] -# -# Vera von Burg, Guang Hao Low, Thomas Haner, Damian S. Steiger, *et al.*, -# "Quantum computing enhanced computational catalysis". -# `Phys. Rev. Research 3, 033055 (2021) -# `__ -# -# .. [#lee2021] -# -# Joonho Lee, Dominic W. Berry, Craig Gidney, William J. Huggins, *et al.*, -# "Even More Efficient Quantum Computations of Chemistry Through Tensor Hypercontraction". -# `PRX Quantum 2, 030305 (2021) -# `__ -# -# .. [#zini2023] -# -# Modjtaba Shokrian Zini, Alain Delgado, Roberto dos Reis, Pablo A. M. Casares, -# Jonathan E. Mueller, Arne-Christian Voigt, Juan Miguel Arrazola, -# "Quantum simulation of battery materials using ionic pseudopotentials". -# `Quantum 7, 1049 (2023) `__ -# -# .. [#delgado2022] -# -# Alain Delgado, Pablo A. M. Casares, Roberto dos Reis, Modjtaba Shokrian Zini, *et al.*, -# "Simulating key properties of lithium-ion batteries with a fault-tolerant quantum computer". -# `Phys. Rev. A 106, 032428 (2022) -# `__ -# About the author -# ---------------- -# +r""" + +Resource estimation for quantum chemistry +========================================= + +.. meta:: + :property="og:description": Learn how to estimate the number of qubits and gates needed to + implement quantum algorithms + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/resource_estimation.jpeg + +.. related:: + tutorial_quantum_chemistry Quantum chemistry with PennyLane + tutorial_vqe A brief overview of VQE + + +*Author: Soran Jahangiri — Posted: 21 November 2022.* + +Quantum algorithms such as +`quantum phase estimation `_ +(QPE) and the `variational quantum eigensolver `_ (VQE) +are widely studied in quantum chemistry as potential avenues to tackle problems that are intractable +for conventional computers. However, we currently do not have quantum computers or simulators +capable of implementing large-scale +versions of these algorithms. This makes it difficult to properly explore their accuracy and +efficiency for problem sizes where the actual advantage of quantum algorithms can potentially occur. +Despite these difficulties, it is still possible to perform **resource estimation** +to assess what we need to implement such quantum algorithms. + +In this demo, we describe how to use PennyLane's resource estimation functionality to estimate the total number of logical qubits and gates required to +implement the QPE algorithm for simulating molecular Hamiltonians represented in first and second +quantization. We focus on `non-Clifford gates `_, which +are the most expensive to implement in a fault-tolerant setting. We also explain how to estimate the +total number of measurements needed to compute expectation values using algorithms such as VQE. + +Quantum Phase Estimation +------------------------ +The QPE algorithm can be used to compute the phase associated with an eigenstate of a unitary +operator. For the purpose of quantum simulation, the unitary operator :math:`U` can be chosen to +share eigenvectors with a molecular Hamiltonian :math:`H,` for example by setting +:math:`U = e^{-iH}.` Estimating the phase of such a unitary then permits recovering the +corresponding eigenvalue of the Hamiltonian. A conceptual QPE circuit diagram is shown below. + +.. figure:: /_static/demonstration_assets/resource_estimation/qpe.png + :width: 60% + :align: center + + Circuit representing the quantum phase estimation algorithm. + +For most cases of interest, this algorithm requires more qubits and longer circuit depths than what +can be implemented on existing hardware. The PennyLane functionality in the +:mod:`qml.resource ` module allows us to estimate the number of logical qubits +and the number of non-Clifford gates that are needed to implement the algorithm. We can estimate +these resources by simply defining system specifications and a target error for estimation. Let's +see how! + +QPE cost for simulating molecules +********************************* +We study the double low-rank Hamiltonian factorization algorithm of [#vonburg2021]_ and use its cost +equations as provided in APPENDIX C of [#lee2021]_. +This algorithm requires the one- and two-electron +`integrals `_ +as input. These integrals can be obtained in different ways and here we use PennyLane to compute +them. We first need to define the atomic symbols and coordinates for the given molecule. Let's use +the water molecule at its equilibrium geometry with the +`6-31g basis set `_ as an example. +""" +import pennylane as qml +import numpy as np +import jax + +jax.config.update("jax_enable_x64", True) + +symbols = ['O', 'H', 'H'] +geometry = np.array([[0.00000000, 0.00000000, 0.28377432], + [0.00000000, 1.45278171, -1.00662237], + [0.00000000, -1.45278171, -1.00662237]]) + +############################################################################## +# Then we construct a molecule object and compute the one- and two-electron +# integrals in the molecular orbital basis. + +mol = qml.qchem.Molecule(symbols, geometry, basis_name='6-31g') +core, one, two = qml.qchem.electron_integrals(mol)() + +############################################################################## +# We now create an instance of the :class:`~.pennylane.resource.DoubleFactorization` class + +algo = qml.resource.DoubleFactorization(one, two) + +############################################################################## +# and obtain the estimated number of non-Clifford gates and logical qubits. + +print(f'Estimated gates : {algo.gates:.2e} \nEstimated qubits: {algo.qubits}') + +############################################################################## +# This estimation is for a target error that is set to the chemical accuracy, 0.0016 +# :math:`\text{Ha},` by default. We can change the target error to a larger value which leads to a +# smaller number of non-Clifford gates and logical qubits. + +chemical_accuracy = 0.0016 +error = chemical_accuracy * 10 +algo = qml.resource.DoubleFactorization(one, two, error=error) +print(f'Estimated gates : {algo.gates:.2e} \nEstimated qubits: {algo.qubits}') + +############################################################################## +# We can also estimate the number of non-Clifford gates with respect to the threshold error values +# for discarding the negligible factors in the factorized Hamiltonian [#vonburg2021]_ and plot the +# estimated numbers. + +threshold = [10**-n for n in range(10)] +n_gates = [] +n_qubits = [] + +for tol in threshold: + algo_ = qml.resource.DoubleFactorization(one, two, tol_factor=tol, tol_eigval=tol) + n_gates.append(algo_.gates) + n_qubits.append(algo_.qubits) + +import matplotlib.pyplot as plt + +fig, ax = plt.subplots(figsize=(5, 3)) +ax.plot(threshold, n_gates, ':o', markerfacecolor='none', color='teal') + +ax.set_ylabel('n gates') +ax.set_xlabel('threshold') +ax.set_xscale('log') +fig.tight_layout() + +############################################################################## +# QPE cost for simulating periodic materials +# ****************************************** +# For periodic materials, we estimate the cost of implementing the QPE algorithm of [#zini2023]_ +# using Hamiltonians represented in first quantization and in a plane wave basis. We first need to +# define the number of plane waves, the number of electrons, and the lattice vectors that construct +# the unit cell of the periodic material. Let's use dilithium iron silicate +# :math:`\text{Li}_2\text{FeSiO}_4` as an example taken from [#delgado2022]_. For this material, the +# unit cell contains 156 electrons and has dimensions :math:`9.49 \times 10.20 \times 11.83` in +# `atomic units `_. We also use :math:`10^5` plane waves. + +planewaves = 100000 +electrons = 156 +vectors = np.array([[9.49, 0.00, 0.00], + [0.00, 10.20, 0.00], + [0.00, 0.00, 11.83]]) + +############################################################################## +# We now create an instance of the :class:`~.pennylane.resource.FirstQuantization` class +algo = qml.resource.FirstQuantization(planewaves, electrons, vectors=vectors) + +############################################################################## +# and obtain the estimated number of non-Clifford gates and logical qubits. +print(f'Estimated gates : {algo.gates:.2e} \nEstimated qubits: {algo.qubits}') + +############################################################################## +# We can also plot the estimated numbers as a function of the number of plane waves for different +# target errors + +error = [0.1, 0.01, 0.001] # in atomic units +planewaves = [10 ** n for n in range(1, 10)] +n_gates = [] +n_qubits = [] + +for er in error: + n_gates_ = [] + n_qubits_ = [] + + for pw in planewaves: + algo_ = qml.resource.FirstQuantization(pw, electrons, vectors=vectors, error=er) + n_gates_.append(algo_.gates) + n_qubits_.append(algo_.qubits) + n_gates.append(n_gates_) + n_qubits.append(n_qubits_) + +fig, ax = plt.subplots(2, 1) + +for i in range(len(n_gates)): + ax[0].plot(planewaves, n_gates[i], ':o', markerfacecolor='none', label=error[i]) +ax[1].plot(planewaves, n_qubits[i], ':o', markerfacecolor='none', label=error[-1]) + +ax[0].set_ylabel('n gates') +ax[1].set_ylabel('n qubits') + +for i in [0, 1]: + ax[i].set_xlabel('n planewaves') + ax[i].tick_params(axis='x') + ax[0].set_yscale('log') + ax[i].set_xscale('log') + ax[i].legend(title='error [Ha]') + +fig.tight_layout() + +############################################################################## +# The algorithm uses a decomposition of the Hamiltonian as a linear combination of unitaries, +# +# .. math:: H=\sum_{i} c_i U_i. +# +# The parameter :math:`\lambda=\sum_i c_i,` which can be interpreted as the 1-norm of the +# Hamiltonian, plays an important role in determining the cost of implementing the QPE +# algorithm [#delgado2022]_. In PennyLane, the 1-norm of the Hamiltonian can be obtained with + +print(f'1-norm of the Hamiltonian: {algo.lamb}') + +############################################################################## +# PennyLane allows you to get more detailed information about the cost of the algorithms as +# explained in the documentation of :class:`~.pennylane.resource.FirstQuantization` +# and :class:`~.pennylane.resource.DoubleFactorization` classes. +# +# Variational quantum eigensolver +# ------------------------------------------ +# In variational quantum algorithms such as VQE, the expectation value of an observable is +# typically computed by decomposing the observable into a linear combination of Pauli words, +# which are tensor products of Pauli and Identity operators. The expectation values are calculated +# through linearity by measuring the expectation value for each of these terms and combining the +# results. The number of qubits required for the measurement is trivially determined by +# the number of qubits the observable acts on. The number of gates required to implement the +# variational algorithm is determined by a circuit ansatz that is also known a priori. However, +# estimating the number of circuit evaluations, i.e. the number of shots, required to achieve a +# certain error in computing the expectation value is not as straightforward. Let's now use +# PennyLane to estimate the number of shots needed to compute the expectation value of the water +# Hamiltonian. +# +# First, we construct the molecular Hamiltonian. + +molecule = qml.qchem.Molecule(symbols, geometry) +H = qml.qchem.molecular_hamiltonian(molecule)[0] +H_coeffs, H_ops = H.terms() + +############################################################################## +# The number of measurements needed to compute :math:`\left \langle H \right \rangle` can be +# obtained with the :func:`~.pennylane.resource.estimate_shots` function, which requires the +# Hamiltonian coefficients as input. The number of measurements required to compute +# :math:`\left \langle H \right \rangle` with a target error set to the chemical accuracy, 0.0016 +# :math:`\text{Ha},` is obtained as follows. + +m = qml.resource.estimate_shots(H_coeffs) +print(f'Shots : {m:.2e}') + +############################################################################## +# This number corresponds to the measurement process where each term in the Hamiltonian is measured +# independently. The number can be reduced by using +# :func:`~.pennylane.pauli.group_observables()`, which partitions the Pauli words into +# groups of commuting terms that can be measured simultaneously. + +ops, coeffs = qml.pauli.group_observables(H_ops, H_coeffs) +coeffs = [np.array(c) for c in coeffs] # cast as numpy array + +m = qml.resource.estimate_shots(coeffs) +print(f'Shots : {m:.2e}') + +############################################################################## +# It is also interesting to illustrate how the number of shots depends on the target error. + +error = np.array([0.02, 0.015, 0.01, 0.005, 0.001]) +m = [qml.resource.estimate_shots(H_coeffs, error=er) for er in error] + +e_ = np.linspace(error[0], error[-1], num=50) +m_ = 1.4e4 / np.linspace(error[0], error[-1], num=50)**2 + +fig, ax = plt.subplots(figsize=(5, 3)) +ax.plot(error, m, 'o', markerfacecolor='none', color='teal', label='estimated') +ax.plot(e_, m_, ':', markerfacecolor='none', color='olive', label='$ 1.4e4 * 1/\epsilon^2 $') + +ax.set_ylabel('shots') +ax.set_xlabel('error [Ha]') +ax.set_yscale('log') +ax.tick_params(axis='x', labelrotation = 90) +ax.legend() +fig.tight_layout() + +############################################################################## +# We have added a line showing the dependency of the shots to the error, as +# :math:`\text{shots} = 1.4\text{e}4 \times 1/\epsilon^2,` for comparison. Can you draw any +# interesting information form the plot? +# +# Conclusions +# ----------- +# This tutorial shows how to use the resource estimation functionality in PennyLane to compute the +# total number of non-Clifford gates and logical qubits required to simulate a Hamiltonian with +# quantum phase estimation algorithms. The estimation can be performed for second-quantized +# molecular Hamiltonians obtained with a double low-rank factorization algorithm, +# and first-quantized Hamiltonians of periodic materials in the plane wave basis. We also discussed +# the estimation of the total number of shots required to obtain the expectation value of an +# observable using the variational quantum eigensolver algorithm. The functionality allows one to +# obtain interesting results about the cost of implementing important quantum algorithms. For +# instance, we estimated the costs with respect to factors such as the target error in obtaining +# energies and the number of basis functions used to simulate a system. Can you think of other +# interesting information that can be obtained using this PennyLane functionality? +# +# References +# ---------- +# +# .. [#vonburg2021] +# +# Vera von Burg, Guang Hao Low, Thomas Haner, Damian S. Steiger, *et al.*, +# "Quantum computing enhanced computational catalysis". +# `Phys. Rev. Research 3, 033055 (2021) +# `__ +# +# .. [#lee2021] +# +# Joonho Lee, Dominic W. Berry, Craig Gidney, William J. Huggins, *et al.*, +# "Even More Efficient Quantum Computations of Chemistry Through Tensor Hypercontraction". +# `PRX Quantum 2, 030305 (2021) +# `__ +# +# .. [#zini2023] +# +# Modjtaba Shokrian Zini, Alain Delgado, Roberto dos Reis, Pablo A. M. Casares, +# Jonathan E. Mueller, Arne-Christian Voigt, Juan Miguel Arrazola, +# "Quantum simulation of battery materials using ionic pseudopotentials". +# `Quantum 7, 1049 (2023) `__ +# +# .. [#delgado2022] +# +# Alain Delgado, Pablo A. M. Casares, Roberto dos Reis, Modjtaba Shokrian Zini, *et al.*, +# "Simulating key properties of lithium-ion batteries with a fault-tolerant quantum computer". +# `Phys. Rev. A 106, 032428 (2022) +# `__ +# About the author +# ---------------- +# diff --git a/demonstrations/tutorial_rosalin.py b/demonstrations/tutorial_rosalin.py index aed37d6224..08b548c590 100644 --- a/demonstrations/tutorial_rosalin.py +++ b/demonstrations/tutorial_rosalin.py @@ -1,666 +1,666 @@ -r""" -Frugal shot optimization with Rosalin -===================================== - -.. meta:: - :property="og:description": The Rosalin optimizer uses a measurement-frugal optimization strategy to minimize the - number of times a quantum computer is accessed. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/sphx_glr_tutorial_rosalin_002.png - -.. related:: - - tutorial_vqe A brief overview of VQE - tutorial_quantum_natural_gradient Quantum natural gradient - tutorial_doubly_stochastic Doubly stochastic gradient descent - tutorial_rotoselect Quantum circuit structure learning - -*Author: Josh Izaac — Posted: 19 May 2020. Last updated: 30 January 2023.* - -In this tutorial we investigate and implement the Rosalin (Random Operator Sampling for -Adaptive Learning with Individual Number of shots) from -Arrasmith et al. [#arrasmith2020]_. In this paper, a strategy -is introduced for reducing the number of shots required when optimizing variational quantum -algorithms, by both: - -* Frugally adapting the number of shots used per parameter update, and -* Performing a weighted sampling of operators from the cost Hamiltonian. - -.. note:: - - The Rosalin optimizer is available in PennyLane via the - :class:`~.pennylane.ShotAdaptiveOptimizer`. - -Background ----------- - -While a large number of papers in variational quantum algorithms focus on the -choice of circuit ansatz, cost function, gradient computation, or initialization method, -the optimization strategy—an important component affecting both convergence time and -quantum resource dependence—is not as frequently considered. Instead, common -'out-of-the-box' classical optimization techniques, such as gradient-free -methods (COBLYA, Nelder-Mead), gradient-descent, and Hessian-free methods (L-BFGS) tend to be used. - -However, for variational algorithms such as :doc:`VQE `, which involve evaluating -a large number of non-commuting operators in the cost function, decreasing the number of -quantum evaluations required for convergence, while still minimizing statistical noise, can -be a delicate balance. - -Recent work has highlighted that 'quantum-aware' optimization techniques -can lead to marked improvements when training variational quantum algorithms: - -* :doc:`/demos/tutorial_quantum_natural_gradient` descent by Stokes et al. [#stokes2019]_, which - takes into account the quantum geometry during the gradient-descent update step. - -* The work of Sweke et al. [#sweke2019]_, which shows - that quantum gradient descent with a finite number of shots is equivalent to - `stochastic gradient descent `_, - and has guaranteed convergence. Furthermore, combining a finite number of shots with - weighted sampling of the cost function terms leads to :doc:`/demos/tutorial_doubly_stochastic`. - -* The iCANS (individual Coupled Adaptive Number of Shots) optimization technique by - Jonas Kuebler et al. [#kubler2020]_ adapts the number - of shots measurements during training, by maximizing the expected gain per shot. - -In this latest result by Arrasmith et al. [#arrasmith2020]_, the -idea of doubly stochastic gradient descent has been used to extend the iCANS optimizer, -resulting in faster convergence. - -Over the course of this tutorial, we will explore their results; beginning first with a -demonstration of *weighted random sampling* of the cost Hamiltonian operators, before -combining this with the shot-frugal iCANS optimizer to perform doubly stochastic -Rosalin optimization. - -Weighted random sampling ------------------------- - -Consider a Hamiltonian :math:`H` expanded as a weighted sum of operators :math:`h_i` that can -be directly measured: - -.. math:: H = \sum_{i=1}^N c_i h_i. - -Due to the linearity of expectation values, the expectation value of this Hamiltonian -can be expressed as the weighted sum of each individual term: - -.. math:: \langle H\rangle = \sum_{i=1}^N c_i \langle h_i\rangle. - -In the :doc:`doubly stochastic gradient descent demonstration `, -we estimated this expectation value by **uniformly sampling** a subset of the terms -at each optimization step, and evaluating each term by using the same finite number of shots -:math:`N.` - -However, what happens if we use a weighted approach to determine how to distribute -our samples across the terms of the Hamiltonian? In **weighted random sampling** (WRS), -the number of shots used to determine the expectation value :math:`\langle h_i\rangle` -is a discrete random variable distributed according to a -`multinomial distribution `__, - -.. math:: S \sim \text{Multinomial}(p_i), - -with event probabilities - -.. math:: p_i = \frac{|c_i|}{\sum_i |c_i|}. - -That is, the number of shots assigned to the measurement of the expectation value of the -:math:`i\text{th}` term of the Hamiltonian is drawn from a probability distribution -*proportional to the magnitude of its coefficient* :math:`c_i.` - -To see this strategy in action, consider the Hamiltonian - -.. math:: H = 2I\otimes X + 4 I\otimes Z - X\otimes X + 5Y\otimes Y + 2 Z\otimes X. - -We can solve for the ground state energy using the variational quantum eigensolver (VQE) algorithm. - -First, let's import NumPy and PennyLane, and define our Hamiltonian. -""" -import pennylane as qml -from pennylane import numpy as np - -# set the random seed -np.random.seed(4) - -coeffs = [2, 4, -1, 5, 2] - -obs = [ - qml.PauliX(1), - qml.PauliZ(1), - qml.PauliX(0) @ qml.PauliX(1), - qml.PauliY(0) @ qml.PauliY(1), - qml.PauliZ(0) @ qml.PauliZ(1) -] - - -############################################################################## -# We can now create our quantum device (let's use the ``default.qubit`` simulator). - -num_layers = 2 -num_wires = 2 - -# create a device that estimates expectation values using a finite number of shots -non_analytic_dev = qml.device("default.qubit", wires=num_wires, shots=100, seed=432423) - -# create a device that calculates exact expectation values -analytic_dev = qml.device("default.qubit", wires=num_wires, shots=None) - -############################################################################## -# Now, let's set the total number of shots, and determine the probability -# for sampling each Hamiltonian term. - -total_shots = 8000 -prob_shots = np.abs(coeffs) / np.sum(np.abs(coeffs)) -print(prob_shots) - -############################################################################## -# We can now use SciPy to create our multinomial distributed random variable -# :math:`S,` using the number of trials (total shot number) and probability values: - -from scipy.stats import multinomial - -si = multinomial(n=total_shots, p=prob_shots) - -############################################################################## -# Sampling from this distribution will provide the number of shots used to -# sample each term in the Hamiltonian: - -samples = si.rvs()[0] -print(samples) -print(sum(samples)) - -############################################################################## -# As expected, if we sum the sampled shots per term, we recover the total number of shots. -# -# Let's now create our cost function. Recall that the cost function must do the -# following: -# -# 1. It must sample from the multinomial distribution we created above, -# to determine the number of shots :math:`s_i` to use to estimate the expectation -# value of the ith Hamiltonian term. -# -# 2. It then must estimate the expectation value :math:`\langle h_i\rangle` -# by creating the required QNode. For our ansatz, we'll use the -# :class:`~.pennylane.templates.layers.StronglyEntanglingLayers`. -# -# 3. And, last but not least, estimate the expectation value -# :math:`\langle H\rangle = \sum_i c_i\langle h_i\rangle.` -# - -from pennylane.templates.layers import StronglyEntanglingLayers - - -@qml.qnode(non_analytic_dev, diff_method="parameter-shift", interface="autograd") -def qnode(weights, observable): - StronglyEntanglingLayers(weights, wires=non_analytic_dev.wires) - return qml.expval(observable) - -def cost(params): - # sample from the multinomial distribution - shots_per_term = si.rvs()[0] - - result = 0 - - for o, c, s in zip(obs, coeffs, shots_per_term): - # evaluate the QNode corresponding to - # the Hamiltonian term, and add it on to our running sum - result += c * qnode(params, o, shots=int(s)) - - return result - - -############################################################################## -# Evaluating our cost function with some initial parameters, we can test out -# that our cost function evaluates correctly. - -param_shape = StronglyEntanglingLayers.shape(n_layers=num_layers, n_wires=num_wires) -init_params = np.random.uniform(low=0.0, high=2*np.pi, size=param_shape, requires_grad=True) -print(cost(init_params)) - - -############################################################################## -# Performing the optimization, with the number of shots randomly -# determined at each optimization step: - -opt = qml.AdamOptimizer(0.05) -params = init_params - -cost_wrs = [] -shots_wrs = [] - -for i in range(100): - params, _cost = opt.step_and_cost(cost, params) - cost_wrs.append(_cost) - shots_wrs.append(total_shots*i) - print("Step {}: cost = {} shots used = {}".format(i, cost_wrs[-1], shots_wrs[-1])) - -############################################################################## -# Let's compare this against an optimization not using weighted random sampling. -# Here, we will split the 8000 total shots evenly across all Hamiltonian terms, -# also known as *uniform deterministic sampling*. - -@qml.qnode(non_analytic_dev, diff_method="parameter-shift", interface="autograd") -def qnode(weights, obs): - StronglyEntanglingLayers(weights, wires=non_analytic_dev.wires) - return qml.expval(obs) - -def cost(params): - shots_per_term = int(total_shots / len(coeffs)) - - result = 0 - - for o, c in zip(obs, coeffs): - - # evaluate the QNode corresponding to - # the Hamiltonian term, and add it on to our running sum - result += c * qnode(params, o, shots=shots_per_term) - - return result - -opt = qml.AdamOptimizer(0.05) -params = init_params - -cost_adam = [] -shots_adam = [] - -for i in range(100): - params, _cost = opt.step_and_cost(cost, params) - cost_adam.append(_cost) - shots_adam.append(total_shots*i) - print("Step {}: cost = {} shots used = {}".format(i, cost_adam[-1], shots_adam[-1])) - -############################################################################## -# Comparing these two techniques: - -from matplotlib import pyplot as plt - -plt.style.use("seaborn-v0_8") -plt.plot(shots_wrs, cost_wrs, "b", label="Adam WRS") -plt.plot(shots_adam, cost_adam, "g", label="Adam") - -plt.ylabel("Cost function value") -plt.xlabel("Number of shots") -plt.legend() -plt.show() - -############################################################################## -# We can see that weighted random sampling performs just as well as the uniform -# deterministic sampling. However, weighted random sampling begins to show a -# non-negligible improvement over deterministic sampling for large Hamiltonians -# with highly non-uniform coefficients. For example, see Fig (3) and (4) of -# Arrasmith et al. [#arrasmith2020]_, comparing weighted random sampling VQE optimization -# for both :math:`\text{H}_2` and :math:`\text{LiH}` molecules. -# -# .. note:: -# -# While not covered here, another approach that could be taken is -# *weighted deterministic sampling*. Here, the number of shots is distributed -# across terms as per -# -# .. math:: s_i = \left\lfloor N \frac{|c_i|}{\sum_i |c_i|}\right\rfloor, -# -# where :math:`N` is the total number of shots. -# - -############################################################################## -# Rosalin: Frugal shot optimization -# --------------------------------- -# -# We can see above that both methods optimize fairly well; weighted random -# sampling converges just as well as evenly distributing the shots across -# all Hamiltonian terms. However, deterministic shot distribution approaches -# will always have a minimum shot value required per expectation value, as below -# this threshold they become biased estimators. This is not the case with random -# sampling; as we saw in the -# :doc:`doubly stochastic gradient descent demonstration `, -# the introduction of randomness allows for as little -# as a single shot per expectation term, while still remaining an unbiased estimator. -# -# Using this insight, Arrasmith et al. [#arrasmith2020]_ modified the iCANS frugal -# shot-optimization technique [#kubler2020]_ to include weighted random sampling, making it -# 'doubly stochastic'. -# -# iCANS optimizer -# ~~~~~~~~~~~~~~~ -# -# Two variants of the iCANS optimizer were introduced in Kübler et al., iCANS1 and iCANS2. -# The iCANS1 optimizer, on which Rosalin is based, frugally distributes a shot budget -# across the partial derivatives of each parameter, which are computed using the -# :doc:`parameter-shift rule `. It works roughly as follows: -# -# 1. The initial step of the optimizer is performed with some specified minimum -# number of shots, :math:`s_{min},` for all partial derivatives. -# -# 2. The parameter-shift rule is then used to estimate the gradient :math:`g_i` -# for each parameter :math:`\theta_i,` parameters, as well as the *variances* -# :math:`v_i` of the estimated gradients. -# -# 3. Gradient descent is performed for each parameter :math:`\theta_i,` using -# the pre-defined learning rate :math:`\alpha` and the gradient information :math:`g_i:` -# -# .. math:: \theta_i = \theta_i - \alpha g_i. -# -# 4. The improvement in the cost function per shot, for a specific parameter value, -# is then calculated via -# -# .. math:: -# -# \gamma_i = \frac{1}{s_i} \left[ \left(\alpha - \frac{1}{2} L\alpha^2\right) -# g_i^2 - \frac{L\alpha^2}{2s_i}v_i \right], -# -# where: -# -# * :math:`L \leq \sum_i|c_i|` is the bound on the `Lipschitz constant -# `__ of the variational quantum algorithm objective function, -# -# * :math:`c_i` are the coefficients of the Hamiltonian, and -# -# * :math:`\alpha` is the learning rate, and *must* be bound such that :math:`\alpha < 2/L` -# for the above expression to hold. -# -# 5. Finally, the new values of :math:`s_i` (shots for partial derivative of parameter -# :math:`\theta_i`) is given by: -# -# .. math:: -# -# s_i = \frac{2L\alpha}{2-L\alpha}\left(\frac{v_i}{g_i^2}\right)\propto -# \frac{v_i}{g_i^2}. -# -# In addition to the above, to counteract the presence of noise in the system, a -# running average of :math:`g_i` and :math:`s_i` (:math:`\chi_i` and :math:`\xi_i` respectively) -# are used when computing :math:`\gamma_i` and :math:`s_i.` -# -# .. note:: -# -# In classical machine learning, the Lipschitz constant of the cost function is generally -# unknown. However, for a variational quantum algorithm with cost of the form -# :math:`f(x) = \langle \psi(x) | \hat{H} |\psi(x)\rangle,` -# an upper bound on the Lipschitz constant is given by :math:`L < \sum_i|c_i|,` -# where :math:`c_i` are the coefficients of :math:`\hat{H}` when decomposed -# into a linear combination of Pauli-operator tensor products. -# -# Rosalin implementation -# ~~~~~~~~~~~~~~~~~~~~~~ -# -# Let's now modify iCANS above to incorporate weighted random sampling of Hamiltonian -# terms — the Rosalin frugal shot optimizer. -# -# Rosalin takes several hyper-parameters: -# -# * ``min_shots``: the minimum number of shots used to estimate the expectations -# of each term in the Hamiltonian. Note that this must be larger than 2 for the variance -# of the gradients to be computed. -# -# * ``mu``: The running average constant :math:`\mu\in[0, 1].` Used to control how quickly the -# number of shots recommended for each gradient component changes. -# -# * ``b``: Regularization bias. The bias should be kept small, but non-zero. -# -# * ``lr``: The learning rate. Recall from above that the learning rate *must* be such -# that :math:`\alpha < 2/L = 2/\sum_i|c_i|.` -# -# Since the Rosalin optimizer has a state that must be preserved between optimization steps, -# let's use a class to create our optimizer. -# - -class Rosalin: - - def __init__(self, obs, coeffs, min_shots, mu=0.99, b=1e-6, lr=0.07): - self.obs = obs - self.coeffs = coeffs - - self.lipschitz = np.sum(np.abs(coeffs)) - - if lr > 2 / self.lipschitz: - raise ValueError("The learning rate must be less than ", 2 / self.lipschitz) - - # hyperparameters - self.min_shots = min_shots - self.mu = mu # running average constant - self.b = b # regularization bias - self.lr = lr # learning rate - - # keep track of the total number of shots used - self.shots_used = 0 - # total number of iterations - self.k = 0 - # Number of shots per parameter - self.s = np.zeros_like(params, dtype=np.float64) + min_shots - - # Running average of the parameter gradients - self.chi = None - # Running average of the variance of the parameter gradients - self.xi = None - - def estimate_hamiltonian(self, params, shots): - """Returns an array containing length ``shots`` single-shot estimates - of the Hamiltonian. The shots are distributed randomly over - the terms in the Hamiltonian, as per a Multinomial distribution. - - Since we are performing single-shot estimates, the QNodes must be - set to 'sample' mode. - """ - # note that convergence depends on seed for random number generation - rosalin_device = qml.device("default.qubit", wires=num_wires, shots=100, seed=93754352) - - # determine the shot probability per term - prob_shots = np.abs(coeffs) / np.sum(np.abs(coeffs)) - - # construct the multinomial distribution, and sample - # from it to determine how many shots to apply per term - si = multinomial(n=shots, p=prob_shots) - shots_per_term = si.rvs()[0] - - results = [] - - @qml.qnode(rosalin_device, diff_method="parameter-shift", interface="autograd") - def qnode(weights, observable): - StronglyEntanglingLayers(weights, wires=rosalin_device.wires) - return qml.sample(observable) - - for o, c, p, s in zip(self.obs, self.coeffs, prob_shots, shots_per_term): - - # if the number of shots is 0, do nothing - if s == 0: - continue - - # evaluate the QNode corresponding to - # the Hamiltonian term - res = qnode(params, o, shots=int(s)) - - if s == 1: - res = np.array([res]) - - # Note that, unlike above, we divide each term by the - # probability per shot. This is because we are sampling one at a time. - results.append(c * res / p) - - return np.concatenate(results) - - def evaluate_grad_var(self, i, params, shots): - """Evaluate the gradient, as well as the variance in the gradient, - for the ith parameter in params, using the parameter-shift rule. - """ - shift = np.zeros_like(params) - shift[i] = np.pi / 2 - - shift_forward = self.estimate_hamiltonian(params + shift, shots) - shift_backward = self.estimate_hamiltonian(params - shift, shots) - - g = np.mean(shift_forward - shift_backward) / 2 - s = np.var((shift_forward - shift_backward) / 2, ddof=1) - - return g, s - - def step(self, params): - """Perform a single step of the Rosalin optimizer.""" - # keep track of the number of shots run - self.shots_used += int(2 * np.sum(self.s)) - - # compute the gradient, as well as the variance in the gradient, - # using the number of shots determined by the array s. - grad = [] - S = [] - - p_ind = list(np.ndindex(*params.shape)) - - for l in p_ind: - # loop through each parameter, performing - # the parameter-shift rule - g_, s_ = self.evaluate_grad_var(l, params, self.s[l]) - grad.append(g_) - S.append(s_) - - grad = np.reshape(np.stack(grad), params.shape) - S = np.reshape(np.stack(S), params.shape) - - # gradient descent update - params = params - self.lr * grad - - if self.xi is None: - self.chi = np.zeros_like(params, dtype=np.float64) - self.xi = np.zeros_like(params, dtype=np.float64) - - # running average of the gradient variance - self.xi = self.mu * self.xi + (1 - self.mu) * S - xi = self.xi / (1 - self.mu ** (self.k + 1)) - - # running average of the gradient - self.chi = self.mu * self.chi + (1 - self.mu) * grad - chi = self.chi / (1 - self.mu ** (self.k + 1)) - - # determine the new optimum shots distribution for the next - # iteration of the optimizer - s = np.ceil( - (2 * self.lipschitz * self.lr * xi) - / ((2 - self.lipschitz * self.lr) * (chi ** 2 + self.b * (self.mu ** self.k))) - ) - - # apply an upper and lower bound on the new shot distributions, - # to avoid the number of shots reducing below min(2, min_shots), - # or growing too significantly. - gamma = ( - (self.lr - self.lipschitz * self.lr ** 2 / 2) * chi ** 2 - - xi * self.lipschitz * self.lr ** 2 / (2 * s) - ) / s - - argmax_gamma = np.unravel_index(np.argmax(gamma), gamma.shape) - smax = s[argmax_gamma] - self.s = np.clip(s, min(2, self.min_shots), max(2, smax)) - - self.k += 1 - return params - - -############################################################################## -# Rosalin optimization -# ~~~~~~~~~~~~~~~~~~~~ -# -# We are now ready to use our Rosalin optimizer to optimize the initial VQE problem. But first let's -# also create a separate cost function using an 'exact' quantum device, so that we can keep track of the -# *exact* cost function value at each iteration. - -@qml.qnode(analytic_dev, interface="autograd") -def cost_analytic(weights): - StronglyEntanglingLayers(weights, wires=analytic_dev.wires) - return qml.expval(qml.Hamiltonian(coeffs, obs)) - -############################################################################## -# Creating the optimizer and beginning the optimization: - - -opt = Rosalin(obs, coeffs, min_shots=10) -params = init_params - -cost_rosalin = [cost_analytic(params)] -shots_rosalin = [0] - -for i in range(60): - params = opt.step(params) - cost_rosalin.append(cost_analytic(params)) - shots_rosalin.append(opt.shots_used) - print(f"Step {i}: cost = {cost_rosalin[-1]}, shots_used = {shots_rosalin[-1]}") - - -############################################################################## -# Let's compare this to a standard Adam optimization. Using 100 shots per quantum -# evaluation, for each update step there are 2 quantum evaluations per parameter. - -adam_shots_per_eval = 100 -adam_shots_per_step = 2 * adam_shots_per_eval * len(params.flatten()) -print(adam_shots_per_step) - -############################################################################## -# Thus, Adam is using 2400 shots per update step. - -params = init_params -opt = qml.AdamOptimizer(0.07) - -adam_dev = qml.device('default.qubit', shots=adam_shots_per_eval, seed=595905) - -@qml.qnode(adam_dev, diff_method="parameter-shift", interface="autograd") -def cost(weights): - StronglyEntanglingLayers(weights, wires=non_analytic_dev.wires) - return qml.expval(qml.Hamiltonian(coeffs, obs)) - -cost_adam = [cost_analytic(params)] -shots_adam = [0] - -for i in range(100): - params = opt.step(cost, params) - cost_adam.append(cost_analytic(params)) - shots_adam.append(adam_shots_per_step * (i + 1)) - print("Step {}: cost = {} shots_used = {}".format(i, cost_adam[-1], shots_adam[-1])) - -############################################################################## -# Plotting both experiments: - -plt.style.use("seaborn-v0_8") -plt.plot(shots_rosalin, cost_rosalin, "b", label="Rosalin") -plt.plot(shots_adam, cost_adam, "g", label="Adam") - -plt.ylabel("Cost function value") -plt.xlabel("Number of shots") -plt.legend() -plt.xlim(0, 300000) -plt.show() - -############################################################################## -# The Rosalin optimizer performs significantly better than the Adam optimizer, -# approaching the ground state energy of the Hamiltonian with strikingly -# fewer shots. -# -# While beyond the scope of this demonstration, the Rosalin optimizer can be -# modified in various other ways; for instance, by incorporating *weighted hybrid -# sampling* (which distributes some shots deterministically, with the remainder -# done randomly), or by adapting the variant iCANS2 optimizer. Download -# this demonstration from the sidebar 👉 and give it a go! ⚛️ - - -############################################################################## -# References -# ---------- -# -# .. [#arrasmith2020] -# -# Andrew Arrasmith, Lukasz Cincio, Rolando D. Somma, and Patrick J. Coles. "Operator Sampling -# for Shot-frugal Optimization in Variational Algorithms." `arXiv:2004.06252 -# `__ (2020). -# -# .. [#stokes2019] -# -# James Stokes, Josh Izaac, Nathan Killoran, and Giuseppe Carleo. "Quantum Natural Gradient." -# `arXiv:1909.02108 `__ (2019). -# -# .. [#sweke2019] -# -# Ryan Sweke, Frederik Wilde, Johannes Jakob Meyer, Maria Schuld, Paul K. Fährmann, Barthélémy -# Meynard-Piganeau, and Jens Eisert. "Stochastic gradient descent for hybrid quantum-classical -# optimization." `arXiv:1910.01155 `__ (2019). -# -# .. [#kubler2020] -# -# Jonas M. Kübler, Andrew Arrasmith, Lukasz Cincio, and Patrick J. Coles. "An Adaptive Optimizer -# for Measurement-Frugal Variational Algorithms." `Quantum 4, 263 -# `__ (2020). -# -# -# About the author -# ---------------- +r""" +Frugal shot optimization with Rosalin +===================================== + +.. meta:: + :property="og:description": The Rosalin optimizer uses a measurement-frugal optimization strategy to minimize the + number of times a quantum computer is accessed. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/sphx_glr_tutorial_rosalin_002.png + +.. related:: + + tutorial_vqe A brief overview of VQE + tutorial_quantum_natural_gradient Quantum natural gradient + tutorial_doubly_stochastic Doubly stochastic gradient descent + tutorial_rotoselect Quantum circuit structure learning + +*Author: Josh Izaac — Posted: 19 May 2020. Last updated: 30 January 2023.* + +In this tutorial we investigate and implement the Rosalin (Random Operator Sampling for +Adaptive Learning with Individual Number of shots) from +Arrasmith et al. [#arrasmith2020]_. In this paper, a strategy +is introduced for reducing the number of shots required when optimizing variational quantum +algorithms, by both: + +* Frugally adapting the number of shots used per parameter update, and +* Performing a weighted sampling of operators from the cost Hamiltonian. + +.. note:: + + The Rosalin optimizer is available in PennyLane via the + :class:`~.pennylane.ShotAdaptiveOptimizer`. + +Background +---------- + +While a large number of papers in variational quantum algorithms focus on the +choice of circuit ansatz, cost function, gradient computation, or initialization method, +the optimization strategy—an important component affecting both convergence time and +quantum resource dependence—is not as frequently considered. Instead, common +'out-of-the-box' classical optimization techniques, such as gradient-free +methods (COBLYA, Nelder-Mead), gradient-descent, and Hessian-free methods (L-BFGS) tend to be used. + +However, for variational algorithms such as :doc:`VQE `, which involve evaluating +a large number of non-commuting operators in the cost function, decreasing the number of +quantum evaluations required for convergence, while still minimizing statistical noise, can +be a delicate balance. + +Recent work has highlighted that 'quantum-aware' optimization techniques +can lead to marked improvements when training variational quantum algorithms: + +* :doc:`/demos/tutorial_quantum_natural_gradient` descent by Stokes et al. [#stokes2019]_, which + takes into account the quantum geometry during the gradient-descent update step. + +* The work of Sweke et al. [#sweke2019]_, which shows + that quantum gradient descent with a finite number of shots is equivalent to + `stochastic gradient descent `_, + and has guaranteed convergence. Furthermore, combining a finite number of shots with + weighted sampling of the cost function terms leads to :doc:`/demos/tutorial_doubly_stochastic`. + +* The iCANS (individual Coupled Adaptive Number of Shots) optimization technique by + Jonas Kuebler et al. [#kubler2020]_ adapts the number + of shots measurements during training, by maximizing the expected gain per shot. + +In this latest result by Arrasmith et al. [#arrasmith2020]_, the +idea of doubly stochastic gradient descent has been used to extend the iCANS optimizer, +resulting in faster convergence. + +Over the course of this tutorial, we will explore their results; beginning first with a +demonstration of *weighted random sampling* of the cost Hamiltonian operators, before +combining this with the shot-frugal iCANS optimizer to perform doubly stochastic +Rosalin optimization. + +Weighted random sampling +------------------------ + +Consider a Hamiltonian :math:`H` expanded as a weighted sum of operators :math:`h_i` that can +be directly measured: + +.. math:: H = \sum_{i=1}^N c_i h_i. + +Due to the linearity of expectation values, the expectation value of this Hamiltonian +can be expressed as the weighted sum of each individual term: + +.. math:: \langle H\rangle = \sum_{i=1}^N c_i \langle h_i\rangle. + +In the :doc:`doubly stochastic gradient descent demonstration `, +we estimated this expectation value by **uniformly sampling** a subset of the terms +at each optimization step, and evaluating each term by using the same finite number of shots +:math:`N.` + +However, what happens if we use a weighted approach to determine how to distribute +our samples across the terms of the Hamiltonian? In **weighted random sampling** (WRS), +the number of shots used to determine the expectation value :math:`\langle h_i\rangle` +is a discrete random variable distributed according to a +`multinomial distribution `__, + +.. math:: S \sim \text{Multinomial}(p_i), + +with event probabilities + +.. math:: p_i = \frac{|c_i|}{\sum_i |c_i|}. + +That is, the number of shots assigned to the measurement of the expectation value of the +:math:`i\text{th}` term of the Hamiltonian is drawn from a probability distribution +*proportional to the magnitude of its coefficient* :math:`c_i.` + +To see this strategy in action, consider the Hamiltonian + +.. math:: H = 2I\otimes X + 4 I\otimes Z - X\otimes X + 5Y\otimes Y + 2 Z\otimes X. + +We can solve for the ground state energy using the variational quantum eigensolver (VQE) algorithm. + +First, let's import NumPy and PennyLane, and define our Hamiltonian. +""" +import pennylane as qml +from pennylane import numpy as np + +# set the random seed +np.random.seed(4) + +coeffs = [2, 4, -1, 5, 2] + +obs = [ + qml.PauliX(1), + qml.PauliZ(1), + qml.PauliX(0) @ qml.PauliX(1), + qml.PauliY(0) @ qml.PauliY(1), + qml.PauliZ(0) @ qml.PauliZ(1) +] + + +############################################################################## +# We can now create our quantum device (let's use the ``default.qubit`` simulator). + +num_layers = 2 +num_wires = 2 + +# create a device that estimates expectation values using a finite number of shots +non_analytic_dev = qml.device("default.qubit", wires=num_wires, shots=100, seed=432423) + +# create a device that calculates exact expectation values +analytic_dev = qml.device("default.qubit", wires=num_wires, shots=None) + +############################################################################## +# Now, let's set the total number of shots, and determine the probability +# for sampling each Hamiltonian term. + +total_shots = 8000 +prob_shots = np.abs(coeffs) / np.sum(np.abs(coeffs)) +print(prob_shots) + +############################################################################## +# We can now use SciPy to create our multinomial distributed random variable +# :math:`S,` using the number of trials (total shot number) and probability values: + +from scipy.stats import multinomial + +si = multinomial(n=total_shots, p=prob_shots) + +############################################################################## +# Sampling from this distribution will provide the number of shots used to +# sample each term in the Hamiltonian: + +samples = si.rvs()[0] +print(samples) +print(sum(samples)) + +############################################################################## +# As expected, if we sum the sampled shots per term, we recover the total number of shots. +# +# Let's now create our cost function. Recall that the cost function must do the +# following: +# +# 1. It must sample from the multinomial distribution we created above, +# to determine the number of shots :math:`s_i` to use to estimate the expectation +# value of the ith Hamiltonian term. +# +# 2. It then must estimate the expectation value :math:`\langle h_i\rangle` +# by creating the required QNode. For our ansatz, we'll use the +# :class:`~.pennylane.templates.layers.StronglyEntanglingLayers`. +# +# 3. And, last but not least, estimate the expectation value +# :math:`\langle H\rangle = \sum_i c_i\langle h_i\rangle.` +# + +from pennylane.templates.layers import StronglyEntanglingLayers + + +@qml.qnode(non_analytic_dev, diff_method="parameter-shift", interface="autograd") +def qnode(weights, observable): + StronglyEntanglingLayers(weights, wires=non_analytic_dev.wires) + return qml.expval(observable) + +def cost(params): + # sample from the multinomial distribution + shots_per_term = si.rvs()[0] + + result = 0 + + for o, c, s in zip(obs, coeffs, shots_per_term): + # evaluate the QNode corresponding to + # the Hamiltonian term, and add it on to our running sum + result += c * qnode(params, o, shots=int(s)) + + return result + + +############################################################################## +# Evaluating our cost function with some initial parameters, we can test out +# that our cost function evaluates correctly. + +param_shape = StronglyEntanglingLayers.shape(n_layers=num_layers, n_wires=num_wires) +init_params = np.random.uniform(low=0.0, high=2*np.pi, size=param_shape, requires_grad=True) +print(cost(init_params)) + + +############################################################################## +# Performing the optimization, with the number of shots randomly +# determined at each optimization step: + +opt = qml.AdamOptimizer(0.05) +params = init_params + +cost_wrs = [] +shots_wrs = [] + +for i in range(100): + params, _cost = opt.step_and_cost(cost, params) + cost_wrs.append(_cost) + shots_wrs.append(total_shots*i) + print("Step {}: cost = {} shots used = {}".format(i, cost_wrs[-1], shots_wrs[-1])) + +############################################################################## +# Let's compare this against an optimization not using weighted random sampling. +# Here, we will split the 8000 total shots evenly across all Hamiltonian terms, +# also known as *uniform deterministic sampling*. + +@qml.qnode(non_analytic_dev, diff_method="parameter-shift", interface="autograd") +def qnode(weights, obs): + StronglyEntanglingLayers(weights, wires=non_analytic_dev.wires) + return qml.expval(obs) + +def cost(params): + shots_per_term = int(total_shots / len(coeffs)) + + result = 0 + + for o, c in zip(obs, coeffs): + + # evaluate the QNode corresponding to + # the Hamiltonian term, and add it on to our running sum + result += c * qnode(params, o, shots=shots_per_term) + + return result + +opt = qml.AdamOptimizer(0.05) +params = init_params + +cost_adam = [] +shots_adam = [] + +for i in range(100): + params, _cost = opt.step_and_cost(cost, params) + cost_adam.append(_cost) + shots_adam.append(total_shots*i) + print("Step {}: cost = {} shots used = {}".format(i, cost_adam[-1], shots_adam[-1])) + +############################################################################## +# Comparing these two techniques: + +from matplotlib import pyplot as plt + +plt.style.use("seaborn-v0_8") +plt.plot(shots_wrs, cost_wrs, "b", label="Adam WRS") +plt.plot(shots_adam, cost_adam, "g", label="Adam") + +plt.ylabel("Cost function value") +plt.xlabel("Number of shots") +plt.legend() +plt.show() + +############################################################################## +# We can see that weighted random sampling performs just as well as the uniform +# deterministic sampling. However, weighted random sampling begins to show a +# non-negligible improvement over deterministic sampling for large Hamiltonians +# with highly non-uniform coefficients. For example, see Fig (3) and (4) of +# Arrasmith et al. [#arrasmith2020]_, comparing weighted random sampling VQE optimization +# for both :math:`\text{H}_2` and :math:`\text{LiH}` molecules. +# +# .. note:: +# +# While not covered here, another approach that could be taken is +# *weighted deterministic sampling*. Here, the number of shots is distributed +# across terms as per +# +# .. math:: s_i = \left\lfloor N \frac{|c_i|}{\sum_i |c_i|}\right\rfloor, +# +# where :math:`N` is the total number of shots. +# + +############################################################################## +# Rosalin: Frugal shot optimization +# --------------------------------- +# +# We can see above that both methods optimize fairly well; weighted random +# sampling converges just as well as evenly distributing the shots across +# all Hamiltonian terms. However, deterministic shot distribution approaches +# will always have a minimum shot value required per expectation value, as below +# this threshold they become biased estimators. This is not the case with random +# sampling; as we saw in the +# :doc:`doubly stochastic gradient descent demonstration `, +# the introduction of randomness allows for as little +# as a single shot per expectation term, while still remaining an unbiased estimator. +# +# Using this insight, Arrasmith et al. [#arrasmith2020]_ modified the iCANS frugal +# shot-optimization technique [#kubler2020]_ to include weighted random sampling, making it +# 'doubly stochastic'. +# +# iCANS optimizer +# ~~~~~~~~~~~~~~~ +# +# Two variants of the iCANS optimizer were introduced in Kübler et al., iCANS1 and iCANS2. +# The iCANS1 optimizer, on which Rosalin is based, frugally distributes a shot budget +# across the partial derivatives of each parameter, which are computed using the +# :doc:`parameter-shift rule `. It works roughly as follows: +# +# 1. The initial step of the optimizer is performed with some specified minimum +# number of shots, :math:`s_{min},` for all partial derivatives. +# +# 2. The parameter-shift rule is then used to estimate the gradient :math:`g_i` +# for each parameter :math:`\theta_i,` parameters, as well as the *variances* +# :math:`v_i` of the estimated gradients. +# +# 3. Gradient descent is performed for each parameter :math:`\theta_i,` using +# the pre-defined learning rate :math:`\alpha` and the gradient information :math:`g_i:` +# +# .. math:: \theta_i = \theta_i - \alpha g_i. +# +# 4. The improvement in the cost function per shot, for a specific parameter value, +# is then calculated via +# +# .. math:: +# +# \gamma_i = \frac{1}{s_i} \left[ \left(\alpha - \frac{1}{2} L\alpha^2\right) +# g_i^2 - \frac{L\alpha^2}{2s_i}v_i \right], +# +# where: +# +# * :math:`L \leq \sum_i|c_i|` is the bound on the `Lipschitz constant +# `__ of the variational quantum algorithm objective function, +# +# * :math:`c_i` are the coefficients of the Hamiltonian, and +# +# * :math:`\alpha` is the learning rate, and *must* be bound such that :math:`\alpha < 2/L` +# for the above expression to hold. +# +# 5. Finally, the new values of :math:`s_i` (shots for partial derivative of parameter +# :math:`\theta_i`) is given by: +# +# .. math:: +# +# s_i = \frac{2L\alpha}{2-L\alpha}\left(\frac{v_i}{g_i^2}\right)\propto +# \frac{v_i}{g_i^2}. +# +# In addition to the above, to counteract the presence of noise in the system, a +# running average of :math:`g_i` and :math:`s_i` (:math:`\chi_i` and :math:`\xi_i` respectively) +# are used when computing :math:`\gamma_i` and :math:`s_i.` +# +# .. note:: +# +# In classical machine learning, the Lipschitz constant of the cost function is generally +# unknown. However, for a variational quantum algorithm with cost of the form +# :math:`f(x) = \langle \psi(x) | \hat{H} |\psi(x)\rangle,` +# an upper bound on the Lipschitz constant is given by :math:`L < \sum_i|c_i|,` +# where :math:`c_i` are the coefficients of :math:`\hat{H}` when decomposed +# into a linear combination of Pauli-operator tensor products. +# +# Rosalin implementation +# ~~~~~~~~~~~~~~~~~~~~~~ +# +# Let's now modify iCANS above to incorporate weighted random sampling of Hamiltonian +# terms — the Rosalin frugal shot optimizer. +# +# Rosalin takes several hyper-parameters: +# +# * ``min_shots``: the minimum number of shots used to estimate the expectations +# of each term in the Hamiltonian. Note that this must be larger than 2 for the variance +# of the gradients to be computed. +# +# * ``mu``: The running average constant :math:`\mu\in[0, 1].` Used to control how quickly the +# number of shots recommended for each gradient component changes. +# +# * ``b``: Regularization bias. The bias should be kept small, but non-zero. +# +# * ``lr``: The learning rate. Recall from above that the learning rate *must* be such +# that :math:`\alpha < 2/L = 2/\sum_i|c_i|.` +# +# Since the Rosalin optimizer has a state that must be preserved between optimization steps, +# let's use a class to create our optimizer. +# + +class Rosalin: + + def __init__(self, obs, coeffs, min_shots, mu=0.99, b=1e-6, lr=0.07): + self.obs = obs + self.coeffs = coeffs + + self.lipschitz = np.sum(np.abs(coeffs)) + + if lr > 2 / self.lipschitz: + raise ValueError("The learning rate must be less than ", 2 / self.lipschitz) + + # hyperparameters + self.min_shots = min_shots + self.mu = mu # running average constant + self.b = b # regularization bias + self.lr = lr # learning rate + + # keep track of the total number of shots used + self.shots_used = 0 + # total number of iterations + self.k = 0 + # Number of shots per parameter + self.s = np.zeros_like(params, dtype=np.float64) + min_shots + + # Running average of the parameter gradients + self.chi = None + # Running average of the variance of the parameter gradients + self.xi = None + + def estimate_hamiltonian(self, params, shots): + """Returns an array containing length ``shots`` single-shot estimates + of the Hamiltonian. The shots are distributed randomly over + the terms in the Hamiltonian, as per a Multinomial distribution. + + Since we are performing single-shot estimates, the QNodes must be + set to 'sample' mode. + """ + # note that convergence depends on seed for random number generation + rosalin_device = qml.device("default.qubit", wires=num_wires, shots=100, seed=93754352) + + # determine the shot probability per term + prob_shots = np.abs(coeffs) / np.sum(np.abs(coeffs)) + + # construct the multinomial distribution, and sample + # from it to determine how many shots to apply per term + si = multinomial(n=shots, p=prob_shots) + shots_per_term = si.rvs()[0] + + results = [] + + @qml.qnode(rosalin_device, diff_method="parameter-shift", interface="autograd") + def qnode(weights, observable): + StronglyEntanglingLayers(weights, wires=rosalin_device.wires) + return qml.sample(observable) + + for o, c, p, s in zip(self.obs, self.coeffs, prob_shots, shots_per_term): + + # if the number of shots is 0, do nothing + if s == 0: + continue + + # evaluate the QNode corresponding to + # the Hamiltonian term + res = qnode(params, o, shots=int(s)) + + if s == 1: + res = np.array([res]) + + # Note that, unlike above, we divide each term by the + # probability per shot. This is because we are sampling one at a time. + results.append(c * res / p) + + return np.concatenate(results) + + def evaluate_grad_var(self, i, params, shots): + """Evaluate the gradient, as well as the variance in the gradient, + for the ith parameter in params, using the parameter-shift rule. + """ + shift = np.zeros_like(params) + shift[i] = np.pi / 2 + + shift_forward = self.estimate_hamiltonian(params + shift, shots) + shift_backward = self.estimate_hamiltonian(params - shift, shots) + + g = np.mean(shift_forward - shift_backward) / 2 + s = np.var((shift_forward - shift_backward) / 2, ddof=1) + + return g, s + + def step(self, params): + """Perform a single step of the Rosalin optimizer.""" + # keep track of the number of shots run + self.shots_used += int(2 * np.sum(self.s)) + + # compute the gradient, as well as the variance in the gradient, + # using the number of shots determined by the array s. + grad = [] + S = [] + + p_ind = list(np.ndindex(*params.shape)) + + for l in p_ind: + # loop through each parameter, performing + # the parameter-shift rule + g_, s_ = self.evaluate_grad_var(l, params, self.s[l]) + grad.append(g_) + S.append(s_) + + grad = np.reshape(np.stack(grad), params.shape) + S = np.reshape(np.stack(S), params.shape) + + # gradient descent update + params = params - self.lr * grad + + if self.xi is None: + self.chi = np.zeros_like(params, dtype=np.float64) + self.xi = np.zeros_like(params, dtype=np.float64) + + # running average of the gradient variance + self.xi = self.mu * self.xi + (1 - self.mu) * S + xi = self.xi / (1 - self.mu ** (self.k + 1)) + + # running average of the gradient + self.chi = self.mu * self.chi + (1 - self.mu) * grad + chi = self.chi / (1 - self.mu ** (self.k + 1)) + + # determine the new optimum shots distribution for the next + # iteration of the optimizer + s = np.ceil( + (2 * self.lipschitz * self.lr * xi) + / ((2 - self.lipschitz * self.lr) * (chi ** 2 + self.b * (self.mu ** self.k))) + ) + + # apply an upper and lower bound on the new shot distributions, + # to avoid the number of shots reducing below min(2, min_shots), + # or growing too significantly. + gamma = ( + (self.lr - self.lipschitz * self.lr ** 2 / 2) * chi ** 2 + - xi * self.lipschitz * self.lr ** 2 / (2 * s) + ) / s + + argmax_gamma = np.unravel_index(np.argmax(gamma), gamma.shape) + smax = s[argmax_gamma] + self.s = np.clip(s, min(2, self.min_shots), max(2, smax)) + + self.k += 1 + return params + + +############################################################################## +# Rosalin optimization +# ~~~~~~~~~~~~~~~~~~~~ +# +# We are now ready to use our Rosalin optimizer to optimize the initial VQE problem. But first let's +# also create a separate cost function using an 'exact' quantum device, so that we can keep track of the +# *exact* cost function value at each iteration. + +@qml.qnode(analytic_dev, interface="autograd") +def cost_analytic(weights): + StronglyEntanglingLayers(weights, wires=analytic_dev.wires) + return qml.expval(qml.Hamiltonian(coeffs, obs)) + +############################################################################## +# Creating the optimizer and beginning the optimization: + + +opt = Rosalin(obs, coeffs, min_shots=10) +params = init_params + +cost_rosalin = [cost_analytic(params)] +shots_rosalin = [0] + +for i in range(60): + params = opt.step(params) + cost_rosalin.append(cost_analytic(params)) + shots_rosalin.append(opt.shots_used) + print(f"Step {i}: cost = {cost_rosalin[-1]}, shots_used = {shots_rosalin[-1]}") + + +############################################################################## +# Let's compare this to a standard Adam optimization. Using 100 shots per quantum +# evaluation, for each update step there are 2 quantum evaluations per parameter. + +adam_shots_per_eval = 100 +adam_shots_per_step = 2 * adam_shots_per_eval * len(params.flatten()) +print(adam_shots_per_step) + +############################################################################## +# Thus, Adam is using 2400 shots per update step. + +params = init_params +opt = qml.AdamOptimizer(0.07) + +adam_dev = qml.device('default.qubit', shots=adam_shots_per_eval, seed=595905) + +@qml.qnode(adam_dev, diff_method="parameter-shift", interface="autograd") +def cost(weights): + StronglyEntanglingLayers(weights, wires=non_analytic_dev.wires) + return qml.expval(qml.Hamiltonian(coeffs, obs)) + +cost_adam = [cost_analytic(params)] +shots_adam = [0] + +for i in range(100): + params = opt.step(cost, params) + cost_adam.append(cost_analytic(params)) + shots_adam.append(adam_shots_per_step * (i + 1)) + print("Step {}: cost = {} shots_used = {}".format(i, cost_adam[-1], shots_adam[-1])) + +############################################################################## +# Plotting both experiments: + +plt.style.use("seaborn-v0_8") +plt.plot(shots_rosalin, cost_rosalin, "b", label="Rosalin") +plt.plot(shots_adam, cost_adam, "g", label="Adam") + +plt.ylabel("Cost function value") +plt.xlabel("Number of shots") +plt.legend() +plt.xlim(0, 300000) +plt.show() + +############################################################################## +# The Rosalin optimizer performs significantly better than the Adam optimizer, +# approaching the ground state energy of the Hamiltonian with strikingly +# fewer shots. +# +# While beyond the scope of this demonstration, the Rosalin optimizer can be +# modified in various other ways; for instance, by incorporating *weighted hybrid +# sampling* (which distributes some shots deterministically, with the remainder +# done randomly), or by adapting the variant iCANS2 optimizer. Download +# this demonstration from the sidebar 👉 and give it a go! ⚛️ + + +############################################################################## +# References +# ---------- +# +# .. [#arrasmith2020] +# +# Andrew Arrasmith, Lukasz Cincio, Rolando D. Somma, and Patrick J. Coles. "Operator Sampling +# for Shot-frugal Optimization in Variational Algorithms." `arXiv:2004.06252 +# `__ (2020). +# +# .. [#stokes2019] +# +# James Stokes, Josh Izaac, Nathan Killoran, and Giuseppe Carleo. "Quantum Natural Gradient." +# `arXiv:1909.02108 `__ (2019). +# +# .. [#sweke2019] +# +# Ryan Sweke, Frederik Wilde, Johannes Jakob Meyer, Maria Schuld, Paul K. Fährmann, Barthélémy +# Meynard-Piganeau, and Jens Eisert. "Stochastic gradient descent for hybrid quantum-classical +# optimization." `arXiv:1910.01155 `__ (2019). +# +# .. [#kubler2020] +# +# Jonas M. Kübler, Andrew Arrasmith, Lukasz Cincio, and Patrick J. Coles. "An Adaptive Optimizer +# for Measurement-Frugal Variational Algorithms." `Quantum 4, 263 +# `__ (2020). +# +# +# About the author +# ---------------- # \ No newline at end of file diff --git a/demonstrations/vqe_parallel.py b/demonstrations/vqe_parallel.py index 7c298007e8..91c47d7bf1 100644 --- a/demonstrations/vqe_parallel.py +++ b/demonstrations/vqe_parallel.py @@ -1,393 +1,393 @@ - -# coding=utf-8 -r""" -VQE with parallel QPUs with Rigetti -======================================== - -.. meta:: - :property="og:description": Using parallel QPUs to - speed up the calculation of the potential energy surface of molecular Hamiltonian. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/vqe_diagram.png - -.. related:: - - tutorial_vqe A brief overview of VQE - -*Author: Tom Bromley — Posted: 14 February 2020. Last updated: 29 August 2023.* - -.. warning:: - This demo requires Python <=3.10 and uses the PennyLane-Rigetti plugin, which is only compatible with PennyLane v0.40 or below. To run this demo with newer versions of PennyLane, you will need to use `a different simulator device `__. - -This tutorial showcases how using asynchronously-evaluated parallel QPUs can speed up the -calculation of the potential energy surface of molecular hydrogen (:math:`H_2`). - -Using a VQE setup, we task two devices from the -`PennyLane-Rigetti `__ plugin with evaluating -separate terms in the qubit Hamiltonian of :math:`H_2.` As these devices are allowed to operate -asynchronously, i.e., at the same time and without having to wait for each other, -the calculation can be performed in roughly half the time. - -We begin by importing the prerequisite libraries: -""" - -import time -import dask - -import matplotlib.pyplot as plt -from pennylane import numpy as np -import pennylane as qml -from pennylane import qchem - -############################################################################## -# -# This tutorial requires the ``pennylane-rigetti`` and ``dask`` -# packages, which are installed separately using: -# -# .. code-block:: bash -# -# pip install pennylane-rigetti -# pip install "dask[delayed]" -# -# Finding the qubit Hamiltonians of :math:`H_{2}` -# ----------------------------------------------- -# -# The objective of this tutorial is to evaluate the potential energy surface of molecular -# hydrogen. This is achieved by finding the ground state energy of :math:`H_{2}` as we increase -# the bond length between the hydrogen atoms. -# -# Each inter-atomic distance results in a different qubit Hamiltonian. Further -# details on the mapping from the electronic Hamiltonian of a molecule to a -# qubit Hamiltonian can be found in the -# :doc:`tutorial_quantum_chemistry` and :doc:`tutorial_vqe` -# tutorials. -# -# We begin by downloading a selection of datasets of :math:`H_2` molecule for -# various bond lengths using the -# `PennyLane Datasets library `__: - -bonds = [0.5, 0.58, 0.7, 0.9, 1.1, 1.3, 1.5, 1.7, 1.9, 2.1] -datasets = qml.data.load("qchem", molname="H2", bondlength=bonds, basis="STO-3G") - -############################################################################## -# We can now extract the qubit Hamiltonians from these datasets for each bond length: - -hamiltonians = [d.hamiltonian for d in datasets] - -############################################################################## -# Each Hamiltonian can be written as a linear combination of fifteen tensor products of Pauli -# matrices. Let's take a look more closely at one of the Hamiltonians: - -h = hamiltonians[0] -_, h_ops = h.terms() - -print("Number of terms: {}\n".format(len(h_ops))) -for op in h_ops: - print("Measurement {} on wires {}".format(str(op), op.wires)) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# Number of terms: 15 -# -# Measurement I(0) on wires Wires([0]) -# Measurement Z(0) on wires Wires([0]) -# Measurement Z(1) on wires Wires([1]) -# Measurement Z(0) @ Z(1) on wires Wires([0, 1]) -# Measurement Y(0) @ X(1) @ X(2) @ Y(3) on wires Wires([0, 1, 2, 3]) -# Measurement Y(0) @ Y(1) @ X(2) @ X(3) on wires Wires([0, 1, 2, 3]) -# Measurement X(0) @ X(1) @ Y(2) @ Y(3) on wires Wires([0, 1, 2, 3]) -# Measurement X(0) @ Y(1) @ Y(2) @ X(3) on wires Wires([0, 1, 2, 3]) -# Measurement Z(2) on wires Wires([2]) -# Measurement Z(0) @ Z(2) on wires Wires([0, 2]) -# Measurement Z(3) on wires Wires([3]) -# Measurement Z(0) @ Z(3) on wires Wires([0, 3]) -# Measurement Z(1) @ Z(2) on wires Wires([1, 2]) -# Measurement Z(1) @ Z(3) on wires Wires([1, 3]) -# Measurement Z(2) @ Z(3) on wires Wires([2, 3]) - -############################################################################## -# Defining the energy function -# ---------------------------- -# -# The fifteen Pauli terms comprising each Hamiltonian can conventionally be evaluated in a -# sequential manner: we evaluate one expectation value at a time before moving on to the next. -# However, this task is highly suited to parallelization. With access to multiple QPUs, -# we can split up evaluating the terms between the QPUs and gain an increase in processing speed. -# -# -# .. note:: -# Some of the Pauli terms commute, and so they can be evaluated in practice with fewer than -# fifteen quantum circuit runs. Nevertheless, these quantum circuit runs can still be -# parallelized to multiple QPUs. -# -# Let's suppose we have access to two quantum devices. In this tutorial we consider two -# simulators from Rigetti: ``4q-qvm`` and ``9q-square-qvm``, but we could also run on hardware -# devices from Rigetti or other providers. -# -# We can evaluate the expectation value of each Hamiltonian with eight terms run on -# one device and seven terms run on the other, as summarized by the diagram below: -# -# .. figure:: /_static/demonstration_assets/vqe_parallel/vqe_diagram.png -# :width: 65% -# :align: center -# -# To do this, start by instantiating a device for each term: - -dev1 = [qml.device("rigetti.qvm", device="4q-qvm") for _ in range(8)] -dev2 = [qml.device("rigetti.qvm", device="9q-square-qvm") for _ in range(7)] -devs = dev1 + dev2 - -############################################################################## -# .. note:: -# -# For the purposes of this demonstration, we are simulating the QPUs using the -# ``rigetti.qvm`` simulator. To run this demonstration on hardware, simply -# swap ``rigetti.qvm`` for ``rigetti.qpu`` and specify the hardware device to run on. -# -# Please refer to the `Rigetti website `__ for an up-to-date -# list on available QPUs. -# -# .. warning:: -# Rigetti's QVM and Quil Compiler services must be running for this tutorial to execute. They -# can be installed by consulting the `Rigetti documentation -# `__ or, for users with Docker, by running: -# -# .. code-block:: bash -# -# docker run -d -p 5555:5555 rigetti/quilc -R -p 5555 -# docker run -d -p 5000:5000 rigetti/qvm -S -p 5000 -# -# We must also define a circuit to prepare the ground state, which is a superposition of the -# Hartree-Fock (:math:`|1100\rangle`) and doubly-excited (:math:`|0011\rangle`) configurations. -# The simple circuit below is able to prepare states of the form :math:`\alpha |1100\rangle + -# \beta |0011\rangle` and hence encode the ground state wave function of the hydrogen molecule. The -# circuit has a single free parameter, which controls a Y-rotation on the third qubit. - - -def circuit(param, H): - qml.BasisState(np.array([1, 1, 0, 0], requires_grad=False), wires=[0, 1, 2, 3]) - qml.RY(param, wires=2) - qml.CNOT(wires=[2, 3]) - qml.CNOT(wires=[2, 0]) - qml.CNOT(wires=[3, 1]) - return qml.expval(H) - - -############################################################################## -# The ground state for each inter-atomic distance is characterized by a different Y-rotation angle. -# The values of these Y-rotations can be found by minimizing the ground state energy as outlined in -# :doc:`tutorial_vqe`. In this tutorial, we load pre-optimized rotations and focus on -# comparing the speed of evaluating the potential energy surface with sequential and parallel -# evaluation. These parameters can be downloaded by clicking :download:`here -# <../_static/demonstration_assets/vqe_parallel/RY_params.npy>`. - -params = np.load("vqe_parallel/RY_params.npy") - -############################################################################## -# Calculating the potential energy surface -# ---------------------------------------- -# The most vanilla execution of these 10 energy surfaces is using the standard PennyLane functionalities by executing the QNodes. -# Internally, this creates a measurement for each term in the Hamiltonian that are then sequentially computed. - -print("Evaluating the potential energy surface sequentially") -t0 = time.time() - -energies_seq = [] -for i, (h, param) in enumerate(zip(hamiltonians, params)): - print( - f"{i+1} / {len(bonds)}: Sequential execution; Running for inter-atomic distance {bonds[i]} Å" - ) - energies_seq.append(qml.QNode(circuit, devs[0])(param, h)) - -dt_seq = time.time() - t0 - -print(f"Evaluation time: {dt_seq:.2f} s") - -############################################################################## -# We can parallelize the individual evaluations using ``dask`` in the following way: We take the 15 terms of the Hamiltonian and -# distribute them to the 15 devices in ``devs``. This evaluation is delayed using ``dask.delayed`` and later computed -# in parallel using ``dask.compute``, which asynchronously executes the delayed objects in ``results``. - - -def compute_energy_parallel(H, devs, param): - H_coeffs, H_ops = H.terms() - assert len(H_ops) == len(devs) - results = [] - - for i in range(len(H_ops)): - qnode = qml.QNode(circuit, devs[i]) - results.append(dask.delayed(qnode)(param, H_ops[i])) - - results = dask.compute(*results, scheduler="threads") - result = sum(c * r for c, r in zip(H_coeffs, results)) - return result - - -############################################################################## -# We can now compute all 10 samples from the energy surface sequentially, where each execution is making use of -# parallel device execution. Curiously, in this example the overhead from doing so outweighs the speed-up -# and the execution is slower than standard execution using ``qml.expval``. For different circuits and -# different Hamiltonians, however, parallelization may provide significant speed-ups. - -print("Evaluating the potential energy surface in parallel") -t0 = time.time() - -energies_par = [] -for i, (h, param) in enumerate(zip(hamiltonians, params)): - print( - f"{i+1} / {len(bonds)}: Parallel execution; Running for inter-atomic distance {bonds[i]} Å" - ) - energies_par.append(compute_energy_parallel(h, devs, param)) - -dt_par = time.time() - t0 - -print(f"Evaluation time: {dt_par:.2f} s") - - -############################################################################## -# We can improve this procedure further by optimizing the measurement. Currently, we are measuring each term of the Hamiltonian -# in a separate measurement. This is not necessary as there are sub-groups of commuting terms in the Hamiltonian that can be measured -# simultaneously. We can utilize the grouping function :func:`~.pennylane.pauli.group_observables` to generate few measurements that -# are executed in parallel: - - -def compute_energy_parallel_optimized(H, devs, param): - H_coeffs, H_ops = H.terms() - assert len(H_ops) == len(devs) - results = [] - - obs_groupings, coeffs_groupings = qml.pauli.group_observables( - H_ops, H_coeffs, "qwc" - ) - - for i, (obs, coeffs) in enumerate(zip(obs_groupings, coeffs_groupings)): - H_part = qml.Hamiltonian(coeffs, obs) - qnode = qml.QNode(circuit, devs[i]) - results.append(dask.delayed(qnode)(param, H_part)) - - result = qml.math.sum(dask.compute(*results, scheduler="threads")) - return result - -print( - "Evaluating the potential energy surface in parallel with measurement optimization" -) -t0 = time.time() - -energies_par_opt = [] -for i, (h, param) in enumerate(zip(hamiltonians, params)): - print( - f"{i+1} / {len(bonds)}: Parallel execution and measurement optimization; Running for inter-atomic distance {bonds[i]} Å" - ) - energies_par_opt.append(compute_energy_parallel_optimized(h, devs, param)) - -dt_par_opt = time.time() - t0 - -print(f"Evaluation time: {dt_par_opt:.2f} s") - - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# Evaluating the potential energy surface sequentially -# 1 / 10: Sequential execution; Running for inter-atomic distance 0.3 Å -# 2 / 10: Sequential execution; Running for inter-atomic distance 0.5 Å -# 3 / 10: Sequential execution; Running for inter-atomic distance 0.7 Å -# 4 / 10: Sequential execution; Running for inter-atomic distance 0.9 Å -# 5 / 10: Sequential execution; Running for inter-atomic distance 1.1 Å -# 6 / 10: Sequential execution; Running for inter-atomic distance 1.3 Å -# 7 / 10: Sequential execution; Running for inter-atomic distance 1.5 Å -# 8 / 10: Sequential execution; Running for inter-atomic distance 1.7 Å -# 9 / 10: Sequential execution; Running for inter-atomic distance 1.9 Å -# 10 / 10: Sequential execution; Running for inter-atomic distance 2.1 Å -# Evaluation time: 39.33 s -# -# Evaluating the potential energy surface in parallel -# 1 / 10: Parallel execution; Running for inter-atomic distance 0.3 Å -# 2 / 10: Parallel execution; Running for inter-atomic distance 0.5 Å -# 3 / 10: Parallel execution; Running for inter-atomic distance 0.7 Å -# 4 / 10: Parallel execution; Running for inter-atomic distance 0.9 Å -# 5 / 10: Parallel execution; Running for inter-atomic distance 1.1 Å -# 6 / 10: Parallel execution; Running for inter-atomic distance 1.3 Å -# 7 / 10: Parallel execution; Running for inter-atomic distance 1.5 Å -# 8 / 10: Parallel execution; Running for inter-atomic distance 1.7 Å -# 9 / 10: Parallel execution; Running for inter-atomic distance 1.9 Å -# 10 / 10: Parallel execution; Running for inter-atomic distance 2.1 Å -# Evaluation time: 73.42 s -# -# Evaluating the potential energy surface in parallel with measurement optimization -# 1 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.3 Å -# 2 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.5 Å -# 3 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.7 Å -# 4 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.9 Å -# 5 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.1 Å -# 6 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.3 Å -# 7 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.5 Å -# 8 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.7 Å -# 9 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.9 Å -# 10 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 2.1 Å -# Evaluation time: 26.51 s - - -############################################################################## -# We have seen how Hamiltonian measurements can be parallelized and optimized at the same time. - -print("Speed up: {0:.2f}".format(dt_seq / dt_par_opt)) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# Speed up: 1.48 - -############################################################################## -# To conclude the tutorial, let's plot the calculated -# potential energy surfaces: - -np.savez( - "vqe_parallel", - energies_seq=energies_seq, - energies_par=energies_par, - energies_par_opt=energies_par_opt, -) - -plt.plot( - bonds, energies_seq, linewidth=2.2, marker="d", color="green", label="sequential" -) -plt.plot(bonds, energies_par, linewidth=2.2, marker="o", color="red", label="parallel") -plt.plot( - bonds, - energies_par_opt, - linewidth=2.2, - marker="d", - color="blue", - label="paralell and optimized", -) -plt.legend(fontsize=12) -plt.title("Potential energy surface for molecular hydrogen", fontsize=12) -plt.xlabel("Atomic separation (Å)", fontsize=16) -plt.ylabel("Ground state energy (Ha)", fontsize=16) -plt.grid(True) - -############################################################################## -# .. figure:: /_static/demonstration_assets/vqe_parallel/vqe_parallel_001.png -# :width: 80% -# :align: center -# - -############################################################################## -# These surfaces overlap, with any variation due to the limited number of shots used to evaluate the -# expectation values in the ``rigetti.qvm`` device (we are using the default value of -# ``shots=1024``). - -############################################################################## -# About the author -# ---------------- -# + +# coding=utf-8 +r""" +VQE with parallel QPUs with Rigetti +======================================== + +.. meta:: + :property="og:description": Using parallel QPUs to + speed up the calculation of the potential energy surface of molecular Hamiltonian. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/vqe_diagram.png + +.. related:: + + tutorial_vqe A brief overview of VQE + +*Author: Tom Bromley — Posted: 14 February 2020. Last updated: 29 August 2023.* + +.. warning:: + This demo requires Python <=3.10 and uses the PennyLane-Rigetti plugin, which is only compatible with PennyLane v0.40 or below. To run this demo with newer versions of PennyLane, you will need to use `a different simulator device `__. + +This tutorial showcases how using asynchronously-evaluated parallel QPUs can speed up the +calculation of the potential energy surface of molecular hydrogen (:math:`H_2`). + +Using a VQE setup, we task two devices from the +`PennyLane-Rigetti `__ plugin with evaluating +separate terms in the qubit Hamiltonian of :math:`H_2.` As these devices are allowed to operate +asynchronously, i.e., at the same time and without having to wait for each other, +the calculation can be performed in roughly half the time. + +We begin by importing the prerequisite libraries: +""" + +import time +import dask + +import matplotlib.pyplot as plt +from pennylane import numpy as np +import pennylane as qml +from pennylane import qchem + +############################################################################## +# +# This tutorial requires the ``pennylane-rigetti`` and ``dask`` +# packages, which are installed separately using: +# +# .. code-block:: bash +# +# pip install pennylane-rigetti +# pip install "dask[delayed]" +# +# Finding the qubit Hamiltonians of :math:`H_{2}` +# ----------------------------------------------- +# +# The objective of this tutorial is to evaluate the potential energy surface of molecular +# hydrogen. This is achieved by finding the ground state energy of :math:`H_{2}` as we increase +# the bond length between the hydrogen atoms. +# +# Each inter-atomic distance results in a different qubit Hamiltonian. Further +# details on the mapping from the electronic Hamiltonian of a molecule to a +# qubit Hamiltonian can be found in the +# :doc:`tutorial_quantum_chemistry` and :doc:`tutorial_vqe` +# tutorials. +# +# We begin by downloading a selection of datasets of :math:`H_2` molecule for +# various bond lengths using the +# `PennyLane Datasets library `__: + +bonds = [0.5, 0.58, 0.7, 0.9, 1.1, 1.3, 1.5, 1.7, 1.9, 2.1] +datasets = qml.data.load("qchem", molname="H2", bondlength=bonds, basis="STO-3G") + +############################################################################## +# We can now extract the qubit Hamiltonians from these datasets for each bond length: + +hamiltonians = [d.hamiltonian for d in datasets] + +############################################################################## +# Each Hamiltonian can be written as a linear combination of fifteen tensor products of Pauli +# matrices. Let's take a look more closely at one of the Hamiltonians: + +h = hamiltonians[0] +_, h_ops = h.terms() + +print("Number of terms: {}\n".format(len(h_ops))) +for op in h_ops: + print("Measurement {} on wires {}".format(str(op), op.wires)) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Number of terms: 15 +# +# Measurement I(0) on wires Wires([0]) +# Measurement Z(0) on wires Wires([0]) +# Measurement Z(1) on wires Wires([1]) +# Measurement Z(0) @ Z(1) on wires Wires([0, 1]) +# Measurement Y(0) @ X(1) @ X(2) @ Y(3) on wires Wires([0, 1, 2, 3]) +# Measurement Y(0) @ Y(1) @ X(2) @ X(3) on wires Wires([0, 1, 2, 3]) +# Measurement X(0) @ X(1) @ Y(2) @ Y(3) on wires Wires([0, 1, 2, 3]) +# Measurement X(0) @ Y(1) @ Y(2) @ X(3) on wires Wires([0, 1, 2, 3]) +# Measurement Z(2) on wires Wires([2]) +# Measurement Z(0) @ Z(2) on wires Wires([0, 2]) +# Measurement Z(3) on wires Wires([3]) +# Measurement Z(0) @ Z(3) on wires Wires([0, 3]) +# Measurement Z(1) @ Z(2) on wires Wires([1, 2]) +# Measurement Z(1) @ Z(3) on wires Wires([1, 3]) +# Measurement Z(2) @ Z(3) on wires Wires([2, 3]) + +############################################################################## +# Defining the energy function +# ---------------------------- +# +# The fifteen Pauli terms comprising each Hamiltonian can conventionally be evaluated in a +# sequential manner: we evaluate one expectation value at a time before moving on to the next. +# However, this task is highly suited to parallelization. With access to multiple QPUs, +# we can split up evaluating the terms between the QPUs and gain an increase in processing speed. +# +# +# .. note:: +# Some of the Pauli terms commute, and so they can be evaluated in practice with fewer than +# fifteen quantum circuit runs. Nevertheless, these quantum circuit runs can still be +# parallelized to multiple QPUs. +# +# Let's suppose we have access to two quantum devices. In this tutorial we consider two +# simulators from Rigetti: ``4q-qvm`` and ``9q-square-qvm``, but we could also run on hardware +# devices from Rigetti or other providers. +# +# We can evaluate the expectation value of each Hamiltonian with eight terms run on +# one device and seven terms run on the other, as summarized by the diagram below: +# +# .. figure:: /_static/demonstration_assets/vqe_parallel/vqe_diagram.png +# :width: 65% +# :align: center +# +# To do this, start by instantiating a device for each term: + +dev1 = [qml.device("rigetti.qvm", device="4q-qvm") for _ in range(8)] +dev2 = [qml.device("rigetti.qvm", device="9q-square-qvm") for _ in range(7)] +devs = dev1 + dev2 + +############################################################################## +# .. note:: +# +# For the purposes of this demonstration, we are simulating the QPUs using the +# ``rigetti.qvm`` simulator. To run this demonstration on hardware, simply +# swap ``rigetti.qvm`` for ``rigetti.qpu`` and specify the hardware device to run on. +# +# Please refer to the `Rigetti website `__ for an up-to-date +# list on available QPUs. +# +# .. warning:: +# Rigetti's QVM and Quil Compiler services must be running for this tutorial to execute. They +# can be installed by consulting the `Rigetti documentation +# `__ or, for users with Docker, by running: +# +# .. code-block:: bash +# +# docker run -d -p 5555:5555 rigetti/quilc -R -p 5555 +# docker run -d -p 5000:5000 rigetti/qvm -S -p 5000 +# +# We must also define a circuit to prepare the ground state, which is a superposition of the +# Hartree-Fock (:math:`|1100\rangle`) and doubly-excited (:math:`|0011\rangle`) configurations. +# The simple circuit below is able to prepare states of the form :math:`\alpha |1100\rangle + +# \beta |0011\rangle` and hence encode the ground state wave function of the hydrogen molecule. The +# circuit has a single free parameter, which controls a Y-rotation on the third qubit. + + +def circuit(param, H): + qml.BasisState(np.array([1, 1, 0, 0], requires_grad=False), wires=[0, 1, 2, 3]) + qml.RY(param, wires=2) + qml.CNOT(wires=[2, 3]) + qml.CNOT(wires=[2, 0]) + qml.CNOT(wires=[3, 1]) + return qml.expval(H) + + +############################################################################## +# The ground state for each inter-atomic distance is characterized by a different Y-rotation angle. +# The values of these Y-rotations can be found by minimizing the ground state energy as outlined in +# :doc:`tutorial_vqe`. In this tutorial, we load pre-optimized rotations and focus on +# comparing the speed of evaluating the potential energy surface with sequential and parallel +# evaluation. These parameters can be downloaded by clicking :download:`here +# <../_static/demonstration_assets/vqe_parallel/RY_params.npy>`. + +params = np.load("vqe_parallel/RY_params.npy") + +############################################################################## +# Calculating the potential energy surface +# ---------------------------------------- +# The most vanilla execution of these 10 energy surfaces is using the standard PennyLane functionalities by executing the QNodes. +# Internally, this creates a measurement for each term in the Hamiltonian that are then sequentially computed. + +print("Evaluating the potential energy surface sequentially") +t0 = time.time() + +energies_seq = [] +for i, (h, param) in enumerate(zip(hamiltonians, params)): + print( + f"{i+1} / {len(bonds)}: Sequential execution; Running for inter-atomic distance {bonds[i]} Å" + ) + energies_seq.append(qml.QNode(circuit, devs[0])(param, h)) + +dt_seq = time.time() - t0 + +print(f"Evaluation time: {dt_seq:.2f} s") + +############################################################################## +# We can parallelize the individual evaluations using ``dask`` in the following way: We take the 15 terms of the Hamiltonian and +# distribute them to the 15 devices in ``devs``. This evaluation is delayed using ``dask.delayed`` and later computed +# in parallel using ``dask.compute``, which asynchronously executes the delayed objects in ``results``. + + +def compute_energy_parallel(H, devs, param): + H_coeffs, H_ops = H.terms() + assert len(H_ops) == len(devs) + results = [] + + for i in range(len(H_ops)): + qnode = qml.QNode(circuit, devs[i]) + results.append(dask.delayed(qnode)(param, H_ops[i])) + + results = dask.compute(*results, scheduler="threads") + result = sum(c * r for c, r in zip(H_coeffs, results)) + return result + + +############################################################################## +# We can now compute all 10 samples from the energy surface sequentially, where each execution is making use of +# parallel device execution. Curiously, in this example the overhead from doing so outweighs the speed-up +# and the execution is slower than standard execution using ``qml.expval``. For different circuits and +# different Hamiltonians, however, parallelization may provide significant speed-ups. + +print("Evaluating the potential energy surface in parallel") +t0 = time.time() + +energies_par = [] +for i, (h, param) in enumerate(zip(hamiltonians, params)): + print( + f"{i+1} / {len(bonds)}: Parallel execution; Running for inter-atomic distance {bonds[i]} Å" + ) + energies_par.append(compute_energy_parallel(h, devs, param)) + +dt_par = time.time() - t0 + +print(f"Evaluation time: {dt_par:.2f} s") + + +############################################################################## +# We can improve this procedure further by optimizing the measurement. Currently, we are measuring each term of the Hamiltonian +# in a separate measurement. This is not necessary as there are sub-groups of commuting terms in the Hamiltonian that can be measured +# simultaneously. We can utilize the grouping function :func:`~.pennylane.pauli.group_observables` to generate few measurements that +# are executed in parallel: + + +def compute_energy_parallel_optimized(H, devs, param): + H_coeffs, H_ops = H.terms() + assert len(H_ops) == len(devs) + results = [] + + obs_groupings, coeffs_groupings = qml.pauli.group_observables( + H_ops, H_coeffs, "qwc" + ) + + for i, (obs, coeffs) in enumerate(zip(obs_groupings, coeffs_groupings)): + H_part = qml.Hamiltonian(coeffs, obs) + qnode = qml.QNode(circuit, devs[i]) + results.append(dask.delayed(qnode)(param, H_part)) + + result = qml.math.sum(dask.compute(*results, scheduler="threads")) + return result + +print( + "Evaluating the potential energy surface in parallel with measurement optimization" +) +t0 = time.time() + +energies_par_opt = [] +for i, (h, param) in enumerate(zip(hamiltonians, params)): + print( + f"{i+1} / {len(bonds)}: Parallel execution and measurement optimization; Running for inter-atomic distance {bonds[i]} Å" + ) + energies_par_opt.append(compute_energy_parallel_optimized(h, devs, param)) + +dt_par_opt = time.time() - t0 + +print(f"Evaluation time: {dt_par_opt:.2f} s") + + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Evaluating the potential energy surface sequentially +# 1 / 10: Sequential execution; Running for inter-atomic distance 0.3 Å +# 2 / 10: Sequential execution; Running for inter-atomic distance 0.5 Å +# 3 / 10: Sequential execution; Running for inter-atomic distance 0.7 Å +# 4 / 10: Sequential execution; Running for inter-atomic distance 0.9 Å +# 5 / 10: Sequential execution; Running for inter-atomic distance 1.1 Å +# 6 / 10: Sequential execution; Running for inter-atomic distance 1.3 Å +# 7 / 10: Sequential execution; Running for inter-atomic distance 1.5 Å +# 8 / 10: Sequential execution; Running for inter-atomic distance 1.7 Å +# 9 / 10: Sequential execution; Running for inter-atomic distance 1.9 Å +# 10 / 10: Sequential execution; Running for inter-atomic distance 2.1 Å +# Evaluation time: 39.33 s +# +# Evaluating the potential energy surface in parallel +# 1 / 10: Parallel execution; Running for inter-atomic distance 0.3 Å +# 2 / 10: Parallel execution; Running for inter-atomic distance 0.5 Å +# 3 / 10: Parallel execution; Running for inter-atomic distance 0.7 Å +# 4 / 10: Parallel execution; Running for inter-atomic distance 0.9 Å +# 5 / 10: Parallel execution; Running for inter-atomic distance 1.1 Å +# 6 / 10: Parallel execution; Running for inter-atomic distance 1.3 Å +# 7 / 10: Parallel execution; Running for inter-atomic distance 1.5 Å +# 8 / 10: Parallel execution; Running for inter-atomic distance 1.7 Å +# 9 / 10: Parallel execution; Running for inter-atomic distance 1.9 Å +# 10 / 10: Parallel execution; Running for inter-atomic distance 2.1 Å +# Evaluation time: 73.42 s +# +# Evaluating the potential energy surface in parallel with measurement optimization +# 1 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.3 Å +# 2 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.5 Å +# 3 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.7 Å +# 4 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.9 Å +# 5 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.1 Å +# 6 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.3 Å +# 7 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.5 Å +# 8 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.7 Å +# 9 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.9 Å +# 10 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 2.1 Å +# Evaluation time: 26.51 s + + +############################################################################## +# We have seen how Hamiltonian measurements can be parallelized and optimized at the same time. + +print("Speed up: {0:.2f}".format(dt_seq / dt_par_opt)) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Speed up: 1.48 + +############################################################################## +# To conclude the tutorial, let's plot the calculated +# potential energy surfaces: + +np.savez( + "vqe_parallel", + energies_seq=energies_seq, + energies_par=energies_par, + energies_par_opt=energies_par_opt, +) + +plt.plot( + bonds, energies_seq, linewidth=2.2, marker="d", color="green", label="sequential" +) +plt.plot(bonds, energies_par, linewidth=2.2, marker="o", color="red", label="parallel") +plt.plot( + bonds, + energies_par_opt, + linewidth=2.2, + marker="d", + color="blue", + label="paralell and optimized", +) +plt.legend(fontsize=12) +plt.title("Potential energy surface for molecular hydrogen", fontsize=12) +plt.xlabel("Atomic separation (Å)", fontsize=16) +plt.ylabel("Ground state energy (Ha)", fontsize=16) +plt.grid(True) + +############################################################################## +# .. figure:: /_static/demonstration_assets/vqe_parallel/vqe_parallel_001.png +# :width: 80% +# :align: center +# + +############################################################################## +# These surfaces overlap, with any variation due to the limited number of shots used to evaluate the +# expectation values in the ``rigetti.qvm`` device (we are using the default value of +# ``shots=1024``). + +############################################################################## +# About the author +# ---------------- +# diff --git a/demonstrations_v2/ensemble_multi_qpu/demo.py b/demonstrations_v2/ensemble_multi_qpu/demo.py index eaf0bc3ea3..8b68a43741 100644 --- a/demonstrations_v2/ensemble_multi_qpu/demo.py +++ b/demonstrations_v2/ensemble_multi_qpu/demo.py @@ -1,581 +1,581 @@ -r""" -Ensemble classification with Rigetti and Qiskit devices -======================================================= - -.. meta:: - :property="og:description": We demonstrate how two QPUs can be - combined in parallel to help solve a machine learning classification problem, - using PyTorch and PennyLane. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/ensemble_diagram.png - -.. related - - tutorial_variational_classifier Variational classifier - -*Author: Tom Bromley — Posted: 14 February 2020. Last updated: 13 December 2021.* - -This tutorial outlines how two QPUs can be combined in parallel to help solve a machine learning -classification problem. - -.. warning:: - This demo does not work with the latest version of Qiskit or the Pennylane-Qiskit plugin and - is compatible with Python versions 3.10 and lower. It is compatible with ``qiskit==0.46`` and - ``pennylane-qiskit==0.35.1``. Older versions of Qiskit and the Pennylane-Qiskit plugin should - not be installed in environments with an existing installation of Qiskit 1.0 or above. - -We use the ``rigetti.qvm`` device to simulate one QPU and the ``qiskit.aer`` device to -simulate another. Each QPU makes an independent prediction, and an ensemble model is -formed by choosing the prediction of the most confident QPU. The iris dataset is used in this -tutorial, consisting of three classes of iris flower. Using a pre-trained model and the PyTorch -interface, we'll see that ensembling allows the QPUs to specialize towards -different classes. - -Let's begin by importing the prerequisite libraries: -""" - -from collections import Counter - -import dask -import matplotlib.pyplot as plt -import numpy as np -import pennylane as qml -import sklearn.datasets -import sklearn.decomposition -import torch -from matplotlib.lines import Line2D -from matplotlib.patches import Patch - -############################################################################## -# This tutorial requires the ``pennylane-rigetti`` and ``pennylane-qiskit`` packages, which can be -# installed by following the instructions `here `__. We also -# make use of the `PyTorch interface `_, which can be installed from `here -# `__. -# -# .. warning:: -# Rigetti's QVM and Quil Compiler services must be running for this tutorial to execute. They -# can be installed by consulting the `Rigetti documentation -# `__ or, for users with Docker, by running: -# -# .. code-block:: bash -# -# docker run -d -p 5555:5555 rigetti/quilc -R -p 5555 -# docker run -d -p 5000:5000 rigetti/qvm -S -p 5000 -# -# Load data -# --------- -# -# The next step is to load the iris dataset. - -n_features = 2 -n_classes = 3 -n_samples = 150 - -data = sklearn.datasets.load_iris() -x = data["data"] -y = data["target"] - -############################################################################## -# We shuffle the data and then embed the four features into a two-dimensional space for ease of -# plotting later on. The first two principal components of the data are used. - -np.random.seed(1967) - -data_order = np.random.permutation(np.arange(n_samples)) -x, y = x[data_order], y[data_order] - -pca = sklearn.decomposition.PCA(n_components=n_features) -pca.fit(x) -x = pca.transform(x) - -############################################################################## -# We will be encoding these two features into quantum circuits using :class:`~.pennylane.RX` -# rotations, and hence renormalize our features to be between :math:`[-\pi, \pi].` - - -x_min = np.min(x, axis=0) -x_max = np.max(x, axis=0) - -x = 2 * np.pi * (x - x_min) / (x_max - x_min) - np.pi - -############################################################################## -# The data is split between a training and a test set. This tutorial uses a model that is -# pre-trained on the training set. - - -split = 125 - -x_train = x[:split] -x_test = x[split:] -y_train = y[:split] -y_test = y[split:] - -############################################################################## -# Finally, let's take a quick look at our data: - - -colours = ["#ec6f86", "#4573e7", "#ad61ed"] - - -def plot_points(x_train, y_train, x_test, y_test): - c_train = [] - c_test = [] - - for y in y_train: - c_train.append(colours[y]) - - for y in y_test: - c_test.append(colours[y]) - - plt.scatter(x_train[:, 0], x_train[:, 1], c=c_train) - plt.scatter(x_test[:, 0], x_test[:, 1], c=c_test, marker="x") - - plt.xlabel("Feature 1", fontsize=16) - plt.ylabel("Feature 2", fontsize=16) - - ax = plt.gca() - ax.set_aspect(1) - - c_transparent = "#00000000" - - custom_lines = [ - Patch(facecolor=colours[0], edgecolor=c_transparent, label="Class 0"), - Patch(facecolor=colours[1], edgecolor=c_transparent, label="Class 1"), - Patch(facecolor=colours[2], edgecolor=c_transparent, label="Class 2"), - Line2D([0], [0], marker="o", color=c_transparent, label="Train", - markerfacecolor="black", markersize=10), - Line2D([0], [0], marker="x", color=c_transparent, label="Test", - markerfacecolor="black", markersize=10), - ] - - ax.legend(handles=custom_lines, bbox_to_anchor=(1.0, 0.75)) - - -plot_points(x_train, y_train, x_test, y_test) -plt.show() - -############################################################################## -# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_001.png -# :width: 80% -# :align: center -# - -############################################################################## -# This plot shows us that class 0 points can be nicely separated, but that there is an overlap -# between points from classes 1 and 2. -# -# Define model -# ------------ -# -# Our model is summarized in the figure below. We use two 4-qubit devices: ``4q-qvm`` -# from the pennyLane-rigetti plugin and ``qiskit.aer`` from the PennyLane-Qiskit plugin. -# -# Data is input using :class:`~.pennylane.RX` rotations and then a different circuit is enacted -# for each device with a unique set of trainable parameters. The output of both circuits is a -# :class:`~.pennylane.PauliZ` measurement on three of the qubits. This is then fed through a -# softmax function, resulting in two 3-dimensional probability vectors corresponding to the 3 -# classes. -# -# Finally, the ensemble model chooses the QPU which is most confident about its prediction -# (i.e., the class with the highest overall probability over all QPUs) and uses that to make a -# prediction. -# -# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_diagram.png -# :width: 80% -# :align: center -# -# Quantum nodes -# ^^^^^^^^^^^^^ -# -# We begin by defining the two quantum devices and the circuits to be run on them. - -n_wires = 4 - -dev0 = qml.device("rigetti.qvm", device="4q-qvm") -dev1 = qml.device("qiskit.aer", wires=4) -devs = [dev0, dev1] - -############################################################################## -# .. note:: -# If you have access to Rigetti hardware, you can swap out ``rigetti.qvm`` for ``rigetti.qpu`` -# and specify the hardware device to run on. Users with access to the IBM hardware can -# swap ``qiskit.aer`` for ``qiskit.remote`` and specify their chosen backend (see `here -# `__). -# -# -# The circuits for both QPUs are shown in the figure below: -# -# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/diagram_circuits.png -# :width: 80% -# :align: center - - -def circuit0(params, x=None): - for i in range(n_wires): - qml.RX(x[i % n_features], wires=i) - qml.Rot(*params[1, 0, i], wires=i) - - qml.CZ(wires=[1, 0]) - qml.CZ(wires=[1, 2]) - qml.CZ(wires=[3, 0]) - - for i in range(n_wires): - qml.Rot(*params[1, 1, i], wires=i) - return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)), qml.expval(qml.PauliZ(2)) - - -def circuit1(params, x=None): - for i in range(n_wires): - qml.RX(x[i % n_features], wires=i) - qml.Rot(*params[0, 0, i], wires=i) - - qml.CZ(wires=[0, 1]) - qml.CZ(wires=[1, 2]) - qml.CZ(wires=[1, 3]) - - for i in range(n_wires): - qml.Rot(*params[0, 1, i], wires=i) - return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)), qml.expval(qml.PauliZ(2)) - - -############################################################################## -# We finally combine the two devices into a :class:`~.pennylane.QNode` list: - - -qnodes = [ - qml.QNode(circuit0, dev0), - qml.QNode(circuit1, dev1), -] - -############################################################################## -# Postprocessing into a prediction -# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# -# The ``predict_point`` function below allows us to find the ensemble prediction, as well as keeping -# track of the individual predictions from each QPU. -# -# We include a ``parallel`` keyword argument for evaluating the :class:`~.pennylane.QNode` list -# in a parallel asynchronous manner. This feature requires the ``dask`` library, which can be -# installed using ``pip install "dask[delayed]"``. When ``parallel=True``, we are able to make -# predictions faster because we do not need to wait for one QPU to output before running on the -# other. - -def decision(softmax): - return int(torch.argmax(softmax)) - - -def predict_point(params, x_point=None, parallel=True): - if parallel: - results = tuple(dask.delayed(q)(params, x=torch.from_numpy(x_point)) for q in qnodes) - results = torch.tensor(dask.compute(*results, scheduler="threads")) - else: - results = tuple(q(params, x=x_point) for q in qnodes) - results = torch.tensor(results) - softmax = torch.nn.functional.softmax(results, dim=1) - choice = torch.where(softmax == torch.max(softmax))[0][0] - chosen_softmax = softmax[choice] - return decision(chosen_softmax), decision(softmax[0]), decision(softmax[1]), int(choice) - - -############################################################################## -# Next, let's define a function to make a predictions over multiple data points. - - -def predict(params, x=None, parallel=True): - predictions_ensemble = [] - predictions_0 = [] - predictions_1 = [] - choices = [] - - for i, x_point in enumerate(x): - if i % 10 == 0 and i > 0: - print("Completed up to iteration {}".format(i)) - results = predict_point(params, x_point=x_point, parallel=parallel) - predictions_ensemble.append(results[0]) - predictions_0.append(results[1]) - predictions_1.append(results[2]) - choices.append(results[3]) - - return predictions_ensemble, predictions_0, predictions_1, choices - - -############################################################################## -# Make predictions -# ---------------- -# -# To test our model, we first load a pre-trained set of parameters which can also be downloaded -# by clicking :download:`here <../_static/demonstration_assets/ensemble_multi_qpu/params.npy>`. - - -params = np.load("../_static/demonstration_assets/ensemble_multi_qpu/params.npy") - -############################################################################## -# We can then make predictions for the training and test datasets. - - -print("Predicting on training dataset") -p_train, p_train_0, p_train_1, choices_train = predict(params, x=x_train) -print("Predicting on test dataset") -p_test, p_test_0, p_test_1, choices_test = predict(params, x=x_test) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# Predicting on training dataset -# Completed up to iteration 10 -# Completed up to iteration 20 -# Completed up to iteration 30 -# Completed up to iteration 40 -# Completed up to iteration 50 -# Completed up to iteration 60 -# Completed up to iteration 70 -# Completed up to iteration 80 -# Completed up to iteration 90 -# Completed up to iteration 100 -# Completed up to iteration 110 -# Completed up to iteration 120 -# Predicting on test dataset -# Completed up to iteration 10 -# Completed up to iteration 20 - -############################################################################## -# Analyze performance -# ------------------- -# -# The last thing to do is test how well the model performs. We begin by looking at the accuracy. -# -# Accuracy -# ^^^^^^^^ - - -def accuracy(predictions, actuals): - count = 0 - - for i in range(len(predictions)): - if predictions[i] == actuals[i]: - count += 1 - - accuracy = count / (len(predictions)) - return accuracy - -############################################################################## - -print("Training accuracy (ensemble): {}".format(accuracy(p_train, y_train))) -print("Training accuracy (QPU0): {}".format(accuracy(p_train_0, y_train))) -print("Training accuracy (QPU1): {}".format(accuracy(p_train_1, y_train))) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# Training accuracy (ensemble): 0.824 -# Training accuracy (QPU0): 0.648 -# Training accuracy (QPU1): 0.296 - -############################################################################## - -print("Test accuracy (ensemble): {}".format(accuracy(p_test, y_test))) -print("Test accuracy (QPU0): {}".format(accuracy(p_test_0, y_test))) -print("Test accuracy (QPU1): {}".format(accuracy(p_test_1, y_test))) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# Test accuracy (ensemble): 0.72 -# Test accuracy (QPU0): 0.56 -# Test accuracy (QPU1): 0.24 - -############################################################################## -# These numbers tell us a few things: -# -# - On both training and test datasets, the ensemble model outperforms the predictions from each -# QPU. This provides a nice example of how QPUs can be used in parallel to gain a performance -# advantage. -# -# - The accuracy of QPU0 is much higher than the accuracy of QPU1. This does not mean that one -# device is intrinsically better than the other. In fact, another set of parameters can lead to -# QPU1 becoming more accurate. We will see in the next section that the difference in accuracy -# is due to specialization of each QPU, which leads to overall better performance of the -# ensemble model. -# -# - The test accuracy is lower than the training accuracy. Here our focus is on analyzing the -# performance of the ensemble model, rather than minimizing the generalization error. -# -# Choice of QPU -# ^^^^^^^^^^^^^ -# -# Is there a link between the class of a datapoint and the QPU chosen to make the prediction in -# the ensemble model? Let's investigate. - - -# Combine choices_train and choices_test to simplify analysis -choices = np.append(choices_train, choices_test) -print("Choices: {}".format(choices)) -print("Choices counts: {}".format(Counter(choices))) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# Choices: [0 0 1 1 0 0 1 0 0 0 1 0 0 0 0 0 0 1 1 0 0 1 0 1 1 0 0 0 1 0 0 1 0 1 0 0 0 -# 0 1 1 0 0 0 0 0 0 0 1 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 1 1 0 0 0 0 1 0 0 0 -# 0 0 0 0 0 1 1 1 1 0 0 0 1 0 1 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 -# 1 0 0 0 1 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 1 1 0 1 0 0 -# 0 0] -# Choices counts: Counter({0: 110, 1: 40}) - -############################################################################## -# The following lines keep track of choices and corresponding predictions in the ensemble model. - - -predictions = np.append(p_train, p_test) -choice_vs_prediction = np.array([(choices[i], predictions[i]) for i in range(n_samples)]) - -############################################################################## -# We can hence find the predictions each QPU was responsible for. - - -choices_vs_prediction_0 = choice_vs_prediction[choice_vs_prediction[:, 0] == 0] -choices_vs_prediction_1 = choice_vs_prediction[choice_vs_prediction[:, 0] == 1] -predictions_0 = choices_vs_prediction_0[:, 1] -predictions_1 = choices_vs_prediction_1[:, 1] - - -expl = "When QPU{} was chosen by the ensemble, it made the following distribution of " \ - "predictions:\n{}" -print(expl.format("0", Counter(predictions_0))) -print("\n" + expl.format("1", Counter(predictions_1))) -print("\nDistribution of classes in iris dataset: {}".format(Counter(y))) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# When QPU0 was chosen by the ensemble, it made the following distribution of predictions: -# Counter({0: 55, 2: 55}) -# -# When QPU1 was chosen by the ensemble, it made the following distribution of predictions: -# Counter({1: 37, 0: 3}) -# -# Distribution of classes in iris dataset: Counter({0: 50, 2: 50, 1: 50}) - -############################################################################## -# These results show us that QPU0 specializes to making predictions on classes 0 and 2, -# while QPU1 specializes to class 1. -# -# Visualization -# ^^^^^^^^^^^^^ -# -# We conclude by visualizing the correct and incorrect predictions on the dataset. The following -# function plots correctly predicted points in green and incorrectly predicted points in red. - - -colours_prediction = {"correct": "#83b5b9", "incorrect": "#f98d91"} -markers = ["o", "v", "d"] - - -def plot_points_prediction(x, y, p, title): - c = {0: [], 1: [], 2: []} - x_ = {0: [], 1: [], 2: []} - - for i in range(n_samples): - x_[y[i]].append(x[i]) - if p[i] == y[i]: - c[y[i]].append(colours_prediction["correct"]) - else: - c[y[i]].append(colours_prediction["incorrect"]) - - for i in range(n_classes): - x_class = np.array(x_[i]) - plt.scatter(x_class[:, 0], x_class[:, 1], c=c[i], marker=markers[i]) - - plt.xlabel("Feature 1", fontsize=16) - plt.ylabel("Feature 2", fontsize=16) - plt.title("Predictions from {} model".format(title)) - - ax = plt.gca() - ax.set_aspect(1) - - c_transparent = "#00000000" - - custom_lines = [ - Patch( - facecolor=colours_prediction["correct"], - edgecolor=c_transparent, label="Correct" - ), - Patch( - facecolor=colours_prediction["incorrect"], - edgecolor=c_transparent, label="Incorrect" - ), - Line2D([0], [0], marker=markers[0], color=c_transparent, label="Class 0", - markerfacecolor="black", markersize=10), - Line2D([0], [0], marker=markers[1], color=c_transparent, label="Class 1", - markerfacecolor="black", markersize=10), - Line2D([0], [0], marker=markers[2], color=c_transparent, label="Class 2", - markerfacecolor="black", markersize=10), - ] - - ax.legend(handles=custom_lines, bbox_to_anchor=(1.0, 0.75)) - - -############################################################################## -# We can again compare the ensemble model with the individual models from each QPU. - - -plot_points_prediction(x, y, predictions, "ensemble") # ensemble -plt.show() - -############################################################################## -# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_002.png -# :width: 80% -# :align: center -# - -############################################################################## - -plot_points_prediction(x, y, np.append(p_train_0, p_test_0), "QPU0") # QPU 0 -plt.show() - -############################################################################## -# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_003.png -# :width: 80% -# :align: center -# - -############################################################################## - -plot_points_prediction(x, y, np.append(p_train_1, p_test_1), "QPU1") # QPU 1 -plt.show() - -############################################################################## -# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_004.png -# :width: 80% -# :align: center -# - -############################################################################## -# These plots reinforce the specialization of the two QPUs. QPU1 concentrates on doing a good job -# at predicting class 1, while QPU0 is focused on classes 0 and 2. By combining together, -# the resultant ensemble performs better. -# -# This tutorial shows how QPUs can work in parallel to realize a performance advantage. Check out -# our :doc:`vqe_parallel` tutorial to see how multiple QPUs can be -# evaluated asynchronously to speed up calculating the potential energy surface of molecular -# hydrogen! - -############################################################################## -# About the author -# ---------------- -# +r""" +Ensemble classification with Rigetti and Qiskit devices +======================================================= + +.. meta:: + :property="og:description": We demonstrate how two QPUs can be + combined in parallel to help solve a machine learning classification problem, + using PyTorch and PennyLane. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/ensemble_diagram.png + +.. related + + tutorial_variational_classifier Variational classifier + +*Author: Tom Bromley — Posted: 14 February 2020. Last updated: 13 December 2021.* + +This tutorial outlines how two QPUs can be combined in parallel to help solve a machine learning +classification problem. + +.. warning:: + This demo does not work with the latest version of Qiskit or the Pennylane-Qiskit plugin and + is compatible with Python versions 3.10 and lower. It is compatible with ``qiskit==0.46`` and + ``pennylane-qiskit==0.35.1``. Older versions of Qiskit and the Pennylane-Qiskit plugin should + not be installed in environments with an existing installation of Qiskit 1.0 or above. + +We use the ``rigetti.qvm`` device to simulate one QPU and the ``qiskit.aer`` device to +simulate another. Each QPU makes an independent prediction, and an ensemble model is +formed by choosing the prediction of the most confident QPU. The iris dataset is used in this +tutorial, consisting of three classes of iris flower. Using a pre-trained model and the PyTorch +interface, we'll see that ensembling allows the QPUs to specialize towards +different classes. + +Let's begin by importing the prerequisite libraries: +""" + +from collections import Counter + +import dask +import matplotlib.pyplot as plt +import numpy as np +import pennylane as qml +import sklearn.datasets +import sklearn.decomposition +import torch +from matplotlib.lines import Line2D +from matplotlib.patches import Patch + +############################################################################## +# This tutorial requires the ``pennylane-rigetti`` and ``pennylane-qiskit`` packages, which can be +# installed by following the instructions `here `__. We also +# make use of the `PyTorch interface `_, which can be installed from `here +# `__. +# +# .. warning:: +# Rigetti's QVM and Quil Compiler services must be running for this tutorial to execute. They +# can be installed by consulting the `Rigetti documentation +# `__ or, for users with Docker, by running: +# +# .. code-block:: bash +# +# docker run -d -p 5555:5555 rigetti/quilc -R -p 5555 +# docker run -d -p 5000:5000 rigetti/qvm -S -p 5000 +# +# Load data +# --------- +# +# The next step is to load the iris dataset. + +n_features = 2 +n_classes = 3 +n_samples = 150 + +data = sklearn.datasets.load_iris() +x = data["data"] +y = data["target"] + +############################################################################## +# We shuffle the data and then embed the four features into a two-dimensional space for ease of +# plotting later on. The first two principal components of the data are used. + +np.random.seed(1967) + +data_order = np.random.permutation(np.arange(n_samples)) +x, y = x[data_order], y[data_order] + +pca = sklearn.decomposition.PCA(n_components=n_features) +pca.fit(x) +x = pca.transform(x) + +############################################################################## +# We will be encoding these two features into quantum circuits using :class:`~.pennylane.RX` +# rotations, and hence renormalize our features to be between :math:`[-\pi, \pi].` + + +x_min = np.min(x, axis=0) +x_max = np.max(x, axis=0) + +x = 2 * np.pi * (x - x_min) / (x_max - x_min) - np.pi + +############################################################################## +# The data is split between a training and a test set. This tutorial uses a model that is +# pre-trained on the training set. + + +split = 125 + +x_train = x[:split] +x_test = x[split:] +y_train = y[:split] +y_test = y[split:] + +############################################################################## +# Finally, let's take a quick look at our data: + + +colours = ["#ec6f86", "#4573e7", "#ad61ed"] + + +def plot_points(x_train, y_train, x_test, y_test): + c_train = [] + c_test = [] + + for y in y_train: + c_train.append(colours[y]) + + for y in y_test: + c_test.append(colours[y]) + + plt.scatter(x_train[:, 0], x_train[:, 1], c=c_train) + plt.scatter(x_test[:, 0], x_test[:, 1], c=c_test, marker="x") + + plt.xlabel("Feature 1", fontsize=16) + plt.ylabel("Feature 2", fontsize=16) + + ax = plt.gca() + ax.set_aspect(1) + + c_transparent = "#00000000" + + custom_lines = [ + Patch(facecolor=colours[0], edgecolor=c_transparent, label="Class 0"), + Patch(facecolor=colours[1], edgecolor=c_transparent, label="Class 1"), + Patch(facecolor=colours[2], edgecolor=c_transparent, label="Class 2"), + Line2D([0], [0], marker="o", color=c_transparent, label="Train", + markerfacecolor="black", markersize=10), + Line2D([0], [0], marker="x", color=c_transparent, label="Test", + markerfacecolor="black", markersize=10), + ] + + ax.legend(handles=custom_lines, bbox_to_anchor=(1.0, 0.75)) + + +plot_points(x_train, y_train, x_test, y_test) +plt.show() + +############################################################################## +# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_001.png +# :width: 80% +# :align: center +# + +############################################################################## +# This plot shows us that class 0 points can be nicely separated, but that there is an overlap +# between points from classes 1 and 2. +# +# Define model +# ------------ +# +# Our model is summarized in the figure below. We use two 4-qubit devices: ``4q-qvm`` +# from the pennyLane-rigetti plugin and ``qiskit.aer`` from the PennyLane-Qiskit plugin. +# +# Data is input using :class:`~.pennylane.RX` rotations and then a different circuit is enacted +# for each device with a unique set of trainable parameters. The output of both circuits is a +# :class:`~.pennylane.PauliZ` measurement on three of the qubits. This is then fed through a +# softmax function, resulting in two 3-dimensional probability vectors corresponding to the 3 +# classes. +# +# Finally, the ensemble model chooses the QPU which is most confident about its prediction +# (i.e., the class with the highest overall probability over all QPUs) and uses that to make a +# prediction. +# +# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_diagram.png +# :width: 80% +# :align: center +# +# Quantum nodes +# ^^^^^^^^^^^^^ +# +# We begin by defining the two quantum devices and the circuits to be run on them. + +n_wires = 4 + +dev0 = qml.device("rigetti.qvm", device="4q-qvm") +dev1 = qml.device("qiskit.aer", wires=4) +devs = [dev0, dev1] + +############################################################################## +# .. note:: +# If you have access to Rigetti hardware, you can swap out ``rigetti.qvm`` for ``rigetti.qpu`` +# and specify the hardware device to run on. Users with access to the IBM hardware can +# swap ``qiskit.aer`` for ``qiskit.remote`` and specify their chosen backend (see `here +# `__). +# +# +# The circuits for both QPUs are shown in the figure below: +# +# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/diagram_circuits.png +# :width: 80% +# :align: center + + +def circuit0(params, x=None): + for i in range(n_wires): + qml.RX(x[i % n_features], wires=i) + qml.Rot(*params[1, 0, i], wires=i) + + qml.CZ(wires=[1, 0]) + qml.CZ(wires=[1, 2]) + qml.CZ(wires=[3, 0]) + + for i in range(n_wires): + qml.Rot(*params[1, 1, i], wires=i) + return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)), qml.expval(qml.PauliZ(2)) + + +def circuit1(params, x=None): + for i in range(n_wires): + qml.RX(x[i % n_features], wires=i) + qml.Rot(*params[0, 0, i], wires=i) + + qml.CZ(wires=[0, 1]) + qml.CZ(wires=[1, 2]) + qml.CZ(wires=[1, 3]) + + for i in range(n_wires): + qml.Rot(*params[0, 1, i], wires=i) + return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)), qml.expval(qml.PauliZ(2)) + + +############################################################################## +# We finally combine the two devices into a :class:`~.pennylane.QNode` list: + + +qnodes = [ + qml.QNode(circuit0, dev0), + qml.QNode(circuit1, dev1), +] + +############################################################################## +# Postprocessing into a prediction +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# +# The ``predict_point`` function below allows us to find the ensemble prediction, as well as keeping +# track of the individual predictions from each QPU. +# +# We include a ``parallel`` keyword argument for evaluating the :class:`~.pennylane.QNode` list +# in a parallel asynchronous manner. This feature requires the ``dask`` library, which can be +# installed using ``pip install "dask[delayed]"``. When ``parallel=True``, we are able to make +# predictions faster because we do not need to wait for one QPU to output before running on the +# other. + +def decision(softmax): + return int(torch.argmax(softmax)) + + +def predict_point(params, x_point=None, parallel=True): + if parallel: + results = tuple(dask.delayed(q)(params, x=torch.from_numpy(x_point)) for q in qnodes) + results = torch.tensor(dask.compute(*results, scheduler="threads")) + else: + results = tuple(q(params, x=x_point) for q in qnodes) + results = torch.tensor(results) + softmax = torch.nn.functional.softmax(results, dim=1) + choice = torch.where(softmax == torch.max(softmax))[0][0] + chosen_softmax = softmax[choice] + return decision(chosen_softmax), decision(softmax[0]), decision(softmax[1]), int(choice) + + +############################################################################## +# Next, let's define a function to make a predictions over multiple data points. + + +def predict(params, x=None, parallel=True): + predictions_ensemble = [] + predictions_0 = [] + predictions_1 = [] + choices = [] + + for i, x_point in enumerate(x): + if i % 10 == 0 and i > 0: + print("Completed up to iteration {}".format(i)) + results = predict_point(params, x_point=x_point, parallel=parallel) + predictions_ensemble.append(results[0]) + predictions_0.append(results[1]) + predictions_1.append(results[2]) + choices.append(results[3]) + + return predictions_ensemble, predictions_0, predictions_1, choices + + +############################################################################## +# Make predictions +# ---------------- +# +# To test our model, we first load a pre-trained set of parameters which can also be downloaded +# by clicking :download:`here <../_static/demonstration_assets/ensemble_multi_qpu/params.npy>`. + + +params = np.load("../_static/demonstration_assets/ensemble_multi_qpu/params.npy") + +############################################################################## +# We can then make predictions for the training and test datasets. + + +print("Predicting on training dataset") +p_train, p_train_0, p_train_1, choices_train = predict(params, x=x_train) +print("Predicting on test dataset") +p_test, p_test_0, p_test_1, choices_test = predict(params, x=x_test) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Predicting on training dataset +# Completed up to iteration 10 +# Completed up to iteration 20 +# Completed up to iteration 30 +# Completed up to iteration 40 +# Completed up to iteration 50 +# Completed up to iteration 60 +# Completed up to iteration 70 +# Completed up to iteration 80 +# Completed up to iteration 90 +# Completed up to iteration 100 +# Completed up to iteration 110 +# Completed up to iteration 120 +# Predicting on test dataset +# Completed up to iteration 10 +# Completed up to iteration 20 + +############################################################################## +# Analyze performance +# ------------------- +# +# The last thing to do is test how well the model performs. We begin by looking at the accuracy. +# +# Accuracy +# ^^^^^^^^ + + +def accuracy(predictions, actuals): + count = 0 + + for i in range(len(predictions)): + if predictions[i] == actuals[i]: + count += 1 + + accuracy = count / (len(predictions)) + return accuracy + +############################################################################## + +print("Training accuracy (ensemble): {}".format(accuracy(p_train, y_train))) +print("Training accuracy (QPU0): {}".format(accuracy(p_train_0, y_train))) +print("Training accuracy (QPU1): {}".format(accuracy(p_train_1, y_train))) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Training accuracy (ensemble): 0.824 +# Training accuracy (QPU0): 0.648 +# Training accuracy (QPU1): 0.296 + +############################################################################## + +print("Test accuracy (ensemble): {}".format(accuracy(p_test, y_test))) +print("Test accuracy (QPU0): {}".format(accuracy(p_test_0, y_test))) +print("Test accuracy (QPU1): {}".format(accuracy(p_test_1, y_test))) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Test accuracy (ensemble): 0.72 +# Test accuracy (QPU0): 0.56 +# Test accuracy (QPU1): 0.24 + +############################################################################## +# These numbers tell us a few things: +# +# - On both training and test datasets, the ensemble model outperforms the predictions from each +# QPU. This provides a nice example of how QPUs can be used in parallel to gain a performance +# advantage. +# +# - The accuracy of QPU0 is much higher than the accuracy of QPU1. This does not mean that one +# device is intrinsically better than the other. In fact, another set of parameters can lead to +# QPU1 becoming more accurate. We will see in the next section that the difference in accuracy +# is due to specialization of each QPU, which leads to overall better performance of the +# ensemble model. +# +# - The test accuracy is lower than the training accuracy. Here our focus is on analyzing the +# performance of the ensemble model, rather than minimizing the generalization error. +# +# Choice of QPU +# ^^^^^^^^^^^^^ +# +# Is there a link between the class of a datapoint and the QPU chosen to make the prediction in +# the ensemble model? Let's investigate. + + +# Combine choices_train and choices_test to simplify analysis +choices = np.append(choices_train, choices_test) +print("Choices: {}".format(choices)) +print("Choices counts: {}".format(Counter(choices))) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Choices: [0 0 1 1 0 0 1 0 0 0 1 0 0 0 0 0 0 1 1 0 0 1 0 1 1 0 0 0 1 0 0 1 0 1 0 0 0 +# 0 1 1 0 0 0 0 0 0 0 1 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 1 1 0 0 0 0 1 0 0 0 +# 0 0 0 0 0 1 1 1 1 0 0 0 1 0 1 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 +# 1 0 0 0 1 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 1 1 0 1 0 0 +# 0 0] +# Choices counts: Counter({0: 110, 1: 40}) + +############################################################################## +# The following lines keep track of choices and corresponding predictions in the ensemble model. + + +predictions = np.append(p_train, p_test) +choice_vs_prediction = np.array([(choices[i], predictions[i]) for i in range(n_samples)]) + +############################################################################## +# We can hence find the predictions each QPU was responsible for. + + +choices_vs_prediction_0 = choice_vs_prediction[choice_vs_prediction[:, 0] == 0] +choices_vs_prediction_1 = choice_vs_prediction[choice_vs_prediction[:, 0] == 1] +predictions_0 = choices_vs_prediction_0[:, 1] +predictions_1 = choices_vs_prediction_1[:, 1] + + +expl = "When QPU{} was chosen by the ensemble, it made the following distribution of " \ + "predictions:\n{}" +print(expl.format("0", Counter(predictions_0))) +print("\n" + expl.format("1", Counter(predictions_1))) +print("\nDistribution of classes in iris dataset: {}".format(Counter(y))) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# When QPU0 was chosen by the ensemble, it made the following distribution of predictions: +# Counter({0: 55, 2: 55}) +# +# When QPU1 was chosen by the ensemble, it made the following distribution of predictions: +# Counter({1: 37, 0: 3}) +# +# Distribution of classes in iris dataset: Counter({0: 50, 2: 50, 1: 50}) + +############################################################################## +# These results show us that QPU0 specializes to making predictions on classes 0 and 2, +# while QPU1 specializes to class 1. +# +# Visualization +# ^^^^^^^^^^^^^ +# +# We conclude by visualizing the correct and incorrect predictions on the dataset. The following +# function plots correctly predicted points in green and incorrectly predicted points in red. + + +colours_prediction = {"correct": "#83b5b9", "incorrect": "#f98d91"} +markers = ["o", "v", "d"] + + +def plot_points_prediction(x, y, p, title): + c = {0: [], 1: [], 2: []} + x_ = {0: [], 1: [], 2: []} + + for i in range(n_samples): + x_[y[i]].append(x[i]) + if p[i] == y[i]: + c[y[i]].append(colours_prediction["correct"]) + else: + c[y[i]].append(colours_prediction["incorrect"]) + + for i in range(n_classes): + x_class = np.array(x_[i]) + plt.scatter(x_class[:, 0], x_class[:, 1], c=c[i], marker=markers[i]) + + plt.xlabel("Feature 1", fontsize=16) + plt.ylabel("Feature 2", fontsize=16) + plt.title("Predictions from {} model".format(title)) + + ax = plt.gca() + ax.set_aspect(1) + + c_transparent = "#00000000" + + custom_lines = [ + Patch( + facecolor=colours_prediction["correct"], + edgecolor=c_transparent, label="Correct" + ), + Patch( + facecolor=colours_prediction["incorrect"], + edgecolor=c_transparent, label="Incorrect" + ), + Line2D([0], [0], marker=markers[0], color=c_transparent, label="Class 0", + markerfacecolor="black", markersize=10), + Line2D([0], [0], marker=markers[1], color=c_transparent, label="Class 1", + markerfacecolor="black", markersize=10), + Line2D([0], [0], marker=markers[2], color=c_transparent, label="Class 2", + markerfacecolor="black", markersize=10), + ] + + ax.legend(handles=custom_lines, bbox_to_anchor=(1.0, 0.75)) + + +############################################################################## +# We can again compare the ensemble model with the individual models from each QPU. + + +plot_points_prediction(x, y, predictions, "ensemble") # ensemble +plt.show() + +############################################################################## +# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_002.png +# :width: 80% +# :align: center +# + +############################################################################## + +plot_points_prediction(x, y, np.append(p_train_0, p_test_0), "QPU0") # QPU 0 +plt.show() + +############################################################################## +# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_003.png +# :width: 80% +# :align: center +# + +############################################################################## + +plot_points_prediction(x, y, np.append(p_train_1, p_test_1), "QPU1") # QPU 1 +plt.show() + +############################################################################## +# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_004.png +# :width: 80% +# :align: center +# + +############################################################################## +# These plots reinforce the specialization of the two QPUs. QPU1 concentrates on doing a good job +# at predicting class 1, while QPU0 is focused on classes 0 and 2. By combining together, +# the resultant ensemble performs better. +# +# This tutorial shows how QPUs can work in parallel to realize a performance advantage. Check out +# our :doc:`vqe_parallel` tutorial to see how multiple QPUs can be +# evaluated asynchronously to speed up calculating the potential energy surface of molecular +# hydrogen! + +############################################################################## +# About the author +# ---------------- +# diff --git a/demonstrations_v2/gbs/demo.py b/demonstrations_v2/gbs/demo.py index 044c2f6c56..f2e1984e33 100644 --- a/demonstrations_v2/gbs/demo.py +++ b/demonstrations_v2/gbs/demo.py @@ -1,488 +1,488 @@ -r""" -.. role:: html(raw) - :format: html - -Quantum advantage with Gaussian Boson Sampling -============================================== - -.. meta:: - :property="og:description": Using light to perform tasks beyond the reach of classical computers. - - :property="og:image": https://pennylane.ai/qml/_static/demo_thumbnails/opengraph_demo_thumbnails/gbs_expt2.png - -.. related:: - - tutorial_gaussian_transformation Gaussian transformation - qsim_beyond_classical Beyond classical computing with qsim - qonn Optimizing a quantum optical neural network - tutorial_photonics Photonic quantum computers - -*Authors: Josh Izaac and Nathan Killoran — Posted: 04 December 2020. Last updated: 04 December 2020.* - -.. warning:: - This demo is only compatible with PennyLane version ``0.29`` or below. - -On the journey to large-scale fault-tolerant quantum computers, one of the first major -milestones is to demonstrate a quantum device carrying out tasks that are beyond the reach of -any classical algorithm. The launch of Xanadu's Borealis device marked an important milestone -within the quantum computing community, wherein our very own quantum computational advantage -experiment using quantum photonics was demonstrated in our `Nature paper `__. -Among other quantum advantage achievements are the Google Quantum team's experiment as can be seen in their paper -`Quantum supremacy using a programmable superconducting processor `__ [#Arute2019]_, -and the experiment from the team led by Chao-Yang Lu and Jian-Wei as can be seen in their paper -`Quantum computational advantage using photons `__ -[#Zhong2020]_. - -While Google's experiment performed the task of :doc:`random circuit sampling ` -using a superconducting processor, both Chao-Yang Lu and Jian-Wei's team and Xanadu leveraged the -quantum properties of light to tackle a task called -`Gaussian Boson Sampling `__ (GBS). - -This tutorial will walk you through the basic elements of GBS, motivate why it is -classically challenging, and show you how to explore GBS using PennyLane and the photonic -quantum devices accessible via the -`PennyLane-Strawberry Fields plugin `__. If you are -interested in possible applications of GBS, or want to access programmable GBS hardware -via the cloud, check out the -`Strawberry Fields website `__ for more details. - -| - -.. image:: /_static/demonstration_assets/gbs/gbs_expt2.png - :align: center - :width: 80% - :target: javascript:void(0); - -.. figure:: /_static/demonstration_assets/gbs/gbs_expt1.png - :align: center - :width: 80% - :target: javascript:void(0); - - *Illustration of the experimental setup used by Zhong et al. in Quantum computational advantage - using photons* [#Zhong2020]_. - -The origins of GBS ------------------- - -Let's first explain the name. `Boson `__ refers to bosonic -matter, which, along with fermions, makes up one of the two elementary classes of particles. -The most prevalent bosonic system in our everyday lives is light, which is made of particles -called photons. Another famous example, though much harder to find, is the Higgs boson. -The distinguishing characteristic of bosons is that they follow "Bose-Einstein statistics", -which very loosely means that the particles like to bunch together (contrast this to fermionic -matter like electrons, which must follow the Pauli Exclusion Principle and keep apart). - -This property can be observed in simple interference experiments such as the -`Hong-Ou Mandel setup `__. -If two single photons are interfered on a balanced beamsplitter, they will both emerge at -the same output port—there is zero probability that they will emerge at separate outputs. -This is a simple but notable quantum property of light; if electrons were brought -together in a similar experiement, they would always appear at separate output ports. - -Gaussian Boson Sampling [#hamilton2017]_ is, in fact, a member of a larger family of -"Boson Sampling" algorithms, -stemming back to the initial proposal of Aaronson and Arkhipov [#aaronson2013]_ in 2013. -Boson Sampling is quantum interferometry writ large. Aaronson and Arkhipov's original proposal -was to inject many single photons into distinct input ports of a large interferometer, then -measure which output ports they appear at. The natural interference properties of bosons -means that photons will appear at the output ports in very unique and specific ways. Boson -Sampling was not proposed with any kind of practical real-world use-case in mind. Like -the random circuit sampling, it's just a quantum system being its best self. With sufficient -size and quality, it is strongly believed to be hard for a classical computer to simulate this efficiently. - -Finally, the "Gaussian" in GBS refers to the fact that we modify the original Boson Sampling -proposal slightly: instead of injecting single photons—which are hard to jointly create in the -size and quality needed to demonstrate Boson Sampling conclusively—we instead use states of -light that are experimentally less demanding (though still challenging!). -These states of light are called Gaussian states, -because they bear strong connections to the -`Gaussian (or Normal) distribution `__ -from statistics. In practice, we use a particular Gaussian state called a -`squeezed state `__ for the inputs, -since these are arguably the most non-classical of Gaussian states. - - -.. note:: While computationally hard to simulate, Boson Sampling devices, on their own, - are not capable of universal quantum computing. However, in combination with other - components, GBS is a key building block for a - universal device [#Bourassa2020]_. - - -Coding a GBS algorithm ----------------------- - -The researchers in [#Zhong2020]_ experimentally demonstrate a GBS device by preparing 50 -squeezed states and injecting them into a 100-mode interferometer. In this demo, -in order to keep things classically simulable, we will stick to a much simpler setting -consisting of 4 squeezed states injected into a 4-mode interferometer. At a high level, -an interferometer on :math:`N` modes can be represented using an :math:`N\times N` unitary -matrix :math:`U.` When decomposed into a quantum optical circuit, the interferometer will -be made up of beamsplitters and phase shifters. - -.. image:: /_static/demonstration_assets/gbs/gbs_circuit2.png - :align: center - :width: 90% - :target: javascript:void(0); - -.. raw:: html - -
- -Simulating this circuit using PennyLane is easy; we can simply read off the gates from left -to right, and convert it into a QNode. -""" - -import numpy as np - -# set the random seed -np.random.seed(42) - -# import PennyLane -import pennylane as qml - -############################################################################## -# We must define the unitary matrix we would like to embed in the circuit. -# We will use SciPy to generate a Haar-random unitary: - -from scipy.stats import unitary_group - -# define the linear interferometer -U = unitary_group.rvs(4) -print(U) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# [[ 0.23648826-0.48221431j 0.06829648+0.04447898j 0.51150074-0.09529866j -# 0.55205719-0.35974699j] -# [-0.11148167+0.69780321j -0.24943828+0.08410701j 0.46705929-0.43192981j -# 0.16220654-0.01817602j] -# [-0.22351926-0.25918352j 0.24364996-0.05375623j -0.09259829-0.53810588j -# 0.27267708+0.66941977j] -# [ 0.11519953-0.28596729j -0.90164923-0.22099186j -0.09627758-0.13105595j -# -0.0200152 +0.12766128j]] -# -# We can now use this to construct the circuit, choosing a compatible -# device. For the simulation, we can use the Strawberry Fields -# Gaussian backend. This backend is perfectly suited for simulation of GBS, -# as the initial states are Gaussian, and all gates transform Gaussian states to other -# Gaussian states. - -n_wires = 4 -cutoff = 10 - -dev = qml.device("strawberryfields.gaussian", wires=n_wires, cutoff_dim=cutoff) - - -@qml.qnode(dev) -def gbs_circuit(): - # prepare the input squeezed states - for i in range(n_wires): - qml.Squeezing(1.0, 0.0, wires=i) - - # linear interferometer - qml.InterferometerUnitary(U, wires=range(n_wires)) - return qml.probs(wires=range(n_wires)) - - -############################################################################## -# A couple of things to note in this particular example: -# -# 1. To prepare the input single mode squeezed vacuum state :math:`|re^{i\phi}\rangle,` -# where :math:`r = 1` and :math:`\phi=0,` we -# apply a squeezing gate (:class:`~pennylane.Squeezing`) to each of the wires (initially in -# the vacuum state). -# -# 2. Next we apply the linear interferometer to all four wires using -# :class:`~pennylane.Interferometer` and the unitary matrix ``U``. This operator -# decomposes the unitary matrix representing the linear interferometer into single-mode -# rotation gates (:class:`~pennylane.PhaseShift`) and two-mode beamsplitters -# (:class:`~pennylane.Beamsplitter`). After applying the interferometer, we will denote the -# output state by :math:`|\psi'\rangle.` -# -# 3. GBS takes place physically in an infinite-dimensional Hilbert space, -# which is not practical for simulation. We need to set an upper limit on the maximum -# number of photons we can detect. This is the -# ``cutoff`` value we defined above; we will only be considering detection events -# containing 0 to 9 photons per mode. -# -# We can now execute the QNode, and extract the resulting probability distribution: - -probs = gbs_circuit().reshape([cutoff] * n_wires) -print(probs.shape) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# (10, 10, 10, 10) -# - -############################################################################## -# For example, element ``[1,2,0,1]`` represents the probability of -# detecting 1 photon on wire -# ``0`` and wire ``3``, and 2 photons at wire ``1``, i.e., the value -# -# .. math:: \text{prob}(1,2,0,1) = \left|\langle{1,2,0,1} \mid \psi'\rangle \right|^2. -# -# Let's extract and view the probabilities of measuring various Fock states. - -# Fock states to measure at output -measure_states = [(0, 0, 0, 0), (1, 1, 0, 0), (0, 1, 0, 1), (1, 1, 1, 1), (2, 0, 0, 0)] - -# extract the probabilities of calculating several -# different Fock states at the output, and print them out -for i in measure_states: - print(f"|{''.join(str(j) for j in i)}>: {probs[i]}") - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# |0000>: 0.17637844761413496 -# |1100>: 0.03473293649420282 -# |0101>: 0.011870900427255589 -# |1111>: 0.005957399165336106 -# |2000>: 0.02957384308320549 -# - -############################################################################## -# The GBS Distribution -# -------------------- -# -# Hamilton et al. [#hamilton2017]_ showed that the probability of -# measuring a final state containing only 0 or 1 photons per mode is given by -# -# .. math:: -# -# \left|\langle{n_1,n_2,\dots,n_N}\middle|{\psi'}\rangle\right|^2 = -# \frac{\left|\text{Haf}[(U(\bigoplus_i\mathrm{tanh}(r_i))U^T)]_{st}\right|^2}{\prod_{i=1}^N \cosh(r_i)} -# -# i.e., the sampled single-photon probability distribution is proportional to the **hafnian** of a -# submatrix of :math:`U(\bigoplus_i\mathrm{tanh}(r_i))U^T.` -# -# .. note:: -# -# The hafnian of a matrix is defined by -# -# .. math:: \text{Haf}(A) = \sum_{\sigma \in \text{PMP}_{2N}}\prod_{i=1}^N A_{\sigma(2i-1)\sigma(2i)}, -# -# where :math:`\text{PMP}_{2N}` is the set of all perfect matching permutations of :math:`2N` elements. In graph theory, the -# hafnian calculates the number of perfect `matchings -# `_ in a graph with -# adjacency matrix :math:`A.` -# -# Compare this to the permanent, which calculates the number of perfect matchings on a *bipartite* -# graph. Notably, the permanent appears in vanilla Boson Sampling in a similar way -# that the hafnian appears in GBS. -# The hafnian turns out to be a generalization of the permanent, with the relationship -# -# .. math:: -# -# \text{Per(A)} = \text{Haf}\left(\left[\begin{matrix} -# 0&A\\ A^T&0 -# \end{matrix}\right]\right). -# -# As any algorithm that could calculate (or even approximate) the hafnian could also calculate the -# permanent—a `#P-hard problem `__---it follows that -# calculating or approximating the hafnian must also be a classically hard problem. This lies behind -# the classical hardness of GBS. -# -# In this demo, we will use the same squeezing parameter, :math:`z=r,` for -# all input states; this allows us to simplify this equation. To start with, the hafnian expression -# simply becomes :math:`\text{Haf}[(UU^T\mathrm{tanh}(r))]_{st},` removing the need for the direct sum. -# -# Thus, we have -# -# .. math:: -# -# \left|\left\langle{n_1,n_2,\dots,n_N}\middle|{\psi'}\right\rangle\right|^2 = -# \frac{\left|\text{Haf}[(UU^T\tanh(r))]_{st}\right|^2}{n_1!n_2!\cdots n_N!\cosh^N(r)}. -# -# Now that we have the theoretical formulas, as well as the probabilities from our simulated GBS -# QNode, we can compare the two and see whether they agree. -# -# In order to calculate the probability of different GBS events classically, we need a -# method for calculating the hafnian. -# For this, we will use `The Walrus -# `_ library (which is installed as a dependency of the -# PennyLane-SF plugin): - -from thewalrus import hafnian as haf - -############################################################################## -# Now, for the right-hand side numerator, we first calculate the submatrix -# :math:`A = [(UU^T\mathrm{tanh}(r))]_{st}:` - -A = np.dot(U, U.T) * np.tanh(1) - -############################################################################## -# In GBS, we determine the submatrix by taking the -# rows and columns corresponding to the measured Fock state. For example, to calculate the submatrix -# in the case of the output measurement :math:`\left|{1,1,0,0}\right\rangle,` -# we have - -print(A[:, [0, 1]][[0, 1]]) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# [[ 0.19343159-0.54582922j 0.43418269-0.09169615j] -# [ 0.43418269-0.09169615j -0.27554025-0.46222197j]] -# - -############################################################################## -# i.e., we consider only the rows and columns where a photon was detected, which gives us -# the submatrix corresponding to indices :math:`0` and :math:`1.` - -############################################################################## -# Comparing to simulation -# ----------------------- -# -# Now that we have a method for calculating the hafnian, let's compare the output to that provided by -# the PennyLane QNode. -# -# **Measuring** :math:`|0,0,0,0\rangle` **at the output** -# -# This corresponds to the hafnian of an *empty* matrix, which is simply 1: - -print(1 / np.cosh(1) ** 4) -print(probs[0, 0, 0, 0]) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# 0.1763784476141347 -# 0.17637844761413496 -# - -############################################################################## -# **Measuring** :math:`|1,1,0,0\rangle` **at the output** - -A = (np.dot(U, U.T) * np.tanh(1))[:, [0, 1]][[0, 1]] -print(np.abs(haf(A)) ** 2 / np.cosh(1) ** 4) -print(probs[1, 1, 0, 0]) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# 0.03473293649420271 -# 0.03473293649420282 -# - -############################################################################## -# **Measuring** :math:`|0,1,0,1\rangle` **at the output** - -A = (np.dot(U, U.T) * np.tanh(1))[:, [1, 3]][[1, 3]] -print(np.abs(haf(A)) ** 2 / np.cosh(1) ** 4) -print(probs[0, 1, 0, 1]) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# 0.011870900427255558 -# 0.011870900427255589 -# - -############################################################################## -# **Measuring** :math:`|1,1,1,1\rangle` **at the output** -# -# This corresponds to the hafnian of the full matrix :math:`A=UU^T\mathrm{tanh}(r):` - -A = np.dot(U, U.T) * np.tanh(1) -print(np.abs(haf(A)) ** 2 / np.cosh(1) ** 4) -print(probs[1, 1, 1, 1]) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# 0.005957399165336081 -# 0.005957399165336106 -# - -############################################################################## -# **Measuring** :math:`|2,0,0,0\rangle` **at the output** -# -# Since we have two photons in mode ``q[0]``, we take two copies of the -# first row and first column, making sure to divide by :math:`2!:` - -A = (np.dot(U, U.T) * np.tanh(1))[:, [0, 0]][[0, 0]] -print(np.abs(haf(A)) ** 2 / (2 * np.cosh(1) ** 4)) -print(probs[2, 0, 0, 0]) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# 0.029573843083205383 -# 0.02957384308320549 -# -# The PennyLane simulation results agree (with almost negligible numerical error) to the -# expected result from the Gaussian boson sampling equation! -# -# This demo provides an entry-level walkthrough to the ideas behind GBS, -# providing you with the basic code needed for exploring the ideas behind -# the photonic quantum advantage paper. Try changing the number of modes, -# the number of injected squeezed states, or the cutoff dimension, and -# see how each of these affect the classical computation time. If you're -# interested in learning more about GBS, or about photonic quantum -# computing in general, the -# `Strawberry Fields website `__ is a great resource. -# -# References -# ---------- -# -# .. [#Arute2019] -# -# Arute, F., Arya, K., Babbush, R., et al. "Quantum supremacy using a programmable -# superconducting processor" -# `Nature 574, 505-510 (2019) `__. -# -# .. [#Zhong2020] -# -# Zhong, H.-S., Wang, H., Deng, Y.-H., et al. (2020). Quantum computational advantage using photons. Science, 10.1126/science.abe8770. -# -# .. [#hamilton2017] -# -# Craig S. Hamilton, Regina Kruse, Linda Sansoni, Sonja Barkhofen, Christine Silberhorn, -# and Igor Jex. Gaussian boson sampling. Physical Review Letters, 119:170501, Oct 2017. -# arXiv:1612.01199, doi:10.1103/PhysRevLett.119.170501. -# -# .. [#aaronson2013] -# -# Scott Aaronson and Alex Arkhipov. The computational complexity of linear optics. Theory of -# Computing, 9(1):143–252, 2013. doi:10.4086/toc.2013.v009a004. -# -# .. [#Bourassa2020] -# -# Bourassa, J. E., Alexander, R. N., Vasmer, et al. (2020). Blueprint for a scalable -# photonic fault-tolerant quantum computer. arXiv preprint arXiv:2010.02905. -# -# -# About the author -# ---------------- -# +r""" +.. role:: html(raw) + :format: html + +Quantum advantage with Gaussian Boson Sampling +============================================== + +.. meta:: + :property="og:description": Using light to perform tasks beyond the reach of classical computers. + + :property="og:image": https://pennylane.ai/qml/_static/demo_thumbnails/opengraph_demo_thumbnails/gbs_expt2.png + +.. related:: + + tutorial_gaussian_transformation Gaussian transformation + qsim_beyond_classical Beyond classical computing with qsim + qonn Optimizing a quantum optical neural network + tutorial_photonics Photonic quantum computers + +*Authors: Josh Izaac and Nathan Killoran — Posted: 04 December 2020. Last updated: 04 December 2020.* + +.. warning:: + This demo is only compatible with PennyLane version ``0.29`` or below. + +On the journey to large-scale fault-tolerant quantum computers, one of the first major +milestones is to demonstrate a quantum device carrying out tasks that are beyond the reach of +any classical algorithm. The launch of Xanadu's Borealis device marked an important milestone +within the quantum computing community, wherein our very own quantum computational advantage +experiment using quantum photonics was demonstrated in our `Nature paper `__. +Among other quantum advantage achievements are the Google Quantum team's experiment as can be seen in their paper +`Quantum supremacy using a programmable superconducting processor `__ [#Arute2019]_, +and the experiment from the team led by Chao-Yang Lu and Jian-Wei as can be seen in their paper +`Quantum computational advantage using photons `__ +[#Zhong2020]_. + +While Google's experiment performed the task of :doc:`random circuit sampling ` +using a superconducting processor, both Chao-Yang Lu and Jian-Wei's team and Xanadu leveraged the +quantum properties of light to tackle a task called +`Gaussian Boson Sampling `__ (GBS). + +This tutorial will walk you through the basic elements of GBS, motivate why it is +classically challenging, and show you how to explore GBS using PennyLane and the photonic +quantum devices accessible via the +`PennyLane-Strawberry Fields plugin `__. If you are +interested in possible applications of GBS, or want to access programmable GBS hardware +via the cloud, check out the +`Strawberry Fields website `__ for more details. + +| + +.. image:: /_static/demonstration_assets/gbs/gbs_expt2.png + :align: center + :width: 80% + :target: javascript:void(0); + +.. figure:: /_static/demonstration_assets/gbs/gbs_expt1.png + :align: center + :width: 80% + :target: javascript:void(0); + + *Illustration of the experimental setup used by Zhong et al. in Quantum computational advantage + using photons* [#Zhong2020]_. + +The origins of GBS +------------------ + +Let's first explain the name. `Boson `__ refers to bosonic +matter, which, along with fermions, makes up one of the two elementary classes of particles. +The most prevalent bosonic system in our everyday lives is light, which is made of particles +called photons. Another famous example, though much harder to find, is the Higgs boson. +The distinguishing characteristic of bosons is that they follow "Bose-Einstein statistics", +which very loosely means that the particles like to bunch together (contrast this to fermionic +matter like electrons, which must follow the Pauli Exclusion Principle and keep apart). + +This property can be observed in simple interference experiments such as the +`Hong-Ou Mandel setup `__. +If two single photons are interfered on a balanced beamsplitter, they will both emerge at +the same output port—there is zero probability that they will emerge at separate outputs. +This is a simple but notable quantum property of light; if electrons were brought +together in a similar experiement, they would always appear at separate output ports. + +Gaussian Boson Sampling [#hamilton2017]_ is, in fact, a member of a larger family of +"Boson Sampling" algorithms, +stemming back to the initial proposal of Aaronson and Arkhipov [#aaronson2013]_ in 2013. +Boson Sampling is quantum interferometry writ large. Aaronson and Arkhipov's original proposal +was to inject many single photons into distinct input ports of a large interferometer, then +measure which output ports they appear at. The natural interference properties of bosons +means that photons will appear at the output ports in very unique and specific ways. Boson +Sampling was not proposed with any kind of practical real-world use-case in mind. Like +the random circuit sampling, it's just a quantum system being its best self. With sufficient +size and quality, it is strongly believed to be hard for a classical computer to simulate this efficiently. + +Finally, the "Gaussian" in GBS refers to the fact that we modify the original Boson Sampling +proposal slightly: instead of injecting single photons—which are hard to jointly create in the +size and quality needed to demonstrate Boson Sampling conclusively—we instead use states of +light that are experimentally less demanding (though still challenging!). +These states of light are called Gaussian states, +because they bear strong connections to the +`Gaussian (or Normal) distribution `__ +from statistics. In practice, we use a particular Gaussian state called a +`squeezed state `__ for the inputs, +since these are arguably the most non-classical of Gaussian states. + + +.. note:: While computationally hard to simulate, Boson Sampling devices, on their own, + are not capable of universal quantum computing. However, in combination with other + components, GBS is a key building block for a + universal device [#Bourassa2020]_. + + +Coding a GBS algorithm +---------------------- + +The researchers in [#Zhong2020]_ experimentally demonstrate a GBS device by preparing 50 +squeezed states and injecting them into a 100-mode interferometer. In this demo, +in order to keep things classically simulable, we will stick to a much simpler setting +consisting of 4 squeezed states injected into a 4-mode interferometer. At a high level, +an interferometer on :math:`N` modes can be represented using an :math:`N\times N` unitary +matrix :math:`U.` When decomposed into a quantum optical circuit, the interferometer will +be made up of beamsplitters and phase shifters. + +.. image:: /_static/demonstration_assets/gbs/gbs_circuit2.png + :align: center + :width: 90% + :target: javascript:void(0); + +.. raw:: html + +
+ +Simulating this circuit using PennyLane is easy; we can simply read off the gates from left +to right, and convert it into a QNode. +""" + +import numpy as np + +# set the random seed +np.random.seed(42) + +# import PennyLane +import pennylane as qml + +############################################################################## +# We must define the unitary matrix we would like to embed in the circuit. +# We will use SciPy to generate a Haar-random unitary: + +from scipy.stats import unitary_group + +# define the linear interferometer +U = unitary_group.rvs(4) +print(U) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# [[ 0.23648826-0.48221431j 0.06829648+0.04447898j 0.51150074-0.09529866j +# 0.55205719-0.35974699j] +# [-0.11148167+0.69780321j -0.24943828+0.08410701j 0.46705929-0.43192981j +# 0.16220654-0.01817602j] +# [-0.22351926-0.25918352j 0.24364996-0.05375623j -0.09259829-0.53810588j +# 0.27267708+0.66941977j] +# [ 0.11519953-0.28596729j -0.90164923-0.22099186j -0.09627758-0.13105595j +# -0.0200152 +0.12766128j]] +# +# We can now use this to construct the circuit, choosing a compatible +# device. For the simulation, we can use the Strawberry Fields +# Gaussian backend. This backend is perfectly suited for simulation of GBS, +# as the initial states are Gaussian, and all gates transform Gaussian states to other +# Gaussian states. + +n_wires = 4 +cutoff = 10 + +dev = qml.device("strawberryfields.gaussian", wires=n_wires, cutoff_dim=cutoff) + + +@qml.qnode(dev) +def gbs_circuit(): + # prepare the input squeezed states + for i in range(n_wires): + qml.Squeezing(1.0, 0.0, wires=i) + + # linear interferometer + qml.InterferometerUnitary(U, wires=range(n_wires)) + return qml.probs(wires=range(n_wires)) + + +############################################################################## +# A couple of things to note in this particular example: +# +# 1. To prepare the input single mode squeezed vacuum state :math:`|re^{i\phi}\rangle,` +# where :math:`r = 1` and :math:`\phi=0,` we +# apply a squeezing gate (:class:`~pennylane.Squeezing`) to each of the wires (initially in +# the vacuum state). +# +# 2. Next we apply the linear interferometer to all four wires using +# :class:`~pennylane.Interferometer` and the unitary matrix ``U``. This operator +# decomposes the unitary matrix representing the linear interferometer into single-mode +# rotation gates (:class:`~pennylane.PhaseShift`) and two-mode beamsplitters +# (:class:`~pennylane.Beamsplitter`). After applying the interferometer, we will denote the +# output state by :math:`|\psi'\rangle.` +# +# 3. GBS takes place physically in an infinite-dimensional Hilbert space, +# which is not practical for simulation. We need to set an upper limit on the maximum +# number of photons we can detect. This is the +# ``cutoff`` value we defined above; we will only be considering detection events +# containing 0 to 9 photons per mode. +# +# We can now execute the QNode, and extract the resulting probability distribution: + +probs = gbs_circuit().reshape([cutoff] * n_wires) +print(probs.shape) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# (10, 10, 10, 10) +# + +############################################################################## +# For example, element ``[1,2,0,1]`` represents the probability of +# detecting 1 photon on wire +# ``0`` and wire ``3``, and 2 photons at wire ``1``, i.e., the value +# +# .. math:: \text{prob}(1,2,0,1) = \left|\langle{1,2,0,1} \mid \psi'\rangle \right|^2. +# +# Let's extract and view the probabilities of measuring various Fock states. + +# Fock states to measure at output +measure_states = [(0, 0, 0, 0), (1, 1, 0, 0), (0, 1, 0, 1), (1, 1, 1, 1), (2, 0, 0, 0)] + +# extract the probabilities of calculating several +# different Fock states at the output, and print them out +for i in measure_states: + print(f"|{''.join(str(j) for j in i)}>: {probs[i]}") + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# |0000>: 0.17637844761413496 +# |1100>: 0.03473293649420282 +# |0101>: 0.011870900427255589 +# |1111>: 0.005957399165336106 +# |2000>: 0.02957384308320549 +# + +############################################################################## +# The GBS Distribution +# -------------------- +# +# Hamilton et al. [#hamilton2017]_ showed that the probability of +# measuring a final state containing only 0 or 1 photons per mode is given by +# +# .. math:: +# +# \left|\langle{n_1,n_2,\dots,n_N}\middle|{\psi'}\rangle\right|^2 = +# \frac{\left|\text{Haf}[(U(\bigoplus_i\mathrm{tanh}(r_i))U^T)]_{st}\right|^2}{\prod_{i=1}^N \cosh(r_i)} +# +# i.e., the sampled single-photon probability distribution is proportional to the **hafnian** of a +# submatrix of :math:`U(\bigoplus_i\mathrm{tanh}(r_i))U^T.` +# +# .. note:: +# +# The hafnian of a matrix is defined by +# +# .. math:: \text{Haf}(A) = \sum_{\sigma \in \text{PMP}_{2N}}\prod_{i=1}^N A_{\sigma(2i-1)\sigma(2i)}, +# +# where :math:`\text{PMP}_{2N}` is the set of all perfect matching permutations of :math:`2N` elements. In graph theory, the +# hafnian calculates the number of perfect `matchings +# `_ in a graph with +# adjacency matrix :math:`A.` +# +# Compare this to the permanent, which calculates the number of perfect matchings on a *bipartite* +# graph. Notably, the permanent appears in vanilla Boson Sampling in a similar way +# that the hafnian appears in GBS. +# The hafnian turns out to be a generalization of the permanent, with the relationship +# +# .. math:: +# +# \text{Per(A)} = \text{Haf}\left(\left[\begin{matrix} +# 0&A\\ A^T&0 +# \end{matrix}\right]\right). +# +# As any algorithm that could calculate (or even approximate) the hafnian could also calculate the +# permanent—a `#P-hard problem `__---it follows that +# calculating or approximating the hafnian must also be a classically hard problem. This lies behind +# the classical hardness of GBS. +# +# In this demo, we will use the same squeezing parameter, :math:`z=r,` for +# all input states; this allows us to simplify this equation. To start with, the hafnian expression +# simply becomes :math:`\text{Haf}[(UU^T\mathrm{tanh}(r))]_{st},` removing the need for the direct sum. +# +# Thus, we have +# +# .. math:: +# +# \left|\left\langle{n_1,n_2,\dots,n_N}\middle|{\psi'}\right\rangle\right|^2 = +# \frac{\left|\text{Haf}[(UU^T\tanh(r))]_{st}\right|^2}{n_1!n_2!\cdots n_N!\cosh^N(r)}. +# +# Now that we have the theoretical formulas, as well as the probabilities from our simulated GBS +# QNode, we can compare the two and see whether they agree. +# +# In order to calculate the probability of different GBS events classically, we need a +# method for calculating the hafnian. +# For this, we will use `The Walrus +# `_ library (which is installed as a dependency of the +# PennyLane-SF plugin): + +from thewalrus import hafnian as haf + +############################################################################## +# Now, for the right-hand side numerator, we first calculate the submatrix +# :math:`A = [(UU^T\mathrm{tanh}(r))]_{st}:` + +A = np.dot(U, U.T) * np.tanh(1) + +############################################################################## +# In GBS, we determine the submatrix by taking the +# rows and columns corresponding to the measured Fock state. For example, to calculate the submatrix +# in the case of the output measurement :math:`\left|{1,1,0,0}\right\rangle,` +# we have + +print(A[:, [0, 1]][[0, 1]]) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# [[ 0.19343159-0.54582922j 0.43418269-0.09169615j] +# [ 0.43418269-0.09169615j -0.27554025-0.46222197j]] +# + +############################################################################## +# i.e., we consider only the rows and columns where a photon was detected, which gives us +# the submatrix corresponding to indices :math:`0` and :math:`1.` + +############################################################################## +# Comparing to simulation +# ----------------------- +# +# Now that we have a method for calculating the hafnian, let's compare the output to that provided by +# the PennyLane QNode. +# +# **Measuring** :math:`|0,0,0,0\rangle` **at the output** +# +# This corresponds to the hafnian of an *empty* matrix, which is simply 1: + +print(1 / np.cosh(1) ** 4) +print(probs[0, 0, 0, 0]) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# 0.1763784476141347 +# 0.17637844761413496 +# + +############################################################################## +# **Measuring** :math:`|1,1,0,0\rangle` **at the output** + +A = (np.dot(U, U.T) * np.tanh(1))[:, [0, 1]][[0, 1]] +print(np.abs(haf(A)) ** 2 / np.cosh(1) ** 4) +print(probs[1, 1, 0, 0]) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# 0.03473293649420271 +# 0.03473293649420282 +# + +############################################################################## +# **Measuring** :math:`|0,1,0,1\rangle` **at the output** + +A = (np.dot(U, U.T) * np.tanh(1))[:, [1, 3]][[1, 3]] +print(np.abs(haf(A)) ** 2 / np.cosh(1) ** 4) +print(probs[0, 1, 0, 1]) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# 0.011870900427255558 +# 0.011870900427255589 +# + +############################################################################## +# **Measuring** :math:`|1,1,1,1\rangle` **at the output** +# +# This corresponds to the hafnian of the full matrix :math:`A=UU^T\mathrm{tanh}(r):` + +A = np.dot(U, U.T) * np.tanh(1) +print(np.abs(haf(A)) ** 2 / np.cosh(1) ** 4) +print(probs[1, 1, 1, 1]) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# 0.005957399165336081 +# 0.005957399165336106 +# + +############################################################################## +# **Measuring** :math:`|2,0,0,0\rangle` **at the output** +# +# Since we have two photons in mode ``q[0]``, we take two copies of the +# first row and first column, making sure to divide by :math:`2!:` + +A = (np.dot(U, U.T) * np.tanh(1))[:, [0, 0]][[0, 0]] +print(np.abs(haf(A)) ** 2 / (2 * np.cosh(1) ** 4)) +print(probs[2, 0, 0, 0]) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# 0.029573843083205383 +# 0.02957384308320549 +# +# The PennyLane simulation results agree (with almost negligible numerical error) to the +# expected result from the Gaussian boson sampling equation! +# +# This demo provides an entry-level walkthrough to the ideas behind GBS, +# providing you with the basic code needed for exploring the ideas behind +# the photonic quantum advantage paper. Try changing the number of modes, +# the number of injected squeezed states, or the cutoff dimension, and +# see how each of these affect the classical computation time. If you're +# interested in learning more about GBS, or about photonic quantum +# computing in general, the +# `Strawberry Fields website `__ is a great resource. +# +# References +# ---------- +# +# .. [#Arute2019] +# +# Arute, F., Arya, K., Babbush, R., et al. "Quantum supremacy using a programmable +# superconducting processor" +# `Nature 574, 505-510 (2019) `__. +# +# .. [#Zhong2020] +# +# Zhong, H.-S., Wang, H., Deng, Y.-H., et al. (2020). Quantum computational advantage using photons. Science, 10.1126/science.abe8770. +# +# .. [#hamilton2017] +# +# Craig S. Hamilton, Regina Kruse, Linda Sansoni, Sonja Barkhofen, Christine Silberhorn, +# and Igor Jex. Gaussian boson sampling. Physical Review Letters, 119:170501, Oct 2017. +# arXiv:1612.01199, doi:10.1103/PhysRevLett.119.170501. +# +# .. [#aaronson2013] +# +# Scott Aaronson and Alex Arkhipov. The computational complexity of linear optics. Theory of +# Computing, 9(1):143–252, 2013. doi:10.4086/toc.2013.v009a004. +# +# .. [#Bourassa2020] +# +# Bourassa, J. E., Alexander, R. N., Vasmer, et al. (2020). Blueprint for a scalable +# photonic fault-tolerant quantum computer. arXiv preprint arXiv:2010.02905. +# +# +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_adaptive_circuits/demo.py b/demonstrations_v2/tutorial_adaptive_circuits/demo.py index 79d3041fb6..3e29579ccd 100644 --- a/demonstrations_v2/tutorial_adaptive_circuits/demo.py +++ b/demonstrations_v2/tutorial_adaptive_circuits/demo.py @@ -1,426 +1,426 @@ -r""" - -Adaptive circuits for quantum chemistry -======================================= - -.. meta:: - :property="og:description": Learn how to build quantum chemistry circuits adaptively - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_adaptive_circuits.png - -.. related:: - tutorial_quantum_chemistry Building molecular Hamiltonians - tutorial_vqe A brief overview of VQE - tutorial_givens_rotations Givens rotations for quantum chemistry - - -*Author: Soran Jahangiri — Posted: 13 September 2021. Last updated: 10 April 2023* - -The key component of variational quantum algorithms for quantum chemistry is the circuit used to -prepare electronic ground states of a molecule. The variational quantum eigensolver (VQE) -[#peruzzo2014]_, [#yudong2019]_ is the method of choice for performing such quantum chemistry -simulations on quantum devices with few qubits. For a given molecule, the appropriate circuit can -be generated by using a pre-selected wavefunction ansatz, for example, the unitary coupled cluster -with single and double excitations (UCCSD) [#romero2017]_. In this approach, we include all -possible single and double excitations of electrons from the occupied spin-orbitals of a reference -state to the unoccupied spin-orbitals [#givenstutorial]_. This makes the construction of the ansatz -straightforward for any given molecule. However, using a pre-constructed ansatz has the disadvantage -of reducing performance in favour of generality: the approach may work well in many cases, but it -will not be optimized for a specific problem. - -In practical applications, including all possible excitations usually increases the cost of the -simulations without improving the accuracy of the results. This motivates implementing a strategy -that allows for approximation of the contribution of the excitations and selects only those -excitations that are found to be important for the given molecule. This can be done by using -adaptive methods to construct a circuit for each given problem [#grimsley2019]_. Using adaptive -circuits helps improve performance at the cost of reducing generality. - -.. figure:: /_static/demonstration_assets/adaptive_circuits/main.png - :width: 75% - :align: center - - Examples of selecting specific gates to generate adaptive circuits. - -In this tutorial, you will learn how to **adaptively** build customized quantum chemistry circuits -to perform ADAPT-VQE [#grimsley2019]_ simulations. This includes a recipe to adaptively select gates -that have a significant contribution to the desired state, while neglecting those that have a small -contribution. You will also learn how to use PennyLane to leverage the sparsity of a molecular -Hamiltonian to make the computation of the expectation values even more efficient. Let's get -started! - -Adaptive circuits ------------------ - -The main idea behind building adaptive circuits is to compute the gradients with respect to all -possible excitation gates and then select gates based on the magnitude of the computed gradients. - -There are different ways to make use of the gradient information and here we discuss one of -these strategies and apply it to compute the ground state energy of LiH. This method requires constructing the -Hamiltonian and determining all possible excitations, which we can do with functionality built into PennyLane. -But we first need to define the molecular parameters, including atomic symbols and coordinates. -Note that the atomic coordinates are in `Bohr `_. -""" - -import pennylane as qml -import jax -import numpy as np -import time - -from pennylane import qchem -from jax import numpy as jnp - -jax.config.update("jax_enable_x64", True) - -symbols = ["Li", "H"] -geometry = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 2.969280527]]) -molecule = qchem.Molecule(symbols, geometry) - -############################################################################## -# We now compute the molecular Hamiltonian in the -# `STO-3G `_ basis and obtain the electronic -# excitations. We restrict ourselves to single and double excitations, but higher-level ones such -# as triple and quadruple excitations can be considered as well. Each of these electronic excitations -# is represented by a gate that excites electrons from the occupied orbitals of a reference state to -# the unoccupied ones. This allows us to prepare a state that is a superposition of the reference -# state and all of the excited states. - -H, qubits = qchem.molecular_hamiltonian( - molecule, - active_electrons=2, - active_orbitals=5 -) - -active_electrons = 2 - -singles, doubles = qchem.excitations(active_electrons, qubits) - -print(f"Total number of excitations = {len(singles) + len(doubles)}") - -############################################################################## -# Note that we have a total of 24 excitations which can be represented by the same number of -# excitation gates [#givenstutorial]_. Let's now use an :class:`~.pennylane.AdaptiveOptimizer` -# implemented in PennyLane to construct an adaptive circuit. -# -# Adaptive Optimizer -# ~~~~~~~~~~~~~~~~~~ -# The adaptive optimizer -# grows an input quantum circuit by adding and optimizing gates selected from a user-defined -# collection of operators. The algorithm first appends all of the gates provided in the initial -# operator pool and computes the circuit gradients with respect to the gate parameters. It retains -# the gate which has the largest gradient and then optimizes its parameter. -# The process of growing the circuit can be repeated until the computed gradients converge to zero. -# Let's use :class:`~.pennylane.AdaptiveOptimizer` to perform an ADAPT-VQE [#grimsley2019]_ -# simulation and build an adaptive circuit for LiH. -# -# We first create the operator pool which contains all single and double excitations. - -singles_excitations = [qml.SingleExcitation(0.0, x) for x in singles] -doubles_excitations = [qml.DoubleExcitation(0.0, x) for x in doubles] -operator_pool = doubles_excitations + singles_excitations - -############################################################################## -# We now define an initial circuit that prepares a Hartree-Fock state and returns the expectation -# value of the Hamiltonian. We also need to define a device. - -hf_state = qchem.hf_state(active_electrons, qubits) -dev = qml.device("default.qubit", wires=qubits) -@qml.qnode(dev) -def circuit(): - [qml.PauliX(i) for i in np.nonzero(hf_state)[0]] - return qml.expval(H) - -############################################################################## -# We instantiate the optimizer and use it to build the circuit adaptively. - -opt = qml.optimize.AdaptiveOptimizer() -for i in range(len(operator_pool)): - circuit, energy, gradient = opt.step_and_cost(circuit, operator_pool) - if i % 3 == 0: - print("n = {:}, E = {:.8f} H, Largest Gradient = {:.3f}".format(i, energy, gradient)) - print(qml.draw(circuit, decimals=None)()) - print() - if gradient < 3e-3: - break - -############################################################################## -# The resulting energy matches the exact energy of the ground electronic state of LiH, which is -# -7.8825378193 Ha, within chemical accuracy. Note that some of the gates appear more than once in -# the circuit. By default, :class:`~.pennylane.AdaptiveOptimizer` does not eliminate the selected -# gates from the pool. We can set ``drain_pool=True`` to prevent repetition of the gates by -# removing the selected gate from the operator pool. - -@qml.qnode(dev) -def circuit(): - [qml.PauliX(i) for i in np.nonzero(hf_state)[0]] - return qml.expval(H) - -opt = qml.optimize.AdaptiveOptimizer() -for i in range(len(operator_pool)): - circuit, energy, gradient = opt.step_and_cost(circuit, operator_pool, drain_pool=True) - if i % 2 == 0: - print("n = {:}, E = {:.8f} H, Largest Gradient = {:.3f}".format(i, energy, gradient)) - print(qml.draw(circuit, decimals=None)()) - print() - if gradient < 3e-3: - break - -############################################################################## -# Manual construction -# ~~~~~~~~~~~~~~~~~~~ -# We can also build adaptive circuits manually by adding groups of gates one at a time. We follow -# these steps: -# -# 1. Compute gradients for all double excitations. -# 2. Select the double excitations with gradients larger than a pre-defined threshold. -# 3. Perform VQE to obtain the optimized parameters for the selected double excitations. -# 4. Repeat steps 1 and 2 for the single excitations. -# 5. Perform the final VQE optimization with all the selected excitations. -# -# We create a circuit that applies a selected group of gates to the reference Hartree-Fock state. - - -# Re-define H using Jax Arrays -molecule = qchem.Molecule(symbols, jnp.array(geometry)) -H, qubits = qchem.molecular_hamiltonian( - molecule, - active_electrons=2, - active_orbitals=5 -) - -def circuit_1(params, excitations): - qml.BasisState(jnp.array(hf_state), wires=range(qubits)) - - for i, excitation in enumerate(excitations): - if len(excitation) == 4: - qml.DoubleExcitation(params[i], wires=excitation) - else: - qml.SingleExcitation(params[i], wires=excitation) - return qml.expval(H) - -############################################################################## -# We now construct our first group of gates by including all the double excitations and compute the -# gradient for each one. We also need to define a cost -# function. We initialize the parameter values to zero such that the gradients are computed -# with respect to the Hartree-Fock state. - - -dev = qml.device("lightning.qubit", wires=qubits) -cost_fn = qml.QNode(circuit_1, dev, interface="jax") - -circuit_gradient = jax.grad(cost_fn, argnums=0) - -params = [0.0] * len(doubles) -grads = circuit_gradient(params, excitations=doubles) - -for i in range(len(doubles)): - print(f"Excitation : {doubles[i]}, Gradient: {grads[i]}") - -############################################################################## -# The computed gradients have different values, reflecting the contribution of each gate -# in the final state prepared by the circuit. Many of the gradient values are zero and we select -# those gates that have a gradient above a pre-defined threshold, which we set to :math:`10^{-5}.` - -doubles_select = [doubles[i] for i in range(len(doubles)) if abs(grads[i]) > 1.0e-5] -doubles_select - -############################################################################## -# There are only 6 double excitation gates, out of the original 16, that have gradients above the -# threshold. We add the selected gates to the circuit and optimize it to determine -# the updated parameters for the selected gates. We also need to define an optimizer. Note that the -# optimization is not very costly as we only have six gates in our circuit. - -import optax - -params_doubles = jnp.zeros(len(doubles_select)) - -opt = optax.sgd(learning_rate=0.5) # sgd stands for StochasticGradientDescent -opt_state = opt.init(params_doubles) - -for n in range(10): - gradient = jax.grad(cost_fn, argnums=0)(params_doubles, excitations=doubles_select) - updates, opt_state = opt.update(gradient, opt_state) - params_doubles = optax.apply_updates(params_doubles, updates) - -############################################################################## -# Now, we keep the selected gates in the circuit and compute the gradients with respect to all of -# the single excitation gates, selecting those that have a non-negligible gradient. To do that, we -# need to slightly modify our circuit such that parameters of the double excitation gates are kept -# fixed while the gradients are computed for the single excitation gates. - - -def circuit_2(params, excitations, gates_select, params_select): - qml.BasisState(hf_state, wires=range(qubits)) - - for i, gate in enumerate(gates_select): - if len(gate) == 4: - qml.DoubleExcitation(params_select[i], wires=gate) - elif len(gate) == 2: - qml.SingleExcitation(params_select[i], wires=gate) - - for i, gate in enumerate(excitations): - if len(gate) == 4: - qml.DoubleExcitation(params[i], wires=gate) - elif len(gate) == 2: - qml.SingleExcitation(params[i], wires=gate) - return qml.expval(H) - - -############################################################################## -# We now compute the gradients for the single excitation gates. - -cost_fn = qml.QNode(circuit_2, dev, interface="jax") -circuit_gradient = jax.grad(cost_fn, argnums=0) -params = [0.0] * len(singles) - -grads = circuit_gradient( - params, - excitations=singles, - gates_select=doubles_select, - params_select=params_doubles -) - -for i in range(len(singles)): - print(f"Excitation : {singles[i]}, Gradient: {grads[i]}") - -############################################################################## -# Similar to the double excitation gates, we select those single excitations that have a gradient -# larger than a predefined threshold. - -singles_select = [singles[i] for i in range(len(singles)) if abs(grads[i]) > 1.0e-5] -singles_select - -############################################################################## -# We now have all of the gates we need to build our circuit. The selected single and double -# excitation gates are highlighted in the figure below. -# -# .. figure:: /_static/demonstration_assets/adaptive_circuits/adapted_circuit.png -# :width: 90% -# :align: center -# -# We perform a final circuit optimization to get the ground-state energy. The resulting energy -# should match the exact energy of the ground electronic state of LiH which is -7.8825378193 Ha. - -cost_fn = qml.QNode(circuit_1, dev, interface="jax") - -params = jnp.zeros(len(doubles_select + singles_select)) - -gates_select = doubles_select + singles_select -opt_state = opt.init(params) - -for n in range(10): - t1 = time.time() - gradient = jax.grad(cost_fn, argnums=0)(params, excitations=doubles_select) - updates, opt_state = opt.update(gradient, opt_state) - params = optax.apply_updates(params, updates) - energy = cost_fn(params, doubles_select) - t2 = time.time() - print("n = {:}, E = {:.8f} H, t = {:.2f} s".format(n, energy, t2 - t1)) - -############################################################################## -# Success! We obtained the ground state energy of LiH, within chemical accuracy, by having -# only 10 gates in our circuit. This is less than half of the total number of single and double -# excitations of LiH (24). - -############################################################################## -# Sparse Hamiltonians -# ------------------- -# -# Molecular Hamiltonians and quantum states are sparse. For instance, let’s look at the Hamiltonian -# we built for LiH. We can compute its matrix representation in the computational basis using the -# Hamiltonian function :meth:`~.pennylane.Hamiltonian.sparse_matrix`. This function -# returns the matrix in the SciPy `sparse coordinate `_ format. - -H_sparse = H.sparse_matrix() -H_sparse - -############################################################################## -# The matrix has :math:`1024^2=1,048,576` entries, but only :math:`11264` of them are non-zero. -# -# .. figure:: /_static/demonstration_assets/adaptive_circuits/h_sparse.png -# :width: 65% -# :align: center -# -# Matrix representation of the LiH Hamiltonian in the computational basis. -# -# Leveraging this sparsity can significantly reduce the -# simulation times. We use the implemented functionality in PennyLane for computing the expectation -# value of the sparse Hamiltonian observable. This can reduce the cost of simulations by -# orders of magnitude depending on the size of the molecule. We use the selected gates obtained in -# the previous steps and perform the final optimization step with the sparse method. Note that the -# sparse method currently only works with the parameter-shift differentiation method. - -excitations = doubles_select + singles_select - -params = jnp.zeros(len(excitations)) - -@qml.qnode(dev, diff_method="parameter-shift", interface="jax") -def circuit(params): - qml.BasisState(hf_state, wires=range(qubits)) - - for i, excitation in enumerate(excitations): - if len(excitation) == 4: - qml.DoubleExcitation(params[i], wires=excitation) - elif len(excitation) == 2: - qml.SingleExcitation(params[i], wires=excitation) - - return qml.expval(qml.SparseHamiltonian(H_sparse, wires=range(qubits))) - - -for n in range(10): - t1 = time.time() - gradient = jax.grad(cost_fn, argnums=0)(params, excitations=doubles_select) - updates, opt_state = opt.update(gradient, opt_state) - params = optax.apply_updates(params, updates) - energy = cost_fn(params, doubles_select) - t2 = time.time() - print("n = {:}, E = {:.8f} H, t = {:.2f} s".format(n, energy, t2 - t1)) - -############################################################################## -# Using the sparse method reproduces the ground state energy while the optimization time is -# much shorter. The average iteration time for the sparse method is about 18 times smaller than that -# of the original non-sparse approach. The performance of the sparse optimization will be even -# better for larger molecules. -# -# Conclusions -# ----------- -# We have learned that building quantum chemistry circuits adaptively and using the -# functionality for sparse objects makes molecular simulations significantly more efficient. We -# learned how to use an adaptive optimizer implemented in PennyLane, that selects the gates one at -# time, to perform ADAPT-VQE [#grimsley2019]_ simulations. We also followed an adaptive strategy -# that selects a group of gates based on information about the gradients. -# -# References -# ---------- -# -# .. [#peruzzo2014] -# -# A. Peruzzo, J. McClean *et al.*, "A variational eigenvalue solver on a photonic -# quantum processor". `Nat. Commun. 5, 4213 (2014). -# `__ -# -# .. [#yudong2019] -# -# Y. Cao, J. Romero, *et al.*, "Quantum Chemistry in the Age of Quantum Computing". -# `Chem. Rev. 2019, 119, 19, 10856-10915. -# `__ -# -# .. [#romero2017] -# -# J. Romero, R. Babbush, *et al.*, "Strategies for quantum computing molecular -# energies using the unitary coupled cluster ansatz". `arXiv:1701.02691 -# `_ -# -# .. [#givenstutorial] -# -# :doc:`tutorial_givens_rotations` -# -# .. [#grimsley2019] -# -# H. R. Grimsley, S. E. Economou, E. Barnes, N. J. Mayhall, "An adaptive -# variational algorithm for exact molecular simulations on a quantum computer". -# `Nat. Commun. 2019, 10, 3007. -# `__ -# -# -# About the author -# ---------------- -# +r""" + +Adaptive circuits for quantum chemistry +======================================= + +.. meta:: + :property="og:description": Learn how to build quantum chemistry circuits adaptively + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_adaptive_circuits.png + +.. related:: + tutorial_quantum_chemistry Building molecular Hamiltonians + tutorial_vqe A brief overview of VQE + tutorial_givens_rotations Givens rotations for quantum chemistry + + +*Author: Soran Jahangiri — Posted: 13 September 2021. Last updated: 10 April 2023* + +The key component of variational quantum algorithms for quantum chemistry is the circuit used to +prepare electronic ground states of a molecule. The variational quantum eigensolver (VQE) +[#peruzzo2014]_, [#yudong2019]_ is the method of choice for performing such quantum chemistry +simulations on quantum devices with few qubits. For a given molecule, the appropriate circuit can +be generated by using a pre-selected wavefunction ansatz, for example, the unitary coupled cluster +with single and double excitations (UCCSD) [#romero2017]_. In this approach, we include all +possible single and double excitations of electrons from the occupied spin-orbitals of a reference +state to the unoccupied spin-orbitals [#givenstutorial]_. This makes the construction of the ansatz +straightforward for any given molecule. However, using a pre-constructed ansatz has the disadvantage +of reducing performance in favour of generality: the approach may work well in many cases, but it +will not be optimized for a specific problem. + +In practical applications, including all possible excitations usually increases the cost of the +simulations without improving the accuracy of the results. This motivates implementing a strategy +that allows for approximation of the contribution of the excitations and selects only those +excitations that are found to be important for the given molecule. This can be done by using +adaptive methods to construct a circuit for each given problem [#grimsley2019]_. Using adaptive +circuits helps improve performance at the cost of reducing generality. + +.. figure:: /_static/demonstration_assets/adaptive_circuits/main.png + :width: 75% + :align: center + + Examples of selecting specific gates to generate adaptive circuits. + +In this tutorial, you will learn how to **adaptively** build customized quantum chemistry circuits +to perform ADAPT-VQE [#grimsley2019]_ simulations. This includes a recipe to adaptively select gates +that have a significant contribution to the desired state, while neglecting those that have a small +contribution. You will also learn how to use PennyLane to leverage the sparsity of a molecular +Hamiltonian to make the computation of the expectation values even more efficient. Let's get +started! + +Adaptive circuits +----------------- + +The main idea behind building adaptive circuits is to compute the gradients with respect to all +possible excitation gates and then select gates based on the magnitude of the computed gradients. + +There are different ways to make use of the gradient information and here we discuss one of +these strategies and apply it to compute the ground state energy of LiH. This method requires constructing the +Hamiltonian and determining all possible excitations, which we can do with functionality built into PennyLane. +But we first need to define the molecular parameters, including atomic symbols and coordinates. +Note that the atomic coordinates are in `Bohr `_. +""" + +import pennylane as qml +import jax +import numpy as np +import time + +from pennylane import qchem +from jax import numpy as jnp + +jax.config.update("jax_enable_x64", True) + +symbols = ["Li", "H"] +geometry = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 2.969280527]]) +molecule = qchem.Molecule(symbols, geometry) + +############################################################################## +# We now compute the molecular Hamiltonian in the +# `STO-3G `_ basis and obtain the electronic +# excitations. We restrict ourselves to single and double excitations, but higher-level ones such +# as triple and quadruple excitations can be considered as well. Each of these electronic excitations +# is represented by a gate that excites electrons from the occupied orbitals of a reference state to +# the unoccupied ones. This allows us to prepare a state that is a superposition of the reference +# state and all of the excited states. + +H, qubits = qchem.molecular_hamiltonian( + molecule, + active_electrons=2, + active_orbitals=5 +) + +active_electrons = 2 + +singles, doubles = qchem.excitations(active_electrons, qubits) + +print(f"Total number of excitations = {len(singles) + len(doubles)}") + +############################################################################## +# Note that we have a total of 24 excitations which can be represented by the same number of +# excitation gates [#givenstutorial]_. Let's now use an :class:`~.pennylane.AdaptiveOptimizer` +# implemented in PennyLane to construct an adaptive circuit. +# +# Adaptive Optimizer +# ~~~~~~~~~~~~~~~~~~ +# The adaptive optimizer +# grows an input quantum circuit by adding and optimizing gates selected from a user-defined +# collection of operators. The algorithm first appends all of the gates provided in the initial +# operator pool and computes the circuit gradients with respect to the gate parameters. It retains +# the gate which has the largest gradient and then optimizes its parameter. +# The process of growing the circuit can be repeated until the computed gradients converge to zero. +# Let's use :class:`~.pennylane.AdaptiveOptimizer` to perform an ADAPT-VQE [#grimsley2019]_ +# simulation and build an adaptive circuit for LiH. +# +# We first create the operator pool which contains all single and double excitations. + +singles_excitations = [qml.SingleExcitation(0.0, x) for x in singles] +doubles_excitations = [qml.DoubleExcitation(0.0, x) for x in doubles] +operator_pool = doubles_excitations + singles_excitations + +############################################################################## +# We now define an initial circuit that prepares a Hartree-Fock state and returns the expectation +# value of the Hamiltonian. We also need to define a device. + +hf_state = qchem.hf_state(active_electrons, qubits) +dev = qml.device("default.qubit", wires=qubits) +@qml.qnode(dev) +def circuit(): + [qml.PauliX(i) for i in np.nonzero(hf_state)[0]] + return qml.expval(H) + +############################################################################## +# We instantiate the optimizer and use it to build the circuit adaptively. + +opt = qml.optimize.AdaptiveOptimizer() +for i in range(len(operator_pool)): + circuit, energy, gradient = opt.step_and_cost(circuit, operator_pool) + if i % 3 == 0: + print("n = {:}, E = {:.8f} H, Largest Gradient = {:.3f}".format(i, energy, gradient)) + print(qml.draw(circuit, decimals=None)()) + print() + if gradient < 3e-3: + break + +############################################################################## +# The resulting energy matches the exact energy of the ground electronic state of LiH, which is +# -7.8825378193 Ha, within chemical accuracy. Note that some of the gates appear more than once in +# the circuit. By default, :class:`~.pennylane.AdaptiveOptimizer` does not eliminate the selected +# gates from the pool. We can set ``drain_pool=True`` to prevent repetition of the gates by +# removing the selected gate from the operator pool. + +@qml.qnode(dev) +def circuit(): + [qml.PauliX(i) for i in np.nonzero(hf_state)[0]] + return qml.expval(H) + +opt = qml.optimize.AdaptiveOptimizer() +for i in range(len(operator_pool)): + circuit, energy, gradient = opt.step_and_cost(circuit, operator_pool, drain_pool=True) + if i % 2 == 0: + print("n = {:}, E = {:.8f} H, Largest Gradient = {:.3f}".format(i, energy, gradient)) + print(qml.draw(circuit, decimals=None)()) + print() + if gradient < 3e-3: + break + +############################################################################## +# Manual construction +# ~~~~~~~~~~~~~~~~~~~ +# We can also build adaptive circuits manually by adding groups of gates one at a time. We follow +# these steps: +# +# 1. Compute gradients for all double excitations. +# 2. Select the double excitations with gradients larger than a pre-defined threshold. +# 3. Perform VQE to obtain the optimized parameters for the selected double excitations. +# 4. Repeat steps 1 and 2 for the single excitations. +# 5. Perform the final VQE optimization with all the selected excitations. +# +# We create a circuit that applies a selected group of gates to the reference Hartree-Fock state. + + +# Re-define H using Jax Arrays +molecule = qchem.Molecule(symbols, jnp.array(geometry)) +H, qubits = qchem.molecular_hamiltonian( + molecule, + active_electrons=2, + active_orbitals=5 +) + +def circuit_1(params, excitations): + qml.BasisState(jnp.array(hf_state), wires=range(qubits)) + + for i, excitation in enumerate(excitations): + if len(excitation) == 4: + qml.DoubleExcitation(params[i], wires=excitation) + else: + qml.SingleExcitation(params[i], wires=excitation) + return qml.expval(H) + +############################################################################## +# We now construct our first group of gates by including all the double excitations and compute the +# gradient for each one. We also need to define a cost +# function. We initialize the parameter values to zero such that the gradients are computed +# with respect to the Hartree-Fock state. + + +dev = qml.device("lightning.qubit", wires=qubits) +cost_fn = qml.QNode(circuit_1, dev, interface="jax") + +circuit_gradient = jax.grad(cost_fn, argnums=0) + +params = [0.0] * len(doubles) +grads = circuit_gradient(params, excitations=doubles) + +for i in range(len(doubles)): + print(f"Excitation : {doubles[i]}, Gradient: {grads[i]}") + +############################################################################## +# The computed gradients have different values, reflecting the contribution of each gate +# in the final state prepared by the circuit. Many of the gradient values are zero and we select +# those gates that have a gradient above a pre-defined threshold, which we set to :math:`10^{-5}.` + +doubles_select = [doubles[i] for i in range(len(doubles)) if abs(grads[i]) > 1.0e-5] +doubles_select + +############################################################################## +# There are only 6 double excitation gates, out of the original 16, that have gradients above the +# threshold. We add the selected gates to the circuit and optimize it to determine +# the updated parameters for the selected gates. We also need to define an optimizer. Note that the +# optimization is not very costly as we only have six gates in our circuit. + +import optax + +params_doubles = jnp.zeros(len(doubles_select)) + +opt = optax.sgd(learning_rate=0.5) # sgd stands for StochasticGradientDescent +opt_state = opt.init(params_doubles) + +for n in range(10): + gradient = jax.grad(cost_fn, argnums=0)(params_doubles, excitations=doubles_select) + updates, opt_state = opt.update(gradient, opt_state) + params_doubles = optax.apply_updates(params_doubles, updates) + +############################################################################## +# Now, we keep the selected gates in the circuit and compute the gradients with respect to all of +# the single excitation gates, selecting those that have a non-negligible gradient. To do that, we +# need to slightly modify our circuit such that parameters of the double excitation gates are kept +# fixed while the gradients are computed for the single excitation gates. + + +def circuit_2(params, excitations, gates_select, params_select): + qml.BasisState(hf_state, wires=range(qubits)) + + for i, gate in enumerate(gates_select): + if len(gate) == 4: + qml.DoubleExcitation(params_select[i], wires=gate) + elif len(gate) == 2: + qml.SingleExcitation(params_select[i], wires=gate) + + for i, gate in enumerate(excitations): + if len(gate) == 4: + qml.DoubleExcitation(params[i], wires=gate) + elif len(gate) == 2: + qml.SingleExcitation(params[i], wires=gate) + return qml.expval(H) + + +############################################################################## +# We now compute the gradients for the single excitation gates. + +cost_fn = qml.QNode(circuit_2, dev, interface="jax") +circuit_gradient = jax.grad(cost_fn, argnums=0) +params = [0.0] * len(singles) + +grads = circuit_gradient( + params, + excitations=singles, + gates_select=doubles_select, + params_select=params_doubles +) + +for i in range(len(singles)): + print(f"Excitation : {singles[i]}, Gradient: {grads[i]}") + +############################################################################## +# Similar to the double excitation gates, we select those single excitations that have a gradient +# larger than a predefined threshold. + +singles_select = [singles[i] for i in range(len(singles)) if abs(grads[i]) > 1.0e-5] +singles_select + +############################################################################## +# We now have all of the gates we need to build our circuit. The selected single and double +# excitation gates are highlighted in the figure below. +# +# .. figure:: /_static/demonstration_assets/adaptive_circuits/adapted_circuit.png +# :width: 90% +# :align: center +# +# We perform a final circuit optimization to get the ground-state energy. The resulting energy +# should match the exact energy of the ground electronic state of LiH which is -7.8825378193 Ha. + +cost_fn = qml.QNode(circuit_1, dev, interface="jax") + +params = jnp.zeros(len(doubles_select + singles_select)) + +gates_select = doubles_select + singles_select +opt_state = opt.init(params) + +for n in range(10): + t1 = time.time() + gradient = jax.grad(cost_fn, argnums=0)(params, excitations=doubles_select) + updates, opt_state = opt.update(gradient, opt_state) + params = optax.apply_updates(params, updates) + energy = cost_fn(params, doubles_select) + t2 = time.time() + print("n = {:}, E = {:.8f} H, t = {:.2f} s".format(n, energy, t2 - t1)) + +############################################################################## +# Success! We obtained the ground state energy of LiH, within chemical accuracy, by having +# only 10 gates in our circuit. This is less than half of the total number of single and double +# excitations of LiH (24). + +############################################################################## +# Sparse Hamiltonians +# ------------------- +# +# Molecular Hamiltonians and quantum states are sparse. For instance, let’s look at the Hamiltonian +# we built for LiH. We can compute its matrix representation in the computational basis using the +# Hamiltonian function :meth:`~.pennylane.Hamiltonian.sparse_matrix`. This function +# returns the matrix in the SciPy `sparse coordinate `_ format. + +H_sparse = H.sparse_matrix() +H_sparse + +############################################################################## +# The matrix has :math:`1024^2=1,048,576` entries, but only :math:`11264` of them are non-zero. +# +# .. figure:: /_static/demonstration_assets/adaptive_circuits/h_sparse.png +# :width: 65% +# :align: center +# +# Matrix representation of the LiH Hamiltonian in the computational basis. +# +# Leveraging this sparsity can significantly reduce the +# simulation times. We use the implemented functionality in PennyLane for computing the expectation +# value of the sparse Hamiltonian observable. This can reduce the cost of simulations by +# orders of magnitude depending on the size of the molecule. We use the selected gates obtained in +# the previous steps and perform the final optimization step with the sparse method. Note that the +# sparse method currently only works with the parameter-shift differentiation method. + +excitations = doubles_select + singles_select + +params = jnp.zeros(len(excitations)) + +@qml.qnode(dev, diff_method="parameter-shift", interface="jax") +def circuit(params): + qml.BasisState(hf_state, wires=range(qubits)) + + for i, excitation in enumerate(excitations): + if len(excitation) == 4: + qml.DoubleExcitation(params[i], wires=excitation) + elif len(excitation) == 2: + qml.SingleExcitation(params[i], wires=excitation) + + return qml.expval(qml.SparseHamiltonian(H_sparse, wires=range(qubits))) + + +for n in range(10): + t1 = time.time() + gradient = jax.grad(cost_fn, argnums=0)(params, excitations=doubles_select) + updates, opt_state = opt.update(gradient, opt_state) + params = optax.apply_updates(params, updates) + energy = cost_fn(params, doubles_select) + t2 = time.time() + print("n = {:}, E = {:.8f} H, t = {:.2f} s".format(n, energy, t2 - t1)) + +############################################################################## +# Using the sparse method reproduces the ground state energy while the optimization time is +# much shorter. The average iteration time for the sparse method is about 18 times smaller than that +# of the original non-sparse approach. The performance of the sparse optimization will be even +# better for larger molecules. +# +# Conclusions +# ----------- +# We have learned that building quantum chemistry circuits adaptively and using the +# functionality for sparse objects makes molecular simulations significantly more efficient. We +# learned how to use an adaptive optimizer implemented in PennyLane, that selects the gates one at +# time, to perform ADAPT-VQE [#grimsley2019]_ simulations. We also followed an adaptive strategy +# that selects a group of gates based on information about the gradients. +# +# References +# ---------- +# +# .. [#peruzzo2014] +# +# A. Peruzzo, J. McClean *et al.*, "A variational eigenvalue solver on a photonic +# quantum processor". `Nat. Commun. 5, 4213 (2014). +# `__ +# +# .. [#yudong2019] +# +# Y. Cao, J. Romero, *et al.*, "Quantum Chemistry in the Age of Quantum Computing". +# `Chem. Rev. 2019, 119, 19, 10856-10915. +# `__ +# +# .. [#romero2017] +# +# J. Romero, R. Babbush, *et al.*, "Strategies for quantum computing molecular +# energies using the unitary coupled cluster ansatz". `arXiv:1701.02691 +# `_ +# +# .. [#givenstutorial] +# +# :doc:`tutorial_givens_rotations` +# +# .. [#grimsley2019] +# +# H. R. Grimsley, S. E. Economou, E. Barnes, N. J. Mayhall, "An adaptive +# variational algorithm for exact molecular simulations on a quantum computer". +# `Nat. Commun. 2019, 10, 3007. +# `__ +# +# +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_adversarial_attacks_QML/metadata.json b/demonstrations_v2/tutorial_adversarial_attacks_QML/metadata.json index 79f6a3580b..ba6ee618b5 100644 --- a/demonstrations_v2/tutorial_adversarial_attacks_QML/metadata.json +++ b/demonstrations_v2/tutorial_adversarial_attacks_QML/metadata.json @@ -1,79 +1,79 @@ -{ - "title": "Adversarial attacks and robustness for quantum machine learning", - "authors": [ - { - "username": "mxw" - }, - { - "username": "kil" - - } - ], - "dateOfPublication": "2024-09-16T00:00:00+00:00", - "dateOfLastModification": "2024-11-08T00:00:00+00:00", - "categories": ["Quantum Machine Learning", "Quantum Computing"], - "tags": ["Quantum machine learning", "QML", "Adversarial attacks", "Quantum security"], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_adversarial_attacks_qml.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_adversarial_attacks_qml.png" - } - ], - "seoDescription": "Learn how to construct adversarial attacks on classification networks and how to make quantum machine learning (QML) models more robust.", - "doi": "", - "references": [ - { - "id": "Wendlinger2024", - "type": "preprint", - "title": "A Comparative Analysis of Adversarial Robustness for Quantum and Classical Machine Learning Models", - "authors": "Maximilian Wendlinger, Kilian Tscharke, Pascal Debus", - "year": "2024", - "doi": "10.48550/arXiv.2404.16154", - "url": "https://arxiv.org/abs/2404.16154" - }, - { - "id": "Goodfellow2014", - "type": "preprint", - "title": "Explaining and harnessing adversarial examples", - "authors": "Ian J. Goodfellow, Jonathon Shlens, Christian Szegedy", - "year": "2014", - "doi": "10.48550/arXiv.1412.6572", - "url": "https://arxiv.org/abs/1412.6572" - - }, - { - "id": "Liu2020", - "type": "preprint", - "title": "A rigorous and robust quantum speed-up in supervised machine learning", - "authors": "Yunchao Liu, Srinivasan Arunachalam, Kristan Temme", - "year": "2020", - "doi": "10.48550/arXiv.2010.02174", - "url": "https://arxiv.org/abs/2010.02174" - - }, - { - "id": "Lu2019", - "type": "preprint", - "title": "Quantum Adversarial Machine Learning", - "authors": "Sirui Lu, Lu-Ming Duan, Dong-Ling Deng", - "year": "2019", - "doi": "10.48550/arXiv.2001.00030", - "url": "https://arxiv.org/abs/2001.00030" - - } - ], - "basedOnPapers": ["10.48550/arXiv.2404.16154"], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_variational_classifier", - "weight": 1.0 - } - ], - "hardware": [] -} +{ + "title": "Adversarial attacks and robustness for quantum machine learning", + "authors": [ + { + "username": "mxw" + }, + { + "username": "kil" + + } + ], + "dateOfPublication": "2024-09-16T00:00:00+00:00", + "dateOfLastModification": "2024-11-08T00:00:00+00:00", + "categories": ["Quantum Machine Learning", "Quantum Computing"], + "tags": ["Quantum machine learning", "QML", "Adversarial attacks", "Quantum security"], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_adversarial_attacks_qml.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_adversarial_attacks_qml.png" + } + ], + "seoDescription": "Learn how to construct adversarial attacks on classification networks and how to make quantum machine learning (QML) models more robust.", + "doi": "", + "references": [ + { + "id": "Wendlinger2024", + "type": "preprint", + "title": "A Comparative Analysis of Adversarial Robustness for Quantum and Classical Machine Learning Models", + "authors": "Maximilian Wendlinger, Kilian Tscharke, Pascal Debus", + "year": "2024", + "doi": "10.48550/arXiv.2404.16154", + "url": "https://arxiv.org/abs/2404.16154" + }, + { + "id": "Goodfellow2014", + "type": "preprint", + "title": "Explaining and harnessing adversarial examples", + "authors": "Ian J. Goodfellow, Jonathon Shlens, Christian Szegedy", + "year": "2014", + "doi": "10.48550/arXiv.1412.6572", + "url": "https://arxiv.org/abs/1412.6572" + + }, + { + "id": "Liu2020", + "type": "preprint", + "title": "A rigorous and robust quantum speed-up in supervised machine learning", + "authors": "Yunchao Liu, Srinivasan Arunachalam, Kristan Temme", + "year": "2020", + "doi": "10.48550/arXiv.2010.02174", + "url": "https://arxiv.org/abs/2010.02174" + + }, + { + "id": "Lu2019", + "type": "preprint", + "title": "Quantum Adversarial Machine Learning", + "authors": "Sirui Lu, Lu-Ming Duan, Dong-Ling Deng", + "year": "2019", + "doi": "10.48550/arXiv.2001.00030", + "url": "https://arxiv.org/abs/2001.00030" + + } + ], + "basedOnPapers": ["10.48550/arXiv.2404.16154"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_variational_classifier", + "weight": 1.0 + } + ], + "hardware": [] +} diff --git a/demonstrations_v2/tutorial_backprop/demo.py b/demonstrations_v2/tutorial_backprop/demo.py index c55bbf280b..9db4a40766 100644 --- a/demonstrations_v2/tutorial_backprop/demo.py +++ b/demonstrations_v2/tutorial_backprop/demo.py @@ -1,456 +1,456 @@ -r""" -Quantum gradients with backpropagation -====================================== - -.. meta:: - :property="og:description": Using backpropagation can speed up training of quantum circuits compared to the parameter-shift rule—if you are using a simulator. - - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/sphx_glr_tutorial_backprop_002.png - -.. related:: - - tutorial_quantum_natural_gradient Quantum natural gradient - -*Author: Josh Izaac — Posted: 11 August 2020. Last updated: 31 January 2021.* - -In PennyLane, any quantum device, whether a hardware device or a simulator, can be -trained using the :doc:`parameter-shift rule ` to compute quantum -gradients. Indeed, the parameter-shift rule is ideally suited to hardware devices, as it does -not require any knowledge about the internal workings of the device; it is sufficient to treat -the device as a 'black box', and to query it with different input values in order to determine the gradient. - -When working with simulators, however, we *do* have access to the internal (classical) -computations being performed. This allows us to take advantage of other methods of computing the -gradient, such as backpropagation, which may be advantageous in certain regimes. In this tutorial, -we will compare and contrast the parameter-shift rule against backpropagation, using -the PennyLane :class:`default.qubit ` -device. - -The parameter-shift rule ------------------------- - -The parameter-shift rule states that, given a variational quantum circuit :math:`U(\boldsymbol -\theta)` composed of parametrized Pauli rotations, and some measured observable :math:`\hat{B},` the -derivative of the expectation value - -.. math:: - - \langle \hat{B} \rangle (\boldsymbol\theta) = - \langle 0 \vert U(\boldsymbol\theta)^\dagger \hat{B} U(\boldsymbol\theta) \vert 0\rangle - -with respect to the input circuit parameters :math:`\boldsymbol{\theta}` is given by - -.. math:: - - \nabla_{\theta_i}\langle \hat{B} \rangle(\boldsymbol\theta) - = \frac{1}{2} - \left[ - \langle \hat{B} \rangle\left(\boldsymbol\theta + \frac{\pi}{2}\hat{\mathbf{e}}_i\right) - - \langle \hat{B} \rangle\left(\boldsymbol\theta - \frac{\pi}{2}\hat{\mathbf{e}}_i\right) - \right]. - -Thus, the gradient of the expectation value can be calculated by evaluating the same variational -quantum circuit, but with shifted parameter values (hence the name, parameter-shift rule!). - -Let's have a go implementing the parameter-shift rule manually in PennyLane. -""" -import pennylane as qml -from jax import numpy as jnp -from matplotlib import pyplot as plt -import jax - -jax.config.update("jax_platform_name", "cpu") -jax.config.update('jax_enable_x64', True) - -# set the random seed -key = jax.random.PRNGKey(42) - - -# create a device to execute the circuit on -dev = qml.device("default.qubit", wires=3) - - -def CNOT_ring(wires): - """Apply CNOTs in a ring pattern""" - n_wires = len(wires) - - for w in wires: - qml.CNOT([w % n_wires, (w + 1) % n_wires]) - - -@qml.qnode(dev, diff_method="parameter-shift") -def circuit(params): - qml.RX(params[0], wires=0) - qml.RY(params[1], wires=1) - qml.RZ(params[2], wires=2) - - CNOT_ring(wires=[0, 1, 2]) - - qml.RX(params[3], wires=0) - qml.RY(params[4], wires=1) - qml.RZ(params[5], wires=2) - - CNOT_ring(wires=[0, 1, 2]) - return qml.expval(qml.PauliY(0) @ qml.PauliZ(2)) - - -############################################################################## -# Let's test the variational circuit evaluation with some parameter input: - -# initial parameters -params = jax.random.normal(key, [6]) - - -print("Parameters:", params) -print("Expectation value:", circuit(params)) - -############################################################################## -# We can also draw the executed quantum circuit: - -fig, ax = qml.draw_mpl(circuit, decimals=2)(params) -plt.show() - - -############################################################################## -# Now that we have defined our variational circuit QNode, we can construct -# a function that computes the gradient of the :math:`i\text{th}` parameter -# using the parameter-shift rule. - -def parameter_shift_term(qnode, params, i): - shifted = params.copy() - shifted = shifted.at[i].add(jnp.pi/2) - forward = qnode(shifted) # forward evaluation - - shifted = shifted.at[i].add(-jnp.pi) - backward = qnode(shifted) # backward evaluation - - return 0.5 * (forward - backward) - -# gradient with respect to the first parameter -print(parameter_shift_term(circuit, params, 0)) - -############################################################################## -# In order to compute the gradient with respect to *all* parameters, we need -# to loop over the index ``i``: - -def parameter_shift(qnode, params): - gradients = jnp.zeros([len(params)]) - - for i in range(len(params)): - gradients = gradients.at[i].set(parameter_shift_term(qnode, params, i)) - - return gradients - -print(parameter_shift(circuit, params)) - -############################################################################## -# We can compare this to PennyLane's *built-in* quantum gradient support by using -# the ``jax.grad`` function. Remember, when we defined the -# QNode, we specified that we wanted it to be differentiable using the parameter-shift -# method (``diff_method="parameter-shift"``). - -grad_function = jax.grad(circuit) -print(grad_function(params)[0]) - -############################################################################## -# Alternatively, we can directly compute quantum gradients of QNodes using -# PennyLane's built in :mod:`qml.gradients ` module: - -print(jnp.stack(qml.gradients.param_shift(circuit)(params))) - -############################################################################## -# If you count the number of quantum evaluations, you will notice that we had to evaluate the circuit -# ``2*len(params)`` number of times in order to compute the quantum gradient with respect to all -# parameters. While reasonably fast for a small number of parameters, as the number of parameters in -# our quantum circuit grows, so does both -# -# 1. the circuit depth (and thus the time taken to evaluate each expectation value or 'forward' pass), and -# -# 2. the number of parameter-shift evaluations required. -# -# Both of these factors increase the time taken to compute the gradient with -# respect to all parameters. -# -# Benchmarking -# ~~~~~~~~~~~~ -# -# Let's consider an example with a significantly larger number of parameters. -# We'll make use of the :class:`~pennylane.StronglyEntanglingLayers` template -# to make a more complicated QNode. - -dev = qml.device("default.qubit", wires=4) - -@qml.qnode(dev, diff_method="parameter-shift") -def circuit(params): - qml.StronglyEntanglingLayers(params, wires=[0, 1, 2, 3]) - return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3)) - -# initialize circuit parameters -param_shape = qml.StronglyEntanglingLayers.shape(n_wires=4, n_layers=15) -params = jax.random.normal(key, param_shape) * 0.1 - -print(params.size) -print(circuit(params)) - -############################################################################## -# This circuit has 180 parameters. Let's see how long it takes to perform a forward -# pass of the circuit. - -import timeit - -reps = 3 -num = 10 -times = timeit.repeat("circuit(params)", globals=globals(), number=num, repeat=reps) -forward_time = min(times) / num - -print(f"Forward pass (best of {reps}): {forward_time} sec per loop") - - -############################################################################## -# We can now estimate the time taken to compute the full gradient vector, -# and see how this compares. - -# create the gradient function -grad_fn = jax.grad(circuit) - -times = timeit.repeat("grad_fn(params)", globals=globals(), number=num, repeat=reps) -backward_time = min(times) / num - -print(f"Gradient computation (best of {reps}): {backward_time} sec per loop") - - -############################################################################## -# Based on the parameter-shift rule, we expect that the amount of time to compute the quantum -# gradients should be approximately :math:`2p\Delta t_{f}` where :math:`p` is the number of -# parameters and :math:`\Delta t_{f}` if the time taken for the forward pass. Let's verify this: - -print(2 * forward_time * params.size) - - -############################################################################## -# Backpropagation -# --------------- -# -# An alternative to the parameter-shift rule for computing gradients is -# `reverse-mode autodifferentiation `__. -# Unlike the parameter-shift method, which requires :math:`2p` circuit evaluations for -# :math:`p` parameters, reverse-mode requires only a *single* forward pass of the -# differentiable function to compute -# the gradient of all variables, at the expense of increased memory usage. -# During the forward pass, the results of all intermediate subexpressions are stored; -# the computation is then traversed *in reverse*, with the gradient computed by repeatedly -# applying the chain rule. -# In most classical machine learning settings (where we are training scalar loss functions -# consisting of a large number of parameters), -# reverse-mode autodifferentiation is the -# preferred method of autodifferentiation—the reduction in computational time enables larger and -# more complex models to be successfully trained. The backpropagation algorithm is a particular -# special-case of reverse-mode autodifferentiation, which has helped lead to the machine learning -# explosion we see today. -# -# In quantum machine learning, however, the inability to store and utilize the results of -# *intermediate* quantum operations on hardware remains a barrier to using backprop; -# while reverse-mode -# autodifferentiation works fine for small quantum simulations, only the -# parameter-shift rule can be used to compute gradients on quantum hardware directly. Nevertheless, -# when training quantum models via classical simulation, it's useful to explore the regimes where -# reverse-mode differentiation may be a better choice than the parameter-shift rule. -# -# Benchmarking -# ~~~~~~~~~~~~ -# -# When creating a QNode, :doc:`PennyLane supports various methods of differentiation -# `, including ``"parameter-shift"`` (which we used previously), -# ``"finite-diff"``, ``"reversible"``, and ``"backprop"``. While ``"parameter-shift"`` works with all devices -# (simulator or hardware), ``"backprop"`` will only work for specific simulator devices that are -# designed to support backpropagation. -# -# One such device is :class:`default.qubit `. It -# has backends written using TensorFlow, JAX, and Autograd, so when used with the -# TensorFlow, JAX, and Autograd interfaces respectively, supports backpropagation. -# In this demo, we will use the JAX interface. - -dev = qml.device("default.qubit", wires=4) - -############################################################################## -# When defining the QNode, we specify ``diff_method="backprop"`` to ensure that -# we are using backpropagation mode. Note that this is the *default differentiation -# mode* for the ``default.qubit`` device. - - -@qml.qnode(dev, diff_method="backprop") -def circuit(params): - qml.StronglyEntanglingLayers(params, wires=[0, 1, 2, 3]) - return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3)) - -# initialize circuit parameters -param_shape = qml.StronglyEntanglingLayers.shape(n_wires=4, n_layers=15) -params = jax.random.normal(key, param_shape) * 0.1 - -print(circuit(params)) - -############################################################################## -# Let's see how long it takes to perform a forward pass of the circuit. - -import timeit - -reps = 3 -num = 10 -times = timeit.repeat("circuit(params)", globals=globals(), number=num, repeat=reps) -forward_time = min(times) / num -print(f"Forward pass (best of {reps}): {forward_time} sec per loop") - - -############################################################################## -# Comparing this to the forward pass from ``default.qubit``, we note that there is some potential -# overhead from using backpropagation. We can now estimate the time required to perform a -# gradient computation via backpropagation: - -times = timeit.repeat("jax.grad(circuit)(params)", globals=globals(), number=num, repeat=reps) -backward_time = min(times) / num -print(f"Backward pass (best of {reps}): {backward_time} sec per loop") - -############################################################################## -# Unlike with the parameter-shift rule, the time taken to perform the backwards pass appears -# of the order of a single forward pass! This can significantly speed up training of simulated -# circuits with many parameters. -# -# Time comparison -# --------------- -# -# Let's compare the two differentiation approaches as the number of trainable parameters -# in the variational circuit increases, by timing both the forward pass and the gradient -# computation as the number of layers is allowed to increase. - -dev = qml.device("default.qubit", wires=4) - -def circuit(params): - qml.StronglyEntanglingLayers(params, wires=[0, 1, 2, 3]) - return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3)) - -############################################################################## -# We'll continue to use the same ansatz as before, but to reduce the time taken -# to collect the data, we'll reduce the number and repetitions of timings per data -# point. Below, we loop over a variational circuit depth ranging from 0 (no gates/ -# trainable parameters) to 20. Each layer will contain :math:`3N` parameters, where -# :math:`N` is the number of wires (in this case, we have :math:`N=4`). - -reps = 2 -num = 3 - -forward_shift = [] -gradient_shift = [] -forward_backprop = [] -gradient_backprop = [] - -qnode_shift = jax.jit(qml.QNode(circuit, dev, diff_method="parameter-shift")) -qnode_backprop = jax.jit(qml.QNode(circuit, dev, diff_method="backprop")) - -grad_qnode_shift = jax.jit(jax.grad(qml.QNode(circuit, dev, diff_method="parameter-shift"))) -grad_qnode_backprop = jax.jit(jax.grad(qml.QNode(circuit, dev, diff_method="backprop"))) - -for depth in range(0, 21): - param_shape = qml.StronglyEntanglingLayers.shape(n_wires=4, n_layers=depth) - params = jax.random.normal(key, param_shape) * 0.1 - - num_params = params.size - - # forward pass timing - # =================== - - - # parameter-shift - t = timeit.repeat("qnode_shift(params)", globals=globals(), number=num, repeat=reps) - forward_shift.append([num_params, min(t) / num]) - - # backprop - t = timeit.repeat("qnode_backprop(params)", globals=globals(), number=num, repeat=reps) - forward_backprop.append([num_params, min(t) / num]) - - if num_params == 0: - continue - - # Gradient timing - # =============== - - # parameter-shift - t = timeit.repeat("grad_qnode_shift(params)", globals=globals(), number=num, repeat=reps) - gradient_shift.append([num_params, min(t) / num]) - - # backprop - t = timeit.repeat("grad_qnode_backprop(params)", globals=globals(), number=num, repeat=reps) - gradient_backprop.append([num_params, min(t) / num]) - -gradient_shift = jnp.array(gradient_shift).T -gradient_backprop = jnp.array(gradient_backprop).T -forward_shift = jnp.array(forward_shift).T -forward_backprop = jnp.array(forward_backprop).T - -############################################################################## -# We now import matplotlib, and plot the results. - -plt.style.use("bmh") - -fig, ax = plt.subplots(1, 1, figsize=(6, 4)) - -ax.plot(*gradient_shift, '.-', label="Parameter-shift") -ax.plot(*gradient_backprop, '.-', label="Backprop") -ax.set_ylabel("Time (s)") -ax.set_xlabel("Number of parameters") -ax.legend() - -plt.show() - -############################################################################## -# .. raw:: html -# -#
-# -# We can see that the computational time for the parameter-shift rule increases with -# increasing number of parameters, as expected, whereas the computational time -# for backpropagation appears much more constant, with perhaps a minute linear increase -# with :math:`p.` Note that the plots are not perfectly linear, with some 'bumpiness' or -# noisiness. This is likely due to low-level operating system jitter, and -# other environmental fluctuations—increasing the number of repeats can help smooth -# out the plot. -# -# For a better comparison, we can scale the time required for computing the quantum -# gradients against the time taken for the corresponding forward pass: - -gradient_shift = gradient_shift.at[1].divide(forward_shift[1, 1:]) -gradient_backprop = gradient_backprop.at[1].divide(forward_backprop[1, 1:]) - -fig, ax = plt.subplots(1, 1, figsize=(6, 4)) - -ax.plot(*gradient_shift, '.-', label="Parameter-shift") -ax.plot(*gradient_backprop, '.-', label="Backprop") - -# perform a least squares regression to determine the linear best fit/gradient -# for the normalized time vs. number of parameters -x = gradient_shift[0] -m_shift, c_shift = jnp.polyfit(*gradient_shift, deg=1) -m_back, c_back = jnp.polyfit(*gradient_backprop, deg=1) - -ax.plot(x, m_shift * x + c_shift, '--', label=f"{m_shift:.2f}p{c_shift:+.2f}") -ax.plot(x, m_back * x + c_back, '--', label=f"{m_back:.2f}p{c_back:+.2f}") - -ax.set_ylabel("Normalized time") -ax.set_xlabel("Number of parameters") -ax.set_xscale("log") -ax.set_yscale("log") -ax.legend() - -plt.show() - -############################################################################## -# .. raw:: html -# -#
-# -# We can now see clearly that there is constant overhead for backpropagation with -# ``default.qubit``, but the parameter-shift rule scales as :math:`\sim 2p.` -# -# -# About the author -# ---------------- -# +r""" +Quantum gradients with backpropagation +====================================== + +.. meta:: + :property="og:description": Using backpropagation can speed up training of quantum circuits compared to the parameter-shift rule—if you are using a simulator. + + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/sphx_glr_tutorial_backprop_002.png + +.. related:: + + tutorial_quantum_natural_gradient Quantum natural gradient + +*Author: Josh Izaac — Posted: 11 August 2020. Last updated: 31 January 2021.* + +In PennyLane, any quantum device, whether a hardware device or a simulator, can be +trained using the :doc:`parameter-shift rule ` to compute quantum +gradients. Indeed, the parameter-shift rule is ideally suited to hardware devices, as it does +not require any knowledge about the internal workings of the device; it is sufficient to treat +the device as a 'black box', and to query it with different input values in order to determine the gradient. + +When working with simulators, however, we *do* have access to the internal (classical) +computations being performed. This allows us to take advantage of other methods of computing the +gradient, such as backpropagation, which may be advantageous in certain regimes. In this tutorial, +we will compare and contrast the parameter-shift rule against backpropagation, using +the PennyLane :class:`default.qubit ` +device. + +The parameter-shift rule +------------------------ + +The parameter-shift rule states that, given a variational quantum circuit :math:`U(\boldsymbol +\theta)` composed of parametrized Pauli rotations, and some measured observable :math:`\hat{B},` the +derivative of the expectation value + +.. math:: + + \langle \hat{B} \rangle (\boldsymbol\theta) = + \langle 0 \vert U(\boldsymbol\theta)^\dagger \hat{B} U(\boldsymbol\theta) \vert 0\rangle + +with respect to the input circuit parameters :math:`\boldsymbol{\theta}` is given by + +.. math:: + + \nabla_{\theta_i}\langle \hat{B} \rangle(\boldsymbol\theta) + = \frac{1}{2} + \left[ + \langle \hat{B} \rangle\left(\boldsymbol\theta + \frac{\pi}{2}\hat{\mathbf{e}}_i\right) + - \langle \hat{B} \rangle\left(\boldsymbol\theta - \frac{\pi}{2}\hat{\mathbf{e}}_i\right) + \right]. + +Thus, the gradient of the expectation value can be calculated by evaluating the same variational +quantum circuit, but with shifted parameter values (hence the name, parameter-shift rule!). + +Let's have a go implementing the parameter-shift rule manually in PennyLane. +""" +import pennylane as qml +from jax import numpy as jnp +from matplotlib import pyplot as plt +import jax + +jax.config.update("jax_platform_name", "cpu") +jax.config.update('jax_enable_x64', True) + +# set the random seed +key = jax.random.PRNGKey(42) + + +# create a device to execute the circuit on +dev = qml.device("default.qubit", wires=3) + + +def CNOT_ring(wires): + """Apply CNOTs in a ring pattern""" + n_wires = len(wires) + + for w in wires: + qml.CNOT([w % n_wires, (w + 1) % n_wires]) + + +@qml.qnode(dev, diff_method="parameter-shift") +def circuit(params): + qml.RX(params[0], wires=0) + qml.RY(params[1], wires=1) + qml.RZ(params[2], wires=2) + + CNOT_ring(wires=[0, 1, 2]) + + qml.RX(params[3], wires=0) + qml.RY(params[4], wires=1) + qml.RZ(params[5], wires=2) + + CNOT_ring(wires=[0, 1, 2]) + return qml.expval(qml.PauliY(0) @ qml.PauliZ(2)) + + +############################################################################## +# Let's test the variational circuit evaluation with some parameter input: + +# initial parameters +params = jax.random.normal(key, [6]) + + +print("Parameters:", params) +print("Expectation value:", circuit(params)) + +############################################################################## +# We can also draw the executed quantum circuit: + +fig, ax = qml.draw_mpl(circuit, decimals=2)(params) +plt.show() + + +############################################################################## +# Now that we have defined our variational circuit QNode, we can construct +# a function that computes the gradient of the :math:`i\text{th}` parameter +# using the parameter-shift rule. + +def parameter_shift_term(qnode, params, i): + shifted = params.copy() + shifted = shifted.at[i].add(jnp.pi/2) + forward = qnode(shifted) # forward evaluation + + shifted = shifted.at[i].add(-jnp.pi) + backward = qnode(shifted) # backward evaluation + + return 0.5 * (forward - backward) + +# gradient with respect to the first parameter +print(parameter_shift_term(circuit, params, 0)) + +############################################################################## +# In order to compute the gradient with respect to *all* parameters, we need +# to loop over the index ``i``: + +def parameter_shift(qnode, params): + gradients = jnp.zeros([len(params)]) + + for i in range(len(params)): + gradients = gradients.at[i].set(parameter_shift_term(qnode, params, i)) + + return gradients + +print(parameter_shift(circuit, params)) + +############################################################################## +# We can compare this to PennyLane's *built-in* quantum gradient support by using +# the ``jax.grad`` function. Remember, when we defined the +# QNode, we specified that we wanted it to be differentiable using the parameter-shift +# method (``diff_method="parameter-shift"``). + +grad_function = jax.grad(circuit) +print(grad_function(params)[0]) + +############################################################################## +# Alternatively, we can directly compute quantum gradients of QNodes using +# PennyLane's built in :mod:`qml.gradients ` module: + +print(jnp.stack(qml.gradients.param_shift(circuit)(params))) + +############################################################################## +# If you count the number of quantum evaluations, you will notice that we had to evaluate the circuit +# ``2*len(params)`` number of times in order to compute the quantum gradient with respect to all +# parameters. While reasonably fast for a small number of parameters, as the number of parameters in +# our quantum circuit grows, so does both +# +# 1. the circuit depth (and thus the time taken to evaluate each expectation value or 'forward' pass), and +# +# 2. the number of parameter-shift evaluations required. +# +# Both of these factors increase the time taken to compute the gradient with +# respect to all parameters. +# +# Benchmarking +# ~~~~~~~~~~~~ +# +# Let's consider an example with a significantly larger number of parameters. +# We'll make use of the :class:`~pennylane.StronglyEntanglingLayers` template +# to make a more complicated QNode. + +dev = qml.device("default.qubit", wires=4) + +@qml.qnode(dev, diff_method="parameter-shift") +def circuit(params): + qml.StronglyEntanglingLayers(params, wires=[0, 1, 2, 3]) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3)) + +# initialize circuit parameters +param_shape = qml.StronglyEntanglingLayers.shape(n_wires=4, n_layers=15) +params = jax.random.normal(key, param_shape) * 0.1 + +print(params.size) +print(circuit(params)) + +############################################################################## +# This circuit has 180 parameters. Let's see how long it takes to perform a forward +# pass of the circuit. + +import timeit + +reps = 3 +num = 10 +times = timeit.repeat("circuit(params)", globals=globals(), number=num, repeat=reps) +forward_time = min(times) / num + +print(f"Forward pass (best of {reps}): {forward_time} sec per loop") + + +############################################################################## +# We can now estimate the time taken to compute the full gradient vector, +# and see how this compares. + +# create the gradient function +grad_fn = jax.grad(circuit) + +times = timeit.repeat("grad_fn(params)", globals=globals(), number=num, repeat=reps) +backward_time = min(times) / num + +print(f"Gradient computation (best of {reps}): {backward_time} sec per loop") + + +############################################################################## +# Based on the parameter-shift rule, we expect that the amount of time to compute the quantum +# gradients should be approximately :math:`2p\Delta t_{f}` where :math:`p` is the number of +# parameters and :math:`\Delta t_{f}` if the time taken for the forward pass. Let's verify this: + +print(2 * forward_time * params.size) + + +############################################################################## +# Backpropagation +# --------------- +# +# An alternative to the parameter-shift rule for computing gradients is +# `reverse-mode autodifferentiation `__. +# Unlike the parameter-shift method, which requires :math:`2p` circuit evaluations for +# :math:`p` parameters, reverse-mode requires only a *single* forward pass of the +# differentiable function to compute +# the gradient of all variables, at the expense of increased memory usage. +# During the forward pass, the results of all intermediate subexpressions are stored; +# the computation is then traversed *in reverse*, with the gradient computed by repeatedly +# applying the chain rule. +# In most classical machine learning settings (where we are training scalar loss functions +# consisting of a large number of parameters), +# reverse-mode autodifferentiation is the +# preferred method of autodifferentiation—the reduction in computational time enables larger and +# more complex models to be successfully trained. The backpropagation algorithm is a particular +# special-case of reverse-mode autodifferentiation, which has helped lead to the machine learning +# explosion we see today. +# +# In quantum machine learning, however, the inability to store and utilize the results of +# *intermediate* quantum operations on hardware remains a barrier to using backprop; +# while reverse-mode +# autodifferentiation works fine for small quantum simulations, only the +# parameter-shift rule can be used to compute gradients on quantum hardware directly. Nevertheless, +# when training quantum models via classical simulation, it's useful to explore the regimes where +# reverse-mode differentiation may be a better choice than the parameter-shift rule. +# +# Benchmarking +# ~~~~~~~~~~~~ +# +# When creating a QNode, :doc:`PennyLane supports various methods of differentiation +# `, including ``"parameter-shift"`` (which we used previously), +# ``"finite-diff"``, ``"reversible"``, and ``"backprop"``. While ``"parameter-shift"`` works with all devices +# (simulator or hardware), ``"backprop"`` will only work for specific simulator devices that are +# designed to support backpropagation. +# +# One such device is :class:`default.qubit `. It +# has backends written using TensorFlow, JAX, and Autograd, so when used with the +# TensorFlow, JAX, and Autograd interfaces respectively, supports backpropagation. +# In this demo, we will use the JAX interface. + +dev = qml.device("default.qubit", wires=4) + +############################################################################## +# When defining the QNode, we specify ``diff_method="backprop"`` to ensure that +# we are using backpropagation mode. Note that this is the *default differentiation +# mode* for the ``default.qubit`` device. + + +@qml.qnode(dev, diff_method="backprop") +def circuit(params): + qml.StronglyEntanglingLayers(params, wires=[0, 1, 2, 3]) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3)) + +# initialize circuit parameters +param_shape = qml.StronglyEntanglingLayers.shape(n_wires=4, n_layers=15) +params = jax.random.normal(key, param_shape) * 0.1 + +print(circuit(params)) + +############################################################################## +# Let's see how long it takes to perform a forward pass of the circuit. + +import timeit + +reps = 3 +num = 10 +times = timeit.repeat("circuit(params)", globals=globals(), number=num, repeat=reps) +forward_time = min(times) / num +print(f"Forward pass (best of {reps}): {forward_time} sec per loop") + + +############################################################################## +# Comparing this to the forward pass from ``default.qubit``, we note that there is some potential +# overhead from using backpropagation. We can now estimate the time required to perform a +# gradient computation via backpropagation: + +times = timeit.repeat("jax.grad(circuit)(params)", globals=globals(), number=num, repeat=reps) +backward_time = min(times) / num +print(f"Backward pass (best of {reps}): {backward_time} sec per loop") + +############################################################################## +# Unlike with the parameter-shift rule, the time taken to perform the backwards pass appears +# of the order of a single forward pass! This can significantly speed up training of simulated +# circuits with many parameters. +# +# Time comparison +# --------------- +# +# Let's compare the two differentiation approaches as the number of trainable parameters +# in the variational circuit increases, by timing both the forward pass and the gradient +# computation as the number of layers is allowed to increase. + +dev = qml.device("default.qubit", wires=4) + +def circuit(params): + qml.StronglyEntanglingLayers(params, wires=[0, 1, 2, 3]) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3)) + +############################################################################## +# We'll continue to use the same ansatz as before, but to reduce the time taken +# to collect the data, we'll reduce the number and repetitions of timings per data +# point. Below, we loop over a variational circuit depth ranging from 0 (no gates/ +# trainable parameters) to 20. Each layer will contain :math:`3N` parameters, where +# :math:`N` is the number of wires (in this case, we have :math:`N=4`). + +reps = 2 +num = 3 + +forward_shift = [] +gradient_shift = [] +forward_backprop = [] +gradient_backprop = [] + +qnode_shift = jax.jit(qml.QNode(circuit, dev, diff_method="parameter-shift")) +qnode_backprop = jax.jit(qml.QNode(circuit, dev, diff_method="backprop")) + +grad_qnode_shift = jax.jit(jax.grad(qml.QNode(circuit, dev, diff_method="parameter-shift"))) +grad_qnode_backprop = jax.jit(jax.grad(qml.QNode(circuit, dev, diff_method="backprop"))) + +for depth in range(0, 21): + param_shape = qml.StronglyEntanglingLayers.shape(n_wires=4, n_layers=depth) + params = jax.random.normal(key, param_shape) * 0.1 + + num_params = params.size + + # forward pass timing + # =================== + + + # parameter-shift + t = timeit.repeat("qnode_shift(params)", globals=globals(), number=num, repeat=reps) + forward_shift.append([num_params, min(t) / num]) + + # backprop + t = timeit.repeat("qnode_backprop(params)", globals=globals(), number=num, repeat=reps) + forward_backprop.append([num_params, min(t) / num]) + + if num_params == 0: + continue + + # Gradient timing + # =============== + + # parameter-shift + t = timeit.repeat("grad_qnode_shift(params)", globals=globals(), number=num, repeat=reps) + gradient_shift.append([num_params, min(t) / num]) + + # backprop + t = timeit.repeat("grad_qnode_backprop(params)", globals=globals(), number=num, repeat=reps) + gradient_backprop.append([num_params, min(t) / num]) + +gradient_shift = jnp.array(gradient_shift).T +gradient_backprop = jnp.array(gradient_backprop).T +forward_shift = jnp.array(forward_shift).T +forward_backprop = jnp.array(forward_backprop).T + +############################################################################## +# We now import matplotlib, and plot the results. + +plt.style.use("bmh") + +fig, ax = plt.subplots(1, 1, figsize=(6, 4)) + +ax.plot(*gradient_shift, '.-', label="Parameter-shift") +ax.plot(*gradient_backprop, '.-', label="Backprop") +ax.set_ylabel("Time (s)") +ax.set_xlabel("Number of parameters") +ax.legend() + +plt.show() + +############################################################################## +# .. raw:: html +# +#
+# +# We can see that the computational time for the parameter-shift rule increases with +# increasing number of parameters, as expected, whereas the computational time +# for backpropagation appears much more constant, with perhaps a minute linear increase +# with :math:`p.` Note that the plots are not perfectly linear, with some 'bumpiness' or +# noisiness. This is likely due to low-level operating system jitter, and +# other environmental fluctuations—increasing the number of repeats can help smooth +# out the plot. +# +# For a better comparison, we can scale the time required for computing the quantum +# gradients against the time taken for the corresponding forward pass: + +gradient_shift = gradient_shift.at[1].divide(forward_shift[1, 1:]) +gradient_backprop = gradient_backprop.at[1].divide(forward_backprop[1, 1:]) + +fig, ax = plt.subplots(1, 1, figsize=(6, 4)) + +ax.plot(*gradient_shift, '.-', label="Parameter-shift") +ax.plot(*gradient_backprop, '.-', label="Backprop") + +# perform a least squares regression to determine the linear best fit/gradient +# for the normalized time vs. number of parameters +x = gradient_shift[0] +m_shift, c_shift = jnp.polyfit(*gradient_shift, deg=1) +m_back, c_back = jnp.polyfit(*gradient_backprop, deg=1) + +ax.plot(x, m_shift * x + c_shift, '--', label=f"{m_shift:.2f}p{c_shift:+.2f}") +ax.plot(x, m_back * x + c_back, '--', label=f"{m_back:.2f}p{c_back:+.2f}") + +ax.set_ylabel("Normalized time") +ax.set_xlabel("Number of parameters") +ax.set_xscale("log") +ax.set_yscale("log") +ax.legend() + +plt.show() + +############################################################################## +# .. raw:: html +# +#
+# +# We can now see clearly that there is constant overhead for backpropagation with +# ``default.qubit``, but the parameter-shift rule scales as :math:`\sim 2p.` +# +# +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_barren_gadgets/barren_gadgets/barren_gadgets.py b/demonstrations_v2/tutorial_barren_gadgets/barren_gadgets/barren_gadgets.py index 0e2929dc41..09d3c89df7 100644 --- a/demonstrations_v2/tutorial_barren_gadgets/barren_gadgets/barren_gadgets.py +++ b/demonstrations_v2/tutorial_barren_gadgets/barren_gadgets/barren_gadgets.py @@ -1,130 +1,130 @@ -import pennylane as qml -from pennylane import numpy as np - -def non_identity_obs(obs): - return [o for o in obs if not isinstance(o, qml.Identity)] - -class PerturbativeGadgets: - """ Class to generate the gadget Hamiltonian corresponding to a given - computational hamiltonian according to the gadget construction derived - by Faehrmann & Cichy - - Args: - perturbation_factor (float) : parameter controlling the magnitude of the - perturbation (aa pre-factor to \lambda_max) - """ - def __init__(self, perturbation_factor=1): - self.perturbation_factor = perturbation_factor - - def gadgetize(self, Hamiltonian, target_locality=3): - """Generation of the perturbative gadget equivalent of the given - Hamiltonian according to the proceedure in Cichy, Fährmann et al. - Args: - Hamiltonian (qml.Hamiltonian) : target Hamiltonian to decompose - into more local terms - target_locality (int > 2) : desired locality of the resulting - gadget Hamiltonian - Returns: - Hgad (qml.Hamiltonian) : gadget Hamiltonian - """ - # checking for unaccounted for situations - self.run_checks(Hamiltonian, target_locality) - computational_qubits, computational_locality, computational_terms = self.get_params(Hamiltonian) - Hamiltonian_coeffs, Hamiltonian_ops = Hamiltonian.terms() - - # total qubit count, updated progressively when adding ancillaries - total_qubits = computational_qubits - #TODO: check proper convergence guarantee - gap = 1 - perturbation_norm = np.sum(np.abs(Hamiltonian_coeffs)) \ - + computational_terms * (computational_locality - 1) - lambda_max = gap / (4 * perturbation_norm) - l = self.perturbation_factor * lambda_max - sign_correction = (-1)**(computational_locality % 2 + 1) - # creating the gadget Hamiltonian - coeffs_anc = [] - coeffs_pert = [] - obs_anc = [] - obs_pert = [] - ancillary_register_size = int(computational_locality / (target_locality - 2)) - for str_count, string in enumerate(Hamiltonian_ops): - previous_total = total_qubits - total_qubits += ancillary_register_size - # Generating the ancillary part - for anc_q in range(previous_total, total_qubits): - coeffs_anc += [0.5, -0.5] - obs_anc += [qml.Identity(anc_q), qml.PauliZ(anc_q)] - # Generating the perturbative part - for anc_q in range(ancillary_register_size): - term = qml.PauliX(previous_total+anc_q) @ qml.PauliX(previous_total+(anc_q+1)%ancillary_register_size) - term = qml.prod(term, *non_identity_obs(string.operands)[ - (target_locality-2)*anc_q:(target_locality-2)*(anc_q+1)]) - obs_pert.append(term) - coeffs_pert += [l * sign_correction * Hamiltonian_coeffs[str_count]] \ - + [l] * (ancillary_register_size - 1) - coeffs = coeffs_anc + coeffs_pert - obs = obs_anc + obs_pert - Hgad = qml.Hamiltonian(coeffs, obs) - return Hgad - - def get_params(self, Hamiltonian): - """ retrieving the parameters n, k and r from the given Hamiltonian - Args: - Hamiltonian (qml.Hamiltonian) : Hamiltonian from which to get the - relevant parameters - Returns: - computational_qubits (int) : total number of qubits acted upon by - the Hamiltonian - computational_locality (int) : maximum number of qubits acted upon - by a single term of the Hamiltonian - computational_terms (int) : number of terms in the sum - composing the Hamiltonian - """ - _, Hamiltonian_ops = Hamiltonian.terms() - # checking how many qubits the Hamiltonian acts on - computational_qubits = len(Hamiltonian.wires) - # getting the number of terms in the Hamiltonian - computational_terms = len(Hamiltonian_ops) - # getting the locality, assuming all terms have the same - computational_locality = max([len(non_identity_obs(Hamiltonian_ops[s])) - for s in range(computational_terms)]) - return computational_qubits, computational_locality, computational_terms - - def run_checks(self, Hamiltonian, target_locality): - """ method to check a few conditions for the correct application of - the methods - Args: - Hamiltonian (qml.Hamiltonian) : Hamiltonian of interest - target_locality (int > 2) : desired locality of the resulting - gadget Hamiltonian - Returns: - None - """ - _, Hamiltonian_ops = Hamiltonian.terms() - computational_qubits, computational_locality, _ = self.get_params(Hamiltonian) - computational_qubits = len(Hamiltonian.wires) - if computational_qubits != Hamiltonian.wires[-1] + 1: - raise Exception('The studied computational Hamiltonian is not acting on ' + - 'the first {} qubits. '.format(computational_qubits) + - 'Decomposition not implemented for this case') - # Check for same string lengths - localities=[] - for string in Hamiltonian_ops: - localities.append(len(non_identity_obs(string))) - if len(np.unique(localities)) > 1: - raise Exception('The given Hamiltonian has terms with different locality.' + - ' Gadgetization not implemented for this case') - # validity of the target locality given the computational locality - if target_locality < 3: - raise Exception('The target locality can not be smaller than 3') - ancillary_register_size = computational_locality / (target_locality - 2) - if int(ancillary_register_size) != ancillary_register_size: - raise Exception('The locality of the Hamiltonian and the target' + - ' locality are not compatible. The gadgetization' + - ' with "unfull" ancillary registers is not' + - ' supported yet. Please choose such that the' + - ' computational locality is divisible by the' + - ' target locality - 2') - - - +import pennylane as qml +from pennylane import numpy as np + +def non_identity_obs(obs): + return [o for o in obs if not isinstance(o, qml.Identity)] + +class PerturbativeGadgets: + """ Class to generate the gadget Hamiltonian corresponding to a given + computational hamiltonian according to the gadget construction derived + by Faehrmann & Cichy + + Args: + perturbation_factor (float) : parameter controlling the magnitude of the + perturbation (aa pre-factor to \lambda_max) + """ + def __init__(self, perturbation_factor=1): + self.perturbation_factor = perturbation_factor + + def gadgetize(self, Hamiltonian, target_locality=3): + """Generation of the perturbative gadget equivalent of the given + Hamiltonian according to the proceedure in Cichy, Fährmann et al. + Args: + Hamiltonian (qml.Hamiltonian) : target Hamiltonian to decompose + into more local terms + target_locality (int > 2) : desired locality of the resulting + gadget Hamiltonian + Returns: + Hgad (qml.Hamiltonian) : gadget Hamiltonian + """ + # checking for unaccounted for situations + self.run_checks(Hamiltonian, target_locality) + computational_qubits, computational_locality, computational_terms = self.get_params(Hamiltonian) + Hamiltonian_coeffs, Hamiltonian_ops = Hamiltonian.terms() + + # total qubit count, updated progressively when adding ancillaries + total_qubits = computational_qubits + #TODO: check proper convergence guarantee + gap = 1 + perturbation_norm = np.sum(np.abs(Hamiltonian_coeffs)) \ + + computational_terms * (computational_locality - 1) + lambda_max = gap / (4 * perturbation_norm) + l = self.perturbation_factor * lambda_max + sign_correction = (-1)**(computational_locality % 2 + 1) + # creating the gadget Hamiltonian + coeffs_anc = [] + coeffs_pert = [] + obs_anc = [] + obs_pert = [] + ancillary_register_size = int(computational_locality / (target_locality - 2)) + for str_count, string in enumerate(Hamiltonian_ops): + previous_total = total_qubits + total_qubits += ancillary_register_size + # Generating the ancillary part + for anc_q in range(previous_total, total_qubits): + coeffs_anc += [0.5, -0.5] + obs_anc += [qml.Identity(anc_q), qml.PauliZ(anc_q)] + # Generating the perturbative part + for anc_q in range(ancillary_register_size): + term = qml.PauliX(previous_total+anc_q) @ qml.PauliX(previous_total+(anc_q+1)%ancillary_register_size) + term = qml.prod(term, *non_identity_obs(string.operands)[ + (target_locality-2)*anc_q:(target_locality-2)*(anc_q+1)]) + obs_pert.append(term) + coeffs_pert += [l * sign_correction * Hamiltonian_coeffs[str_count]] \ + + [l] * (ancillary_register_size - 1) + coeffs = coeffs_anc + coeffs_pert + obs = obs_anc + obs_pert + Hgad = qml.Hamiltonian(coeffs, obs) + return Hgad + + def get_params(self, Hamiltonian): + """ retrieving the parameters n, k and r from the given Hamiltonian + Args: + Hamiltonian (qml.Hamiltonian) : Hamiltonian from which to get the + relevant parameters + Returns: + computational_qubits (int) : total number of qubits acted upon by + the Hamiltonian + computational_locality (int) : maximum number of qubits acted upon + by a single term of the Hamiltonian + computational_terms (int) : number of terms in the sum + composing the Hamiltonian + """ + _, Hamiltonian_ops = Hamiltonian.terms() + # checking how many qubits the Hamiltonian acts on + computational_qubits = len(Hamiltonian.wires) + # getting the number of terms in the Hamiltonian + computational_terms = len(Hamiltonian_ops) + # getting the locality, assuming all terms have the same + computational_locality = max([len(non_identity_obs(Hamiltonian_ops[s])) + for s in range(computational_terms)]) + return computational_qubits, computational_locality, computational_terms + + def run_checks(self, Hamiltonian, target_locality): + """ method to check a few conditions for the correct application of + the methods + Args: + Hamiltonian (qml.Hamiltonian) : Hamiltonian of interest + target_locality (int > 2) : desired locality of the resulting + gadget Hamiltonian + Returns: + None + """ + _, Hamiltonian_ops = Hamiltonian.terms() + computational_qubits, computational_locality, _ = self.get_params(Hamiltonian) + computational_qubits = len(Hamiltonian.wires) + if computational_qubits != Hamiltonian.wires[-1] + 1: + raise Exception('The studied computational Hamiltonian is not acting on ' + + 'the first {} qubits. '.format(computational_qubits) + + 'Decomposition not implemented for this case') + # Check for same string lengths + localities=[] + for string in Hamiltonian_ops: + localities.append(len(non_identity_obs(string))) + if len(np.unique(localities)) > 1: + raise Exception('The given Hamiltonian has terms with different locality.' + + ' Gadgetization not implemented for this case') + # validity of the target locality given the computational locality + if target_locality < 3: + raise Exception('The target locality can not be smaller than 3') + ancillary_register_size = computational_locality / (target_locality - 2) + if int(ancillary_register_size) != ancillary_register_size: + raise Exception('The locality of the Hamiltonian and the target' + + ' locality are not compatible. The gadgetization' + + ' with "unfull" ancillary registers is not' + + ' supported yet. Please choose such that the' + + ' computational locality is divisible by the' + + ' target locality - 2') + + + diff --git a/demonstrations_v2/tutorial_barren_gadgets/barren_gadgets/layered_ansatz.py b/demonstrations_v2/tutorial_barren_gadgets/barren_gadgets/layered_ansatz.py index 7a26d9e059..22f324835e 100644 --- a/demonstrations_v2/tutorial_barren_gadgets/barren_gadgets/layered_ansatz.py +++ b/demonstrations_v2/tutorial_barren_gadgets/barren_gadgets/layered_ansatz.py @@ -1,54 +1,54 @@ -import pennylane as qml -from pennylane import numpy as np - -""" Based on the SimplifiedTwoDesign template from pennylane -https://docs.pennylane.ai/en/latest/code/api/pennylane.SimplifiedTwoDesign.html -as proposed in `Cerezo et al. (2021) `_. -but changin the Y-rotations for a random choice of {X, Y, Z}-rotations. -""" - -def build_ansatz(initial_layer_weights, weights, wires, gate_sequence=None): - - n_layers = qml.math.shape(weights)[0] - op_list = [] - - # initial rotations - for i in range(len(wires)): - op_list.append(qml.RY(initial_layer_weights[i], wires=wires[i])) - - # generating the rotation sequence - if gate_sequence is None: - gate_sequence = generate_random_gate_sequence(qml.math.shape(weights)) - - # repeated layers - for layer in range(n_layers): - - # even layer of entanglers - even_wires = [wires[i : i + 2] for i in range(0, len(wires) - 1, 2)] - for i, wire_pair in enumerate(even_wires): - op_list.append(qml.CZ(wires=wire_pair)) - op_list.append(gate_sequence[layer, i, 0].item()(weights[layer, i, 0], wires=wire_pair[0])) - op_list.append(gate_sequence[layer, i, 1].item()(weights[layer, i, 1], wires=wire_pair[1])) - # op_list.append(qml.RX(weights[layer, i, 0], wires=wire_pair[0])) - # op_list.append(qml.RX(weights[layer, i, 1], wires=wire_pair[1])) - - # odd layer of entanglers - odd_wires = [wires[i : i + 2] for i in range(1, len(wires) - 1, 2)] - for i, wire_pair in enumerate(odd_wires): - op_list.append(qml.CZ(wires=wire_pair)) - op_list.append(gate_sequence[layer, len(wires) // 2 + i, 0].item()(weights[layer, len(wires) // 2 + i, 0], wires=wire_pair[0])) - op_list.append(gate_sequence[layer, len(wires) // 2 + i, 1].item()(weights[layer, len(wires) // 2 + i, 1], wires=wire_pair[1])) - # op_list.append(qml.RX(weights[layer, len(wires) // 2 + i, 0], wires=wire_pair[0])) - # op_list.append(qml.RX(weights[layer, len(wires) // 2 + i, 1], wires=wire_pair[1])) - - return op_list - -def generate_random_gate_sequence(shape): - gate_set = [qml.RX, qml.RY, qml.RZ] - return np.random.choice(gate_set, size=shape) - -def get_parameter_shape(n_layers, n_wires): - if n_wires == 1: - return [(n_wires,), (n_layers,)] - return [(n_wires,), (n_layers, n_wires - 1, 2)] - +import pennylane as qml +from pennylane import numpy as np + +""" Based on the SimplifiedTwoDesign template from pennylane +https://docs.pennylane.ai/en/latest/code/api/pennylane.SimplifiedTwoDesign.html +as proposed in `Cerezo et al. (2021) `_. +but changin the Y-rotations for a random choice of {X, Y, Z}-rotations. +""" + +def build_ansatz(initial_layer_weights, weights, wires, gate_sequence=None): + + n_layers = qml.math.shape(weights)[0] + op_list = [] + + # initial rotations + for i in range(len(wires)): + op_list.append(qml.RY(initial_layer_weights[i], wires=wires[i])) + + # generating the rotation sequence + if gate_sequence is None: + gate_sequence = generate_random_gate_sequence(qml.math.shape(weights)) + + # repeated layers + for layer in range(n_layers): + + # even layer of entanglers + even_wires = [wires[i : i + 2] for i in range(0, len(wires) - 1, 2)] + for i, wire_pair in enumerate(even_wires): + op_list.append(qml.CZ(wires=wire_pair)) + op_list.append(gate_sequence[layer, i, 0].item()(weights[layer, i, 0], wires=wire_pair[0])) + op_list.append(gate_sequence[layer, i, 1].item()(weights[layer, i, 1], wires=wire_pair[1])) + # op_list.append(qml.RX(weights[layer, i, 0], wires=wire_pair[0])) + # op_list.append(qml.RX(weights[layer, i, 1], wires=wire_pair[1])) + + # odd layer of entanglers + odd_wires = [wires[i : i + 2] for i in range(1, len(wires) - 1, 2)] + for i, wire_pair in enumerate(odd_wires): + op_list.append(qml.CZ(wires=wire_pair)) + op_list.append(gate_sequence[layer, len(wires) // 2 + i, 0].item()(weights[layer, len(wires) // 2 + i, 0], wires=wire_pair[0])) + op_list.append(gate_sequence[layer, len(wires) // 2 + i, 1].item()(weights[layer, len(wires) // 2 + i, 1], wires=wire_pair[1])) + # op_list.append(qml.RX(weights[layer, len(wires) // 2 + i, 0], wires=wire_pair[0])) + # op_list.append(qml.RX(weights[layer, len(wires) // 2 + i, 1], wires=wire_pair[1])) + + return op_list + +def generate_random_gate_sequence(shape): + gate_set = [qml.RX, qml.RY, qml.RZ] + return np.random.choice(gate_set, size=shape) + +def get_parameter_shape(n_layers, n_wires): + if n_wires == 1: + return [(n_wires,), (n_layers,)] + return [(n_wires,), (n_layers, n_wires - 1, 2)] + diff --git a/demonstrations_v2/tutorial_barren_gadgets/demo.py b/demonstrations_v2/tutorial_barren_gadgets/demo.py index 0eacb1fff7..f0d19253c1 100644 --- a/demonstrations_v2/tutorial_barren_gadgets/demo.py +++ b/demonstrations_v2/tutorial_barren_gadgets/demo.py @@ -1,390 +1,390 @@ -r""" -Perturbative Gadgets for Variational Quantum Algorithms -========================================== - -.. meta:: - :property="og:description": Use perturbative gadgets to avoid cost-function-dependent barren plateaus - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_barren_gadgets.png - - -.. related:: - tutorial_barren_plateaus Barren plateaus in quantum neural networks¶ - tutorial_local_cost_functions Alleviating barren plateaus with local cost functions - -*Author: Simon Cichy — Posted: 09 December 2022. Last updated: 09 December 2022.* - -Variational quantum algorithms are seen as one of the most primising candidates -for useful applications of quantum computers in the near term, but there are -still a few hurdles to overcome when it comes to practical implementation. -One of them, is the trainability. -In other words, one needs to ensure that the cost function is not flat. -In this tutorial, we will explore the application of perturbative gadgets in -variational quantum algorithms to outgo the issue of cost-function-dependent -barren plateaus, as proposed in Ref. [#cichy2022]_ - -Some context ------------- - -Barren plateaus refer to the phenomenon where the gradients of the cost function -decay exponentially with the size of the problem. Essentially, the cost -landscape becomes flat, with exception of some small regions, e.g., around -the minimum. -That is a problem because increasing the precision of the cost -function requires more measurements from the quantum device due to shot noise, -and an exponential number of measurements would render the algorithm impractical. -If you are not familiar yet with the concept of barren plateaus, I recommend you -first check out the demonstrations on :doc:`barren plateaus ` -and :doc:`avoiding barren plateaus with local cost functions `. - -As presented in the second aforementioned demo, barren plateaus are more severe when using global -cost functions compared to local ones. -A global cost function requires the simultaneous measurement of all -qubits at once. In contrast, a local one is constructed from terms that only -act on a small subset of qubits. - -We want to explore this topic further and learn about one possible mitigation -strategy. -Thinking about Variational Quantum Eigensolver (VQE) applications, let us consider cost functions that are -expectation values of Hamiltonians such as - -.. math:: C(\theta) = \operatorname{Tr} \left[ H V(\theta) |00\ldots 0\rangle \! \langle 00\ldots 0| V(\theta)^\dagger\right]. - -Here :math:`|00\ldots 0\rangle` is our initial state, -:math:`V(\theta)` is the circuit ansatz and :math:`H` the Hamiltonian -whose expectation value we need to minimize. -In some cases, it is easy to find a local cost function which can substitute a global one with the same ground state. -Take, for instance, the following Hamiltonians that induce global and local cost functions, respectively. - - -.. math:: H_G = \mathbb{I} - |00\ldots 0\rangle \! \langle 00\ldots 0| \quad \textrm{ and } \quad H_L = \mathbb{I} - \frac{1}{n} \sum_j |0\rangle \! \langle 0|_j. - -Those are two different Hamiltonians (not just different formulations of the -same one), but they share the same ground state: - - -.. math:: |\psi_{\textrm{min}} \rangle = |00\ldots 0\rangle. - -Therefore, one can work with either Hamiltonian to perform the VQE routine. -However, it is not always so simple. -What if we want to find the minimum eigenenergy of -:math:`H = X \otimes X \otimes Y \otimes Z + Z \otimes Y \otimes X \otimes X` ? -It is not always trivial to construct a local cost -function that has the same minimum as the cost function of interest. -This is where perturbative gadgets come into play! - - -The definitions ---------------- -Perturbative gadgets are a common tool in adiabatic quantum computing. -Their goal is to find a Hamiltonian with local interactions that mimics -another Hamiltonian with more complex couplings. - -Ideally, they would want to implement the target Hamiltonian with complex couplings, but since it's hard to implement more than few-body interactions on hardware, they cannot do so. Perturbative gadgets work by increasing the dimension of the Hilbert space (i.e., the number -of qubits) and "encoding" the target Hamiltonian in the low-energy -subspace of a so-called "gadget" Hamiltonian. - -Let us now construct such a gadget Hamiltonian tailored for VQE applications. -First, we start from a target Hamiltonian that is a linear combination of -Pauli words acting on :math:`k` qubits each: - -.. math:: H^\text{target} = \sum_i c_i h_i, - -where :math:`h_i = \sigma_{i,1} \otimes \sigma_{i,2} \otimes \ldots \otimes \sigma_{i,k},` -:math:`\sigma_{i,j} \in \{ X, Y, Z \},` and :math:`c_i \in \mathbb{R}.` -Now we construct the gadget Hamiltonian. -For each term :math:`h_i,` we will need :math:`k` additional qubits, which we -call auxiliary qubits, and to add two terms to the Hamiltonian: -an "unperturbed" part :math:`H^\text{aux}_i` and a perturbation :math:`V_i` -of strength :math:`\lambda.` -The unperturbed part penalizes each of the newly added qubits for not being in -the :math:`|0\rangle` state - -.. math:: H^\text{aux}_i = \sum_{j=1}^k |1\rangle \! \langle 1|_{i,j} = \sum_{j=1}^k \frac{1}{2}(\mathbb{I} - Z_{i,j}). - -On the other hand, the perturbation part implements one of the operators in the Pauli word -:math:`\sigma_{i,j}` on the corresponding qubit of the target register and a -pair of Pauli :math:`X` gates on two of the auxiliary qubits: - -.. math:: V_i = \sum_{j=1}^k c_{i,j} \sigma_{i,j} \otimes X_{i,j} \otimes X_{i,(j+1) \mathrm{mod }k}. - -In the end, - -.. math:: H^\text{gad} = \sum_{i} \left( H^\text{aux}_i + \lambda V_i \right). - - - -To grasp this idea better, this is what would result from working with a Hamiltonian -acting on a total of :math:`8` qubits and having :math:`3` terms, each of them being a -:math:`4`-body interaction. - -.. figure:: ../_static/demonstration_assets/barren_gadgets/gadget-terms-tutorial.png - :align: center - :width: 90% - -For each of the terms :math:`h_1`, :math:`h_2,` and :math:`h_3` we add :math:`4` auxiliary qubits. -In the end, our gadget Hamiltonian acts on :math:`8+3\cdot 4 = 20` qubits. - -The penalization (red) acts only on the auxiliary registers, penalizing each -qubit individually, while the perturbations couple the target with the auxiliary qubits. - -As shown in Ref. [#cichy2022]_, this construction results in a spectrum that, for low energies, is similar -to that of the original Hamiltonian. -This means that by minimizing the gadget Hamiltonian and reaching its global -minimum, the resulting state will be close to the global minimum of -:math:`H^\text{target}.` - -Since it is a local cost function, it is better behaved with respect to -barren plateaus than the global cost function, making it more trainable. -As a result, one can mitigate the onset of cost-function-dependent barren -plateaus by substituting the global cost function with the resulting gadget -and using that for training instead. That is what we will do in the rest of this tutorial. -""" - -############################################################################## -# First, a few imports. PennyLane and NumPy of course, and a few -# functions specific to our tutorial. -# The ``PerturbativeGadget`` class allows the user to generate the gadget Hamiltonian -# from a user-given target Hamiltonian in an automated way. -# For those who want to check its inner workings, -# you can find the code here: -# :download:`barren_gadgets.py `. -# The functions ``get_parameter_shape``, ``generate_random_gate_sequence``, and -# ``build_ansatz`` (for the details: -# :download:`layered_ansatz.py <../_static/demonstration_assets/barren_gadgets/layered_ansatz.py>` -# ) are there to build the parameterized quantum circuit we use in this demo. -# The first computes the shape of the array of trainable parameters that the -# circuit will need. The second generates a random sequence of Pauli rotations -# from :math:`\{R_X, R_Y, R_Z\}` with the right dimension. -# Finally, ``build_ansatz`` puts the pieces together. - -import pennylane as qml -from pennylane import numpy as np -from barren_gadgets.barren_gadgets import PerturbativeGadgets -from barren_gadgets.layered_ansatz import ( - generate_random_gate_sequence, - get_parameter_shape, - build_ansatz, -) - -np.random.seed(3) - -############################################################################## -# Now, let's take the example given above: -# -# .. math:: H = X \otimes X \otimes Y \otimes Z + Z \otimes Y \otimes X \otimes X. -# -# First, we construct our target Hamiltonian in PennyLane. -# For this, we use the -# :class:`~pennylane.Hamiltonian` class. - - -H_target = qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliY(2) @ qml.PauliZ(3) \ - + qml.PauliZ(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliX(3) - -############################################################################## -# Now we can check that we constructed what we wanted. - -print(H_target) - -############################################################################## -# We indeed have a Hamiltonian composed of two terms with the expected Pauli -# words. -# Next, we can construct the corresponding gadget Hamiltonian. -# Using the class ``PerturbativeGadgets``, we can automatically -# generate the gadget Hamiltonian from the target Hamiltonian. -# The object ``gadgetizer`` will contain all the information about the settings of -# the gadgetization procedure (there are quite a few knobs one can tweak, -# but we'll skip that for now). -# Then, the method ``gadgetize`` takes a -# :class:`~pennylane.Hamiltonian` -# object and generates the -# corresponding gadget Hamiltonian. - -gadgetizer = PerturbativeGadgets() -H_gadget = gadgetizer.gadgetize(H_target) -H_gadget - -############################################################################## -# So, let's see what we got. -# We started with 4 target qubits (labelled ``0`` to ``3``) and two 4-body terms. -# Thus we get 4 additional qubits twice (``4`` to ``11``). -# The first 16 elements of our Hamiltonian correspond to the unperturbed part. -# The last 8 are the perturbation. They are a little scrambled, but one can -# recognize the 8 Paulis from the target Hamiltonian on the qubits ``0`` to -# ``3`` and the cyclic pairwise :math:`X` structure on the auxiliaries. -# Indeed, they are :math:`(X_4X_5, X_5X_6, X_6X_7, X_7X_4)` and -# :math:`(X_8X_9, X_9X_{10}, X_{10}X_{11}, X_{11}X_8).` - -############################################################################## -# Training with the gadget Hamiltonian -# ----------------------------------- -# Now that we have a little intuition on how the gadget Hamiltonian construction -# works, we will use it to train. -# Classical simulations of qubit systems are expensive, so we will simplify further -# to a target Hamiltonian with a single term, and show that using the -# gadget Hamiltonian for training allows us to minimize the target Hamiltonian. -# So, let us construct the two Hamiltonians of interest. - - -H_target = 1 * qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliY(2) @ qml.PauliZ(3) -gadgetizer = PerturbativeGadgets(perturbation_factor=10) -H_gadget = gadgetizer.gadgetize(H_target) - -############################################################################## -# Then we need to set up our variational quantum algorithm. -# That is, we choose a circuit ansatz with randomly initialized weights, -# the cost function, the optimizer with its step size, the number of -# optimization steps, and the device to run the circuit on. -# For an ansatz, we will use a variation of the -# `qml.SimplifiedTwoDesign `_, -# which was proposed in previous -# works on cost-function-dependent barren plateaus [#cerezo2021]_. -# I will skip the details of the construction, since it is not our focus here, -# and just show what it looks like. -# Here is the circuit for a small example - -shapes = get_parameter_shape(n_layers=3, n_wires=5) -init_weights = [np.pi / 4] * shapes[0][0] -weights = np.random.uniform(0, np.pi, size=shapes[1]) - - -@qml.qnode(qml.device("default.qubit", wires=range(5))) -def display_circuit(weights): - build_ansatz(initial_layer_weights=init_weights, weights=weights, wires=range(5)) - return qml.expval(qml.PauliZ(wires=0)) - -import matplotlib.pyplot as plt -qml.draw_mpl(display_circuit)(weights) -plt.show() - -############################################################################## -# Now we build the circuit for our actual experiment. - - -# Total number of qubits: target + auxiliary -num_qubits = 4 + 1 * 4 - -# Other parameters of the ansatz: weights and gate sequence -shapes = get_parameter_shape(n_layers=num_qubits, n_wires=num_qubits) -init_weights = [np.pi / 4] * shapes[0][0] -weights = np.random.uniform(0, np.pi, size=shapes[1]) -random_gate_sequence = generate_random_gate_sequence(qml.math.shape(weights)) - -############################################################################## -# For the classical optimization, we will use the standard gradient descent -# algorithm and perform 500 iterations. For the quantum part, we will simulate -# our circuit using the -# `default.qubit `_ -# simulator. - -opt = qml.GradientDescentOptimizer(stepsize=0.1) -max_iter = 500 -dev = qml.device("default.qubit", wires=range(num_qubits)) - -############################################################################## -# Finally, we will use two cost functions and create a -# `QNode `_ for each. -# The first cost function, the training cost, is the loss function of the optimization. -# For the training, we use the gadget Hamiltonian. To ensure -# that our gadget optimization is proceeding as intended, -# we also define another cost function based on the target Hamiltonian. -# We will evaluate its value at each iteration for monitoring purposes, but it -# will not be used in the optimization. - - - -@qml.qnode(dev) -def training_cost(weights): - build_ansatz( - initial_layer_weights=init_weights, - weights=weights, - wires=range(num_qubits), - gate_sequence=random_gate_sequence, - ) - return qml.expval(H_gadget) - - -@qml.qnode(dev) -def monitoring_cost(weights): - build_ansatz( - initial_layer_weights=init_weights, - weights=weights, - wires=range(num_qubits), - gate_sequence=random_gate_sequence, - ) - return qml.expval(H_target) - - -############################################################################## -# The idea is that if we reach the global minimum for the gadget Hamiltonian, we -# should also be close to the global minimum of the target Hamiltonian, which is -# what we are ultimately interested in. -# To see the results and plot them, we will save the cost values -# at each iteration. - -costs_lists = {} -costs_lists["training"] = [training_cost(weights)] -costs_lists["monitoring"] = [monitoring_cost(weights)] - -############################################################################## -# Now everything is set up, let's run the optimization and see how it goes. -# Be careful, this will take a while. - -for it in range(max_iter): - weights = opt.step(training_cost, weights) - costs_lists["training"].append(training_cost(weights)) - costs_lists["monitoring"].append(monitoring_cost(weights)) - - -plt.style.use("seaborn") - -plt.figure() -plt.plot(costs_lists["training"]) -plt.plot(costs_lists["monitoring"]) -plt.legend(["training", "monitoring"]) -plt.xlabel("Number of iterations") -plt.ylabel("Cost values") -plt.show() - -############################################################################## -# -# Since our example target Hamiltonian is a single Pauli string, we know -# without needing any training that it has only :math:`\pm 1` eigenvalues. -# It is a very simple example, but we see that the training of our circuit using -# the gadget Hamiltonian as a cost function did indeed allow us to reach the -# global minimum of the target cost function. -# -# Now that you have an idea of how you can use perturbative gadgets in -# variational quantum algorithms, you can try applying them to more complex -# problems! However, be aware of the exponential scaling of classical -# simulations of quantum systems; adding linearly many auxiliary qubits -# quickly becomes hard to simulate. -# For those interested in the theory behind it or more formal statements of -# "how close" the results using the gadget are from the targeted ones, -# check out the original paper [#cichy2022]_. -# There you will also find further discussions on the advantages and limits of -# this proposal, -# as well as a more general recipe to design other gadget -# constructions with similar properties. -# Also, the complete code with explanations on how to reproduce the -# figures from the paper can be found in -# `this repository `_. -# -# References -# ---------- -# -# .. [#cichy2022] -# -# Cichy, S., Faehrmann, P.K., Khatri, S., Eisert, J. -# "A perturbative gadget for delaying the onset of barren plateaus in variational quantum algorithms." `arXiv:2210.03099 -# `__, 2022. -# -# .. [#cerezo2021] -# -# Cerezo, M., Sone, A., Volkoff, T. et al. -# "Cost function dependent barren plateaus in shallow parametrized quantum circuits." `Nat Commun 12, 1791 -# `__, 2021. -# -# About the author -# ---------------- +r""" +Perturbative Gadgets for Variational Quantum Algorithms +========================================== + +.. meta:: + :property="og:description": Use perturbative gadgets to avoid cost-function-dependent barren plateaus + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_barren_gadgets.png + + +.. related:: + tutorial_barren_plateaus Barren plateaus in quantum neural networks¶ + tutorial_local_cost_functions Alleviating barren plateaus with local cost functions + +*Author: Simon Cichy — Posted: 09 December 2022. Last updated: 09 December 2022.* + +Variational quantum algorithms are seen as one of the most primising candidates +for useful applications of quantum computers in the near term, but there are +still a few hurdles to overcome when it comes to practical implementation. +One of them, is the trainability. +In other words, one needs to ensure that the cost function is not flat. +In this tutorial, we will explore the application of perturbative gadgets in +variational quantum algorithms to outgo the issue of cost-function-dependent +barren plateaus, as proposed in Ref. [#cichy2022]_ + +Some context +------------ + +Barren plateaus refer to the phenomenon where the gradients of the cost function +decay exponentially with the size of the problem. Essentially, the cost +landscape becomes flat, with exception of some small regions, e.g., around +the minimum. +That is a problem because increasing the precision of the cost +function requires more measurements from the quantum device due to shot noise, +and an exponential number of measurements would render the algorithm impractical. +If you are not familiar yet with the concept of barren plateaus, I recommend you +first check out the demonstrations on :doc:`barren plateaus ` +and :doc:`avoiding barren plateaus with local cost functions `. + +As presented in the second aforementioned demo, barren plateaus are more severe when using global +cost functions compared to local ones. +A global cost function requires the simultaneous measurement of all +qubits at once. In contrast, a local one is constructed from terms that only +act on a small subset of qubits. + +We want to explore this topic further and learn about one possible mitigation +strategy. +Thinking about Variational Quantum Eigensolver (VQE) applications, let us consider cost functions that are +expectation values of Hamiltonians such as + +.. math:: C(\theta) = \operatorname{Tr} \left[ H V(\theta) |00\ldots 0\rangle \! \langle 00\ldots 0| V(\theta)^\dagger\right]. + +Here :math:`|00\ldots 0\rangle` is our initial state, +:math:`V(\theta)` is the circuit ansatz and :math:`H` the Hamiltonian +whose expectation value we need to minimize. +In some cases, it is easy to find a local cost function which can substitute a global one with the same ground state. +Take, for instance, the following Hamiltonians that induce global and local cost functions, respectively. + + +.. math:: H_G = \mathbb{I} - |00\ldots 0\rangle \! \langle 00\ldots 0| \quad \textrm{ and } \quad H_L = \mathbb{I} - \frac{1}{n} \sum_j |0\rangle \! \langle 0|_j. + +Those are two different Hamiltonians (not just different formulations of the +same one), but they share the same ground state: + + +.. math:: |\psi_{\textrm{min}} \rangle = |00\ldots 0\rangle. + +Therefore, one can work with either Hamiltonian to perform the VQE routine. +However, it is not always so simple. +What if we want to find the minimum eigenenergy of +:math:`H = X \otimes X \otimes Y \otimes Z + Z \otimes Y \otimes X \otimes X` ? +It is not always trivial to construct a local cost +function that has the same minimum as the cost function of interest. +This is where perturbative gadgets come into play! + + +The definitions +--------------- +Perturbative gadgets are a common tool in adiabatic quantum computing. +Their goal is to find a Hamiltonian with local interactions that mimics +another Hamiltonian with more complex couplings. + +Ideally, they would want to implement the target Hamiltonian with complex couplings, but since it's hard to implement more than few-body interactions on hardware, they cannot do so. Perturbative gadgets work by increasing the dimension of the Hilbert space (i.e., the number +of qubits) and "encoding" the target Hamiltonian in the low-energy +subspace of a so-called "gadget" Hamiltonian. + +Let us now construct such a gadget Hamiltonian tailored for VQE applications. +First, we start from a target Hamiltonian that is a linear combination of +Pauli words acting on :math:`k` qubits each: + +.. math:: H^\text{target} = \sum_i c_i h_i, + +where :math:`h_i = \sigma_{i,1} \otimes \sigma_{i,2} \otimes \ldots \otimes \sigma_{i,k},` +:math:`\sigma_{i,j} \in \{ X, Y, Z \},` and :math:`c_i \in \mathbb{R}.` +Now we construct the gadget Hamiltonian. +For each term :math:`h_i,` we will need :math:`k` additional qubits, which we +call auxiliary qubits, and to add two terms to the Hamiltonian: +an "unperturbed" part :math:`H^\text{aux}_i` and a perturbation :math:`V_i` +of strength :math:`\lambda.` +The unperturbed part penalizes each of the newly added qubits for not being in +the :math:`|0\rangle` state + +.. math:: H^\text{aux}_i = \sum_{j=1}^k |1\rangle \! \langle 1|_{i,j} = \sum_{j=1}^k \frac{1}{2}(\mathbb{I} - Z_{i,j}). + +On the other hand, the perturbation part implements one of the operators in the Pauli word +:math:`\sigma_{i,j}` on the corresponding qubit of the target register and a +pair of Pauli :math:`X` gates on two of the auxiliary qubits: + +.. math:: V_i = \sum_{j=1}^k c_{i,j} \sigma_{i,j} \otimes X_{i,j} \otimes X_{i,(j+1) \mathrm{mod }k}. + +In the end, + +.. math:: H^\text{gad} = \sum_{i} \left( H^\text{aux}_i + \lambda V_i \right). + + + +To grasp this idea better, this is what would result from working with a Hamiltonian +acting on a total of :math:`8` qubits and having :math:`3` terms, each of them being a +:math:`4`-body interaction. + +.. figure:: ../_static/demonstration_assets/barren_gadgets/gadget-terms-tutorial.png + :align: center + :width: 90% + +For each of the terms :math:`h_1`, :math:`h_2,` and :math:`h_3` we add :math:`4` auxiliary qubits. +In the end, our gadget Hamiltonian acts on :math:`8+3\cdot 4 = 20` qubits. + +The penalization (red) acts only on the auxiliary registers, penalizing each +qubit individually, while the perturbations couple the target with the auxiliary qubits. + +As shown in Ref. [#cichy2022]_, this construction results in a spectrum that, for low energies, is similar +to that of the original Hamiltonian. +This means that by minimizing the gadget Hamiltonian and reaching its global +minimum, the resulting state will be close to the global minimum of +:math:`H^\text{target}.` + +Since it is a local cost function, it is better behaved with respect to +barren plateaus than the global cost function, making it more trainable. +As a result, one can mitigate the onset of cost-function-dependent barren +plateaus by substituting the global cost function with the resulting gadget +and using that for training instead. That is what we will do in the rest of this tutorial. +""" + +############################################################################## +# First, a few imports. PennyLane and NumPy of course, and a few +# functions specific to our tutorial. +# The ``PerturbativeGadget`` class allows the user to generate the gadget Hamiltonian +# from a user-given target Hamiltonian in an automated way. +# For those who want to check its inner workings, +# you can find the code here: +# :download:`barren_gadgets.py `. +# The functions ``get_parameter_shape``, ``generate_random_gate_sequence``, and +# ``build_ansatz`` (for the details: +# :download:`layered_ansatz.py <../_static/demonstration_assets/barren_gadgets/layered_ansatz.py>` +# ) are there to build the parameterized quantum circuit we use in this demo. +# The first computes the shape of the array of trainable parameters that the +# circuit will need. The second generates a random sequence of Pauli rotations +# from :math:`\{R_X, R_Y, R_Z\}` with the right dimension. +# Finally, ``build_ansatz`` puts the pieces together. + +import pennylane as qml +from pennylane import numpy as np +from barren_gadgets.barren_gadgets import PerturbativeGadgets +from barren_gadgets.layered_ansatz import ( + generate_random_gate_sequence, + get_parameter_shape, + build_ansatz, +) + +np.random.seed(3) + +############################################################################## +# Now, let's take the example given above: +# +# .. math:: H = X \otimes X \otimes Y \otimes Z + Z \otimes Y \otimes X \otimes X. +# +# First, we construct our target Hamiltonian in PennyLane. +# For this, we use the +# :class:`~pennylane.Hamiltonian` class. + + +H_target = qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliY(2) @ qml.PauliZ(3) \ + + qml.PauliZ(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliX(3) + +############################################################################## +# Now we can check that we constructed what we wanted. + +print(H_target) + +############################################################################## +# We indeed have a Hamiltonian composed of two terms with the expected Pauli +# words. +# Next, we can construct the corresponding gadget Hamiltonian. +# Using the class ``PerturbativeGadgets``, we can automatically +# generate the gadget Hamiltonian from the target Hamiltonian. +# The object ``gadgetizer`` will contain all the information about the settings of +# the gadgetization procedure (there are quite a few knobs one can tweak, +# but we'll skip that for now). +# Then, the method ``gadgetize`` takes a +# :class:`~pennylane.Hamiltonian` +# object and generates the +# corresponding gadget Hamiltonian. + +gadgetizer = PerturbativeGadgets() +H_gadget = gadgetizer.gadgetize(H_target) +H_gadget + +############################################################################## +# So, let's see what we got. +# We started with 4 target qubits (labelled ``0`` to ``3``) and two 4-body terms. +# Thus we get 4 additional qubits twice (``4`` to ``11``). +# The first 16 elements of our Hamiltonian correspond to the unperturbed part. +# The last 8 are the perturbation. They are a little scrambled, but one can +# recognize the 8 Paulis from the target Hamiltonian on the qubits ``0`` to +# ``3`` and the cyclic pairwise :math:`X` structure on the auxiliaries. +# Indeed, they are :math:`(X_4X_5, X_5X_6, X_6X_7, X_7X_4)` and +# :math:`(X_8X_9, X_9X_{10}, X_{10}X_{11}, X_{11}X_8).` + +############################################################################## +# Training with the gadget Hamiltonian +# ----------------------------------- +# Now that we have a little intuition on how the gadget Hamiltonian construction +# works, we will use it to train. +# Classical simulations of qubit systems are expensive, so we will simplify further +# to a target Hamiltonian with a single term, and show that using the +# gadget Hamiltonian for training allows us to minimize the target Hamiltonian. +# So, let us construct the two Hamiltonians of interest. + + +H_target = 1 * qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliY(2) @ qml.PauliZ(3) +gadgetizer = PerturbativeGadgets(perturbation_factor=10) +H_gadget = gadgetizer.gadgetize(H_target) + +############################################################################## +# Then we need to set up our variational quantum algorithm. +# That is, we choose a circuit ansatz with randomly initialized weights, +# the cost function, the optimizer with its step size, the number of +# optimization steps, and the device to run the circuit on. +# For an ansatz, we will use a variation of the +# `qml.SimplifiedTwoDesign `_, +# which was proposed in previous +# works on cost-function-dependent barren plateaus [#cerezo2021]_. +# I will skip the details of the construction, since it is not our focus here, +# and just show what it looks like. +# Here is the circuit for a small example + +shapes = get_parameter_shape(n_layers=3, n_wires=5) +init_weights = [np.pi / 4] * shapes[0][0] +weights = np.random.uniform(0, np.pi, size=shapes[1]) + + +@qml.qnode(qml.device("default.qubit", wires=range(5))) +def display_circuit(weights): + build_ansatz(initial_layer_weights=init_weights, weights=weights, wires=range(5)) + return qml.expval(qml.PauliZ(wires=0)) + +import matplotlib.pyplot as plt +qml.draw_mpl(display_circuit)(weights) +plt.show() + +############################################################################## +# Now we build the circuit for our actual experiment. + + +# Total number of qubits: target + auxiliary +num_qubits = 4 + 1 * 4 + +# Other parameters of the ansatz: weights and gate sequence +shapes = get_parameter_shape(n_layers=num_qubits, n_wires=num_qubits) +init_weights = [np.pi / 4] * shapes[0][0] +weights = np.random.uniform(0, np.pi, size=shapes[1]) +random_gate_sequence = generate_random_gate_sequence(qml.math.shape(weights)) + +############################################################################## +# For the classical optimization, we will use the standard gradient descent +# algorithm and perform 500 iterations. For the quantum part, we will simulate +# our circuit using the +# `default.qubit `_ +# simulator. + +opt = qml.GradientDescentOptimizer(stepsize=0.1) +max_iter = 500 +dev = qml.device("default.qubit", wires=range(num_qubits)) + +############################################################################## +# Finally, we will use two cost functions and create a +# `QNode `_ for each. +# The first cost function, the training cost, is the loss function of the optimization. +# For the training, we use the gadget Hamiltonian. To ensure +# that our gadget optimization is proceeding as intended, +# we also define another cost function based on the target Hamiltonian. +# We will evaluate its value at each iteration for monitoring purposes, but it +# will not be used in the optimization. + + + +@qml.qnode(dev) +def training_cost(weights): + build_ansatz( + initial_layer_weights=init_weights, + weights=weights, + wires=range(num_qubits), + gate_sequence=random_gate_sequence, + ) + return qml.expval(H_gadget) + + +@qml.qnode(dev) +def monitoring_cost(weights): + build_ansatz( + initial_layer_weights=init_weights, + weights=weights, + wires=range(num_qubits), + gate_sequence=random_gate_sequence, + ) + return qml.expval(H_target) + + +############################################################################## +# The idea is that if we reach the global minimum for the gadget Hamiltonian, we +# should also be close to the global minimum of the target Hamiltonian, which is +# what we are ultimately interested in. +# To see the results and plot them, we will save the cost values +# at each iteration. + +costs_lists = {} +costs_lists["training"] = [training_cost(weights)] +costs_lists["monitoring"] = [monitoring_cost(weights)] + +############################################################################## +# Now everything is set up, let's run the optimization and see how it goes. +# Be careful, this will take a while. + +for it in range(max_iter): + weights = opt.step(training_cost, weights) + costs_lists["training"].append(training_cost(weights)) + costs_lists["monitoring"].append(monitoring_cost(weights)) + + +plt.style.use("seaborn") + +plt.figure() +plt.plot(costs_lists["training"]) +plt.plot(costs_lists["monitoring"]) +plt.legend(["training", "monitoring"]) +plt.xlabel("Number of iterations") +plt.ylabel("Cost values") +plt.show() + +############################################################################## +# +# Since our example target Hamiltonian is a single Pauli string, we know +# without needing any training that it has only :math:`\pm 1` eigenvalues. +# It is a very simple example, but we see that the training of our circuit using +# the gadget Hamiltonian as a cost function did indeed allow us to reach the +# global minimum of the target cost function. +# +# Now that you have an idea of how you can use perturbative gadgets in +# variational quantum algorithms, you can try applying them to more complex +# problems! However, be aware of the exponential scaling of classical +# simulations of quantum systems; adding linearly many auxiliary qubits +# quickly becomes hard to simulate. +# For those interested in the theory behind it or more formal statements of +# "how close" the results using the gadget are from the targeted ones, +# check out the original paper [#cichy2022]_. +# There you will also find further discussions on the advantages and limits of +# this proposal, +# as well as a more general recipe to design other gadget +# constructions with similar properties. +# Also, the complete code with explanations on how to reproduce the +# figures from the paper can be found in +# `this repository `_. +# +# References +# ---------- +# +# .. [#cichy2022] +# +# Cichy, S., Faehrmann, P.K., Khatri, S., Eisert, J. +# "A perturbative gadget for delaying the onset of barren plateaus in variational quantum algorithms." `arXiv:2210.03099 +# `__, 2022. +# +# .. [#cerezo2021] +# +# Cerezo, M., Sone, A., Volkoff, T. et al. +# "Cost function dependent barren plateaus in shallow parametrized quantum circuits." `Nat Commun 12, 1791 +# `__, 2021. +# +# About the author +# ---------------- # \ No newline at end of file diff --git a/demonstrations_v2/tutorial_differentiable_HF/demo.py b/demonstrations_v2/tutorial_differentiable_HF/demo.py index e06dc3fa4d..d1820613d1 100644 --- a/demonstrations_v2/tutorial_differentiable_HF/demo.py +++ b/demonstrations_v2/tutorial_differentiable_HF/demo.py @@ -1,393 +1,393 @@ -r""" - -Differentiable Hartree-Fock -=========================== - -.. meta:: - :property="og:description": Learn how to use the differentiable Hartree-Fock solver - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/differentiable_HF.png - -.. related:: - tutorial_quantum_chemistry Building molecular Hamiltonians - tutorial_vqe A brief overview of VQE - tutorial_givens_rotations Givens rotations for quantum chemistry - tutorial_adaptive_circuits Adaptive circuits for quantum chemistry - - -*Author: Soran Jahangiri — Posted: 09 May 2022. Last updated: 09 May 2022.* - -In this tutorial, you will learn how to use PennyLane's differentiable Hartree-Fock solver -[#arrazola2021]_. The quantum chemistry module in PennyLane, :mod:`qml.qchem `, -provides built-in methods for constructing -atomic and molecular orbitals, building Fock matrices and solving the self-consistent field -equations to obtain optimized orbitals which can be used to construct fully-differentiable -molecular Hamiltonians. PennyLane allows users to natively compute derivatives of all these objects -with respect to the underlying parameters using the methods of -`automatic differentiation `_. We -introduce a workflow to jointly optimize circuit parameters, nuclear coordinates and basis set -parameters in a variational quantum eigensolver algorithm. You will also learn how to visualize the -atomic and molecular orbitals which can be used to create an animation like this: - -.. figure:: /_static/demonstration_assets/differentiable_HF/h2.gif - :width: 60% - :align: center - - The bonding molecular orbital of hydrogen visualized during a full geometry, circuit and - basis set optimization. - -Let's get started! - -Differentiable Hamiltonians ---------------------------- - -Variational quantum algorithms aim to calculate the energy of a molecule by constructing a -parameterized quantum circuit and finding a set of parameters that minimize the expectation value of -the electronic `molecular Hamiltonian `_. The -optimization can be carried out by computing the gradients of the expectation value with respect to -these parameters and iteratively updating them until convergence is achieved. In principle, the -optimization process is not limited to the circuit parameters and can be extended to include the -parameters of the Hamiltonian that can be optimized concurrently with the circuit parameters. The -aim is now to obtain the set of parameters that minimize the following expectation value - -.. math:: \left \langle \Psi(\theta) | H(\beta) | \Psi(\theta) \right \rangle, - -where :math:`\theta` and :math:`\beta` represent the circuit and Hamiltonian parameters, -respectively. - -Computing the gradient of a molecular Hamiltonian is challenging because the dependency of the -Hamiltonian on the molecular parameters is typically not very straightforward. This makes symbolic -differentiation methods, which obtain derivatives of an input function by direct mathematical -manipulation, of limited scope. Furthermore, numerical differentiation methods based on -`finite differences `_ are not always -reliable due to their intrinsic instability, especially when the number of -differentiable parameters is large. These limitations can be alleviated by using automatic -differentiation methods which can be used to compute exact gradients of a function, implemented with -computer code, using resources comparable to those required to evaluate the function itself. - -Efficient optimization of the molecular Hamiltonian parameters in a variational quantum algorithm -is essential for tackling problems such as -`geometry optimization `_ and vibrational -frequency -calculations. These problems require computing the first- and second-order derivatives of the -molecular energy with respect to nuclear coordinates which can be efficiently obtained if the -variational workflow is automatically differentiable. Another important example is the simultaneous -optimization of the parameters of the basis set used to construct the atomic orbitals which can in -principle increase the accuracy of the computed energy without increasing the number of qubits in a -quantum simulation. The joint optimization of the circuit and Hamiltonian parameters can also be -used when the chemical problem involves optimizing the parameters of external potentials. - -The Hartree-Fock method ------------------------ - -The main goal of the Hartree-Fock method is to obtain molecular orbitals that minimize the -energy of a system where electrons are treated as independent particles that experience a mean field -generated by the other electrons. These optimized molecular orbitals are then used to -construct one- and two-body electron integrals in the basis of molecular orbitals, :math:`\phi,` - -.. math:: h_{pq} =\int dx \,\phi_p^*(x)\left(-\frac{\nabla^2}{2}-\sum_{i=1}^N\frac{Z_i}{|r-R_i|}\right)\phi_q(x),\\\\ -.. math:: h_{pqrs} = \int dx_1 dx_2\, \frac{\phi_p^*(x_1)\phi_q^*(x_2)\phi_r(x_2)\phi_s(x_1)}{|r_1-r_2|}. - -These integrals are used to generate a differentiable -`second-quantized `_ molecular Hamiltonian as - - -.. math:: H=\sum_{pq} h_{pq}a_p^\dagger a_q +\frac{1}{2}\sum_{pqrs}h_{pqrs}a_p^\dagger a_q^\dagger a_r a_s, - -where :math:`a^\dagger` and :math:`a` are the fermionic creation and annihilation operators, -respectively. This Hamiltonian is then transformed to the qubit basis. Let's see how this can be -done in PennyLane. - -To get started, we need to define the atomic symbols and the nuclear coordinates of the molecule. -For the hydrogen molecule we have -""" - -import pennylane as qml -import numpy as np -import jax -import jax.numpy as jnp -import matplotlib.pyplot as plt -np.set_printoptions(precision=5) -jax.config.update("jax_enable_x64", True) - -symbols = ["H", "H"] -# optimized geometry at the Hartree-Fock level -geometry = jnp.array([[-0.672943567415407, 0.0, 0.0], - [ 0.672943567415407, 0.0, 0.0]]) - -############################################################################## -# We can now compute the Hartree-Fock energy and its gradient with respect to the -# nuclear coordinates. To do that, we create a molecule object that stores all the molecular -# parameters needed to perform a Hartree-Fock calculation. - -mol = qml.qchem.Molecule(symbols, geometry) - -############################################################################## -# The Hartree-Fock energy can now be computed with the -# :func:`~.pennylane.qchem.hf_energy` function which is a function transform - -qml.qchem.hf_energy(mol)() - -############################################################################## -# We now compute the gradient of the energy with respect to the nuclear coordinates - -jax.grad(qml.qchem.hf_energy(mol), argnums=0)(geometry, mol.coeff, mol.alpha) - -############################################################################## -# The obtained gradients are equal or very close to zero because the geometry we used here has been -# previously optimized at the Hartree-Fock level. You can use a different geometry and verify that -# the newly computed gradients are not all zero. -# -# We can also compute the values and gradients of several other quantities that are obtained during -# the Hartree-Fock procedure. These include all integrals over basis functions, matrices formed from -# these integrals and the one- and two-body integrals over molecular orbitals [#arrazola2021]_. -# Let's look at a few examples. -# -# We first compute the overlap integral between the two S-type atomic orbitals of the hydrogen -# atoms. Here we are using the `STO-3G `_ -# basis set in which each of the atomic orbitals is represented by one basis function composed of -# three primitive Gaussian functions. These basis functions can be accessed from the molecule -# object as - -S1 = mol.basis_set[0] -S2 = mol.basis_set[1] - -############################################################################## -# We can check the parameters of the basis functions as - -for param in S1.params: - print(param) - -############################################################################## -# This returns the exponents, contraction coefficients and the centres of the three Gaussian -# functions of the STO-3G basis set. These parameters can be also obtained individually by using -# ``S1.alpha``, ``S1.coeff`` and ``S1.r``, respectively. You can verify that both of the ``S1`` an -# ``S2`` atomic orbitals have the same exponents and contraction coefficients but are centred on -# different hydrogen atoms. You can also verify that the orbitals are S-type by printing the angular -# momentum quantum numbers with - -S1.l - -############################################################################## -# This gives us a tuple of three integers, representing the exponents of the :math:`x,` :math:`y` -# and :math:`z` components in the Gaussian functions [#arrazola2021]_. -# -# We can now compute the overlap integral, -# -# .. math:: -# -# S_{\mu \nu} = \int \chi_\mu^* (r) \chi_\nu (r) dr -# -# between the atomic orbitals :math:`\chi,` by passing the orbitals and the initial values of their -# centres to the :func:`~.pennylane.qchem.overlap_integral` function. The centres of the orbitals -# are those of the hydrogen atoms by default and are therefore treated as differentiable parameters -# by PennyLane. - -qml.qchem.overlap_integral(S1, S2)() - -############################################################################## -# You can verify that the overlap integral between two identical atomic orbitals is equal to one. -# We can now compute the gradient of the overlap integral with respect to the orbital centres - -jax.grad(qml.qchem.overlap_integral(S1, S2))(geometry, mol.coeff, mol.alpha) - -############################################################################## -# Can you explain why some of the computed gradients are zero? -# -# Let's now plot the atomic orbitals and their overlap. We can do it by using -# the :py:meth:`~.pennylane.qchem.Molecule.atomic_orbital` function, which evaluates the -# atomic orbital at a given coordinate. For instance, the value of the S orbital on the first -# hydrogen atom can be computed at the origin as - -V1 = mol.atomic_orbital(0) -V1(0.0, 0.0, 0.0) - -############################################################################## -# We can evaluate this orbital at different points along the :math:`x` axis and plot it. - -x = np.linspace(-5, 5, 1000) -plt.plot(x, V1(x, 0.0, 0.0), color='teal') -plt.xlabel('X [Bohr]') -plt.show() - -############################################################################## -# We can also plot the second S orbital and visualize the overlap between them - -V2 = mol.atomic_orbital(1) -plt.plot(x, V1(x, 0.0, 0.0), color='teal') -plt.plot(x, V2(x, 0.0, 0.0), color='teal') -plt.fill_between( - x, np.minimum(V1(x, 0.0, 0.0), V2(x, 0.0, 0.0)), color = 'red', alpha = 0.5, hatch = '||') -plt.xlabel('X [Bohr]') -plt.show() - -############################################################################## -# By looking at the orbitals, can you guess at what distance the value of the overlap becomes -# negligible? Can you verify your guess by computing the overlap at that distance? -# -# Similarly, we can plot the molecular orbitals of the hydrogen molecule obtained from the -# Hartree-Fock calculations. We plot the cross-section of the bonding orbital on the :math:`x-y` -# plane. - -n = 30 # number of grid points along each axis -qml.qchem.hf_energy(mol)() -mol.mo_coefficients = mol.mo_coefficients.T -mo = mol.molecular_orbital(0) -x, y = np.meshgrid(np.linspace(-2, 2, n), - np.linspace(-2, 2, n)) -val = np.vectorize(mo)(x, y, 0) -val = np.array([val[i][j] for i in range(n) for j in range(n)]).reshape(n, n) - -fig, ax = plt.subplots() -co = ax.contour(x, y, val, 10, cmap='summer_r', zorder=0) -ax.clabel(co, inline=2, fontsize=10) -plt.scatter(mol.coordinates[:,0], mol.coordinates[:,1], s = 80, color='black') -ax.set_xlabel('X [Bohr]') -ax.set_ylabel('Y [Bohr]') -plt.show() - -############################################################################## -# VQE simulations -# --------------- -# -# By performing the Hartree-Fock calculations, we obtain a set of one- and two-body integrals -# over molecular orbitals that can be used to construct the molecular Hamiltonian with the -# :func:`~.pennylane.qchem.molecular_hamiltonian` function. - -hamiltonian, qubits = qml.qchem.molecular_hamiltonian(mol) -print(hamiltonian) - -############################################################################## -# The Hamiltonian contains 15 terms and, importantly, the coefficients of the Hamiltonian are all -# differentiable. We can construct a circuit and perform a VQE simulation in which both of the -# circuit parameters and the nuclear coordinates are optimized simultaneously by using the computed -# gradients. We will have two sets of differentiable parameters: the first set is the -# rotation angles of the excitation gates which are applied to the reference Hartree-Fock state -# to construct the exact ground state. The second set contains the nuclear coordinates of the -# hydrogen atoms. - -dev = qml.device("default.qubit", wires=4) -def energy(): - @qml.qnode(dev, interface="jax") - def circuit(*args): - qml.BasisState(np.array([1, 1, 0, 0]), wires=range(4)) - qml.DoubleExcitation(*args[0], wires=[0, 1, 2, 3]) - mol = qml.qchem.Molecule(symbols, geometry, alpha=args[3], coeff=args[2]) - H = qml.qchem.molecular_hamiltonian(mol, args=args[1:])[0] - return qml.expval(H) - return circuit - -############################################################################## -# Note that we only use the :class:`~.pennylane.DoubleExcitation` gate as the -# :class:`~.pennylane.SingleExcitation` ones can be neglected in this particular example -# [#szabo1996]_. We now compute the gradients of the energy with respect to the circuit parameter -# and the nuclear coordinates and update the parameters iteratively. Note that the nuclear -# coordinate gradients are simply the forces on the atomic nuclei. - -# initial value of the circuit parameter -circuit_param = jnp.array([0.0]) - -geometry = jnp.array([[0.0, 0.02, -0.672943567415407], - [0.1, 0.0, 0.672943567415407]]) - -for n in range(36): - mol = qml.qchem.Molecule(symbols, geometry) - args = [circuit_param, geometry, mol.coeff, mol.alpha] - # gradient for circuit parameters - g_param = jax.grad(energy(), argnums = 0)(*args) - circuit_param = circuit_param - 0.25 * g_param[0] - - # gradient for nuclear coordinates - forces = jax.grad(energy(), argnums = 1)(*args) - geometry = geometry - 0.5 * forces - - if n % 5 == 0: - print(f'n: {n}, E: {energy()(*args):.8f}, Force-max: {abs(forces).max():.8f}') - -############################################################################## -# After 35 steps of optimization, the forces on the atomic nuclei and the gradient of the -# circuit parameter are both approaching zero, and the energy of the molecule is that of the -# optimized geometry at the -# `full-CI `_ level: -# :math:`-1.1373060483` Ha. You can print the optimized geometry and verify that the final bond -# length of hydrogen is identical to the one computed with full-CI which is :math:`1.389` -# `Bohr `_. -# -# We are now ready to perform a full optimization where the circuit parameters, the nuclear -# coordinates and the basis set parameters are all differentiable parameters that can be optimized -# simultaneously. - -symbols = ["H", "H"] -# initial values of the nuclear coordinates -geometry = jnp.array([[0.0, 0.0, -0.672943567415407], - [0.0, 0.0, 0.672943567415407]]) - -# initial values of the basis set contraction coefficients -coeff = jnp.array([[0.1543289673, 0.5353281423, 0.4446345422], - [0.1543289673, 0.5353281423, 0.4446345422]]) - -# initial values of the basis set exponents -alpha = jnp.array([[3.42525091, 0.62391373, 0.1688554], - [3.42525091, 0.62391373, 0.1688554]]) - -# initial value of the circuit parameter -circuit_param = jnp.array([0.0]) - -mol = qml.qchem.Molecule(symbols, geometry, coeff=coeff, alpha=alpha) -args = [circuit_param, geometry, coeff, alpha] - -for n in range(36): - args = [circuit_param, geometry, coeff, alpha] - mol = qml.qchem.Molecule(symbols, geometry, alpha=alpha, coeff=coeff) - - # gradient for circuit parameters - g_param = jax.grad(energy(), argnums=[0, 1, 2, 3])(*args)[0] - circuit_param = circuit_param - 0.25 * g_param[0] - - # gradient for nuclear coordinates - value, gradients = jax.value_and_grad(energy(), argnums=[1, 2, 3])(*args) - geometry = geometry - 0.5 * gradients[0] - alpha = alpha - 0.25 * gradients[2] - coeff = coeff - 0.25 * gradients[1] - - if n % 5 == 0: - print(f'n: {n}, E: {value:.8f}, Force-max: {abs(gradients[0]).max():.8f}') - -############################################################################## -# You can also print the gradients of the circuit and basis set parameters and confirm that they are -# approaching zero. The computed energy of :math:`-1.14040160` Ha is -# lower than the full-CI energy, :math:`-1.1373060483` Ha (obtained with the STO-3G basis set for -# the hydrogen molecule) because we have optimized the basis set parameters in our example. This -# means that we can reach a lower energy for hydrogen without increasing the basis set size, which -# would otherwise lead to a larger number of qubits. -# -# Conclusions -# ----------- -# This tutorial introduces an important feature of PennyLane that allows performing -# fully-differentiable Hartree-Fock and subsequently VQE simulations. This feature provides two -# major benefits: i) All gradient computations needed for parameter optimization can be carried out -# with the elegant methods of automatic differentiation which facilitates simultaneous optimizations -# of circuit and Hamiltonian parameters in applications such as VQE molecular geometry -# optimizations. ii) By optimizing the molecular parameters such as the exponent and contraction -# coefficients of Gaussian functions of the basis set, one can reach a lower energy without -# increasing the number of basis functions. Can you think of other interesting molecular parameters -# that can be optimized along with the nuclear coordinates and basis set parameters that we -# optimized in this tutorial? -# -# References -# ---------- -# -# .. [#arrazola2021] -# -# Juan Miguel Arrazola, Soran Jahangiri, Alain Delgado, Jack Ceroni *et al.*, "Differentiable -# quantum computational chemistry with PennyLane". `arXiv:2111.09967 -# `__ -# -# .. [#szabo1996] -# -# Attila Szabo, Neil S. Ostlund, "Modern Quantum Chemistry: Introduction to Advanced Electronic -# Structure Theory". Dover Publications, 1996. -# -# -# About the author -# ---------------- -# +r""" + +Differentiable Hartree-Fock +=========================== + +.. meta:: + :property="og:description": Learn how to use the differentiable Hartree-Fock solver + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/differentiable_HF.png + +.. related:: + tutorial_quantum_chemistry Building molecular Hamiltonians + tutorial_vqe A brief overview of VQE + tutorial_givens_rotations Givens rotations for quantum chemistry + tutorial_adaptive_circuits Adaptive circuits for quantum chemistry + + +*Author: Soran Jahangiri — Posted: 09 May 2022. Last updated: 09 May 2022.* + +In this tutorial, you will learn how to use PennyLane's differentiable Hartree-Fock solver +[#arrazola2021]_. The quantum chemistry module in PennyLane, :mod:`qml.qchem `, +provides built-in methods for constructing +atomic and molecular orbitals, building Fock matrices and solving the self-consistent field +equations to obtain optimized orbitals which can be used to construct fully-differentiable +molecular Hamiltonians. PennyLane allows users to natively compute derivatives of all these objects +with respect to the underlying parameters using the methods of +`automatic differentiation `_. We +introduce a workflow to jointly optimize circuit parameters, nuclear coordinates and basis set +parameters in a variational quantum eigensolver algorithm. You will also learn how to visualize the +atomic and molecular orbitals which can be used to create an animation like this: + +.. figure:: /_static/demonstration_assets/differentiable_HF/h2.gif + :width: 60% + :align: center + + The bonding molecular orbital of hydrogen visualized during a full geometry, circuit and + basis set optimization. + +Let's get started! + +Differentiable Hamiltonians +--------------------------- + +Variational quantum algorithms aim to calculate the energy of a molecule by constructing a +parameterized quantum circuit and finding a set of parameters that minimize the expectation value of +the electronic `molecular Hamiltonian `_. The +optimization can be carried out by computing the gradients of the expectation value with respect to +these parameters and iteratively updating them until convergence is achieved. In principle, the +optimization process is not limited to the circuit parameters and can be extended to include the +parameters of the Hamiltonian that can be optimized concurrently with the circuit parameters. The +aim is now to obtain the set of parameters that minimize the following expectation value + +.. math:: \left \langle \Psi(\theta) | H(\beta) | \Psi(\theta) \right \rangle, + +where :math:`\theta` and :math:`\beta` represent the circuit and Hamiltonian parameters, +respectively. + +Computing the gradient of a molecular Hamiltonian is challenging because the dependency of the +Hamiltonian on the molecular parameters is typically not very straightforward. This makes symbolic +differentiation methods, which obtain derivatives of an input function by direct mathematical +manipulation, of limited scope. Furthermore, numerical differentiation methods based on +`finite differences `_ are not always +reliable due to their intrinsic instability, especially when the number of +differentiable parameters is large. These limitations can be alleviated by using automatic +differentiation methods which can be used to compute exact gradients of a function, implemented with +computer code, using resources comparable to those required to evaluate the function itself. + +Efficient optimization of the molecular Hamiltonian parameters in a variational quantum algorithm +is essential for tackling problems such as +`geometry optimization `_ and vibrational +frequency +calculations. These problems require computing the first- and second-order derivatives of the +molecular energy with respect to nuclear coordinates which can be efficiently obtained if the +variational workflow is automatically differentiable. Another important example is the simultaneous +optimization of the parameters of the basis set used to construct the atomic orbitals which can in +principle increase the accuracy of the computed energy without increasing the number of qubits in a +quantum simulation. The joint optimization of the circuit and Hamiltonian parameters can also be +used when the chemical problem involves optimizing the parameters of external potentials. + +The Hartree-Fock method +----------------------- + +The main goal of the Hartree-Fock method is to obtain molecular orbitals that minimize the +energy of a system where electrons are treated as independent particles that experience a mean field +generated by the other electrons. These optimized molecular orbitals are then used to +construct one- and two-body electron integrals in the basis of molecular orbitals, :math:`\phi,` + +.. math:: h_{pq} =\int dx \,\phi_p^*(x)\left(-\frac{\nabla^2}{2}-\sum_{i=1}^N\frac{Z_i}{|r-R_i|}\right)\phi_q(x),\\\\ +.. math:: h_{pqrs} = \int dx_1 dx_2\, \frac{\phi_p^*(x_1)\phi_q^*(x_2)\phi_r(x_2)\phi_s(x_1)}{|r_1-r_2|}. + +These integrals are used to generate a differentiable +`second-quantized `_ molecular Hamiltonian as + + +.. math:: H=\sum_{pq} h_{pq}a_p^\dagger a_q +\frac{1}{2}\sum_{pqrs}h_{pqrs}a_p^\dagger a_q^\dagger a_r a_s, + +where :math:`a^\dagger` and :math:`a` are the fermionic creation and annihilation operators, +respectively. This Hamiltonian is then transformed to the qubit basis. Let's see how this can be +done in PennyLane. + +To get started, we need to define the atomic symbols and the nuclear coordinates of the molecule. +For the hydrogen molecule we have +""" + +import pennylane as qml +import numpy as np +import jax +import jax.numpy as jnp +import matplotlib.pyplot as plt +np.set_printoptions(precision=5) +jax.config.update("jax_enable_x64", True) + +symbols = ["H", "H"] +# optimized geometry at the Hartree-Fock level +geometry = jnp.array([[-0.672943567415407, 0.0, 0.0], + [ 0.672943567415407, 0.0, 0.0]]) + +############################################################################## +# We can now compute the Hartree-Fock energy and its gradient with respect to the +# nuclear coordinates. To do that, we create a molecule object that stores all the molecular +# parameters needed to perform a Hartree-Fock calculation. + +mol = qml.qchem.Molecule(symbols, geometry) + +############################################################################## +# The Hartree-Fock energy can now be computed with the +# :func:`~.pennylane.qchem.hf_energy` function which is a function transform + +qml.qchem.hf_energy(mol)() + +############################################################################## +# We now compute the gradient of the energy with respect to the nuclear coordinates + +jax.grad(qml.qchem.hf_energy(mol), argnums=0)(geometry, mol.coeff, mol.alpha) + +############################################################################## +# The obtained gradients are equal or very close to zero because the geometry we used here has been +# previously optimized at the Hartree-Fock level. You can use a different geometry and verify that +# the newly computed gradients are not all zero. +# +# We can also compute the values and gradients of several other quantities that are obtained during +# the Hartree-Fock procedure. These include all integrals over basis functions, matrices formed from +# these integrals and the one- and two-body integrals over molecular orbitals [#arrazola2021]_. +# Let's look at a few examples. +# +# We first compute the overlap integral between the two S-type atomic orbitals of the hydrogen +# atoms. Here we are using the `STO-3G `_ +# basis set in which each of the atomic orbitals is represented by one basis function composed of +# three primitive Gaussian functions. These basis functions can be accessed from the molecule +# object as + +S1 = mol.basis_set[0] +S2 = mol.basis_set[1] + +############################################################################## +# We can check the parameters of the basis functions as + +for param in S1.params: + print(param) + +############################################################################## +# This returns the exponents, contraction coefficients and the centres of the three Gaussian +# functions of the STO-3G basis set. These parameters can be also obtained individually by using +# ``S1.alpha``, ``S1.coeff`` and ``S1.r``, respectively. You can verify that both of the ``S1`` an +# ``S2`` atomic orbitals have the same exponents and contraction coefficients but are centred on +# different hydrogen atoms. You can also verify that the orbitals are S-type by printing the angular +# momentum quantum numbers with + +S1.l + +############################################################################## +# This gives us a tuple of three integers, representing the exponents of the :math:`x,` :math:`y` +# and :math:`z` components in the Gaussian functions [#arrazola2021]_. +# +# We can now compute the overlap integral, +# +# .. math:: +# +# S_{\mu \nu} = \int \chi_\mu^* (r) \chi_\nu (r) dr +# +# between the atomic orbitals :math:`\chi,` by passing the orbitals and the initial values of their +# centres to the :func:`~.pennylane.qchem.overlap_integral` function. The centres of the orbitals +# are those of the hydrogen atoms by default and are therefore treated as differentiable parameters +# by PennyLane. + +qml.qchem.overlap_integral(S1, S2)() + +############################################################################## +# You can verify that the overlap integral between two identical atomic orbitals is equal to one. +# We can now compute the gradient of the overlap integral with respect to the orbital centres + +jax.grad(qml.qchem.overlap_integral(S1, S2))(geometry, mol.coeff, mol.alpha) + +############################################################################## +# Can you explain why some of the computed gradients are zero? +# +# Let's now plot the atomic orbitals and their overlap. We can do it by using +# the :py:meth:`~.pennylane.qchem.Molecule.atomic_orbital` function, which evaluates the +# atomic orbital at a given coordinate. For instance, the value of the S orbital on the first +# hydrogen atom can be computed at the origin as + +V1 = mol.atomic_orbital(0) +V1(0.0, 0.0, 0.0) + +############################################################################## +# We can evaluate this orbital at different points along the :math:`x` axis and plot it. + +x = np.linspace(-5, 5, 1000) +plt.plot(x, V1(x, 0.0, 0.0), color='teal') +plt.xlabel('X [Bohr]') +plt.show() + +############################################################################## +# We can also plot the second S orbital and visualize the overlap between them + +V2 = mol.atomic_orbital(1) +plt.plot(x, V1(x, 0.0, 0.0), color='teal') +plt.plot(x, V2(x, 0.0, 0.0), color='teal') +plt.fill_between( + x, np.minimum(V1(x, 0.0, 0.0), V2(x, 0.0, 0.0)), color = 'red', alpha = 0.5, hatch = '||') +plt.xlabel('X [Bohr]') +plt.show() + +############################################################################## +# By looking at the orbitals, can you guess at what distance the value of the overlap becomes +# negligible? Can you verify your guess by computing the overlap at that distance? +# +# Similarly, we can plot the molecular orbitals of the hydrogen molecule obtained from the +# Hartree-Fock calculations. We plot the cross-section of the bonding orbital on the :math:`x-y` +# plane. + +n = 30 # number of grid points along each axis +qml.qchem.hf_energy(mol)() +mol.mo_coefficients = mol.mo_coefficients.T +mo = mol.molecular_orbital(0) +x, y = np.meshgrid(np.linspace(-2, 2, n), + np.linspace(-2, 2, n)) +val = np.vectorize(mo)(x, y, 0) +val = np.array([val[i][j] for i in range(n) for j in range(n)]).reshape(n, n) + +fig, ax = plt.subplots() +co = ax.contour(x, y, val, 10, cmap='summer_r', zorder=0) +ax.clabel(co, inline=2, fontsize=10) +plt.scatter(mol.coordinates[:,0], mol.coordinates[:,1], s = 80, color='black') +ax.set_xlabel('X [Bohr]') +ax.set_ylabel('Y [Bohr]') +plt.show() + +############################################################################## +# VQE simulations +# --------------- +# +# By performing the Hartree-Fock calculations, we obtain a set of one- and two-body integrals +# over molecular orbitals that can be used to construct the molecular Hamiltonian with the +# :func:`~.pennylane.qchem.molecular_hamiltonian` function. + +hamiltonian, qubits = qml.qchem.molecular_hamiltonian(mol) +print(hamiltonian) + +############################################################################## +# The Hamiltonian contains 15 terms and, importantly, the coefficients of the Hamiltonian are all +# differentiable. We can construct a circuit and perform a VQE simulation in which both of the +# circuit parameters and the nuclear coordinates are optimized simultaneously by using the computed +# gradients. We will have two sets of differentiable parameters: the first set is the +# rotation angles of the excitation gates which are applied to the reference Hartree-Fock state +# to construct the exact ground state. The second set contains the nuclear coordinates of the +# hydrogen atoms. + +dev = qml.device("default.qubit", wires=4) +def energy(): + @qml.qnode(dev, interface="jax") + def circuit(*args): + qml.BasisState(np.array([1, 1, 0, 0]), wires=range(4)) + qml.DoubleExcitation(*args[0], wires=[0, 1, 2, 3]) + mol = qml.qchem.Molecule(symbols, geometry, alpha=args[3], coeff=args[2]) + H = qml.qchem.molecular_hamiltonian(mol, args=args[1:])[0] + return qml.expval(H) + return circuit + +############################################################################## +# Note that we only use the :class:`~.pennylane.DoubleExcitation` gate as the +# :class:`~.pennylane.SingleExcitation` ones can be neglected in this particular example +# [#szabo1996]_. We now compute the gradients of the energy with respect to the circuit parameter +# and the nuclear coordinates and update the parameters iteratively. Note that the nuclear +# coordinate gradients are simply the forces on the atomic nuclei. + +# initial value of the circuit parameter +circuit_param = jnp.array([0.0]) + +geometry = jnp.array([[0.0, 0.02, -0.672943567415407], + [0.1, 0.0, 0.672943567415407]]) + +for n in range(36): + mol = qml.qchem.Molecule(symbols, geometry) + args = [circuit_param, geometry, mol.coeff, mol.alpha] + # gradient for circuit parameters + g_param = jax.grad(energy(), argnums = 0)(*args) + circuit_param = circuit_param - 0.25 * g_param[0] + + # gradient for nuclear coordinates + forces = jax.grad(energy(), argnums = 1)(*args) + geometry = geometry - 0.5 * forces + + if n % 5 == 0: + print(f'n: {n}, E: {energy()(*args):.8f}, Force-max: {abs(forces).max():.8f}') + +############################################################################## +# After 35 steps of optimization, the forces on the atomic nuclei and the gradient of the +# circuit parameter are both approaching zero, and the energy of the molecule is that of the +# optimized geometry at the +# `full-CI `_ level: +# :math:`-1.1373060483` Ha. You can print the optimized geometry and verify that the final bond +# length of hydrogen is identical to the one computed with full-CI which is :math:`1.389` +# `Bohr `_. +# +# We are now ready to perform a full optimization where the circuit parameters, the nuclear +# coordinates and the basis set parameters are all differentiable parameters that can be optimized +# simultaneously. + +symbols = ["H", "H"] +# initial values of the nuclear coordinates +geometry = jnp.array([[0.0, 0.0, -0.672943567415407], + [0.0, 0.0, 0.672943567415407]]) + +# initial values of the basis set contraction coefficients +coeff = jnp.array([[0.1543289673, 0.5353281423, 0.4446345422], + [0.1543289673, 0.5353281423, 0.4446345422]]) + +# initial values of the basis set exponents +alpha = jnp.array([[3.42525091, 0.62391373, 0.1688554], + [3.42525091, 0.62391373, 0.1688554]]) + +# initial value of the circuit parameter +circuit_param = jnp.array([0.0]) + +mol = qml.qchem.Molecule(symbols, geometry, coeff=coeff, alpha=alpha) +args = [circuit_param, geometry, coeff, alpha] + +for n in range(36): + args = [circuit_param, geometry, coeff, alpha] + mol = qml.qchem.Molecule(symbols, geometry, alpha=alpha, coeff=coeff) + + # gradient for circuit parameters + g_param = jax.grad(energy(), argnums=[0, 1, 2, 3])(*args)[0] + circuit_param = circuit_param - 0.25 * g_param[0] + + # gradient for nuclear coordinates + value, gradients = jax.value_and_grad(energy(), argnums=[1, 2, 3])(*args) + geometry = geometry - 0.5 * gradients[0] + alpha = alpha - 0.25 * gradients[2] + coeff = coeff - 0.25 * gradients[1] + + if n % 5 == 0: + print(f'n: {n}, E: {value:.8f}, Force-max: {abs(gradients[0]).max():.8f}') + +############################################################################## +# You can also print the gradients of the circuit and basis set parameters and confirm that they are +# approaching zero. The computed energy of :math:`-1.14040160` Ha is +# lower than the full-CI energy, :math:`-1.1373060483` Ha (obtained with the STO-3G basis set for +# the hydrogen molecule) because we have optimized the basis set parameters in our example. This +# means that we can reach a lower energy for hydrogen without increasing the basis set size, which +# would otherwise lead to a larger number of qubits. +# +# Conclusions +# ----------- +# This tutorial introduces an important feature of PennyLane that allows performing +# fully-differentiable Hartree-Fock and subsequently VQE simulations. This feature provides two +# major benefits: i) All gradient computations needed for parameter optimization can be carried out +# with the elegant methods of automatic differentiation which facilitates simultaneous optimizations +# of circuit and Hamiltonian parameters in applications such as VQE molecular geometry +# optimizations. ii) By optimizing the molecular parameters such as the exponent and contraction +# coefficients of Gaussian functions of the basis set, one can reach a lower energy without +# increasing the number of basis functions. Can you think of other interesting molecular parameters +# that can be optimized along with the nuclear coordinates and basis set parameters that we +# optimized in this tutorial? +# +# References +# ---------- +# +# .. [#arrazola2021] +# +# Juan Miguel Arrazola, Soran Jahangiri, Alain Delgado, Jack Ceroni *et al.*, "Differentiable +# quantum computational chemistry with PennyLane". `arXiv:2111.09967 +# `__ +# +# .. [#szabo1996] +# +# Attila Szabo, Neil S. Ostlund, "Modern Quantum Chemistry: Introduction to Advanced Electronic +# Structure Theory". Dover Publications, 1996. +# +# +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_doubly_stochastic/demo.py b/demonstrations_v2/tutorial_doubly_stochastic/demo.py index b75b365834..58a18ea688 100644 --- a/demonstrations_v2/tutorial_doubly_stochastic/demo.py +++ b/demonstrations_v2/tutorial_doubly_stochastic/demo.py @@ -1,407 +1,407 @@ -r""" -Doubly stochastic gradient descent -================================== - -.. meta:: - :property="og:description": Minimize a Hamiltonian via an adaptive shot optimization - strategy with doubly stochastic gradient descent. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/single_shot.png - -.. related:: - - tutorial_backprop Quantum gradients with backpropagation - tutorial_quantum_natural_gradient Quantum natural gradient - tutorial_rosalin Frugal shot optimization with Rosalin - -*Author: Josh Izaac — Posted: 16 October 2019. Last updated: 20 January 2021.* - -In this tutorial we investigate and implement the doubly stochastic gradient descent -paper from `Ryan Sweke et al. (2019) `__. In this paper, -it is shown that quantum gradient descent, where a finite number of measurement samples -(or *shots*) are used to estimate the gradient, is a form of stochastic gradient descent. -Furthermore, if the optimization involves a linear combination of expectation values -(such as VQE), sampling from the terms in this linear combination can further reduce required -resources, allowing for "doubly stochastic gradient descent". - -Note that based on very similar observations, `Jonas Kuebler et al. (2019) `_ -recently proposed an optimizer (which they call the *individual Coupled Adaptive -Number of Shots (iCANS)* optimizer) that adapts the shot number of -measurements during training. - -Background ----------- - -In classical machine learning, `stochastic gradient descent -`_ is a common optimization strategy -where the standard gradient descent parameter update rule, - -.. math:: \theta^{(t+1)} = \theta^{(t)} - \eta \nabla \mathcal{L}(\theta^{(t)}), - -is modified such that - -.. math:: \theta^{(t+1)} = \theta^{(t)} - \eta g^{(t)}(\theta^{(t)}) - -where :math:`\eta` is the step-size, and :math:`\{g^{(t)}(\theta)\}` is a sequence of random -variables such that - -.. math:: \mathbb{E}[g^{(t)}(\theta)] = \nabla\mathcal{L}(\theta). - -In general, stochastic gradient descent is preferred over standard gradient -descent for several reasons: - -1. Samples of the gradient estimator :math:`g^{(t)}(\theta)` can typically - be computed much more efficiently than :math:`\mathcal{L}(\theta),` - -2. Stochasticity can help to avoid local minima and saddle points, - -3. Numerical evidence shows that convergence properties are superior to regular gradient descent. - -In variational quantum algorithms, a parametrized quantum circuit :math:`U(\theta)` -is optimized by a classical optimization loop in order to minimize a function of the expectation -values. For example, consider the expectation values - -.. math:: \langle A_i \rangle = \langle 0 | U(\theta)^\dagger A_i U(\theta) | 0\rangle - -for a set of observables :math:`\{A_i\},` and loss function - -.. math:: \mathcal{L}(\theta, \langle A_1 \rangle, \dots, \langle A_M \rangle). - -While the expectation values can be calculated analytically in classical simulations, -on quantum hardware we are limited to *sampling* from the expectation values; as the -number of samples (or shots) increase, we converge on the analytic expectation value, but can -never recover the exact expression. Furthermore, the parameter-shift rule -(`Schuld et al., 2018 `__) allows for analytic -quantum gradients to be computed from a linear combination of the variational circuits' -expectation values. - -Putting these two results together, `Sweke et al. (2019) `__ -show that samples of the expectation value fed into the parameter-shift rule provide -unbiased estimators of the quantum gradient—resulting in a form of stochastic gradient descent -(referred to as QSGD). Moreover, they show that convergence of the stochastic gradient -descent is guaranteed in sufficiently simplified settings, even in the case where the number -of shots is 1! - -.. note:: - - It is worth noting that the smaller the number of shots used, the larger the - variance in the estimated expectation value. As a result, it may take - more optimization steps for convergence than using a larger number of shots, - or an exact value. - - At the same time, a reduced number of shots may significantly reduce the - wall time of each optimization step, leading to a reduction in the overall - optimization time. - -""" - -############################################################################## -# Let's consider a simple example in PennyLane, comparing analytic gradient -# descent (with exact expectation values) to stochastic gradient descent -# using a finite number of shots. -# -# A single-shot stochastic gradient descent -# ----------------------------------------- -# -# Consider the Hamiltonian -# -# .. math:: -# -# H = \begin{bmatrix} -# 8 & 4 & 0 & -6\\ -# 4 & 0 & 4 & 0\\ -# 0 & 4 & 8 & 0\\ -# -6 & 0 & 0 & 0 -# \end{bmatrix}. -# -# We can solve for the ground state energy using -# the variational quantum eigensolver (VQE) algorithm. -# -# Let's use the ``lightning.qubit`` simulator for both the analytic gradient, -# as well as the estimated gradient using number of shots :math:`N\in\{1, 100\}.` - -import pennylane as qml -import numpy as np -from pennylane import numpy as pnp - -np.random.seed(3) - -from pennylane import expval -from pennylane.templates.layers import StronglyEntanglingLayers - -num_layers = 2 -num_wires = 2 -eta = 0.01 -steps = 200 - -dev_analytic = qml.device("lightning.qubit", wires=num_wires, shots=None) -dev_stochastic = qml.device("lightning.qubit", wires=num_wires, shots=1000) - -############################################################################## -# We can use ``qml.Hermitian`` to directly specify that we want to measure -# the expectation value of the matrix :math:`H:` - -H = np.array([[8, 4, 0, -6], [4, 0, 4, 0], [0, 4, 8, 0], [-6, 0, 0, 0]]) - - -def circuit(params): - StronglyEntanglingLayers(weights=params, wires=[0, 1]) - return expval(qml.Hermitian(H, wires=[0, 1])) - - -############################################################################## -# Now, we create three QNodes, each corresponding to a device above, -# and optimize them using gradient descent via the parameter-shift rule. - -qnode_analytic = qml.QNode(circuit, dev_analytic, interface="autograd", diff_method="parameter-shift") -qnode_stochastic = qml.QNode(circuit, dev_stochastic, interface="autograd", diff_method="parameter-shift") - -param_shape = StronglyEntanglingLayers.shape(n_layers=num_layers, n_wires=num_wires) -init_params = pnp.random.uniform(low=0, high=2 * np.pi, size=param_shape, requires_grad=True) - -# Optimizing using exact gradient descent - -cost_GD = [] -params_GD = init_params -opt = qml.GradientDescentOptimizer(eta) - -for _ in range(steps): - cost_GD.append(qnode_analytic(params_GD)) - params_GD = opt.step(qnode_analytic, params_GD) - -# Optimizing using stochastic gradient descent with shots=1 - -cost_SGD1 = [] -params_SGD1 = init_params -opt = qml.GradientDescentOptimizer(eta) - -for _ in range(steps): - cost_SGD1.append(qnode_stochastic(params_SGD1, shots=1)) - params_SGD1 = opt.step(qnode_stochastic, params_SGD1, shots=1) - -# Optimizing using stochastic gradient descent with shots=100 - -cost_SGD100 = [] -params_SGD100 = init_params -opt = qml.GradientDescentOptimizer(eta) - -for _ in range(steps): - cost_SGD100.append(qnode_stochastic(params_SGD100, shots=100)) - params_SGD100 = opt.step(qnode_stochastic, params_SGD100, shots=100) - -############################################################################## -# Note that in the latter two cases we are sampling from an unbiased -# estimator of the cost function, not the analytic cost function. -# -# To track optimization convergence, approaches could include: -# -# * Evaluating the cost function with a larger number of samples at specified -# intervals, -# -# * Keeping track of the *moving average* of the low-shot cost evaluations. -# -# We can now plot the cost against optimization step for the three cases above. - -from matplotlib import pyplot as plt - -plt.style.use("seaborn") -plt.plot(cost_GD[:100], label="Vanilla gradient descent") -plt.plot(cost_SGD100[:100], "--", label="QSGD (100 shots)") -plt.plot(cost_SGD1[:100], ".", label="QSGD (1 shot)") - -# analytic ground state -min_energy = min(np.linalg.eigvalsh(H)) -plt.hlines(min_energy, 0, 100, linestyles=":", label="Ground-state energy") - -plt.ylabel("Cost function value") -plt.xlabel("Optimization steps") -plt.legend() -plt.show() - -############################################################################## -# Using the trained parameters from each optimization strategy, we can -# evaluate the analytic quantum device: - -print("Vanilla gradient descent min energy = ", qnode_analytic(params_GD)) -print( - "Stochastic gradient descent (shots=100) min energy = ", - qnode_analytic(params_SGD100), -) -print( - "Stochastic gradient descent (shots=1) min energy = ", qnode_analytic(params_SGD1) -) - - -############################################################################## -# Amazingly, we see that even the ``shots=1`` optimization converged -# to a reasonably close approximation of the ground-state energy! - -############################################################################## -# Doubly stochastic gradient descent for VQE -# ------------------------------------------ -# -# As noted in `Sweke et al. (2019) `__, -# variational quantum algorithms often include terms consisting of linear combinations -# of expectation values. This is true of the parameter-shift rule (where the -# gradient of each parameter is determined by shifting the parameter by macroscopic -# amounts and taking the difference), as well as VQE, where the Hamiltonian -# is usually decomposed into a sum of Pauli expectation values. -# -# Consider the Hamiltonian from the previous section. As this Hamiltonian is a -# Hermitian observable, we can always express it as a sum of Pauli matrices using -# the relation -# -# .. math:: -# -# H = \sum_{i,j=0,1,2,3} a_{i,j} (\sigma_i\otimes \sigma_j), -# -# where -# -# .. math:: -# -# a_{i,j} = \frac{1}{4}\text{tr}[(\sigma_i\otimes \sigma_j )H], ~~ \sigma = \{I, X, Y, Z\}. -# -# Applying this, we can see that -# -# .. math:: -# -# H = 4 + 2I\otimes X + 4I \otimes Z - X\otimes X + 5 Y\otimes Y + 2Z\otimes X. -# -# To perform "doubly stochastic" gradient descent, we simply apply the stochastic -# gradient descent approach from above, but in addition also uniformly sample -# a subset of the terms for the Hamiltonian expectation at each optimization step. -# This inserts another element of stochasticity into the system—all the while -# convergence continues to be guaranteed! -# -# Let's create a QNode that randomly samples a single term from the above -# Hamiltonian as the observable to be measured. - -I = np.identity(2) -X = np.array([[0, 1], [1, 0]]) -Y = np.array([[0, -1j], [1j, 0]]) -Z = np.array([[1, 0], [0, -1]]) - -terms = np.array( - [ - 2 * np.kron(I, X), - 4 * np.kron(I, Z), - -np.kron(X, X), - 5 * np.kron(Y, Y), - 2 * np.kron(Z, X), - ] -) - - -@qml.qnode(dev_stochastic, interface="autograd") -def circuit(params, n=None): - StronglyEntanglingLayers(weights=params, wires=[0, 1]) - idx = np.random.choice(np.arange(5), size=n, replace=False) - A = np.sum(terms[idx], axis=0) - return expval(qml.Hermitian(A, wires=[0, 1])) - - -def loss(params, shots=None): - return 4 + (5 / 1) * circuit(params, shots=shots, n=1) - - -############################################################################## -# Optimizing the circuit using gradient descent via the parameter-shift rule: - -cost = [] -params = init_params -opt = qml.GradientDescentOptimizer(0.005) - -for _ in range(250): - cost.append(loss(params, shots=100)) - params = opt.step(loss, params, shots=100) - - -############################################################################## -# During doubly stochastic gradient descent, we are sampling from terms of the -# analytic cost function, so it is not entirely instructive to plot the cost -# versus optimization step—partial sums of the terms in the Hamiltonian -# may have minimum energy below the ground state energy of the total Hamiltonian. -# Nevertheless, we can keep track of the cost value moving average during doubly -# stochastic gradient descent as an indicator of convergence. - - -def moving_average(data, n=3): - ret = np.cumsum(data, dtype=np.float64) - ret[n:] = ret[n:] - ret[:-n] - return ret[n - 1:] / n - - -average = np.vstack([np.arange(25, 200), moving_average(cost, n=50)[:-26]]) - -plt.plot(cost_GD, label="Vanilla gradient descent") -plt.plot(cost, ".", label="Doubly QSGD") -plt.plot(average[0], average[1], "--", label="Doubly QSGD (moving average)") -plt.hlines(min_energy, 0, 200, linestyles=":", label="Ground state energy") - -plt.ylabel("Cost function value") -plt.xlabel("Optimization steps") -plt.xlim(-2, 200) -plt.legend() -plt.show() - -############################################################################## -# Finally, verifying that the doubly stochastic gradient descent optimization -# correctly provides the ground state energy when evaluated for a larger -# number of shots: - -print("Doubly stochastic gradient descent min energy = ", qnode_analytic(params)) - -############################################################################## -# While stochastic gradient descent requires more optimization steps to achieve -# convergence, it is worth noting that it requires significantly fewer quantum -# device evaluations, and thus may as a result take less time overall. - -############################################################################## -# Adaptive stochasticity -# ---------------------- -# -# To improve on the convergence, we may even consider a crude "adaptive" modification -# of the doubly stochastic gradient descent optimization performed above. In this -# approach, we successively increase the number of terms we are sampling from as -# the optimization proceeds, as well as increasing the number of shots. - -cost = [] -params = init_params -opt = qml.GradientDescentOptimizer(0.005) - -for i in range(250): - n = min(i // 25 + 1, 5) - - def loss(params, shots=None): - return 4 + (5 / n) * circuit(params, shots=shots, n=n) - - cost.append(loss(params, shots=int(1 + (n - 1) ** 2))) - params = opt.step(loss, params, shots=int(1 + (n - 1) ** 2)) - -average = np.vstack([np.arange(25, 200), moving_average(cost, n=50)[:-26]]) - -plt.plot(cost_GD, label="Vanilla gradient descent") -plt.plot(cost, ".", label="Adaptive QSGD") -plt.plot(average[0], average[1], "--", label="Adaptive QSGD (moving average)") -plt.hlines(min_energy, 0, 250, linestyles=":", label="Ground state energy") - -plt.ylabel("Cost function value") -plt.xlabel("Optimization steps") -plt.xlim(-2, 200) -plt.legend() -plt.show() - -print("Adaptive QSGD min energy = ", qnode_analytic(params)) - -############################################################################## -# References -# ---------- -# -# 1. Ryan Sweke, Frederik Wilde, Johannes Jakob Meyer, Maria Schuld, Paul K. Fährmann, -# Barthélémy Meynard-Piganeau, Jens Eisert. "Stochastic gradient descent for -# hybrid quantum-classical optimization." `arXiv:1910.01155 -# `__, 2019. -# -# -# About the author -# ---------------- +r""" +Doubly stochastic gradient descent +================================== + +.. meta:: + :property="og:description": Minimize a Hamiltonian via an adaptive shot optimization + strategy with doubly stochastic gradient descent. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/single_shot.png + +.. related:: + + tutorial_backprop Quantum gradients with backpropagation + tutorial_quantum_natural_gradient Quantum natural gradient + tutorial_rosalin Frugal shot optimization with Rosalin + +*Author: Josh Izaac — Posted: 16 October 2019. Last updated: 20 January 2021.* + +In this tutorial we investigate and implement the doubly stochastic gradient descent +paper from `Ryan Sweke et al. (2019) `__. In this paper, +it is shown that quantum gradient descent, where a finite number of measurement samples +(or *shots*) are used to estimate the gradient, is a form of stochastic gradient descent. +Furthermore, if the optimization involves a linear combination of expectation values +(such as VQE), sampling from the terms in this linear combination can further reduce required +resources, allowing for "doubly stochastic gradient descent". + +Note that based on very similar observations, `Jonas Kuebler et al. (2019) `_ +recently proposed an optimizer (which they call the *individual Coupled Adaptive +Number of Shots (iCANS)* optimizer) that adapts the shot number of +measurements during training. + +Background +---------- + +In classical machine learning, `stochastic gradient descent +`_ is a common optimization strategy +where the standard gradient descent parameter update rule, + +.. math:: \theta^{(t+1)} = \theta^{(t)} - \eta \nabla \mathcal{L}(\theta^{(t)}), + +is modified such that + +.. math:: \theta^{(t+1)} = \theta^{(t)} - \eta g^{(t)}(\theta^{(t)}) + +where :math:`\eta` is the step-size, and :math:`\{g^{(t)}(\theta)\}` is a sequence of random +variables such that + +.. math:: \mathbb{E}[g^{(t)}(\theta)] = \nabla\mathcal{L}(\theta). + +In general, stochastic gradient descent is preferred over standard gradient +descent for several reasons: + +1. Samples of the gradient estimator :math:`g^{(t)}(\theta)` can typically + be computed much more efficiently than :math:`\mathcal{L}(\theta),` + +2. Stochasticity can help to avoid local minima and saddle points, + +3. Numerical evidence shows that convergence properties are superior to regular gradient descent. + +In variational quantum algorithms, a parametrized quantum circuit :math:`U(\theta)` +is optimized by a classical optimization loop in order to minimize a function of the expectation +values. For example, consider the expectation values + +.. math:: \langle A_i \rangle = \langle 0 | U(\theta)^\dagger A_i U(\theta) | 0\rangle + +for a set of observables :math:`\{A_i\},` and loss function + +.. math:: \mathcal{L}(\theta, \langle A_1 \rangle, \dots, \langle A_M \rangle). + +While the expectation values can be calculated analytically in classical simulations, +on quantum hardware we are limited to *sampling* from the expectation values; as the +number of samples (or shots) increase, we converge on the analytic expectation value, but can +never recover the exact expression. Furthermore, the parameter-shift rule +(`Schuld et al., 2018 `__) allows for analytic +quantum gradients to be computed from a linear combination of the variational circuits' +expectation values. + +Putting these two results together, `Sweke et al. (2019) `__ +show that samples of the expectation value fed into the parameter-shift rule provide +unbiased estimators of the quantum gradient—resulting in a form of stochastic gradient descent +(referred to as QSGD). Moreover, they show that convergence of the stochastic gradient +descent is guaranteed in sufficiently simplified settings, even in the case where the number +of shots is 1! + +.. note:: + + It is worth noting that the smaller the number of shots used, the larger the + variance in the estimated expectation value. As a result, it may take + more optimization steps for convergence than using a larger number of shots, + or an exact value. + + At the same time, a reduced number of shots may significantly reduce the + wall time of each optimization step, leading to a reduction in the overall + optimization time. + +""" + +############################################################################## +# Let's consider a simple example in PennyLane, comparing analytic gradient +# descent (with exact expectation values) to stochastic gradient descent +# using a finite number of shots. +# +# A single-shot stochastic gradient descent +# ----------------------------------------- +# +# Consider the Hamiltonian +# +# .. math:: +# +# H = \begin{bmatrix} +# 8 & 4 & 0 & -6\\ +# 4 & 0 & 4 & 0\\ +# 0 & 4 & 8 & 0\\ +# -6 & 0 & 0 & 0 +# \end{bmatrix}. +# +# We can solve for the ground state energy using +# the variational quantum eigensolver (VQE) algorithm. +# +# Let's use the ``lightning.qubit`` simulator for both the analytic gradient, +# as well as the estimated gradient using number of shots :math:`N\in\{1, 100\}.` + +import pennylane as qml +import numpy as np +from pennylane import numpy as pnp + +np.random.seed(3) + +from pennylane import expval +from pennylane.templates.layers import StronglyEntanglingLayers + +num_layers = 2 +num_wires = 2 +eta = 0.01 +steps = 200 + +dev_analytic = qml.device("lightning.qubit", wires=num_wires, shots=None) +dev_stochastic = qml.device("lightning.qubit", wires=num_wires, shots=1000) + +############################################################################## +# We can use ``qml.Hermitian`` to directly specify that we want to measure +# the expectation value of the matrix :math:`H:` + +H = np.array([[8, 4, 0, -6], [4, 0, 4, 0], [0, 4, 8, 0], [-6, 0, 0, 0]]) + + +def circuit(params): + StronglyEntanglingLayers(weights=params, wires=[0, 1]) + return expval(qml.Hermitian(H, wires=[0, 1])) + + +############################################################################## +# Now, we create three QNodes, each corresponding to a device above, +# and optimize them using gradient descent via the parameter-shift rule. + +qnode_analytic = qml.QNode(circuit, dev_analytic, interface="autograd", diff_method="parameter-shift") +qnode_stochastic = qml.QNode(circuit, dev_stochastic, interface="autograd", diff_method="parameter-shift") + +param_shape = StronglyEntanglingLayers.shape(n_layers=num_layers, n_wires=num_wires) +init_params = pnp.random.uniform(low=0, high=2 * np.pi, size=param_shape, requires_grad=True) + +# Optimizing using exact gradient descent + +cost_GD = [] +params_GD = init_params +opt = qml.GradientDescentOptimizer(eta) + +for _ in range(steps): + cost_GD.append(qnode_analytic(params_GD)) + params_GD = opt.step(qnode_analytic, params_GD) + +# Optimizing using stochastic gradient descent with shots=1 + +cost_SGD1 = [] +params_SGD1 = init_params +opt = qml.GradientDescentOptimizer(eta) + +for _ in range(steps): + cost_SGD1.append(qnode_stochastic(params_SGD1, shots=1)) + params_SGD1 = opt.step(qnode_stochastic, params_SGD1, shots=1) + +# Optimizing using stochastic gradient descent with shots=100 + +cost_SGD100 = [] +params_SGD100 = init_params +opt = qml.GradientDescentOptimizer(eta) + +for _ in range(steps): + cost_SGD100.append(qnode_stochastic(params_SGD100, shots=100)) + params_SGD100 = opt.step(qnode_stochastic, params_SGD100, shots=100) + +############################################################################## +# Note that in the latter two cases we are sampling from an unbiased +# estimator of the cost function, not the analytic cost function. +# +# To track optimization convergence, approaches could include: +# +# * Evaluating the cost function with a larger number of samples at specified +# intervals, +# +# * Keeping track of the *moving average* of the low-shot cost evaluations. +# +# We can now plot the cost against optimization step for the three cases above. + +from matplotlib import pyplot as plt + +plt.style.use("seaborn") +plt.plot(cost_GD[:100], label="Vanilla gradient descent") +plt.plot(cost_SGD100[:100], "--", label="QSGD (100 shots)") +plt.plot(cost_SGD1[:100], ".", label="QSGD (1 shot)") + +# analytic ground state +min_energy = min(np.linalg.eigvalsh(H)) +plt.hlines(min_energy, 0, 100, linestyles=":", label="Ground-state energy") + +plt.ylabel("Cost function value") +plt.xlabel("Optimization steps") +plt.legend() +plt.show() + +############################################################################## +# Using the trained parameters from each optimization strategy, we can +# evaluate the analytic quantum device: + +print("Vanilla gradient descent min energy = ", qnode_analytic(params_GD)) +print( + "Stochastic gradient descent (shots=100) min energy = ", + qnode_analytic(params_SGD100), +) +print( + "Stochastic gradient descent (shots=1) min energy = ", qnode_analytic(params_SGD1) +) + + +############################################################################## +# Amazingly, we see that even the ``shots=1`` optimization converged +# to a reasonably close approximation of the ground-state energy! + +############################################################################## +# Doubly stochastic gradient descent for VQE +# ------------------------------------------ +# +# As noted in `Sweke et al. (2019) `__, +# variational quantum algorithms often include terms consisting of linear combinations +# of expectation values. This is true of the parameter-shift rule (where the +# gradient of each parameter is determined by shifting the parameter by macroscopic +# amounts and taking the difference), as well as VQE, where the Hamiltonian +# is usually decomposed into a sum of Pauli expectation values. +# +# Consider the Hamiltonian from the previous section. As this Hamiltonian is a +# Hermitian observable, we can always express it as a sum of Pauli matrices using +# the relation +# +# .. math:: +# +# H = \sum_{i,j=0,1,2,3} a_{i,j} (\sigma_i\otimes \sigma_j), +# +# where +# +# .. math:: +# +# a_{i,j} = \frac{1}{4}\text{tr}[(\sigma_i\otimes \sigma_j )H], ~~ \sigma = \{I, X, Y, Z\}. +# +# Applying this, we can see that +# +# .. math:: +# +# H = 4 + 2I\otimes X + 4I \otimes Z - X\otimes X + 5 Y\otimes Y + 2Z\otimes X. +# +# To perform "doubly stochastic" gradient descent, we simply apply the stochastic +# gradient descent approach from above, but in addition also uniformly sample +# a subset of the terms for the Hamiltonian expectation at each optimization step. +# This inserts another element of stochasticity into the system—all the while +# convergence continues to be guaranteed! +# +# Let's create a QNode that randomly samples a single term from the above +# Hamiltonian as the observable to be measured. + +I = np.identity(2) +X = np.array([[0, 1], [1, 0]]) +Y = np.array([[0, -1j], [1j, 0]]) +Z = np.array([[1, 0], [0, -1]]) + +terms = np.array( + [ + 2 * np.kron(I, X), + 4 * np.kron(I, Z), + -np.kron(X, X), + 5 * np.kron(Y, Y), + 2 * np.kron(Z, X), + ] +) + + +@qml.qnode(dev_stochastic, interface="autograd") +def circuit(params, n=None): + StronglyEntanglingLayers(weights=params, wires=[0, 1]) + idx = np.random.choice(np.arange(5), size=n, replace=False) + A = np.sum(terms[idx], axis=0) + return expval(qml.Hermitian(A, wires=[0, 1])) + + +def loss(params, shots=None): + return 4 + (5 / 1) * circuit(params, shots=shots, n=1) + + +############################################################################## +# Optimizing the circuit using gradient descent via the parameter-shift rule: + +cost = [] +params = init_params +opt = qml.GradientDescentOptimizer(0.005) + +for _ in range(250): + cost.append(loss(params, shots=100)) + params = opt.step(loss, params, shots=100) + + +############################################################################## +# During doubly stochastic gradient descent, we are sampling from terms of the +# analytic cost function, so it is not entirely instructive to plot the cost +# versus optimization step—partial sums of the terms in the Hamiltonian +# may have minimum energy below the ground state energy of the total Hamiltonian. +# Nevertheless, we can keep track of the cost value moving average during doubly +# stochastic gradient descent as an indicator of convergence. + + +def moving_average(data, n=3): + ret = np.cumsum(data, dtype=np.float64) + ret[n:] = ret[n:] - ret[:-n] + return ret[n - 1:] / n + + +average = np.vstack([np.arange(25, 200), moving_average(cost, n=50)[:-26]]) + +plt.plot(cost_GD, label="Vanilla gradient descent") +plt.plot(cost, ".", label="Doubly QSGD") +plt.plot(average[0], average[1], "--", label="Doubly QSGD (moving average)") +plt.hlines(min_energy, 0, 200, linestyles=":", label="Ground state energy") + +plt.ylabel("Cost function value") +plt.xlabel("Optimization steps") +plt.xlim(-2, 200) +plt.legend() +plt.show() + +############################################################################## +# Finally, verifying that the doubly stochastic gradient descent optimization +# correctly provides the ground state energy when evaluated for a larger +# number of shots: + +print("Doubly stochastic gradient descent min energy = ", qnode_analytic(params)) + +############################################################################## +# While stochastic gradient descent requires more optimization steps to achieve +# convergence, it is worth noting that it requires significantly fewer quantum +# device evaluations, and thus may as a result take less time overall. + +############################################################################## +# Adaptive stochasticity +# ---------------------- +# +# To improve on the convergence, we may even consider a crude "adaptive" modification +# of the doubly stochastic gradient descent optimization performed above. In this +# approach, we successively increase the number of terms we are sampling from as +# the optimization proceeds, as well as increasing the number of shots. + +cost = [] +params = init_params +opt = qml.GradientDescentOptimizer(0.005) + +for i in range(250): + n = min(i // 25 + 1, 5) + + def loss(params, shots=None): + return 4 + (5 / n) * circuit(params, shots=shots, n=n) + + cost.append(loss(params, shots=int(1 + (n - 1) ** 2))) + params = opt.step(loss, params, shots=int(1 + (n - 1) ** 2)) + +average = np.vstack([np.arange(25, 200), moving_average(cost, n=50)[:-26]]) + +plt.plot(cost_GD, label="Vanilla gradient descent") +plt.plot(cost, ".", label="Adaptive QSGD") +plt.plot(average[0], average[1], "--", label="Adaptive QSGD (moving average)") +plt.hlines(min_energy, 0, 250, linestyles=":", label="Ground state energy") + +plt.ylabel("Cost function value") +plt.xlabel("Optimization steps") +plt.xlim(-2, 200) +plt.legend() +plt.show() + +print("Adaptive QSGD min energy = ", qnode_analytic(params)) + +############################################################################## +# References +# ---------- +# +# 1. Ryan Sweke, Frederik Wilde, Johannes Jakob Meyer, Maria Schuld, Paul K. Fährmann, +# Barthélémy Meynard-Piganeau, Jens Eisert. "Stochastic gradient descent for +# hybrid quantum-classical optimization." `arXiv:1910.01155 +# `__, 2019. +# +# +# About the author +# ---------------- # \ No newline at end of file diff --git a/demonstrations_v2/tutorial_fermionic_operators/demo.py b/demonstrations_v2/tutorial_fermionic_operators/demo.py index d06f98aafb..673c5a705a 100644 --- a/demonstrations_v2/tutorial_fermionic_operators/demo.py +++ b/demonstrations_v2/tutorial_fermionic_operators/demo.py @@ -1,231 +1,231 @@ -r""" - -Fermionic operators -=================== - -.. meta:: - :property="og:description": Learn how to work with fermionic operators - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_fermionic_operators.png - -.. related:: - tutorial_quantum_chemistry Building molecular Hamiltonians - tutorial_vqe A brief overview of VQE - -*Author: Soran Jahangiri — Posted: 27 June 2023. Last updated: 27 June 2023.* - -Fermionic creation and annihilation operators are commonly used to construct -`Hamiltonians `_ and other observables of molecules and spin -systems [#surjan]_. In this demo, you will learn how to use PennyLane to create fermionic operators -and map them to a qubit representation for use in quantum algorithms. - -Constructing fermionic operators --------------------------------- -The fermionic `creation and annihilation `_ -operators can be constructed in PennyLane similarly to Pauli operators by using -:class:`~.pennylane.FermiC` and :class:`~.pennylane.FermiA` for creation and annihilation operators, -respectively. -""" - -from pennylane import FermiC, FermiA - -a0_dag = FermiC(0) -a1 = FermiA(1) - -############################################################################## -# We used the compact notations ``a0_dag`` to denote a creation operator applied to -# the :math:`0\text{th}` orbital and ``a1`` to denote an annihilation operator applied to the -# :math:`1\text{st}` orbital. Once created, these operators can be multiplied or added to each other -# to create new operators. A product of fermionic operators will be called a *Fermi word* and a -# linear combination of Fermi words will be called a *Fermi sentence*. - -fermi_word = a0_dag * a1 -fermi_sentence = 1.3 * a0_dag * a1 + 2.4 * a1 -print(fermi_sentence) - -############################################################################## -# In this simple example, we first created the operator :math:`a^{\dagger}_0 a_1` and then created -# the linear combination :math:`1.3 a^{\dagger}_0 a_1 + 2.4 a_1.` We can also perform -# arithmetic operations between Fermi words and Fermi sentences. - -fermi_sentence = fermi_sentence * fermi_word + 2.3 * fermi_word -print(fermi_sentence) - -############################################################################## -# Beyond multiplication, summation, and subtraction, we can exponentiate fermionic operators in -# PennyLane to an integer power. For instance, we can create a more complicated operator -# -# .. math:: -# -# 1.2 \times a_0^{\dagger} + 0.5 \times a_1 - 2.3 \times \left ( a_0^{\dagger} a_1 \right )^2, -# -# in the same way that you would write down the operator on a piece of paper: - -fermi_sentence = 1.2 * a0_dag + 0.5 * a1 - 2.3 * (a0_dag * a1) ** 2 -print(fermi_sentence) - -############################################################################## -# This Fermi sentence can be mapped to the qubit basis using the -# `Jordan-Wigner `_ -# transformation to get a linear combination of Pauli operators. - -from pennylane import jordan_wigner - -pauli_sentence = jordan_wigner(fermi_sentence) -pauli_sentence - -############################################################################## -# Fermionic Hamiltonians -# ---------------------- -# Now that we have nice tools to create and manipulate fermionic operators, we can build some -# interesting fermionic Hamiltonians. -# -# A toy model -# ^^^^^^^^^^^ -# Our first example is a toy Hamiltonian inspired by the -# `Hückel method `_, which is a method for -# describing molecules with alternating single and double bonds. Our toy model is a simplified -# version of the Hückel Hamiltonian and assumes only two orbitals and a single electron. -# -# .. math:: -# -# H = \alpha \left (a^{\dagger}_0 a_0 + a^{\dagger}_1 a_1 \right ) + -# \beta \left (a^{\dagger}_0 a_1 + a^{\dagger}_1 a_0 \right ). -# -# This Hamiltonian can be constructed with pre-defined values :math:`\alpha = 0.01` and -# :math:`\beta = -0.02.` - -h1 = 0.01 * (FermiC(0) * FermiA(0) + FermiC(1) * FermiA(1)) -h2 = -0.02 * (FermiC(0) * FermiA(1) + FermiC(1) * FermiA(0)) -h = h1 + h2 -print(h) - -############################################################################## -# The fermionic Hamiltonian can be converted to the qubit Hamiltonian with: - -h = jordan_wigner(h) - -############################################################################## -# The matrix representation of the qubit Hamiltonian in the computational basis can be diagonalized -# to get its eigenpairs. - -import numpy as np - -val, vec = np.linalg.eigh(h.sparse_matrix().toarray()) -print(f"eigenvalues:\n{val}") -print() -print(f"eigenvectors:\n{np.real(vec.T)}") - -############################################################################## -# -# Hydrogen molecule -# ^^^^^^^^^^^^^^^^^ -# The `second quantized `_ molecular electronic -# Hamiltonian is usually constructed as -# -# .. math:: -# H = \sum_{\alpha \in \{\uparrow, \downarrow \} } \sum_{pq} c_{pq} a_{p,\alpha}^{\dagger} -# a_{q, \alpha} + \frac{1}{2} \sum_{\alpha, \beta \in \{\uparrow, \downarrow \} } \sum_{pqrs} -# c_{pqrs} a_{p, \alpha}^{\dagger} a_{q, \beta}^{\dagger} a_{r, \beta} a_{s, \alpha}, -# -# where :math:`\alpha` and :math:`\beta` denote the electron spin and :math:`p, q, r, s` are the -# orbital indices. The coefficients :math:`c` are integrals over -# molecular orbitals that are obtained from -# `Hartree-Fock `_ -# calculations. These integrals can be computed with PennyLane using the -# :func:`~.pennylane.qchem.electron_integrals` function. We can build the molecular Hamiltonian for -# the hydrogen molecule as an example. We first define the atom types and the atomic coordinates. - -import pennylane as qml -from jax import numpy as jnp - -symbols = ["H", "H"] -geometry = jnp.array([[-0.67294, 0.0, 0.0], [0.67294, 0.0, 0.0]]) - -############################################################################## -# Then we compute the one- and two-electron integrals, which are the coefficients :math:`c` in the -# second quantized molecular Hamiltonian defined above. We also obtain the core constant, which is -# later used to calculate the contribution of the nuclear energy to the Hamiltonian. - -mol = qml.qchem.Molecule(symbols, geometry) -core, one, two = qml.qchem.electron_integrals(mol)() - -############################################################################## -# These integrals are computed over molecular orbitals. Each molecular orbital contains a pair of -# electrons with different spins. We have assumed that the spatial distribution of these electron -# pairs is the same to simplify the calculation of the integrals. However, to properly account for -# all electrons, we need to duplicate the integrals for electrons with the same spin. For example, -# the :math:`pq` integral, which is the integral over the orbital :math:`p` and the orbital -# :math:`q,` can be used -# for both spin-up and spin-down electrons. Then, if we have a :math:`2 \times 2` matrix of such -# integrals, it will become a :math:`4 \times 4` matrix. The code block below simply extends the -# integrals by duplicating terms to account for both spin-up and spin-down electrons. - -for i in range(4): - if i < 2: - one = one.repeat(2, axis=i) - two = two.repeat(2, axis=i) - -############################################################################## -# We can now construct the fermionic Hamiltonian for the hydrogen molecule. The one-body terms, -# which are the first part in the Hamiltonian above, can be added first. We will use -# `itertools `_ to efficiently -# create all the combinations we need. Some of these combinations are not allowed because of spin -# restrictions and we need to exclude them. You can find more details about -# constructing a molecular Hamiltonian in reference [#surjan]_. - -import itertools - -n = one.shape[0] - -h = 0.0 - -for p, q in itertools.product(range(n), repeat=2): - if p % 2 == q % 2: # to account for spin-forbidden terms - h += one[p, q] * FermiC(p) * FermiA(q) - -############################################################################## -# The two-body terms can be added with: - -for p, q, r, s in itertools.product(range(n), repeat=4): - if p % 2 == s % 2 and q % 2 == r % 2: # to account for spin-forbidden terms - h += two[p, q, r, s] / 2 * FermiC(p) * FermiC(q) * FermiA(r) * FermiA(s) - -############################################################################## -# We then simplify the Hamiltonian to remove terms with negligible coefficients and then map it to -# the qubit basis. - -h.simplify() -h = jordan_wigner(h) - -############################################################################## -# We also need to include the contribution of the nuclear energy. - -h += np.sum(core * qml.Identity(0)) - -############################################################################## -# This gives us the qubit Hamiltonian which can be used as an input for quantum algorithms. We can -# also compute the ground-state energy by diagonalizing the matrix representation of the -# Hamiltonian in the computational basis. - -np.linalg.eigh(h.sparse_matrix().toarray())[0].min() - -############################################################################## -# Summary -# ------- -# This demo explains how to create and manipulate fermionic operators in PennyLane, which is as -# easy as writing the operators on paper. PennyLane supports several arithmetic operations between -# fermionic operators and provides tools for mapping them to the qubit basis. This makes it easy and -# intuitive to construct complicated fermionic Hamiltonians such as -# `molecular Hamiltonians `_. -# -# References -# ---------- -# -# .. [#surjan] -# -# Peter R. Surjan, "Second Quantized Approach to Quantum Chemistry". Springer-Verlag, 1989. -# -# -# About the author -# ---------------- -# +r""" + +Fermionic operators +=================== + +.. meta:: + :property="og:description": Learn how to work with fermionic operators + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_fermionic_operators.png + +.. related:: + tutorial_quantum_chemistry Building molecular Hamiltonians + tutorial_vqe A brief overview of VQE + +*Author: Soran Jahangiri — Posted: 27 June 2023. Last updated: 27 June 2023.* + +Fermionic creation and annihilation operators are commonly used to construct +`Hamiltonians `_ and other observables of molecules and spin +systems [#surjan]_. In this demo, you will learn how to use PennyLane to create fermionic operators +and map them to a qubit representation for use in quantum algorithms. + +Constructing fermionic operators +-------------------------------- +The fermionic `creation and annihilation `_ +operators can be constructed in PennyLane similarly to Pauli operators by using +:class:`~.pennylane.FermiC` and :class:`~.pennylane.FermiA` for creation and annihilation operators, +respectively. +""" + +from pennylane import FermiC, FermiA + +a0_dag = FermiC(0) +a1 = FermiA(1) + +############################################################################## +# We used the compact notations ``a0_dag`` to denote a creation operator applied to +# the :math:`0\text{th}` orbital and ``a1`` to denote an annihilation operator applied to the +# :math:`1\text{st}` orbital. Once created, these operators can be multiplied or added to each other +# to create new operators. A product of fermionic operators will be called a *Fermi word* and a +# linear combination of Fermi words will be called a *Fermi sentence*. + +fermi_word = a0_dag * a1 +fermi_sentence = 1.3 * a0_dag * a1 + 2.4 * a1 +print(fermi_sentence) + +############################################################################## +# In this simple example, we first created the operator :math:`a^{\dagger}_0 a_1` and then created +# the linear combination :math:`1.3 a^{\dagger}_0 a_1 + 2.4 a_1.` We can also perform +# arithmetic operations between Fermi words and Fermi sentences. + +fermi_sentence = fermi_sentence * fermi_word + 2.3 * fermi_word +print(fermi_sentence) + +############################################################################## +# Beyond multiplication, summation, and subtraction, we can exponentiate fermionic operators in +# PennyLane to an integer power. For instance, we can create a more complicated operator +# +# .. math:: +# +# 1.2 \times a_0^{\dagger} + 0.5 \times a_1 - 2.3 \times \left ( a_0^{\dagger} a_1 \right )^2, +# +# in the same way that you would write down the operator on a piece of paper: + +fermi_sentence = 1.2 * a0_dag + 0.5 * a1 - 2.3 * (a0_dag * a1) ** 2 +print(fermi_sentence) + +############################################################################## +# This Fermi sentence can be mapped to the qubit basis using the +# `Jordan-Wigner `_ +# transformation to get a linear combination of Pauli operators. + +from pennylane import jordan_wigner + +pauli_sentence = jordan_wigner(fermi_sentence) +pauli_sentence + +############################################################################## +# Fermionic Hamiltonians +# ---------------------- +# Now that we have nice tools to create and manipulate fermionic operators, we can build some +# interesting fermionic Hamiltonians. +# +# A toy model +# ^^^^^^^^^^^ +# Our first example is a toy Hamiltonian inspired by the +# `Hückel method `_, which is a method for +# describing molecules with alternating single and double bonds. Our toy model is a simplified +# version of the Hückel Hamiltonian and assumes only two orbitals and a single electron. +# +# .. math:: +# +# H = \alpha \left (a^{\dagger}_0 a_0 + a^{\dagger}_1 a_1 \right ) + +# \beta \left (a^{\dagger}_0 a_1 + a^{\dagger}_1 a_0 \right ). +# +# This Hamiltonian can be constructed with pre-defined values :math:`\alpha = 0.01` and +# :math:`\beta = -0.02.` + +h1 = 0.01 * (FermiC(0) * FermiA(0) + FermiC(1) * FermiA(1)) +h2 = -0.02 * (FermiC(0) * FermiA(1) + FermiC(1) * FermiA(0)) +h = h1 + h2 +print(h) + +############################################################################## +# The fermionic Hamiltonian can be converted to the qubit Hamiltonian with: + +h = jordan_wigner(h) + +############################################################################## +# The matrix representation of the qubit Hamiltonian in the computational basis can be diagonalized +# to get its eigenpairs. + +import numpy as np + +val, vec = np.linalg.eigh(h.sparse_matrix().toarray()) +print(f"eigenvalues:\n{val}") +print() +print(f"eigenvectors:\n{np.real(vec.T)}") + +############################################################################## +# +# Hydrogen molecule +# ^^^^^^^^^^^^^^^^^ +# The `second quantized `_ molecular electronic +# Hamiltonian is usually constructed as +# +# .. math:: +# H = \sum_{\alpha \in \{\uparrow, \downarrow \} } \sum_{pq} c_{pq} a_{p,\alpha}^{\dagger} +# a_{q, \alpha} + \frac{1}{2} \sum_{\alpha, \beta \in \{\uparrow, \downarrow \} } \sum_{pqrs} +# c_{pqrs} a_{p, \alpha}^{\dagger} a_{q, \beta}^{\dagger} a_{r, \beta} a_{s, \alpha}, +# +# where :math:`\alpha` and :math:`\beta` denote the electron spin and :math:`p, q, r, s` are the +# orbital indices. The coefficients :math:`c` are integrals over +# molecular orbitals that are obtained from +# `Hartree-Fock `_ +# calculations. These integrals can be computed with PennyLane using the +# :func:`~.pennylane.qchem.electron_integrals` function. We can build the molecular Hamiltonian for +# the hydrogen molecule as an example. We first define the atom types and the atomic coordinates. + +import pennylane as qml +from jax import numpy as jnp + +symbols = ["H", "H"] +geometry = jnp.array([[-0.67294, 0.0, 0.0], [0.67294, 0.0, 0.0]]) + +############################################################################## +# Then we compute the one- and two-electron integrals, which are the coefficients :math:`c` in the +# second quantized molecular Hamiltonian defined above. We also obtain the core constant, which is +# later used to calculate the contribution of the nuclear energy to the Hamiltonian. + +mol = qml.qchem.Molecule(symbols, geometry) +core, one, two = qml.qchem.electron_integrals(mol)() + +############################################################################## +# These integrals are computed over molecular orbitals. Each molecular orbital contains a pair of +# electrons with different spins. We have assumed that the spatial distribution of these electron +# pairs is the same to simplify the calculation of the integrals. However, to properly account for +# all electrons, we need to duplicate the integrals for electrons with the same spin. For example, +# the :math:`pq` integral, which is the integral over the orbital :math:`p` and the orbital +# :math:`q,` can be used +# for both spin-up and spin-down electrons. Then, if we have a :math:`2 \times 2` matrix of such +# integrals, it will become a :math:`4 \times 4` matrix. The code block below simply extends the +# integrals by duplicating terms to account for both spin-up and spin-down electrons. + +for i in range(4): + if i < 2: + one = one.repeat(2, axis=i) + two = two.repeat(2, axis=i) + +############################################################################## +# We can now construct the fermionic Hamiltonian for the hydrogen molecule. The one-body terms, +# which are the first part in the Hamiltonian above, can be added first. We will use +# `itertools `_ to efficiently +# create all the combinations we need. Some of these combinations are not allowed because of spin +# restrictions and we need to exclude them. You can find more details about +# constructing a molecular Hamiltonian in reference [#surjan]_. + +import itertools + +n = one.shape[0] + +h = 0.0 + +for p, q in itertools.product(range(n), repeat=2): + if p % 2 == q % 2: # to account for spin-forbidden terms + h += one[p, q] * FermiC(p) * FermiA(q) + +############################################################################## +# The two-body terms can be added with: + +for p, q, r, s in itertools.product(range(n), repeat=4): + if p % 2 == s % 2 and q % 2 == r % 2: # to account for spin-forbidden terms + h += two[p, q, r, s] / 2 * FermiC(p) * FermiC(q) * FermiA(r) * FermiA(s) + +############################################################################## +# We then simplify the Hamiltonian to remove terms with negligible coefficients and then map it to +# the qubit basis. + +h.simplify() +h = jordan_wigner(h) + +############################################################################## +# We also need to include the contribution of the nuclear energy. + +h += np.sum(core * qml.Identity(0)) + +############################################################################## +# This gives us the qubit Hamiltonian which can be used as an input for quantum algorithms. We can +# also compute the ground-state energy by diagonalizing the matrix representation of the +# Hamiltonian in the computational basis. + +np.linalg.eigh(h.sparse_matrix().toarray())[0].min() + +############################################################################## +# Summary +# ------- +# This demo explains how to create and manipulate fermionic operators in PennyLane, which is as +# easy as writing the operators on paper. PennyLane supports several arithmetic operations between +# fermionic operators and provides tools for mapping them to the qubit basis. This makes it easy and +# intuitive to construct complicated fermionic Hamiltonians such as +# `molecular Hamiltonians `_. +# +# References +# ---------- +# +# .. [#surjan] +# +# Peter R. Surjan, "Second Quantized Approach to Quantum Chemistry". Springer-Verlag, 1989. +# +# +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_givens_rotations/demo.py b/demonstrations_v2/tutorial_givens_rotations/demo.py index 1c312bf402..9413006ffb 100644 --- a/demonstrations_v2/tutorial_givens_rotations/demo.py +++ b/demonstrations_v2/tutorial_givens_rotations/demo.py @@ -1,506 +1,506 @@ -r""" - -Givens rotations for quantum chemistry -====================================== - -.. meta:: - :property="og:description": Discover the building blocks of quantum circuits for - quantum chemistry - - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/Givens_rotations.png - -.. related:: - tutorial_quantum_chemistry Building molecular Hamiltonians - tutorial_vqe A brief overview of VQE - - -*Author: Juan Miguel Arrazola — Posted: 30 June 2021. Last updated: 30 June 2021.* - -In the book `"Sophie's world" `_, the young -protagonist receives a white envelope containing a letter -with an intriguing question: "Why is Lego the most ingenious toy in the world?" At first baffled by -this curious message, she decides to reflect on the question. As told by the book's narrator, -she arrives at a conclusion: - -*The best thing about them was that with Lego she could construct any kind of object. And then -she could separate the blocks and construct something new. What more could one ask of a toy? -Sophie decided that Lego really could be called the most ingenious toy in the world.* - - - -.. figure:: ../_static/demonstration_assets/givens_rotations/lego-circuit.svg - :align: center - :width: 50% - - -In this tutorial, you will learn about the building blocks of quantum circuits for quantum -chemistry: Givens rotations. These are operations that can be used to construct any kind of -particle-conserving circuit. We discuss single and double excitation gates, which are particular -types of Givens rotations that play an important role in quantum chemistry. Notably, -controlled single excitation gates are universal for particle-conserving unitaries. You will also -learn how to use these gates to build arbitrary states of a fixed number of particles. - -Particle-conserving unitaries ------------------------------ - -Understanding the electronic structure of molecules is arguably the central problem in quantum chemistry. -Quantum computers tackle this problem by using systems of qubits to -represent the quantum states of the electrons. One method is to consider a -collection of `molecular orbitals `_, which -capture the three-dimensional region of space occupied by the electrons. Each orbital can be -occupied by at most two electrons, each with a different spin orientation. In this case we refer to -*spin orbitals* that can be occupied by a single electron. - -The state of electrons in a molecule can then be described by specifying how the -orbitals are occupied. The `Jordan-Wigner representation -`_ provides a -convenient way to do this: we associate a qubit with each spin orbital and -use its states to represent occupied :math:`|1\rangle` or unoccupied -:math:`|0\rangle` spin orbitals. - -An :math:`n`-qubit state with `Hamming weight `_ -:math:`k`, i.e., with :math:`k` qubits in state :math:`|1\rangle,` represents a state of -:math:`k` electrons in :math:`n` spin orbitals. For example :math:`|1010\rangle` is a state of -two electrons in two spin orbitals. More generally, superpositions over all basis states with a -fixed number of particles are valid states of the electrons in a molecule. These are states such as - -.. math:: - - |\psi\rangle = c_1|1100\rangle + c_2|1010\rangle + c_3|1001\rangle + c_4|0110\rangle + - c_5|0101\rangle + c_6|0011\rangle, - -for some coefficients :math:`c_i.` - -.. figure:: ../_static/demonstration_assets/givens_rotations/orbitals_states.png - :align: center - :width: 50% - - States of a system with six spin orbitals and three electrons. Orbitals are for illustration; - they correspond to carbon dioxide, which has more electrons and orbitals. - -Because the number of electrons in a molecule is -fixed, any transformation must conserve the number of particles. We refer to these as -**particle-conserving unitaries**. When designing quantum circuits and algorithms for quantum -chemistry, it is desirable to employ only particle-conserving gates that guarantee that the -states of the system remain valid. This raises the questions: what are the simplest -particle-conserving unitaries? Like Legos, how can they be used to construct any quantum circuit for -quantum chemistry applications? - -Givens rotations ----------------- - -Consider single-qubit gates. In their most general form, they perform the transformation - -.. math:: - - U|0\rangle &= a |0\rangle + b |1\rangle,\\ - U|1\rangle &= c |1\rangle + d |0\rangle, - -where :math:`|a|^2+|b|^2=|c|^2+|d|^2=1` and :math:`ab^* + cd^*=0.` This gate is -particle-conserving only if :math:`b=d=0,` which means that the only single-qubit gates that -preserve particle number are diagonal gates of the form - -.. math:: - - U = \begin{pmatrix} - e^{i\theta} & 0\\ - 0 & e^{i\phi} - \end{pmatrix}. - -On their own, these gates are not very interesting. They can only be used to change the -relative phases of states in a superposition; they cannot be used to create and control such -superpositions. So let's take a look at two-qubit gates. - -Basis states of two qubits can be categorized depending on -their number of particles. - -We have: - -- :math:`|00\rangle` with zero particles, -- :math:`|01\rangle,|10\rangle` with one particle, and -- :math:`|11\rangle` with two particles. - -We can now consider transformations that couple the states :math:`|01\rangle,|10\rangle.` These -are gates of the form - -.. math:: - - U|01\rangle &= a |01\rangle + b |10\rangle\\ - U|10\rangle &= c |10\rangle + d |01\rangle. - -This should be familiar: the unitary has the same form as a single-qubit gate, except that the -states :math:`|01\rangle, |10\rangle` respectively take the place of :math:`|0\rangle, -|1\rangle`. This correspondence has a name: the `dual-rail qubit -`_, where a two-level system is constructed -by specifying in which of two possible systems a single particle is located. The -difference compared to single-qubit gates is that any values of the parameters :math:`a, -b,c,d` give rise to a valid particle-conserving unitary. Take for instance the two-qubit gate - -.. math:: - - G(\theta)=\begin{pmatrix} - 1 & 0 & 0 & 0\\ - 0 & \cos (\theta/2) & -\sin (\theta/2) & 0\\ - 0 & \sin(\theta/2) & \cos(\theta/2) & 0\\ - 0 & 0 & 0 & 1 - \end{pmatrix}. - - -This is an example of a `Givens rotation `_: a -rotation in a two-dimensional subspace of a larger Hilbert space. In this case, we are performing a -Givens rotation in a two-dimensional subspace of the four-dimensional space of two-qubit states. -This gate allows us to create superpositions by exchanging the particle -between the two qubits. Such transformations can be interpreted as a **single excitation**, -where we view the exchange from :math:`|10\rangle` to :math:`|01\rangle` as exciting the electron -from the first to the second qubit. - -.. figure:: ../_static/demonstration_assets/givens_rotations/Givens_rotations_1.png - :align: center - :width: 35% - - A Givens rotation can be used to couple states that differ by a single excitation. - -This gate is implemented in PennyLane as the :class:`~.pennylane.SingleExcitation` operation. -We can use it to prepare an equal superposition of three-qubit states with a single particle -:math:`\frac{1}{\sqrt{3}}(|001\rangle + |010\rangle + |100\rangle).` We apply two single -excitation gates with parameters :math:`\theta, \phi` to the initial state :math:`|100\rangle.` -The excitation gates act on qubits 0,1 and 0,2 respectively. This yields the state - -.. math:: - - |\psi\rangle = \cos(\theta/2)\cos(\phi/2)|100\rangle - \sin(\theta/2)|010\rangle - - \cos(\theta/2)\sin(\phi/2)|001\rangle. - -Since the amplitude of :math:`|010\rangle` must be :math:`1/\sqrt{3},` we conclude that -:math:`-\sin(\theta/2)=1/\sqrt{3}.` This in turn implies that :math:`\cos(\theta/2)=\sqrt{2/3}` and -therefore :math:`-\sin(\phi/2)=1/\sqrt{2}.` Thus, to prepare an equal superposition state we -choose the angles of rotation to be - -.. math:: - - \theta &= - 2 \arcsin(1/\sqrt{3}),\\ - \phi &= - 2 \arcsin(1/\sqrt{2}). - -This can be implemented in PennyLane as follows: -""" - -import pennylane as qml -from jax import numpy as jnp -import jax - -jax.config.update("jax_enable_x64", True) -dev = qml.device('lightning.qubit', wires=3) - -@qml.qnode(dev, interface="jax") -def circuit(x, y): - # prepares the reference state |100> - qml.BasisState(jnp.array([1, 0, 0]), wires=[0, 1, 2]) - # applies the single excitations - qml.SingleExcitation(x, wires=[0, 1]) - qml.SingleExcitation(y, wires=[0, 2]) - return qml.state() - -x = -2 * jnp.arcsin(jnp.sqrt(1/3)) -y = -2 * jnp.arcsin(jnp.sqrt(1/2)) -print(circuit(x, y)) - -############################################################################## -# The components of the output state are ordered according to their binary -# representation, so entry 1 is :math:`|001\rangle`, entry 2 is :math:`|010\rangle,` and entry 4 is -# :math:`|100\rangle,` meaning we indeed prepared the desired state. We can check this by -# reshaping the output state - -tensor_state = circuit(x, y).reshape(2, 2, 2) -print("Amplitude of state |001> = ", tensor_state[0, 0, 1]) -print("Amplitude of state |010> = ", tensor_state[0, 1, 0]) -print("Amplitude of state |100> = ", tensor_state[1, 0, 0]) - -############################################################################## -# We can also study **double excitations** involving the transfer of two particles. For example, -# consider a Givens rotation in the subspace spanned by the states -# :math:`|1100\rangle` and :math:`|0011\rangle.` These -# states differ by a double excitation since we can map :math:`|1100\rangle` to -# :math:`|0011\rangle` by exciting the particles from the first two qubits to the last two. -# Mathematically, this gate can be represented by a unitary :math:`G^{(2)}(\theta)` that performs -# the mapping -# -# .. math:: -# -# G^{(2)}|0011\rangle &= \cos (\theta/2)|0011\rangle + \sin (\theta/2)|1100\rangle\\ -# G^{(2)}|1100\rangle &= \cos (\theta/2)|1100\rangle - \sin (\theta/2)|0011\rangle, -# -# while leaving all other basis states unchanged. This gate is implemented in PennyLane as the -# :class:`~.pennylane.DoubleExcitation` operation. -# -# -# .. figure:: ../_static/demonstration_assets/givens_rotations/Givens_rotations_2.png -# :align: center -# :width: 35% -# -# A Givens rotation can also be used to couple states that differ by a double excitation. -# -# -# In the context of quantum chemistry, it is common to consider excitations on a fixed reference -# state and include only the excitations that preserve the spin orientation of the electron. -# For a system with :math:`n` qubits and :math:`k` particles, this reference state is typically -# chosen as the state with the first :math:`k` qubits in state :math:`|1\rangle,` and the remainder -# in state :math:`|0\rangle.` -# PennyLane allows you to obtain all such excitations using the function -# :func:`~.pennylane.qchem.excitations`. Let's employ it to build a circuit that includes -# all single and double excitations acting on a reference state of three particles in six qubits. -# We apply a random rotation for each gate: - -nr_particles = 3 -nr_qubits = 6 - -singles, doubles = qml.qchem.excitations(3, 6) -print(f"Single excitations = {singles}") -print(f"Double excitations = {doubles}") - -############################################################################## -# Now we continue to build the circuit: - -from jax import random - -dev2 = qml.device('lightning.qubit', wires=6) - -@qml.qnode(dev2, interface="jax") -def circuit2(x, y): - # prepares reference state - qml.BasisState(jnp.array([1, 1, 1, 0, 0, 0]), wires=[0, 1, 2, 3, 4, 5]) - # apply all single excitations - for i, s in enumerate(singles): - qml.SingleExcitation(x[i], wires=s) - # apply all double excitations - for j, d in enumerate(doubles): - qml.DoubleExcitation(y[j], wires=d) - return qml.state() - -# random angles of rotation -key = random.PRNGKey(0) -key_x, key_y = random.split(key) -x = random.normal(key_x, shape=(len(singles),)) -y = random.normal(key_y, shape=(len(singles),)) - -output = circuit2(x, y) - -############################################################################## -# We can check which basis states appear in the resulting superposition to confirm that they -# involve only states with three particles. - -# constructs binary representation of states with non-zero amplitude -import numpy as np - -states = [np.binary_repr(i, width=6) for i in range(len(output)) if output[i] != 0] -print(states) - -############################################################################## -# Besides these Givens rotations, there are other versions that have been -# reported in the literature and used to construct circuits for quantum chemistry. For instance, -# Ref. [#anselmetti]_ considers a different sign convention for single-excitation gates, -# -# .. math:: -# G(\theta)=\begin{pmatrix} -# 1 & 0 & 0 & 0\\ -# 0 & \cos (\theta/2) & \sin (\theta/2) & 0\\ -# 0 & -\sin(\theta/2) & \cos(\theta/2) & 0\\ -# 0 & 0 & 0 & 1 -# \end{pmatrix}, -# -# and Ref. [#barkoutsos]_ introduces the particle-conserving gates listed below, which are all -# Givens rotations -# -# .. math:: -# U_1(\theta, \phi) &= \begin{pmatrix} -# 1 & 0 & 0 & 0\\ -# 0 & \cos (\theta) & e^{i\phi}\sin (\theta) & 0\\ -# 0 & e^{-i\phi}\sin(\theta) & -\cos(\theta) & 0\\ -# 0 & 0 & 0 & 1 -# \end{pmatrix},\\ -# -# U_2(\theta) &= \begin{pmatrix} -# 1 & 0 & 0 & 0\\ -# 0 & \cos (2\theta) & -i\sin (2\theta) & 0\\ -# 0 & -i\sin(2\theta) & \cos(2\theta) & 0\\ -# 0 & 0 & 0 & 1 -# \end{pmatrix}. -# -# Givens rotations are a powerful abstraction for understanding -# quantum circuits for quantum chemistry. Instead of thinking of single-qubit gates and CNOTs as the -# building-blocks of quantum circuits, we can be more clever and select two-dimensional subspaces -# spanned by states with an equal number of particles, and use Givens rotations in that subspace -# to construct the circuits. 🧠 -# -# Controlled excitation gates -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# -# Single-qubit gates and CNOT gates are universal for quantum -# computing: they can be used to implement any conceivable quantum computation. If Givens -# rotations are analogous to single-qubit gates, then **controlled** Givens rotations are -# analogous to two-qubit gates. In universality constructions, the ability to control operations -# based on the states of other qubits is essential, so for this reason it's natural to study -# controlled Givens rotations. The simplest of these are controlled single-excitation gates, -# which are three-qubit gates that perform the mapping -# -# .. math:: -# CG(\theta) |101\rangle &= \cos (\theta/2)|101\rangle + \sin (\theta/2)|110\rangle,\\ -# CG(\theta) |110\rangle &= \cos (\theta/2)|110\rangle - \sin (\theta/2)|101\rangle, -# -# while leaving all other basis states unchanged. This gate only excites a particle -# from the second to third qubit, and vice versa, if the first (control) qubit is in state -# :math:`|1\rangle.` This is a useful property: as the name suggests, it provides us with better -# control over the transformations we want to apply. Suppose we aim to prepare the state -# -# .. math:: -# |\psi\rangle = \frac{1}{2}(|110000\rangle + |001100\rangle + |000011\rangle + |100100\rangle). -# -# Some inspection is enough to see that the states :math:`|001100\rangle` and :math:`|000011\rangle` -# differ by a double excitation from the reference state :math:`|110000\rangle.` Meanwhile, the state -# :math:`|100100\rangle` differs by a single excitation. It is thus tempting to think that applying -# two double-excitation gates and a single-excitation gate can be used to prepare the target state. -# It won't work! Applying the single-excitation gate on qubits 1 and 3 will also lead to an -# undesired contribution for the state :math:`|011000\rangle` through a coupling with -# :math:`|001100\rangle.` Let's check that this is the case: - -dev = qml.device('default.qubit', wires=6) - -@qml.qnode(dev, interface="jax") -def circuit3(x, y, z): - qml.BasisState(jnp.array([1, 1, 0, 0, 0, 0]), wires=[i for i in range(6)]) - qml.DoubleExcitation(x, wires=[0, 1, 2, 3]) - qml.DoubleExcitation(y, wires=[0, 1, 4, 5]) - qml.SingleExcitation(z, wires=[1, 3]) - return qml.state() - -x = -2 * jnp.arcsin(jnp.sqrt(1/4)) -y = -2 * jnp.arcsin(jnp.sqrt(1/3)) -z = -2 * jnp.arcsin(jnp.sqrt(1/2)) - -output = circuit3(x, y, z) -states = [np.binary_repr(i, width=6) for i in range(len(output)) if output[i] != 0] -print(states) - -############################################################################## -# Indeed, we have a non-zero coefficient for :math:`|011000\rangle.` To address this problem, -# we can instead apply the single-excitation gate controlled on the -# state of the first qubit. This ensures that there is no coupling with the state :math:`| -# 001100\rangle` since here the first qubit is in state :math:`|0\rangle.` Let's implement the circuit -# above, this time controlling on the state of the first qubit and verify that we can prepare the -# desired state. To perform the control, we use the :func:`~.pennylane.ctrl` transform: - -@qml.qnode(dev, interface="jax") -def circuit4(x, y, z): - qml.BasisState(jnp.array([1, 1, 0, 0, 0, 0]), wires=[i for i in range(6)]) - qml.DoubleExcitation(x, wires=[0, 1, 2, 3]) - qml.DoubleExcitation(y, wires=[0, 1, 4, 5]) - # single excitation controlled on qubit 0 - qml.ctrl(qml.SingleExcitation, control=0)(z, wires=[1, 3]) - return qml.state() - -output = circuit4(x, y, z) -states = [np.binary_repr(i, width=6) for i in range(len(output)) if output[i] != 0] -print(states) - -############################################################################## -# It was proven in Ref. [#arrazola]_ that controlled single-excitation gates are universal for -# particle-conserving unitaries. With enough ingenuity, you can use these operations to construct -# any kind of circuit for quantum chemistry applications. What more could you ask from a gate? -# -# State preparation -# ----------------- -# -# We can bring all these pieces together and implement a circuit capable of preparing -# four-qubit states of two particles with real coefficients. The main idea is that we can -# perform the construction one basis state at a time by applying a suitable excitation gate, -# which may need to be controlled. -# -# -# .. figure:: ../_static/demonstration_assets/givens_rotations/circuit.png -# :align: center -# :width: 70% -# -# A circuit for preparing four-qubit states with two particles. -# -# -# Starting from the reference state :math:`|1100\rangle,` we create a superposition -# with the state :math:`|1010\rangle` by applying a single-excitation gate on qubits 1 and 2. -# Similarly, we create a superposition with the state :math:`|1001\rangle` with a single -# excitation between qubits 1 and 3. This leaves us with a state of the form -# -# .. math:: -# |\psi\rangle = a |1100\rangle + b |1010\rangle + c |1001\rangle. -# -# We can now perform two single excitations from qubit 0 to qubits 2 and 3. These will have -# to be controlled on the state of qubit 1. Finally, applying a double-excitation gate on all qubits -# can create a superposition of the form -# -# .. math:: -# -# |\psi\rangle = c_1|1100\rangle + c_2|1010\rangle + c_3|1001\rangle + c_4|0110\rangle + -# c_5|0101\rangle + c_6|0011\rangle, -# -# which is our intended outcome. Let's use this approach to create an equal superposition over -# all two-particle states on four qubits. We follow the same strategy as before, setting the angle -# of the :math:`k`-th Givens rotation as :math:`-2 \arcsin(1/\sqrt{n-k}),` where :math:`n` is the -# number of basis states in the superposition. - -dev = qml.device('default.qubit', wires=4) - -@qml.qnode(dev, interface="jax") -def state_preparation(params): - qml.BasisState(jnp.array([1, 1, 0, 0]), wires=[0, 1, 2, 3]) - qml.SingleExcitation(params[0], wires=[1, 2]) - qml.SingleExcitation(params[1], wires=[1, 3]) - # single excitations controlled on qubit 1 - qml.ctrl(qml.SingleExcitation, control=1)(params[2], wires=[0, 2]) - qml.ctrl(qml.SingleExcitation, control=1)(params[3], wires=[0, 3]) - qml.DoubleExcitation(params[4], wires=[0, 1, 2, 3]) - return qml.state() - -n = 6 -params = jnp.array([-2 * jnp.arcsin(1/jnp.sqrt(n-i)) for i in range(n-1)]) - -output = state_preparation(params) -# sets very small coefficients to zero -output = jnp.where(jnp.abs(output) < 1e-10, 0.0, output) -states = [np.binary_repr(i, width=4) for i in range(len(output)) if output[i] != 0] -print("Basis states = ", states) -print("Output state =", output) - -############################################################################## -# Success! This is the equal superposition state we wanted to prepare. 🚀 -# -# When it comes to quantum circuits for quantum chemistry, a wide variety of -# architectures have been proposed. Researchers in the field are faced with the apparent -# choice of making a selection among these circuits to conduct their computations and benchmark new -# algorithms. Like a kid in a toy store, it is challenging to pick just one. -# -# Ultimately, the aim of this tutorial is to provide you with the conceptual and software tools -# to implement any of these proposed circuits, and **also to design your own**. It's not only fun -# to play with toys; it's also fun to build them. -# -# -# References -# ---------- -# -# .. [#anselmetti] -# -# G-L. R. Anselmetti, D. Wierichs, C. Gogolin, Christian, and R. M. Parrish, -# "Local, Expressive, Quantum-Number-Preserving VQE Ansatze -# for Fermionic Systems", arXiv:2104.05695, (2021). -# -# .. [#barkoutsos] -# -# P. KL. Barkoutsos, Panagiotis, et al., "Quantum algorithms for electronic structure -# calculations: Particle-hole Hamiltonian and optimized wave-function expansions", Physical -# Review A 98(2), 022322, (2018). -# -# .. [#arrazola] -# -# J. M. Arrazola, O. Di Matteo, N. Quesada, S. Jahangiri, A. Delgado, N. Killoran, "Universal -# quantum circuits for quantum chemistry", arXiv:2106.13839, (2021) -# -# -# About the author -# ---------------- -# +r""" + +Givens rotations for quantum chemistry +====================================== + +.. meta:: + :property="og:description": Discover the building blocks of quantum circuits for + quantum chemistry + + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/Givens_rotations.png + +.. related:: + tutorial_quantum_chemistry Building molecular Hamiltonians + tutorial_vqe A brief overview of VQE + + +*Author: Juan Miguel Arrazola — Posted: 30 June 2021. Last updated: 30 June 2021.* + +In the book `"Sophie's world" `_, the young +protagonist receives a white envelope containing a letter +with an intriguing question: "Why is Lego the most ingenious toy in the world?" At first baffled by +this curious message, she decides to reflect on the question. As told by the book's narrator, +she arrives at a conclusion: + +*The best thing about them was that with Lego she could construct any kind of object. And then +she could separate the blocks and construct something new. What more could one ask of a toy? +Sophie decided that Lego really could be called the most ingenious toy in the world.* + + + +.. figure:: ../_static/demonstration_assets/givens_rotations/lego-circuit.svg + :align: center + :width: 50% + + +In this tutorial, you will learn about the building blocks of quantum circuits for quantum +chemistry: Givens rotations. These are operations that can be used to construct any kind of +particle-conserving circuit. We discuss single and double excitation gates, which are particular +types of Givens rotations that play an important role in quantum chemistry. Notably, +controlled single excitation gates are universal for particle-conserving unitaries. You will also +learn how to use these gates to build arbitrary states of a fixed number of particles. + +Particle-conserving unitaries +----------------------------- + +Understanding the electronic structure of molecules is arguably the central problem in quantum chemistry. +Quantum computers tackle this problem by using systems of qubits to +represent the quantum states of the electrons. One method is to consider a +collection of `molecular orbitals `_, which +capture the three-dimensional region of space occupied by the electrons. Each orbital can be +occupied by at most two electrons, each with a different spin orientation. In this case we refer to +*spin orbitals* that can be occupied by a single electron. + +The state of electrons in a molecule can then be described by specifying how the +orbitals are occupied. The `Jordan-Wigner representation +`_ provides a +convenient way to do this: we associate a qubit with each spin orbital and +use its states to represent occupied :math:`|1\rangle` or unoccupied +:math:`|0\rangle` spin orbitals. + +An :math:`n`-qubit state with `Hamming weight `_ +:math:`k`, i.e., with :math:`k` qubits in state :math:`|1\rangle,` represents a state of +:math:`k` electrons in :math:`n` spin orbitals. For example :math:`|1010\rangle` is a state of +two electrons in two spin orbitals. More generally, superpositions over all basis states with a +fixed number of particles are valid states of the electrons in a molecule. These are states such as + +.. math:: + + |\psi\rangle = c_1|1100\rangle + c_2|1010\rangle + c_3|1001\rangle + c_4|0110\rangle + + c_5|0101\rangle + c_6|0011\rangle, + +for some coefficients :math:`c_i.` + +.. figure:: ../_static/demonstration_assets/givens_rotations/orbitals_states.png + :align: center + :width: 50% + + States of a system with six spin orbitals and three electrons. Orbitals are for illustration; + they correspond to carbon dioxide, which has more electrons and orbitals. + +Because the number of electrons in a molecule is +fixed, any transformation must conserve the number of particles. We refer to these as +**particle-conserving unitaries**. When designing quantum circuits and algorithms for quantum +chemistry, it is desirable to employ only particle-conserving gates that guarantee that the +states of the system remain valid. This raises the questions: what are the simplest +particle-conserving unitaries? Like Legos, how can they be used to construct any quantum circuit for +quantum chemistry applications? + +Givens rotations +---------------- + +Consider single-qubit gates. In their most general form, they perform the transformation + +.. math:: + + U|0\rangle &= a |0\rangle + b |1\rangle,\\ + U|1\rangle &= c |1\rangle + d |0\rangle, + +where :math:`|a|^2+|b|^2=|c|^2+|d|^2=1` and :math:`ab^* + cd^*=0.` This gate is +particle-conserving only if :math:`b=d=0,` which means that the only single-qubit gates that +preserve particle number are diagonal gates of the form + +.. math:: + + U = \begin{pmatrix} + e^{i\theta} & 0\\ + 0 & e^{i\phi} + \end{pmatrix}. + +On their own, these gates are not very interesting. They can only be used to change the +relative phases of states in a superposition; they cannot be used to create and control such +superpositions. So let's take a look at two-qubit gates. + +Basis states of two qubits can be categorized depending on +their number of particles. + +We have: + +- :math:`|00\rangle` with zero particles, +- :math:`|01\rangle,|10\rangle` with one particle, and +- :math:`|11\rangle` with two particles. + +We can now consider transformations that couple the states :math:`|01\rangle,|10\rangle.` These +are gates of the form + +.. math:: + + U|01\rangle &= a |01\rangle + b |10\rangle\\ + U|10\rangle &= c |10\rangle + d |01\rangle. + +This should be familiar: the unitary has the same form as a single-qubit gate, except that the +states :math:`|01\rangle, |10\rangle` respectively take the place of :math:`|0\rangle, +|1\rangle`. This correspondence has a name: the `dual-rail qubit +`_, where a two-level system is constructed +by specifying in which of two possible systems a single particle is located. The +difference compared to single-qubit gates is that any values of the parameters :math:`a, +b,c,d` give rise to a valid particle-conserving unitary. Take for instance the two-qubit gate + +.. math:: + + G(\theta)=\begin{pmatrix} + 1 & 0 & 0 & 0\\ + 0 & \cos (\theta/2) & -\sin (\theta/2) & 0\\ + 0 & \sin(\theta/2) & \cos(\theta/2) & 0\\ + 0 & 0 & 0 & 1 + \end{pmatrix}. + + +This is an example of a `Givens rotation `_: a +rotation in a two-dimensional subspace of a larger Hilbert space. In this case, we are performing a +Givens rotation in a two-dimensional subspace of the four-dimensional space of two-qubit states. +This gate allows us to create superpositions by exchanging the particle +between the two qubits. Such transformations can be interpreted as a **single excitation**, +where we view the exchange from :math:`|10\rangle` to :math:`|01\rangle` as exciting the electron +from the first to the second qubit. + +.. figure:: ../_static/demonstration_assets/givens_rotations/Givens_rotations_1.png + :align: center + :width: 35% + + A Givens rotation can be used to couple states that differ by a single excitation. + +This gate is implemented in PennyLane as the :class:`~.pennylane.SingleExcitation` operation. +We can use it to prepare an equal superposition of three-qubit states with a single particle +:math:`\frac{1}{\sqrt{3}}(|001\rangle + |010\rangle + |100\rangle).` We apply two single +excitation gates with parameters :math:`\theta, \phi` to the initial state :math:`|100\rangle.` +The excitation gates act on qubits 0,1 and 0,2 respectively. This yields the state + +.. math:: + + |\psi\rangle = \cos(\theta/2)\cos(\phi/2)|100\rangle - \sin(\theta/2)|010\rangle - + \cos(\theta/2)\sin(\phi/2)|001\rangle. + +Since the amplitude of :math:`|010\rangle` must be :math:`1/\sqrt{3},` we conclude that +:math:`-\sin(\theta/2)=1/\sqrt{3}.` This in turn implies that :math:`\cos(\theta/2)=\sqrt{2/3}` and +therefore :math:`-\sin(\phi/2)=1/\sqrt{2}.` Thus, to prepare an equal superposition state we +choose the angles of rotation to be + +.. math:: + + \theta &= - 2 \arcsin(1/\sqrt{3}),\\ + \phi &= - 2 \arcsin(1/\sqrt{2}). + +This can be implemented in PennyLane as follows: +""" + +import pennylane as qml +from jax import numpy as jnp +import jax + +jax.config.update("jax_enable_x64", True) +dev = qml.device('lightning.qubit', wires=3) + +@qml.qnode(dev, interface="jax") +def circuit(x, y): + # prepares the reference state |100> + qml.BasisState(jnp.array([1, 0, 0]), wires=[0, 1, 2]) + # applies the single excitations + qml.SingleExcitation(x, wires=[0, 1]) + qml.SingleExcitation(y, wires=[0, 2]) + return qml.state() + +x = -2 * jnp.arcsin(jnp.sqrt(1/3)) +y = -2 * jnp.arcsin(jnp.sqrt(1/2)) +print(circuit(x, y)) + +############################################################################## +# The components of the output state are ordered according to their binary +# representation, so entry 1 is :math:`|001\rangle`, entry 2 is :math:`|010\rangle,` and entry 4 is +# :math:`|100\rangle,` meaning we indeed prepared the desired state. We can check this by +# reshaping the output state + +tensor_state = circuit(x, y).reshape(2, 2, 2) +print("Amplitude of state |001> = ", tensor_state[0, 0, 1]) +print("Amplitude of state |010> = ", tensor_state[0, 1, 0]) +print("Amplitude of state |100> = ", tensor_state[1, 0, 0]) + +############################################################################## +# We can also study **double excitations** involving the transfer of two particles. For example, +# consider a Givens rotation in the subspace spanned by the states +# :math:`|1100\rangle` and :math:`|0011\rangle.` These +# states differ by a double excitation since we can map :math:`|1100\rangle` to +# :math:`|0011\rangle` by exciting the particles from the first two qubits to the last two. +# Mathematically, this gate can be represented by a unitary :math:`G^{(2)}(\theta)` that performs +# the mapping +# +# .. math:: +# +# G^{(2)}|0011\rangle &= \cos (\theta/2)|0011\rangle + \sin (\theta/2)|1100\rangle\\ +# G^{(2)}|1100\rangle &= \cos (\theta/2)|1100\rangle - \sin (\theta/2)|0011\rangle, +# +# while leaving all other basis states unchanged. This gate is implemented in PennyLane as the +# :class:`~.pennylane.DoubleExcitation` operation. +# +# +# .. figure:: ../_static/demonstration_assets/givens_rotations/Givens_rotations_2.png +# :align: center +# :width: 35% +# +# A Givens rotation can also be used to couple states that differ by a double excitation. +# +# +# In the context of quantum chemistry, it is common to consider excitations on a fixed reference +# state and include only the excitations that preserve the spin orientation of the electron. +# For a system with :math:`n` qubits and :math:`k` particles, this reference state is typically +# chosen as the state with the first :math:`k` qubits in state :math:`|1\rangle,` and the remainder +# in state :math:`|0\rangle.` +# PennyLane allows you to obtain all such excitations using the function +# :func:`~.pennylane.qchem.excitations`. Let's employ it to build a circuit that includes +# all single and double excitations acting on a reference state of three particles in six qubits. +# We apply a random rotation for each gate: + +nr_particles = 3 +nr_qubits = 6 + +singles, doubles = qml.qchem.excitations(3, 6) +print(f"Single excitations = {singles}") +print(f"Double excitations = {doubles}") + +############################################################################## +# Now we continue to build the circuit: + +from jax import random + +dev2 = qml.device('lightning.qubit', wires=6) + +@qml.qnode(dev2, interface="jax") +def circuit2(x, y): + # prepares reference state + qml.BasisState(jnp.array([1, 1, 1, 0, 0, 0]), wires=[0, 1, 2, 3, 4, 5]) + # apply all single excitations + for i, s in enumerate(singles): + qml.SingleExcitation(x[i], wires=s) + # apply all double excitations + for j, d in enumerate(doubles): + qml.DoubleExcitation(y[j], wires=d) + return qml.state() + +# random angles of rotation +key = random.PRNGKey(0) +key_x, key_y = random.split(key) +x = random.normal(key_x, shape=(len(singles),)) +y = random.normal(key_y, shape=(len(singles),)) + +output = circuit2(x, y) + +############################################################################## +# We can check which basis states appear in the resulting superposition to confirm that they +# involve only states with three particles. + +# constructs binary representation of states with non-zero amplitude +import numpy as np + +states = [np.binary_repr(i, width=6) for i in range(len(output)) if output[i] != 0] +print(states) + +############################################################################## +# Besides these Givens rotations, there are other versions that have been +# reported in the literature and used to construct circuits for quantum chemistry. For instance, +# Ref. [#anselmetti]_ considers a different sign convention for single-excitation gates, +# +# .. math:: +# G(\theta)=\begin{pmatrix} +# 1 & 0 & 0 & 0\\ +# 0 & \cos (\theta/2) & \sin (\theta/2) & 0\\ +# 0 & -\sin(\theta/2) & \cos(\theta/2) & 0\\ +# 0 & 0 & 0 & 1 +# \end{pmatrix}, +# +# and Ref. [#barkoutsos]_ introduces the particle-conserving gates listed below, which are all +# Givens rotations +# +# .. math:: +# U_1(\theta, \phi) &= \begin{pmatrix} +# 1 & 0 & 0 & 0\\ +# 0 & \cos (\theta) & e^{i\phi}\sin (\theta) & 0\\ +# 0 & e^{-i\phi}\sin(\theta) & -\cos(\theta) & 0\\ +# 0 & 0 & 0 & 1 +# \end{pmatrix},\\ +# +# U_2(\theta) &= \begin{pmatrix} +# 1 & 0 & 0 & 0\\ +# 0 & \cos (2\theta) & -i\sin (2\theta) & 0\\ +# 0 & -i\sin(2\theta) & \cos(2\theta) & 0\\ +# 0 & 0 & 0 & 1 +# \end{pmatrix}. +# +# Givens rotations are a powerful abstraction for understanding +# quantum circuits for quantum chemistry. Instead of thinking of single-qubit gates and CNOTs as the +# building-blocks of quantum circuits, we can be more clever and select two-dimensional subspaces +# spanned by states with an equal number of particles, and use Givens rotations in that subspace +# to construct the circuits. 🧠 +# +# Controlled excitation gates +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Single-qubit gates and CNOT gates are universal for quantum +# computing: they can be used to implement any conceivable quantum computation. If Givens +# rotations are analogous to single-qubit gates, then **controlled** Givens rotations are +# analogous to two-qubit gates. In universality constructions, the ability to control operations +# based on the states of other qubits is essential, so for this reason it's natural to study +# controlled Givens rotations. The simplest of these are controlled single-excitation gates, +# which are three-qubit gates that perform the mapping +# +# .. math:: +# CG(\theta) |101\rangle &= \cos (\theta/2)|101\rangle + \sin (\theta/2)|110\rangle,\\ +# CG(\theta) |110\rangle &= \cos (\theta/2)|110\rangle - \sin (\theta/2)|101\rangle, +# +# while leaving all other basis states unchanged. This gate only excites a particle +# from the second to third qubit, and vice versa, if the first (control) qubit is in state +# :math:`|1\rangle.` This is a useful property: as the name suggests, it provides us with better +# control over the transformations we want to apply. Suppose we aim to prepare the state +# +# .. math:: +# |\psi\rangle = \frac{1}{2}(|110000\rangle + |001100\rangle + |000011\rangle + |100100\rangle). +# +# Some inspection is enough to see that the states :math:`|001100\rangle` and :math:`|000011\rangle` +# differ by a double excitation from the reference state :math:`|110000\rangle.` Meanwhile, the state +# :math:`|100100\rangle` differs by a single excitation. It is thus tempting to think that applying +# two double-excitation gates and a single-excitation gate can be used to prepare the target state. +# It won't work! Applying the single-excitation gate on qubits 1 and 3 will also lead to an +# undesired contribution for the state :math:`|011000\rangle` through a coupling with +# :math:`|001100\rangle.` Let's check that this is the case: + +dev = qml.device('default.qubit', wires=6) + +@qml.qnode(dev, interface="jax") +def circuit3(x, y, z): + qml.BasisState(jnp.array([1, 1, 0, 0, 0, 0]), wires=[i for i in range(6)]) + qml.DoubleExcitation(x, wires=[0, 1, 2, 3]) + qml.DoubleExcitation(y, wires=[0, 1, 4, 5]) + qml.SingleExcitation(z, wires=[1, 3]) + return qml.state() + +x = -2 * jnp.arcsin(jnp.sqrt(1/4)) +y = -2 * jnp.arcsin(jnp.sqrt(1/3)) +z = -2 * jnp.arcsin(jnp.sqrt(1/2)) + +output = circuit3(x, y, z) +states = [np.binary_repr(i, width=6) for i in range(len(output)) if output[i] != 0] +print(states) + +############################################################################## +# Indeed, we have a non-zero coefficient for :math:`|011000\rangle.` To address this problem, +# we can instead apply the single-excitation gate controlled on the +# state of the first qubit. This ensures that there is no coupling with the state :math:`| +# 001100\rangle` since here the first qubit is in state :math:`|0\rangle.` Let's implement the circuit +# above, this time controlling on the state of the first qubit and verify that we can prepare the +# desired state. To perform the control, we use the :func:`~.pennylane.ctrl` transform: + +@qml.qnode(dev, interface="jax") +def circuit4(x, y, z): + qml.BasisState(jnp.array([1, 1, 0, 0, 0, 0]), wires=[i for i in range(6)]) + qml.DoubleExcitation(x, wires=[0, 1, 2, 3]) + qml.DoubleExcitation(y, wires=[0, 1, 4, 5]) + # single excitation controlled on qubit 0 + qml.ctrl(qml.SingleExcitation, control=0)(z, wires=[1, 3]) + return qml.state() + +output = circuit4(x, y, z) +states = [np.binary_repr(i, width=6) for i in range(len(output)) if output[i] != 0] +print(states) + +############################################################################## +# It was proven in Ref. [#arrazola]_ that controlled single-excitation gates are universal for +# particle-conserving unitaries. With enough ingenuity, you can use these operations to construct +# any kind of circuit for quantum chemistry applications. What more could you ask from a gate? +# +# State preparation +# ----------------- +# +# We can bring all these pieces together and implement a circuit capable of preparing +# four-qubit states of two particles with real coefficients. The main idea is that we can +# perform the construction one basis state at a time by applying a suitable excitation gate, +# which may need to be controlled. +# +# +# .. figure:: ../_static/demonstration_assets/givens_rotations/circuit.png +# :align: center +# :width: 70% +# +# A circuit for preparing four-qubit states with two particles. +# +# +# Starting from the reference state :math:`|1100\rangle,` we create a superposition +# with the state :math:`|1010\rangle` by applying a single-excitation gate on qubits 1 and 2. +# Similarly, we create a superposition with the state :math:`|1001\rangle` with a single +# excitation between qubits 1 and 3. This leaves us with a state of the form +# +# .. math:: +# |\psi\rangle = a |1100\rangle + b |1010\rangle + c |1001\rangle. +# +# We can now perform two single excitations from qubit 0 to qubits 2 and 3. These will have +# to be controlled on the state of qubit 1. Finally, applying a double-excitation gate on all qubits +# can create a superposition of the form +# +# .. math:: +# +# |\psi\rangle = c_1|1100\rangle + c_2|1010\rangle + c_3|1001\rangle + c_4|0110\rangle + +# c_5|0101\rangle + c_6|0011\rangle, +# +# which is our intended outcome. Let's use this approach to create an equal superposition over +# all two-particle states on four qubits. We follow the same strategy as before, setting the angle +# of the :math:`k`-th Givens rotation as :math:`-2 \arcsin(1/\sqrt{n-k}),` where :math:`n` is the +# number of basis states in the superposition. + +dev = qml.device('default.qubit', wires=4) + +@qml.qnode(dev, interface="jax") +def state_preparation(params): + qml.BasisState(jnp.array([1, 1, 0, 0]), wires=[0, 1, 2, 3]) + qml.SingleExcitation(params[0], wires=[1, 2]) + qml.SingleExcitation(params[1], wires=[1, 3]) + # single excitations controlled on qubit 1 + qml.ctrl(qml.SingleExcitation, control=1)(params[2], wires=[0, 2]) + qml.ctrl(qml.SingleExcitation, control=1)(params[3], wires=[0, 3]) + qml.DoubleExcitation(params[4], wires=[0, 1, 2, 3]) + return qml.state() + +n = 6 +params = jnp.array([-2 * jnp.arcsin(1/jnp.sqrt(n-i)) for i in range(n-1)]) + +output = state_preparation(params) +# sets very small coefficients to zero +output = jnp.where(jnp.abs(output) < 1e-10, 0.0, output) +states = [np.binary_repr(i, width=4) for i in range(len(output)) if output[i] != 0] +print("Basis states = ", states) +print("Output state =", output) + +############################################################################## +# Success! This is the equal superposition state we wanted to prepare. 🚀 +# +# When it comes to quantum circuits for quantum chemistry, a wide variety of +# architectures have been proposed. Researchers in the field are faced with the apparent +# choice of making a selection among these circuits to conduct their computations and benchmark new +# algorithms. Like a kid in a toy store, it is challenging to pick just one. +# +# Ultimately, the aim of this tutorial is to provide you with the conceptual and software tools +# to implement any of these proposed circuits, and **also to design your own**. It's not only fun +# to play with toys; it's also fun to build them. +# +# +# References +# ---------- +# +# .. [#anselmetti] +# +# G-L. R. Anselmetti, D. Wierichs, C. Gogolin, Christian, and R. M. Parrish, +# "Local, Expressive, Quantum-Number-Preserving VQE Ansatze +# for Fermionic Systems", arXiv:2104.05695, (2021). +# +# .. [#barkoutsos] +# +# P. KL. Barkoutsos, Panagiotis, et al., "Quantum algorithms for electronic structure +# calculations: Particle-hole Hamiltonian and optimized wave-function expansions", Physical +# Review A 98(2), 022322, (2018). +# +# .. [#arrazola] +# +# J. M. Arrazola, O. Di Matteo, N. Quesada, S. Jahangiri, A. Delgado, N. Killoran, "Universal +# quantum circuits for quantum chemistry", arXiv:2106.13839, (2021) +# +# +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_haar_measure/demo.py b/demonstrations_v2/tutorial_haar_measure/demo.py index 5807b5ab34..edde00d2d7 100644 --- a/demonstrations_v2/tutorial_haar_measure/demo.py +++ b/demonstrations_v2/tutorial_haar_measure/demo.py @@ -1,812 +1,812 @@ -r""".. role:: html(raw) - :format: html - -Understanding the Haar measure -============================== - -.. meta:: - :property="og:description": Learn all about the Haar measure and how to randomly sample quantum states. - - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/spherical_int_dtheta.png - -.. related:: - - tutorial_unitary_designs Unitary designs - quantum_volume Quantum volume - qsim_beyond_classical Beyond classical computing with qsim - tutorial_barren_plateaus Barren plateaus in quantum neural networks - - -*Author: Olivia Di Matteo — Posted: 22 March 2021. Last updated: 22 March 2021.* - -If you've ever dug into the literature about random quantum circuits, -variational ansatz structure, or anything related to the structure and -properties of unitary operations, you've likely come across a statement like the -following: "Assume that :math:`U` is sampled uniformly at random from the Haar -measure". In this demo, we're going to unravel this cryptic statement and take -an in-depth look at what it means. You'll gain an understanding of the general -concept of a *measure*, the Haar measure and its special properties, and you'll -learn how to sample from it using tools available in PennyLane and other -scientific computing frameworks. By the end of this demo, you'll be able to -include that important statement in your own work with confidence! - -.. note:: - - To get the most out of this demo, it is helpful if you are familiar with - `integration of multi-dimensional functions - `__, the `Bloch sphere - `__, and the conceptual ideas - behind `decompositions - `__ and factorizations of - unitary matrices (see, e.g., 4.5.1 and 4.5.2 of [#NandC2000]_). - -Measure -------- - -`Measure theory `__ is a -branch of mathematics that studies things that are measurable—think length, -area, or volume, but generalized to mathematical spaces and even higher -dimensions. Loosely, the measure tells you about how "stuff" is distributed and -concentrated in a mathematical set or space. An intuitive way to understand -the measure is to think about a sphere. An arbitrary point on a sphere can be -parametrized by three numbers—depending on what you're doing, you may use -Cartesian coordinates :math:`(x, y, z),` or it may be more convenient to use -spherical coordinates :math:`(\rho, \phi, \theta).` - -Suppose you wanted to compute the volume of a solid sphere with radius -:math:`r.` This can be done by integrating over the three coordinates -:math:`\rho, \phi,` and :math:`\theta.` Your first thought here may be to simply -integrate each parameter over its full range, like so: - -.. math:: - - V = \int_0^{r} \int_0^{2\pi} \int_0^{\pi} d\rho~ d\phi~ d\theta = 2\pi^2 r - -But, we know that the volume of a sphere of radius :math:`r` is -:math:`\frac{4}{3}\pi r^3,` so what we got from this integral is clearly wrong! -Taking the integral naively like this doesn't take into account the structure of -the sphere with respect to the parameters. For example, consider -two small, infinitesimal elements of area with the same difference in -:math:`\theta` and :math:`\phi,` but at different values of :math:`\theta:` - -.. figure:: /_static/demonstration_assets/haar_measure/spherical_int_dtheta.png - :align: center - :width: 50% - - -Even though the differences :math:`d\theta` and :math:`d\phi` themselves are the -same, there is way more "stuff" near the equator of the sphere than there is -near the poles. We must take into account the value of :math:`\theta` when -computing the integral! Specifically, we multiply by the function -:math:`\sin\theta`---the properties of the :math:`\sin` function mean that the -most weight will occur around the equator where :math:`\theta=\pi/2,` and the -least weight near the poles where :math:`\theta=0` and :math:`\theta=\pi.` - -Similar care must be taken for :math:`\rho.` The contribution to volume of -parts of the sphere with a large :math:`\rho` is far more than for a small -:math:`\rho`---we should expect the contribution to be proportional to -:math:`\rho^2,` given that the surface area of a sphere of radius :math:`r` is -:math:`4\pi r^2.` - -On the other hand, for a fixed :math:`\rho` and :math:`\theta,` the length of -the :math:`d\phi` is the same all around the circle. If put all these facts -together, we find that the actual expression for the integral should look like -this: - -.. math:: - - V = \int_0^r \int_0^{2\pi} \int_0^{\pi} \rho^2 \sin \theta~ d\rho~ d\phi~ - d\theta = \frac{4}{3}\pi r^3 - -These extra terms that we had to add to the integral, :math:`\rho^2 \sin -\theta`, constitute the *measure*. The measure weights portions of the sphere -differently depending on where they are in the space. While we need to know the -measure to properly integrate over the sphere, knowledge of the measure also -gives us the means to perform another important task, that of sampling points in -the space uniformly at random. We can't simply sample each parameter from the -uniform distribution over its domain—as we experienced already, this doesn't -take into account how the sphere is spread out over space. The measure describes -the distribution of each parameter and gives a recipe for sampling them in order -to obtain something properly uniform. - -The Haar measure ----------------- - -Operations in quantum computing are described by unitary matrices. -Unitary matrices, like points on a sphere, can be expressed in terms of a fixed -set of coordinates, or parameters. For example, the most general single-qubit rotation -implemented in PennyLane (:class:`~.pennylane.Rot`) is expressed in terms of three -parameters like so, - -.. math:: - - U(\phi, \theta, \omega) = \begin{pmatrix} e^{-i(\phi + \omega)/2} - \cos(\theta/2) & -e^{i(\phi - \omega)/2} \sin(\theta/2) - \\ e^{-i(\phi - \omega)/2} \sin(\theta/2) & e^{i(\phi + - \omega)/2} \cos(\theta/2) \end{pmatrix}. - -For every dimension :math:`N,` the unitary matrices of size :math:`N \times N` -constitute the *unitary group* :math:`U(N).` We can perform operations on -elements of this group, such as apply functions to them, integrate over them, or -sample uniformly over them, just as we can do to points on a sphere. When we do -such tasks with respect to the sphere, we have to add the measure in order to -properly weight the different regions of space. The *Haar measure* provides the -analogous terms we need for working with the unitary group. - -For an :math:`N`-dimensional system, the Haar measure, often denoted by -:math:`\mu_N,` tells us how to weight the elements of :math:`U(N).` For -example, suppose :math:`f` is a function that acts on elements of :math:`U(N),` -and we would like to take its integral over the group. We must write this -integral with respect to the Haar measure, like so: - -.. math:: - - \int_{V \in U(N)} f(V) d\mu_N(V). - -As with the measure term of the sphere, :math:`d\mu_N` itself can be broken down -into components depending on individual parameters. While the Haar -measure can be defined for every dimension :math:`N,` the mathematical form gets -quite hairy for larger dimensions—in general, an :math:`N`-dimensional unitary -requires at least :math:`N^2 - 1` parameters, which is a lot to keep track of! -Therefore we'll start with the case of a single qubit :math:`(N=2),` then show -how things generalize. - -Single-qubit Haar measure -~~~~~~~~~~~~~~~~~~~~~~~~~ - -The single-qubit case provides a particularly nice entry point because we can -continue our comparison to spheres by visualizing single-qubit states on the -Bloch sphere. As expressed above, the measure provides a recipe for sampling -elements of the unitary group in a properly uniform manner, given the structure -of the group. One useful consequence of this is that it provides a method to -sample quantum *states* uniformly at random—we simply generate Haar-random -unitaries, and apply them to a fixed basis state such as :math:`\vert 0\rangle.` - -We'll see how this works in good time. First, we'll take a look at what happens -when we ignore the measure and do things *wrong*. Suppose we sample quantum -states by applying unitaries obtained by the parametrization above, but sample -the angles :math:`\omega, \phi,` and :math:`\theta` from the flat uniform -distribution between :math:`[0, 2\pi)` (fun fact: there is a measure implicit in -this kind of sampling too! It just has a constant value, because each point is -equally likely to be sampled). - -""" - -import pennylane as qml -import numpy as np -import matplotlib.pyplot as plt - -# set the random seed -np.random.seed(42) - -# Use the mixed state simulator to save some steps in plotting later -dev = qml.device('default.mixed', wires=1) - -@qml.qnode(dev) -def not_a_haar_random_unitary(): - # Sample all parameters from their flat uniform distribution - phi, theta, omega = 2 * np.pi * np.random.uniform(size=3) - qml.Rot(phi, theta, omega, wires=0) - return qml.state() - -num_samples = 2021 - -not_haar_samples = [not_a_haar_random_unitary() for _ in range(num_samples)] - -###################################################################### -# In order to plot these on the Bloch sphere, we'll need to do one more -# step, and convert the quantum states into Bloch vectors. -# - -X = np.array([[0, 1], [1, 0]]) -Y = np.array([[0, -1j], [1j, 0]]) -Z = np.array([[1, 0], [0, -1]]) - -# Used the mixed state simulator so we could have the density matrix for this part! -def convert_to_bloch_vector(rho): - """Convert a density matrix to a Bloch vector.""" - ax = np.trace(np.dot(rho, X)).real - ay = np.trace(np.dot(rho, Y)).real - az = np.trace(np.dot(rho, Z)).real - return [ax, ay, az] - -not_haar_bloch_vectors = np.array([convert_to_bloch_vector(s) for s in not_haar_samples]) - -###################################################################### -# With this done, let's find out where our "uniformly random" states ended up: - -def plot_bloch_sphere(bloch_vectors): - """ Helper function to plot vectors on a sphere.""" - fig = plt.figure(figsize=(6, 6)) - ax = fig.add_subplot(111, projection='3d') - fig.subplots_adjust(left=0, right=1, bottom=0, top=1) - - ax.grid(False) - ax.set_axis_off() - ax.view_init(30, 45) - - # Draw the axes (source: https://github.com/matplotlib/matplotlib/issues/13575) - x, y, z = np.array([[-1.5,0,0], [0,-1.5,0], [0,0,-1.5]]) - u, v, w = np.array([[3,0,0], [0,3,0], [0,0,3]]) - ax.quiver(x, y, z, u, v, w, arrow_length_ratio=0.05, color="black", linewidth=0.5) - - ax.text(0, 0, 1.7, r"|0⟩", color="black", fontsize=16) - ax.text(0, 0, -1.9, r"|1⟩", color="black", fontsize=16) - ax.text(1.9, 0, 0, r"|+⟩", color="black", fontsize=16) - ax.text(-1.7, 0, 0, r"|–⟩", color="black", fontsize=16) - ax.text(0, 1.7, 0, r"|i+⟩", color="black", fontsize=16) - ax.text(0,-1.9, 0, r"|i–⟩", color="black", fontsize=16) - - ax.scatter( - bloch_vectors[:,0], bloch_vectors[:,1], bloch_vectors[:, 2], c='#e29d9e', alpha=0.3 - ) - plt.show() - -plot_bloch_sphere(not_haar_bloch_vectors) - -###################################################################### -# You can see from this plot that even though our parameters were sampled from a -# uniform distribution, there is a noticeable amount of clustering around the poles -# of the sphere. Despite the input parameters being uniform, the output is very -# much *not* uniform. Just like the regular sphere, the measure is larger near -# the equator, and if we just sample uniformly, we won't end up populating that -# area as much. To take that into account we will need to sample from the proper -# Haar measure, and weight the different parameters appropriately. -# -# For a single qubit, the Haar measure looks much like the case of a sphere, -# minus the radial component. Intuitively, all qubit state vectors have length -# 1, so it makes sense that this wouldn't play a role here. The parameter that -# we will have to weight differently is :math:`\theta,` and in fact the -# adjustment in measure is identical to that we had to do with the polar axis of -# the sphere, i.e., :math:`\sin \theta.` In order to sample the :math:`\theta` -# uniformly at random in this context, we must sample from the distribution -# :math:`\hbox{Pr}(\theta) = \sin \theta.` We can accomplish this by setting up -# a custom probability distribution with -# `rv_continuous `__ -# in ``scipy``. - -from scipy.stats import rv_continuous - -class sin_prob_dist(rv_continuous): - def _pdf(self, theta): - # The 0.5 is so that the distribution is normalized - return 0.5 * np.sin(theta) - -# Samples of theta should be drawn from between 0 and pi -sin_sampler = sin_prob_dist(a=0, b=np.pi) - -@qml.qnode(dev) -def haar_random_unitary(): - phi, omega = 2 * np.pi * np.random.uniform(size=2) # Sample phi and omega as normal - theta = sin_sampler.rvs(size=1)[0] # Sample theta from our new distribution - qml.Rot(phi, theta, omega, wires=0) - return qml.state() - -haar_samples = [haar_random_unitary() for _ in range(num_samples)] -haar_bloch_vectors = np.array([convert_to_bloch_vector(s) for s in haar_samples]) - -plot_bloch_sphere(haar_bloch_vectors) - -###################################################################### -# We see that when we use the correct measure, our qubit states are now -# much better distributed over the sphere. Putting this information together, -# we can now write the explicit form for the single-qubit Haar measure: -# -# .. math:: -# -# d\mu_2 = \sin \theta d\theta \cdot d\omega \cdot d\phi. -# -# Show me more math! -# ~~~~~~~~~~~~~~~~~~ -# -# While we can easily visualize the single-qubit case, this is no longer -# possible when we increase the number of qubits. Regardless, we can still -# obtain a mathematical expression for the Haar measure in arbitrary -# dimensions. In the previous section, we expressed the Haar measure in terms of -# a set of parameters that can be used to specify the unitary group -# :math:`U(2).` Such a parametrization is not unique, and in fact there are -# multiple ways to *factorize*, or decompose an :math:`N`-dimensional unitary -# operation into a set of parameters. -# -# Many of these parametrizations come to us from the study of photonics. Here, -# arbitrary operations are broken down into elementary operations involving only -# a few parameters which correspond directly to parameters of the physical -# apparatus used to implement them (beamsplitters and phase shifts). Rather than -# qubits, such operations act on modes, or *qumodes*. They are expressed as -# elements of the :math:`N`-dimensional `special unitary group -# `__. This group, written -# as :math:`SU(N),` is the continuous group consisting of all :math:`N \times N` -# unitary operations with determinant 1 (essentially like :math:`U(N),` minus -# a potential global phase). -# -# -# .. note:: -# -# Elements of :math:`SU(N)` and :math:`U(N)` can still be considered as -# multi-qubit operations in the cases where :math:`N` is a power of 2, but -# they must be translated from continuous-variable operations into qubit -# operations. (In PennyLane, this can be done by feeding the unitaries to -# the :class:`~.pennylane.QubitUnitary` operation directly. Alternatively, -# one can use *quantum compilation* to express the operations as a sequence -# of elementary gates such as Pauli rotations and CNOTs.) -# -# .. admonition:: Tip -# -# If you haven't had many opportunities to work in terms of qumodes, the -# `Strawberry Fields documentation -# `__ is a -# good starting point. -# -# For example, we saw already above that for :math:`N=2,` we can write -# -# .. math:: -# -# U(\phi, \theta, \omega) = \begin{pmatrix} e^{-i(\phi + \omega)/2} -# \cos(\theta/2) & -e^{i(\phi - \omega)/2} \sin(\theta/2) -# \\ e^{-i(\phi - \omega)/2} \sin(\theta/2) & e^{i(\phi + -# \omega)/2} \cos(\theta/2) \end{pmatrix}. -# -# -# This unitary can be factorized as follows: -# -# .. math:: -# -# U(\phi, \theta, \omega) = -# \begin{pmatrix} -# e^{-i\omega/2} & 0 \\ 0 & e^{i\omega/2} -# \end{pmatrix} -# \begin{pmatrix} -# \cos(\theta/2) & -\sin(\theta/2) \\ \sin(\theta/2) & \cos(\theta/2) -# \end{pmatrix} -# \begin{pmatrix} -# e^{-i\phi/2} & 0 \\ 0 & e^{i\phi/2} -# \end{pmatrix} -# -# The middle operation is a beamsplitter; the other two operations are phase -# shifts. We saw earlier that for :math:`N=2,` :math:`d\mu_2 = \sin\theta -# d\theta d\omega d\phi`---note how the parameter in the beamsplitter -# contributes to the measure in a different way than those of the phase -# shifts. As mentioned above, for larger values of :math:`N` there are multiple -# ways to decompose the unitary. Such decompositions rewrite elements in -# :math:`SU(N)` acting on :math:`N` modes as a sequence of operations acting -# only on 2 modes, :math:`SU(2),` and single-mode phase shifts. Shown below are -# three examples [#deGuise2018]_, [#Clements2016]_, [#Reck1994]_: -# -# .. figure:: /_static/demonstration_assets/haar_measure/unitaries.png -# :align: center -# :width: 95% -# -# -# In these graphics, every wire is a different mode. Every box represents an -# operation on one or more modes, and the number in the box indicates the number -# of parameters. The boxes containing a ``1`` are simply phase shifts on -# individual modes. The blocks containing a ``3`` are :math:`SU(2)` transforms -# with 3 parameters, such as the :math:`U(\phi, \theta, \omega)` above. Those -# containing a ``2`` are :math:`SU(2)` transforms on pairs of modes with 2 -# parameters, similar to the 3-parameter ones but with :math:`\omega = \phi.` -# -# Although the decompositions all produce the same set of operations, their -# structure and parametrization may have consequences in practice. The first [#deGuise2018]_ -# has a particularly convenient form that leads to a recursive definition -# of the Haar measure. The decomposition is formulated recursively such that an -# :math:`SU(N)` operation can be implemented by sandwiching an :math:`SU(2)` -# transformation between two :math:`SU(N-1)` transformations, like so: -# -# | -# -# .. figure:: /_static/demonstration_assets/haar_measure/sun.svg -# :align: center -# :width: 80% -# -# | -# -# The Haar measure is then constructed recursively as a product of 3 -# terms. The first term depends on the parameters in the first :math:`SU(N-1)` -# transformation; the second depends on the parameters in the lone :math:`SU(2)` -# transformation; and the third term depends on the parameters in the other -# :math:`SU(N-1)` transformation. -# -# :math:`SU(2)` is the "base case" of the recursion—we simply have the Haar measure -# as expressed above. -# -# | -# -# .. figure:: /_static/demonstration_assets/haar_measure/su2_haar.svg -# :align: center -# :width: 25% -# -# | -# -# Moving on up, we can write elements of :math:`SU(3)` as a sequence of three -# :math:`SU(2)` transformations. The Haar measure :math:`d\mu_3` then consists -# of two copies of :math:`d\mu_2,` with an extra term in between to take into -# account the middle transformation. -# -# | -# -# .. figure:: /_static/demonstration_assets/haar_measure/su3_haar.svg -# :align: center -# :width: 80% -# -# | -# -# For :math:`SU(4)` and upwards, the form changes slightly, but still follows -# the pattern of two copies of :math:`d\mu_{N-1}` with a term in between. -# -# | -# -# .. figure:: /_static/demonstration_assets/haar_measure/su4_premerge.svg -# :align: center -# :width: 90% -# -# | -# -# For larger systems, however, the recursive composition allows for some of the -# :math:`SU(2)` transformations on the lower modes to be grouped. We can take -# advantage of this and aggregate some of the parameters: -# -# | -# -# .. figure:: /_static/demonstration_assets/haar_measure/su4_triangle_merge.svg -# :align: center -# :width: 100% -# -# | -# -# This leads to one copy of :math:`d\mu_{N-1},` which we'll denote as -# :math:`d\mu_{N-1}^\prime,` containing only a portion of the full set of terms -# (as detailed in [#deGuise2018]_, this is called a *coset measure*). -# -# | -# -# .. figure:: /_static/demonstration_assets/haar_measure/su4_haar.svg -# :align: center -# :width: 100% -# -# | -# -# Putting everything together, we have that -# -# .. math:: -# -# d\mu_N = d\mu_{N-1}^\prime \times \sin \theta_{N-1} -# \sin^{2(N-2)}\left(\frac{\theta_{N-1}}{2}\right) d\theta_{N-1} d\omega_{N-1} \times d\mu_{N-1} -# -# The middle portion depends on the value of :math:`N,` and the parameters -# :math:`\theta_{N-1}` and :math:`\omega_{N-1}` contained in the :math:`(N-1)`'th -# :math:`SU(N)` transformation. This is thus a convenient, systematic way to -# construct the :math:`N`-dimensional Haar measure for the unitary group. As a -# final note, even though unitaries can be parametrized in different ways, the -# underlying Haar measure is *unique*. This is a consequence of it being an -# invariant measure, as will be shown later. -# -# Haar-random matrices from the :math:`QR` decomposition -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# -# Nice-looking math aside, sometimes you just need to generate a large number of -# high-dimensional Haar-random matrices. It would be very cumbersome to sample -# and keep track of the distributions of so many parameters; furthermore, the -# measure above requires you to parametrize your operations in a fixed way. -# There is a much quicker way to perform the sampling by taking a (slightly -# modified) `QR decomposition -# `__ of complex-valued -# matrices. This algorithm is detailed in [#Mezzadri2006]_, and consists of the -# following steps: -# -# 1. Generate an :math:`N \times N` matrix :math:`Z` with complex numbers :math:`a+bi` -# where both :math:`a` and :math:`b` are normally distributed with mean 0 and variance 1 -# (this is sampling from the distribution known as the *Ginibre ensemble*). -# 2. Compute a QR decomposition :math:`Z = QR.` -# 3. Compute the diagonal matrix :math:`\Lambda = \hbox{diag}(R_{ii}/|R_{ii}|).` -# 4. Compute :math:`Q^\prime = Q \Lambda,` which will be Haar-random. -# -# - -from numpy.linalg import qr - -def qr_haar(N): - """Generate a Haar-random matrix using the QR decomposition.""" - # Step 1 - A, B = np.random.normal(size=(N, N)), np.random.normal(size=(N, N)) - Z = A + 1j * B - - # Step 2 - Q, R = qr(Z) - - # Step 3 - Lambda = np.diag([R[i, i] / np.abs(R[i, i]) for i in range(N)]) - - # Step 4 - return np.dot(Q, Lambda) - -###################################################################### -# Let's check that this method actually generates Haar-random unitaries -# by trying it out for :math:`N=2` and plotting on the Bloch sphere. -# - -@qml.qnode(dev) -def qr_haar_random_unitary(): - qml.QubitUnitary(qr_haar(2), wires=0) - return qml.state() - -qr_haar_samples = [qr_haar_random_unitary() for _ in range(num_samples)] -qr_haar_bloch_vectors = np.array([convert_to_bloch_vector(s) for s in qr_haar_samples]) -plot_bloch_sphere(qr_haar_bloch_vectors) - -###################################################################### -# As expected, we find our qubit states are distributed uniformly over the -# sphere. This particular method is what's implemented in packages like -# ``scipy``'s `unitary_group -# `__ -# function. -# -# Now, it's clear that this method works, but it is also important to -# understand *why* it works. Step 1 is fairly straightforward—the base of our -# samples is a matrix full of complex values chosen from a typical -# distribution. This isn't enough by itself, since unitary matrices also -# have constraints—their rows and columns must be orthonormal. -# These constraints are where step 2 comes in—the outcome of a generic -# QR decomposition consists of an *orthonormal* matrix :math:`Q,` and and upper -# triangular matrix :math:`R.` Since our original matrix was complex-valued, we end -# up with a :math:`Q` that is in fact already unitary. But why not stop there? Why -# do we then perform steps 3 and 4? -# -# Steps 3 and 4 are needed because, while the QR decomposition yields a unitary, -# it is not a unitary that is properly Haar-random. In [#Mezzadri2006]_, it is -# explained that a uniform distribution over unitary matrices should also yield -# a uniform distribution over the *eigenvalues* of those matrices, i.e., every -# eigenvalue should be equally likely. Just using the QR decomposition out of -# the box produces an *uneven* distribution of eigenvalues of the unitaries! -# This discrepancy stems from the fact that the QR decomposition is not unique. -# We can take any unitary diagonal matrix :math:`\Lambda,` and re-express the decomposition -# as :math:`QR = Q\Lambda \Lambda^\dagger R = Q^\prime R^\prime.` Step 3 removes this -# redundancy by fixing a :math:`\Lambda` that depends on :math:`R,` leading to a unique -# value of :math:`Q^\prime = Q \Lambda,` and a uniform distribution of eigenvalues. -# -# .. admonition:: Try it! -# -# Use the ``qr_haar`` function above to generate random unitaries and construct -# a distribution of their eigenvalues. Then, comment out the lines for steps 3 and -# 4 and do the same—you'll find that the distribution is no longer uniform. -# Check out reference [#Mezzadri2006]_ for additional details and examples. - -###################################################################### -# Fun (and not-so-fun) facts -# -------------------------- -# -# We've now learned what the Haar measure is, and both an analytical and -# numerical means of sampling quantum states and unitary operations uniformly at -# random. The Haar measure also has many neat properties that play a role in -# quantum computing. -# -# Invariance of the Haar measure -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# -# Earlier, we showed that the Haar measure is used when integrating functions over -# the unitary group: -# -# .. math:: -# -# \int_{V \in U(N)} f(V) d\mu_N(V). -# -# One of the defining features of the Haar measure is that it is both left and -# right *invariant* under unitary transformations. That is, -# -# .. math:: -# -# \int_{V \in U(N)} f(\color{red}{W}V) d\mu_N(V) = \int_{V \in U(N)} f(V\color{red}{W}) d\mu_N(V) = \int_{V \in U(N)} f(V) d\mu_N(V). -# -# This holds true for *any* other :math:`N\times N` unitary :math:`W!` A -# consequence of such invariance is that if :math:`V` is Haar-random, then so is -# :math:`V^T,` :math:`V^\dagger,` and any product of another unitary matrix and -# :math:`V` (where the product may be taken on either side). -# -# Another consequence of this invariance has to do with the structure of the entries -# themselves: they must all come from the same distribution. This is because the -# measure remains invariant under permutations, since permutations are unitary--- -# the whole thing still has to be Haar random no matter how the entries are ordered, -# so all distributions must be the same. The specific distribution is complex -# numbers :math:`a+bi` where both :math:`a` and :math:`b` has mean 0 and variance -# :math:`1/N` [#Meckes2014]_ (so, much like Ginibre ensemble we used in the QR decomposition -# above, but with a different variance and constraints due to orthonormality). -# -# Concentration of measure -# ~~~~~~~~~~~~~~~~~~~~~~~~ -# -# An unfortunate (although interesting) property of the Haar measure is that it -# suffers from the phenomenon of `concentration of measure -# `__. Most of the -# "stuff" in the space concentrates around a certain area, and this gets worse -# as the size of the system increases. You can see the beginnings of by looking -# at the sphere. For the 3-dimensional sphere, we saw graphically how there is -# concentration around the equator, and how the measure takes that into account -# with the additional factor of :math:`\sin \theta.` This property becomes -# increasingly prominent for `higher-dimensional spheres -# `__. -# -# .. important:: -# -# The concentration described here is not referring to what we witnessed -# earlier on, when we sampled quantum states (points on the Bloch sphere) -# incorrectly and found that they clustered around the poles. However, that -# is not unrelated. Concentration of measure refers to where the measure -# itself is concentrated, and which parts of the space should be more heavily -# weighted. For the case of the sphere, it is the equatorial area, and when -# we didn't sample properly and take that concentration into account, we -# obtained an uneven distribution. -# -# Let's consider an :math:`N`-dimensional unit sphere. Points on the sphere, or -# vectors in this space, are parametrized by :math:`N-1` real coordinates. -# Suppose we have some function :math:`f` that maps points on that sphere to -# real numbers. Sample a point :math:`x` on that sphere from the uniform -# measure, and compute the value of :math:`f(x).` How close do you think the -# result will be to the mean value of the function, :math:`E[f],` over the -# entire sphere? -# -# A result called `Levy's lemma -# `__ -# [#Gerken2013]_, [#Hayden2006]_ expresses how likely it is that :math:`f(x)` is a specific -# distance away from the mean. It states that, for an :math:`x` selected -# uniformly at random, the probability that :math:`f(x)` deviates from -# :math:`E[f]` by some amount :math:`\epsilon` is bounded by: -# -# .. math:: -# -# \hbox{Pr}(|f(x) - E[f]| \ge \epsilon) \leq 2 \exp\left[-\frac{N\epsilon^2}{9\pi^3 \eta^2}\right]. -# -# A constraint on the function :math:`f` is that it must be `Lipschitz -# continuous `__, where -# :math:`\eta` is the *Lipschitz constant* of the function. The important aspect -# here is the likelihood of deviating significantly from the mean by an amount -# :math:`\epsilon` decreases exponentially with :math:`\epsilon.` Furthermore, -# increasing the dimension :math:`N` also makes the deviation exponentially less -# likely. -# -# Now, this result seems unrelated to quantum states—it concerns higher- -# dimensional spheres. However, recall that a quantum state vector is a complex -# vector whose squared values sum to 1, similar to vectors on a sphere. If you -# "unroll" a quantum state vector of dimension :math:`N = 2^n` by stacking its -# real and complex parts, you end with a vector of length :math:`2 \cdot 2^{n}` -# which ends up behaving just like a unit vector on the sphere in this -# dimension. Given that measure concentrates on spheres, and quantum state -# vectors can be converted to vectors on spheres, functions on random quantum -# states will also demonstrate concentration. -# -# This is bad news! To do useful things in quantum computing, we need a lot of -# qubits. But the more qubits we have, the more our randomly sampled states will -# look the same (specifically, random states will concentrate around the -# maximally entangled state [#Hayden2006]_). This has important consequences for -# near-term algorithms (as detailed in the next section), and any algorithm that -# involves uniform sampling of quantum states and operations. -# -# Haar measure and barren plateaus -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# -# Suppose you are venturing out to solve a new problem using an algorithm such -# as the :doc:`variational quantum eigensolver `. A -# critical component of such methods is the choice of :doc:`variational ansatz -# `. Having now learned a bit about the properties of -# the Haar measure, you may think it would make sense to use this for the -# parametrization. Variational ansaetze are, after all, parametrized quantum -# circuits, so why not choose an ansatz that corresponds directly to a -# parametrization for Haar-random unitaries? The initial parameter selection -# will give you a state in the Hilbert space uniformly at random. Then, since -# this ansatz spans the entire Hilbert space, you're guaranteed to be able to -# represent the target ground state with your ansatz, and it should be able to -# find it with no issue ... right? -# -# Unfortunately, while such an ansatz is extremely *expressive* (i.e., it is -# capable of representing any possible state), these ansaetze actually suffer -# the most from the barren plateau problem [#McClean2018]_, [#Holmes2021]_. -# :doc:`Barren plateaus ` are regions in the -# cost landscape of a parametrized circuit where both the gradient and its -# variance approach 0, leading the optimizer to get stuck in a local minimum. -# This was explored recently in the work of [#Holmes2021]_, wherein closeness to -# the Haar measure was actually used as a metric for expressivity. The closer -# things are to the Haar measure, the more expressive they are, but they are -# also more prone to exhibiting barren plateaus. -# -# -# .. figure:: /_static/demonstration_assets/haar_measure/holmes-costlandscapes.png -# :align: center -# :width: 50% -# -# Image source: [#Holmes2021]_. A highly expressive ansatz that can access -# much of the space of possible unitaries (i.e., an ansatz capable of -# producing unitaries in something close to a Haar-random manner) is very -# likely to have flat cost landscapes and suffer from the barren plateau -# problem. -# -# It turns out that the types of ansaetze know as *hardware-efficient ansaetze* -# also suffer from this problem if they are "random enough" (this notion will be -# formalized in a future demo). It was shown in [#McClean2018]_ that this is a -# consequence of the concentration of measure phenomenon described above. The -# values of gradients and variances can be computed for classes of circuits on -# average by integrating with respect to the Haar measure, and it is shown that -# these values decrease exponentially in the number of qubits, and thus huge -# swaths of the cost landscape are simply and fundamentally flat. -# -# Conclusion -# ---------- -# -# The Haar measure plays an important role in quantum computing—anywhere -# you might be dealing with sampling random circuits, or averaging over -# all possible unitary operations, you'll want to do so with respect -# to the Haar measure. -# -# There are two important aspects of this that we have yet to touch upon, -# however. The first is whether it is efficient to sample from the Haar measure—given -# that the number of parameters to keep track of is exponential in the -# number of qubits, certainly not. But a more interesting question is do we -# *need* to always sample from the full Haar measure? The answer to this is -# "no" in a very interesting way. Depending on the task at hand, you may be able -# to take a shortcut using something called a *unitary design*. In an upcoming -# demo, we will explore the amazing world of unitary designs and their -# applications! -# -# References -# ---------- -# -# .. [#NandC2000] -# -# M. A. Nielsen, and I. L. Chuang (2000) "Quantum Computation and Quantum Information", -# Cambridge University Press. -# -# .. [#deGuise2018] -# -# H. de Guise, O. Di Matteo, and L. L. Sánchez-Soto. (2018) "Simple factorization -# of unitary transformations", `Phys. Rev. A 97 022328 -# `__. -# (`arXiv `__) -# -# .. [#Clements2016] -# -# W. R. Clements, P. C. Humphreys, B. J. Metcalf, W. S. Kolthammer, and -# I. A. Walmsley (2016) “Optimal design for universal multiport -# interferometers”, \ `Optica 3, 1460–1465 -# `__. -# (`arXiv `__) -# -# .. [#Reck1994] -# -# M. Reck, A. Zeilinger, H. J. Bernstein, and P. Bertani (1994) “Experimental -# realization of any discrete unitary operator”, `Phys. Rev. Lett.73, 58–61 -# `__. -# -# .. [#Mezzadri2006] -# -# F. Mezzadri (2006) "How to generate random matrices from the classical compact groups". -# (`arXiv `__) -# -# .. [#Meckes2014] -# -# E. Meckes (2019) `"The Random Matrix Theory of the Classical Compact Groups" -# `_, Cambridge University Press. -# -# .. [#Gerken2013] -# -# M. Gerken (2013) "Measure concentration: Levy's Lemma" -# (`lecture notes `__). -# -# -# .. [#Hayden2006] -# -# P. Hayden, D. W. Leung, and A. Winter (2006) "Aspects of generic -# entanglement", `Comm. Math. Phys. Vol. 265, No. 1, pp. 95-117 -# `__. -# (`arXiv `__) -# -# .. [#McClean2018] -# -# J. R. McClean, S. Boixo, V. N. Smelyanskiy, R. Babbush, and H. Neven -# (2018) "Barren plateaus in quantum neural network training -# landscapes", `Nature Communications, 9(1) -# `__. -# (`arXiv `__) -# -# .. [#Holmes2021] -# -# Z. Holmes, K. Sharma, M. Cerezo, and P. J. Coles (2021) "Connecting ansatz -# expressibility to gradient magnitudes and barren plateaus". (`arXiv -# `__) +r""".. role:: html(raw) + :format: html + +Understanding the Haar measure +============================== + +.. meta:: + :property="og:description": Learn all about the Haar measure and how to randomly sample quantum states. + + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/spherical_int_dtheta.png + +.. related:: + + tutorial_unitary_designs Unitary designs + quantum_volume Quantum volume + qsim_beyond_classical Beyond classical computing with qsim + tutorial_barren_plateaus Barren plateaus in quantum neural networks + + +*Author: Olivia Di Matteo — Posted: 22 March 2021. Last updated: 22 March 2021.* + +If you've ever dug into the literature about random quantum circuits, +variational ansatz structure, or anything related to the structure and +properties of unitary operations, you've likely come across a statement like the +following: "Assume that :math:`U` is sampled uniformly at random from the Haar +measure". In this demo, we're going to unravel this cryptic statement and take +an in-depth look at what it means. You'll gain an understanding of the general +concept of a *measure*, the Haar measure and its special properties, and you'll +learn how to sample from it using tools available in PennyLane and other +scientific computing frameworks. By the end of this demo, you'll be able to +include that important statement in your own work with confidence! + +.. note:: + + To get the most out of this demo, it is helpful if you are familiar with + `integration of multi-dimensional functions + `__, the `Bloch sphere + `__, and the conceptual ideas + behind `decompositions + `__ and factorizations of + unitary matrices (see, e.g., 4.5.1 and 4.5.2 of [#NandC2000]_). + +Measure +------- + +`Measure theory `__ is a +branch of mathematics that studies things that are measurable—think length, +area, or volume, but generalized to mathematical spaces and even higher +dimensions. Loosely, the measure tells you about how "stuff" is distributed and +concentrated in a mathematical set or space. An intuitive way to understand +the measure is to think about a sphere. An arbitrary point on a sphere can be +parametrized by three numbers—depending on what you're doing, you may use +Cartesian coordinates :math:`(x, y, z),` or it may be more convenient to use +spherical coordinates :math:`(\rho, \phi, \theta).` + +Suppose you wanted to compute the volume of a solid sphere with radius +:math:`r.` This can be done by integrating over the three coordinates +:math:`\rho, \phi,` and :math:`\theta.` Your first thought here may be to simply +integrate each parameter over its full range, like so: + +.. math:: + + V = \int_0^{r} \int_0^{2\pi} \int_0^{\pi} d\rho~ d\phi~ d\theta = 2\pi^2 r + +But, we know that the volume of a sphere of radius :math:`r` is +:math:`\frac{4}{3}\pi r^3,` so what we got from this integral is clearly wrong! +Taking the integral naively like this doesn't take into account the structure of +the sphere with respect to the parameters. For example, consider +two small, infinitesimal elements of area with the same difference in +:math:`\theta` and :math:`\phi,` but at different values of :math:`\theta:` + +.. figure:: /_static/demonstration_assets/haar_measure/spherical_int_dtheta.png + :align: center + :width: 50% + + +Even though the differences :math:`d\theta` and :math:`d\phi` themselves are the +same, there is way more "stuff" near the equator of the sphere than there is +near the poles. We must take into account the value of :math:`\theta` when +computing the integral! Specifically, we multiply by the function +:math:`\sin\theta`---the properties of the :math:`\sin` function mean that the +most weight will occur around the equator where :math:`\theta=\pi/2,` and the +least weight near the poles where :math:`\theta=0` and :math:`\theta=\pi.` + +Similar care must be taken for :math:`\rho.` The contribution to volume of +parts of the sphere with a large :math:`\rho` is far more than for a small +:math:`\rho`---we should expect the contribution to be proportional to +:math:`\rho^2,` given that the surface area of a sphere of radius :math:`r` is +:math:`4\pi r^2.` + +On the other hand, for a fixed :math:`\rho` and :math:`\theta,` the length of +the :math:`d\phi` is the same all around the circle. If put all these facts +together, we find that the actual expression for the integral should look like +this: + +.. math:: + + V = \int_0^r \int_0^{2\pi} \int_0^{\pi} \rho^2 \sin \theta~ d\rho~ d\phi~ + d\theta = \frac{4}{3}\pi r^3 + +These extra terms that we had to add to the integral, :math:`\rho^2 \sin +\theta`, constitute the *measure*. The measure weights portions of the sphere +differently depending on where they are in the space. While we need to know the +measure to properly integrate over the sphere, knowledge of the measure also +gives us the means to perform another important task, that of sampling points in +the space uniformly at random. We can't simply sample each parameter from the +uniform distribution over its domain—as we experienced already, this doesn't +take into account how the sphere is spread out over space. The measure describes +the distribution of each parameter and gives a recipe for sampling them in order +to obtain something properly uniform. + +The Haar measure +---------------- + +Operations in quantum computing are described by unitary matrices. +Unitary matrices, like points on a sphere, can be expressed in terms of a fixed +set of coordinates, or parameters. For example, the most general single-qubit rotation +implemented in PennyLane (:class:`~.pennylane.Rot`) is expressed in terms of three +parameters like so, + +.. math:: + + U(\phi, \theta, \omega) = \begin{pmatrix} e^{-i(\phi + \omega)/2} + \cos(\theta/2) & -e^{i(\phi - \omega)/2} \sin(\theta/2) + \\ e^{-i(\phi - \omega)/2} \sin(\theta/2) & e^{i(\phi + + \omega)/2} \cos(\theta/2) \end{pmatrix}. + +For every dimension :math:`N,` the unitary matrices of size :math:`N \times N` +constitute the *unitary group* :math:`U(N).` We can perform operations on +elements of this group, such as apply functions to them, integrate over them, or +sample uniformly over them, just as we can do to points on a sphere. When we do +such tasks with respect to the sphere, we have to add the measure in order to +properly weight the different regions of space. The *Haar measure* provides the +analogous terms we need for working with the unitary group. + +For an :math:`N`-dimensional system, the Haar measure, often denoted by +:math:`\mu_N,` tells us how to weight the elements of :math:`U(N).` For +example, suppose :math:`f` is a function that acts on elements of :math:`U(N),` +and we would like to take its integral over the group. We must write this +integral with respect to the Haar measure, like so: + +.. math:: + + \int_{V \in U(N)} f(V) d\mu_N(V). + +As with the measure term of the sphere, :math:`d\mu_N` itself can be broken down +into components depending on individual parameters. While the Haar +measure can be defined for every dimension :math:`N,` the mathematical form gets +quite hairy for larger dimensions—in general, an :math:`N`-dimensional unitary +requires at least :math:`N^2 - 1` parameters, which is a lot to keep track of! +Therefore we'll start with the case of a single qubit :math:`(N=2),` then show +how things generalize. + +Single-qubit Haar measure +~~~~~~~~~~~~~~~~~~~~~~~~~ + +The single-qubit case provides a particularly nice entry point because we can +continue our comparison to spheres by visualizing single-qubit states on the +Bloch sphere. As expressed above, the measure provides a recipe for sampling +elements of the unitary group in a properly uniform manner, given the structure +of the group. One useful consequence of this is that it provides a method to +sample quantum *states* uniformly at random—we simply generate Haar-random +unitaries, and apply them to a fixed basis state such as :math:`\vert 0\rangle.` + +We'll see how this works in good time. First, we'll take a look at what happens +when we ignore the measure and do things *wrong*. Suppose we sample quantum +states by applying unitaries obtained by the parametrization above, but sample +the angles :math:`\omega, \phi,` and :math:`\theta` from the flat uniform +distribution between :math:`[0, 2\pi)` (fun fact: there is a measure implicit in +this kind of sampling too! It just has a constant value, because each point is +equally likely to be sampled). + +""" + +import pennylane as qml +import numpy as np +import matplotlib.pyplot as plt + +# set the random seed +np.random.seed(42) + +# Use the mixed state simulator to save some steps in plotting later +dev = qml.device('default.mixed', wires=1) + +@qml.qnode(dev) +def not_a_haar_random_unitary(): + # Sample all parameters from their flat uniform distribution + phi, theta, omega = 2 * np.pi * np.random.uniform(size=3) + qml.Rot(phi, theta, omega, wires=0) + return qml.state() + +num_samples = 2021 + +not_haar_samples = [not_a_haar_random_unitary() for _ in range(num_samples)] + +###################################################################### +# In order to plot these on the Bloch sphere, we'll need to do one more +# step, and convert the quantum states into Bloch vectors. +# + +X = np.array([[0, 1], [1, 0]]) +Y = np.array([[0, -1j], [1j, 0]]) +Z = np.array([[1, 0], [0, -1]]) + +# Used the mixed state simulator so we could have the density matrix for this part! +def convert_to_bloch_vector(rho): + """Convert a density matrix to a Bloch vector.""" + ax = np.trace(np.dot(rho, X)).real + ay = np.trace(np.dot(rho, Y)).real + az = np.trace(np.dot(rho, Z)).real + return [ax, ay, az] + +not_haar_bloch_vectors = np.array([convert_to_bloch_vector(s) for s in not_haar_samples]) + +###################################################################### +# With this done, let's find out where our "uniformly random" states ended up: + +def plot_bloch_sphere(bloch_vectors): + """ Helper function to plot vectors on a sphere.""" + fig = plt.figure(figsize=(6, 6)) + ax = fig.add_subplot(111, projection='3d') + fig.subplots_adjust(left=0, right=1, bottom=0, top=1) + + ax.grid(False) + ax.set_axis_off() + ax.view_init(30, 45) + + # Draw the axes (source: https://github.com/matplotlib/matplotlib/issues/13575) + x, y, z = np.array([[-1.5,0,0], [0,-1.5,0], [0,0,-1.5]]) + u, v, w = np.array([[3,0,0], [0,3,0], [0,0,3]]) + ax.quiver(x, y, z, u, v, w, arrow_length_ratio=0.05, color="black", linewidth=0.5) + + ax.text(0, 0, 1.7, r"|0⟩", color="black", fontsize=16) + ax.text(0, 0, -1.9, r"|1⟩", color="black", fontsize=16) + ax.text(1.9, 0, 0, r"|+⟩", color="black", fontsize=16) + ax.text(-1.7, 0, 0, r"|–⟩", color="black", fontsize=16) + ax.text(0, 1.7, 0, r"|i+⟩", color="black", fontsize=16) + ax.text(0,-1.9, 0, r"|i–⟩", color="black", fontsize=16) + + ax.scatter( + bloch_vectors[:,0], bloch_vectors[:,1], bloch_vectors[:, 2], c='#e29d9e', alpha=0.3 + ) + plt.show() + +plot_bloch_sphere(not_haar_bloch_vectors) + +###################################################################### +# You can see from this plot that even though our parameters were sampled from a +# uniform distribution, there is a noticeable amount of clustering around the poles +# of the sphere. Despite the input parameters being uniform, the output is very +# much *not* uniform. Just like the regular sphere, the measure is larger near +# the equator, and if we just sample uniformly, we won't end up populating that +# area as much. To take that into account we will need to sample from the proper +# Haar measure, and weight the different parameters appropriately. +# +# For a single qubit, the Haar measure looks much like the case of a sphere, +# minus the radial component. Intuitively, all qubit state vectors have length +# 1, so it makes sense that this wouldn't play a role here. The parameter that +# we will have to weight differently is :math:`\theta,` and in fact the +# adjustment in measure is identical to that we had to do with the polar axis of +# the sphere, i.e., :math:`\sin \theta.` In order to sample the :math:`\theta` +# uniformly at random in this context, we must sample from the distribution +# :math:`\hbox{Pr}(\theta) = \sin \theta.` We can accomplish this by setting up +# a custom probability distribution with +# `rv_continuous `__ +# in ``scipy``. + +from scipy.stats import rv_continuous + +class sin_prob_dist(rv_continuous): + def _pdf(self, theta): + # The 0.5 is so that the distribution is normalized + return 0.5 * np.sin(theta) + +# Samples of theta should be drawn from between 0 and pi +sin_sampler = sin_prob_dist(a=0, b=np.pi) + +@qml.qnode(dev) +def haar_random_unitary(): + phi, omega = 2 * np.pi * np.random.uniform(size=2) # Sample phi and omega as normal + theta = sin_sampler.rvs(size=1)[0] # Sample theta from our new distribution + qml.Rot(phi, theta, omega, wires=0) + return qml.state() + +haar_samples = [haar_random_unitary() for _ in range(num_samples)] +haar_bloch_vectors = np.array([convert_to_bloch_vector(s) for s in haar_samples]) + +plot_bloch_sphere(haar_bloch_vectors) + +###################################################################### +# We see that when we use the correct measure, our qubit states are now +# much better distributed over the sphere. Putting this information together, +# we can now write the explicit form for the single-qubit Haar measure: +# +# .. math:: +# +# d\mu_2 = \sin \theta d\theta \cdot d\omega \cdot d\phi. +# +# Show me more math! +# ~~~~~~~~~~~~~~~~~~ +# +# While we can easily visualize the single-qubit case, this is no longer +# possible when we increase the number of qubits. Regardless, we can still +# obtain a mathematical expression for the Haar measure in arbitrary +# dimensions. In the previous section, we expressed the Haar measure in terms of +# a set of parameters that can be used to specify the unitary group +# :math:`U(2).` Such a parametrization is not unique, and in fact there are +# multiple ways to *factorize*, or decompose an :math:`N`-dimensional unitary +# operation into a set of parameters. +# +# Many of these parametrizations come to us from the study of photonics. Here, +# arbitrary operations are broken down into elementary operations involving only +# a few parameters which correspond directly to parameters of the physical +# apparatus used to implement them (beamsplitters and phase shifts). Rather than +# qubits, such operations act on modes, or *qumodes*. They are expressed as +# elements of the :math:`N`-dimensional `special unitary group +# `__. This group, written +# as :math:`SU(N),` is the continuous group consisting of all :math:`N \times N` +# unitary operations with determinant 1 (essentially like :math:`U(N),` minus +# a potential global phase). +# +# +# .. note:: +# +# Elements of :math:`SU(N)` and :math:`U(N)` can still be considered as +# multi-qubit operations in the cases where :math:`N` is a power of 2, but +# they must be translated from continuous-variable operations into qubit +# operations. (In PennyLane, this can be done by feeding the unitaries to +# the :class:`~.pennylane.QubitUnitary` operation directly. Alternatively, +# one can use *quantum compilation* to express the operations as a sequence +# of elementary gates such as Pauli rotations and CNOTs.) +# +# .. admonition:: Tip +# +# If you haven't had many opportunities to work in terms of qumodes, the +# `Strawberry Fields documentation +# `__ is a +# good starting point. +# +# For example, we saw already above that for :math:`N=2,` we can write +# +# .. math:: +# +# U(\phi, \theta, \omega) = \begin{pmatrix} e^{-i(\phi + \omega)/2} +# \cos(\theta/2) & -e^{i(\phi - \omega)/2} \sin(\theta/2) +# \\ e^{-i(\phi - \omega)/2} \sin(\theta/2) & e^{i(\phi + +# \omega)/2} \cos(\theta/2) \end{pmatrix}. +# +# +# This unitary can be factorized as follows: +# +# .. math:: +# +# U(\phi, \theta, \omega) = +# \begin{pmatrix} +# e^{-i\omega/2} & 0 \\ 0 & e^{i\omega/2} +# \end{pmatrix} +# \begin{pmatrix} +# \cos(\theta/2) & -\sin(\theta/2) \\ \sin(\theta/2) & \cos(\theta/2) +# \end{pmatrix} +# \begin{pmatrix} +# e^{-i\phi/2} & 0 \\ 0 & e^{i\phi/2} +# \end{pmatrix} +# +# The middle operation is a beamsplitter; the other two operations are phase +# shifts. We saw earlier that for :math:`N=2,` :math:`d\mu_2 = \sin\theta +# d\theta d\omega d\phi`---note how the parameter in the beamsplitter +# contributes to the measure in a different way than those of the phase +# shifts. As mentioned above, for larger values of :math:`N` there are multiple +# ways to decompose the unitary. Such decompositions rewrite elements in +# :math:`SU(N)` acting on :math:`N` modes as a sequence of operations acting +# only on 2 modes, :math:`SU(2),` and single-mode phase shifts. Shown below are +# three examples [#deGuise2018]_, [#Clements2016]_, [#Reck1994]_: +# +# .. figure:: /_static/demonstration_assets/haar_measure/unitaries.png +# :align: center +# :width: 95% +# +# +# In these graphics, every wire is a different mode. Every box represents an +# operation on one or more modes, and the number in the box indicates the number +# of parameters. The boxes containing a ``1`` are simply phase shifts on +# individual modes. The blocks containing a ``3`` are :math:`SU(2)` transforms +# with 3 parameters, such as the :math:`U(\phi, \theta, \omega)` above. Those +# containing a ``2`` are :math:`SU(2)` transforms on pairs of modes with 2 +# parameters, similar to the 3-parameter ones but with :math:`\omega = \phi.` +# +# Although the decompositions all produce the same set of operations, their +# structure and parametrization may have consequences in practice. The first [#deGuise2018]_ +# has a particularly convenient form that leads to a recursive definition +# of the Haar measure. The decomposition is formulated recursively such that an +# :math:`SU(N)` operation can be implemented by sandwiching an :math:`SU(2)` +# transformation between two :math:`SU(N-1)` transformations, like so: +# +# | +# +# .. figure:: /_static/demonstration_assets/haar_measure/sun.svg +# :align: center +# :width: 80% +# +# | +# +# The Haar measure is then constructed recursively as a product of 3 +# terms. The first term depends on the parameters in the first :math:`SU(N-1)` +# transformation; the second depends on the parameters in the lone :math:`SU(2)` +# transformation; and the third term depends on the parameters in the other +# :math:`SU(N-1)` transformation. +# +# :math:`SU(2)` is the "base case" of the recursion—we simply have the Haar measure +# as expressed above. +# +# | +# +# .. figure:: /_static/demonstration_assets/haar_measure/su2_haar.svg +# :align: center +# :width: 25% +# +# | +# +# Moving on up, we can write elements of :math:`SU(3)` as a sequence of three +# :math:`SU(2)` transformations. The Haar measure :math:`d\mu_3` then consists +# of two copies of :math:`d\mu_2,` with an extra term in between to take into +# account the middle transformation. +# +# | +# +# .. figure:: /_static/demonstration_assets/haar_measure/su3_haar.svg +# :align: center +# :width: 80% +# +# | +# +# For :math:`SU(4)` and upwards, the form changes slightly, but still follows +# the pattern of two copies of :math:`d\mu_{N-1}` with a term in between. +# +# | +# +# .. figure:: /_static/demonstration_assets/haar_measure/su4_premerge.svg +# :align: center +# :width: 90% +# +# | +# +# For larger systems, however, the recursive composition allows for some of the +# :math:`SU(2)` transformations on the lower modes to be grouped. We can take +# advantage of this and aggregate some of the parameters: +# +# | +# +# .. figure:: /_static/demonstration_assets/haar_measure/su4_triangle_merge.svg +# :align: center +# :width: 100% +# +# | +# +# This leads to one copy of :math:`d\mu_{N-1},` which we'll denote as +# :math:`d\mu_{N-1}^\prime,` containing only a portion of the full set of terms +# (as detailed in [#deGuise2018]_, this is called a *coset measure*). +# +# | +# +# .. figure:: /_static/demonstration_assets/haar_measure/su4_haar.svg +# :align: center +# :width: 100% +# +# | +# +# Putting everything together, we have that +# +# .. math:: +# +# d\mu_N = d\mu_{N-1}^\prime \times \sin \theta_{N-1} +# \sin^{2(N-2)}\left(\frac{\theta_{N-1}}{2}\right) d\theta_{N-1} d\omega_{N-1} \times d\mu_{N-1} +# +# The middle portion depends on the value of :math:`N,` and the parameters +# :math:`\theta_{N-1}` and :math:`\omega_{N-1}` contained in the :math:`(N-1)`'th +# :math:`SU(N)` transformation. This is thus a convenient, systematic way to +# construct the :math:`N`-dimensional Haar measure for the unitary group. As a +# final note, even though unitaries can be parametrized in different ways, the +# underlying Haar measure is *unique*. This is a consequence of it being an +# invariant measure, as will be shown later. +# +# Haar-random matrices from the :math:`QR` decomposition +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Nice-looking math aside, sometimes you just need to generate a large number of +# high-dimensional Haar-random matrices. It would be very cumbersome to sample +# and keep track of the distributions of so many parameters; furthermore, the +# measure above requires you to parametrize your operations in a fixed way. +# There is a much quicker way to perform the sampling by taking a (slightly +# modified) `QR decomposition +# `__ of complex-valued +# matrices. This algorithm is detailed in [#Mezzadri2006]_, and consists of the +# following steps: +# +# 1. Generate an :math:`N \times N` matrix :math:`Z` with complex numbers :math:`a+bi` +# where both :math:`a` and :math:`b` are normally distributed with mean 0 and variance 1 +# (this is sampling from the distribution known as the *Ginibre ensemble*). +# 2. Compute a QR decomposition :math:`Z = QR.` +# 3. Compute the diagonal matrix :math:`\Lambda = \hbox{diag}(R_{ii}/|R_{ii}|).` +# 4. Compute :math:`Q^\prime = Q \Lambda,` which will be Haar-random. +# +# + +from numpy.linalg import qr + +def qr_haar(N): + """Generate a Haar-random matrix using the QR decomposition.""" + # Step 1 + A, B = np.random.normal(size=(N, N)), np.random.normal(size=(N, N)) + Z = A + 1j * B + + # Step 2 + Q, R = qr(Z) + + # Step 3 + Lambda = np.diag([R[i, i] / np.abs(R[i, i]) for i in range(N)]) + + # Step 4 + return np.dot(Q, Lambda) + +###################################################################### +# Let's check that this method actually generates Haar-random unitaries +# by trying it out for :math:`N=2` and plotting on the Bloch sphere. +# + +@qml.qnode(dev) +def qr_haar_random_unitary(): + qml.QubitUnitary(qr_haar(2), wires=0) + return qml.state() + +qr_haar_samples = [qr_haar_random_unitary() for _ in range(num_samples)] +qr_haar_bloch_vectors = np.array([convert_to_bloch_vector(s) for s in qr_haar_samples]) +plot_bloch_sphere(qr_haar_bloch_vectors) + +###################################################################### +# As expected, we find our qubit states are distributed uniformly over the +# sphere. This particular method is what's implemented in packages like +# ``scipy``'s `unitary_group +# `__ +# function. +# +# Now, it's clear that this method works, but it is also important to +# understand *why* it works. Step 1 is fairly straightforward—the base of our +# samples is a matrix full of complex values chosen from a typical +# distribution. This isn't enough by itself, since unitary matrices also +# have constraints—their rows and columns must be orthonormal. +# These constraints are where step 2 comes in—the outcome of a generic +# QR decomposition consists of an *orthonormal* matrix :math:`Q,` and and upper +# triangular matrix :math:`R.` Since our original matrix was complex-valued, we end +# up with a :math:`Q` that is in fact already unitary. But why not stop there? Why +# do we then perform steps 3 and 4? +# +# Steps 3 and 4 are needed because, while the QR decomposition yields a unitary, +# it is not a unitary that is properly Haar-random. In [#Mezzadri2006]_, it is +# explained that a uniform distribution over unitary matrices should also yield +# a uniform distribution over the *eigenvalues* of those matrices, i.e., every +# eigenvalue should be equally likely. Just using the QR decomposition out of +# the box produces an *uneven* distribution of eigenvalues of the unitaries! +# This discrepancy stems from the fact that the QR decomposition is not unique. +# We can take any unitary diagonal matrix :math:`\Lambda,` and re-express the decomposition +# as :math:`QR = Q\Lambda \Lambda^\dagger R = Q^\prime R^\prime.` Step 3 removes this +# redundancy by fixing a :math:`\Lambda` that depends on :math:`R,` leading to a unique +# value of :math:`Q^\prime = Q \Lambda,` and a uniform distribution of eigenvalues. +# +# .. admonition:: Try it! +# +# Use the ``qr_haar`` function above to generate random unitaries and construct +# a distribution of their eigenvalues. Then, comment out the lines for steps 3 and +# 4 and do the same—you'll find that the distribution is no longer uniform. +# Check out reference [#Mezzadri2006]_ for additional details and examples. + +###################################################################### +# Fun (and not-so-fun) facts +# -------------------------- +# +# We've now learned what the Haar measure is, and both an analytical and +# numerical means of sampling quantum states and unitary operations uniformly at +# random. The Haar measure also has many neat properties that play a role in +# quantum computing. +# +# Invariance of the Haar measure +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Earlier, we showed that the Haar measure is used when integrating functions over +# the unitary group: +# +# .. math:: +# +# \int_{V \in U(N)} f(V) d\mu_N(V). +# +# One of the defining features of the Haar measure is that it is both left and +# right *invariant* under unitary transformations. That is, +# +# .. math:: +# +# \int_{V \in U(N)} f(\color{red}{W}V) d\mu_N(V) = \int_{V \in U(N)} f(V\color{red}{W}) d\mu_N(V) = \int_{V \in U(N)} f(V) d\mu_N(V). +# +# This holds true for *any* other :math:`N\times N` unitary :math:`W!` A +# consequence of such invariance is that if :math:`V` is Haar-random, then so is +# :math:`V^T,` :math:`V^\dagger,` and any product of another unitary matrix and +# :math:`V` (where the product may be taken on either side). +# +# Another consequence of this invariance has to do with the structure of the entries +# themselves: they must all come from the same distribution. This is because the +# measure remains invariant under permutations, since permutations are unitary--- +# the whole thing still has to be Haar random no matter how the entries are ordered, +# so all distributions must be the same. The specific distribution is complex +# numbers :math:`a+bi` where both :math:`a` and :math:`b` has mean 0 and variance +# :math:`1/N` [#Meckes2014]_ (so, much like Ginibre ensemble we used in the QR decomposition +# above, but with a different variance and constraints due to orthonormality). +# +# Concentration of measure +# ~~~~~~~~~~~~~~~~~~~~~~~~ +# +# An unfortunate (although interesting) property of the Haar measure is that it +# suffers from the phenomenon of `concentration of measure +# `__. Most of the +# "stuff" in the space concentrates around a certain area, and this gets worse +# as the size of the system increases. You can see the beginnings of by looking +# at the sphere. For the 3-dimensional sphere, we saw graphically how there is +# concentration around the equator, and how the measure takes that into account +# with the additional factor of :math:`\sin \theta.` This property becomes +# increasingly prominent for `higher-dimensional spheres +# `__. +# +# .. important:: +# +# The concentration described here is not referring to what we witnessed +# earlier on, when we sampled quantum states (points on the Bloch sphere) +# incorrectly and found that they clustered around the poles. However, that +# is not unrelated. Concentration of measure refers to where the measure +# itself is concentrated, and which parts of the space should be more heavily +# weighted. For the case of the sphere, it is the equatorial area, and when +# we didn't sample properly and take that concentration into account, we +# obtained an uneven distribution. +# +# Let's consider an :math:`N`-dimensional unit sphere. Points on the sphere, or +# vectors in this space, are parametrized by :math:`N-1` real coordinates. +# Suppose we have some function :math:`f` that maps points on that sphere to +# real numbers. Sample a point :math:`x` on that sphere from the uniform +# measure, and compute the value of :math:`f(x).` How close do you think the +# result will be to the mean value of the function, :math:`E[f],` over the +# entire sphere? +# +# A result called `Levy's lemma +# `__ +# [#Gerken2013]_, [#Hayden2006]_ expresses how likely it is that :math:`f(x)` is a specific +# distance away from the mean. It states that, for an :math:`x` selected +# uniformly at random, the probability that :math:`f(x)` deviates from +# :math:`E[f]` by some amount :math:`\epsilon` is bounded by: +# +# .. math:: +# +# \hbox{Pr}(|f(x) - E[f]| \ge \epsilon) \leq 2 \exp\left[-\frac{N\epsilon^2}{9\pi^3 \eta^2}\right]. +# +# A constraint on the function :math:`f` is that it must be `Lipschitz +# continuous `__, where +# :math:`\eta` is the *Lipschitz constant* of the function. The important aspect +# here is the likelihood of deviating significantly from the mean by an amount +# :math:`\epsilon` decreases exponentially with :math:`\epsilon.` Furthermore, +# increasing the dimension :math:`N` also makes the deviation exponentially less +# likely. +# +# Now, this result seems unrelated to quantum states—it concerns higher- +# dimensional spheres. However, recall that a quantum state vector is a complex +# vector whose squared values sum to 1, similar to vectors on a sphere. If you +# "unroll" a quantum state vector of dimension :math:`N = 2^n` by stacking its +# real and complex parts, you end with a vector of length :math:`2 \cdot 2^{n}` +# which ends up behaving just like a unit vector on the sphere in this +# dimension. Given that measure concentrates on spheres, and quantum state +# vectors can be converted to vectors on spheres, functions on random quantum +# states will also demonstrate concentration. +# +# This is bad news! To do useful things in quantum computing, we need a lot of +# qubits. But the more qubits we have, the more our randomly sampled states will +# look the same (specifically, random states will concentrate around the +# maximally entangled state [#Hayden2006]_). This has important consequences for +# near-term algorithms (as detailed in the next section), and any algorithm that +# involves uniform sampling of quantum states and operations. +# +# Haar measure and barren plateaus +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Suppose you are venturing out to solve a new problem using an algorithm such +# as the :doc:`variational quantum eigensolver `. A +# critical component of such methods is the choice of :doc:`variational ansatz +# `. Having now learned a bit about the properties of +# the Haar measure, you may think it would make sense to use this for the +# parametrization. Variational ansaetze are, after all, parametrized quantum +# circuits, so why not choose an ansatz that corresponds directly to a +# parametrization for Haar-random unitaries? The initial parameter selection +# will give you a state in the Hilbert space uniformly at random. Then, since +# this ansatz spans the entire Hilbert space, you're guaranteed to be able to +# represent the target ground state with your ansatz, and it should be able to +# find it with no issue ... right? +# +# Unfortunately, while such an ansatz is extremely *expressive* (i.e., it is +# capable of representing any possible state), these ansaetze actually suffer +# the most from the barren plateau problem [#McClean2018]_, [#Holmes2021]_. +# :doc:`Barren plateaus ` are regions in the +# cost landscape of a parametrized circuit where both the gradient and its +# variance approach 0, leading the optimizer to get stuck in a local minimum. +# This was explored recently in the work of [#Holmes2021]_, wherein closeness to +# the Haar measure was actually used as a metric for expressivity. The closer +# things are to the Haar measure, the more expressive they are, but they are +# also more prone to exhibiting barren plateaus. +# +# +# .. figure:: /_static/demonstration_assets/haar_measure/holmes-costlandscapes.png +# :align: center +# :width: 50% +# +# Image source: [#Holmes2021]_. A highly expressive ansatz that can access +# much of the space of possible unitaries (i.e., an ansatz capable of +# producing unitaries in something close to a Haar-random manner) is very +# likely to have flat cost landscapes and suffer from the barren plateau +# problem. +# +# It turns out that the types of ansaetze know as *hardware-efficient ansaetze* +# also suffer from this problem if they are "random enough" (this notion will be +# formalized in a future demo). It was shown in [#McClean2018]_ that this is a +# consequence of the concentration of measure phenomenon described above. The +# values of gradients and variances can be computed for classes of circuits on +# average by integrating with respect to the Haar measure, and it is shown that +# these values decrease exponentially in the number of qubits, and thus huge +# swaths of the cost landscape are simply and fundamentally flat. +# +# Conclusion +# ---------- +# +# The Haar measure plays an important role in quantum computing—anywhere +# you might be dealing with sampling random circuits, or averaging over +# all possible unitary operations, you'll want to do so with respect +# to the Haar measure. +# +# There are two important aspects of this that we have yet to touch upon, +# however. The first is whether it is efficient to sample from the Haar measure—given +# that the number of parameters to keep track of is exponential in the +# number of qubits, certainly not. But a more interesting question is do we +# *need* to always sample from the full Haar measure? The answer to this is +# "no" in a very interesting way. Depending on the task at hand, you may be able +# to take a shortcut using something called a *unitary design*. In an upcoming +# demo, we will explore the amazing world of unitary designs and their +# applications! +# +# References +# ---------- +# +# .. [#NandC2000] +# +# M. A. Nielsen, and I. L. Chuang (2000) "Quantum Computation and Quantum Information", +# Cambridge University Press. +# +# .. [#deGuise2018] +# +# H. de Guise, O. Di Matteo, and L. L. Sánchez-Soto. (2018) "Simple factorization +# of unitary transformations", `Phys. Rev. A 97 022328 +# `__. +# (`arXiv `__) +# +# .. [#Clements2016] +# +# W. R. Clements, P. C. Humphreys, B. J. Metcalf, W. S. Kolthammer, and +# I. A. Walmsley (2016) “Optimal design for universal multiport +# interferometers”, \ `Optica 3, 1460–1465 +# `__. +# (`arXiv `__) +# +# .. [#Reck1994] +# +# M. Reck, A. Zeilinger, H. J. Bernstein, and P. Bertani (1994) “Experimental +# realization of any discrete unitary operator”, `Phys. Rev. Lett.73, 58–61 +# `__. +# +# .. [#Mezzadri2006] +# +# F. Mezzadri (2006) "How to generate random matrices from the classical compact groups". +# (`arXiv `__) +# +# .. [#Meckes2014] +# +# E. Meckes (2019) `"The Random Matrix Theory of the Classical Compact Groups" +# `_, Cambridge University Press. +# +# .. [#Gerken2013] +# +# M. Gerken (2013) "Measure concentration: Levy's Lemma" +# (`lecture notes `__). +# +# +# .. [#Hayden2006] +# +# P. Hayden, D. W. Leung, and A. Winter (2006) "Aspects of generic +# entanglement", `Comm. Math. Phys. Vol. 265, No. 1, pp. 95-117 +# `__. +# (`arXiv `__) +# +# .. [#McClean2018] +# +# J. R. McClean, S. Boixo, V. N. Smelyanskiy, R. Babbush, and H. Neven +# (2018) "Barren plateaus in quantum neural network training +# landscapes", `Nature Communications, 9(1) +# `__. +# (`arXiv `__) +# +# .. [#Holmes2021] +# +# Z. Holmes, K. Sharma, M. Cerezo, and P. J. Coles (2021) "Connecting ansatz +# expressibility to gradient magnitudes and barren plateaus". (`arXiv +# `__) # \ No newline at end of file diff --git a/demonstrations_v2/tutorial_here_comes_the_sun/demo.py b/demonstrations_v2/tutorial_here_comes_the_sun/demo.py index 88da27d3e5..5278e01fd1 100644 --- a/demonstrations_v2/tutorial_here_comes_the_sun/demo.py +++ b/demonstrations_v2/tutorial_here_comes_the_sun/demo.py @@ -1,576 +1,576 @@ -r""" - -Here comes the SU(N): multivariate quantum gates and gradients -============================================================== - -.. meta:: - :property="og:description": Learn about multivariate quantum gates for optimization - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_here_comes_the_sun.png - -.. related:: - - tutorial_vqe A brief overview of VQE - tutorial_general_parshift General parameter-shift rules for quantum gradients - tutorial_unitary_designs Unitary designs and their uses in quantum computing - - -*Author: David Wierichs — Posted: 03 April 2023.* - -How do we choose an ansatz when designing a quantum circuit for a variational -quantum algorithm? And what happens if we do not start with elementary hardware-friendly -gates and compose them, but we instead use a more complex building block for local qubit -interactions and allow for multi-parameter gates from the start? -Can we differentiate such circuits, and how do they perform in optimization tasks? - -Let's find out! - -In this tutorial, you will learn about the :math:`\mathrm{SU}(N)` gate -:class:`~pennylane.SpecialUnitary`, a particular quantum gate which -can act like *any* gate on its qubits by choosing the parameters accordingly. -We will look at a custom derivative rule [#wiersema]_ for this gate and compare it to two -alternative differentiation strategies, namely finite differences and the `stochastic -parameter-shift rule `_. -Finally, we will compare the performance of -``qml.SpecialUnitary`` for a toy minimization problem to that of two other general -local gates. That is, we compare the trainability of equally expressive ansätze. - -Ansätze, so many ansätze ------------------------- - -Variational quantum algorithms have been promoted to be useful for many applications. -When designing these algorithms, a central task is to choose the quantum circuit ansatz, -which provides a parameterization of quantum states. In the course of a variational algorithm, -the circuit parameters are then optimized in order to minimize some cost function. -The choice of the ansatz can have a big impact on the quantum states that can be found -by the algorithm (expressivity) and on the optimization's behaviour (trainability). - -Typically, it also affects the -computational cost of executing the algorithm on quantum hardware and the strength of the noise -that enters the computation. Finally, the application itself influences, or -even fixes, the choice of ansatz for some variational quantum algorithms, -which can lead to constraints in the ansatz design. - -.. figure:: ../_static/demonstration_assets/here_comes_the_sun/SUN_demo_Ansatz.png - :align: center - :width: 90% - -While a number of best practices for ansatz design have been developed, -a lot is still unknown about the connection between circuit structures and the -resulting properties. Therefore, circuit design is often also based on intuition or heuristics; -an ansatz reported in the literature might just have turned out -to work particularly well for a given problem or might fall into a "standard" -category of circuits. - -If the application does not constrain the choice of ansatz, we may want to avoid choosing -somewhat arbitrary circuit ansätze that may introduce undesirable biases. -Instead, we will want to reflect the generic structure of the problem by performing a -fully general operation on the qubit register. -However, if we were to do so, the number of parameters required to produce such a general -operation would grow much too quickly. Instead, we want to consider fully general operations -*on a few qubits* and compose them into a fabric of local gates. For two-qubit operations, -the fabric could look like this: - -.. figure:: ../_static/demonstration_assets/here_comes_the_sun/SUN_demo_SU4.png - :align: center - :width: 60% - -The general local operation can be implemented by composing a suitable combination -of elementary gates, like single-qubit rotations and CNOT gates. Alternatively, we may -choose a canonical parameterization of the group that contains all local operations, and we will -see that this is an advantageous approach for the trainability of the ansatz. - -.. figure:: ../_static/demonstration_assets/here_comes_the_sun/SUN_demo_optimization.png - :align: center - :width: 60% - -Before we can use the :math:`\mathrm{SU}(N)` gate in training, we will need to -learn how to differentiate it in a quantum circuit. But first things first: -let's start with a brief math intro — no really, just a *Liettle* bit. - -The special unitary group SU(N) and its Lie algebra ---------------------------------------------------- - -The gate we will look at is given by a specific parameterization of the -`special unitary group `__ -:math:`\mathrm{SU}(N),` where :math:`N=2^n` is the Hilbert space dimension of the gate -for :math:`n` qubits. Mathematically, the group can be defined as the set of operators -(or matrices) that can be inverted by taking their adjoint and that have -determinant :math:`1.` In general, all quantum gates acting on :math:`n` qubits are -elements of :math:`\mathrm{SU}(N)` up to a global phase. - -The group :math:`\mathrm{SU}(N)` is a `Lie group `__, -and its associated `Lie algebra `__ -is :math:`\mathfrak{su}(N).` For our purposes, it will be sufficient to look at a matrix -representation of the algebra and we may define it as - -.. math:: - - \mathfrak{su}(N) = - \{\Omega \in \mathbb{C}^{N\times N}: \Omega^\dagger=-\Omega, \operatorname{Tr}[\Omega]=0\}. - -The conditions are that the elements :math:`\Omega` are *skew-Hermitian* and that their trace vanishes. -We will use so-called canonical coordinates for the algebra which are simply the coefficients -in the Pauli basis. That is, we consider the Pauli basis elements multiplied with the -imaginary unit :math:`i,` except for the identity: - -.. math:: - - G_m \in \mathcal{P}^{(n)} = i \left\{I,X,Y,Z\right\}^n \setminus \{i I^n\}. - -A Lie algebra element :math:`\Omega` can be written as - -.. math:: - - \Omega = \sum_{m=1}^d \theta_m G_m,\quad \theta_m \in \mathbb{R} - -and those coefficients :math:`\theta` are precisely the canonical coordinates. -You may ask why we included the prefactor :math:`i` in the definition of :math:`G_m` and why we excluded -the identity (times :math:`i`). This was done to match the properties of :math:`\mathfrak{su}(N);` -the prefactor makes the basis elements skew-Hermitian and the identity would not have a -vanishing trace. Indeed, one can check that the dimension of :math:`\mathfrak{su}(N)` is -:math:`4^n-1` and that there are :math:`4^n` Pauli words, so that one Pauli word — the identity — had to go -in any case... We can use the canonical coordinates of the algebra to express a *group element* in -:math:`\mathrm{SU}(N)` as well, and the ``qml.SpecialUnitary`` gate we will use is defined as - -.. math:: - - U(\boldsymbol{\theta}) = \exp\left\{\sum_{m=1}^d \theta_m G_m \right\}. - -The number of coordinates and Pauli words in :math:`\mathcal{P}^{(n)}` is :math:`d=4^n-1.` -Therefore, this will be the number of parameters that a single ``qml.SpecialUnitary`` gate acting on -:math:`n` qubits will take. For example, it takes just three parameters for a single qubit, which -is why :class:`~pennylane.Rot` and :class:`~pennylane.U3` take three parameters and may -produce *any* single-qubit rotation. It takes a modest 15 parameters for two qubits, -but it already requires 63 parameters for three qubits. - -For unitaries generated by a single operator, i.e. of the form :math:`\exp(i\theta G),` -there is a plethora of differentiation techniques that allow us to compute its derivative. -However, a standard parameter-shift rule, for example, will not do the job if there are -non-commuting terms :math:`G_m` in the multi-parameter gate :math:`U(\boldsymbol{\theta})` above. -So how *do* we compute the derivative? - -Obtaining the gradient ----------------------- - -In variational quantum algorithms, we typically use the circuit to prepare a quantum state and -then we measure some observable :math:`H.` The resulting real-valued output is considered to be the -cost function :math:`C` that should be minimized. If we want to use gradient-based optimization for -this task, we need a method to compute the gradient :math:`\nabla C` in addition to the cost -function itself. As derived in the publication [#wiersema]_, this is possible on quantum hardware -for :math:`\mathrm{SU}(N)` gates as long as the gates themselves can be implemented. -The implementation in PennyLane follows the decomposition idea described in App. F3, but the -main text of [#wiersema]_ proposes an additional method that scales better in some scenarios -(the caveat being that this method requires additional gates to be available on the quantum hardware). -Here, we will focus on the former method. -We will not go through the entire derivation, but note the following key points: - -- The gradient with respect to all :math:`d` parameters of an :math:`\mathrm{SU}(N)` gate can be - computed using :math:`2d` auxiliary circuits. Each of the circuits contains one additional - operation compared to the original circuit, namely a ``qml.PauliRot`` gate with rotation - angles of :math:`\pm\frac{\pi}{2}.` Note that these Pauli rotations act on up to :math:`n` - qubits. -- This differentiation method uses automatic differentiation during compilation and - classical coprocessing steps, but is compatible with quantum hardware. For large :math:`n,` - the classical processing steps can quickly become prohibitively expensive. -- The computed gradient is not an approximative technique but allows for an exact computation - of the gradient on simulators. On quantum hardware, this leads to unbiased gradient - estimators. - -The implementation in PennyLane takes care of creating the additional circuits and evaluating -them, and with adequate post-processing we get the gradient :math:`\nabla C.` - -Comparing gradient methods --------------------------- - -Before we dive into using ``qml.SpecialUnitary`` in an optimization task, let's compare -a few methods to compute the gradient with respect to the parameters of such a gate. -In particular, we will look at a finite difference (FD) approach, the stochastic parameter-shift -rule, and the custom gradient method we described above. - -For the first approach, we will use the standard central difference recipe given by - -.. math:: - - \partial_{\text{FD},\theta_j}C(\boldsymbol{\theta}) - =\left[C\left(\boldsymbol{\theta}+\frac{\delta}{2}\boldsymbol{e}_j\right) - -C\left(\boldsymbol{\theta}-\frac{\delta}{2}\boldsymbol{e}_j\right)\right] / \delta. - -Here, :math:`\delta` is a shift parameter that we need to choose and :math:`\boldsymbol{e}_j` is the -:math:`j`-th canonical basis vector, i.e. the all-zeros vector with a one in the -:math:`j`-th entry. This approach is agnostic to the differentiated function and does -not exploit its structure. - -In contrast, the stochastic parameter-shift rule is a differentiation recipe developed particularly -for multi-parameter gates like the :math:`\mathrm{SU}(N)` gates [#banchi]_. It involves the -approximate evaluation of an integral by sampling *splitting times* :math:`\tau` and -evaluating an expression close to the non-stochastic parameter-shift rule for each sample. -For more details, also consider the -:doc:`demo on the stochastic parameter-shift rule `. - -So, let's dive into a toy example and explore the three gradient methods! -We start by defining a simple one-qubit circuit that contains a single :math:`\mathrm{SU}(2)` -gate and measures the expectation value of :math:`H=\frac{3}{5} Z - \frac{4}{5} Y.` -As ``qml.SpecialUnitary`` requires automatic differentiation subroutines even for the -hardware-ready derivative recipe, we will make use of JAX. -""" - -import pennylane as qml -import numpy as np -import jax - -jax.config.update("jax_enable_x64", True) -jax.config.update("jax_platform_name", "cpu") -jnp = jax.numpy - -dev = qml.device("default.qubit", wires=1) -H = 0.6 * qml.PauliZ(0) - 0.8 * qml.PauliY(0) - - -def qfunc(theta): - qml.SpecialUnitary(theta, wires=0) - return qml.expval(H) - - -circuit = qml.QNode(qfunc, dev, interface="jax", diff_method="parameter-shift") - -theta = jnp.array([0.4, 0.2, -0.5]) - -############################################################################## -# Now we need to set up the differentiation methods. For this demonstration, we will -# keep the first and last entry of ``theta`` fixed and only compute the gradient for the -# second parameter. This allows us to visualize the results easily and keeps the -# computational effort to a minimum. -# -# We start with the finite-difference -# recipe, using a shift scale of :math:`\delta=0.75.` This choice of :math:`\delta,` -# which is much larger than usual for numerical differentiation on classical computers, -# is adapted to the scenario of shot-based gradients (see App. F2 of [#wiersema]_). -# We compute the derivative with respect to the second entry of theta, so we will use -# the unit vector :math:`e_2:` - -unit_vector = np.array([0.0, 1.0, 0.0]) - - -def central_diff_grad(theta, delta): - plus_eval = circuit(theta + delta / 2 * unit_vector) - minus_eval = circuit(theta - delta / 2 * unit_vector) - return (plus_eval - minus_eval) / delta - - -delta = 0.75 -print(f"Central difference: {central_diff_grad(theta, delta):.5f}") - -############################################################################## -# Next up, we implement the stochastic parameter-shift rule. Of course we do not do -# so in full generality, but for the particular circuit in this example. We will -# sample ten splitting times to obtain the gradient entry. For each splitting time, -# we need to insert a Pauli-:math:`Y` rotation because :math:`\theta_2` belongs to -# the Pauli-:math:`Y` component of :math:`A(\boldsymbol{\theta}).` For this, we define -# an auxiliary circuit. - - -@jax.jit -@qml.qnode(dev, interface="jax") -def aux_circuit(theta, tau, sign): - qml.SpecialUnitary(tau * theta, wires=0) - # This corresponds to the parameter-shift evaluations of RY at 0 - qml.RY(-sign * np.pi / 2, wires=0) - qml.SpecialUnitary((1 - tau) * theta, wires=0) - return qml.expval(H) - - -def stochastic_parshift_grad(theta, num_samples): - grad = 0 - splitting_times = np.random.random(size=num_samples) - for tau in splitting_times: - # Evaluate the two-term parameter-shift rule of the auxiliar circuit - grad += aux_circuit(theta, tau, 1.0) - aux_circuit(theta, tau, -1.0) - return grad / num_samples - - -num_samples = 10 -print(f"Stochastic parameter-shift: {stochastic_parshift_grad(theta, num_samples):.5f}") - -############################################################################## -# Finally, we can make use of the custom parameter-shift rule introduced in -# [#wiersema]_, which is readily available in PennyLane. Due to the implementation -# chosen internally, the full gradient is returned; we need to pick the second -# gradient entry manually. For this small toy problem, this is -# not an issue. - -sun_grad = jax.grad(circuit) -print(f"Custom SU(N) gradient: {sun_grad(theta)[1]:.5f}") - -############################################################################## -# We obtained three values for the gradient of interest, and they do not agree. -# So what is going on here? First, let's use automatic differentiation to compute -# the exact value and see which method agrees with it (we again need to extract the -# corresponding entry from the full gradient). - -autodiff_circuit = qml.QNode(qfunc, dev, interface="jax", diff_method="parameter-shift") -exact_grad = jax.grad(autodiff_circuit)(theta)[1] -print(f"Exact gradient: {exact_grad:.5f}") - -############################################################################## -# As we can see, automatic differentiation confirmed that the custom differentiation method -# gave us the correct result. Why do the other methods disagree? -# This is because the finite difference recipe is an *approximate* gradient -# method. This means it has an error even if all circuit evaluations are -# made exact (up to numerical precision) like in the example above. -# As for the stochastic parameter-shift rule, you may already guess why there is -# a deviation: indeed, the *stochastic* nature of this method leads to derivative -# values that are scattered around the true value. It is an unbiased estimator, -# so the average will approach the exact value with increasingly many evaluations. -# To demonstrate this, let's compute the same derivative many times and plot -# a histogram of what we get. We'll do so for ``num_samples=2``, ``10`` and ``100``. - -import matplotlib.pyplot as plt - -plt.rcParams.update({"font.size": 12}) - -fig, ax = plt.subplots(1, 1, figsize=(6, 4)) -colors = ["#ACE3FF", "#FF87EB", "#FFE096"] -for num_samples, color in zip([2, 10, 100], colors): - grads = [stochastic_parshift_grad(theta, num_samples) for _ in range(1000)] - ax.hist(grads, label=f"{num_samples} samples", alpha=0.9, color=color) -ylim = ax.get_ylim() -ax.plot([exact_grad] * 2, ylim, ls="--", c="k", label="Exact") -ax.set(xlabel=r"$\partial_{SPS,\theta_2}C(\theta)$", ylabel="Frequency", ylim=ylim) -ax.legend(loc="upper left") -plt.tight_layout() -plt.show() - -############################################################################## -# As we can see, the stochastic parameter-shift rule comes with a variance -# that can be reduced at the additional cost of evaluating the auxiliary circuit -# for more splitting times. -# -# On quantum hardware, all measurement results are statistical in nature anyway. -# So how does this stochasticity combine with the -# three differentiation methods? We will not go into detail here, but refer -# to [#wiersema]_ to see how the custom differentiation rule proposed in the -# main text leads to the lowest mean squared error. For a single-qubit circuit -# similar to the one above, but with the single gate :math:`U(\boldsymbol{\theta})=\exp(iaX+ibY),` -# the derivative and its expected variance are shown in the following -# (recoloured) plot from the manuscript: -# -# .. figure:: ../_static/demonstration_assets/here_comes_the_sun/sampled_grad.png -# :align: center -# :width: 70% -# -# As we can see, the custom :math:`\mathrm{SU}(N)` parameter-shift rule produces the -# gradient estimates with the smallest variance. For small values of the parameter -# :math:`b,` which is fixed for each panel, the custom shift rule and the stochastic -# shift rule approach the standard two-term parameter-shift rule, which would be exact -# for :math:`b=0.` -# The finite difference gradient shown here was obtained using the shift -# scale :math:`\delta=0.75,` as well. As we can see, this suppresses the variance down to -# a level comparable to those of the shift rule derivatives and this shift scale is a -# reasonable trade-off between the variance and the systematic error we observed earlier. -# As shown in App. F3 of [#wiersema]_, this scale is indeed close to the optimal choice -# if we were to compute the gradient with 100 shots per circuit. -# -# Comparing ansatz structures -# --------------------------- -# -# We discussed above that there are many circuit architectures available and that choosing -# a suitable ansatz is important but can be difficult. Here, we will compare a simple ansatz -# based on the ``qml.SpecialUnitary`` gate discussed above to other approaches that fully -# parametrize the special unitary group for the respective number of qubits. -# In particular, we will compare ``qml.SpecialUnitary`` to standard decompositions from the -# literature that parametrize :math:`\mathrm{SU}(N)` with elementary gates, as well as to a -# sequence of Pauli rotation gates that also allows us to create any special unitary. -# Let us start by defining the decomposition of a two-qubit unitary. -# We choose the decomposition, which is optimal but not unique, from [#vatan]_. -# The Pauli rotation sequence is available in PennyLane -# via ``qml.ArbitraryUnitary`` and we will not need to implement it ourselves. - - -def two_qubit_decomp(params, wires): - """Implement an arbitrary SU(4) gate on two qubits - using the decomposition from Theorem 5 in - https://arxiv.org/pdf/quant-ph/0308006.pdf""" - i, j = wires - # Single U(2) parameterization on both qubits separately - qml.Rot(*params[:3], wires=i) - qml.Rot(*params[3:6], wires=j) - qml.CNOT(wires=[j, i]) # First CNOT - qml.RZ(params[6], wires=i) - qml.RY(params[7], wires=j) - qml.CNOT(wires=[i, j]) # Second CNOT - qml.RY(params[8], wires=j) - qml.CNOT(wires=[j, i]) # Third CNOT - # Single U(2) parameterization on both qubits separately - qml.Rot(*params[9:12], wires=i) - qml.Rot(*params[12:15], wires=j) - - -# The three building blocks on two qubits we will compare are: -operations = { - ("Decomposition", "decomposition"): two_qubit_decomp, - ("PauliRot sequence",) * 2: qml.ArbitraryUnitary, - ("$\mathrm{SU}(N)$ gate", "SU(N) gate"): qml.SpecialUnitary, -} - -############################################################################## -# Now that we have the template for the composition approach in place, we construct a toy -# problem to solve using the ansätze. We will sample a random Hamiltonian in the Pauli basis -# (this time without the prefactor :math:`i,` as we want to construct a Hermitian operator) -# with independent coefficients that follow a normal distribution: -# -# .. math:: -# -# H = \sum_{m=1}^d h_m G_m,\quad h_m\sim \mathcal{N}(0,1). -# -# We will work with six qubits. - -num_wires = 6 -wires = list(range(num_wires)) -np.random.seed(62213) - -coefficients = np.random.randn(4**num_wires - 1) -# Create the matrices for the entire Pauli basis -basis = qml.ops.qubit.special_unitary.pauli_basis_matrices(num_wires) -# Construct the Hamiltonian from the normal random coefficients and the basis -H_matrix = qml.math.tensordot(coefficients, basis, axes=[[0], [0]]) -H = qml.Hermitian(H_matrix, wires=wires) -# Compute the ground state energy -E_min = min(qml.eigvals(H)) -print(f"Ground state energy: {E_min:.5f}") - -############################################################################## -# Using the toy problem Hamiltonian and the three ansätze for :math:`\mathrm{SU}(N)` operations -# from above, we create a circuit template that applies these operations in a brick-layer -# architecture with two blocks and each operation acting on ``loc=2`` qubits. -# For this we define a ``QNode``: - -loc = 2 -d = loc**4 - 1 # d = 15 for two-qubit operations -dev = qml.device("default.qubit", wires=num_wires) -# two blocks with two layers. Each layer contains three operations with d parameters -param_shape = (2, 2, 3, d) -init_params = np.zeros(param_shape) - - -def circuit(params, operation=None): - """Apply an operation in a brickwall-like pattern to a qubit register and measure H. - Parameters are assumed to have the dimensions (number of blocks, number of - wires per operation, number of operations per layer, and number of parameters - per operation), in that order. - """ - for params_block in params: - for i, params_layer in enumerate(params_block): - for j, params_op in enumerate(params_layer): - wires_op = [w % num_wires for w in range(loc * j + i, loc * (j + 1) + i)] - operation(params_op, wires_op) - return qml.expval(H) - - -qnode = qml.QNode(circuit, dev, interface="jax") -print(qml.draw(qnode)(init_params, qml.SpecialUnitary)) - -############################################################################## -# We can now proceed to prepare the optimization task using this circuit -# and an optimization routine of our choice. For simplicity, we run a vanilla gradient -# descent optimization with a fixed learning rate for 500 steps. Again, we use JAX - -# for auto-differentiation. - -learning_rate = 5e-4 -num_steps = 500 -init_params = jax.numpy.array(init_params) -grad_fn = jax.jit(jax.jacobian(qnode), static_argnums=1) -qnode = jax.jit(qnode, static_argnums=1) - -############################################################################## -# With this configuration, let's run the optimization! - -energies = {} -for (name, print_name), operation in operations.items(): - print(f"Running the optimization for the {print_name}") - params = init_params.copy() - energy = [] - for step in range(num_steps): - cost = qnode(params, operation) - params = params - learning_rate * grad_fn(params, operation) - energy.append(cost) # Store energy value - if step % 50 == 0: # Report current energy - print(f"{step:3d} Steps: {cost:.6f}") - - energy.append(qnode(params, operation)) # Final energy value - energies[name] = energy - -############################################################################## -# So, did it work? Judging from the intermediate energy values, it seems that the optimization -# outcomes differ notably. But let's take a look at the relative error in energy across the -# optimization process. - -fig, ax = plt.subplots(1, 1) -styles = [":", "--", "-"] -colors = ["#70CEFF", "#C756B2", "#FFE096"] -for (name, energy), c, ls in zip(energies.items(), colors, styles): - error = (energy - E_min) / abs(E_min) - ax.plot(list(range(len(error))), error, label=name, c=c, ls=ls, lw=2.5) - -ax.set(xlabel="Iteration", ylabel="Relative error") -ax.legend() -plt.show() - -############################################################################## -# We find that the optimization indeed performs significantly better for ``qml.SpecialUnitary`` -# than for the other two general unitaries, while using the same number of parameters and -# preserving the expressibility of the circuit ansatz. This -# means that we found a particularly well-trainable parameterization of the local unitaries which -# allows us to reduce the energy of the prepared quantum state more easily while maintaining the -# number of parameters. -# -# -# Conclusion -# ---------- -# -# To summarize, in this tutorial we introduced ``qml.SpecialUnitary``, a multi-parameter -# gate that can act like *any* gate on the qubits it is applied to and that is constructed -# with Lie theory in mind. We discussed three methods of differentiating quantum circuits -# that use this gate, showing that a new custom parameter-shift rule presented in -# [#wiersema]_ is particularly suitable to produce unbiased gradient estimates with the -# lowest variance. Afterwards, we used this differentiation technique when comparing -# the performance of ``qml.SpecialUnitary`` to that of other gates that can act -# like *any* gate locally. For this, we ran a gradient-based optimization for a toy model -# Hamiltonian and found that ``qml.SpecialUnitary`` is particularly well-trainable, achieving -# lower energies significantly quicker than the other tested gates. -# -# There are still exciting questions to answer about ``qml.SpecialUnitary``: How can the -# custom parameter-shift rule be used for other gates, and what does the so-called -# *Dynamical Lie algebra* of these gates have to do with it? How can we implement -# the ``qml.SpecialUnitary`` gate on hardware? Is the unitary time evolution implemented -# by this gate special in a physical sense? -# -# The answers to some, but not all, of these questions can be found in [#wiersema]_. -# We are certain that there are many more interesting aspects of this gate to be uncovered! -# If you want to learn more, consider the other literature references below, -# as well as the documentation of :class:`~pennylane.SpecialUnitary`. -# -# References -# ---------- -# -# .. [#vatan] -# -# Farrokh Vatan and Colin Williams, -# "Optimal Quantum Circuits for General Two-Qubit Gates", -# `arXiv:quant-ph/0308006 `__ (2003). -# -# .. [#wiersema] -# -# R. Wiersema, D. Lewis, D. Wierichs, J. F. Carrasquilla, and N. Killoran. -# "Here comes the SU(N): multivariate quantum gates and gradients" -# `arXiv:2303.11355 `__ (2023). -# -# .. [#banchi] -# -# Leonardo Banchi and Gavin E. Crooks. "Measuring Analytic Gradients of -# General Quantum Evolution with the Stochastic Parameter Shift Rule." -# `Quantum 5, 386 `__ (2021). -# -# About the author -# ---------------- -# +r""" + +Here comes the SU(N): multivariate quantum gates and gradients +============================================================== + +.. meta:: + :property="og:description": Learn about multivariate quantum gates for optimization + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_here_comes_the_sun.png + +.. related:: + + tutorial_vqe A brief overview of VQE + tutorial_general_parshift General parameter-shift rules for quantum gradients + tutorial_unitary_designs Unitary designs and their uses in quantum computing + + +*Author: David Wierichs — Posted: 03 April 2023.* + +How do we choose an ansatz when designing a quantum circuit for a variational +quantum algorithm? And what happens if we do not start with elementary hardware-friendly +gates and compose them, but we instead use a more complex building block for local qubit +interactions and allow for multi-parameter gates from the start? +Can we differentiate such circuits, and how do they perform in optimization tasks? + +Let's find out! + +In this tutorial, you will learn about the :math:`\mathrm{SU}(N)` gate +:class:`~pennylane.SpecialUnitary`, a particular quantum gate which +can act like *any* gate on its qubits by choosing the parameters accordingly. +We will look at a custom derivative rule [#wiersema]_ for this gate and compare it to two +alternative differentiation strategies, namely finite differences and the `stochastic +parameter-shift rule `_. +Finally, we will compare the performance of +``qml.SpecialUnitary`` for a toy minimization problem to that of two other general +local gates. That is, we compare the trainability of equally expressive ansätze. + +Ansätze, so many ansätze +------------------------ + +Variational quantum algorithms have been promoted to be useful for many applications. +When designing these algorithms, a central task is to choose the quantum circuit ansatz, +which provides a parameterization of quantum states. In the course of a variational algorithm, +the circuit parameters are then optimized in order to minimize some cost function. +The choice of the ansatz can have a big impact on the quantum states that can be found +by the algorithm (expressivity) and on the optimization's behaviour (trainability). + +Typically, it also affects the +computational cost of executing the algorithm on quantum hardware and the strength of the noise +that enters the computation. Finally, the application itself influences, or +even fixes, the choice of ansatz for some variational quantum algorithms, +which can lead to constraints in the ansatz design. + +.. figure:: ../_static/demonstration_assets/here_comes_the_sun/SUN_demo_Ansatz.png + :align: center + :width: 90% + +While a number of best practices for ansatz design have been developed, +a lot is still unknown about the connection between circuit structures and the +resulting properties. Therefore, circuit design is often also based on intuition or heuristics; +an ansatz reported in the literature might just have turned out +to work particularly well for a given problem or might fall into a "standard" +category of circuits. + +If the application does not constrain the choice of ansatz, we may want to avoid choosing +somewhat arbitrary circuit ansätze that may introduce undesirable biases. +Instead, we will want to reflect the generic structure of the problem by performing a +fully general operation on the qubit register. +However, if we were to do so, the number of parameters required to produce such a general +operation would grow much too quickly. Instead, we want to consider fully general operations +*on a few qubits* and compose them into a fabric of local gates. For two-qubit operations, +the fabric could look like this: + +.. figure:: ../_static/demonstration_assets/here_comes_the_sun/SUN_demo_SU4.png + :align: center + :width: 60% + +The general local operation can be implemented by composing a suitable combination +of elementary gates, like single-qubit rotations and CNOT gates. Alternatively, we may +choose a canonical parameterization of the group that contains all local operations, and we will +see that this is an advantageous approach for the trainability of the ansatz. + +.. figure:: ../_static/demonstration_assets/here_comes_the_sun/SUN_demo_optimization.png + :align: center + :width: 60% + +Before we can use the :math:`\mathrm{SU}(N)` gate in training, we will need to +learn how to differentiate it in a quantum circuit. But first things first: +let's start with a brief math intro — no really, just a *Liettle* bit. + +The special unitary group SU(N) and its Lie algebra +--------------------------------------------------- + +The gate we will look at is given by a specific parameterization of the +`special unitary group `__ +:math:`\mathrm{SU}(N),` where :math:`N=2^n` is the Hilbert space dimension of the gate +for :math:`n` qubits. Mathematically, the group can be defined as the set of operators +(or matrices) that can be inverted by taking their adjoint and that have +determinant :math:`1.` In general, all quantum gates acting on :math:`n` qubits are +elements of :math:`\mathrm{SU}(N)` up to a global phase. + +The group :math:`\mathrm{SU}(N)` is a `Lie group `__, +and its associated `Lie algebra `__ +is :math:`\mathfrak{su}(N).` For our purposes, it will be sufficient to look at a matrix +representation of the algebra and we may define it as + +.. math:: + + \mathfrak{su}(N) = + \{\Omega \in \mathbb{C}^{N\times N}: \Omega^\dagger=-\Omega, \operatorname{Tr}[\Omega]=0\}. + +The conditions are that the elements :math:`\Omega` are *skew-Hermitian* and that their trace vanishes. +We will use so-called canonical coordinates for the algebra which are simply the coefficients +in the Pauli basis. That is, we consider the Pauli basis elements multiplied with the +imaginary unit :math:`i,` except for the identity: + +.. math:: + + G_m \in \mathcal{P}^{(n)} = i \left\{I,X,Y,Z\right\}^n \setminus \{i I^n\}. + +A Lie algebra element :math:`\Omega` can be written as + +.. math:: + + \Omega = \sum_{m=1}^d \theta_m G_m,\quad \theta_m \in \mathbb{R} + +and those coefficients :math:`\theta` are precisely the canonical coordinates. +You may ask why we included the prefactor :math:`i` in the definition of :math:`G_m` and why we excluded +the identity (times :math:`i`). This was done to match the properties of :math:`\mathfrak{su}(N);` +the prefactor makes the basis elements skew-Hermitian and the identity would not have a +vanishing trace. Indeed, one can check that the dimension of :math:`\mathfrak{su}(N)` is +:math:`4^n-1` and that there are :math:`4^n` Pauli words, so that one Pauli word — the identity — had to go +in any case... We can use the canonical coordinates of the algebra to express a *group element* in +:math:`\mathrm{SU}(N)` as well, and the ``qml.SpecialUnitary`` gate we will use is defined as + +.. math:: + + U(\boldsymbol{\theta}) = \exp\left\{\sum_{m=1}^d \theta_m G_m \right\}. + +The number of coordinates and Pauli words in :math:`\mathcal{P}^{(n)}` is :math:`d=4^n-1.` +Therefore, this will be the number of parameters that a single ``qml.SpecialUnitary`` gate acting on +:math:`n` qubits will take. For example, it takes just three parameters for a single qubit, which +is why :class:`~pennylane.Rot` and :class:`~pennylane.U3` take three parameters and may +produce *any* single-qubit rotation. It takes a modest 15 parameters for two qubits, +but it already requires 63 parameters for three qubits. + +For unitaries generated by a single operator, i.e. of the form :math:`\exp(i\theta G),` +there is a plethora of differentiation techniques that allow us to compute its derivative. +However, a standard parameter-shift rule, for example, will not do the job if there are +non-commuting terms :math:`G_m` in the multi-parameter gate :math:`U(\boldsymbol{\theta})` above. +So how *do* we compute the derivative? + +Obtaining the gradient +---------------------- + +In variational quantum algorithms, we typically use the circuit to prepare a quantum state and +then we measure some observable :math:`H.` The resulting real-valued output is considered to be the +cost function :math:`C` that should be minimized. If we want to use gradient-based optimization for +this task, we need a method to compute the gradient :math:`\nabla C` in addition to the cost +function itself. As derived in the publication [#wiersema]_, this is possible on quantum hardware +for :math:`\mathrm{SU}(N)` gates as long as the gates themselves can be implemented. +The implementation in PennyLane follows the decomposition idea described in App. F3, but the +main text of [#wiersema]_ proposes an additional method that scales better in some scenarios +(the caveat being that this method requires additional gates to be available on the quantum hardware). +Here, we will focus on the former method. +We will not go through the entire derivation, but note the following key points: + +- The gradient with respect to all :math:`d` parameters of an :math:`\mathrm{SU}(N)` gate can be + computed using :math:`2d` auxiliary circuits. Each of the circuits contains one additional + operation compared to the original circuit, namely a ``qml.PauliRot`` gate with rotation + angles of :math:`\pm\frac{\pi}{2}.` Note that these Pauli rotations act on up to :math:`n` + qubits. +- This differentiation method uses automatic differentiation during compilation and + classical coprocessing steps, but is compatible with quantum hardware. For large :math:`n,` + the classical processing steps can quickly become prohibitively expensive. +- The computed gradient is not an approximative technique but allows for an exact computation + of the gradient on simulators. On quantum hardware, this leads to unbiased gradient + estimators. + +The implementation in PennyLane takes care of creating the additional circuits and evaluating +them, and with adequate post-processing we get the gradient :math:`\nabla C.` + +Comparing gradient methods +-------------------------- + +Before we dive into using ``qml.SpecialUnitary`` in an optimization task, let's compare +a few methods to compute the gradient with respect to the parameters of such a gate. +In particular, we will look at a finite difference (FD) approach, the stochastic parameter-shift +rule, and the custom gradient method we described above. + +For the first approach, we will use the standard central difference recipe given by + +.. math:: + + \partial_{\text{FD},\theta_j}C(\boldsymbol{\theta}) + =\left[C\left(\boldsymbol{\theta}+\frac{\delta}{2}\boldsymbol{e}_j\right) + -C\left(\boldsymbol{\theta}-\frac{\delta}{2}\boldsymbol{e}_j\right)\right] / \delta. + +Here, :math:`\delta` is a shift parameter that we need to choose and :math:`\boldsymbol{e}_j` is the +:math:`j`-th canonical basis vector, i.e. the all-zeros vector with a one in the +:math:`j`-th entry. This approach is agnostic to the differentiated function and does +not exploit its structure. + +In contrast, the stochastic parameter-shift rule is a differentiation recipe developed particularly +for multi-parameter gates like the :math:`\mathrm{SU}(N)` gates [#banchi]_. It involves the +approximate evaluation of an integral by sampling *splitting times* :math:`\tau` and +evaluating an expression close to the non-stochastic parameter-shift rule for each sample. +For more details, also consider the +:doc:`demo on the stochastic parameter-shift rule `. + +So, let's dive into a toy example and explore the three gradient methods! +We start by defining a simple one-qubit circuit that contains a single :math:`\mathrm{SU}(2)` +gate and measures the expectation value of :math:`H=\frac{3}{5} Z - \frac{4}{5} Y.` +As ``qml.SpecialUnitary`` requires automatic differentiation subroutines even for the +hardware-ready derivative recipe, we will make use of JAX. +""" + +import pennylane as qml +import numpy as np +import jax + +jax.config.update("jax_enable_x64", True) +jax.config.update("jax_platform_name", "cpu") +jnp = jax.numpy + +dev = qml.device("default.qubit", wires=1) +H = 0.6 * qml.PauliZ(0) - 0.8 * qml.PauliY(0) + + +def qfunc(theta): + qml.SpecialUnitary(theta, wires=0) + return qml.expval(H) + + +circuit = qml.QNode(qfunc, dev, interface="jax", diff_method="parameter-shift") + +theta = jnp.array([0.4, 0.2, -0.5]) + +############################################################################## +# Now we need to set up the differentiation methods. For this demonstration, we will +# keep the first and last entry of ``theta`` fixed and only compute the gradient for the +# second parameter. This allows us to visualize the results easily and keeps the +# computational effort to a minimum. +# +# We start with the finite-difference +# recipe, using a shift scale of :math:`\delta=0.75.` This choice of :math:`\delta,` +# which is much larger than usual for numerical differentiation on classical computers, +# is adapted to the scenario of shot-based gradients (see App. F2 of [#wiersema]_). +# We compute the derivative with respect to the second entry of theta, so we will use +# the unit vector :math:`e_2:` + +unit_vector = np.array([0.0, 1.0, 0.0]) + + +def central_diff_grad(theta, delta): + plus_eval = circuit(theta + delta / 2 * unit_vector) + minus_eval = circuit(theta - delta / 2 * unit_vector) + return (plus_eval - minus_eval) / delta + + +delta = 0.75 +print(f"Central difference: {central_diff_grad(theta, delta):.5f}") + +############################################################################## +# Next up, we implement the stochastic parameter-shift rule. Of course we do not do +# so in full generality, but for the particular circuit in this example. We will +# sample ten splitting times to obtain the gradient entry. For each splitting time, +# we need to insert a Pauli-:math:`Y` rotation because :math:`\theta_2` belongs to +# the Pauli-:math:`Y` component of :math:`A(\boldsymbol{\theta}).` For this, we define +# an auxiliary circuit. + + +@jax.jit +@qml.qnode(dev, interface="jax") +def aux_circuit(theta, tau, sign): + qml.SpecialUnitary(tau * theta, wires=0) + # This corresponds to the parameter-shift evaluations of RY at 0 + qml.RY(-sign * np.pi / 2, wires=0) + qml.SpecialUnitary((1 - tau) * theta, wires=0) + return qml.expval(H) + + +def stochastic_parshift_grad(theta, num_samples): + grad = 0 + splitting_times = np.random.random(size=num_samples) + for tau in splitting_times: + # Evaluate the two-term parameter-shift rule of the auxiliar circuit + grad += aux_circuit(theta, tau, 1.0) - aux_circuit(theta, tau, -1.0) + return grad / num_samples + + +num_samples = 10 +print(f"Stochastic parameter-shift: {stochastic_parshift_grad(theta, num_samples):.5f}") + +############################################################################## +# Finally, we can make use of the custom parameter-shift rule introduced in +# [#wiersema]_, which is readily available in PennyLane. Due to the implementation +# chosen internally, the full gradient is returned; we need to pick the second +# gradient entry manually. For this small toy problem, this is +# not an issue. + +sun_grad = jax.grad(circuit) +print(f"Custom SU(N) gradient: {sun_grad(theta)[1]:.5f}") + +############################################################################## +# We obtained three values for the gradient of interest, and they do not agree. +# So what is going on here? First, let's use automatic differentiation to compute +# the exact value and see which method agrees with it (we again need to extract the +# corresponding entry from the full gradient). + +autodiff_circuit = qml.QNode(qfunc, dev, interface="jax", diff_method="parameter-shift") +exact_grad = jax.grad(autodiff_circuit)(theta)[1] +print(f"Exact gradient: {exact_grad:.5f}") + +############################################################################## +# As we can see, automatic differentiation confirmed that the custom differentiation method +# gave us the correct result. Why do the other methods disagree? +# This is because the finite difference recipe is an *approximate* gradient +# method. This means it has an error even if all circuit evaluations are +# made exact (up to numerical precision) like in the example above. +# As for the stochastic parameter-shift rule, you may already guess why there is +# a deviation: indeed, the *stochastic* nature of this method leads to derivative +# values that are scattered around the true value. It is an unbiased estimator, +# so the average will approach the exact value with increasingly many evaluations. +# To demonstrate this, let's compute the same derivative many times and plot +# a histogram of what we get. We'll do so for ``num_samples=2``, ``10`` and ``100``. + +import matplotlib.pyplot as plt + +plt.rcParams.update({"font.size": 12}) + +fig, ax = plt.subplots(1, 1, figsize=(6, 4)) +colors = ["#ACE3FF", "#FF87EB", "#FFE096"] +for num_samples, color in zip([2, 10, 100], colors): + grads = [stochastic_parshift_grad(theta, num_samples) for _ in range(1000)] + ax.hist(grads, label=f"{num_samples} samples", alpha=0.9, color=color) +ylim = ax.get_ylim() +ax.plot([exact_grad] * 2, ylim, ls="--", c="k", label="Exact") +ax.set(xlabel=r"$\partial_{SPS,\theta_2}C(\theta)$", ylabel="Frequency", ylim=ylim) +ax.legend(loc="upper left") +plt.tight_layout() +plt.show() + +############################################################################## +# As we can see, the stochastic parameter-shift rule comes with a variance +# that can be reduced at the additional cost of evaluating the auxiliary circuit +# for more splitting times. +# +# On quantum hardware, all measurement results are statistical in nature anyway. +# So how does this stochasticity combine with the +# three differentiation methods? We will not go into detail here, but refer +# to [#wiersema]_ to see how the custom differentiation rule proposed in the +# main text leads to the lowest mean squared error. For a single-qubit circuit +# similar to the one above, but with the single gate :math:`U(\boldsymbol{\theta})=\exp(iaX+ibY),` +# the derivative and its expected variance are shown in the following +# (recoloured) plot from the manuscript: +# +# .. figure:: ../_static/demonstration_assets/here_comes_the_sun/sampled_grad.png +# :align: center +# :width: 70% +# +# As we can see, the custom :math:`\mathrm{SU}(N)` parameter-shift rule produces the +# gradient estimates with the smallest variance. For small values of the parameter +# :math:`b,` which is fixed for each panel, the custom shift rule and the stochastic +# shift rule approach the standard two-term parameter-shift rule, which would be exact +# for :math:`b=0.` +# The finite difference gradient shown here was obtained using the shift +# scale :math:`\delta=0.75,` as well. As we can see, this suppresses the variance down to +# a level comparable to those of the shift rule derivatives and this shift scale is a +# reasonable trade-off between the variance and the systematic error we observed earlier. +# As shown in App. F3 of [#wiersema]_, this scale is indeed close to the optimal choice +# if we were to compute the gradient with 100 shots per circuit. +# +# Comparing ansatz structures +# --------------------------- +# +# We discussed above that there are many circuit architectures available and that choosing +# a suitable ansatz is important but can be difficult. Here, we will compare a simple ansatz +# based on the ``qml.SpecialUnitary`` gate discussed above to other approaches that fully +# parametrize the special unitary group for the respective number of qubits. +# In particular, we will compare ``qml.SpecialUnitary`` to standard decompositions from the +# literature that parametrize :math:`\mathrm{SU}(N)` with elementary gates, as well as to a +# sequence of Pauli rotation gates that also allows us to create any special unitary. +# Let us start by defining the decomposition of a two-qubit unitary. +# We choose the decomposition, which is optimal but not unique, from [#vatan]_. +# The Pauli rotation sequence is available in PennyLane +# via ``qml.ArbitraryUnitary`` and we will not need to implement it ourselves. + + +def two_qubit_decomp(params, wires): + """Implement an arbitrary SU(4) gate on two qubits + using the decomposition from Theorem 5 in + https://arxiv.org/pdf/quant-ph/0308006.pdf""" + i, j = wires + # Single U(2) parameterization on both qubits separately + qml.Rot(*params[:3], wires=i) + qml.Rot(*params[3:6], wires=j) + qml.CNOT(wires=[j, i]) # First CNOT + qml.RZ(params[6], wires=i) + qml.RY(params[7], wires=j) + qml.CNOT(wires=[i, j]) # Second CNOT + qml.RY(params[8], wires=j) + qml.CNOT(wires=[j, i]) # Third CNOT + # Single U(2) parameterization on both qubits separately + qml.Rot(*params[9:12], wires=i) + qml.Rot(*params[12:15], wires=j) + + +# The three building blocks on two qubits we will compare are: +operations = { + ("Decomposition", "decomposition"): two_qubit_decomp, + ("PauliRot sequence",) * 2: qml.ArbitraryUnitary, + ("$\mathrm{SU}(N)$ gate", "SU(N) gate"): qml.SpecialUnitary, +} + +############################################################################## +# Now that we have the template for the composition approach in place, we construct a toy +# problem to solve using the ansätze. We will sample a random Hamiltonian in the Pauli basis +# (this time without the prefactor :math:`i,` as we want to construct a Hermitian operator) +# with independent coefficients that follow a normal distribution: +# +# .. math:: +# +# H = \sum_{m=1}^d h_m G_m,\quad h_m\sim \mathcal{N}(0,1). +# +# We will work with six qubits. + +num_wires = 6 +wires = list(range(num_wires)) +np.random.seed(62213) + +coefficients = np.random.randn(4**num_wires - 1) +# Create the matrices for the entire Pauli basis +basis = qml.ops.qubit.special_unitary.pauli_basis_matrices(num_wires) +# Construct the Hamiltonian from the normal random coefficients and the basis +H_matrix = qml.math.tensordot(coefficients, basis, axes=[[0], [0]]) +H = qml.Hermitian(H_matrix, wires=wires) +# Compute the ground state energy +E_min = min(qml.eigvals(H)) +print(f"Ground state energy: {E_min:.5f}") + +############################################################################## +# Using the toy problem Hamiltonian and the three ansätze for :math:`\mathrm{SU}(N)` operations +# from above, we create a circuit template that applies these operations in a brick-layer +# architecture with two blocks and each operation acting on ``loc=2`` qubits. +# For this we define a ``QNode``: + +loc = 2 +d = loc**4 - 1 # d = 15 for two-qubit operations +dev = qml.device("default.qubit", wires=num_wires) +# two blocks with two layers. Each layer contains three operations with d parameters +param_shape = (2, 2, 3, d) +init_params = np.zeros(param_shape) + + +def circuit(params, operation=None): + """Apply an operation in a brickwall-like pattern to a qubit register and measure H. + Parameters are assumed to have the dimensions (number of blocks, number of + wires per operation, number of operations per layer, and number of parameters + per operation), in that order. + """ + for params_block in params: + for i, params_layer in enumerate(params_block): + for j, params_op in enumerate(params_layer): + wires_op = [w % num_wires for w in range(loc * j + i, loc * (j + 1) + i)] + operation(params_op, wires_op) + return qml.expval(H) + + +qnode = qml.QNode(circuit, dev, interface="jax") +print(qml.draw(qnode)(init_params, qml.SpecialUnitary)) + +############################################################################## +# We can now proceed to prepare the optimization task using this circuit +# and an optimization routine of our choice. For simplicity, we run a vanilla gradient +# descent optimization with a fixed learning rate for 500 steps. Again, we use JAX + +# for auto-differentiation. + +learning_rate = 5e-4 +num_steps = 500 +init_params = jax.numpy.array(init_params) +grad_fn = jax.jit(jax.jacobian(qnode), static_argnums=1) +qnode = jax.jit(qnode, static_argnums=1) + +############################################################################## +# With this configuration, let's run the optimization! + +energies = {} +for (name, print_name), operation in operations.items(): + print(f"Running the optimization for the {print_name}") + params = init_params.copy() + energy = [] + for step in range(num_steps): + cost = qnode(params, operation) + params = params - learning_rate * grad_fn(params, operation) + energy.append(cost) # Store energy value + if step % 50 == 0: # Report current energy + print(f"{step:3d} Steps: {cost:.6f}") + + energy.append(qnode(params, operation)) # Final energy value + energies[name] = energy + +############################################################################## +# So, did it work? Judging from the intermediate energy values, it seems that the optimization +# outcomes differ notably. But let's take a look at the relative error in energy across the +# optimization process. + +fig, ax = plt.subplots(1, 1) +styles = [":", "--", "-"] +colors = ["#70CEFF", "#C756B2", "#FFE096"] +for (name, energy), c, ls in zip(energies.items(), colors, styles): + error = (energy - E_min) / abs(E_min) + ax.plot(list(range(len(error))), error, label=name, c=c, ls=ls, lw=2.5) + +ax.set(xlabel="Iteration", ylabel="Relative error") +ax.legend() +plt.show() + +############################################################################## +# We find that the optimization indeed performs significantly better for ``qml.SpecialUnitary`` +# than for the other two general unitaries, while using the same number of parameters and +# preserving the expressibility of the circuit ansatz. This +# means that we found a particularly well-trainable parameterization of the local unitaries which +# allows us to reduce the energy of the prepared quantum state more easily while maintaining the +# number of parameters. +# +# +# Conclusion +# ---------- +# +# To summarize, in this tutorial we introduced ``qml.SpecialUnitary``, a multi-parameter +# gate that can act like *any* gate on the qubits it is applied to and that is constructed +# with Lie theory in mind. We discussed three methods of differentiating quantum circuits +# that use this gate, showing that a new custom parameter-shift rule presented in +# [#wiersema]_ is particularly suitable to produce unbiased gradient estimates with the +# lowest variance. Afterwards, we used this differentiation technique when comparing +# the performance of ``qml.SpecialUnitary`` to that of other gates that can act +# like *any* gate locally. For this, we ran a gradient-based optimization for a toy model +# Hamiltonian and found that ``qml.SpecialUnitary`` is particularly well-trainable, achieving +# lower energies significantly quicker than the other tested gates. +# +# There are still exciting questions to answer about ``qml.SpecialUnitary``: How can the +# custom parameter-shift rule be used for other gates, and what does the so-called +# *Dynamical Lie algebra* of these gates have to do with it? How can we implement +# the ``qml.SpecialUnitary`` gate on hardware? Is the unitary time evolution implemented +# by this gate special in a physical sense? +# +# The answers to some, but not all, of these questions can be found in [#wiersema]_. +# We are certain that there are many more interesting aspects of this gate to be uncovered! +# If you want to learn more, consider the other literature references below, +# as well as the documentation of :class:`~pennylane.SpecialUnitary`. +# +# References +# ---------- +# +# .. [#vatan] +# +# Farrokh Vatan and Colin Williams, +# "Optimal Quantum Circuits for General Two-Qubit Gates", +# `arXiv:quant-ph/0308006 `__ (2003). +# +# .. [#wiersema] +# +# R. Wiersema, D. Lewis, D. Wierichs, J. F. Carrasquilla, and N. Killoran. +# "Here comes the SU(N): multivariate quantum gates and gradients" +# `arXiv:2303.11355 `__ (2023). +# +# .. [#banchi] +# +# Leonardo Banchi and Gavin E. Crooks. "Measuring Analytic Gradients of +# General Quantum Evolution with the Stochastic Parameter Shift Rule." +# `Quantum 5, 386 `__ (2021). +# +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_initial_state_preparation/demo.py b/demonstrations_v2/tutorial_initial_state_preparation/demo.py index 8a2cb89e5c..dc50c939c0 100644 --- a/demonstrations_v2/tutorial_initial_state_preparation/demo.py +++ b/demonstrations_v2/tutorial_initial_state_preparation/demo.py @@ -1,364 +1,364 @@ -r""" - -Initial state preparation for quantum chemistry -=============================================== - -A high-quality initial state can significantly reduce the runtime of many quantum algorithms. From -the variational quantum eigensolver (VQE) to quantum phase estimation (QPE) and even the recent -`intermediate-scale quantum (ISQ) `_ algorithms, obtaining the ground state of a chemical system requires -a good initial state. For instance, in the case of VQE, a good initial state directly translates into fewer -optimization steps. In QPE, the probability of measuring the ground-state energy is directly -proportional to the overlap squared of the initial and ground states. Even beyond quantum chemistry, -good initial guesses are important for algorithms like quantum approximate optimization (QAOA) -and Grover search. - -Much like searching for a needle in a haystack, there are a lot of things you might try -to prepare a good guess for the ground state in the high-dimensional Hilbert space. In this -tutorial, we show how to use traditional computational chemistry techniques to -get a good initial state. Such an initial state will not be exactly -the ground state, but it will certainly be better than the standard guess of a computational -basis state :math:`|0\rangle^{\otimes N}` or the Hartree-Fock state. - -.. figure:: ../_static/demonstration_assets/initial_state_preparation/qchem_input_states.png - :align: center - :width: 65% - :target: javascript:void(0) - -Importing initial states ------------------------- -We can import initial states obtained from several post-Hartree-Fock quantum chemistry methods -to PennyLane. These methods are incredibly diverse in terms of their outputs, not always returning -an object that is easy to turn into a PennyLane state vector. - -We have already done this hard conversion work: all that you need to do is run these methods and -pass their outputs to PennyLane's :func:`~.pennylane.qchem.import_state` function. The currently -supported methods are configuration interaction with singles and doubles (CISD), coupled cluster -(CCSD), density-matrix renormalization group (DMRG) and semistochastic heat-bath configuration -interaction (SHCI). - -We now show how this works on the linear :math:`\text{H}_3^+` molecule as an example. - - -CISD states -~~~~~~~~~~~ -The first line of attack for initial state preparation is often a CISD calculation, performed with the `PySCF `_ -library. CISD is unsophisticated, but it is fast. It will not be of much help for strongly correlated molecules, -but it is better than Hartree-Fock. Here is the code example using the restricted Hartree-Fock -orbitals (:func:`~.pennylane.qchem.import_state` works for unrestricted orbitals, too). -""" - -from pyscf import gto, scf, ci -from pennylane.qchem import import_state -import numpy as np - -R = 1.2 -# create the H3+ molecule -mol = gto.M(atom=[["H", (0, 0, 0)], ["H", (0, 0, R)], ["H", (0, 0, 2 * R)]], charge=1) -# perfrom restricted Hartree-Fock and then CISD -myhf = scf.RHF(mol).run() -myci = ci.CISD(myhf).run() -wf_cisd = import_state(myci, tol=1e-1) -print(f"CISD-based state vector: \n{np.round(wf_cisd.real, 4)}") - -############################################################################## -# The final object, PennyLane's state vector ``wf_cisd``, is ready to be used as an -# initial state in a quantum circuit in PennyLane--we will showcase this below for VQE. -# -# Conversion for CISD to a state vector is straightforward: simply assign the PySCF-stored -# CI coefficients to appropriate entries in the state vector based on the determinant they correspond to. -# The second attribute passed to ``import_state()``, ``tol``, specifies the cutoff beyond -# which contributions to the wavefunctions are neglected. Internally, wavefunctions are -# stored in their Slater determinant representation. If their prefactor coefficient -# is below ``tol``, those determinants are dropped from the expression. - -############################################################################## -# CCSD states -# ~~~~~~~~~~~ -# The function :func:`~.pennylane.qchem.import_state` is general and works similarly for CCSD. It can -# automatically detect the input type and apply the appropriate conversion protocol. - -from pyscf import cc - -mycc = cc.CCSD(myhf).run() -wf_ccsd = import_state(mycc, tol=1e-1) -print(f"CCSD-based state vector: \n{np.round(wf_ccsd.real, 4)}") - -############################################################################## -# For CCSD conversion, at present the exponential form is expanded and terms are collected **to -# second order** to obtain the CI coefficients. -# -# DMRG states -# ~~~~~~~~~~~ -# For more complex or more correlated molecules, initial states from DMRG or -# SHCI will be better options. DMRG calculations involve using the library `Block2 `_, -# which can be installed with ``pip``: -# -# .. code-block:: bash -# -# pip install block2 -# -# The DMRG calculation is run on top of the molecular orbitals obtained by Hartree-Fock, -# stored in the ``myhf`` object, which we can reuse from before. -# -# .. code-block:: python -# -# from pyscf import mcscf -# from pyblock2.driver.core import DMRGDriver, SymmetryTypes -# from pyblock2._pyscf.ao2mo import integrals as itg -# -# # obtain molecular integrals and other parameters for DMRG -# mc = mcscf.CASCI(myhf, mol.nao, mol.nelectron) -# ncas, n_elec, spin, ecore, h1e, g2e, orb_sym = \ -# itg.get_rhf_integrals(myhf, mc.ncore, mc.ncas, g2e_symm=8) -# -# # initialize the DMRG solver, Hamiltonian (as matrix-product operator, MPO) and -# # state (as matrix-product state, MPS) -# driver = DMRGDriver(scratch="./dmrg_temp", symm_type=SymmetryTypes.SZ) -# driver.initialize_system(n_sites=ncas, n_elec=n_elec, spin=spin, orb_sym=orb_sym) -# mpo = driver.get_qc_mpo(h1e=h1e, g2e=g2e, ecore=ecore, iprint=0) -# ket = driver.get_random_mps(tag="GS") -# -# # execute DMRG by modifying the ket state in-place to minimize the energy -# driver.dmrg(mpo, ket, n_sweeps=30,bond_dims=[100,200],\ -# noises=[1e-3,1e-5],thrds=[1e-6,1e-7],tol=1e-6) -# -# # post-process the MPS to get an initial state -# dets, coeffs = driver.get_csf_coefficients(ket, iprint=0) -# dets = dets.tolist() -# wf_dmrg = import_state((dets, coeffs), tol=1e-1) -# print(f"DMRG-based state vector: \n {np.round(wf_dmrg, 4)}") -# -# .. code-block:: bash -# -# DMRG-based state vector -# [ 0. 0. 0. 0. 0. 0. 0. 0. 0. -# 0. 0. 0. -0.2243 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.9745 0. 0. 0. 0. 0. -# 0. 0. 0. 0. 0. 0. 0. 0. 0. -# 0. ] - -############################################################################## -# The crucial part is calling ``get_csf_coefficients()`` on the solution stored in -# MPS form in the ``ket``. This triggers an internal reconstruction calculation that -# converts the MPS to the sum of Slater determinants form, returning the output -# as a tuple ``(array([int]), array(float]))``. The first element expresses a given Slater -# determinant using Fock occupation vectors of length equal to the number of spatial -# orbitals. In Block2 notation, the entries can be ``0`` (orbital unoccupied), ``1`` (orbital occupied with spin-up -# electron), ``2`` (occupied with spin-down), and ``3`` (doubly occupied). The first -# element, ``array([int])``, must be converted to ``list`` -# for :func:`~.pennylane.qchem.import_state` to accept it. -# The second element stores the CI coefficients. -# -# In principle, this functionality can be used to generate any initial state, provided -# the user specifies a list of Slater determinants and their coefficients in this form. -# Let's take this opportunity to create the Hartree-Fock initial state, to compare the -# other states against it later on. - -hf_primer = ([[3, 0, 0]], np.array([1.0])) -wf_hf = import_state(hf_primer) - -############################################################################## -# SHCI states -# ~~~~~~~~~~~ -# -# The SHCI calculations utilize the library `Dice `_, and can be run -# using PySCF through the interface module `SHCI-SCF `_. -# For Dice, the execution process is similar to that of DMRG: -# -# .. code-block:: python -# -# from pyscf.shciscf import shci -# -# # prepare PySCF CASCI object, whose solver will be the SHCI method -# ncas, nelecas_a, nelecas_b = mol.nao, mol.nelectron // 2, mol.nelectron // 2 -# myshci = mcscf.CASCI(myhf, ncas, (nelecas_a, nelecas_b)) -# -# # set up essentials for the SHCI solver -# output_file = f"shci_output.out" -# myshci.fcisolver = shci.SHCI(myhf.mol) -# myshci.fcisolver.outputFile = output_file -# -# # execute SHCI through the PySCF interface -# e_tot, e_ci, ss, mo_coeff, mo_energies = myshci.kernel(verbose=5) -# -# # post-process the shci_output.out to extract the wave function -# # results and create the tuple of dets (list([str])) and coeffs (array([float])) -# # shci_data = (dets, coeffs) -# wf_shci = import_state(shci_data, tol=1e-1) -# print(f"SHCI-based state vector\n{wf_shci}") -# -# .. code-block:: bash -# -# SHCI-based state vector -# [ 0. 0. 0. 0. 0. 0. 0. 0. 0. -# 0. 0. 0. 0.2243 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.9745 0. 0. 0. 0. 0. -# 0. 0. 0. 0. 0. 0. 0. 0. 0. -# 0. ] - -############################################################################## -# The Dice output file prints determinants using symbols ``0`` (unoccupied orbital), -# ``a`` and ``b`` (orbital occupied with spin-up and spin-down electron, respectively), -# and ``2`` (doubly occupied orbital). These determinant outputs, and corresponding -# coefficients, should be extracted and arranged as ``(list([str]), array(float]))``, -# where each string combines all the determinant symbols ``0, a, b, 2`` for a single -# determinant with no spaces. For example, for the HF state we created in the DMRG section, -# the SHCI output should read ``([["200"]], np.array([1.]))`` - -############################################################################## -# Application: speed up VQE -# ------------------------- -# Let us now demonstrate how the choice of a better initial state shortens the runtime -# of VQE for obtaining the ground-state energy of a molecule. As a first step, create our -# linear :math:`\text{H}_3^+` molecule, a device, and a simple VQE circuit with single and double excitations: - -import pennylane as qml -from pennylane import qchem -from jax import numpy as jnp - -# generate the molecular Hamiltonian for H3+ -symbols = ["H", "H", "H"] -geometry = jnp.array([[0, 0, 0], [0, 0, R/0.529], [0, 0, 2*R/0.529]]) -molecule = qchem.Molecule(symbols, geometry, charge=1) - -H2mol, qubits = qchem.molecular_hamiltonian(molecule) -wires = list(range(qubits)) -dev = qml.device("default.qubit", wires=qubits) - -# create all possible excitations in H3+ -singles, doubles = qchem.excitations(2, qubits) -excitations = singles + doubles - -############################################################################## -# Now let's run VQE with the Hartree-Fock initial state. We first build the VQE circuit: - - -@qml.qnode(dev) -def circuit_VQE(theta, initial_state): - qml.StatePrep(initial_state, wires=wires) - for i, excitation in enumerate(excitations): - if len(excitation) == 4: - qml.DoubleExcitation(theta[i], wires=excitation) - else: - qml.SingleExcitation(theta[i], wires=excitation) - return qml.expval(H2mol) - - -def cost_fn(param): - return circuit_VQE(param, initial_state=wf_hf) - - -############################################################################## -# Next, we create the VQE optimizer, initialize the variational parameters and run the VQE optimization. -import optax -import jax -jax.config.update("jax_enable_x64", True) - -opt = optax.sgd(learning_rate=0.4) # sgd stands for StochasticGradientDescent -theta = jnp.array(jnp.zeros(len(excitations))) -delta_E, iteration = 10, 0 -results_hf = [] -opt_state = opt.init(theta) -prev_energy = cost_fn(theta) - -# run the VQE optimization loop until convergence threshold is reached -while abs(delta_E) > 1e-5: - gradient = jax.grad(cost_fn)(theta) - updates, opt_state = opt.update(gradient, opt_state) - theta = optax.apply_updates(theta, updates) - new_energy = cost_fn(theta) - delta_E = new_energy - prev_energy - prev_energy = new_energy - results_hf.append(new_energy) - if len(results_hf) % 5 == 0: - print(f"Step = {len(results_hf)}, Energy = {new_energy:.6f} Ha") -print(f"Starting with HF state took {len(results_hf)} iterations until convergence.") - -############################################################################## -# And compare with how things go when you run it with the CISD initial state: - - -def cost_fn_cisd(param): - return circuit_VQE(param, initial_state=wf_cisd) - - -theta = jnp.array(jnp.zeros(len(excitations))) -delta_E, iteration = 10, 0 -results_cisd = [] -opt_state = opt.init(theta) -prev_energy = cost_fn_cisd(theta) - -while abs(delta_E) > 1e-5: - gradient = jax.grad(cost_fn_cisd)(theta) - updates, opt_state = opt.update(gradient, opt_state) - theta = optax.apply_updates(theta, updates) - new_energy = cost_fn_cisd(theta) - delta_E = new_energy - prev_energy - prev_energy = new_energy - results_cisd.append(new_energy) - if len(results_cisd) % 5 == 0: - print(f"Step = {len(results_cisd)}, Energy = {new_energy:.6f} Ha") -print( - f"Starting with CISD state took {len(results_cisd)} iterations until convergence." -) - -############################################################################## -# Let's visualize the comparison between the two initial states, and see that indeed -# we get to the ground state much faster by starting with the CISD state. - -import matplotlib.pyplot as plt - -fig, ax = plt.subplots() -ax.plot(range(len(results_hf)), results_hf, color="r", marker="o", label="wf_hf") -ax.plot(range(len(results_cisd)), results_cisd, color="b", marker="o", label="wf_cisd") -ax.legend(fontsize=16) -ax.tick_params(axis="both", labelsize=16) -ax.set_xlabel("Iteration", fontsize=20) -ax.set_ylabel("Energy, Ha", fontsize=20) -plt.tight_layout() -plt.show() - -############################################################################## -# Indeed, the CISD state significantly shortens the VQE runtime. -# -# It is sometimes possible to foresee the extent of this speed-up of a particular initial state -# by computing its overlap with the ground state--a traditional metric of success for initial -# states in quantum algorithms. Because in our examples the states are regular arrays, computing an -# overlap between different states is as easy as computing a dot product - -print(np.dot(wf_cisd, wf_hf).real) -print(np.dot(wf_ccsd, wf_hf).real) -print(np.dot(wf_cisd, wf_ccsd).real) - -############################################################################## -# In this particular case of :math:`\text{H}_3^+,` even CISD gives the exact wavefunction, hence both overlaps -# with the HF state are identical. In more correlated molecules, overlaps will show that the more -# multireference methods DMRG and SHCI are farther away from the Hartree-Fock state, -# allowing them to perform better (you can check this by printing the overlaps with -# DMRG and SHCI in a more correlated molecule). If a ground state in such a case was known, -# the overlap to it could tell us directly the quality of the initial state. - -############################################################################## -# Conclusion -# ----------- -# This demo shows how to import initial states from outputs of traditional quantum chemistry methods -# for use in PennyLane. We showcased simple workflows for how to run -# a variety of state-of-the-art post-Hartree-Fock methods, from libraries such as -# `PySCF `_, -# `Block2 `_ and -# `Dice `_, to generate outputs that can then be -# converted to PennyLane's state vector format with a single line of code. With these -# initial states, we use the example of VQE to demonstrate how a better choice -# of initial state can lead to improved algorithmic performance. For the molecule -# used in our example, the CISD state was sufficient: however, in more correlated -# molecules, DMRG and SHCI initial states typically provide the best speed-ups. -# -# About the author -# ---------------- -# +r""" + +Initial state preparation for quantum chemistry +=============================================== + +A high-quality initial state can significantly reduce the runtime of many quantum algorithms. From +the variational quantum eigensolver (VQE) to quantum phase estimation (QPE) and even the recent +`intermediate-scale quantum (ISQ) `_ algorithms, obtaining the ground state of a chemical system requires +a good initial state. For instance, in the case of VQE, a good initial state directly translates into fewer +optimization steps. In QPE, the probability of measuring the ground-state energy is directly +proportional to the overlap squared of the initial and ground states. Even beyond quantum chemistry, +good initial guesses are important for algorithms like quantum approximate optimization (QAOA) +and Grover search. + +Much like searching for a needle in a haystack, there are a lot of things you might try +to prepare a good guess for the ground state in the high-dimensional Hilbert space. In this +tutorial, we show how to use traditional computational chemistry techniques to +get a good initial state. Such an initial state will not be exactly +the ground state, but it will certainly be better than the standard guess of a computational +basis state :math:`|0\rangle^{\otimes N}` or the Hartree-Fock state. + +.. figure:: ../_static/demonstration_assets/initial_state_preparation/qchem_input_states.png + :align: center + :width: 65% + :target: javascript:void(0) + +Importing initial states +------------------------ +We can import initial states obtained from several post-Hartree-Fock quantum chemistry methods +to PennyLane. These methods are incredibly diverse in terms of their outputs, not always returning +an object that is easy to turn into a PennyLane state vector. + +We have already done this hard conversion work: all that you need to do is run these methods and +pass their outputs to PennyLane's :func:`~.pennylane.qchem.import_state` function. The currently +supported methods are configuration interaction with singles and doubles (CISD), coupled cluster +(CCSD), density-matrix renormalization group (DMRG) and semistochastic heat-bath configuration +interaction (SHCI). + +We now show how this works on the linear :math:`\text{H}_3^+` molecule as an example. + + +CISD states +~~~~~~~~~~~ +The first line of attack for initial state preparation is often a CISD calculation, performed with the `PySCF `_ +library. CISD is unsophisticated, but it is fast. It will not be of much help for strongly correlated molecules, +but it is better than Hartree-Fock. Here is the code example using the restricted Hartree-Fock +orbitals (:func:`~.pennylane.qchem.import_state` works for unrestricted orbitals, too). +""" + +from pyscf import gto, scf, ci +from pennylane.qchem import import_state +import numpy as np + +R = 1.2 +# create the H3+ molecule +mol = gto.M(atom=[["H", (0, 0, 0)], ["H", (0, 0, R)], ["H", (0, 0, 2 * R)]], charge=1) +# perfrom restricted Hartree-Fock and then CISD +myhf = scf.RHF(mol).run() +myci = ci.CISD(myhf).run() +wf_cisd = import_state(myci, tol=1e-1) +print(f"CISD-based state vector: \n{np.round(wf_cisd.real, 4)}") + +############################################################################## +# The final object, PennyLane's state vector ``wf_cisd``, is ready to be used as an +# initial state in a quantum circuit in PennyLane--we will showcase this below for VQE. +# +# Conversion for CISD to a state vector is straightforward: simply assign the PySCF-stored +# CI coefficients to appropriate entries in the state vector based on the determinant they correspond to. +# The second attribute passed to ``import_state()``, ``tol``, specifies the cutoff beyond +# which contributions to the wavefunctions are neglected. Internally, wavefunctions are +# stored in their Slater determinant representation. If their prefactor coefficient +# is below ``tol``, those determinants are dropped from the expression. + +############################################################################## +# CCSD states +# ~~~~~~~~~~~ +# The function :func:`~.pennylane.qchem.import_state` is general and works similarly for CCSD. It can +# automatically detect the input type and apply the appropriate conversion protocol. + +from pyscf import cc + +mycc = cc.CCSD(myhf).run() +wf_ccsd = import_state(mycc, tol=1e-1) +print(f"CCSD-based state vector: \n{np.round(wf_ccsd.real, 4)}") + +############################################################################## +# For CCSD conversion, at present the exponential form is expanded and terms are collected **to +# second order** to obtain the CI coefficients. +# +# DMRG states +# ~~~~~~~~~~~ +# For more complex or more correlated molecules, initial states from DMRG or +# SHCI will be better options. DMRG calculations involve using the library `Block2 `_, +# which can be installed with ``pip``: +# +# .. code-block:: bash +# +# pip install block2 +# +# The DMRG calculation is run on top of the molecular orbitals obtained by Hartree-Fock, +# stored in the ``myhf`` object, which we can reuse from before. +# +# .. code-block:: python +# +# from pyscf import mcscf +# from pyblock2.driver.core import DMRGDriver, SymmetryTypes +# from pyblock2._pyscf.ao2mo import integrals as itg +# +# # obtain molecular integrals and other parameters for DMRG +# mc = mcscf.CASCI(myhf, mol.nao, mol.nelectron) +# ncas, n_elec, spin, ecore, h1e, g2e, orb_sym = \ +# itg.get_rhf_integrals(myhf, mc.ncore, mc.ncas, g2e_symm=8) +# +# # initialize the DMRG solver, Hamiltonian (as matrix-product operator, MPO) and +# # state (as matrix-product state, MPS) +# driver = DMRGDriver(scratch="./dmrg_temp", symm_type=SymmetryTypes.SZ) +# driver.initialize_system(n_sites=ncas, n_elec=n_elec, spin=spin, orb_sym=orb_sym) +# mpo = driver.get_qc_mpo(h1e=h1e, g2e=g2e, ecore=ecore, iprint=0) +# ket = driver.get_random_mps(tag="GS") +# +# # execute DMRG by modifying the ket state in-place to minimize the energy +# driver.dmrg(mpo, ket, n_sweeps=30,bond_dims=[100,200],\ +# noises=[1e-3,1e-5],thrds=[1e-6,1e-7],tol=1e-6) +# +# # post-process the MPS to get an initial state +# dets, coeffs = driver.get_csf_coefficients(ket, iprint=0) +# dets = dets.tolist() +# wf_dmrg = import_state((dets, coeffs), tol=1e-1) +# print(f"DMRG-based state vector: \n {np.round(wf_dmrg, 4)}") +# +# .. code-block:: bash +# +# DMRG-based state vector +# [ 0. 0. 0. 0. 0. 0. 0. 0. 0. +# 0. 0. 0. -0.2243 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.9745 0. 0. 0. 0. 0. +# 0. 0. 0. 0. 0. 0. 0. 0. 0. +# 0. ] + +############################################################################## +# The crucial part is calling ``get_csf_coefficients()`` on the solution stored in +# MPS form in the ``ket``. This triggers an internal reconstruction calculation that +# converts the MPS to the sum of Slater determinants form, returning the output +# as a tuple ``(array([int]), array(float]))``. The first element expresses a given Slater +# determinant using Fock occupation vectors of length equal to the number of spatial +# orbitals. In Block2 notation, the entries can be ``0`` (orbital unoccupied), ``1`` (orbital occupied with spin-up +# electron), ``2`` (occupied with spin-down), and ``3`` (doubly occupied). The first +# element, ``array([int])``, must be converted to ``list`` +# for :func:`~.pennylane.qchem.import_state` to accept it. +# The second element stores the CI coefficients. +# +# In principle, this functionality can be used to generate any initial state, provided +# the user specifies a list of Slater determinants and their coefficients in this form. +# Let's take this opportunity to create the Hartree-Fock initial state, to compare the +# other states against it later on. + +hf_primer = ([[3, 0, 0]], np.array([1.0])) +wf_hf = import_state(hf_primer) + +############################################################################## +# SHCI states +# ~~~~~~~~~~~ +# +# The SHCI calculations utilize the library `Dice `_, and can be run +# using PySCF through the interface module `SHCI-SCF `_. +# For Dice, the execution process is similar to that of DMRG: +# +# .. code-block:: python +# +# from pyscf.shciscf import shci +# +# # prepare PySCF CASCI object, whose solver will be the SHCI method +# ncas, nelecas_a, nelecas_b = mol.nao, mol.nelectron // 2, mol.nelectron // 2 +# myshci = mcscf.CASCI(myhf, ncas, (nelecas_a, nelecas_b)) +# +# # set up essentials for the SHCI solver +# output_file = f"shci_output.out" +# myshci.fcisolver = shci.SHCI(myhf.mol) +# myshci.fcisolver.outputFile = output_file +# +# # execute SHCI through the PySCF interface +# e_tot, e_ci, ss, mo_coeff, mo_energies = myshci.kernel(verbose=5) +# +# # post-process the shci_output.out to extract the wave function +# # results and create the tuple of dets (list([str])) and coeffs (array([float])) +# # shci_data = (dets, coeffs) +# wf_shci = import_state(shci_data, tol=1e-1) +# print(f"SHCI-based state vector\n{wf_shci}") +# +# .. code-block:: bash +# +# SHCI-based state vector +# [ 0. 0. 0. 0. 0. 0. 0. 0. 0. +# 0. 0. 0. 0.2243 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.9745 0. 0. 0. 0. 0. +# 0. 0. 0. 0. 0. 0. 0. 0. 0. +# 0. ] + +############################################################################## +# The Dice output file prints determinants using symbols ``0`` (unoccupied orbital), +# ``a`` and ``b`` (orbital occupied with spin-up and spin-down electron, respectively), +# and ``2`` (doubly occupied orbital). These determinant outputs, and corresponding +# coefficients, should be extracted and arranged as ``(list([str]), array(float]))``, +# where each string combines all the determinant symbols ``0, a, b, 2`` for a single +# determinant with no spaces. For example, for the HF state we created in the DMRG section, +# the SHCI output should read ``([["200"]], np.array([1.]))`` + +############################################################################## +# Application: speed up VQE +# ------------------------- +# Let us now demonstrate how the choice of a better initial state shortens the runtime +# of VQE for obtaining the ground-state energy of a molecule. As a first step, create our +# linear :math:`\text{H}_3^+` molecule, a device, and a simple VQE circuit with single and double excitations: + +import pennylane as qml +from pennylane import qchem +from jax import numpy as jnp + +# generate the molecular Hamiltonian for H3+ +symbols = ["H", "H", "H"] +geometry = jnp.array([[0, 0, 0], [0, 0, R/0.529], [0, 0, 2*R/0.529]]) +molecule = qchem.Molecule(symbols, geometry, charge=1) + +H2mol, qubits = qchem.molecular_hamiltonian(molecule) +wires = list(range(qubits)) +dev = qml.device("default.qubit", wires=qubits) + +# create all possible excitations in H3+ +singles, doubles = qchem.excitations(2, qubits) +excitations = singles + doubles + +############################################################################## +# Now let's run VQE with the Hartree-Fock initial state. We first build the VQE circuit: + + +@qml.qnode(dev) +def circuit_VQE(theta, initial_state): + qml.StatePrep(initial_state, wires=wires) + for i, excitation in enumerate(excitations): + if len(excitation) == 4: + qml.DoubleExcitation(theta[i], wires=excitation) + else: + qml.SingleExcitation(theta[i], wires=excitation) + return qml.expval(H2mol) + + +def cost_fn(param): + return circuit_VQE(param, initial_state=wf_hf) + + +############################################################################## +# Next, we create the VQE optimizer, initialize the variational parameters and run the VQE optimization. +import optax +import jax +jax.config.update("jax_enable_x64", True) + +opt = optax.sgd(learning_rate=0.4) # sgd stands for StochasticGradientDescent +theta = jnp.array(jnp.zeros(len(excitations))) +delta_E, iteration = 10, 0 +results_hf = [] +opt_state = opt.init(theta) +prev_energy = cost_fn(theta) + +# run the VQE optimization loop until convergence threshold is reached +while abs(delta_E) > 1e-5: + gradient = jax.grad(cost_fn)(theta) + updates, opt_state = opt.update(gradient, opt_state) + theta = optax.apply_updates(theta, updates) + new_energy = cost_fn(theta) + delta_E = new_energy - prev_energy + prev_energy = new_energy + results_hf.append(new_energy) + if len(results_hf) % 5 == 0: + print(f"Step = {len(results_hf)}, Energy = {new_energy:.6f} Ha") +print(f"Starting with HF state took {len(results_hf)} iterations until convergence.") + +############################################################################## +# And compare with how things go when you run it with the CISD initial state: + + +def cost_fn_cisd(param): + return circuit_VQE(param, initial_state=wf_cisd) + + +theta = jnp.array(jnp.zeros(len(excitations))) +delta_E, iteration = 10, 0 +results_cisd = [] +opt_state = opt.init(theta) +prev_energy = cost_fn_cisd(theta) + +while abs(delta_E) > 1e-5: + gradient = jax.grad(cost_fn_cisd)(theta) + updates, opt_state = opt.update(gradient, opt_state) + theta = optax.apply_updates(theta, updates) + new_energy = cost_fn_cisd(theta) + delta_E = new_energy - prev_energy + prev_energy = new_energy + results_cisd.append(new_energy) + if len(results_cisd) % 5 == 0: + print(f"Step = {len(results_cisd)}, Energy = {new_energy:.6f} Ha") +print( + f"Starting with CISD state took {len(results_cisd)} iterations until convergence." +) + +############################################################################## +# Let's visualize the comparison between the two initial states, and see that indeed +# we get to the ground state much faster by starting with the CISD state. + +import matplotlib.pyplot as plt + +fig, ax = plt.subplots() +ax.plot(range(len(results_hf)), results_hf, color="r", marker="o", label="wf_hf") +ax.plot(range(len(results_cisd)), results_cisd, color="b", marker="o", label="wf_cisd") +ax.legend(fontsize=16) +ax.tick_params(axis="both", labelsize=16) +ax.set_xlabel("Iteration", fontsize=20) +ax.set_ylabel("Energy, Ha", fontsize=20) +plt.tight_layout() +plt.show() + +############################################################################## +# Indeed, the CISD state significantly shortens the VQE runtime. +# +# It is sometimes possible to foresee the extent of this speed-up of a particular initial state +# by computing its overlap with the ground state--a traditional metric of success for initial +# states in quantum algorithms. Because in our examples the states are regular arrays, computing an +# overlap between different states is as easy as computing a dot product + +print(np.dot(wf_cisd, wf_hf).real) +print(np.dot(wf_ccsd, wf_hf).real) +print(np.dot(wf_cisd, wf_ccsd).real) + +############################################################################## +# In this particular case of :math:`\text{H}_3^+,` even CISD gives the exact wavefunction, hence both overlaps +# with the HF state are identical. In more correlated molecules, overlaps will show that the more +# multireference methods DMRG and SHCI are farther away from the Hartree-Fock state, +# allowing them to perform better (you can check this by printing the overlaps with +# DMRG and SHCI in a more correlated molecule). If a ground state in such a case was known, +# the overlap to it could tell us directly the quality of the initial state. + +############################################################################## +# Conclusion +# ----------- +# This demo shows how to import initial states from outputs of traditional quantum chemistry methods +# for use in PennyLane. We showcased simple workflows for how to run +# a variety of state-of-the-art post-Hartree-Fock methods, from libraries such as +# `PySCF `_, +# `Block2 `_ and +# `Dice `_, to generate outputs that can then be +# converted to PennyLane's state vector format with a single line of code. With these +# initial states, we use the example of VQE to demonstrate how a better choice +# of initial state can lead to improved algorithmic performance. For the molecule +# used in our example, the CISD state was sufficient: however, in more correlated +# molecules, DMRG and SHCI initial states typically provide the best speed-ups. +# +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_jax_transformations/demo.py b/demonstrations_v2/tutorial_jax_transformations/demo.py index 476e8fa3a4..9c13c37271 100644 --- a/demonstrations_v2/tutorial_jax_transformations/demo.py +++ b/demonstrations_v2/tutorial_jax_transformations/demo.py @@ -1,311 +1,311 @@ -r""" -Using JAX with PennyLane -======================== - -.. meta:: - :property="og:description": Learn how to use JAX with PennyLane. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/jax.png - -.. related:: - - tutorial_qubit_rotation Basic tutorial: qubit rotation - tutorial_vqe A brief overview of VQE - tutorial_vqt Variational Quantum Thermalizer - -*Author: Chase Roberts — Posted: 12 April 2021. Last updated: 12 April 2021.* - -JAX is an incredibly powerful scientific computing library that has been gaining traction in -both the physics and deep learning communities. While JAX was originally designed for -classical machine learning (ML), many of its transformations are also useful -for quantum machine learning (QML), and can be used directly with PennyLane. -""" - -############################################################################## -# .. figure:: ../_static/demonstration_assets/jax_logo/jax.png -# :width: 50% -# :align: center -# -# In this tutorial, we'll go over a number of JAX transformations and show how you can -# use them to build and optimize quantum circuits. We'll show examples of how to -# do gradient descent with ``jax.grad``, run quantum circuits in parallel -# using ``jax.vmap``, compile and optimize simulations with ``jax.jit``, -# and control and seed the random nature of quantum computer simulations -# with ``jax.random``. By the end of this tutorial you should feel just as comfortable -# transforming quantum computing programs with JAX as you do transforming your -# neural networks. -# -# If this is your first time reading PennyLane code, we recommend going through -# the :doc:`basic tutorial ` -# first. It's all in vanilla NumPy, so you should be able to -# easily transfer what you learn to JAX when you come back. -# -# With that said, we begin by importing PennyLane, JAX, the JAX-provided version of NumPy and -# set up a two-qubit device for computations. We'll be using the ``default.qubit`` device -# for the first part of this tutorial. - -import jax -import jax.numpy as jnp -import pennylane as qml - -# Added to silence some warnings. -jax.config.update("jax_enable_x64", True) - -dev = qml.device("default.qubit", wires=2) - -############################################################################## -# Let's start with a simple example circuit that generates a two-qubit entangled state, -# then evaluates the expectation value of the Pauli-Z operator on the first wire. - - -@qml.qnode(dev, interface="jax") -def circuit(param): - # These two gates represent our QML model. - qml.RX(param, wires=0) - qml.CNOT(wires=[0, 1]) - - # The expval here will be the "cost function" we try to minimize. - # Usually, this would be defined by the problem we want to solve, - # but for this example we'll just use a single PauliZ. - return qml.expval(qml.PauliZ(0)) - - -############################################################################## -# We can now execute the circuit just like any other python function. -print(f"Result: {repr(circuit(0.123))}") - -############################################################################## -# Notice that the output of the circuit is a JAX ``DeviceArray``. -# In fact, when we use the ``default.qubit`` device, the entire computation -# is done in JAX, so we can use all of the JAX tools out of the box! -# -# Now let's move on to an example of a transformation. The code we wrote above is entirely -# differentiable, so let's calculate its gradient with ``jax.grad``. -print("\nGradient Descent") -print("---------------") - -# We use jax.grad here to transform our circuit method into one -# that calcuates the gradient of the output relative to the input. - -grad_circuit = jax.grad(circuit) -print(f"grad_circuit(jnp.pi / 2): {grad_circuit(jnp.pi / 2):0.3f}") - -# We can then use this grad_circuit function to optimize the parameter value -# via gradient descent. -param = 0.123 # Some initial value. - -print(f"Initial param: {param:0.3f}") -print(f"Initial cost: {circuit(param):0.3f}") - -for _ in range(100): # Run for 100 steps. - param -= grad_circuit(param) # Gradient-descent update. - -print(f"Tuned param: {param:0.3f}") -print(f"Tuned cost: {circuit(param):0.3f}") - -############################################################################# -# And that's QML in a nutshell! If you've done classical machine learning before, -# the above training loop should feel very familiar to you. The only difference is -# that we used a quantum computer (or rather, a simulation of one) as part of our -# model and cost calculation. In the end, almost all QML problems involve tuning some -# parameters and minimizing some cost function, just like classical ML. -# While classical ML focuses on learning classical systems like language or vision, -# QML is most useful for learning about quantum systems. For example, -# :doc:`finding chemical ground states ` -# or learning to :doc:`sample thermal energy states `. - - -############################################################################## -# Batching and Evolutionary Strategies -# ------------------------------------- -# -# .. figure:: ../_static/demonstration_assets/jax_logo/jaxvmap.png -# :width: 50% -# :align: center -# -# We just showed how we can use gradient methods to learn a parameter value, -# but on real quantum computing hardware, calculating gradients can be really expensive and noisy. -# Another approach is to use `evolutionary strategies `__ -# (ES) to learn these parameters. -# Here, we will be using the ``jax.vmap`` `transform `__ -# to make running batches of circuits much easier. ``vmap`` essentially transforms a single quantum computer into -# multiple running in parallel! - -print("\n\nBatching and Evolutionary Strategies") -print("------------------------------------") - -# Create a vectorized version of our original circuit. -vcircuit = jax.vmap(circuit) - -# Now, we call the ``vcircuit`` with multiple parameters at once and get back a -# batch of expectations. -# This examples runs 3 quantum circuits in parallel. -batch_params = jnp.array([1.02, 0.123, -0.571]) - -batched_results = vcircuit(batch_params) -print(f"Batched result: {batched_results}") - -############################################################################## -# Let's now set up our ES training loop. The idea is pretty simple. First, we -# calculate the expected values of each of our parameters. The cost values -# then determine the "weight" of that example. The lower the cost, the larger the weight. -# These batches are then used to generate a new set of parameters. - -# Needed to do randomness with JAX. -# For more info on how JAX handles randomness, see the documentation. -# https://jax.readthedocs.io/en/latest/jax.random.html -key = jax.random.PRNGKey(0) - -# Generate our first set of samples. -params = jax.random.normal(key, (100,)) -mean = jnp.average(params) -var = 1.0 -print(f"Initial value: {mean:0.3f}") -print(f"Initial cost: {circuit(mean):0.3f}") - -for _ in range(200): - # In this line, we run all 100 circuits in parallel. - costs = vcircuit(params) - - # Use exp(-x) here since the costs could be negative. - weights = jnp.exp(-costs) - mean = jnp.average(params, weights=weights) - - # We decrease the variance as we converge to a solution. - var = var * 0.97 - - # Split the PRNGKey to generate a new set of random samples. - key, split = jax.random.split(key) - params = jax.random.normal(split, (100,)) * var + mean - -print(f"Final value: {mean:0.3f}") -print(f"Final cost: {circuit(mean):0.3f}") - - -############################################################################# -# How to use jax.jit: Compiling Circuit Execution -# ----------------------------------------------- -# .. figure:: ../_static/demonstration_assets/jax_logo/jaxjit.png -# :width: 50% -# :align: center -# -# JAX is built on top of `XLA `__, a powerful -# numerics library that can optimize and cross compile computations to different hardware, -# including CPUs, GPUs, etc. JAX can compile its computation to XLA via the ``jax.jit`` -# `transform. `__ -# -# When compiling an XLA program, the compiler will do several rounds of optimization -# passes to enhance the performance of the computation. Because of this compilation overhead, -# you'll generally find the first time calling the function to be slow, but all subsequent -# calls are much, much faster. You'll likely want to do it if you're running -# the same circuit over and over but with different parameters, like you would find in almost -# all variational quantum algorithms. - - -print("\n\nJit Example") -print("-----------") - - -@qml.qnode(dev, interface="jax") -def circuit(param): - qml.RX(param, wires=0) - qml.CNOT(wires=[0, 1]) - return qml.expval(qml.PauliZ(0)) - - -# Compiling your circuit with JAX is very easy, just add jax.jit! -jit_circuit = jax.jit(circuit) - -import time - -# No jit. -start = time.time() -# JAX runs async, so .block_until_ready() blocks until the computation -# is actually finished. You'll only need to use this if you're doing benchmarking. -circuit(0.123).block_until_ready() -no_jit_time = time.time() - start - -# First call with jit. -start = time.time() -jit_circuit(0.123).block_until_ready() -first_time = time.time() - start - -# Second call with jit. -start = time.time() -jit_circuit(0.123).block_until_ready() -second_time = time.time() - start - - -print(f"No jit time: {no_jit_time:0.8f} seconds") -# Compilation overhead will make the first call slower than without jit... -print(f"First run time: {first_time:0.8f} seconds") -# ... but the second run time is >100x faster than the first! -print(f"Second run time: {second_time:0.8f} seconds") - - -# You can see that for the cost of some compilation overhead, we can -# greatly increase our performance of our simulation by orders of magnitude. - -############################################################################## -# Shots and Sampling with JAX -# ---------------------------- -# -# JAX was designed to enable experiments to be as repeatable as possible. Because of this, -# JAX requires us to seed all randomly generated values (as you saw in the above -# batching example). Sadly, the universe doesn't allow us to seed real quantum computers, -# so if we want our JAX to mimic a real device, we'll have to handle randomness ourselves. -# -# To learn more about how JAX handles randomness, visit their -# `documentation site. `__ -# -# .. note:: -# This example only applies if you are using ``jax.jit``. Otherwise, PennyLane -# automatically seeds and resets the random-number-generator for you on each call. -# -# To set the random number generating key, you'll have to pass the ``jax.random.PRNGKey`` -# when constructing the device. Because of this, if you want to use ``jax.jit`` with randomness, -# the device construction will have to happen within that jitted method. - -print("\n\nRandomness") -print("----------") - - -# Let's create our circuit with randomness and compile it with jax.jit. -@jax.jit -def circuit(key, param): - # Notice how the device construction now happens within the jitted method. - dev = qml.device("default.qubit", wires=2, shots=10, seed=key) - - # Now we can create our qnode within the circuit function. - @qml.qnode(dev, interface="jax", diff_method=None) - def my_circuit(): - qml.RX(param, wires=0) - qml.CNOT(wires=[0, 1]) - return qml.sample(qml.PauliZ(0)) - - return my_circuit() - - -key1 = jax.random.PRNGKey(0) -key2 = jax.random.PRNGKey(1) - -# Notice that the first two runs return exactly the same results, -print(f"key1: {circuit(key1, jnp.pi/2)}") -print(f"key1: {circuit(key1, jnp.pi/2)}") - -# The second run has different results. -print(f"key2: {circuit(key2, jnp.pi/2)}") - -################################################ -# Closing Remarks -# ---------------- -# By now, using JAX with PennyLane should feel very natural. They -# complement each other very nicely; JAX with its powerful transforms, and PennyLane -# with its easy access to quantum computers. We're still in early days of -# development, but we hope to continue to grow our ecosystem around JAX, -# and by extension, grow JAX into quantum computing and quantum machine learning. -# The future looks bright for this field, and we're excited to see what you build! -# -# -# About the author -# ---------------- -# +r""" +Using JAX with PennyLane +======================== + +.. meta:: + :property="og:description": Learn how to use JAX with PennyLane. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/jax.png + +.. related:: + + tutorial_qubit_rotation Basic tutorial: qubit rotation + tutorial_vqe A brief overview of VQE + tutorial_vqt Variational Quantum Thermalizer + +*Author: Chase Roberts — Posted: 12 April 2021. Last updated: 12 April 2021.* + +JAX is an incredibly powerful scientific computing library that has been gaining traction in +both the physics and deep learning communities. While JAX was originally designed for +classical machine learning (ML), many of its transformations are also useful +for quantum machine learning (QML), and can be used directly with PennyLane. +""" + +############################################################################## +# .. figure:: ../_static/demonstration_assets/jax_logo/jax.png +# :width: 50% +# :align: center +# +# In this tutorial, we'll go over a number of JAX transformations and show how you can +# use them to build and optimize quantum circuits. We'll show examples of how to +# do gradient descent with ``jax.grad``, run quantum circuits in parallel +# using ``jax.vmap``, compile and optimize simulations with ``jax.jit``, +# and control and seed the random nature of quantum computer simulations +# with ``jax.random``. By the end of this tutorial you should feel just as comfortable +# transforming quantum computing programs with JAX as you do transforming your +# neural networks. +# +# If this is your first time reading PennyLane code, we recommend going through +# the :doc:`basic tutorial ` +# first. It's all in vanilla NumPy, so you should be able to +# easily transfer what you learn to JAX when you come back. +# +# With that said, we begin by importing PennyLane, JAX, the JAX-provided version of NumPy and +# set up a two-qubit device for computations. We'll be using the ``default.qubit`` device +# for the first part of this tutorial. + +import jax +import jax.numpy as jnp +import pennylane as qml + +# Added to silence some warnings. +jax.config.update("jax_enable_x64", True) + +dev = qml.device("default.qubit", wires=2) + +############################################################################## +# Let's start with a simple example circuit that generates a two-qubit entangled state, +# then evaluates the expectation value of the Pauli-Z operator on the first wire. + + +@qml.qnode(dev, interface="jax") +def circuit(param): + # These two gates represent our QML model. + qml.RX(param, wires=0) + qml.CNOT(wires=[0, 1]) + + # The expval here will be the "cost function" we try to minimize. + # Usually, this would be defined by the problem we want to solve, + # but for this example we'll just use a single PauliZ. + return qml.expval(qml.PauliZ(0)) + + +############################################################################## +# We can now execute the circuit just like any other python function. +print(f"Result: {repr(circuit(0.123))}") + +############################################################################## +# Notice that the output of the circuit is a JAX ``DeviceArray``. +# In fact, when we use the ``default.qubit`` device, the entire computation +# is done in JAX, so we can use all of the JAX tools out of the box! +# +# Now let's move on to an example of a transformation. The code we wrote above is entirely +# differentiable, so let's calculate its gradient with ``jax.grad``. +print("\nGradient Descent") +print("---------------") + +# We use jax.grad here to transform our circuit method into one +# that calcuates the gradient of the output relative to the input. + +grad_circuit = jax.grad(circuit) +print(f"grad_circuit(jnp.pi / 2): {grad_circuit(jnp.pi / 2):0.3f}") + +# We can then use this grad_circuit function to optimize the parameter value +# via gradient descent. +param = 0.123 # Some initial value. + +print(f"Initial param: {param:0.3f}") +print(f"Initial cost: {circuit(param):0.3f}") + +for _ in range(100): # Run for 100 steps. + param -= grad_circuit(param) # Gradient-descent update. + +print(f"Tuned param: {param:0.3f}") +print(f"Tuned cost: {circuit(param):0.3f}") + +############################################################################# +# And that's QML in a nutshell! If you've done classical machine learning before, +# the above training loop should feel very familiar to you. The only difference is +# that we used a quantum computer (or rather, a simulation of one) as part of our +# model and cost calculation. In the end, almost all QML problems involve tuning some +# parameters and minimizing some cost function, just like classical ML. +# While classical ML focuses on learning classical systems like language or vision, +# QML is most useful for learning about quantum systems. For example, +# :doc:`finding chemical ground states ` +# or learning to :doc:`sample thermal energy states `. + + +############################################################################## +# Batching and Evolutionary Strategies +# ------------------------------------- +# +# .. figure:: ../_static/demonstration_assets/jax_logo/jaxvmap.png +# :width: 50% +# :align: center +# +# We just showed how we can use gradient methods to learn a parameter value, +# but on real quantum computing hardware, calculating gradients can be really expensive and noisy. +# Another approach is to use `evolutionary strategies `__ +# (ES) to learn these parameters. +# Here, we will be using the ``jax.vmap`` `transform `__ +# to make running batches of circuits much easier. ``vmap`` essentially transforms a single quantum computer into +# multiple running in parallel! + +print("\n\nBatching and Evolutionary Strategies") +print("------------------------------------") + +# Create a vectorized version of our original circuit. +vcircuit = jax.vmap(circuit) + +# Now, we call the ``vcircuit`` with multiple parameters at once and get back a +# batch of expectations. +# This examples runs 3 quantum circuits in parallel. +batch_params = jnp.array([1.02, 0.123, -0.571]) + +batched_results = vcircuit(batch_params) +print(f"Batched result: {batched_results}") + +############################################################################## +# Let's now set up our ES training loop. The idea is pretty simple. First, we +# calculate the expected values of each of our parameters. The cost values +# then determine the "weight" of that example. The lower the cost, the larger the weight. +# These batches are then used to generate a new set of parameters. + +# Needed to do randomness with JAX. +# For more info on how JAX handles randomness, see the documentation. +# https://jax.readthedocs.io/en/latest/jax.random.html +key = jax.random.PRNGKey(0) + +# Generate our first set of samples. +params = jax.random.normal(key, (100,)) +mean = jnp.average(params) +var = 1.0 +print(f"Initial value: {mean:0.3f}") +print(f"Initial cost: {circuit(mean):0.3f}") + +for _ in range(200): + # In this line, we run all 100 circuits in parallel. + costs = vcircuit(params) + + # Use exp(-x) here since the costs could be negative. + weights = jnp.exp(-costs) + mean = jnp.average(params, weights=weights) + + # We decrease the variance as we converge to a solution. + var = var * 0.97 + + # Split the PRNGKey to generate a new set of random samples. + key, split = jax.random.split(key) + params = jax.random.normal(split, (100,)) * var + mean + +print(f"Final value: {mean:0.3f}") +print(f"Final cost: {circuit(mean):0.3f}") + + +############################################################################# +# How to use jax.jit: Compiling Circuit Execution +# ----------------------------------------------- +# .. figure:: ../_static/demonstration_assets/jax_logo/jaxjit.png +# :width: 50% +# :align: center +# +# JAX is built on top of `XLA `__, a powerful +# numerics library that can optimize and cross compile computations to different hardware, +# including CPUs, GPUs, etc. JAX can compile its computation to XLA via the ``jax.jit`` +# `transform. `__ +# +# When compiling an XLA program, the compiler will do several rounds of optimization +# passes to enhance the performance of the computation. Because of this compilation overhead, +# you'll generally find the first time calling the function to be slow, but all subsequent +# calls are much, much faster. You'll likely want to do it if you're running +# the same circuit over and over but with different parameters, like you would find in almost +# all variational quantum algorithms. + + +print("\n\nJit Example") +print("-----------") + + +@qml.qnode(dev, interface="jax") +def circuit(param): + qml.RX(param, wires=0) + qml.CNOT(wires=[0, 1]) + return qml.expval(qml.PauliZ(0)) + + +# Compiling your circuit with JAX is very easy, just add jax.jit! +jit_circuit = jax.jit(circuit) + +import time + +# No jit. +start = time.time() +# JAX runs async, so .block_until_ready() blocks until the computation +# is actually finished. You'll only need to use this if you're doing benchmarking. +circuit(0.123).block_until_ready() +no_jit_time = time.time() - start + +# First call with jit. +start = time.time() +jit_circuit(0.123).block_until_ready() +first_time = time.time() - start + +# Second call with jit. +start = time.time() +jit_circuit(0.123).block_until_ready() +second_time = time.time() - start + + +print(f"No jit time: {no_jit_time:0.8f} seconds") +# Compilation overhead will make the first call slower than without jit... +print(f"First run time: {first_time:0.8f} seconds") +# ... but the second run time is >100x faster than the first! +print(f"Second run time: {second_time:0.8f} seconds") + + +# You can see that for the cost of some compilation overhead, we can +# greatly increase our performance of our simulation by orders of magnitude. + +############################################################################## +# Shots and Sampling with JAX +# ---------------------------- +# +# JAX was designed to enable experiments to be as repeatable as possible. Because of this, +# JAX requires us to seed all randomly generated values (as you saw in the above +# batching example). Sadly, the universe doesn't allow us to seed real quantum computers, +# so if we want our JAX to mimic a real device, we'll have to handle randomness ourselves. +# +# To learn more about how JAX handles randomness, visit their +# `documentation site. `__ +# +# .. note:: +# This example only applies if you are using ``jax.jit``. Otherwise, PennyLane +# automatically seeds and resets the random-number-generator for you on each call. +# +# To set the random number generating key, you'll have to pass the ``jax.random.PRNGKey`` +# when constructing the device. Because of this, if you want to use ``jax.jit`` with randomness, +# the device construction will have to happen within that jitted method. + +print("\n\nRandomness") +print("----------") + + +# Let's create our circuit with randomness and compile it with jax.jit. +@jax.jit +def circuit(key, param): + # Notice how the device construction now happens within the jitted method. + dev = qml.device("default.qubit", wires=2, shots=10, seed=key) + + # Now we can create our qnode within the circuit function. + @qml.qnode(dev, interface="jax", diff_method=None) + def my_circuit(): + qml.RX(param, wires=0) + qml.CNOT(wires=[0, 1]) + return qml.sample(qml.PauliZ(0)) + + return my_circuit() + + +key1 = jax.random.PRNGKey(0) +key2 = jax.random.PRNGKey(1) + +# Notice that the first two runs return exactly the same results, +print(f"key1: {circuit(key1, jnp.pi/2)}") +print(f"key1: {circuit(key1, jnp.pi/2)}") + +# The second run has different results. +print(f"key2: {circuit(key2, jnp.pi/2)}") + +################################################ +# Closing Remarks +# ---------------- +# By now, using JAX with PennyLane should feel very natural. They +# complement each other very nicely; JAX with its powerful transforms, and PennyLane +# with its easy access to quantum computers. We're still in early days of +# development, but we hope to continue to grow our ecosystem around JAX, +# and by extension, grow JAX into quantum computing and quantum machine learning. +# The future looks bright for this field, and we're excited to see what you build! +# +# +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_mapping/demo.py b/demonstrations_v2/tutorial_mapping/demo.py index 96a757a506..8e88a64420 100644 --- a/demonstrations_v2/tutorial_mapping/demo.py +++ b/demonstrations_v2/tutorial_mapping/demo.py @@ -1,348 +1,348 @@ -r""" - -Mapping fermionic operators to qubit operators -============================================== - -Simulating quantum systems stands as one of the most anticipated applications of quantum -chemistry with the potential to transform our understanding of chemical and physical systems. These -simulations typically require mapping schemes that transform fermionic representations into qubit -representations. There are a variety of mapping schemes used in quantum computing but the -conventional ones are the Jordan-Wigner, Parity, and Bravyi-Kitaev transformations [#Tranter]_. In -this demo, you will learn about these mapping schemes and their implementation in PennyLane. You -will also learn how to use these mappings in the context of computing the ground state energy -of a molecular system. - -.. figure:: ../_static/demonstration_assets/mapping/long_image.png - :align: center - :width: 80% - :target: javascript:void(0) - -Jordan-Wigner Mapping ---------------------- -The state of a quantum system in the `second quantized `__ -formalism is typically represented in the occupation-number basis. For fermions, the occupation -number is either :math:`0` or :math:`1` as a result of the Pauli exclusion principle. The -occupation-number basis states can be represented by a vector that is constructed by -applying the fermionic creation operators, :math:`a^\dagger,` to a vacuum state: - -.. math:: - - a^\dagger | 0 \rangle = | 1 \rangle. - -Similarly, electrons can be removed from a state by applying the fermionic annihilation -operators :math:`a:` - -.. math:: - - a | 1 \rangle = | 0 \rangle. - -An intuitive way to represent a fermionic system in the qubit basis is to store the fermionic -occupation numbers in qubit states. This requires constructing qubit creation and annihilation -operators that can be applied to an initial state to provide the desired occupation number state. -These operators are defined as - -.. math:: - - Q^{\dagger}_j = \frac{1}{2}(X_j - iY_j), - -and - -.. math:: - - Q_j = \frac{1}{2}(X_j + iY_j), - -where :math:`X` and :math:`Y` are Pauli operators. However, an important property of fermionic -creation and annihilation operators is the anti-commutation relations between them, which is not -preserved by directly using the analogous qubit operators. - -.. math:: - - [a^{\dagger}_j, a^{\dagger}_k]_+ = 0, \:\:\:\:\:\:\: [a_j, a_k]_+=0, \:\:\:\:\:\:\: [a_j, a^{\dagger}_k]_+ = \delta_{jk} I, - -where :math:`I` is the identity operator. These relations are essential for capturing the Pauli -exclusion principle which requires the fermionic wave function to be antisymmetric. The -anti-commutation relations between fermionic operators can be incorporated by adding a sequence of -Pauli :math:`Z` operators to the qubit operators (can you verify that?). According to these, -the fermionic creation and annihilation operators can be represented in the basis of Pauli operators: - -.. math:: - - a_{j}^{\dagger} = \frac{1}{2}(X_j - iY_j) \otimes_{k`__, -:math:`a_{5}^{\dagger},` which creates an electron in the fifth orbital. One -way to do this in PennyLane is to use :func:`~.pennylane.fermi.from_string`. We -then map the operator using :func:`~.pennylane.fermi.jordan_wigner`. -""" - -import pennylane as qml -from pennylane.fermi import from_string, jordan_wigner - -qubits = 10 -fermi_op = from_string("5+") -pauli_jw = jordan_wigner(fermi_op, ps=True) -pauli_jw - -############################################################################### -# The long sequence of the :math:`Z` operations can significantly increase the -# resources needed to implement the operator on quantum hardware, as it may require using entangling -# operations across multiple qubits, which can be challenging to implement efficiently. One way to -# avoid having such long tails of :math:`Z` operations is to work in the parity basis where the -# fermionic state stores the parity instead of the occupation number. -# -# Parity Mapping -# -------------- -# In the Parity representation, the state of a fermionic system is represented with a binary -# vector that corresponds to the parity of the orbitals. Note that parity for a set of orbitals -# is defined as the sum (mod 2) of the occupation numbers. Let's look at an example using the -# PennyLane function :func:`~.pennylane.qchem.hf_state` to obtain the state of a system with -# :math:`10` orbitals and :math:`5` electrons in both the occupation-number and parity bases. - -orbitals = 10 -electrons = 5 -state_number = qml.qchem.hf_state(electrons, orbitals) -state_parity = qml.qchem.hf_state(electrons, orbitals, basis="parity") - -print("State in occupation number basis:\n", state_number) -print("State in parity basis:\n", state_parity) - -############################################################################## -# Note that Parity mapping solves the non-locality problem of the parity information by storing -# the parity of orbital :math:`j` in qubit :math:`j` while the occupation information for the -# orbital is stored non-locally. In the parity basis, we cannot represent the creation or -# annihilation of a particle in orbital -# :math:`j` by simply operating with qubit creation or annihilation operators. In fact, the state of -# the :math:`(j − 1)`-th qubit provides information about the occupation state of qubit :math:`j` -# and whether we need to act with a creation or annihilation operator. Similarly, the creation or -# annihilation of a particle in qubit :math:`j` changes the parity of all qubits following it. -# As a result, the operator that is equivalent to creation and annihilation operators in -# the parity basis is a two-qubit operator acting on qubits :math:`j` and :math:`j − 1,` and -# an update operator which updates the parity of all qubits with index larger than j: -# -# .. math:: -# -# a_{j}^{\dagger} = \frac{1}{2}(Z_{j-1} \otimes X_j - iY_j) \otimes_{k>j} X_{k} -# -# and -# -# .. math:: -# -# a_{j} = \frac{1}{2}(Z_{j-1} \otimes X_j + iY_j) \otimes_{k>j} X_{k} -# -# Let's now look at an example where we map our fermionic operator :math:`a_{5}^{\dagger}` in a -# :math:`10` qubit system with -# Parity mapping using :func:`~.pennylane.fermi.parity_transform` in PennyLane. - -qubits = 10 -pauli_pr = qml.parity_transform(fermi_op, qubits, ps=True) -pauli_pr - -############################################################################## -# It is evident from this example that the Parity mapping doesn't improve upon the -# the Jordan-Wigner mapping as the long :math:`Z` strings are now replaced by :math:`X` -# strings. However, a very important advantage of using parity mapping is the ability to taper two -# qubits by leveraging symmetries of molecular Hamiltonians. You can find -# more information about this in our -# `qubit tapering `__ demo. -# Let's look at an example. - -generators = [qml.prod(*[qml.Z(i) for i in range(qubits-1)]), qml.Z(qubits-1)] -paulixops = qml.paulix_ops(generators, qubits) -paulix_sector = [1, 1] -taper_op = qml.taper(pauli_pr, generators, paulixops, paulix_sector) -qml.simplify(taper_op) - -############################################################################### -# Note that the tapered operator doesn't include qubit :math:`8` and :math:`9.` -# -# Bravyi-Kitaev Mapping -# --------------------- -# The Bravyi-Kitaev mapping aims to improve the linear scaling of the Jordan-Wigner and Parity -# mappings, in the number of qubits, -# by storing both the occupation number and the parity non-locally. In this formalism, even-labelled -# qubits store the occupation number of orbitals and odd-labelled qubits store parity -# through partial sums of occupation numbers. The corresponding creation and annihilation operators -# are defined `here `__. -# Let's use the :func:`~.pennylane.fermi.bravyi_kitaev` function to map our :math:`a_{5}^{\dagger}` -# operator. - -pauli_bk = qml.bravyi_kitaev(fermi_op, qubits, ps=True) -pauli_bk - -############################################################################## -# It is clear that the local nature of the transformation in the Bravyi-Kitaev mapping helps to -# improve the number of qubits. This advantage becomes even more clear if you -# work with a larger qubit -# system. We now use the Bravyi-Kitaev mapping to construct a qubit Hamiltonian and -# compute its ground state energy with the `VQE `__ -# method. -# -# Energy Calculation -# ------------------ -# To perform a VQE calculation for a desired Hamiltonian, we need an initial state typically set to -# a Hartree-Fock state, and a set of excitation operators to build an ansatz that allows us to -# obtain the ground state and then compute the expectation value of the Hamiltonian. It is important -# to note that the initial state and the excitation operators should be consistent with the -# mapping scheme used for obtaining the qubit Hamiltonian. Let's now build these three components -# for :math:`H_2` and compute its ground state energy. For this example, we will use the -# Bravyi-Kitaev transformation but similar calculations can be run with the other mappings. -# -# Molecular Hamiltonian -# ^^^^^^^^^^^^^^^^^^^^^ -# First, let's build the molecular Hamiltonian. This requires defining the atomic symbols and -# coordinates. - -from pennylane import qchem -from jax import numpy as jnp -import jax - -jax.config.update("jax_enable_x64", True) - -symbols = ['H', 'H'] -geometry = jnp.array([[0.0, 0.0, -0.69434785], - [0.0, 0.0, 0.69434785]]) - -mol = qchem.Molecule(symbols, geometry) - -############################################################################## -# We then use the :func:`~pennylane.qchem.fermionic_hamiltonian` function to build -# the fermionic Hamiltonian for our molecule. - -h_fermi = qchem.fermionic_hamiltonian(mol)() - -############################################################################## -# We now use :func:`~pennylane.fermi.bravyi_kitaev` to transform the fermionic Hamiltonian to its -# qubit representation. - -electrons = 2 -qubits = len(h_fermi.wires) -h_pauli = qml.bravyi_kitaev(h_fermi, qubits, tol=1e-16) - -############################################################################## -# Initial state -# ^^^^^^^^^^^^^ -# We now need the initial state that has the correct number of electrons. We use the Hartree-Fock -# state which can be obtained in a user-defined basis by using :func:`~.pennylane.qchem.hf_state` in -# PennyLane. For that, we need to specify the number of electrons, the number of orbitals and the -# desired mapping. - -hf_state = qchem.hf_state(electrons, qubits, basis="bravyi_kitaev") - -############################################################################## -# Excitation operators -# ^^^^^^^^^^^^^^^^^^^^ -# We now build our quantum circuit with the :class:`~.pennylane.UCCSD` ansatz. This ansatz is -# constructed with a set of single and double excitation operators. In PennyLane, we have -# :class:`~.pennylane.SingleExcitation` and :class:`~.pennylane.DoubleExcitation` operators which -# are very efficient, but they are only compatible with the Jordan-Wigner mapping. Here we construct -# the excitation operators manually. We start from the fermionic single and double excitation -# operators defined as [#Yordanov]_ -# -# .. math:: -# -# T_i^k(\theta) = \theta(a_k^{\dagger}a_i - a_i^{\dagger}a_k) -# -# and -# -# .. math:: -# -# T_{ij}^{kl}(\theta) = \theta(a_k^{\dagger}a_l^{\dagger}a_i a_j - -# a_i^{\dagger}a_j^{\dagger}a_k a_l), -# -# where :math:`\theta` is an adjustable parameter. We can easily construct these fermionic -# excitation operators in PennyLane and then map them to the qubit basis with -# :func:`~pennylane.fermi.bravyi_kitaev`, similar to the way we transformed the fermionic -# Hamiltonian. - -from pennylane.fermi import from_string - -singles, doubles = qchem.excitations(electrons, qubits) - -singles_fermi = [] -for ex in singles: - singles_fermi.append(from_string(f"{ex[1]}+ {ex[0]}-") - - from_string(f"{ex[0]}+ {ex[1]}-")) - -doubles_fermi = [] -for ex in doubles: - doubles_fermi.append(from_string(f"{ex[3]}+ {ex[2]}+ {ex[1]}- {ex[0]}-") - - from_string(f"{ex[0]}+ {ex[1]}+ {ex[2]}- {ex[3]}-")) - -############################################################################## -# The fermionic operators are now mapped to qubit operators. - -singles_pauli = [] -for op in singles_fermi: - singles_pauli.append(qml.bravyi_kitaev(op, qubits, ps=True)) - -doubles_pauli = [] -for op in doubles_fermi: - doubles_pauli.append(qml.bravyi_kitaev(op, qubits, ps=True)) - -############################################################################## -# Note that we need to exponentiate these operators to be able to use them in the circuit -# [#Yordanov]_. We also use a set of pre-defined parameters to construct the excitation gates. - -params = jnp.array([0.22347661, 0.0, 0.0]) - -dev = qml.device("default.qubit", wires=qubits) - -@qml.qnode(dev) -def circuit(params): - qml.BasisState(hf_state, wires=range(qubits)) - - for i, excitation in enumerate(doubles_pauli): - qml.exp((excitation * params[i] / 2).operation()), range(qubits) - - for j, excitation in enumerate(singles_pauli): - qml.exp((excitation * params[i + j + 1] / 2).operation()), range(qubits) - - return qml.expval(h_pauli) - -print('Energy =', circuit(params)) - -############################################################################## -# Using the above circuit, we produce the ground state energy of :math:`H_2` molecule. -# -# Conclusion -# --------------- -# In this demo, we learned about various mapping schemes available in PennyLane and how -# they can be used to convert fermionic operators to qubits operators. We also learned -# the pros and cons associated with each scheme. The Jordan-Wigner mapping provides an intuitive -# approach while parity mapping allows tapering qubits in molecular systems. However, these two -# methods usually give qubit operators with a long chain of Pauli operators, which makes them -# challenging to implement on quantum hardware. The Bravyi-Kitaev mapping, on the other hand, -# emphasizes locality and resource efficiency, making it an attractive option for certain -# applications. Through this demonstration, we recognize the importance of choosing an appropriate -# mapping scheme tailored to the specific problem at hand and the available quantum -# resources. Lastly, we showed how a user can employ these different mappings in ground state energy -# calculations through an example. We would like to encourage the interested readers to run -# calculations for different molecular systems and observe how the scaling in the number of qubits -# is influenced by the selected mapping techniques. -# -# References -# ---------- -# -# .. [#Tranter] -# -# A. Tranter, S. Sofia, *et al.*, "The Bravyi–Kitaev Transformation: -# Properties and Applications". `International Journal of Quantum Chemistry 115.19 (2015). -# `__ -# -# .. [#Yordanov] -# -# Y. S. Yordanov, *et al.*, "Efficient quantum circuits for quantum computational chemistry". -# `Physical Review A 102.6 (2020). -# `__ -# -# About the author -# ---------------- +r""" + +Mapping fermionic operators to qubit operators +============================================== + +Simulating quantum systems stands as one of the most anticipated applications of quantum +chemistry with the potential to transform our understanding of chemical and physical systems. These +simulations typically require mapping schemes that transform fermionic representations into qubit +representations. There are a variety of mapping schemes used in quantum computing but the +conventional ones are the Jordan-Wigner, Parity, and Bravyi-Kitaev transformations [#Tranter]_. In +this demo, you will learn about these mapping schemes and their implementation in PennyLane. You +will also learn how to use these mappings in the context of computing the ground state energy +of a molecular system. + +.. figure:: ../_static/demonstration_assets/mapping/long_image.png + :align: center + :width: 80% + :target: javascript:void(0) + +Jordan-Wigner Mapping +--------------------- +The state of a quantum system in the `second quantized `__ +formalism is typically represented in the occupation-number basis. For fermions, the occupation +number is either :math:`0` or :math:`1` as a result of the Pauli exclusion principle. The +occupation-number basis states can be represented by a vector that is constructed by +applying the fermionic creation operators, :math:`a^\dagger,` to a vacuum state: + +.. math:: + + a^\dagger | 0 \rangle = | 1 \rangle. + +Similarly, electrons can be removed from a state by applying the fermionic annihilation +operators :math:`a:` + +.. math:: + + a | 1 \rangle = | 0 \rangle. + +An intuitive way to represent a fermionic system in the qubit basis is to store the fermionic +occupation numbers in qubit states. This requires constructing qubit creation and annihilation +operators that can be applied to an initial state to provide the desired occupation number state. +These operators are defined as + +.. math:: + + Q^{\dagger}_j = \frac{1}{2}(X_j - iY_j), + +and + +.. math:: + + Q_j = \frac{1}{2}(X_j + iY_j), + +where :math:`X` and :math:`Y` are Pauli operators. However, an important property of fermionic +creation and annihilation operators is the anti-commutation relations between them, which is not +preserved by directly using the analogous qubit operators. + +.. math:: + + [a^{\dagger}_j, a^{\dagger}_k]_+ = 0, \:\:\:\:\:\:\: [a_j, a_k]_+=0, \:\:\:\:\:\:\: [a_j, a^{\dagger}_k]_+ = \delta_{jk} I, + +where :math:`I` is the identity operator. These relations are essential for capturing the Pauli +exclusion principle which requires the fermionic wave function to be antisymmetric. The +anti-commutation relations between fermionic operators can be incorporated by adding a sequence of +Pauli :math:`Z` operators to the qubit operators (can you verify that?). According to these, +the fermionic creation and annihilation operators can be represented in the basis of Pauli operators: + +.. math:: + + a_{j}^{\dagger} = \frac{1}{2}(X_j - iY_j) \otimes_{k`__, +:math:`a_{5}^{\dagger},` which creates an electron in the fifth orbital. One +way to do this in PennyLane is to use :func:`~.pennylane.fermi.from_string`. We +then map the operator using :func:`~.pennylane.fermi.jordan_wigner`. +""" + +import pennylane as qml +from pennylane.fermi import from_string, jordan_wigner + +qubits = 10 +fermi_op = from_string("5+") +pauli_jw = jordan_wigner(fermi_op, ps=True) +pauli_jw + +############################################################################### +# The long sequence of the :math:`Z` operations can significantly increase the +# resources needed to implement the operator on quantum hardware, as it may require using entangling +# operations across multiple qubits, which can be challenging to implement efficiently. One way to +# avoid having such long tails of :math:`Z` operations is to work in the parity basis where the +# fermionic state stores the parity instead of the occupation number. +# +# Parity Mapping +# -------------- +# In the Parity representation, the state of a fermionic system is represented with a binary +# vector that corresponds to the parity of the orbitals. Note that parity for a set of orbitals +# is defined as the sum (mod 2) of the occupation numbers. Let's look at an example using the +# PennyLane function :func:`~.pennylane.qchem.hf_state` to obtain the state of a system with +# :math:`10` orbitals and :math:`5` electrons in both the occupation-number and parity bases. + +orbitals = 10 +electrons = 5 +state_number = qml.qchem.hf_state(electrons, orbitals) +state_parity = qml.qchem.hf_state(electrons, orbitals, basis="parity") + +print("State in occupation number basis:\n", state_number) +print("State in parity basis:\n", state_parity) + +############################################################################## +# Note that Parity mapping solves the non-locality problem of the parity information by storing +# the parity of orbital :math:`j` in qubit :math:`j` while the occupation information for the +# orbital is stored non-locally. In the parity basis, we cannot represent the creation or +# annihilation of a particle in orbital +# :math:`j` by simply operating with qubit creation or annihilation operators. In fact, the state of +# the :math:`(j − 1)`-th qubit provides information about the occupation state of qubit :math:`j` +# and whether we need to act with a creation or annihilation operator. Similarly, the creation or +# annihilation of a particle in qubit :math:`j` changes the parity of all qubits following it. +# As a result, the operator that is equivalent to creation and annihilation operators in +# the parity basis is a two-qubit operator acting on qubits :math:`j` and :math:`j − 1,` and +# an update operator which updates the parity of all qubits with index larger than j: +# +# .. math:: +# +# a_{j}^{\dagger} = \frac{1}{2}(Z_{j-1} \otimes X_j - iY_j) \otimes_{k>j} X_{k} +# +# and +# +# .. math:: +# +# a_{j} = \frac{1}{2}(Z_{j-1} \otimes X_j + iY_j) \otimes_{k>j} X_{k} +# +# Let's now look at an example where we map our fermionic operator :math:`a_{5}^{\dagger}` in a +# :math:`10` qubit system with +# Parity mapping using :func:`~.pennylane.fermi.parity_transform` in PennyLane. + +qubits = 10 +pauli_pr = qml.parity_transform(fermi_op, qubits, ps=True) +pauli_pr + +############################################################################## +# It is evident from this example that the Parity mapping doesn't improve upon the +# the Jordan-Wigner mapping as the long :math:`Z` strings are now replaced by :math:`X` +# strings. However, a very important advantage of using parity mapping is the ability to taper two +# qubits by leveraging symmetries of molecular Hamiltonians. You can find +# more information about this in our +# `qubit tapering `__ demo. +# Let's look at an example. + +generators = [qml.prod(*[qml.Z(i) for i in range(qubits-1)]), qml.Z(qubits-1)] +paulixops = qml.paulix_ops(generators, qubits) +paulix_sector = [1, 1] +taper_op = qml.taper(pauli_pr, generators, paulixops, paulix_sector) +qml.simplify(taper_op) + +############################################################################### +# Note that the tapered operator doesn't include qubit :math:`8` and :math:`9.` +# +# Bravyi-Kitaev Mapping +# --------------------- +# The Bravyi-Kitaev mapping aims to improve the linear scaling of the Jordan-Wigner and Parity +# mappings, in the number of qubits, +# by storing both the occupation number and the parity non-locally. In this formalism, even-labelled +# qubits store the occupation number of orbitals and odd-labelled qubits store parity +# through partial sums of occupation numbers. The corresponding creation and annihilation operators +# are defined `here `__. +# Let's use the :func:`~.pennylane.fermi.bravyi_kitaev` function to map our :math:`a_{5}^{\dagger}` +# operator. + +pauli_bk = qml.bravyi_kitaev(fermi_op, qubits, ps=True) +pauli_bk + +############################################################################## +# It is clear that the local nature of the transformation in the Bravyi-Kitaev mapping helps to +# improve the number of qubits. This advantage becomes even more clear if you +# work with a larger qubit +# system. We now use the Bravyi-Kitaev mapping to construct a qubit Hamiltonian and +# compute its ground state energy with the `VQE `__ +# method. +# +# Energy Calculation +# ------------------ +# To perform a VQE calculation for a desired Hamiltonian, we need an initial state typically set to +# a Hartree-Fock state, and a set of excitation operators to build an ansatz that allows us to +# obtain the ground state and then compute the expectation value of the Hamiltonian. It is important +# to note that the initial state and the excitation operators should be consistent with the +# mapping scheme used for obtaining the qubit Hamiltonian. Let's now build these three components +# for :math:`H_2` and compute its ground state energy. For this example, we will use the +# Bravyi-Kitaev transformation but similar calculations can be run with the other mappings. +# +# Molecular Hamiltonian +# ^^^^^^^^^^^^^^^^^^^^^ +# First, let's build the molecular Hamiltonian. This requires defining the atomic symbols and +# coordinates. + +from pennylane import qchem +from jax import numpy as jnp +import jax + +jax.config.update("jax_enable_x64", True) + +symbols = ['H', 'H'] +geometry = jnp.array([[0.0, 0.0, -0.69434785], + [0.0, 0.0, 0.69434785]]) + +mol = qchem.Molecule(symbols, geometry) + +############################################################################## +# We then use the :func:`~pennylane.qchem.fermionic_hamiltonian` function to build +# the fermionic Hamiltonian for our molecule. + +h_fermi = qchem.fermionic_hamiltonian(mol)() + +############################################################################## +# We now use :func:`~pennylane.fermi.bravyi_kitaev` to transform the fermionic Hamiltonian to its +# qubit representation. + +electrons = 2 +qubits = len(h_fermi.wires) +h_pauli = qml.bravyi_kitaev(h_fermi, qubits, tol=1e-16) + +############################################################################## +# Initial state +# ^^^^^^^^^^^^^ +# We now need the initial state that has the correct number of electrons. We use the Hartree-Fock +# state which can be obtained in a user-defined basis by using :func:`~.pennylane.qchem.hf_state` in +# PennyLane. For that, we need to specify the number of electrons, the number of orbitals and the +# desired mapping. + +hf_state = qchem.hf_state(electrons, qubits, basis="bravyi_kitaev") + +############################################################################## +# Excitation operators +# ^^^^^^^^^^^^^^^^^^^^ +# We now build our quantum circuit with the :class:`~.pennylane.UCCSD` ansatz. This ansatz is +# constructed with a set of single and double excitation operators. In PennyLane, we have +# :class:`~.pennylane.SingleExcitation` and :class:`~.pennylane.DoubleExcitation` operators which +# are very efficient, but they are only compatible with the Jordan-Wigner mapping. Here we construct +# the excitation operators manually. We start from the fermionic single and double excitation +# operators defined as [#Yordanov]_ +# +# .. math:: +# +# T_i^k(\theta) = \theta(a_k^{\dagger}a_i - a_i^{\dagger}a_k) +# +# and +# +# .. math:: +# +# T_{ij}^{kl}(\theta) = \theta(a_k^{\dagger}a_l^{\dagger}a_i a_j - +# a_i^{\dagger}a_j^{\dagger}a_k a_l), +# +# where :math:`\theta` is an adjustable parameter. We can easily construct these fermionic +# excitation operators in PennyLane and then map them to the qubit basis with +# :func:`~pennylane.fermi.bravyi_kitaev`, similar to the way we transformed the fermionic +# Hamiltonian. + +from pennylane.fermi import from_string + +singles, doubles = qchem.excitations(electrons, qubits) + +singles_fermi = [] +for ex in singles: + singles_fermi.append(from_string(f"{ex[1]}+ {ex[0]}-") + - from_string(f"{ex[0]}+ {ex[1]}-")) + +doubles_fermi = [] +for ex in doubles: + doubles_fermi.append(from_string(f"{ex[3]}+ {ex[2]}+ {ex[1]}- {ex[0]}-") + - from_string(f"{ex[0]}+ {ex[1]}+ {ex[2]}- {ex[3]}-")) + +############################################################################## +# The fermionic operators are now mapped to qubit operators. + +singles_pauli = [] +for op in singles_fermi: + singles_pauli.append(qml.bravyi_kitaev(op, qubits, ps=True)) + +doubles_pauli = [] +for op in doubles_fermi: + doubles_pauli.append(qml.bravyi_kitaev(op, qubits, ps=True)) + +############################################################################## +# Note that we need to exponentiate these operators to be able to use them in the circuit +# [#Yordanov]_. We also use a set of pre-defined parameters to construct the excitation gates. + +params = jnp.array([0.22347661, 0.0, 0.0]) + +dev = qml.device("default.qubit", wires=qubits) + +@qml.qnode(dev) +def circuit(params): + qml.BasisState(hf_state, wires=range(qubits)) + + for i, excitation in enumerate(doubles_pauli): + qml.exp((excitation * params[i] / 2).operation()), range(qubits) + + for j, excitation in enumerate(singles_pauli): + qml.exp((excitation * params[i + j + 1] / 2).operation()), range(qubits) + + return qml.expval(h_pauli) + +print('Energy =', circuit(params)) + +############################################################################## +# Using the above circuit, we produce the ground state energy of :math:`H_2` molecule. +# +# Conclusion +# --------------- +# In this demo, we learned about various mapping schemes available in PennyLane and how +# they can be used to convert fermionic operators to qubits operators. We also learned +# the pros and cons associated with each scheme. The Jordan-Wigner mapping provides an intuitive +# approach while parity mapping allows tapering qubits in molecular systems. However, these two +# methods usually give qubit operators with a long chain of Pauli operators, which makes them +# challenging to implement on quantum hardware. The Bravyi-Kitaev mapping, on the other hand, +# emphasizes locality and resource efficiency, making it an attractive option for certain +# applications. Through this demonstration, we recognize the importance of choosing an appropriate +# mapping scheme tailored to the specific problem at hand and the available quantum +# resources. Lastly, we showed how a user can employ these different mappings in ground state energy +# calculations through an example. We would like to encourage the interested readers to run +# calculations for different molecular systems and observe how the scaling in the number of qubits +# is influenced by the selected mapping techniques. +# +# References +# ---------- +# +# .. [#Tranter] +# +# A. Tranter, S. Sofia, *et al.*, "The Bravyi–Kitaev Transformation: +# Properties and Applications". `International Journal of Quantum Chemistry 115.19 (2015). +# `__ +# +# .. [#Yordanov] +# +# Y. S. Yordanov, *et al.*, "Efficient quantum circuits for quantum computational chemistry". +# `Physical Review A 102.6 (2020). +# `__ +# +# About the author +# ---------------- diff --git a/demonstrations_v2/tutorial_measurement_optimize/demo.py b/demonstrations_v2/tutorial_measurement_optimize/demo.py index 198b7d2bfd..f43645c3ba 100644 --- a/demonstrations_v2/tutorial_measurement_optimize/demo.py +++ b/demonstrations_v2/tutorial_measurement_optimize/demo.py @@ -1,844 +1,844 @@ -r""" -Measurement optimization -======================== - -.. meta:: - :property="og:description": Optimize and reduce the number of measurements required to evaluate a variational algorithm cost function. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/grouping.png - -.. related:: - - tutorial_vqe A brief overview of VQE - tutorial_quantum_chemistry Building molecular Hamiltonians - tutorial_qaoa_intro Intro to QAOA - -*Author: Josh Izaac — Posted: 18 January 2021. Last updated: 29 August 2023.* - -The variational quantum eigensolver (VQE) is the OG variational quantum algorithm. Harnessing -near-term quantum hardware to solve for the electronic structure of molecules, VQE is *the* -algorithm that sparked the variational circuit craze of the last 5 years, and holds great -promise for showcasing a quantum advantage on near-term quantum hardware. It has also inspired -other quantum algorithms such as the :doc:`Quantum Approximate Optimization Algorithm (QAOA) -`. - -To scale VQE beyond the regime of classical computation, however, we need to solve for the -ground state of increasingly larger molecules. A consequence is that the number of -measurements we need to make on the quantum hardware also grows polynomially—a huge bottleneck, -especially when quantum hardware access is limited and expensive. - -To mitigate this 'measurement problem', a plethora of recent research dropped over the course of -2019 and 2020 [#yen2020]_ [#izmaylov2019]_ [#huggins2019]_ [#gokhale2020]_ [#verteletskyi2020]_ , -exploring potential strategies to minimize the number of measurements required. In fact, by grouping -commuting terms of the Hamiltonian, we can significantly reduce the number of -measurements needed—in some cases, reducing the number of measurements by up to 90%! - -.. figure:: /_static/demonstration_assets/measurement_optimize/grouping.png - :width: 90% - :align: center - -In this demonstration, we revisit the VQE algorithm, see first-hand how the required number of -measurements scales as molecule size increases, and finally use these measurement optimization -strategies to minimize the number of measurements we need to make. These techniques are valuable -beyond just VQE, allowing you to add measurement optimization to your toolkit of techniques to -perform variational algorithms more efficiently. - -Revisiting VQE --------------- - -The study of :doc:`variational quantum algorithms ` was spearheaded -by the introduction of the :doc:`variational quantum eigensolver ` (VQE) algorithm in -2014 [#peruzzo2014]_. While classical variational techniques have been known for decades to estimate -the ground state energy of a molecule, VQE allowed this variational technique to be applied using -quantum computers. Since then, the field of variational quantum algorithms has evolved -significantly, with larger and more complex models being proposed (such as -:doc:`quantum neural networks `, :doc:`QGANs `, and -:doc:`variational classifiers `). However, quantum chemistry -remains one of the flagship use-cases for variational quantum algorithms, and VQE the standard-bearer. - -Part of the appeal of VQE lies within its simplicity. A circuit ansatz :math:`U(\theta)` is chosen -(typically the Unitary Coupled-Cluster Singles and Doubles -(:func:`~pennylane.templates.subroutines.UCCSD`) ansatz), and the qubit representation of the -molecular Hamiltonian is computed: - -.. math:: H = \sum_i c_i h_i, - -where :math:`h_i` are the terms of the Hamiltonian written as a tensor product of Pauli operators or the identity -acting on wire :math:`n,` :math:`P_n \in \{I, \sigma_x, \sigma_y, \sigma_z\}:` - -.. math:: h_i = \bigotimes_{n=0}^{N-1} P_n. - -(The :math:`h_i` product of Pauli terms is often referred to as a 'Pauli word' in the literature.) The cost -function of the VQE is then simply the expectation value of this Hamiltonian on the state obtained -after running the variational quantum circuit: - -.. math:: \text{cost}(\theta) = \langle 0 | U(\theta)^\dagger H U(\theta) | 0 \rangle. - -By using a classical optimizer to *minimize* this quantity, we can estimate -the ground state energy of the Hamiltonian :math:`H:` - -.. math:: H U(\theta_{min}) |0\rangle = E_{min} U(\theta_{min}) |0\rangle. - -In practice, when we are using quantum hardware to compute these expectation values we expand out -the Hamiltonian as its summation, resulting in separate expectation values that need to be calculated for each term: - -.. math:: - - \text{cost}(\theta) = \langle 0 | U(\theta)^\dagger \left(\sum_i c_i h_i\right) U(\theta) | 0 \rangle - = \sum_i c_i \langle 0 | U(\theta)^\dagger h_i U(\theta) | 0 \rangle. - -.. note:: - - How do we compute the qubit representation of the molecular Hamiltonian? This is a more - complicated story that involves applying a self-consistent field method (such as Hartree-Fock), - and then performing a fermionic-to-qubit mapping such as the Jordan-Wigner or Bravyi-Kitaev - transformations. - - For more details on this process, check out the :doc:`/demos/tutorial_quantum_chemistry` - tutorial. - -The measurement problem ------------------------ - -For small molecules, the VQE algorithm scales and performs exceedingly well. For example, for the -Hydrogen molecule :math:`\text{H}_2,` the final Hamiltonian in its qubit representation -has 15 terms that need to be measured. Let's obtain the Hamiltonian from -`PennyLane's dataset library `__ -to verify the number of terms. In this tutorial, we use the :func:`~.pennylane.data.load` -function to download the dataset of the molecule. - -""" - -import functools -import warnings -import jax -from jax import numpy as jnp -import pennylane as qml - -jax.config.update("jax_enable_x64", True) - -dataset = qml.data.load("qchem", molname="H2", bondlength=0.7)[0] -H, num_qubits = dataset.hamiltonian, len(dataset.hamiltonian.wires) - -print("Required number of qubits:", num_qubits) -print(H) - -############################################################################### -# Here, we can see that the Hamiltonian involves 15 terms, so we expect to compute 15 expectation values -# on hardware. Let's generate the cost function to check this. - -# Create a 4 qubit simulator -dev = qml.device("default.qubit", shots=1000, seed=904932) - -# number of electrons -electrons = 2 - -# Define the Hartree-Fock initial state for our variational circuit -initial_state = qml.qchem.hf_state(electrons, num_qubits) - -# Construct the UCCSD ansatz -singles, doubles = qml.qchem.excitations(electrons, num_qubits) -s_wires, d_wires = qml.qchem.excitations_to_wires(singles, doubles) -ansatz = functools.partial( - qml.UCCSD, init_state=initial_state, s_wires=s_wires, d_wires=d_wires -) - -# generate the cost function -@qml.qnode(dev, interface="jax") -def cost_circuit(params): - ansatz(params, wires=range(num_qubits)) - return qml.expval(H) - -############################################################################## -# If we evaluate this cost function, we can see that it corresponds to 15 different -# executions under the hood—one per expectation value: - -from jax import random as random -key, scale = random.PRNGKey(0), jnp.pi -params = random.normal(key, shape=(len(singles) + len(doubles),)) * scale -with qml.Tracker(dev) as tracker: # track the number of executions - print("Cost function value:", cost_circuit(params)) - -print("Number of quantum evaluations:", tracker.totals['executions']) - -############################################################################## -# How about a larger molecule? Let's try the -# `water molecule `__: - -dataset = qml.data.load('qchem', molname="H2O")[0] -H, num_qubits = dataset.hamiltonian, len(dataset.hamiltonian.wires) - -print("Required number of qubits:", num_qubits) -print("Number of Hamiltonian terms/required measurements:", len(H.terms()[0])) - -print("\n", H) - - -############################################################################## -# Simply going from two atoms in :math:`\text{H}_2` to three in :math:`\text{H}_2 \text{O}` -# resulted in over triple the number of qubits required and 1086 measurements that must be made! -# -# We can see that as the size of our molecule increases, we run into a problem: larger molecules -# result in Hamiltonians that not only require a larger number of qubits :math:`N` in their -# representation, but the number of terms in the Hamiltonian scales like -# :math:`\mathcal{O}(N^4)!` 😱😱😱 -# -# We can mitigate this somewhat by choosing smaller `basis sets -# `__ to represent the electronic structure -# wavefunction, however this would be done at the cost of solution accuracy, and doesn't reduce the number of -# measurements significantly enough to allow us to scale to classically intractable problems. -# -# .. figure:: /_static/demonstration_assets/measurement_optimize/n4.png -# :width: 70% -# :align: center -# -# The number of qubit Hamiltonian terms required to represent various molecules in the specified -# basis sets (adapted from `Minimizing State Preparations for VQE by Gokhale et al. -# `__) - - -############################################################################## -# Simultaneously measuring observables -# ------------------------------------ -# -# One of the assumptions we made above was that every term in the Hamiltonian must be measured independently. -# However, this might not be the case. From the `Heisenberg uncertainty relationship -# `__ for two -# observables :math:`\hat{A}` and :math:`\hat{B},` we know that -# -# .. math:: \sigma_A^2 \sigma_B^2 \geq \frac{1}{2}\left|\left\langle [\hat{A}, \hat{B}] \right\rangle\right|, -# -# where :math:`\sigma^2_A` and :math:`\sigma^2_B` are the variances of measuring the expectation value of the -# associated observables, and -# -# .. math:: [\hat{A}, \hat{B}] = \hat{A}\hat{B}-\hat{B}\hat{A} -# -# is the commutator. Therefore, -# -# - If the two observables :math:`\hat{A}` and :math:`\hat{B}` do not commute (:math:`[\hat{A}, -# \hat{B}] \neq 0`), then :math:`\sigma_A^2 -# \sigma_B^2 > 0` and we cannot simultaneously measure the expectation values of the two -# observables. -# -# .. -# -# - If :math:`\hat{A}` and :math:`\hat{B}` **do** commute (:math:`[\hat{A}, -# \hat{B}] = 0`), then :math:`\sigma_A^2 -# \sigma_B^2 \geq 0` and there exists a measurement basis where we can **simultaneously measure** the -# expectation value of both observables on the same state. -# -# To explore why commutativity and simultaneous measurement are related, let's assume that there -# is a complete, orthonormal eigenbasis :math:`|\phi_n\rangle` that *simultaneously -# diagonalizes* both :math:`\hat{A}` and :math:`\hat{B}:` -# -# .. math:: -# -# ① ~~ \hat{A} |\phi_n\rangle &= \lambda_{A,n} |\phi_n\rangle,\\ -# ② ~~ \hat{B} |\phi_n\rangle &= \lambda_{B,n} |\phi_n\rangle. -# -# where :math:`\lambda_{A,n}` and :math:`\lambda_{B,n}` are the corresponding eigenvalues. -# If we pre-multiply the first equation by :math:`\hat{B},` and the second by :math:`\hat{A}` -# (both denoted in blue): -# -# .. math:: -# -# \color{blue}{\hat{B}}\hat{A} |\phi_n\rangle &= \lambda_{A,n} \color{blue}{\hat{B}} -# |\phi_n\rangle = \lambda_{A,n} \color{blue}{\lambda_{B,n}} |\phi_n\rangle,\\ -# \color{blue}{\hat{A}}\hat{B} |\phi_n\rangle &= \lambda_{B,n} \color{blue}{\hat{A}} -# |\phi_n\rangle = \lambda_{A,n} \color{blue}{\lambda_{B,n}} |\phi_n\rangle. -# -# We can see that assuming a simultaneous eigenbasis requires that -# :math:`\hat{A}\hat{B}|\phi_n\rangle = \hat{B}\hat{A}|\phi_n\rangle.` Or, rearranging, -# -# .. math:: (\hat{A}\hat{B} - \hat{B}\hat{A}) |\phi_n\rangle = [\hat{A}, \hat{B}]|\phi_n\rangle = 0. -# -# Our assumption that :math:`|\phi_n\rangle` simultaneously diagonalizes both :math:`\hat{A}` and -# :math:`\hat{B}` only holds true if the two observables commute. -# -# So far, this seems awfully theoretical. What does this mean in practice? -# -# In the realm of variational circuits, we typically want to compute expectation values of an -# observable on a given state :math:`|\psi\rangle.` If we have two commuting observables, we now know that -# they share a simultaneous eigenbasis: -# -# .. math:: -# -# \hat{A} &= \sum_n \lambda_{A, n} |\phi_n\rangle\langle \phi_n|,\\ -# \hat{B} &= \sum_n \lambda_{B, n} |\phi_n\rangle\langle \phi_n|. -# -# Substituting this into the expression for the expectation values: -# -# .. math:: -# -# \langle\hat{A}\rangle &= \langle \psi | \hat{A} | \psi \rangle = \langle \psi | \left( \sum_n -# \lambda_{A, n} |\phi_n\rangle\langle \phi_n| \right) | \psi \rangle = \sum_n \lambda_{A,n} -# |\langle \phi_n|\psi\rangle|^2,\\ -# \langle\hat{B}\rangle &= \langle \psi | \hat{B} | \psi \rangle = \langle \psi | \left( \sum_n -# \lambda_{B, n} |\phi_n\rangle\langle \phi_n| \right) | \psi \rangle = \sum_n \lambda_{B,n} -# |\langle \phi_n|\psi\rangle|^2. -# -# So, assuming we know the eigenvalues of the commuting observables in advance, if we perform a -# measurement in their shared eigenbasis (the :math:`|\phi_n\rangle`), we only need to perform a **single measurement** of the -# probabilities :math:`|\langle \phi_n|\psi\rangle|^2` in order to recover both expectation values! 😍 -# -# Fantastic! But, can we use this to reduce the number of measurements we need to perform in the VQE algorithm? -# To do so, we must find the answer to two questions: -# -# 1. How do we determine which terms of the cost Hamiltonian commute? -# -# 2. How do we rotate the circuit into the shared eigenbasis prior to measurement? -# -# The answers to these questions aren't necessarily easy nor straightforward. Thankfully, there are -# some recent techniques we can harness to address both. - -############################################################################## -# Qubit-wise commuting Pauli terms -# -------------------------------- -# -# Back when we summarized the VQE algorithm, we saw that each term of the Hamiltonian is generally represented -# as a tensor product of Pauli operators: -# -# .. math:: h_i = \bigotimes_{n=0}^{N-1} P_n. -# -# Luckily, this tensor product structure allows us to take a bit of a shortcut. Rather than consider -# **full commutativity**, we can consider a more strict condition known as **qubit-wise -# commutativity** (QWC). -# -# To start with, let's consider single-qubit Pauli operators and the identity. We know that the Pauli operators -# commute with themselves as well as the identity, but they do *not* commute with -# each other: -# -# .. math:: -# -# [\sigma_i, I] = 0, ~~~ [\sigma_i, \sigma_i] = 0, ~~~ [\sigma_i, \sigma_j] = c \sigma_k \delta_{ij}. -# -# Now consider two tensor products of Pauli terms, for example :math:`X\otimes Y \otimes I` and -# :math:`X\otimes I \otimes Z.` We say that these two terms are qubit-wise commuting, since, if -# we compare each subsystem in the tensor product, we see that every one commutes: -# -# .. math:: -# -# \begin{array}{ | *1c | *1c | *1c | *1c | *1c |} -# X &\otimes &Y &\otimes &I\\ -# X &\otimes &I &\otimes &Z -# \end{array} ~~~~ \Rightarrow ~~~~ [X, X] = 0, ~~~ [Y, I] = 0, ~~~ [I, Z] = 0. -# -# As a consequence, both terms must commute: -# -# .. math:: [X\otimes Y \otimes I, X\otimes I \otimes Z] = 0. -# -# .. important:: -# -# Qubit-wise commutativity is a **sufficient** but not **necessary** condition -# for full commutativity. For example, the two Pauli terms :math:`Y\otimes Y` and -# :math:`X\otimes X` are not qubit-wise commuting, but do commute (have a go verifying this!). -# -# Once we have identified a qubit-wise commuting pair of Pauli terms, it is also straightforward to -# find the gates to rotate the circuit into the shared eigenbasis. To do so, we simply rotate -# each qubit one-by-one depending on the Pauli operator we are measuring on that wire: -# -# .. raw:: html -# -# -#
-# -# .. rst-class:: docstable -# -# +------------------+-------------------------------+ -# | Observable | Rotation gate | -# +==================+===============================+ -# | :math:`X` | :math:`RY(-\pi/2) = H` | -# +------------------+-------------------------------+ -# | :math:`Y` | :math:`RX(\pi/2)=HS^{-1}=HSZ` | -# +------------------+-------------------------------+ -# | :math:`Z` | :math:`I` | -# +------------------+-------------------------------+ -# | :math:`I` | :math:`I` | -# +------------------+-------------------------------+ -# -# .. raw:: html -# -#
-# -# Therefore, in this particular example: -# -# * Wire 0: we are measuring both terms in the :math:`X` basis, apply the Hadamard gate -# * Wire 1: we are measuring both terms in the :math:`Y` basis, apply a :math:`RX(\pi/2)` gate -# * Wire 2: we are measuring both terms in the :math:`Z` basis (the computational basis), no gate needs to be applied. -# -# Let's use PennyLane to verify this. - - -obs = [ - qml.PauliX(0) @ qml.PauliY(1), - qml.PauliX(0) @ qml.PauliZ(2) -] - - -############################################################################## -# First, let's naively use two separate circuit evaluations to measure -# the two QWC terms. - - -dev = qml.device("default.qubit", wires=3) - -@qml.qnode(dev, interface="jax") -def circuit1(weights): - qml.StronglyEntanglingLayers(weights, wires=range(3)) - return qml.expval(obs[0]) - - -@qml.qnode(dev, interface="jax") -def circuit2(weights): - qml.StronglyEntanglingLayers(weights, wires=range(3)) - return qml.expval(obs[1]) - -param_shape = qml.templates.StronglyEntanglingLayers.shape(n_layers=3, n_wires=3) -key, scale = random.PRNGKey(192933), 0.1 -weights = scale * random.normal(key, shape=param_shape) - -print("Expectation value of XYI = ", circuit1(weights)) -print("Expectation value of XIZ = ", circuit2(weights)) - -############################################################################## -# Now, let's use our QWC approach to reduce this down to a *single* measurement -# of the probabilities in the shared eigenbasis of both QWC observables: - -@qml.qnode(dev, interface="jax") -def circuit_qwc(weights): - qml.StronglyEntanglingLayers(weights, wires=range(3)) - - # rotate wire 0 into the shared eigenbasis - qml.RY(-jnp.pi / 2, wires=0) - - # rotate wire 1 into the shared eigenbasis - qml.RX(jnp.pi / 2, wires=1) - - # wire 2 does not require a rotation - - # measure probabilities in the computational basis - return qml.probs(wires=range(3)) - - -rotated_probs = circuit_qwc(weights) -print(rotated_probs) - -############################################################################## -# We're not quite there yet; we have only calculated the probabilities of the variational circuit -# rotated into the shared eigenbasis—the :math:`|\langle \phi_n |\psi\rangle|^2.` To recover the -# *expectation values* of the two QWC observables from the probabilities, recall that we need one -# final piece of information: their eigenvalues :math:`\lambda_{A, n}` and :math:`\lambda_{B, n}.` -# -# We know that the single-qubit Pauli operators each have eigenvalues :math:`(1, -1),` while the identity -# operator has eigenvalues :math:`(1, 1).` We can make use of ``np.kron`` to quickly -# generate the eigenvalues of the full Pauli terms, making sure that the order -# of the eigenvalues in the Kronecker product corresponds to the tensor product. - -eigenvalues_XYI = jnp.kron(jnp.kron(jnp.array([1, -1]), jnp.array([1, -1])), jnp.array([1, 1])) -eigenvalues_XIZ = jnp.kron(jnp.kron(jnp.array([1, -1]), jnp.array([1, 1])), jnp.array([1, -1])) - -# Taking the linear combination of the eigenvalues and the probabilities -print("Expectation value of XYI = ", jnp.dot(eigenvalues_XYI, rotated_probs)) -print("Expectation value of XIZ = ", jnp.dot(eigenvalues_XIZ, rotated_probs)) - - -############################################################################## -# Compare this to the result when we used two circuit evaluations. We have successfully used a -# single circuit evaluation to recover both expectation values! -# -# Luckily, PennyLane automatically performs this QWC grouping under the hood. We simply -# return the two QWC Pauli terms from the QNode: - -@qml.qnode(dev, interface="jax") -def circuit(weights): - qml.StronglyEntanglingLayers(weights, wires=range(3)) - return [ - qml.expval(qml.PauliX(0) @ qml.PauliY(1)), - qml.expval(qml.PauliX(0) @ qml.PauliZ(2)) - ] - - -print(circuit(weights)) - - -############################################################################## -# Behind the scenes, PennyLane is making use of our built-in -# :mod:`qml.pauli ` module, which contains functions for diagonalizing QWC -# terms: - -rotations, new_obs = qml.pauli.diagonalize_qwc_pauli_words(obs) - -print(rotations) -print(new_obs) - - -############################################################################## -# Here, the first line corresponds to the basis rotations that were discussed above, written in -# terms of ``RX`` and ``RY`` rotations. Check out the :mod:`qml.pauli ` -# documentation for more details on its provided functionality and how it works. -# -# Given a Hamiltonian containing a large number of Pauli terms, -# there is a high likelihood of there being a significant number of terms that qubit-wise commute. Can -# we somehow partition the terms into **fewest** number of QWC groups to minimize the number of measurements -# we need to take? - -############################################################################## -# Grouping QWC terms -# ------------------ -# -# A nice example is provided in [#verteletskyi2020]_ showing how we might tackle this. Say we have -# the following Hamiltonian defined over four qubits: -# -# .. math:: H = Z_0 + Z_0 Z_1 + Z_0 Z_1 Z_2 + Z_0 Z_1 Z_2 Z_3 + X_2 X_3 + Y_0 X_2 X_3 + Y_0 Y_1 X_2 X_3, -# -# where we are using the shorthand :math:`P_0 P_2 = P\otimes I \otimes P \otimes I` for brevity. -# If we go through and work out which Pauli terms are qubit-wise commuting, we can represent -# this in a neat way using a graph: -# -# .. figure:: /_static/demonstration_assets/measurement_optimize/graph1.png -# :width: 70% -# :align: center -# -# In the above graph, every node represents an individual Pauli term of the Hamiltonian, with -# edges connecting terms that are qubit-wise commuting. Groups of qubit-wise commuting terms are -# represented as **complete subgraphs**. Straight away, we can make an observation: -# there is no unique solution for partitioning the Hamiltonian into groups of qubit-wise commuting -# terms! In fact, there are several solutions: -# -# .. figure:: /_static/demonstration_assets/measurement_optimize/graph2.png -# :width: 90% -# :align: center -# -# Of course, of the potential solutions above, there is one that is more optimal than the others --- -# on the bottom left, we have partitioned the graph into *two* complete subgraphs, as opposed to the -# other solutions that require three complete subgraphs. If we were to go with this solution, -# we would be able to measure the expectation value of the Hamiltonian using two circuit evaluations. -# -# This problem—finding the minimum number of complete subgraphs of a graph—is actually quite well -# known in graph theory, where it is referred to as the `minimum clique cover problem -# `__ (with 'clique' being another term for a complete subgraph). -# -# Unfortunately, that's where our good fortune ends—the minimum clique cover problem is known to -# be `NP-hard `__, meaning there is no known (classical) -# solution to finding the optimum/minimum clique cover in polynomial time. -# -# Thankfully, there is a silver lining: we know of polynomial-time algorithms for finding -# *approximate* solutions to the minimum clique cover problem. These heuristic approaches, while -# not guaranteed to find the optimum solution, scale quadratically with the number of nodes in the -# graph/terms in the Hamiltonian [#yen2020]_, so work reasonably well in practice. -# -# Many of these heuristic approaches have roots in another graph problem known as `graph -# colouring `__; the assignment of colours to -# the graph's vertices such that no adjacent vertices have the same colour. How is this related -# to the minimum clique cover problem, though? If we take our QWC graph above, and generate the -# `complement graph `__ by drawing edges -# between all *non*-adjacent nodes, -# -# .. figure:: /_static/demonstration_assets/measurement_optimize/graph3.png -# :width: 100% -# :align: center -# -# we see that solving the minimum clique cover problem on the QWC graph is equivalent to solving the -# graph colouring problem on the complement graph using the minimum possible number of colours. -# While there are various different heuristic algorithms, a common one is `greedy colouring -# `__; in fact, the open-source graph -# package `NetworkX even provides a function for greedy colouring -# `__, -# ``nx.greedy_color``. -# -# Let's give this a go, using NetworkX to solve the minimum clique problem for observable grouping. -# First, we'll need to generate the QWC graph (with each node corresponding to a Hamiltonian -# term, and edges indicating two terms that are QWC). - -import networkx as nx -from matplotlib import pyplot as plt - -terms = [ - qml.PauliZ(0), - qml.PauliZ(0) @ qml.PauliZ(1), - qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2), - qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3), - qml.PauliX(2) @ qml.PauliX(3), - qml.PauliY(0) @ qml.PauliX(2) @ qml.PauliX(3), - qml.PauliY(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliX(3) -] - -def format_pauli_word(term): - """Convenience function that nicely formats a PennyLane - tensor observable as a Pauli word""" - if isinstance(term, qml.ops.Prod): - return " ".join([format_pauli_word(t) for t in term]) - - return f"{term.name[-1]}{term.wires.tolist()[0]}" - -G = nx.Graph() - -with warnings.catch_warnings(): - # Muting irrelevant warnings - warnings.filterwarnings( - "ignore", - message="The behaviour of operator ", - category=UserWarning, - ) - - # add the terms to the graph - G.add_nodes_from(terms) - - # add QWC edges - G.add_edges_from([ - [terms[0], terms[1]], # Z0 <--> Z0 Z1 - [terms[0], terms[2]], # Z0 <--> Z0 Z1 Z2 - [terms[0], terms[3]], # Z0 <--> Z0 Z1 Z2 Z3 - [terms[1], terms[2]], # Z0 Z1 <--> Z0 Z1 Z2 - [terms[2], terms[3]], # Z0 Z1 Z2 <--> Z0 Z1 Z2 Z3 - [terms[1], terms[3]], # Z0 Z1 <--> Z0 Z1 Z2 Z3 - [terms[0], terms[4]], # Z0 <--> X2 X3 - [terms[1], terms[4]], # Z0 Z1 <--> X2 X3 - [terms[4], terms[5]], # X2 X3 <--> Y0 X2 X3 - [terms[4], terms[6]], # X2 X3 <--> Y0 Y1 X2 X3 - [terms[5], terms[6]], # Y0 X2 X3 <--> Y0 Y1 X2 X3 - ]) - - plt.margins(x=0.1) - coords = nx.spring_layout(G, seed=1) - nx.draw( - G, - coords, - labels={node: format_pauli_word(node) for node in terms}, - with_labels=True, - node_size=500, - font_size=8, - node_color="#9eded1", - edge_color="#c1c1c1", - ) - - ############################################################################## - # We can now generate the complement graph (compare this to our handdrawn - # version above!): - - C = nx.complement(G) - coords = nx.spring_layout(C, seed=1) - - nx.draw( - C, - coords, - labels={node: format_pauli_word(node) for node in terms}, - with_labels=True, - node_size=500, - font_size=8, - node_color="#9eded1", - edge_color="#c1c1c1" - ) - - ############################################################################## - # Now that we have the complement graph, we can perform a greedy coloring to - # determine the minimum number of QWC groups: - - groups = nx.coloring.greedy_color(C, strategy="largest_first") - - # plot the complement graph with the greedy colouring - nx.draw( - C, - coords, - labels={node: format_pauli_word(node) for node in terms}, - with_labels=True, - node_size=500, - font_size=8, - node_color=[("#9eded1", "#aad4f0")[groups[node]] for node in C], - edge_color="#c1c1c1" - ) - - -num_groups = len(set(groups.values())) -print("Minimum number of QWC groupings found:", num_groups) - - -for i in range(num_groups): - print(f"\nGroup {i}:") - - for term, group_id in groups.items(): - if group_id == i: - print(format_pauli_word(term)) - -############################################################################## -# Putting it all together -# ----------------------- -# -# So, we now have a strategy for minimizing the number of measurements we need to perform -# for our VQE problem: -# -# 1. Determine which terms of the Hamiltonian are qubit-wise commuting, and use -# this to construct a graph representing the QWC relationship. -# -# 2. Construct the complement QWC graph. -# -# 3. Use a graph colouring heuristic algorithm to determine a graph colouring for the complement graph -# with a minimum number of colours. Each coloured vertex set corresponds to a -# qubit-wise commuting group of Hamiltonian terms. -# -# 4. Generate and evaluate the circuit ansatz (with additional rotations) per -# QWC grouping, extracting probability distributions. -# -# 5. Finally, post-process the probability distributions with the observable eigenvalues -# to recover the Hamiltonian expectation value. -# -# Luckily, the PennyLane ``pauli`` module makes this relatively easy. Let's walk through -# the entire process using the provided grouping functions. -# -# Steps 1-3 (finding and grouping QWC terms in the Hamiltonian) can be done via the -# :func:`qml.pauli.group_observables ` function: - -obs_groupings = qml.pauli.group_observables(terms, grouping_type='qwc', method='rlf') - - -############################################################################## -# The ``grouping_type`` argument allows us to choose how the commuting terms -# are determined (more on that later!) whereas ``method`` determines the colouring -# heuristic (in this case, ``"rlf"`` refers to Recursive Largest First, a variant of largest first colouring heuristic). -# -# If we want to see what the required rotations and measurements are, we can use the -# :func:`qml.pauli.diagonalize_qwc_groupings ` -# function: - -rotations, measurements = qml.pauli.diagonalize_qwc_groupings(obs_groupings) - -############################################################################## -# However, this isn't strictly necessary—recall previously that the QNode -# has the capability to *automatically* measure qubit-wise commuting observables! - -dev = qml.device("lightning.qubit", wires=4) - -@qml.qnode(dev, interface="jax") -def circuit(weights, group=None, **kwargs): - qml.StronglyEntanglingLayers(weights, wires=range(4)) - return [qml.expval(o) for o in group] - -param_shape = qml.templates.StronglyEntanglingLayers.shape(n_layers=3, n_wires=4) -key = random.PRNGKey(1) -weights = random.normal(key, shape=param_shape) * 0.1 -result = [jnp.array(circuit(weights, group=g)) for g in obs_groupings] - -print("Term expectation values:") -for group, expvals in enumerate(result): - print(f"Group {group} expectation values:", expvals) - -# Since all the coefficients of the Hamiltonian are unity, -# we can simply sum the expectation values. -print(" = ", jnp.sum(jnp.hstack(result))) - - -############################################################################## -# Finally, we don't need to go through this process manually every time; if our cost function can be -# written in the form of an expectation value of a Hamiltonian (as is the case for most VQE and QAOA -# problems), we can use the option ``grouping_type="qwc"`` in :class:`~.pennylane.Hamiltonian` to -# automatically optimize the measurements. - -H = qml.Hamiltonian(coeffs=jnp.ones(len(terms)), observables=terms, grouping_type="qwc") -_, H_ops = H.terms() -@qml.qnode(dev, interface="jax") -def cost_fn(weights): - qml.StronglyEntanglingLayers(weights, wires=range(4)) - return qml.expval(H) -print(cost_fn(weights)) - -############################################################################## -# Beyond VQE -# ---------- -# -# Wait, hang on. We dove so deeply into measurement grouping and optimization, we forgot to check -# how this affects the number of measurements required to perform the VQE on :math:`\text{H}_2 \text{O}!` -# Let's use our new-found knowledge to see what happens. - -dataset = qml.data.load('qchem', molname="H2O")[0] -H, num_qubits = dataset.hamiltonian, len(dataset.hamiltonian.wires) -print("Number of Hamiltonian terms/required measurements:", len(H_ops)) - -# grouping -groups = qml.pauli.group_observables(H_ops, grouping_type='qwc', method='rlf') -print("Number of required measurements after optimization:", len(groups)) - -############################################################################## -# We went from 1086 required measurements/circuit evaluations to 320 (just over *one thousand* -# down to *three hundred* 😱😱😱). -# -# As impressive as this is, however, this is just the beginning of the optimization. -# -# While finding qubit-wise commutating terms is relatively straightforward, with a little -# extra computation we can push this number down even further. Recent work has explored -# the savings that can be made by considering *full* commutativity [#yen2020]_, unitary -# partitioning [#izmaylov2019]_, and Fermionic basis rotation grouping [#huggins2019]_. -# Work has also been performed to reduce the classical overhead associated with measurement -# optimization, allowing the classical measurement grouping to be performed in linear time -# [#gokhale2020]_. For example, recall that qubit-wise commutativity is only a subset of -# full commutativity; if we consider full commutativity instead, we can further reduce the -# number of groups required. -# -# Finally, it is worth pointing out that, as the field of variational quantum algorithms grows, this -# problem of measurement optimization no longer just applies to the VQE algorithm (the algorithm it -# was born from). Instead, there are a multitude of algorithms that could benefit from these -# measurement optimization techniques (QAOA being a prime example). -# -# So the next time you are working on a variational quantum algorithm and the number -# of measurements required begins to explode—stop, take a deep breath 😤, and consider grouping -# and optimizing your measurements. -# -# .. note:: -# -# Qubit-wise commuting group information for a wide variety of molecules has been -# pre-computed, and is available for download in -# in the `PennyLane Datasets library `__. - -############################################################################## -# References -# ---------- -# -# .. [#peruzzo2014] -# -# Alberto Peruzzo, Jarrod McClean *et al.*, "A variational eigenvalue solver on a photonic -# quantum processor". `Nature Communications 5, 4213 (2014). -# `__ -# -# .. [#yen2020] -# -# Tzu-Ching Yen, Vladyslav Verteletskyi, and Artur F. Izmaylov. "Measuring all compatible -# operators in one series of single-qubit measurements using unitary transformations." `Journal of -# Chemical Theory and Computation 16.4 (2020): 2400-2409. -# `__ -# -# .. [#izmaylov2019] -# -# Artur F. Izmaylov, *et al.* "Unitary partitioning approach to the measurement problem in the -# variational quantum eigensolver method." `Journal of Chemical Theory and Computation 16.1 (2019): -# 190-195. `__ -# -# .. [#huggins2019] -# -# William J. Huggins, *et al.* "Efficient and noise resilient measurements for quantum chemistry -# on near-term quantum computers." `arXiv preprint arXiv:1907.13117 (2019). -# `__ -# -# .. [#gokhale2020] -# -# Pranav Gokhale, *et al.* "Minimizing state preparations in variational quantum eigensolver by -# partitioning into commuting families." `arXiv preprint arXiv:1907.13623 (2019). -# `__ -# -# .. [#verteletskyi2020] -# -# Vladyslav Verteletskyi, Tzu-Ching Yen, and Artur F. Izmaylov. "Measurement optimization in the -# variational quantum eigensolver using a minimum clique cover." `The Journal of Chemical Physics -# 152.12 (2020): 124114. `__ -# -# -# About the author -# ---------------- -# +r""" +Measurement optimization +======================== + +.. meta:: + :property="og:description": Optimize and reduce the number of measurements required to evaluate a variational algorithm cost function. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/grouping.png + +.. related:: + + tutorial_vqe A brief overview of VQE + tutorial_quantum_chemistry Building molecular Hamiltonians + tutorial_qaoa_intro Intro to QAOA + +*Author: Josh Izaac — Posted: 18 January 2021. Last updated: 29 August 2023.* + +The variational quantum eigensolver (VQE) is the OG variational quantum algorithm. Harnessing +near-term quantum hardware to solve for the electronic structure of molecules, VQE is *the* +algorithm that sparked the variational circuit craze of the last 5 years, and holds great +promise for showcasing a quantum advantage on near-term quantum hardware. It has also inspired +other quantum algorithms such as the :doc:`Quantum Approximate Optimization Algorithm (QAOA) +`. + +To scale VQE beyond the regime of classical computation, however, we need to solve for the +ground state of increasingly larger molecules. A consequence is that the number of +measurements we need to make on the quantum hardware also grows polynomially—a huge bottleneck, +especially when quantum hardware access is limited and expensive. + +To mitigate this 'measurement problem', a plethora of recent research dropped over the course of +2019 and 2020 [#yen2020]_ [#izmaylov2019]_ [#huggins2019]_ [#gokhale2020]_ [#verteletskyi2020]_ , +exploring potential strategies to minimize the number of measurements required. In fact, by grouping +commuting terms of the Hamiltonian, we can significantly reduce the number of +measurements needed—in some cases, reducing the number of measurements by up to 90%! + +.. figure:: /_static/demonstration_assets/measurement_optimize/grouping.png + :width: 90% + :align: center + +In this demonstration, we revisit the VQE algorithm, see first-hand how the required number of +measurements scales as molecule size increases, and finally use these measurement optimization +strategies to minimize the number of measurements we need to make. These techniques are valuable +beyond just VQE, allowing you to add measurement optimization to your toolkit of techniques to +perform variational algorithms more efficiently. + +Revisiting VQE +-------------- + +The study of :doc:`variational quantum algorithms ` was spearheaded +by the introduction of the :doc:`variational quantum eigensolver ` (VQE) algorithm in +2014 [#peruzzo2014]_. While classical variational techniques have been known for decades to estimate +the ground state energy of a molecule, VQE allowed this variational technique to be applied using +quantum computers. Since then, the field of variational quantum algorithms has evolved +significantly, with larger and more complex models being proposed (such as +:doc:`quantum neural networks `, :doc:`QGANs `, and +:doc:`variational classifiers `). However, quantum chemistry +remains one of the flagship use-cases for variational quantum algorithms, and VQE the standard-bearer. + +Part of the appeal of VQE lies within its simplicity. A circuit ansatz :math:`U(\theta)` is chosen +(typically the Unitary Coupled-Cluster Singles and Doubles +(:func:`~pennylane.templates.subroutines.UCCSD`) ansatz), and the qubit representation of the +molecular Hamiltonian is computed: + +.. math:: H = \sum_i c_i h_i, + +where :math:`h_i` are the terms of the Hamiltonian written as a tensor product of Pauli operators or the identity +acting on wire :math:`n,` :math:`P_n \in \{I, \sigma_x, \sigma_y, \sigma_z\}:` + +.. math:: h_i = \bigotimes_{n=0}^{N-1} P_n. + +(The :math:`h_i` product of Pauli terms is often referred to as a 'Pauli word' in the literature.) The cost +function of the VQE is then simply the expectation value of this Hamiltonian on the state obtained +after running the variational quantum circuit: + +.. math:: \text{cost}(\theta) = \langle 0 | U(\theta)^\dagger H U(\theta) | 0 \rangle. + +By using a classical optimizer to *minimize* this quantity, we can estimate +the ground state energy of the Hamiltonian :math:`H:` + +.. math:: H U(\theta_{min}) |0\rangle = E_{min} U(\theta_{min}) |0\rangle. + +In practice, when we are using quantum hardware to compute these expectation values we expand out +the Hamiltonian as its summation, resulting in separate expectation values that need to be calculated for each term: + +.. math:: + + \text{cost}(\theta) = \langle 0 | U(\theta)^\dagger \left(\sum_i c_i h_i\right) U(\theta) | 0 \rangle + = \sum_i c_i \langle 0 | U(\theta)^\dagger h_i U(\theta) | 0 \rangle. + +.. note:: + + How do we compute the qubit representation of the molecular Hamiltonian? This is a more + complicated story that involves applying a self-consistent field method (such as Hartree-Fock), + and then performing a fermionic-to-qubit mapping such as the Jordan-Wigner or Bravyi-Kitaev + transformations. + + For more details on this process, check out the :doc:`/demos/tutorial_quantum_chemistry` + tutorial. + +The measurement problem +----------------------- + +For small molecules, the VQE algorithm scales and performs exceedingly well. For example, for the +Hydrogen molecule :math:`\text{H}_2,` the final Hamiltonian in its qubit representation +has 15 terms that need to be measured. Let's obtain the Hamiltonian from +`PennyLane's dataset library `__ +to verify the number of terms. In this tutorial, we use the :func:`~.pennylane.data.load` +function to download the dataset of the molecule. + +""" + +import functools +import warnings +import jax +from jax import numpy as jnp +import pennylane as qml + +jax.config.update("jax_enable_x64", True) + +dataset = qml.data.load("qchem", molname="H2", bondlength=0.7)[0] +H, num_qubits = dataset.hamiltonian, len(dataset.hamiltonian.wires) + +print("Required number of qubits:", num_qubits) +print(H) + +############################################################################### +# Here, we can see that the Hamiltonian involves 15 terms, so we expect to compute 15 expectation values +# on hardware. Let's generate the cost function to check this. + +# Create a 4 qubit simulator +dev = qml.device("default.qubit", shots=1000, seed=904932) + +# number of electrons +electrons = 2 + +# Define the Hartree-Fock initial state for our variational circuit +initial_state = qml.qchem.hf_state(electrons, num_qubits) + +# Construct the UCCSD ansatz +singles, doubles = qml.qchem.excitations(electrons, num_qubits) +s_wires, d_wires = qml.qchem.excitations_to_wires(singles, doubles) +ansatz = functools.partial( + qml.UCCSD, init_state=initial_state, s_wires=s_wires, d_wires=d_wires +) + +# generate the cost function +@qml.qnode(dev, interface="jax") +def cost_circuit(params): + ansatz(params, wires=range(num_qubits)) + return qml.expval(H) + +############################################################################## +# If we evaluate this cost function, we can see that it corresponds to 15 different +# executions under the hood—one per expectation value: + +from jax import random as random +key, scale = random.PRNGKey(0), jnp.pi +params = random.normal(key, shape=(len(singles) + len(doubles),)) * scale +with qml.Tracker(dev) as tracker: # track the number of executions + print("Cost function value:", cost_circuit(params)) + +print("Number of quantum evaluations:", tracker.totals['executions']) + +############################################################################## +# How about a larger molecule? Let's try the +# `water molecule `__: + +dataset = qml.data.load('qchem', molname="H2O")[0] +H, num_qubits = dataset.hamiltonian, len(dataset.hamiltonian.wires) + +print("Required number of qubits:", num_qubits) +print("Number of Hamiltonian terms/required measurements:", len(H.terms()[0])) + +print("\n", H) + + +############################################################################## +# Simply going from two atoms in :math:`\text{H}_2` to three in :math:`\text{H}_2 \text{O}` +# resulted in over triple the number of qubits required and 1086 measurements that must be made! +# +# We can see that as the size of our molecule increases, we run into a problem: larger molecules +# result in Hamiltonians that not only require a larger number of qubits :math:`N` in their +# representation, but the number of terms in the Hamiltonian scales like +# :math:`\mathcal{O}(N^4)!` 😱😱😱 +# +# We can mitigate this somewhat by choosing smaller `basis sets +# `__ to represent the electronic structure +# wavefunction, however this would be done at the cost of solution accuracy, and doesn't reduce the number of +# measurements significantly enough to allow us to scale to classically intractable problems. +# +# .. figure:: /_static/demonstration_assets/measurement_optimize/n4.png +# :width: 70% +# :align: center +# +# The number of qubit Hamiltonian terms required to represent various molecules in the specified +# basis sets (adapted from `Minimizing State Preparations for VQE by Gokhale et al. +# `__) + + +############################################################################## +# Simultaneously measuring observables +# ------------------------------------ +# +# One of the assumptions we made above was that every term in the Hamiltonian must be measured independently. +# However, this might not be the case. From the `Heisenberg uncertainty relationship +# `__ for two +# observables :math:`\hat{A}` and :math:`\hat{B},` we know that +# +# .. math:: \sigma_A^2 \sigma_B^2 \geq \frac{1}{2}\left|\left\langle [\hat{A}, \hat{B}] \right\rangle\right|, +# +# where :math:`\sigma^2_A` and :math:`\sigma^2_B` are the variances of measuring the expectation value of the +# associated observables, and +# +# .. math:: [\hat{A}, \hat{B}] = \hat{A}\hat{B}-\hat{B}\hat{A} +# +# is the commutator. Therefore, +# +# - If the two observables :math:`\hat{A}` and :math:`\hat{B}` do not commute (:math:`[\hat{A}, +# \hat{B}] \neq 0`), then :math:`\sigma_A^2 +# \sigma_B^2 > 0` and we cannot simultaneously measure the expectation values of the two +# observables. +# +# .. +# +# - If :math:`\hat{A}` and :math:`\hat{B}` **do** commute (:math:`[\hat{A}, +# \hat{B}] = 0`), then :math:`\sigma_A^2 +# \sigma_B^2 \geq 0` and there exists a measurement basis where we can **simultaneously measure** the +# expectation value of both observables on the same state. +# +# To explore why commutativity and simultaneous measurement are related, let's assume that there +# is a complete, orthonormal eigenbasis :math:`|\phi_n\rangle` that *simultaneously +# diagonalizes* both :math:`\hat{A}` and :math:`\hat{B}:` +# +# .. math:: +# +# ① ~~ \hat{A} |\phi_n\rangle &= \lambda_{A,n} |\phi_n\rangle,\\ +# ② ~~ \hat{B} |\phi_n\rangle &= \lambda_{B,n} |\phi_n\rangle. +# +# where :math:`\lambda_{A,n}` and :math:`\lambda_{B,n}` are the corresponding eigenvalues. +# If we pre-multiply the first equation by :math:`\hat{B},` and the second by :math:`\hat{A}` +# (both denoted in blue): +# +# .. math:: +# +# \color{blue}{\hat{B}}\hat{A} |\phi_n\rangle &= \lambda_{A,n} \color{blue}{\hat{B}} +# |\phi_n\rangle = \lambda_{A,n} \color{blue}{\lambda_{B,n}} |\phi_n\rangle,\\ +# \color{blue}{\hat{A}}\hat{B} |\phi_n\rangle &= \lambda_{B,n} \color{blue}{\hat{A}} +# |\phi_n\rangle = \lambda_{A,n} \color{blue}{\lambda_{B,n}} |\phi_n\rangle. +# +# We can see that assuming a simultaneous eigenbasis requires that +# :math:`\hat{A}\hat{B}|\phi_n\rangle = \hat{B}\hat{A}|\phi_n\rangle.` Or, rearranging, +# +# .. math:: (\hat{A}\hat{B} - \hat{B}\hat{A}) |\phi_n\rangle = [\hat{A}, \hat{B}]|\phi_n\rangle = 0. +# +# Our assumption that :math:`|\phi_n\rangle` simultaneously diagonalizes both :math:`\hat{A}` and +# :math:`\hat{B}` only holds true if the two observables commute. +# +# So far, this seems awfully theoretical. What does this mean in practice? +# +# In the realm of variational circuits, we typically want to compute expectation values of an +# observable on a given state :math:`|\psi\rangle.` If we have two commuting observables, we now know that +# they share a simultaneous eigenbasis: +# +# .. math:: +# +# \hat{A} &= \sum_n \lambda_{A, n} |\phi_n\rangle\langle \phi_n|,\\ +# \hat{B} &= \sum_n \lambda_{B, n} |\phi_n\rangle\langle \phi_n|. +# +# Substituting this into the expression for the expectation values: +# +# .. math:: +# +# \langle\hat{A}\rangle &= \langle \psi | \hat{A} | \psi \rangle = \langle \psi | \left( \sum_n +# \lambda_{A, n} |\phi_n\rangle\langle \phi_n| \right) | \psi \rangle = \sum_n \lambda_{A,n} +# |\langle \phi_n|\psi\rangle|^2,\\ +# \langle\hat{B}\rangle &= \langle \psi | \hat{B} | \psi \rangle = \langle \psi | \left( \sum_n +# \lambda_{B, n} |\phi_n\rangle\langle \phi_n| \right) | \psi \rangle = \sum_n \lambda_{B,n} +# |\langle \phi_n|\psi\rangle|^2. +# +# So, assuming we know the eigenvalues of the commuting observables in advance, if we perform a +# measurement in their shared eigenbasis (the :math:`|\phi_n\rangle`), we only need to perform a **single measurement** of the +# probabilities :math:`|\langle \phi_n|\psi\rangle|^2` in order to recover both expectation values! 😍 +# +# Fantastic! But, can we use this to reduce the number of measurements we need to perform in the VQE algorithm? +# To do so, we must find the answer to two questions: +# +# 1. How do we determine which terms of the cost Hamiltonian commute? +# +# 2. How do we rotate the circuit into the shared eigenbasis prior to measurement? +# +# The answers to these questions aren't necessarily easy nor straightforward. Thankfully, there are +# some recent techniques we can harness to address both. + +############################################################################## +# Qubit-wise commuting Pauli terms +# -------------------------------- +# +# Back when we summarized the VQE algorithm, we saw that each term of the Hamiltonian is generally represented +# as a tensor product of Pauli operators: +# +# .. math:: h_i = \bigotimes_{n=0}^{N-1} P_n. +# +# Luckily, this tensor product structure allows us to take a bit of a shortcut. Rather than consider +# **full commutativity**, we can consider a more strict condition known as **qubit-wise +# commutativity** (QWC). +# +# To start with, let's consider single-qubit Pauli operators and the identity. We know that the Pauli operators +# commute with themselves as well as the identity, but they do *not* commute with +# each other: +# +# .. math:: +# +# [\sigma_i, I] = 0, ~~~ [\sigma_i, \sigma_i] = 0, ~~~ [\sigma_i, \sigma_j] = c \sigma_k \delta_{ij}. +# +# Now consider two tensor products of Pauli terms, for example :math:`X\otimes Y \otimes I` and +# :math:`X\otimes I \otimes Z.` We say that these two terms are qubit-wise commuting, since, if +# we compare each subsystem in the tensor product, we see that every one commutes: +# +# .. math:: +# +# \begin{array}{ | *1c | *1c | *1c | *1c | *1c |} +# X &\otimes &Y &\otimes &I\\ +# X &\otimes &I &\otimes &Z +# \end{array} ~~~~ \Rightarrow ~~~~ [X, X] = 0, ~~~ [Y, I] = 0, ~~~ [I, Z] = 0. +# +# As a consequence, both terms must commute: +# +# .. math:: [X\otimes Y \otimes I, X\otimes I \otimes Z] = 0. +# +# .. important:: +# +# Qubit-wise commutativity is a **sufficient** but not **necessary** condition +# for full commutativity. For example, the two Pauli terms :math:`Y\otimes Y` and +# :math:`X\otimes X` are not qubit-wise commuting, but do commute (have a go verifying this!). +# +# Once we have identified a qubit-wise commuting pair of Pauli terms, it is also straightforward to +# find the gates to rotate the circuit into the shared eigenbasis. To do so, we simply rotate +# each qubit one-by-one depending on the Pauli operator we are measuring on that wire: +# +# .. raw:: html +# +# +#
+# +# .. rst-class:: docstable +# +# +------------------+-------------------------------+ +# | Observable | Rotation gate | +# +==================+===============================+ +# | :math:`X` | :math:`RY(-\pi/2) = H` | +# +------------------+-------------------------------+ +# | :math:`Y` | :math:`RX(\pi/2)=HS^{-1}=HSZ` | +# +------------------+-------------------------------+ +# | :math:`Z` | :math:`I` | +# +------------------+-------------------------------+ +# | :math:`I` | :math:`I` | +# +------------------+-------------------------------+ +# +# .. raw:: html +# +#
+# +# Therefore, in this particular example: +# +# * Wire 0: we are measuring both terms in the :math:`X` basis, apply the Hadamard gate +# * Wire 1: we are measuring both terms in the :math:`Y` basis, apply a :math:`RX(\pi/2)` gate +# * Wire 2: we are measuring both terms in the :math:`Z` basis (the computational basis), no gate needs to be applied. +# +# Let's use PennyLane to verify this. + + +obs = [ + qml.PauliX(0) @ qml.PauliY(1), + qml.PauliX(0) @ qml.PauliZ(2) +] + + +############################################################################## +# First, let's naively use two separate circuit evaluations to measure +# the two QWC terms. + + +dev = qml.device("default.qubit", wires=3) + +@qml.qnode(dev, interface="jax") +def circuit1(weights): + qml.StronglyEntanglingLayers(weights, wires=range(3)) + return qml.expval(obs[0]) + + +@qml.qnode(dev, interface="jax") +def circuit2(weights): + qml.StronglyEntanglingLayers(weights, wires=range(3)) + return qml.expval(obs[1]) + +param_shape = qml.templates.StronglyEntanglingLayers.shape(n_layers=3, n_wires=3) +key, scale = random.PRNGKey(192933), 0.1 +weights = scale * random.normal(key, shape=param_shape) + +print("Expectation value of XYI = ", circuit1(weights)) +print("Expectation value of XIZ = ", circuit2(weights)) + +############################################################################## +# Now, let's use our QWC approach to reduce this down to a *single* measurement +# of the probabilities in the shared eigenbasis of both QWC observables: + +@qml.qnode(dev, interface="jax") +def circuit_qwc(weights): + qml.StronglyEntanglingLayers(weights, wires=range(3)) + + # rotate wire 0 into the shared eigenbasis + qml.RY(-jnp.pi / 2, wires=0) + + # rotate wire 1 into the shared eigenbasis + qml.RX(jnp.pi / 2, wires=1) + + # wire 2 does not require a rotation + + # measure probabilities in the computational basis + return qml.probs(wires=range(3)) + + +rotated_probs = circuit_qwc(weights) +print(rotated_probs) + +############################################################################## +# We're not quite there yet; we have only calculated the probabilities of the variational circuit +# rotated into the shared eigenbasis—the :math:`|\langle \phi_n |\psi\rangle|^2.` To recover the +# *expectation values* of the two QWC observables from the probabilities, recall that we need one +# final piece of information: their eigenvalues :math:`\lambda_{A, n}` and :math:`\lambda_{B, n}.` +# +# We know that the single-qubit Pauli operators each have eigenvalues :math:`(1, -1),` while the identity +# operator has eigenvalues :math:`(1, 1).` We can make use of ``np.kron`` to quickly +# generate the eigenvalues of the full Pauli terms, making sure that the order +# of the eigenvalues in the Kronecker product corresponds to the tensor product. + +eigenvalues_XYI = jnp.kron(jnp.kron(jnp.array([1, -1]), jnp.array([1, -1])), jnp.array([1, 1])) +eigenvalues_XIZ = jnp.kron(jnp.kron(jnp.array([1, -1]), jnp.array([1, 1])), jnp.array([1, -1])) + +# Taking the linear combination of the eigenvalues and the probabilities +print("Expectation value of XYI = ", jnp.dot(eigenvalues_XYI, rotated_probs)) +print("Expectation value of XIZ = ", jnp.dot(eigenvalues_XIZ, rotated_probs)) + + +############################################################################## +# Compare this to the result when we used two circuit evaluations. We have successfully used a +# single circuit evaluation to recover both expectation values! +# +# Luckily, PennyLane automatically performs this QWC grouping under the hood. We simply +# return the two QWC Pauli terms from the QNode: + +@qml.qnode(dev, interface="jax") +def circuit(weights): + qml.StronglyEntanglingLayers(weights, wires=range(3)) + return [ + qml.expval(qml.PauliX(0) @ qml.PauliY(1)), + qml.expval(qml.PauliX(0) @ qml.PauliZ(2)) + ] + + +print(circuit(weights)) + + +############################################################################## +# Behind the scenes, PennyLane is making use of our built-in +# :mod:`qml.pauli ` module, which contains functions for diagonalizing QWC +# terms: + +rotations, new_obs = qml.pauli.diagonalize_qwc_pauli_words(obs) + +print(rotations) +print(new_obs) + + +############################################################################## +# Here, the first line corresponds to the basis rotations that were discussed above, written in +# terms of ``RX`` and ``RY`` rotations. Check out the :mod:`qml.pauli ` +# documentation for more details on its provided functionality and how it works. +# +# Given a Hamiltonian containing a large number of Pauli terms, +# there is a high likelihood of there being a significant number of terms that qubit-wise commute. Can +# we somehow partition the terms into **fewest** number of QWC groups to minimize the number of measurements +# we need to take? + +############################################################################## +# Grouping QWC terms +# ------------------ +# +# A nice example is provided in [#verteletskyi2020]_ showing how we might tackle this. Say we have +# the following Hamiltonian defined over four qubits: +# +# .. math:: H = Z_0 + Z_0 Z_1 + Z_0 Z_1 Z_2 + Z_0 Z_1 Z_2 Z_3 + X_2 X_3 + Y_0 X_2 X_3 + Y_0 Y_1 X_2 X_3, +# +# where we are using the shorthand :math:`P_0 P_2 = P\otimes I \otimes P \otimes I` for brevity. +# If we go through and work out which Pauli terms are qubit-wise commuting, we can represent +# this in a neat way using a graph: +# +# .. figure:: /_static/demonstration_assets/measurement_optimize/graph1.png +# :width: 70% +# :align: center +# +# In the above graph, every node represents an individual Pauli term of the Hamiltonian, with +# edges connecting terms that are qubit-wise commuting. Groups of qubit-wise commuting terms are +# represented as **complete subgraphs**. Straight away, we can make an observation: +# there is no unique solution for partitioning the Hamiltonian into groups of qubit-wise commuting +# terms! In fact, there are several solutions: +# +# .. figure:: /_static/demonstration_assets/measurement_optimize/graph2.png +# :width: 90% +# :align: center +# +# Of course, of the potential solutions above, there is one that is more optimal than the others --- +# on the bottom left, we have partitioned the graph into *two* complete subgraphs, as opposed to the +# other solutions that require three complete subgraphs. If we were to go with this solution, +# we would be able to measure the expectation value of the Hamiltonian using two circuit evaluations. +# +# This problem—finding the minimum number of complete subgraphs of a graph—is actually quite well +# known in graph theory, where it is referred to as the `minimum clique cover problem +# `__ (with 'clique' being another term for a complete subgraph). +# +# Unfortunately, that's where our good fortune ends—the minimum clique cover problem is known to +# be `NP-hard `__, meaning there is no known (classical) +# solution to finding the optimum/minimum clique cover in polynomial time. +# +# Thankfully, there is a silver lining: we know of polynomial-time algorithms for finding +# *approximate* solutions to the minimum clique cover problem. These heuristic approaches, while +# not guaranteed to find the optimum solution, scale quadratically with the number of nodes in the +# graph/terms in the Hamiltonian [#yen2020]_, so work reasonably well in practice. +# +# Many of these heuristic approaches have roots in another graph problem known as `graph +# colouring `__; the assignment of colours to +# the graph's vertices such that no adjacent vertices have the same colour. How is this related +# to the minimum clique cover problem, though? If we take our QWC graph above, and generate the +# `complement graph `__ by drawing edges +# between all *non*-adjacent nodes, +# +# .. figure:: /_static/demonstration_assets/measurement_optimize/graph3.png +# :width: 100% +# :align: center +# +# we see that solving the minimum clique cover problem on the QWC graph is equivalent to solving the +# graph colouring problem on the complement graph using the minimum possible number of colours. +# While there are various different heuristic algorithms, a common one is `greedy colouring +# `__; in fact, the open-source graph +# package `NetworkX even provides a function for greedy colouring +# `__, +# ``nx.greedy_color``. +# +# Let's give this a go, using NetworkX to solve the minimum clique problem for observable grouping. +# First, we'll need to generate the QWC graph (with each node corresponding to a Hamiltonian +# term, and edges indicating two terms that are QWC). + +import networkx as nx +from matplotlib import pyplot as plt + +terms = [ + qml.PauliZ(0), + qml.PauliZ(0) @ qml.PauliZ(1), + qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2), + qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3), + qml.PauliX(2) @ qml.PauliX(3), + qml.PauliY(0) @ qml.PauliX(2) @ qml.PauliX(3), + qml.PauliY(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliX(3) +] + +def format_pauli_word(term): + """Convenience function that nicely formats a PennyLane + tensor observable as a Pauli word""" + if isinstance(term, qml.ops.Prod): + return " ".join([format_pauli_word(t) for t in term]) + + return f"{term.name[-1]}{term.wires.tolist()[0]}" + +G = nx.Graph() + +with warnings.catch_warnings(): + # Muting irrelevant warnings + warnings.filterwarnings( + "ignore", + message="The behaviour of operator ", + category=UserWarning, + ) + + # add the terms to the graph + G.add_nodes_from(terms) + + # add QWC edges + G.add_edges_from([ + [terms[0], terms[1]], # Z0 <--> Z0 Z1 + [terms[0], terms[2]], # Z0 <--> Z0 Z1 Z2 + [terms[0], terms[3]], # Z0 <--> Z0 Z1 Z2 Z3 + [terms[1], terms[2]], # Z0 Z1 <--> Z0 Z1 Z2 + [terms[2], terms[3]], # Z0 Z1 Z2 <--> Z0 Z1 Z2 Z3 + [terms[1], terms[3]], # Z0 Z1 <--> Z0 Z1 Z2 Z3 + [terms[0], terms[4]], # Z0 <--> X2 X3 + [terms[1], terms[4]], # Z0 Z1 <--> X2 X3 + [terms[4], terms[5]], # X2 X3 <--> Y0 X2 X3 + [terms[4], terms[6]], # X2 X3 <--> Y0 Y1 X2 X3 + [terms[5], terms[6]], # Y0 X2 X3 <--> Y0 Y1 X2 X3 + ]) + + plt.margins(x=0.1) + coords = nx.spring_layout(G, seed=1) + nx.draw( + G, + coords, + labels={node: format_pauli_word(node) for node in terms}, + with_labels=True, + node_size=500, + font_size=8, + node_color="#9eded1", + edge_color="#c1c1c1", + ) + + ############################################################################## + # We can now generate the complement graph (compare this to our handdrawn + # version above!): + + C = nx.complement(G) + coords = nx.spring_layout(C, seed=1) + + nx.draw( + C, + coords, + labels={node: format_pauli_word(node) for node in terms}, + with_labels=True, + node_size=500, + font_size=8, + node_color="#9eded1", + edge_color="#c1c1c1" + ) + + ############################################################################## + # Now that we have the complement graph, we can perform a greedy coloring to + # determine the minimum number of QWC groups: + + groups = nx.coloring.greedy_color(C, strategy="largest_first") + + # plot the complement graph with the greedy colouring + nx.draw( + C, + coords, + labels={node: format_pauli_word(node) for node in terms}, + with_labels=True, + node_size=500, + font_size=8, + node_color=[("#9eded1", "#aad4f0")[groups[node]] for node in C], + edge_color="#c1c1c1" + ) + + +num_groups = len(set(groups.values())) +print("Minimum number of QWC groupings found:", num_groups) + + +for i in range(num_groups): + print(f"\nGroup {i}:") + + for term, group_id in groups.items(): + if group_id == i: + print(format_pauli_word(term)) + +############################################################################## +# Putting it all together +# ----------------------- +# +# So, we now have a strategy for minimizing the number of measurements we need to perform +# for our VQE problem: +# +# 1. Determine which terms of the Hamiltonian are qubit-wise commuting, and use +# this to construct a graph representing the QWC relationship. +# +# 2. Construct the complement QWC graph. +# +# 3. Use a graph colouring heuristic algorithm to determine a graph colouring for the complement graph +# with a minimum number of colours. Each coloured vertex set corresponds to a +# qubit-wise commuting group of Hamiltonian terms. +# +# 4. Generate and evaluate the circuit ansatz (with additional rotations) per +# QWC grouping, extracting probability distributions. +# +# 5. Finally, post-process the probability distributions with the observable eigenvalues +# to recover the Hamiltonian expectation value. +# +# Luckily, the PennyLane ``pauli`` module makes this relatively easy. Let's walk through +# the entire process using the provided grouping functions. +# +# Steps 1-3 (finding and grouping QWC terms in the Hamiltonian) can be done via the +# :func:`qml.pauli.group_observables ` function: + +obs_groupings = qml.pauli.group_observables(terms, grouping_type='qwc', method='rlf') + + +############################################################################## +# The ``grouping_type`` argument allows us to choose how the commuting terms +# are determined (more on that later!) whereas ``method`` determines the colouring +# heuristic (in this case, ``"rlf"`` refers to Recursive Largest First, a variant of largest first colouring heuristic). +# +# If we want to see what the required rotations and measurements are, we can use the +# :func:`qml.pauli.diagonalize_qwc_groupings ` +# function: + +rotations, measurements = qml.pauli.diagonalize_qwc_groupings(obs_groupings) + +############################################################################## +# However, this isn't strictly necessary—recall previously that the QNode +# has the capability to *automatically* measure qubit-wise commuting observables! + +dev = qml.device("lightning.qubit", wires=4) + +@qml.qnode(dev, interface="jax") +def circuit(weights, group=None, **kwargs): + qml.StronglyEntanglingLayers(weights, wires=range(4)) + return [qml.expval(o) for o in group] + +param_shape = qml.templates.StronglyEntanglingLayers.shape(n_layers=3, n_wires=4) +key = random.PRNGKey(1) +weights = random.normal(key, shape=param_shape) * 0.1 +result = [jnp.array(circuit(weights, group=g)) for g in obs_groupings] + +print("Term expectation values:") +for group, expvals in enumerate(result): + print(f"Group {group} expectation values:", expvals) + +# Since all the coefficients of the Hamiltonian are unity, +# we can simply sum the expectation values. +print(" = ", jnp.sum(jnp.hstack(result))) + + +############################################################################## +# Finally, we don't need to go through this process manually every time; if our cost function can be +# written in the form of an expectation value of a Hamiltonian (as is the case for most VQE and QAOA +# problems), we can use the option ``grouping_type="qwc"`` in :class:`~.pennylane.Hamiltonian` to +# automatically optimize the measurements. + +H = qml.Hamiltonian(coeffs=jnp.ones(len(terms)), observables=terms, grouping_type="qwc") +_, H_ops = H.terms() +@qml.qnode(dev, interface="jax") +def cost_fn(weights): + qml.StronglyEntanglingLayers(weights, wires=range(4)) + return qml.expval(H) +print(cost_fn(weights)) + +############################################################################## +# Beyond VQE +# ---------- +# +# Wait, hang on. We dove so deeply into measurement grouping and optimization, we forgot to check +# how this affects the number of measurements required to perform the VQE on :math:`\text{H}_2 \text{O}!` +# Let's use our new-found knowledge to see what happens. + +dataset = qml.data.load('qchem', molname="H2O")[0] +H, num_qubits = dataset.hamiltonian, len(dataset.hamiltonian.wires) +print("Number of Hamiltonian terms/required measurements:", len(H_ops)) + +# grouping +groups = qml.pauli.group_observables(H_ops, grouping_type='qwc', method='rlf') +print("Number of required measurements after optimization:", len(groups)) + +############################################################################## +# We went from 1086 required measurements/circuit evaluations to 320 (just over *one thousand* +# down to *three hundred* 😱😱😱). +# +# As impressive as this is, however, this is just the beginning of the optimization. +# +# While finding qubit-wise commutating terms is relatively straightforward, with a little +# extra computation we can push this number down even further. Recent work has explored +# the savings that can be made by considering *full* commutativity [#yen2020]_, unitary +# partitioning [#izmaylov2019]_, and Fermionic basis rotation grouping [#huggins2019]_. +# Work has also been performed to reduce the classical overhead associated with measurement +# optimization, allowing the classical measurement grouping to be performed in linear time +# [#gokhale2020]_. For example, recall that qubit-wise commutativity is only a subset of +# full commutativity; if we consider full commutativity instead, we can further reduce the +# number of groups required. +# +# Finally, it is worth pointing out that, as the field of variational quantum algorithms grows, this +# problem of measurement optimization no longer just applies to the VQE algorithm (the algorithm it +# was born from). Instead, there are a multitude of algorithms that could benefit from these +# measurement optimization techniques (QAOA being a prime example). +# +# So the next time you are working on a variational quantum algorithm and the number +# of measurements required begins to explode—stop, take a deep breath 😤, and consider grouping +# and optimizing your measurements. +# +# .. note:: +# +# Qubit-wise commuting group information for a wide variety of molecules has been +# pre-computed, and is available for download in +# in the `PennyLane Datasets library `__. + +############################################################################## +# References +# ---------- +# +# .. [#peruzzo2014] +# +# Alberto Peruzzo, Jarrod McClean *et al.*, "A variational eigenvalue solver on a photonic +# quantum processor". `Nature Communications 5, 4213 (2014). +# `__ +# +# .. [#yen2020] +# +# Tzu-Ching Yen, Vladyslav Verteletskyi, and Artur F. Izmaylov. "Measuring all compatible +# operators in one series of single-qubit measurements using unitary transformations." `Journal of +# Chemical Theory and Computation 16.4 (2020): 2400-2409. +# `__ +# +# .. [#izmaylov2019] +# +# Artur F. Izmaylov, *et al.* "Unitary partitioning approach to the measurement problem in the +# variational quantum eigensolver method." `Journal of Chemical Theory and Computation 16.1 (2019): +# 190-195. `__ +# +# .. [#huggins2019] +# +# William J. Huggins, *et al.* "Efficient and noise resilient measurements for quantum chemistry +# on near-term quantum computers." `arXiv preprint arXiv:1907.13117 (2019). +# `__ +# +# .. [#gokhale2020] +# +# Pranav Gokhale, *et al.* "Minimizing state preparations in variational quantum eigensolver by +# partitioning into commuting families." `arXiv preprint arXiv:1907.13623 (2019). +# `__ +# +# .. [#verteletskyi2020] +# +# Vladyslav Verteletskyi, Tzu-Ching Yen, and Artur F. Izmaylov. "Measurement optimization in the +# variational quantum eigensolver using a minimum clique cover." `The Journal of Chemical Physics +# 152.12 (2020): 124114. `__ +# +# +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_pasqal/demo.py b/demonstrations_v2/tutorial_pasqal/demo.py index b49898f392..8618d05f4b 100644 --- a/demonstrations_v2/tutorial_pasqal/demo.py +++ b/demonstrations_v2/tutorial_pasqal/demo.py @@ -1,362 +1,362 @@ -r""" -Quantum computation with neutral atoms -====================================== - -.. meta:: - :property="og:description": Neutral atom quantum devices allow you to place - qubits within interesting three-dimensional configurations. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/pasqal_thumbnail.png - -.. related:: - ahs_aquila Pulse programming on neutral atom hardware - -*Author: Nathan Killoran — Posted: 13 October 2020. Last updated: 21 January 2021.* - -Quantum computing architectures come in many flavours: superconducting qubits, ion traps, -photonics, silicon, and more. One very interesting physical substrate is *neutral atoms*. These -quantum devices have some basic similarities to ion traps. Ion-trap devices make use of atoms -that have an imbalance between protons (positively charged) and electrons (negatively charged). -Neutral atoms, on the other hand, have an equal number of protons and electrons. - -In neutral-atom systems, the individual atoms can be easily programmed into various two- or -three-dimensional configurations. Like ion-trap systems, individual qubits can be encoded into -the energy levels of the atoms. The qubits thus retain the three-dimensional geometry of their -host atoms. Qubits that are nearby in space can be programmed to interact with one another -via two-qubit gates. This opens up some tantalizing possibilities for exotic quantum-computing -circuit topologies. - -.. figure:: https://raw.githubusercontent.com/lhenriet/cirq-pasqal/fc4f9c7792a8737fde76d4c05828aa538be8452e/pasqal-tutorials/files/eiffel_tower.png - :align: center - :width: 50% - - .. - - Neutral atoms (green dots) arranged in various configurations. These atoms can be - used to encode qubits and carry out quantum computations. Image originally from [#barredo2017]_. - -The startup `Pasqal `_ is one of the companies working to bring -neutral-atom quantum computing devices to the world. To support this new class of devices, -Pasqal has contributed some new features to the quantum software library `Cirq `_. - -In this demo, we will use PennyLane, Cirq, and TensorFlow to show off the unique abilities of -neutral atom devices, leveraging them to make a variational quantum circuit which has a -very unique topology: *the Eiffel tower*. Specifically, we will build a simple toy -circuit whose qubits are arranged like the Eiffel tower. The girders between -the points on the tower will represent two-qubit gates, with the final output of our -variational circuit coming at the very peak of the tower. - -Let's get to it! - -.. note:: - - To run this demo locally, you will need to install `Cirq - `_, (version >= 0.9.1), and the - `PennyLane-cirq plugin `_ - (version >= 0.13). You will also need to download a copy of the data, which - is available `here - `_. - -""" - -############################################################################## -# Building the Eiffel tower -# ------------------------- -# -# Our first step will be to load and visualize the data for the Eiffel tower -# configuration, which was generously provided by the team at Pasqal. -# (If running locally, the line below should be updated with the local -# path where you have saved the downloaded data). - -import numpy as np -coords = np.loadtxt("../_static/demonstration_assets/pasqal/Eiffel_tower_data.dat") -xs = coords[:,0] -ys = coords[:,1] -zs = coords[:,2] - -import matplotlib.pyplot as plt -fig = plt.figure() -ax = fig.add_subplot(111, projection='3d') -ax.view_init(40, 15) -fig.subplots_adjust(left=0, right=1, bottom=0, top=1) -ax.set_xlim(-20, 20) -ax.set_ylim(-20, 20) -ax.set_zlim(-40, 10) -plt.axis('off') -ax.scatter(xs, ys, zs, c='g',alpha=0.3) -plt.show() - -############################################################################## -# This dataset contains 126 points. Each point represents a distinct -# neutral-atom qubit. Simulating this many qubits would be outside the -# reach of Cirq's built-in simulators, so for this demo, -# we will pare down to just 9 points, evenly spaced around the tower. -# These are highlighted in red below. -# - -fig = plt.figure() -ax = fig.add_subplot(111, projection='3d') -ax.view_init(40, 15) -fig.subplots_adjust(left=0, right=1, bottom=0, top=1) -ax.set_xlim(-20, 20) -ax.set_ylim(-20, 20) -ax.set_zlim(-40, 10) -plt.axis('off') -ax.scatter(xs, ys, zs, c='g', alpha=0.3) - -base_mask = [3, 7, 11, 15] -qubit_mask = [48, 51, 60, 63, 96, 97, 98, 99, 125] -input_coords = coords[base_mask] # we'll need this for a plot later -qubit_coords = coords[qubit_mask] - -subset_xs = qubit_coords[:, 0] -subset_ys = qubit_coords[:, 1] -subset_zs = qubit_coords[:, 2] -ax.scatter(subset_xs, subset_ys, subset_zs, c='r', alpha=1.0) -plt.show() - -############################################################################## -# Converting to Cirq qubits -# ------------------------- -# -# Our next step will be to convert these datapoints into objects that -# Cirq understands as qubits. For neutral-atom devices in Cirq, we can use the -# ``ThreeDQubit`` class, which carries information about the three-dimensional -# arrangement of the qubits. -# -# Now, neutral-atom devices come with some physical restrictions. -# Specifically, in a particular three-dimensional configuration, qubits that -# are too distant from one another can't easily interact. Instead, there is -# a notion of a *control radius;* any atoms which are within the system's -# control radius can interact with one another. Qubits separated by a -# distance larger than the control radius cannot interact. -# -# In order to allow our Eiffel tower qubits to interact with -# one another more easily, we will artificially scale some dimensions -# when placing the atoms. - -from cirq_pasqal import ThreeDQubit -xy_scale = 1.5 -z_scale = 0.75 -qubits = [ThreeDQubit(xy_scale * x, xy_scale * y, z_scale * z) - for x, y, z in qubit_coords] - -############################################################################## -# To simulate a neutral-atom quantum computation, we can use the -# ``"cirq.pasqal"`` device, available via the -# `PennyLane-Cirq plugin `_. -# We will need to provide this device with the ``ThreeDQubit`` object that we created -# above. We also need to instantiate the device with a fixed control radius. - -import pennylane as qml - -num_wires = len(qubits) -control_radius = 32.4 -dev = qml.device("cirq.pasqal", control_radius=control_radius, - qubits=qubits, wires=num_wires) - -############################################################################## -# Creating a quantum circuit -# -------------------------- -# -# We will now make a variational circuit out of the Eiffel tower configuration -# from above. Each of the 9 qubits we are using can be thought of -# as a single wire in a quantum circuit. We will cause these qubits to interact by applying -# a sequence of two-qubit gates. Specifically, the circuit consists of several -# stages: -# -# i. Input classical data is converted into quantum information at the first -# (lowest) vertical level of qubits. In this example, our classical data -# will be simple bit strings, which we can embed by using single-qubit -# bit flips (a simple -# `data-embedding `_ -# strategy). -# -# ii. For each corner of the tower, CNOTs are enacted between the first- -# and second-level qubits. -# -# iii. All qubits from the second level interact with a single "peak" qubit -# using a parametrized controlled-rotation operation. The free parameters -# of our variational circuit enter here. -# -# The output of our circuit is determined via a Pauli-Z measurement on -# the final "peak" qubit. -# -# That's a few things to keep track of, so let's show the circuit via a -# three-dimensional image: - -first_lvl_coords = qubit_coords[:4] -second_lvl_coords = qubit_coords[4:8] -peak_coords = qubit_coords[8] - -input_x, input_y, input_z = [input_coords[:, idx] - for idx in range(3)] -second_x, second_y, second_z = [first_lvl_coords[:, idx] - for idx in range(3)] -third_x, third_y, third_z = [second_lvl_coords[:, idx] - for idx in range(3)] -peak_x, peak_y, peak_z = peak_coords - -fig = plt.figure() -ax = fig.add_subplot(111, projection='3d') -ax.view_init(40, 15) -fig.subplots_adjust(left=0, right=1, bottom=0, top=1) -ax.set_xlim(-20, 20) -ax.set_ylim(-20, 20) -ax.set_zlim(-40, 10) -plt.axis('off') - -ax.scatter(xs, ys, zs, c='g', alpha=0.3) -ax.scatter(subset_xs, subset_ys, subset_zs, c='r', alpha=1.0); - -# Two-qubit gates between second and third levels -for corner in range(4): - ax.plot(xs=[second_x[corner], third_x[corner]], - ys=[second_y[corner], third_y[corner]], - zs=[second_z[corner], third_z[corner]], - c='k'); - -# Two-qubit gates between third level and peak -for corner in range(4): - ax.plot(xs=[third_x[corner], peak_x], - ys=[third_y[corner], peak_y], - zs=[third_z[corner], peak_z], - c='k'); - -# Additional lines to guide the eye -for corner in range(4): - ax.plot(xs=[input_x[corner], second_x[corner]], - ys=[input_y[corner], second_y[corner]], - zs=[input_z[corner], second_z[corner]], - c='grey', linestyle='--'); - ax.plot(xs=[second_x[corner], second_x[(corner + 1) % 4]], - ys=[second_y[corner], second_y[(corner + 1) % 4]], - zs=[second_z[corner], second_z[(corner + 1) % 4]], - c='grey', linestyle='--'); - ax.plot(xs=[third_x[corner], third_x[(corner + 1) % 4]], - ys=[third_y[corner], third_y[(corner + 1) % 4]], - zs=[third_z[corner], third_z[(corner + 1) % 4]], - c='grey', linestyle='--'); - -plt.show() - -############################################################################## -# In this figure, the red dots represent the specific qubits we will use in -# our circuit (the green dots are not used in this demo). -# -# The solid black lines indicate two-qubit gates between these qubits. -# The dashed grey lines are meant to guide the eye, but could also be -# used to make a more complex model by adding further two-qubit gates. -# -# Classical data is loaded in at the bottom qubits (the "tower legs") and -# the final measurement result is read out from the top "peak" qubit. -# The order of gate execution proceeds vertically from bottom to top, and -# clockwise at each level. -# -# The code below creates this particular quantum circuit configuration in -# PennyLane: - -peak_qubit = 8 - -def controlled_rotation(phi, wires): - qml.RY(phi, wires=wires[1]) - qml.CNOT(wires=wires) - qml.RY(-phi, wires=wires[1]) - qml.CNOT(wires=wires) - -@qml.qnode(dev, interface="tf") -def circuit(weights, data): - - # Input classical data loaded into qubits at second level - for idx in range(4): - if data[idx]: - qml.PauliX(wires=idx) - - # Interact qubits from second and third levels - for idx in range(4): - qml.CNOT(wires=[idx, idx + 4]) - - # Interact qubits from third level with peak using parameterized gates - for idx, wire in enumerate(range(4, 8)): - controlled_rotation(weights[idx], wires=[wire, peak_qubit]) - - return qml.expval(qml.PauliZ(wires=peak_qubit)) - - -############################################################################## -# Training the circuit -# -------------------- -# -# Let's now leverage this variational circuit to tackle a toy classification -# problem. -# For the purposes of this demo, we will consider a very simple classifier: -# -# * if the first input qubit is in the state :math:`\mid 0 \rangle,` the model -# should make the prediction "0", and -# -# * if the first input qubit is in the state :math:`\mid 1 \rangle,` the model -# should predict "1" (independent of the states of all other qubits). -# -# In other words, the idealized trained model should learn an -# identity transformation between the first qubit and the final one, while -# ignoring the states of all other qubits. -# -# With this goal in mind, we can create a basic cost function. This cost -# function randomly samples possible 4-bit input bitstrings, and compares -# the circuit's output with the value of the first bit. The other bits -# can be thought of as noise that we don't want our model to learn. - - -import tensorflow as tf -np.random.seed(143) -init_weights = np.pi * np.random.rand(4) - -weights = tf.Variable(init_weights, dtype=tf.float64) - -data = np.random.randint(0, 2, size=4) - -def cost(): - data = np.random.randint(0, 2, size=4) - label = data[0] - output = (-circuit(weights, data) + 1) / 2 - return tf.abs(output - label) ** 2 - -opt = tf.keras.optimizers.Adam(learning_rate=0.1) - -for step in range(100): - opt.minimize(cost, [weights]) - if step % 5 == 0: - print("Step {}: cost={}".format(step, cost())) - -print("Final cost value: {}".format(cost())) - -############################################################################## -# Success! The circuit has learned to transfer the state of the first qubit -# to the state of the last qubit, while ignoring the state of all other input -# qubits. -# -# The programmable three-dimensional configurations of neutral-atom quantum -# computers provide a special tool that is hard to replicate in other -# platforms. Could the physical -# arrangement of qubits, in particular the third dimension, be leveraged to -# make quantum algorithms more sparse or efficient? Could neutral-atom -# systems—with their unique programmability of the geometry—allow us to -# rapidly prototype and experiment with new circuit topologies? What -# possibilities could this open up for quantum computing, quantum chemistry, -# or quantum machine learning? -# - -############################################################################## -# References -# ---------- -# -# .. [#barredo2017] -# -# Daniel Barredo, Vincent Lienhard, Sylvain de Leseleuc, Thierry Lahaye, and Antoine Browaeys. -# "Synthetic three-dimensional atomic structures assembled atom by atom." -# `arXiv:1712.02727 -# `__, 2017. -# -# -# About the author -# ---------------- +r""" +Quantum computation with neutral atoms +====================================== + +.. meta:: + :property="og:description": Neutral atom quantum devices allow you to place + qubits within interesting three-dimensional configurations. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/pasqal_thumbnail.png + +.. related:: + ahs_aquila Pulse programming on neutral atom hardware + +*Author: Nathan Killoran — Posted: 13 October 2020. Last updated: 21 January 2021.* + +Quantum computing architectures come in many flavours: superconducting qubits, ion traps, +photonics, silicon, and more. One very interesting physical substrate is *neutral atoms*. These +quantum devices have some basic similarities to ion traps. Ion-trap devices make use of atoms +that have an imbalance between protons (positively charged) and electrons (negatively charged). +Neutral atoms, on the other hand, have an equal number of protons and electrons. + +In neutral-atom systems, the individual atoms can be easily programmed into various two- or +three-dimensional configurations. Like ion-trap systems, individual qubits can be encoded into +the energy levels of the atoms. The qubits thus retain the three-dimensional geometry of their +host atoms. Qubits that are nearby in space can be programmed to interact with one another +via two-qubit gates. This opens up some tantalizing possibilities for exotic quantum-computing +circuit topologies. + +.. figure:: https://raw.githubusercontent.com/lhenriet/cirq-pasqal/fc4f9c7792a8737fde76d4c05828aa538be8452e/pasqal-tutorials/files/eiffel_tower.png + :align: center + :width: 50% + + .. + + Neutral atoms (green dots) arranged in various configurations. These atoms can be + used to encode qubits and carry out quantum computations. Image originally from [#barredo2017]_. + +The startup `Pasqal `_ is one of the companies working to bring +neutral-atom quantum computing devices to the world. To support this new class of devices, +Pasqal has contributed some new features to the quantum software library `Cirq `_. + +In this demo, we will use PennyLane, Cirq, and TensorFlow to show off the unique abilities of +neutral atom devices, leveraging them to make a variational quantum circuit which has a +very unique topology: *the Eiffel tower*. Specifically, we will build a simple toy +circuit whose qubits are arranged like the Eiffel tower. The girders between +the points on the tower will represent two-qubit gates, with the final output of our +variational circuit coming at the very peak of the tower. + +Let's get to it! + +.. note:: + + To run this demo locally, you will need to install `Cirq + `_, (version >= 0.9.1), and the + `PennyLane-cirq plugin `_ + (version >= 0.13). You will also need to download a copy of the data, which + is available `here + `_. + +""" + +############################################################################## +# Building the Eiffel tower +# ------------------------- +# +# Our first step will be to load and visualize the data for the Eiffel tower +# configuration, which was generously provided by the team at Pasqal. +# (If running locally, the line below should be updated with the local +# path where you have saved the downloaded data). + +import numpy as np +coords = np.loadtxt("../_static/demonstration_assets/pasqal/Eiffel_tower_data.dat") +xs = coords[:,0] +ys = coords[:,1] +zs = coords[:,2] + +import matplotlib.pyplot as plt +fig = plt.figure() +ax = fig.add_subplot(111, projection='3d') +ax.view_init(40, 15) +fig.subplots_adjust(left=0, right=1, bottom=0, top=1) +ax.set_xlim(-20, 20) +ax.set_ylim(-20, 20) +ax.set_zlim(-40, 10) +plt.axis('off') +ax.scatter(xs, ys, zs, c='g',alpha=0.3) +plt.show() + +############################################################################## +# This dataset contains 126 points. Each point represents a distinct +# neutral-atom qubit. Simulating this many qubits would be outside the +# reach of Cirq's built-in simulators, so for this demo, +# we will pare down to just 9 points, evenly spaced around the tower. +# These are highlighted in red below. +# + +fig = plt.figure() +ax = fig.add_subplot(111, projection='3d') +ax.view_init(40, 15) +fig.subplots_adjust(left=0, right=1, bottom=0, top=1) +ax.set_xlim(-20, 20) +ax.set_ylim(-20, 20) +ax.set_zlim(-40, 10) +plt.axis('off') +ax.scatter(xs, ys, zs, c='g', alpha=0.3) + +base_mask = [3, 7, 11, 15] +qubit_mask = [48, 51, 60, 63, 96, 97, 98, 99, 125] +input_coords = coords[base_mask] # we'll need this for a plot later +qubit_coords = coords[qubit_mask] + +subset_xs = qubit_coords[:, 0] +subset_ys = qubit_coords[:, 1] +subset_zs = qubit_coords[:, 2] +ax.scatter(subset_xs, subset_ys, subset_zs, c='r', alpha=1.0) +plt.show() + +############################################################################## +# Converting to Cirq qubits +# ------------------------- +# +# Our next step will be to convert these datapoints into objects that +# Cirq understands as qubits. For neutral-atom devices in Cirq, we can use the +# ``ThreeDQubit`` class, which carries information about the three-dimensional +# arrangement of the qubits. +# +# Now, neutral-atom devices come with some physical restrictions. +# Specifically, in a particular three-dimensional configuration, qubits that +# are too distant from one another can't easily interact. Instead, there is +# a notion of a *control radius;* any atoms which are within the system's +# control radius can interact with one another. Qubits separated by a +# distance larger than the control radius cannot interact. +# +# In order to allow our Eiffel tower qubits to interact with +# one another more easily, we will artificially scale some dimensions +# when placing the atoms. + +from cirq_pasqal import ThreeDQubit +xy_scale = 1.5 +z_scale = 0.75 +qubits = [ThreeDQubit(xy_scale * x, xy_scale * y, z_scale * z) + for x, y, z in qubit_coords] + +############################################################################## +# To simulate a neutral-atom quantum computation, we can use the +# ``"cirq.pasqal"`` device, available via the +# `PennyLane-Cirq plugin `_. +# We will need to provide this device with the ``ThreeDQubit`` object that we created +# above. We also need to instantiate the device with a fixed control radius. + +import pennylane as qml + +num_wires = len(qubits) +control_radius = 32.4 +dev = qml.device("cirq.pasqal", control_radius=control_radius, + qubits=qubits, wires=num_wires) + +############################################################################## +# Creating a quantum circuit +# -------------------------- +# +# We will now make a variational circuit out of the Eiffel tower configuration +# from above. Each of the 9 qubits we are using can be thought of +# as a single wire in a quantum circuit. We will cause these qubits to interact by applying +# a sequence of two-qubit gates. Specifically, the circuit consists of several +# stages: +# +# i. Input classical data is converted into quantum information at the first +# (lowest) vertical level of qubits. In this example, our classical data +# will be simple bit strings, which we can embed by using single-qubit +# bit flips (a simple +# `data-embedding `_ +# strategy). +# +# ii. For each corner of the tower, CNOTs are enacted between the first- +# and second-level qubits. +# +# iii. All qubits from the second level interact with a single "peak" qubit +# using a parametrized controlled-rotation operation. The free parameters +# of our variational circuit enter here. +# +# The output of our circuit is determined via a Pauli-Z measurement on +# the final "peak" qubit. +# +# That's a few things to keep track of, so let's show the circuit via a +# three-dimensional image: + +first_lvl_coords = qubit_coords[:4] +second_lvl_coords = qubit_coords[4:8] +peak_coords = qubit_coords[8] + +input_x, input_y, input_z = [input_coords[:, idx] + for idx in range(3)] +second_x, second_y, second_z = [first_lvl_coords[:, idx] + for idx in range(3)] +third_x, third_y, third_z = [second_lvl_coords[:, idx] + for idx in range(3)] +peak_x, peak_y, peak_z = peak_coords + +fig = plt.figure() +ax = fig.add_subplot(111, projection='3d') +ax.view_init(40, 15) +fig.subplots_adjust(left=0, right=1, bottom=0, top=1) +ax.set_xlim(-20, 20) +ax.set_ylim(-20, 20) +ax.set_zlim(-40, 10) +plt.axis('off') + +ax.scatter(xs, ys, zs, c='g', alpha=0.3) +ax.scatter(subset_xs, subset_ys, subset_zs, c='r', alpha=1.0); + +# Two-qubit gates between second and third levels +for corner in range(4): + ax.plot(xs=[second_x[corner], third_x[corner]], + ys=[second_y[corner], third_y[corner]], + zs=[second_z[corner], third_z[corner]], + c='k'); + +# Two-qubit gates between third level and peak +for corner in range(4): + ax.plot(xs=[third_x[corner], peak_x], + ys=[third_y[corner], peak_y], + zs=[third_z[corner], peak_z], + c='k'); + +# Additional lines to guide the eye +for corner in range(4): + ax.plot(xs=[input_x[corner], second_x[corner]], + ys=[input_y[corner], second_y[corner]], + zs=[input_z[corner], second_z[corner]], + c='grey', linestyle='--'); + ax.plot(xs=[second_x[corner], second_x[(corner + 1) % 4]], + ys=[second_y[corner], second_y[(corner + 1) % 4]], + zs=[second_z[corner], second_z[(corner + 1) % 4]], + c='grey', linestyle='--'); + ax.plot(xs=[third_x[corner], third_x[(corner + 1) % 4]], + ys=[third_y[corner], third_y[(corner + 1) % 4]], + zs=[third_z[corner], third_z[(corner + 1) % 4]], + c='grey', linestyle='--'); + +plt.show() + +############################################################################## +# In this figure, the red dots represent the specific qubits we will use in +# our circuit (the green dots are not used in this demo). +# +# The solid black lines indicate two-qubit gates between these qubits. +# The dashed grey lines are meant to guide the eye, but could also be +# used to make a more complex model by adding further two-qubit gates. +# +# Classical data is loaded in at the bottom qubits (the "tower legs") and +# the final measurement result is read out from the top "peak" qubit. +# The order of gate execution proceeds vertically from bottom to top, and +# clockwise at each level. +# +# The code below creates this particular quantum circuit configuration in +# PennyLane: + +peak_qubit = 8 + +def controlled_rotation(phi, wires): + qml.RY(phi, wires=wires[1]) + qml.CNOT(wires=wires) + qml.RY(-phi, wires=wires[1]) + qml.CNOT(wires=wires) + +@qml.qnode(dev, interface="tf") +def circuit(weights, data): + + # Input classical data loaded into qubits at second level + for idx in range(4): + if data[idx]: + qml.PauliX(wires=idx) + + # Interact qubits from second and third levels + for idx in range(4): + qml.CNOT(wires=[idx, idx + 4]) + + # Interact qubits from third level with peak using parameterized gates + for idx, wire in enumerate(range(4, 8)): + controlled_rotation(weights[idx], wires=[wire, peak_qubit]) + + return qml.expval(qml.PauliZ(wires=peak_qubit)) + + +############################################################################## +# Training the circuit +# -------------------- +# +# Let's now leverage this variational circuit to tackle a toy classification +# problem. +# For the purposes of this demo, we will consider a very simple classifier: +# +# * if the first input qubit is in the state :math:`\mid 0 \rangle,` the model +# should make the prediction "0", and +# +# * if the first input qubit is in the state :math:`\mid 1 \rangle,` the model +# should predict "1" (independent of the states of all other qubits). +# +# In other words, the idealized trained model should learn an +# identity transformation between the first qubit and the final one, while +# ignoring the states of all other qubits. +# +# With this goal in mind, we can create a basic cost function. This cost +# function randomly samples possible 4-bit input bitstrings, and compares +# the circuit's output with the value of the first bit. The other bits +# can be thought of as noise that we don't want our model to learn. + + +import tensorflow as tf +np.random.seed(143) +init_weights = np.pi * np.random.rand(4) + +weights = tf.Variable(init_weights, dtype=tf.float64) + +data = np.random.randint(0, 2, size=4) + +def cost(): + data = np.random.randint(0, 2, size=4) + label = data[0] + output = (-circuit(weights, data) + 1) / 2 + return tf.abs(output - label) ** 2 + +opt = tf.keras.optimizers.Adam(learning_rate=0.1) + +for step in range(100): + opt.minimize(cost, [weights]) + if step % 5 == 0: + print("Step {}: cost={}".format(step, cost())) + +print("Final cost value: {}".format(cost())) + +############################################################################## +# Success! The circuit has learned to transfer the state of the first qubit +# to the state of the last qubit, while ignoring the state of all other input +# qubits. +# +# The programmable three-dimensional configurations of neutral-atom quantum +# computers provide a special tool that is hard to replicate in other +# platforms. Could the physical +# arrangement of qubits, in particular the third dimension, be leveraged to +# make quantum algorithms more sparse or efficient? Could neutral-atom +# systems—with their unique programmability of the geometry—allow us to +# rapidly prototype and experiment with new circuit topologies? What +# possibilities could this open up for quantum computing, quantum chemistry, +# or quantum machine learning? +# + +############################################################################## +# References +# ---------- +# +# .. [#barredo2017] +# +# Daniel Barredo, Vincent Lienhard, Sylvain de Leseleuc, Thierry Lahaye, and Antoine Browaeys. +# "Synthetic three-dimensional atomic structures assembled atom by atom." +# `arXiv:1712.02727 +# `__, 2017. +# +# +# About the author +# ---------------- # \ No newline at end of file diff --git a/demonstrations_v2/tutorial_qchem_external/demo.py b/demonstrations_v2/tutorial_qchem_external/demo.py index ae25f561bf..36bc3094f3 100644 --- a/demonstrations_v2/tutorial_qchem_external/demo.py +++ b/demonstrations_v2/tutorial_qchem_external/demo.py @@ -1,230 +1,230 @@ -r""" - -Using PennyLane with PySCF and OpenFermion -========================================== - -.. meta:: - :property="og:description": Learn how to integrate external quantum chemistry libraries with PennyLane. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_external_libs.png - - -.. related:: - tutorial_quantum_chemistry Quantum chemistry with PennyLane - tutorial_vqe A brief overview of VQE - tutorial_givens_rotations Givens rotations for quantum chemistry - tutorial_adaptive_circuits Adaptive circuits for quantum chemistry - -*Author: Soran Jahangiri — Posted: 3 January 2023.* - -The quantum chemistry module in PennyLane, :mod:`qchem `, provides built-in -methods to compute molecular integrals, solve Hartree-Fock equations, and construct -`fully-differentiable `_ molecular -Hamiltonians. PennyLane also lets you take advantage of various -external resources and libraries to build upon existing tools. In this demo we will show you how -to integrate PennyLane with `PySCF `_ and -`OpenFermion `_ to compute molecular integrals, -construct molecular Hamiltonians, and import initial states. - -Building molecular Hamiltonians -------------------------------- -In PennyLane, Hamiltonians for quantum chemistry are built with the -:func:`~.pennylane.qchem.molecular_hamiltonian` function by specifying a backend for solving the -Hartree–Fock equations. The default backend is the differentiable Hartree–Fock solver of the -:mod:`qchem ` module. A molecular Hamiltonian can also be constructed with -non-differentiable backends that use the electronic structure package -`PySCF `_ or the -`OpenFermion-PySCF `_ plugin. These -backends can be selected by setting the keyword argument ``method='pyscf'`` or -``method='openfermion'`` in :func:`~.pennylane.qchem.molecular_hamiltonian`. This requires -``PySCF`` or ``OpenFermion-PySCF`` to be installed by the user depending on the desired backend: - -.. code-block:: bash - - pip install pyscf # for method='pyscf` - pip install openfermionpyscf # for method='openfermion` - -For example, the molecular Hamiltonian for a water molecule can be constructed with the ``pyscf`` -backend as: -""" - -import pennylane as qml -import numpy as np - -symbols = ["H", "O", "H"] -geometry = np.array([[-0.0399, -0.0038, 0.0000], - [ 1.5780, 0.8540, 0.0000], - [ 2.7909, -0.5159, 0.0000]]) -molecule = qml.qchem.Molecule(symbols, geometry) - -H, qubits = qml.qchem.molecular_hamiltonian(molecule, method="pyscf") -print(H) - -############################################################################## -# This generates a PennyLane :class:`~.pennylane.Hamiltonian` that can be used in a VQE workflow or -# converted to a -# `sparse matrix `_ -# in the computational basis. -# -# Additionally, if you have built your electronic Hamiltonian independently using -# `OpenFermion `_ tools, it can -# be readily converted to a PennyLane observable using the -# :func:`~.pennylane.import_operator` function. Here is an example: - -from openfermion.ops import QubitOperator - -H = 0.1 * QubitOperator('X0 X1') + 0.2 * QubitOperator('Z0') -H = qml.qchem.import_operator(H) - -print(f'Type: \n {type(H)} \n') -print(f'Hamiltonian: \n {H}') - -############################################################################## -# Computing molecular integrals -# ----------------------------- -# In order to build a -# `molecular Hamiltonian `_, we need -# one- and two-electron integrals in the molecular orbital basis. These integrals are used to -# construct a fermionic Hamiltonian which is then mapped onto the qubit basis. These molecular -# integrals can be computed with the -# :func:`~.pennylane.qchem.electron_integrals` function of PennyLane. Alternatively, the integrals -# can be computed with the `PySCF `_ package and used in PennyLane -# workflows such as building a -# `fermionic Hamiltonian `_ or -# quantum `resource estimation `_. -# Let's use water as an example. -# -# First, we define the PySCF molecule object and run a restricted Hartree-Fock -# calculation: - -from pyscf import gto, ao2mo, scf - -mol_pyscf = gto.M(atom = '''H -0.02111417 -0.00201087 0.; - O 0.83504162 0.45191733 0.; - H 1.47688065 -0.27300252 0.''') -rhf = scf.RHF(mol_pyscf) -energy = rhf.kernel() - -############################################################################## -# We obtain the molecular integrals ``one_ao`` and ``two_ao`` in the basis of atomic orbitals -# by following the example `here `_: - -one_ao = mol_pyscf.intor_symmetric('int1e_kin') + mol_pyscf.intor_symmetric('int1e_nuc') -two_ao = mol_pyscf.intor('int2e_sph') - -############################################################################## -# These integrals are then mapped to the basis of molecular orbitals: - -one_mo = np.einsum('pi,pq,qj->ij', rhf.mo_coeff, one_ao, rhf.mo_coeff) -two_mo = ao2mo.incore.full(two_ao, rhf.mo_coeff) - -############################################################################## -# Note that the two-electron integral tensor is represented in -# `chemists' notation `_. To use it -# in PennyLane, we need to convert it into the so-called -# *physicists' notation*: - -two_mo = np.swapaxes(two_mo, 1, 3) - -############################################################################## -# Let's now look at an example where these molecular integrals are used to build the fermionic -# Hamiltonian of water. To do that we also need to compute the nuclear energy contribution: - -core_constant = np.array([rhf.energy_nuc()]) - -############################################################################## -# We now use the integrals to construct a fermionic Hamiltonian with PennyLane's powerful tools -# for creating and manipulating -# `fermionic operators `_: - -H_fermionic = qml.qchem.fermionic_observable(core_constant, one_mo, two_mo) - -############################################################################## -# The Hamiltonian can be mapped to the qubit basis with the :func:`~.pennylane.jordan_wigner` -# function: - -H = qml.jordan_wigner(H_fermionic) - -############################################################################## -# Importing initial states -# ------------------------ -# Simulating molecules with quantum algorithms requires defining an initial state that should have -# non-zero overlap with the molecular ground state. A trivial choice for the initial state is the -# Hartree-Fock state which is obtained by putting the electrons in the lowest-energy molecular -# orbitals. For molecules with a complicated electronic structure, the Hartree-Fock state has -# only a small overlap with the ground state, which makes executing quantum algorithms -# inefficient. -# -# Initial states obtained from affordable post-Hartree-Fock calculations can be used to make the -# quantum workflow more performant. For instance, configuration interaction (CI) and coupled cluster -# (CC) calculations with single and double (SD) excitations can be performed using PySCF and the -# resulting wave function can be used as the initial state in the quantum algorithm. PennyLane -# provides the :func:`~.pennylane.qchem.import_state` function that takes a PySCF solver object, -# extracts the wave function and returns a state vector in the computational basis that can be used -# in a quantum circuit. Let’s look at an example. -# -# First, we run CCSD calculations for the hydrogen molecule to obtain the solver object. - -from pyscf import gto, scf, cc - -mol = gto.M(atom=[['H', (0, 0, 0)], ['H', (0, 0, 0.7)]]) -myhf = scf.RHF(mol).run() -mycc = cc.CCSD(myhf).run() - -############################################################################## -# Then, we use the :func:`~.pennylane.qchem.import_state` function to obtain the -# state vector. - -state = qml.qchem.import_state(mycc) -print(state) - -############################################################################## -# You can verify that this state is a superposition of the Hartree-Fock state and a doubly-excited -# state. -# -# Converting fermionic operators -# ------------------------------ -# Fermionic operators are commonly used to construct observables for molecules and spin systems. -# You can easily convert between fermionic operators created with PennyLane and OpenFermion by using -# the :func:`~.pennylane.from_openfermion` and :func:`~.pennylane.to_openfermion` functions. Let's -# look at some examples. First, we create a fermionic operator with OpenFermion and convert it to a -# PennyLane fermionic operator. - -from openfermion import FermionOperator -openfermion_op = 0.5 * FermionOperator('0^ 2') + FermionOperator('0 2^') -pennylane_op = qml.from_openfermion(openfermion_op) -print(pennylane_op) - -############################################################################## -# The resulting operator can be used in PennyLane like any other fermionic object. We now take this -# PennyLane fermionic operator and convert it back to an OpenFermion operator. - -openfermion_op = qml.to_openfermion(pennylane_op) -print(openfermion_op) - -############################################################################## -# The :func:`~.pennylane.from_openfermion` and :func:`~.pennylane.to_openfermion` functions support -# converting several operator types. You can look at the function documentations for more details -# and examples. - -############################################################################## -# Conclusions -# ----------- -# This tutorial demonstrates how to use PennyLane with external quantum chemistry libraries such as -# `PySCF `_ and -# `OpenFermion `_. -# -# To summarize: -# -# 1. We can construct molecular Hamiltonians in PennyLane by using a user-installed version of PySCF -# by passing the argument ``method=pyscf`` to the :func:`~.pennylane.qchem.molecular_hamiltonian` -# function. -# 2. We can directly use one- and two-electron integrals from PySCF, but we need to convert the -# tensor containing the two-electron integrals from chemists' notation to physicists' notation. -# 3. We can easily convert between OpenFermion operators and PennyLane operators using the -# :func:`~.pennylane.from_openfermion` and :func:`~.pennylane.to_openfermion` functions. -# 4. Finally, we can convert PySCF wave functions to PennyLane state vectors using the -# :func:`~.pennylane.qchem.import_state` function. -# -# About the author -# ---------------- -# +r""" + +Using PennyLane with PySCF and OpenFermion +========================================== + +.. meta:: + :property="og:description": Learn how to integrate external quantum chemistry libraries with PennyLane. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_external_libs.png + + +.. related:: + tutorial_quantum_chemistry Quantum chemistry with PennyLane + tutorial_vqe A brief overview of VQE + tutorial_givens_rotations Givens rotations for quantum chemistry + tutorial_adaptive_circuits Adaptive circuits for quantum chemistry + +*Author: Soran Jahangiri — Posted: 3 January 2023.* + +The quantum chemistry module in PennyLane, :mod:`qchem `, provides built-in +methods to compute molecular integrals, solve Hartree-Fock equations, and construct +`fully-differentiable `_ molecular +Hamiltonians. PennyLane also lets you take advantage of various +external resources and libraries to build upon existing tools. In this demo we will show you how +to integrate PennyLane with `PySCF `_ and +`OpenFermion `_ to compute molecular integrals, +construct molecular Hamiltonians, and import initial states. + +Building molecular Hamiltonians +------------------------------- +In PennyLane, Hamiltonians for quantum chemistry are built with the +:func:`~.pennylane.qchem.molecular_hamiltonian` function by specifying a backend for solving the +Hartree–Fock equations. The default backend is the differentiable Hartree–Fock solver of the +:mod:`qchem ` module. A molecular Hamiltonian can also be constructed with +non-differentiable backends that use the electronic structure package +`PySCF `_ or the +`OpenFermion-PySCF `_ plugin. These +backends can be selected by setting the keyword argument ``method='pyscf'`` or +``method='openfermion'`` in :func:`~.pennylane.qchem.molecular_hamiltonian`. This requires +``PySCF`` or ``OpenFermion-PySCF`` to be installed by the user depending on the desired backend: + +.. code-block:: bash + + pip install pyscf # for method='pyscf` + pip install openfermionpyscf # for method='openfermion` + +For example, the molecular Hamiltonian for a water molecule can be constructed with the ``pyscf`` +backend as: +""" + +import pennylane as qml +import numpy as np + +symbols = ["H", "O", "H"] +geometry = np.array([[-0.0399, -0.0038, 0.0000], + [ 1.5780, 0.8540, 0.0000], + [ 2.7909, -0.5159, 0.0000]]) +molecule = qml.qchem.Molecule(symbols, geometry) + +H, qubits = qml.qchem.molecular_hamiltonian(molecule, method="pyscf") +print(H) + +############################################################################## +# This generates a PennyLane :class:`~.pennylane.Hamiltonian` that can be used in a VQE workflow or +# converted to a +# `sparse matrix `_ +# in the computational basis. +# +# Additionally, if you have built your electronic Hamiltonian independently using +# `OpenFermion `_ tools, it can +# be readily converted to a PennyLane observable using the +# :func:`~.pennylane.import_operator` function. Here is an example: + +from openfermion.ops import QubitOperator + +H = 0.1 * QubitOperator('X0 X1') + 0.2 * QubitOperator('Z0') +H = qml.qchem.import_operator(H) + +print(f'Type: \n {type(H)} \n') +print(f'Hamiltonian: \n {H}') + +############################################################################## +# Computing molecular integrals +# ----------------------------- +# In order to build a +# `molecular Hamiltonian `_, we need +# one- and two-electron integrals in the molecular orbital basis. These integrals are used to +# construct a fermionic Hamiltonian which is then mapped onto the qubit basis. These molecular +# integrals can be computed with the +# :func:`~.pennylane.qchem.electron_integrals` function of PennyLane. Alternatively, the integrals +# can be computed with the `PySCF `_ package and used in PennyLane +# workflows such as building a +# `fermionic Hamiltonian `_ or +# quantum `resource estimation `_. +# Let's use water as an example. +# +# First, we define the PySCF molecule object and run a restricted Hartree-Fock +# calculation: + +from pyscf import gto, ao2mo, scf + +mol_pyscf = gto.M(atom = '''H -0.02111417 -0.00201087 0.; + O 0.83504162 0.45191733 0.; + H 1.47688065 -0.27300252 0.''') +rhf = scf.RHF(mol_pyscf) +energy = rhf.kernel() + +############################################################################## +# We obtain the molecular integrals ``one_ao`` and ``two_ao`` in the basis of atomic orbitals +# by following the example `here `_: + +one_ao = mol_pyscf.intor_symmetric('int1e_kin') + mol_pyscf.intor_symmetric('int1e_nuc') +two_ao = mol_pyscf.intor('int2e_sph') + +############################################################################## +# These integrals are then mapped to the basis of molecular orbitals: + +one_mo = np.einsum('pi,pq,qj->ij', rhf.mo_coeff, one_ao, rhf.mo_coeff) +two_mo = ao2mo.incore.full(two_ao, rhf.mo_coeff) + +############################################################################## +# Note that the two-electron integral tensor is represented in +# `chemists' notation `_. To use it +# in PennyLane, we need to convert it into the so-called +# *physicists' notation*: + +two_mo = np.swapaxes(two_mo, 1, 3) + +############################################################################## +# Let's now look at an example where these molecular integrals are used to build the fermionic +# Hamiltonian of water. To do that we also need to compute the nuclear energy contribution: + +core_constant = np.array([rhf.energy_nuc()]) + +############################################################################## +# We now use the integrals to construct a fermionic Hamiltonian with PennyLane's powerful tools +# for creating and manipulating +# `fermionic operators `_: + +H_fermionic = qml.qchem.fermionic_observable(core_constant, one_mo, two_mo) + +############################################################################## +# The Hamiltonian can be mapped to the qubit basis with the :func:`~.pennylane.jordan_wigner` +# function: + +H = qml.jordan_wigner(H_fermionic) + +############################################################################## +# Importing initial states +# ------------------------ +# Simulating molecules with quantum algorithms requires defining an initial state that should have +# non-zero overlap with the molecular ground state. A trivial choice for the initial state is the +# Hartree-Fock state which is obtained by putting the electrons in the lowest-energy molecular +# orbitals. For molecules with a complicated electronic structure, the Hartree-Fock state has +# only a small overlap with the ground state, which makes executing quantum algorithms +# inefficient. +# +# Initial states obtained from affordable post-Hartree-Fock calculations can be used to make the +# quantum workflow more performant. For instance, configuration interaction (CI) and coupled cluster +# (CC) calculations with single and double (SD) excitations can be performed using PySCF and the +# resulting wave function can be used as the initial state in the quantum algorithm. PennyLane +# provides the :func:`~.pennylane.qchem.import_state` function that takes a PySCF solver object, +# extracts the wave function and returns a state vector in the computational basis that can be used +# in a quantum circuit. Let’s look at an example. +# +# First, we run CCSD calculations for the hydrogen molecule to obtain the solver object. + +from pyscf import gto, scf, cc + +mol = gto.M(atom=[['H', (0, 0, 0)], ['H', (0, 0, 0.7)]]) +myhf = scf.RHF(mol).run() +mycc = cc.CCSD(myhf).run() + +############################################################################## +# Then, we use the :func:`~.pennylane.qchem.import_state` function to obtain the +# state vector. + +state = qml.qchem.import_state(mycc) +print(state) + +############################################################################## +# You can verify that this state is a superposition of the Hartree-Fock state and a doubly-excited +# state. +# +# Converting fermionic operators +# ------------------------------ +# Fermionic operators are commonly used to construct observables for molecules and spin systems. +# You can easily convert between fermionic operators created with PennyLane and OpenFermion by using +# the :func:`~.pennylane.from_openfermion` and :func:`~.pennylane.to_openfermion` functions. Let's +# look at some examples. First, we create a fermionic operator with OpenFermion and convert it to a +# PennyLane fermionic operator. + +from openfermion import FermionOperator +openfermion_op = 0.5 * FermionOperator('0^ 2') + FermionOperator('0 2^') +pennylane_op = qml.from_openfermion(openfermion_op) +print(pennylane_op) + +############################################################################## +# The resulting operator can be used in PennyLane like any other fermionic object. We now take this +# PennyLane fermionic operator and convert it back to an OpenFermion operator. + +openfermion_op = qml.to_openfermion(pennylane_op) +print(openfermion_op) + +############################################################################## +# The :func:`~.pennylane.from_openfermion` and :func:`~.pennylane.to_openfermion` functions support +# converting several operator types. You can look at the function documentations for more details +# and examples. + +############################################################################## +# Conclusions +# ----------- +# This tutorial demonstrates how to use PennyLane with external quantum chemistry libraries such as +# `PySCF `_ and +# `OpenFermion `_. +# +# To summarize: +# +# 1. We can construct molecular Hamiltonians in PennyLane by using a user-installed version of PySCF +# by passing the argument ``method=pyscf`` to the :func:`~.pennylane.qchem.molecular_hamiltonian` +# function. +# 2. We can directly use one- and two-electron integrals from PySCF, but we need to convert the +# tensor containing the two-electron integrals from chemists' notation to physicists' notation. +# 3. We can easily convert between OpenFermion operators and PennyLane operators using the +# :func:`~.pennylane.from_openfermion` and :func:`~.pennylane.to_openfermion` functions. +# 4. Finally, we can convert PySCF wave functions to PennyLane state vectors using the +# :func:`~.pennylane.qchem.import_state` function. +# +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_qft_arithmetics/demo.py b/demonstrations_v2/tutorial_qft_arithmetics/demo.py index 3d67048454..92929830f5 100644 --- a/demonstrations_v2/tutorial_qft_arithmetics/demo.py +++ b/demonstrations_v2/tutorial_qft_arithmetics/demo.py @@ -1,433 +1,433 @@ -r""".. _qft_arithmetics: - -Basic arithmetic with the quantum Fourier transform (QFT) -======================================= - -.. meta:: - :property="og:description": Learn how to use the quantum Fourier transform (QFT) to do basic arithmetic - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qft_arithmetics_thumbnail.png - -.. related:: - tutorial_qubit_rotation Basis tutorial: qubit rotation - - - -*Author: Guillermo Alonso-Linaje — Posted: 07 November 2022.* - -Arithmetic is a fundamental branch of mathematics that consists of the study of the main operations with numbers such as -addition, multiplication, subtraction and division. Using arithmetic operations we can understand the world around us -and solve many of our daily tasks. - -Arithmetic is crucial for the implementation of any kind of algorithm in classical computer science, but also in quantum computing. For this reason, in this demo we are going to show -an approach to defining arithmetic operations on quantum computers. The simplest and most direct way to achieve this goal is to use the -quantum Fourier transform (QFT), which we will demonstrate on a basic level. - -In this demo we will not focus on understanding how the QFT is built, -as we can find a great explanation in the -`PennyLane Codebook `__. Instead, we will develop the -intuition for how it works and how we can best take advantage of it. - -Motivation ----------- - -The first question we have to ask ourselves is whether it makes sense to perform these basic operations on a quantum computer. Is the -goal purely academic or is it really something that is needed in certain algorithms? Why implement in a quantum computer -something that we can do with a calculator? - -When it comes to basic quantum computing algorithms like the Deustch–Jozsa or -Grover's algorithm, we might think that we have never needed to apply any arithmetic operations such as addition or multiplication. -However, the reality is different. When we learn about these algorithms from an academic point of view, -we work with a ready-made operator that we never have to worry about, the *oracle*. -Unfortunately, in real-world situations, we will have to build this seemingly magical operator by hand. -As an example, let's imagine that we want to use Grover's algorithm to search for `magic squares `__. -To define the oracle, which determines whether a solution is valid or not, we must perform sums over the rows and -columns to check that they all have the same value. Therefore, to create this oracle, -we will need to define a sum operator within the quantum computer. - -The second question we face is why we should work with the QFT at all. There are other procedures that could be used to perform these basic operations; for example, by -imitating the classical algorithm. But, as we can see in [#Draper2000]_, it has already been proven that the QFT needs fewer qubits to perform these operations, which is -nowadays of vital importance. - -We will organize the demo as follows. Initially, we will talk about the Fourier basis to give an intuitive idea of how -it works, after which we will address different basic arithmetic operations. Finally, we will move on to a practical example -in which we will factor numbers using Grover's algorithm. - - -QFT representation ------------------ - -To apply the QFT to basic arithmetic operations, our objective now is to learn how to add, -subtract and multiply numbers using quantum devices. As we are working with qubits, -—which, like bits, can take the -values :math:`0` or :math:`1`—we will represent the numbers in binary. For the -purposes of this tutorial, we will assume that we are working only with -integers. Therefore, if we have :math:`n` qubits, we will be able to -represent the numbers from :math:`0` to :math:`2^n-1.` - -The first thing we need to know is PennyLane's -standard for encoding numbers in a binary format. A binary number can be -represented as a string of 1s and 0s, which we can represent as the multi-qubit state - -.. math:: \vert m \rangle = \vert \overline{q_0q_1...q_{n-1}}\rangle, - -where the formula to obtain the equivalent decimal number :math:`m` will be: - -.. math:: m= \sum_{i = 0}^{n-1}2^{n-1-i}q_i. - -Note that :math:`\vert m \rangle` refers to the basic state -generated by the binary encoding of the number :math:`m.` -For instance, the natural number :math:`6` -is represented by the quantum state :math:`\vert 110\rangle,` since :math:`\vert 110 \rangle = 1 \cdot 2^2 + 1\cdot 2^1+0\cdot 2^0 = 6.` - -Let’s see how we would represent all the integers from :math:`0` to :math:`7` using the product state of three qubits, visualized by separate Bloch spheres for each qubit. - -.. figure:: /_static/demonstration_assets/qft_arithmetics/3_qubits_computational_basis.gif - :width: 90% - :align: center - - Representation of integers using a computational basis of three qubits. - -.. note:: - - The `Bloch sphere `_ - is a way of graphically representing the state of a qubit. - At the top of the sphere we place the state :math:`\vert 0 \rangle,` at the bottom - :math:`\vert 1 \rangle,` and in the rest of the - sphere we will place the possible states in superposition. It is a very useful - representation that helps better visualize and interpret quantum gates such as rotations. - -We can use -the :class:`qml.BasisEmbedding ` -template to obtain the binary representation in a simple way. -Let's see how we would code the number :math:`6.` -""" - -import pennylane as qml -import matplotlib.pyplot as plt - -dev = qml.device("default.qubit", wires=3) - -@qml.compile -@qml.qnode(dev) -def basis_embedding_circuit(m): - qml.BasisEmbedding(m, wires=range(3)) - return qml.state() - -m = 6 # number to be encoded - -qml.draw_mpl(basis_embedding_circuit, show_all_wires=True)(m) -plt.show() - -###################################################################### -# -# As we can see, the first qubit—the :math:`0`-th wire—is placed on top and the rest of the qubits are -# below it. However, this is not the only way we could represent numbers. -# We can also represent them in different bases, such as the so-called *Fourier base*. -# -# In this case, all the states of the basis will be represented via qubits in -# the XY-plane of the Bloch sphere, each rotated by a certain -# amount. -# -# -# How do we know how much we must rotate each qubit to represent a certain number? -# It is actually very easy! Suppose we are working with -# :math:`n` qubits and we want to represent the number :math:`m` in the -# Fourier basis. Then the :math:`j`-th qubit will have the phase: -# -# .. math:: \alpha_j = \frac{2m\pi}{2^{j}}. -# -# Now we can represent numbers in the Fourier basis using three qubits: -# -# .. figure:: /_static/demonstration_assets/qft_arithmetics/3_qubits_fourier_basis.gif -# :width: 90% -# :align: center -# -# Representation of integers using the Fourier basis with three qubits -# -# As we can see, the third qubit will rotate -# :math:`\frac{1}{8}` of a turn counterclockwise with each number. The next qubit -# rotates :math:`\frac{1}{4}` of a full turn and, finally, the first qubit rotates -# half a turn for each increase in number. -# -# Adding a number to a register -# ------------------------------ -# -# The fact that the states encoding the numbers are now in phase gives us great -# flexibility in carrying out our arithmetic operations. To see this in practice, -# let’s look at the situation in which want to create an operator Sum -# such that: -# -# .. math:: \text{Sum(k)}\vert m \rangle = \vert m + k \rangle. -# -# The procedure to implement this unitary operation is the following: -# -# #. We convert the state from the computational basis into the Fourier basis by applying the QFT to the :math:`\vert m \rangle` state via the :class:`~pennylane.QFT` operator. -# -# #. We rotate the :math:`j`-th qubit by the angle :math:`\frac{2k\pi}{2^{j}}` using the :math:`R_Z` gate, which leads to the new phases, :math:`\frac{2(m + k)\pi}{2^{j}}.` -# -# #. We apply the QFT inverse to return to the computational basis and obtain :math:`m+k.` -# -# -# Let's see how this process would look in PennyLane. -# - -import pennylane as qml -import numpy as np - -n_wires = 4 -dev = qml.device("default.qubit", wires=n_wires, shots=1) - -def add_k_fourier(k, wires): - for j in range(len(wires)): - qml.RZ(k * np.pi / (2**j), wires=wires[j]) - -@qml.qnode(dev) -def sum(m, k): - qml.BasisEmbedding(m, wires=range(n_wires)) # m encoding - - qml.QFT(wires=range(n_wires)) # step 1 - - add_k_fourier(k, range(n_wires)) # step 2 - - qml.adjoint(qml.QFT)(wires=range(n_wires)) # step 3 - - return qml.sample() - - -print(f"The ket representation of the sum of 3 and 4 is {sum(3,4)}") - -###################################################################### -# Perfect, we have obtained :math:`\vert 0111 \rangle,` which is equivalent to the number :math:`7` in binary! -# -# Note that this is a deterministic algorithm, which means that we have obtained the desired solution by executing a single shot. -# On the other hand, if the result of an operation is greater than the maximum -# value :math:`2^n-1,` we will start again from zero, that is to say, we -# will calculate the sum modulo :math:`2^n-1.` For instance, in our three-qubit example, suppose that -# we want to calculate :math:`6+3.` We see that we do not have -# enough memory space, as :math:`6+3 = 9 > 2^3-1.` The result we will get will -# be :math:`9 \pmod 8 = 1,` or :math:`\vert 001 \rangle` in binary. Make sure to use -# enough qubits to represent your solutions! -# Finally, it is important to point out that it is not necessary to know how the -# QFT is constructed in order to use it. By knowing the properties of the -# new basis, we can use it in a simple way. -# -# Adding two different registers -# ------------------------------ -# -# In this previous algorithm, we had to pass to the operator the value of :math:`k` that we wanted to add to the starting state. -# But at other times, instead of passing that value to the operator, we would like it to pull the information from another register. -# That is, we are looking for a new operator :math:`\text{Sum}_2` such that -# -# .. math:: \text{Sum}_2\vert m \rangle \vert k \rangle \vert 0 \rangle = \vert m \rangle \vert k \rangle \vert m+k \rangle. -# -# In this case, we can understand the third register (which is initially -# at :math:`0`) as a counter that will tally as many units as :math:`m` and -# :math:`k` combined. The binary decomposition will -# make this simple. If we have :math:`\vert m \rangle = \vert \overline{q_0q_1q_2} \rangle,` we will -# have to add :math:`1` to the counter if :math:`q_2 = 1` and nothing -# otherwise. In general, we should add :math:`2^{n-i-1}` units if the :math:`i`-th -# qubit is in state :math:`\vert 1 \rangle` and 0 otherwise. As we can see, this is the same idea that is also -# behind the concept of a controlled gate. Indeed, observe that we will, indeed, apply a corresponding -# phase if indeed the control qubit is in the state :math:`\vert 1\rangle.` -# Let us now code the :math:`\text{Sum}_2` operator. - -wires_m = [0, 1, 2] # qubits needed to encode m -wires_k = [3, 4, 5] # qubits needed to encode k -wires_solution = [6, 7, 8, 9] # qubits needed to encode the solution - -dev = qml.device("default.qubit", wires=wires_m + wires_k + wires_solution, shots=1) - -n_wires = len(dev.wires) # total number of qubits used - -def addition(wires_m, wires_k, wires_solution): - # prepare solution qubits to counting - qml.QFT(wires=wires_solution) - - # add m to the counter - for i in range(len(wires_m)): - qml.ctrl(add_k_fourier, control=wires_m[i])(2 **(len(wires_m) - i - 1), wires_solution) - - # add k to the counter - for i in range(len(wires_k)): - qml.ctrl(add_k_fourier, control=wires_k[i])(2 **(len(wires_k) - i - 1), wires_solution) - - # return to computational basis - qml.adjoint(qml.QFT)(wires=wires_solution) - -@qml.qnode(dev) -def sum2(m, k, wires_m, wires_k, wires_solution): - # m and k codification - qml.BasisEmbedding(m, wires=wires_m) - qml.BasisEmbedding(k, wires=wires_k) - - # apply the addition circuit - addition(wires_m, wires_k, wires_solution) - - return qml.sample(wires=wires_solution) - -print(f"The ket representation of the sum of 7 and 3 is " - f"{sum2(7, 3, wires_m, wires_k, wires_solution)}") - -qml.draw_mpl(sum2, show_all_wires=True)(7, 3, wires_m, wires_k, wires_solution) -plt.show() - -###################################################################### -# Great! We have just seen how to add a number to a counter. In the example above, -# we added :math:`3 + 7` to get :math:`10,` which in binary -# is :math:`\vert 1010 \rangle.` -# -# Multiplying qubits -# ------------------- -# -# Following the same idea, we will see how easily we can -# implement multiplication. For this purpose we'll take two arbitrary numbers :math:`m` and :math:`k` -# to carry out the operation. This time, we look for an operator Mul such that -# -# .. math:: \text{Mul}\vert m \rangle \vert k \rangle \vert 0 \rangle = \vert m \rangle \vert k \rangle \vert m\cdot k \rangle. -# -# To understand the multiplication process, let's work with the binary decomposition of -# :math:`k:=\sum_{i=0}^{n-1}2^{n-i-1}k_i` and -# :math:`m:=\sum_{j=0}^{l-1}2^{l-j-1}m_i.` In this case, the product would -# be: -# -# .. math:: k \cdot m = \sum_{i=0}^{n-1}\sum_{j = 0}^{l-1}m_ik_i (2^{n-i-1} \cdot 2^{l-j-1}). -# -# In other words, if :math:`k_i = 1` and :math:`m_i = 1,` we would add -# :math:`2^{n-i-1} \cdot 2^{l-j-1}` units to the counter, where :math:`n` and :math:`l` -# are the number of qubits with which we encode :math:`m` and :math:`k` respectively. -# Let's code to see how it works! - -wires_m = [0, 1, 2] # qubits needed to encode m -wires_k = [3, 4, 5] # qubits needed to encode k -wires_solution = [6, 7, 8, 9, 10] # qubits needed to encode the solution - -dev = qml.device("default.qubit", wires=wires_m + wires_k + wires_solution, shots=1) - -n_wires = len(dev.wires) - -def multiplication(wires_m, wires_k, wires_solution): - # prepare sol-qubits to counting - qml.QFT(wires=wires_solution) - - # add m to the counter - for i in range(len(wires_k)): - for j in range(len(wires_m)): - coeff = 2 ** (len(wires_m) + len(wires_k) - i - j - 2) - qml.ctrl(add_k_fourier, control=[wires_k[i], wires_m[j]])(coeff, wires_solution) - - # return to computational basis - qml.adjoint(qml.QFT)(wires=wires_solution) - -@qml.qnode(dev) -def mul(m, k): - # m and k codification - qml.BasisEmbedding(m, wires=wires_m) - qml.BasisEmbedding(k, wires=wires_k) - - # Apply multiplication - multiplication(wires_m, wires_k, wires_solution) - - return qml.sample(wires=wires_solution) - - -print(f"The ket representation of the multiplication of 3 and 7 is {mul(3,7)}") - -qml.draw_mpl(mul, show_all_wires=True)(3, 7) -plt.show() - - -###################################################################### -# Awesome! We have multiplied :math:`7 \cdot 3` and, as a result, we have -# :math:`\vert 10101 \rangle,` which is :math:`21` in binary. -# -# -# Factorization with Grover -# ------------------------- -# -# With this, we have already gained a large repertoire of interesting -# operations that we can do, but we can give the idea one more twist and -# apply what we have learned in an example. -# -# Let’s imagine now that we want just the opposite: to factor the -# number :math:`21` as a product of two terms. Is this something we could do -# using our previous reasoning? The answer is yes! We can make use of -# `Grover's algorithm `_ to -# amplify the states whose product is the number we -# are looking for. All we would need is to construct the oracle :math:`U,` i.e., an -# operator such that -# -# .. math:: U\vert m \rangle \vert k \rangle = \vert m \rangle \vert k \rangle \text{ if }m\cdot k \not = 21, -# -# .. math:: U\vert m \rangle \vert k \rangle = -\vert m \rangle \vert k \rangle \text{ if }m\cdot k = 21 -# -# The idea of the oracle is as simple as this: -# -# #. use auxiliary registers to store the product, -# #. check if the product state is :math:`\vert 10101 \rangle` and, in that case, change the sign, -# #. execute the inverse of the circuit to clear the auxiliary qubits. -# #. calculate the probabilities and see which states have been amplified. -# -# Let's go back to PennyLane to implement this idea. - -n = 21 # number we want to factor - -wires_m = [0, 1, 2] # qubits needed to encode m -wires_k = [3, 4, 5] # qubits needed to encode k -wires_solution = [6, 7, 8, 9, 10] # qubits needed to encode the solution - -dev = qml.device("default.qubit", wires=wires_m + wires_k + wires_solution) - -n_wires = len(dev.wires) - -@qml.qnode(dev) -def factorization(n, wires_m, wires_k, wires_solution): - # Superposition of the input - for wire in wires_m: - qml.Hadamard(wires=wire) - - for wire in wires_k: - qml.Hadamard(wires=wire) - - # Apply the multiplication - multiplication(wires_m, wires_k, wires_solution) - - # Change sign of n - qml.FlipSign(n, wires=wires_solution) - - # Uncompute multiplication - qml.adjoint(multiplication)(wires_m, wires_k, wires_solution) - - # Apply Grover operator - qml.GroverOperator(wires=wires_m + wires_k) - - return qml.probs(wires=wires_m) - - -plt.bar(range(2 ** len(wires_m)), factorization(n, wires_m, wires_k, wires_solution)) -plt.xlabel("Basic states") -plt.ylabel("Probability") -plt.show() - -###################################################################### -# By plotting the probabilities of obtaining each basic state we see that -# prime factors have been amplified! Factorization via Grover’s algorithm -# does not achieve exponential improvement that -# `Shor's algorithm `_ does, but we -# can see that this construction is simple and a great example to -# illustrate basic arithmetic! -# -# I hope we can now all see that oracles are not something magical and that there -# is a lot of work behind their construction! This will help us in the future to build -# more complicated operators, but until then, let’s keep on learning. 🚀 -# -# References -# ---------- -# -# .. [#Draper2000] -# -# Thomas G. Draper, "Addition on a Quantum Computer". `arXiv:quant-ph/0008033 `__. -# -# -# About the author -# ---------------- -# +r""".. _qft_arithmetics: + +Basic arithmetic with the quantum Fourier transform (QFT) +======================================= + +.. meta:: + :property="og:description": Learn how to use the quantum Fourier transform (QFT) to do basic arithmetic + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qft_arithmetics_thumbnail.png + +.. related:: + tutorial_qubit_rotation Basis tutorial: qubit rotation + + + +*Author: Guillermo Alonso-Linaje — Posted: 07 November 2022.* + +Arithmetic is a fundamental branch of mathematics that consists of the study of the main operations with numbers such as +addition, multiplication, subtraction and division. Using arithmetic operations we can understand the world around us +and solve many of our daily tasks. + +Arithmetic is crucial for the implementation of any kind of algorithm in classical computer science, but also in quantum computing. For this reason, in this demo we are going to show +an approach to defining arithmetic operations on quantum computers. The simplest and most direct way to achieve this goal is to use the +quantum Fourier transform (QFT), which we will demonstrate on a basic level. + +In this demo we will not focus on understanding how the QFT is built, +as we can find a great explanation in the +`PennyLane Codebook `__. Instead, we will develop the +intuition for how it works and how we can best take advantage of it. + +Motivation +---------- + +The first question we have to ask ourselves is whether it makes sense to perform these basic operations on a quantum computer. Is the +goal purely academic or is it really something that is needed in certain algorithms? Why implement in a quantum computer +something that we can do with a calculator? + +When it comes to basic quantum computing algorithms like the Deustch–Jozsa or +Grover's algorithm, we might think that we have never needed to apply any arithmetic operations such as addition or multiplication. +However, the reality is different. When we learn about these algorithms from an academic point of view, +we work with a ready-made operator that we never have to worry about, the *oracle*. +Unfortunately, in real-world situations, we will have to build this seemingly magical operator by hand. +As an example, let's imagine that we want to use Grover's algorithm to search for `magic squares `__. +To define the oracle, which determines whether a solution is valid or not, we must perform sums over the rows and +columns to check that they all have the same value. Therefore, to create this oracle, +we will need to define a sum operator within the quantum computer. + +The second question we face is why we should work with the QFT at all. There are other procedures that could be used to perform these basic operations; for example, by +imitating the classical algorithm. But, as we can see in [#Draper2000]_, it has already been proven that the QFT needs fewer qubits to perform these operations, which is +nowadays of vital importance. + +We will organize the demo as follows. Initially, we will talk about the Fourier basis to give an intuitive idea of how +it works, after which we will address different basic arithmetic operations. Finally, we will move on to a practical example +in which we will factor numbers using Grover's algorithm. + + +QFT representation +----------------- + +To apply the QFT to basic arithmetic operations, our objective now is to learn how to add, +subtract and multiply numbers using quantum devices. As we are working with qubits, +—which, like bits, can take the +values :math:`0` or :math:`1`—we will represent the numbers in binary. For the +purposes of this tutorial, we will assume that we are working only with +integers. Therefore, if we have :math:`n` qubits, we will be able to +represent the numbers from :math:`0` to :math:`2^n-1.` + +The first thing we need to know is PennyLane's +standard for encoding numbers in a binary format. A binary number can be +represented as a string of 1s and 0s, which we can represent as the multi-qubit state + +.. math:: \vert m \rangle = \vert \overline{q_0q_1...q_{n-1}}\rangle, + +where the formula to obtain the equivalent decimal number :math:`m` will be: + +.. math:: m= \sum_{i = 0}^{n-1}2^{n-1-i}q_i. + +Note that :math:`\vert m \rangle` refers to the basic state +generated by the binary encoding of the number :math:`m.` +For instance, the natural number :math:`6` +is represented by the quantum state :math:`\vert 110\rangle,` since :math:`\vert 110 \rangle = 1 \cdot 2^2 + 1\cdot 2^1+0\cdot 2^0 = 6.` + +Let’s see how we would represent all the integers from :math:`0` to :math:`7` using the product state of three qubits, visualized by separate Bloch spheres for each qubit. + +.. figure:: /_static/demonstration_assets/qft_arithmetics/3_qubits_computational_basis.gif + :width: 90% + :align: center + + Representation of integers using a computational basis of three qubits. + +.. note:: + + The `Bloch sphere `_ + is a way of graphically representing the state of a qubit. + At the top of the sphere we place the state :math:`\vert 0 \rangle,` at the bottom + :math:`\vert 1 \rangle,` and in the rest of the + sphere we will place the possible states in superposition. It is a very useful + representation that helps better visualize and interpret quantum gates such as rotations. + +We can use +the :class:`qml.BasisEmbedding ` +template to obtain the binary representation in a simple way. +Let's see how we would code the number :math:`6.` +""" + +import pennylane as qml +import matplotlib.pyplot as plt + +dev = qml.device("default.qubit", wires=3) + +@qml.compile +@qml.qnode(dev) +def basis_embedding_circuit(m): + qml.BasisEmbedding(m, wires=range(3)) + return qml.state() + +m = 6 # number to be encoded + +qml.draw_mpl(basis_embedding_circuit, show_all_wires=True)(m) +plt.show() + +###################################################################### +# +# As we can see, the first qubit—the :math:`0`-th wire—is placed on top and the rest of the qubits are +# below it. However, this is not the only way we could represent numbers. +# We can also represent them in different bases, such as the so-called *Fourier base*. +# +# In this case, all the states of the basis will be represented via qubits in +# the XY-plane of the Bloch sphere, each rotated by a certain +# amount. +# +# +# How do we know how much we must rotate each qubit to represent a certain number? +# It is actually very easy! Suppose we are working with +# :math:`n` qubits and we want to represent the number :math:`m` in the +# Fourier basis. Then the :math:`j`-th qubit will have the phase: +# +# .. math:: \alpha_j = \frac{2m\pi}{2^{j}}. +# +# Now we can represent numbers in the Fourier basis using three qubits: +# +# .. figure:: /_static/demonstration_assets/qft_arithmetics/3_qubits_fourier_basis.gif +# :width: 90% +# :align: center +# +# Representation of integers using the Fourier basis with three qubits +# +# As we can see, the third qubit will rotate +# :math:`\frac{1}{8}` of a turn counterclockwise with each number. The next qubit +# rotates :math:`\frac{1}{4}` of a full turn and, finally, the first qubit rotates +# half a turn for each increase in number. +# +# Adding a number to a register +# ------------------------------ +# +# The fact that the states encoding the numbers are now in phase gives us great +# flexibility in carrying out our arithmetic operations. To see this in practice, +# let’s look at the situation in which want to create an operator Sum +# such that: +# +# .. math:: \text{Sum(k)}\vert m \rangle = \vert m + k \rangle. +# +# The procedure to implement this unitary operation is the following: +# +# #. We convert the state from the computational basis into the Fourier basis by applying the QFT to the :math:`\vert m \rangle` state via the :class:`~pennylane.QFT` operator. +# +# #. We rotate the :math:`j`-th qubit by the angle :math:`\frac{2k\pi}{2^{j}}` using the :math:`R_Z` gate, which leads to the new phases, :math:`\frac{2(m + k)\pi}{2^{j}}.` +# +# #. We apply the QFT inverse to return to the computational basis and obtain :math:`m+k.` +# +# +# Let's see how this process would look in PennyLane. +# + +import pennylane as qml +import numpy as np + +n_wires = 4 +dev = qml.device("default.qubit", wires=n_wires, shots=1) + +def add_k_fourier(k, wires): + for j in range(len(wires)): + qml.RZ(k * np.pi / (2**j), wires=wires[j]) + +@qml.qnode(dev) +def sum(m, k): + qml.BasisEmbedding(m, wires=range(n_wires)) # m encoding + + qml.QFT(wires=range(n_wires)) # step 1 + + add_k_fourier(k, range(n_wires)) # step 2 + + qml.adjoint(qml.QFT)(wires=range(n_wires)) # step 3 + + return qml.sample() + + +print(f"The ket representation of the sum of 3 and 4 is {sum(3,4)}") + +###################################################################### +# Perfect, we have obtained :math:`\vert 0111 \rangle,` which is equivalent to the number :math:`7` in binary! +# +# Note that this is a deterministic algorithm, which means that we have obtained the desired solution by executing a single shot. +# On the other hand, if the result of an operation is greater than the maximum +# value :math:`2^n-1,` we will start again from zero, that is to say, we +# will calculate the sum modulo :math:`2^n-1.` For instance, in our three-qubit example, suppose that +# we want to calculate :math:`6+3.` We see that we do not have +# enough memory space, as :math:`6+3 = 9 > 2^3-1.` The result we will get will +# be :math:`9 \pmod 8 = 1,` or :math:`\vert 001 \rangle` in binary. Make sure to use +# enough qubits to represent your solutions! +# Finally, it is important to point out that it is not necessary to know how the +# QFT is constructed in order to use it. By knowing the properties of the +# new basis, we can use it in a simple way. +# +# Adding two different registers +# ------------------------------ +# +# In this previous algorithm, we had to pass to the operator the value of :math:`k` that we wanted to add to the starting state. +# But at other times, instead of passing that value to the operator, we would like it to pull the information from another register. +# That is, we are looking for a new operator :math:`\text{Sum}_2` such that +# +# .. math:: \text{Sum}_2\vert m \rangle \vert k \rangle \vert 0 \rangle = \vert m \rangle \vert k \rangle \vert m+k \rangle. +# +# In this case, we can understand the third register (which is initially +# at :math:`0`) as a counter that will tally as many units as :math:`m` and +# :math:`k` combined. The binary decomposition will +# make this simple. If we have :math:`\vert m \rangle = \vert \overline{q_0q_1q_2} \rangle,` we will +# have to add :math:`1` to the counter if :math:`q_2 = 1` and nothing +# otherwise. In general, we should add :math:`2^{n-i-1}` units if the :math:`i`-th +# qubit is in state :math:`\vert 1 \rangle` and 0 otherwise. As we can see, this is the same idea that is also +# behind the concept of a controlled gate. Indeed, observe that we will, indeed, apply a corresponding +# phase if indeed the control qubit is in the state :math:`\vert 1\rangle.` +# Let us now code the :math:`\text{Sum}_2` operator. + +wires_m = [0, 1, 2] # qubits needed to encode m +wires_k = [3, 4, 5] # qubits needed to encode k +wires_solution = [6, 7, 8, 9] # qubits needed to encode the solution + +dev = qml.device("default.qubit", wires=wires_m + wires_k + wires_solution, shots=1) + +n_wires = len(dev.wires) # total number of qubits used + +def addition(wires_m, wires_k, wires_solution): + # prepare solution qubits to counting + qml.QFT(wires=wires_solution) + + # add m to the counter + for i in range(len(wires_m)): + qml.ctrl(add_k_fourier, control=wires_m[i])(2 **(len(wires_m) - i - 1), wires_solution) + + # add k to the counter + for i in range(len(wires_k)): + qml.ctrl(add_k_fourier, control=wires_k[i])(2 **(len(wires_k) - i - 1), wires_solution) + + # return to computational basis + qml.adjoint(qml.QFT)(wires=wires_solution) + +@qml.qnode(dev) +def sum2(m, k, wires_m, wires_k, wires_solution): + # m and k codification + qml.BasisEmbedding(m, wires=wires_m) + qml.BasisEmbedding(k, wires=wires_k) + + # apply the addition circuit + addition(wires_m, wires_k, wires_solution) + + return qml.sample(wires=wires_solution) + +print(f"The ket representation of the sum of 7 and 3 is " + f"{sum2(7, 3, wires_m, wires_k, wires_solution)}") + +qml.draw_mpl(sum2, show_all_wires=True)(7, 3, wires_m, wires_k, wires_solution) +plt.show() + +###################################################################### +# Great! We have just seen how to add a number to a counter. In the example above, +# we added :math:`3 + 7` to get :math:`10,` which in binary +# is :math:`\vert 1010 \rangle.` +# +# Multiplying qubits +# ------------------- +# +# Following the same idea, we will see how easily we can +# implement multiplication. For this purpose we'll take two arbitrary numbers :math:`m` and :math:`k` +# to carry out the operation. This time, we look for an operator Mul such that +# +# .. math:: \text{Mul}\vert m \rangle \vert k \rangle \vert 0 \rangle = \vert m \rangle \vert k \rangle \vert m\cdot k \rangle. +# +# To understand the multiplication process, let's work with the binary decomposition of +# :math:`k:=\sum_{i=0}^{n-1}2^{n-i-1}k_i` and +# :math:`m:=\sum_{j=0}^{l-1}2^{l-j-1}m_i.` In this case, the product would +# be: +# +# .. math:: k \cdot m = \sum_{i=0}^{n-1}\sum_{j = 0}^{l-1}m_ik_i (2^{n-i-1} \cdot 2^{l-j-1}). +# +# In other words, if :math:`k_i = 1` and :math:`m_i = 1,` we would add +# :math:`2^{n-i-1} \cdot 2^{l-j-1}` units to the counter, where :math:`n` and :math:`l` +# are the number of qubits with which we encode :math:`m` and :math:`k` respectively. +# Let's code to see how it works! + +wires_m = [0, 1, 2] # qubits needed to encode m +wires_k = [3, 4, 5] # qubits needed to encode k +wires_solution = [6, 7, 8, 9, 10] # qubits needed to encode the solution + +dev = qml.device("default.qubit", wires=wires_m + wires_k + wires_solution, shots=1) + +n_wires = len(dev.wires) + +def multiplication(wires_m, wires_k, wires_solution): + # prepare sol-qubits to counting + qml.QFT(wires=wires_solution) + + # add m to the counter + for i in range(len(wires_k)): + for j in range(len(wires_m)): + coeff = 2 ** (len(wires_m) + len(wires_k) - i - j - 2) + qml.ctrl(add_k_fourier, control=[wires_k[i], wires_m[j]])(coeff, wires_solution) + + # return to computational basis + qml.adjoint(qml.QFT)(wires=wires_solution) + +@qml.qnode(dev) +def mul(m, k): + # m and k codification + qml.BasisEmbedding(m, wires=wires_m) + qml.BasisEmbedding(k, wires=wires_k) + + # Apply multiplication + multiplication(wires_m, wires_k, wires_solution) + + return qml.sample(wires=wires_solution) + + +print(f"The ket representation of the multiplication of 3 and 7 is {mul(3,7)}") + +qml.draw_mpl(mul, show_all_wires=True)(3, 7) +plt.show() + + +###################################################################### +# Awesome! We have multiplied :math:`7 \cdot 3` and, as a result, we have +# :math:`\vert 10101 \rangle,` which is :math:`21` in binary. +# +# +# Factorization with Grover +# ------------------------- +# +# With this, we have already gained a large repertoire of interesting +# operations that we can do, but we can give the idea one more twist and +# apply what we have learned in an example. +# +# Let’s imagine now that we want just the opposite: to factor the +# number :math:`21` as a product of two terms. Is this something we could do +# using our previous reasoning? The answer is yes! We can make use of +# `Grover's algorithm `_ to +# amplify the states whose product is the number we +# are looking for. All we would need is to construct the oracle :math:`U,` i.e., an +# operator such that +# +# .. math:: U\vert m \rangle \vert k \rangle = \vert m \rangle \vert k \rangle \text{ if }m\cdot k \not = 21, +# +# .. math:: U\vert m \rangle \vert k \rangle = -\vert m \rangle \vert k \rangle \text{ if }m\cdot k = 21 +# +# The idea of the oracle is as simple as this: +# +# #. use auxiliary registers to store the product, +# #. check if the product state is :math:`\vert 10101 \rangle` and, in that case, change the sign, +# #. execute the inverse of the circuit to clear the auxiliary qubits. +# #. calculate the probabilities and see which states have been amplified. +# +# Let's go back to PennyLane to implement this idea. + +n = 21 # number we want to factor + +wires_m = [0, 1, 2] # qubits needed to encode m +wires_k = [3, 4, 5] # qubits needed to encode k +wires_solution = [6, 7, 8, 9, 10] # qubits needed to encode the solution + +dev = qml.device("default.qubit", wires=wires_m + wires_k + wires_solution) + +n_wires = len(dev.wires) + +@qml.qnode(dev) +def factorization(n, wires_m, wires_k, wires_solution): + # Superposition of the input + for wire in wires_m: + qml.Hadamard(wires=wire) + + for wire in wires_k: + qml.Hadamard(wires=wire) + + # Apply the multiplication + multiplication(wires_m, wires_k, wires_solution) + + # Change sign of n + qml.FlipSign(n, wires=wires_solution) + + # Uncompute multiplication + qml.adjoint(multiplication)(wires_m, wires_k, wires_solution) + + # Apply Grover operator + qml.GroverOperator(wires=wires_m + wires_k) + + return qml.probs(wires=wires_m) + + +plt.bar(range(2 ** len(wires_m)), factorization(n, wires_m, wires_k, wires_solution)) +plt.xlabel("Basic states") +plt.ylabel("Probability") +plt.show() + +###################################################################### +# By plotting the probabilities of obtaining each basic state we see that +# prime factors have been amplified! Factorization via Grover’s algorithm +# does not achieve exponential improvement that +# `Shor's algorithm `_ does, but we +# can see that this construction is simple and a great example to +# illustrate basic arithmetic! +# +# I hope we can now all see that oracles are not something magical and that there +# is a lot of work behind their construction! This will help us in the future to build +# more complicated operators, but until then, let’s keep on learning. 🚀 +# +# References +# ---------- +# +# .. [#Draper2000] +# +# Thomas G. Draper, "Addition on a Quantum Computer". `arXiv:quant-ph/0008033 `__. +# +# +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_quantum_chemistry/demo.py b/demonstrations_v2/tutorial_quantum_chemistry/demo.py index b015fb33b7..6925c9fe1b 100644 --- a/demonstrations_v2/tutorial_quantum_chemistry/demo.py +++ b/demonstrations_v2/tutorial_quantum_chemistry/demo.py @@ -1,331 +1,331 @@ -r""" -Building molecular Hamiltonians -=============================== - - -.. meta:: - :property="og:description": Learn how to build electronic Hamiltonians of molecules. - - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/water_structure.png - -.. related:: - tutorial_vqe A brief overview of VQE - -*Author: Alain Delgado — Posted: 02 August 2020. Last updated: 29 August 2023.* - -.. note:: - - A wide variety of molecular data, including Hamiltonians, is - available on the `PennyLane Datasets service `__. - -The ultimate goal of computational quantum chemistry is to unravel the -quantum effects that determine the structure and properties of molecules. Reaching -this goal is challenging since the characteristic energies associated with -these effects, e.g., the electronic correlation energy, are typically a tiny fraction -of the total energy of the molecule. - -Accurate molecular properties can be computed from the wave function describing the -interacting electrons in a molecule. The **electronic** wave function -:math:`\Psi(r)` satisfies the `Schrödinger equation -`_ - -.. math:: - H_e \Psi(r) = E \Psi(r), - -where :math:`H_e` and :math:`E` denote the electronic Hamiltonian and the -total energy of the molecule, respectively. When solving the latter equation, -the nuclei of the molecule can be treated as point particles whose coordinates -are fixed [#BornOpp1927]_. In this approximation, both the total energy and -the electronic Hamiltonian depend parametrically on the nuclear coordinates. - - -In this tutorial, you will learn how to use PennyLane to build a -representation of the electronic Hamiltonian :math:`H_e` that can be used to perform -**quantum** simulations of molecules [#yudong2019]_. First, we show how to define -the structure of the molecule in terms of the symbols and the coordinates of -the atoms. Next, we describe how to solve the `Hartree-Fock -equations `_ for the target -molecule. Finally, we discuss some advanced features that can be used to simulate -more complicated systems. - -Let's get started! - -Defining the molecular structure --------------------------------- -In this example we construct the electronic Hamiltonian of the water molecule. - - -.. figure:: ../_static/demonstration_assets/quantum_chemistry/water_structure.png - :width: 30% - :align: center - -The structure of a molecule is defined by the symbols and the nuclear coordinates of -its constituent atoms. It can be specified using different `chemical file formats -`_. Within PennyLane, the molecular -structure is defined by providing a list with the atomic symbols and a one-dimensional -array with the nuclear coordinates in -`atomic units `_. -""" -import numpy as np - -symbols = ["H", "O", "H"] -coordinates = np.array([[-0.0399, -0.0038, 0.0], [1.5780, 0.8540, 0.0], [2.7909, -0.5159, 0.0]]) - -############################################################################## -# The :func:`~.pennylane.qchem.read_structure` function can also be used to read the -# molecular geometry from an external file. - - -from pennylane import qchem - -symbols, coordinates = qchem.read_structure("quantum_chemistry/h2o.xyz") - -############################################################################## -# The xyz format is supported. -# -# Solving the Hartree-Fock equations -# ---------------------------------- -# The molecule's electronic Hamiltonian is commonly represented using the -# `second-quantization `_ formalism, -# which we will explore in more detail in the -# next section. To that aim, a basis of **single-particle** states needs to be chosen. -# In quantum chemistry these states are the -# `molecular orbitals `_ -# which describe the wave function of a single electron in the molecule. -# -# Molecular orbitals are typically represented as a linear combination of **atomic orbitals**. -# The expansion coefficients in the atomic basis are calculated using the -# `Hartree-Fock (HF) method `_. -# In the HF approximation, each electron in the molecule is treated as an **independent** -# particle that moves under the influence of the Coulomb potential due to the nuclei, and a mean -# field generated by all other electrons [#pople1977]_. The optimized coefficients are precisely -# what we need to build the second-quantized Hamiltonian. -# -# PennyLane provides a differentiable Hartree-Fock solver and the functionality to construct a -# fully-differentiable molecular Hamiltonian. -# -# Building the Hamiltonian -# ------------------------ -# In the second quantization formalism, the electronic wave function of the molecule -# is represented in the occupation number basis. For :math:`M` *spin* molecular -# orbitals, the elements of this basis are labelled as -# :math:`\vert n_0, n_1, \dots, n_{M-1} \rangle,` where :math:`n_i = 0,1` -# indicates the occupation of each orbital. In this representation, the electronic -# Hamiltonian is given by -# -# .. math:: -# H = \sum_{p,q} h_{pq} c_p^\dagger c_q + -# \frac{1}{2} \sum_{p,q,r,s} h_{pqrs} c_p^\dagger c_q^\dagger c_r c_s, -# -# where :math:`c^\dagger` and :math:`c` are the electron creation -# and annihilation operators, respectively, and the coefficients -# :math:`h_{pq}` and :math:`h_{pqrs}` denote the one- and two-electron -# Coulomb integrals [#ref_integrals]_ evaluated using the Hartree-Fock -# orbitals. -# -# We can use the states of :math:`M` qubits to encode any element -# of the occupation number basis -# -# .. math:: -# \vert n_0, n_1, \dots, n_{M-1} \rangle \rightarrow \vert q_0 q_1 \cdots q_{M-1} \rangle. -# -# This implies that we need to map the fermionic operators onto operators -# that act on qubits. This can be done by using the -# `Jordan-Wigner `_ -# transformation [#seeley2012]_ which allows us to decompose the fermionic Hamiltonian -# into a linear combination of the tensor product of Pauli operators -# -# .. math:: -# H = \sum_j C_j \otimes_i \sigma_i^{(j)}, -# -# where :math:`C_j` is a scalar coefficient and :math:`\sigma_i` represents an -# element of the Pauli group :math:`\{ I, X, Y, Z \}.` -# -# In PennyLane we have the :func:`~.pennylane.qchem.molecular_hamiltonian` -# function which encapsulates all the steps explained above. It simplifies the process of building -# the electronic Hamiltonian to a single line of code. We just need to input -# the molecule, as shown below: - -molecule = qchem.Molecule(symbols, coordinates) -H, qubits = qchem.molecular_hamiltonian(molecule) -print("Number of qubits: {:}".format(qubits)) -print("Qubit Hamiltonian") -print(H) - -############################################################################## -# Advanced features -# ----------------- -# The :class:`~.pennylane.qchem.Molecule` object allows us to define additional -# keyword arguments to solve the Hartree-Fock equations of more complicated systems. -# The net charge of the molecule may be specified to simulate positively or negatively -# charged molecules. For a neutral system we choose - -charge = 0 - -############################################################################## -# We can also specify the -# `spin multiplicity `_. For the -# water molecule, which contains ten electrons, the `Slater determinant -# `_ resulting from occupying the five -# lowest-energy orbitals with two *paired* electrons per orbital has spin multiplicity one. -# Alternatively, if we define an occupation where the first four orbitals are doubly occupied -# and the next two are singly occupied by *unpaired* electrons, the HF state will have -# multiplicity three. -# -# | -# -# .. figure:: ../_static/demonstration_assets/quantum_chemistry/hf_references.png -# :width: 50% -# :align: center -# -# | -# -# For the neutral water molecule we have, - -multiplicity = 1 - -############################################################################## -# As mentioned above, molecular orbitals are represented as a linear combination -# of atomic orbitals which are typically modeled as `Gaussian-type orbitals -# `_. We can specify different types -# of `Gaussian atomic bases `_. In this example we -# choose a `minimal basis set -# `_. - -basis_set = "sto-3g" - -############################################################################## -# PennyLane also allows us to define an active space [#truhlar2018]_ to perform quantum -# simulations with a reduced number of qubits. This is done by classifying the molecular -# orbitals as core, active, and external orbitals: -# -# * Core orbitals are always occupied by two electrons. -# * Active orbitals can be occupied by zero, one, or two electrons. -# * The external orbitals are never occupied. -# -# Within this approximation, a certain number of **active electrons** are allowed to -# populate a finite set of **active orbitals**. -# -# .. figure:: ../_static/demonstration_assets/quantum_chemistry/sketch_active_space.png -# :width: 40% -# :align: center -# -# .. note:: -# The number of active **spin-orbitals** determines the **number of qubits** required -# to perform the quantum simulations. -# -# For the water molecule in a minimal basis set we have a total of ten electrons -# and seven molecular orbitals. In this example we define a symmetric active space with -# four electrons and four active orbitals using -# the :func:`~.pennylane.qchem.active_space` function: - -electrons = 10 -orbitals = 7 -core, active = qchem.active_space(electrons, orbitals, active_electrons=4, active_orbitals=4) - -############################################################################## -# Viewing the results: - -print("List of core orbitals: {:}".format(core)) -print("List of active orbitals: {:}".format(active)) -print("Number of qubits: {:}".format(2 * len(active))) - -############################################################################## -# Finally, we use the :func:`~.pennylane.qchem.molecular_hamiltonian` function to -# build the resulting Hamiltonian of the water molecule: - -molecule = qchem.Molecule( - symbols, - coordinates, - charge=charge, - mult=multiplicity, - basis_name=basis_set -) - -H, qubits = qchem.molecular_hamiltonian( - molecule, - active_electrons=4, - active_orbitals=4, -) - -print("Number of qubits required to perform quantum simulations: {:}".format(qubits)) -print("Hamiltonian of the water molecule") -print(H) - -############################################################################## -# In this case, since we have truncated the basis of molecular orbitals, the resulting -# observable is an approximation of the Hamiltonian generated in the -# section `Building the Hamiltonian `__. -# -# OpenFermion-PySCF backend -# ------------------------- -# The :func:`~.pennylane.qchem.molecular_hamiltonian` function can also be used to construct the -# molecular Hamiltonian with a non-differentiable backend that uses the -# `OpenFermion-PySCF `_ plugin interfaced with the -# electronic structure package `PySCF `_. This -# backend can be selected by setting ``method='pyscf'`` in -# :func:`~.pennylane.qchem.molecular_hamiltonian`: - -molecule = qchem.Molecule(symbols, coordinates) -H, qubits = qchem.molecular_hamiltonian(molecule, method="pyscf") -print(H) - -############################################################################## -# This backend requires the ``OpenFermion-PySCF`` plugin to be installed by the user with -# -# .. code-block:: bash -# -# pip install openfermionpyscf -# -# Additionally, if you have built your electronic Hamiltonian independently using -# `OpenFermion `_ tools, it can -# be readily converted to a PennyLane observable using the -# :func:`~.pennylane.qchem.import_operator` function. -# -# You have completed the tutorial! Now, select your favorite molecule and build its electronic -# Hamiltonian. -# To see how simple it is to implement the VQE algorithm to compute the ground-state energy of -# your molecule using PennyLane, take a look at the tutorial :doc:`tutorial_vqe`. -# -# References -# ---------- -# -# .. [#yudong2019] -# -# Yudong Cao, Jonathan Romero, *et al.*, "Quantum Chemistry in the Age of Quantum Computing". -# `Chem. Rev. 2019, 119, 19, 10856-10915. -# `_ -# -# .. [#BornOpp1927] -# -# M. Born, J.R. Oppenheimer, "Quantum Theory of the Molecules". -# `Annalen der Physik 84, 457-484 (1927) -# `_ -# -# .. [#pople1977] -# -# Rolf Seeger, John Pople. "Self‐consistent molecular orbital methods. XVIII. Constraints and -# stability in Hartree–Fock theory". `Journal of Chemical Physics 66, -# 3045 (1977). `_ -# -# .. [#ref_integrals] -# -# J.T. Fermann, E.F. Valeev, "Fundamentals of Molecular Integrals Evaluation". -# `arXiv:2007.12057 `_ -# -# .. [#seeley2012] -# -# Jacob T. Seeley, Martin J. Richard, Peter J. Love. "The Bravyi-Kitaev transformation for -# quantum computation of electronic structure". `Journal of Chemical Physics 137, 224109 (2012). -# `_ -# -# .. [#truhlar2018] -# -# J.J. Bao, S.S. Dong, L. Gagliardi, D.G. Truhlar. "Automatic Selection of an -# Active Space for Calculating Electronic Excitation Spectra by MS-CASPT2 or MC-PDFT". -# `Journal of Chemical Theory and Computation 14, 2017 (2018). -# `_ -# -# About the author -# ---------------- -# +r""" +Building molecular Hamiltonians +=============================== + + +.. meta:: + :property="og:description": Learn how to build electronic Hamiltonians of molecules. + + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/water_structure.png + +.. related:: + tutorial_vqe A brief overview of VQE + +*Author: Alain Delgado — Posted: 02 August 2020. Last updated: 29 August 2023.* + +.. note:: + + A wide variety of molecular data, including Hamiltonians, is + available on the `PennyLane Datasets service `__. + +The ultimate goal of computational quantum chemistry is to unravel the +quantum effects that determine the structure and properties of molecules. Reaching +this goal is challenging since the characteristic energies associated with +these effects, e.g., the electronic correlation energy, are typically a tiny fraction +of the total energy of the molecule. + +Accurate molecular properties can be computed from the wave function describing the +interacting electrons in a molecule. The **electronic** wave function +:math:`\Psi(r)` satisfies the `Schrödinger equation +`_ + +.. math:: + H_e \Psi(r) = E \Psi(r), + +where :math:`H_e` and :math:`E` denote the electronic Hamiltonian and the +total energy of the molecule, respectively. When solving the latter equation, +the nuclei of the molecule can be treated as point particles whose coordinates +are fixed [#BornOpp1927]_. In this approximation, both the total energy and +the electronic Hamiltonian depend parametrically on the nuclear coordinates. + + +In this tutorial, you will learn how to use PennyLane to build a +representation of the electronic Hamiltonian :math:`H_e` that can be used to perform +**quantum** simulations of molecules [#yudong2019]_. First, we show how to define +the structure of the molecule in terms of the symbols and the coordinates of +the atoms. Next, we describe how to solve the `Hartree-Fock +equations `_ for the target +molecule. Finally, we discuss some advanced features that can be used to simulate +more complicated systems. + +Let's get started! + +Defining the molecular structure +-------------------------------- +In this example we construct the electronic Hamiltonian of the water molecule. + + +.. figure:: ../_static/demonstration_assets/quantum_chemistry/water_structure.png + :width: 30% + :align: center + +The structure of a molecule is defined by the symbols and the nuclear coordinates of +its constituent atoms. It can be specified using different `chemical file formats +`_. Within PennyLane, the molecular +structure is defined by providing a list with the atomic symbols and a one-dimensional +array with the nuclear coordinates in +`atomic units `_. +""" +import numpy as np + +symbols = ["H", "O", "H"] +coordinates = np.array([[-0.0399, -0.0038, 0.0], [1.5780, 0.8540, 0.0], [2.7909, -0.5159, 0.0]]) + +############################################################################## +# The :func:`~.pennylane.qchem.read_structure` function can also be used to read the +# molecular geometry from an external file. + + +from pennylane import qchem + +symbols, coordinates = qchem.read_structure("quantum_chemistry/h2o.xyz") + +############################################################################## +# The xyz format is supported. +# +# Solving the Hartree-Fock equations +# ---------------------------------- +# The molecule's electronic Hamiltonian is commonly represented using the +# `second-quantization `_ formalism, +# which we will explore in more detail in the +# next section. To that aim, a basis of **single-particle** states needs to be chosen. +# In quantum chemistry these states are the +# `molecular orbitals `_ +# which describe the wave function of a single electron in the molecule. +# +# Molecular orbitals are typically represented as a linear combination of **atomic orbitals**. +# The expansion coefficients in the atomic basis are calculated using the +# `Hartree-Fock (HF) method `_. +# In the HF approximation, each electron in the molecule is treated as an **independent** +# particle that moves under the influence of the Coulomb potential due to the nuclei, and a mean +# field generated by all other electrons [#pople1977]_. The optimized coefficients are precisely +# what we need to build the second-quantized Hamiltonian. +# +# PennyLane provides a differentiable Hartree-Fock solver and the functionality to construct a +# fully-differentiable molecular Hamiltonian. +# +# Building the Hamiltonian +# ------------------------ +# In the second quantization formalism, the electronic wave function of the molecule +# is represented in the occupation number basis. For :math:`M` *spin* molecular +# orbitals, the elements of this basis are labelled as +# :math:`\vert n_0, n_1, \dots, n_{M-1} \rangle,` where :math:`n_i = 0,1` +# indicates the occupation of each orbital. In this representation, the electronic +# Hamiltonian is given by +# +# .. math:: +# H = \sum_{p,q} h_{pq} c_p^\dagger c_q + +# \frac{1}{2} \sum_{p,q,r,s} h_{pqrs} c_p^\dagger c_q^\dagger c_r c_s, +# +# where :math:`c^\dagger` and :math:`c` are the electron creation +# and annihilation operators, respectively, and the coefficients +# :math:`h_{pq}` and :math:`h_{pqrs}` denote the one- and two-electron +# Coulomb integrals [#ref_integrals]_ evaluated using the Hartree-Fock +# orbitals. +# +# We can use the states of :math:`M` qubits to encode any element +# of the occupation number basis +# +# .. math:: +# \vert n_0, n_1, \dots, n_{M-1} \rangle \rightarrow \vert q_0 q_1 \cdots q_{M-1} \rangle. +# +# This implies that we need to map the fermionic operators onto operators +# that act on qubits. This can be done by using the +# `Jordan-Wigner `_ +# transformation [#seeley2012]_ which allows us to decompose the fermionic Hamiltonian +# into a linear combination of the tensor product of Pauli operators +# +# .. math:: +# H = \sum_j C_j \otimes_i \sigma_i^{(j)}, +# +# where :math:`C_j` is a scalar coefficient and :math:`\sigma_i` represents an +# element of the Pauli group :math:`\{ I, X, Y, Z \}.` +# +# In PennyLane we have the :func:`~.pennylane.qchem.molecular_hamiltonian` +# function which encapsulates all the steps explained above. It simplifies the process of building +# the electronic Hamiltonian to a single line of code. We just need to input +# the molecule, as shown below: + +molecule = qchem.Molecule(symbols, coordinates) +H, qubits = qchem.molecular_hamiltonian(molecule) +print("Number of qubits: {:}".format(qubits)) +print("Qubit Hamiltonian") +print(H) + +############################################################################## +# Advanced features +# ----------------- +# The :class:`~.pennylane.qchem.Molecule` object allows us to define additional +# keyword arguments to solve the Hartree-Fock equations of more complicated systems. +# The net charge of the molecule may be specified to simulate positively or negatively +# charged molecules. For a neutral system we choose + +charge = 0 + +############################################################################## +# We can also specify the +# `spin multiplicity `_. For the +# water molecule, which contains ten electrons, the `Slater determinant +# `_ resulting from occupying the five +# lowest-energy orbitals with two *paired* electrons per orbital has spin multiplicity one. +# Alternatively, if we define an occupation where the first four orbitals are doubly occupied +# and the next two are singly occupied by *unpaired* electrons, the HF state will have +# multiplicity three. +# +# | +# +# .. figure:: ../_static/demonstration_assets/quantum_chemistry/hf_references.png +# :width: 50% +# :align: center +# +# | +# +# For the neutral water molecule we have, + +multiplicity = 1 + +############################################################################## +# As mentioned above, molecular orbitals are represented as a linear combination +# of atomic orbitals which are typically modeled as `Gaussian-type orbitals +# `_. We can specify different types +# of `Gaussian atomic bases `_. In this example we +# choose a `minimal basis set +# `_. + +basis_set = "sto-3g" + +############################################################################## +# PennyLane also allows us to define an active space [#truhlar2018]_ to perform quantum +# simulations with a reduced number of qubits. This is done by classifying the molecular +# orbitals as core, active, and external orbitals: +# +# * Core orbitals are always occupied by two electrons. +# * Active orbitals can be occupied by zero, one, or two electrons. +# * The external orbitals are never occupied. +# +# Within this approximation, a certain number of **active electrons** are allowed to +# populate a finite set of **active orbitals**. +# +# .. figure:: ../_static/demonstration_assets/quantum_chemistry/sketch_active_space.png +# :width: 40% +# :align: center +# +# .. note:: +# The number of active **spin-orbitals** determines the **number of qubits** required +# to perform the quantum simulations. +# +# For the water molecule in a minimal basis set we have a total of ten electrons +# and seven molecular orbitals. In this example we define a symmetric active space with +# four electrons and four active orbitals using +# the :func:`~.pennylane.qchem.active_space` function: + +electrons = 10 +orbitals = 7 +core, active = qchem.active_space(electrons, orbitals, active_electrons=4, active_orbitals=4) + +############################################################################## +# Viewing the results: + +print("List of core orbitals: {:}".format(core)) +print("List of active orbitals: {:}".format(active)) +print("Number of qubits: {:}".format(2 * len(active))) + +############################################################################## +# Finally, we use the :func:`~.pennylane.qchem.molecular_hamiltonian` function to +# build the resulting Hamiltonian of the water molecule: + +molecule = qchem.Molecule( + symbols, + coordinates, + charge=charge, + mult=multiplicity, + basis_name=basis_set +) + +H, qubits = qchem.molecular_hamiltonian( + molecule, + active_electrons=4, + active_orbitals=4, +) + +print("Number of qubits required to perform quantum simulations: {:}".format(qubits)) +print("Hamiltonian of the water molecule") +print(H) + +############################################################################## +# In this case, since we have truncated the basis of molecular orbitals, the resulting +# observable is an approximation of the Hamiltonian generated in the +# section `Building the Hamiltonian `__. +# +# OpenFermion-PySCF backend +# ------------------------- +# The :func:`~.pennylane.qchem.molecular_hamiltonian` function can also be used to construct the +# molecular Hamiltonian with a non-differentiable backend that uses the +# `OpenFermion-PySCF `_ plugin interfaced with the +# electronic structure package `PySCF `_. This +# backend can be selected by setting ``method='pyscf'`` in +# :func:`~.pennylane.qchem.molecular_hamiltonian`: + +molecule = qchem.Molecule(symbols, coordinates) +H, qubits = qchem.molecular_hamiltonian(molecule, method="pyscf") +print(H) + +############################################################################## +# This backend requires the ``OpenFermion-PySCF`` plugin to be installed by the user with +# +# .. code-block:: bash +# +# pip install openfermionpyscf +# +# Additionally, if you have built your electronic Hamiltonian independently using +# `OpenFermion `_ tools, it can +# be readily converted to a PennyLane observable using the +# :func:`~.pennylane.qchem.import_operator` function. +# +# You have completed the tutorial! Now, select your favorite molecule and build its electronic +# Hamiltonian. +# To see how simple it is to implement the VQE algorithm to compute the ground-state energy of +# your molecule using PennyLane, take a look at the tutorial :doc:`tutorial_vqe`. +# +# References +# ---------- +# +# .. [#yudong2019] +# +# Yudong Cao, Jonathan Romero, *et al.*, "Quantum Chemistry in the Age of Quantum Computing". +# `Chem. Rev. 2019, 119, 19, 10856-10915. +# `_ +# +# .. [#BornOpp1927] +# +# M. Born, J.R. Oppenheimer, "Quantum Theory of the Molecules". +# `Annalen der Physik 84, 457-484 (1927) +# `_ +# +# .. [#pople1977] +# +# Rolf Seeger, John Pople. "Self‐consistent molecular orbital methods. XVIII. Constraints and +# stability in Hartree–Fock theory". `Journal of Chemical Physics 66, +# 3045 (1977). `_ +# +# .. [#ref_integrals] +# +# J.T. Fermann, E.F. Valeev, "Fundamentals of Molecular Integrals Evaluation". +# `arXiv:2007.12057 `_ +# +# .. [#seeley2012] +# +# Jacob T. Seeley, Martin J. Richard, Peter J. Love. "The Bravyi-Kitaev transformation for +# quantum computation of electronic structure". `Journal of Chemical Physics 137, 224109 (2012). +# `_ +# +# .. [#truhlar2018] +# +# J.J. Bao, S.S. Dong, L. Gagliardi, D.G. Truhlar. "Automatic Selection of an +# Active Space for Calculating Electronic Excitation Spectra by MS-CASPT2 or MC-PDFT". +# `Journal of Chemical Theory and Computation 14, 2017 (2018). +# `_ +# +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_quantum_dropout/demo.py b/demonstrations_v2/tutorial_quantum_dropout/demo.py index cebca15758..e0ea5e2ba2 100644 --- a/demonstrations_v2/tutorial_quantum_dropout/demo.py +++ b/demonstrations_v2/tutorial_quantum_dropout/demo.py @@ -1,702 +1,702 @@ -r"""Dropout for Quantum Neural Networks -=================================== -""" - -###################################################################### -# Are you struggling with overfitting while training Quantum Neural Networks (QNNs)? -# -# In this demo, we show how to exploit the quantum version of the dropout technique to avoid the problem of -# overfitting in overparametrized QNNs. What follows is based on the paper “A General -# Approach to Dropout in Quantum Neural Networks” by F. Scala, et al. [#dropout]_. -# -# .. figure:: ../_static/demonstration_assets/quantum_dropout/socialthumbnail_large_QuantumDropout_2024-03-07.png -# :align: center -# :width: 60% -# :target: javascript:void(0) -# -# -# What is overfitting and dropout? -# --------------------------------- -# -# Neural Networks (NNs) usually require highly flexible models with lots of trainable parameters in -# order to *learn* a certain underlying function (or data distribution). -# However, being able to learn with low in-sample error is not enough; *generalization* — the ability to provide -# good predictions on previously unseen data — is also desirable. -# -# Highly expressive models may suffer from **overfitting**, which means that -# they are trained too well on the training data, and as a result perform poorly on new, unseen -# data. This happens because the model has learned the noise in the training data, rather than the -# underlying pattern that is generalizable to new data. -# -# **Dropout** is a common technique for classical Deep Neural Networks (DNNs) preventing computational units -# from becoming too specialized and reducing the risk of overfitting [#Hinton2012]_, [#Srivastava2014]_. It consists of randomly removing -# neurons or connections *only during training* to block the flow of information. Once the -# model is trained, the DNN is employed in its original form. -# -# Why dropout for Quantum Neural Networks? -# ---------------------------------------- -# -# Recently, it has been shown that the use of overparametrized QNN models -# changes the optimization landscape by removing lots of local minima [#Kiani2020]_, [#Larocca2023]_. On the one hand, this increased number of -# parameters leads to faster and easier training, but on the other hand, it may drive -# the model to overfit the data. This is also strictly related to the `repeated encoding `__ of classical -# data to achieve nonlinearity in the computation. This is why, inspired from classical DNNs, one -# can think of applying some sort of dropout to QNNs. This would correspond to randomly dropping some -# (groups of) parameterized gates during training to achieve better generalization. -# -# Quantum dropout of rotations in a sine regression -# -------------------------------------------------- -# -# In this demo we will exploit quantum dropout to avoid overfitting during the regression of noisy -# data originally coming from the *sinusoidal* function. In particular, we will randomly “drop” -# rotations during the training phase. In practice, this will correspond to temporarily setting parameters to a value of 0. -# -# Let’s start by importing Pennylane and ``numpy`` and fixing the random seed for reproducibility: -# - -import numpy as np -import pennylane as qml - -seed = 12345 -np.random.seed(seed=seed) - -###################################################################### -# The circuit -# ~~~~~~~~~~~ -# -# Now we define the embedding of classical data and the variational ansatz that will then be combined -# to construct our QNN. Dropout will happen inside the variational ansatz. Obtaining dropout with standard -# Pennylane would be quite straightforward by means of some "if statements", but the training procedure -# will take ages. Here we will leverage JAX in order to speed up the training process with -# Just In Time (JIT) compilation. The drawback is that the definition of the variational ansatz becomes a -# little elaborated, since JAX has its own language for conditional statements. For this purpose we -# define two functions ``true_cond`` and ``false_cond`` to work with ``jax.lax.cond```, which is the JAX -# conditional statement. See this `demo `__ -# for additional insights on how to optimize QNNs with JAX. -# -# Practically speaking, rotation dropout will be performed by passing a list to the ansatz. -# The single qubit rotations are applied depending on the values stored in this list: -# if the value is negative the rotation is dropped (rotation dropout), otherwise it is applied. -# How to produce this list will be explained later in this demo (see the ``make_dropout`` function). - -import jax # require for Just In Time (JIT) compilation -import jax.numpy as jnp - -jax.config.update("jax_platform_name", "cpu") -jax.config.update("jax_enable_x64", True) - - -def embedding(x, wires): - # Encodes the datum multiple times in the register, - # employing also nonlinear functions - assert len(x) == 1 # check feature is 1-D - for i in wires: - qml.RY(jnp.arcsin(x), wires=i) - for i in wires: - qml.RZ(jnp.arccos(x ** 2), wires=i) - - -def true_cond(angle): - # necessary for using an if statement within jitted function - # exploiting jax.lax.cond - # if this function is assessed the rotation is dropped - return 0.0 - - -def false_cond(angle): - # necessary for using an if statement within jitted function - # exploiting jax.lax.cond - # if this function is assessed the rotation is kept - return angle - - -def var_ansatz( - theta, wires, rotations=[qml.RX, qml.RZ, qml.RX], entangler=qml.CNOT, keep_rotation=None -): - - """Single layer of the variational ansatz for our QNN. - We have a single qubit rotation per each qubit (wire) followed by - a linear chain of entangling gates (entangler). This structure is repeated per each rotation in `rotations` - (defining `inner_layers`). - The single qubit rotations are applied depending on the values stored in `keep_rotation`: - if the value is negative the rotation is dropped (rotation dropout), otherwise it is applied. - - Params: - - theta: variational angles that will undergo optimization - - wires: list of qubits (wires) - - rotations: list of rotation kind per each `inner_layer` - - entangler: entangling gate - - keep_rotation: list of lists. There is one list per each `inner_layer`. - In each list there are indexes of the rotations that we want to apply. - Some of these values may be substituted by -1 value - which means that the rotation gate wont be applied (dropout). - """ - - # the length of `rotations` defines the number of inner layers - N = len(wires) - assert len(theta) == 3 * N - wires = list(wires) - - counter = 0 - # keep_rotations contains a list per each inner_layer - for rots in keep_rotation: - # we cicle over the elements of the lists inside keep_rotation - for qb, keep_or_drop in enumerate(rots): - rot = rotations[counter] # each inner layer can have a different rotation - - angle = theta[counter * N + qb] - # conditional statement implementing dropout - # if `keep_or_drop` is negative the rotation is dropped - angle_drop = jax.lax.cond(keep_or_drop < 0, true_cond, false_cond, angle) - rot(angle_drop, wires=wires[qb]) - for qb in wires[:-1]: - entangler(wires=[wires[qb], wires[qb + 1]]) - counter += 1 - - -###################################################################### -# And then we define the hyperparameters of our QNN, namely the number of qubits, -# the number of sublayers in the variational ansatz (``inner_layers``) and the resulting -# number of parameters per layer: -# - -n_qubits = 5 -inner_layers = 3 -params_per_layer = n_qubits * inner_layers - -###################################################################### -# Now we actually build the QNN: -# - - -def create_circuit(n_qubits, layers): - device = qml.device("default.qubit", wires=n_qubits) - - @qml.qnode(device) - def circuit(x, theta, keep_rot): - # print(x) - # print(theta) - - for i in range(layers): - embedding(x, wires=range(n_qubits)) - - keep_rotation = keep_rot[i] - - var_ansatz( - theta[i * params_per_layer : (i + 1) * params_per_layer], - wires=range(n_qubits), - entangler=qml.CNOT, - keep_rotation=keep_rotation, - ) - - return qml.expval(qml.PauliZ(wires=0)) # we measure only the first qubit - - return circuit - - -###################################################################### -# Let’s have a look at a single layer of our QNN: -# -import matplotlib.pyplot as plt - - -plt.style.use("pennylane.drawer.plot") # set pennylane theme, which is nice to see - -# create the circuit with given number of qubits and layers -layers = 1 -circ = create_circuit(n_qubits, layers=layers) - -# for the moment let's keep all the rotations in all sublayers -keep_all_rot = [ - [list(range((n_qubits))) for j in range(1, inner_layers + 1)], -] -# we count the parameters -numbered_params = np.array(range(params_per_layer * layers), dtype=float) -# we encode a single coordinate -single_sample = np.array([0]) - -qml.draw_mpl(circ, decimals=2,)(single_sample, numbered_params, keep_all_rot) - -plt.show() - -###################################################################### -# We now build the model that we will employ for the regression task. -# Since we want to have an overparametrized QNN, we will add 10 layers and we exploit -# ``JAX`` to speed the training up: -# - -layers = 10 -qnn_tmp = create_circuit(n_qubits, layers) -qnn_tmp = jax.jit(qnn_tmp) -qnn_batched = jax.vmap( - qnn_tmp, (0, None, None) -) # we want to vmap on 0-axis of the first circuit param -# in this way we process in parallel all the inputs -# We jit for faster execution -qnn = jax.jit(qnn_batched) - - -###################################################################### -# Dropping rotations -# ~~~~~~~~~~~~~~~~~~ -# -# As anticipated, we need to set some random parameters to 0 at each optimization step. Given a layer -# dropout rate :math:`p_L` (this will be called ``layer_drop_rate``) and the gate dropout rate :math:`p_G` -# (this will be called ``rot_drop_rate``), the probability :math:`p` that a -# gate is dropped in a layer can be calculated with the conditioned probability law: -# -# .. math:: -# -# -# p=p(A\cap B)=p(A|B)p(B)=p_Gp_L -# -# where :math:`B` represents the selection of a specific layer and -# :math:`A` the selection of a specific gate within the chosen layer. -# -# In the following cell we define a function that produces the list of the indices of rotation gates that -# are kept. For gates which are dropped, the value ``-1`` is assigned to the corresponding index. The structure of the list -# is nested; we have one list per ``inner_layer`` inside one list per each layer, all contained in another list. -# This function will be called at each iteration. -# - - -def make_dropout(key): - drop_layers = [] - - for lay in range(layers): - # each layer has prob p_L=layer_drop_rate of being dropped - # according to that for every layer we sample - # if we have to appy dropout in it or not - out = jax.random.choice( - key, jnp.array(range(2)), p=jnp.array([1 - layer_drop_rate, layer_drop_rate]) - ) - key = jax.random.split(key)[0] # update the random key - - if out == 1: # if it has to be dropped - drop_layers.append(lay) - - keep_rot = [] - # we make list of indexes corresponding to the rotations gates - # that are kept in the computation during a single train step - for i in range(layers): - # each list is divded in layers and then in "inner layers" - # this is strictly related to the QNN architecture that we use - keep_rot_layer = [list(range((n_qubits))) for j in range(1, inner_layers + 1)] - - if i in drop_layers: # if dropout has to be applied in this layer - keep_rot_layer = [] # list of indexes for a single layer - inner_keep_r = [] # list of indexes for a single inner layer - for param in range(params_per_layer): - # each rotation within the layer has prob p=rot_drop_rate of being dropped - # according to that for every parameter (rotation) we sample - # if we have to drop it or not - out = jax.random.choice( - key, jnp.array(range(2)), p=jnp.array([1 - rot_drop_rate, rot_drop_rate]) - ) - key = jax.random.split(key)[0] # update the random key - - if out == 0: # if we have to keep it - inner_keep_r.append(param % n_qubits) # % is required because we work - # inner layer by inner layer - else: # if the rotation has to be dropped - inner_keep_r.append(-1) # we assign the value -1 - - if param % n_qubits == n_qubits - 1: # if it's the last qubit of the register - # append the inner layer list - keep_rot_layer.append(inner_keep_r) - # and reset it - inner_keep_r = [] - - keep_rot.append(keep_rot_layer) - - return jnp.array(keep_rot) - - -###################################################################### -# We can check the output of the ``make_dropout`` function: -# - -# setting the drop probability -layer_drop_rate, rot_drop_rate = 0.5, 0.3 # 15% probability of dropping a gate - -# JAX random key -key = jax.random.PRNGKey(12345) -# create the list of indexes, -# -1 implies we are dropping a gate -keep_rot = make_dropout(key) - -# let's just print the list for first layer -print(keep_rot[0]) - -###################################################################### -# Noisy sinusoidal function -# ~~~~~~~~~~~~~~~~~~~~~~~~~ -# -# To test the effectiveness of the dropout technique, we will use a prototypical dataset -# with which it is very easy to overfit: the sinusoidal function. We produce some -# points according to the :math:`\sin` function and then we add some white Gaussian noise -# (noise that follows a normal distribution) :math:`\epsilon.` The noise is essential to obtain overfitting; -# when our model is extremely expressive, it is capable of exactly fit each point and some parameters -# become hyper-specialized in recognizing the noisy features. This makes predictions on new unseen -# data difficult, since the overfitting model did not learn the true underlying data distribution. -# The dropout technique will help in avoiding co-adaptation and hyper-specialization, -# effectively reducing overfitting. -# - -from sklearn.model_selection import train_test_split - - -def make_sin_dataset(dataset_size=100, test_size=0.4, noise_value=0.4, plot=False): - """1D regression problem y=sin(x*\pi)""" - # x-axis - x_ax = np.linspace(-1, 1, dataset_size) - y = [[np.sin(x * np.pi)] for x in x_ax] - np.random.seed(123) - # noise vector - noise = np.array([np.random.normal(0, 0.5, 1) for i in y]) * noise_value - X = np.array(x_ax) - y = np.array(y + noise) # apply noise - - # split the dataset - X_train, X_test, y_train, y_test = train_test_split( - X, y, test_size=test_size, random_state=40, shuffle=True - ) - - X_train = X_train.reshape(-1, 1) - X_test = X_test.reshape(-1, 1) - - y_train = y_train.reshape(-1, 1) - y_test = y_test.reshape(-1, 1) - - return X_train, X_test, y_train, y_test - - -from matplotlib import ticker - -X, X_test, y, y_test = make_sin_dataset(dataset_size=20, test_size=0.25) - - -fig, ax = plt.subplots() -plt.plot(X, y, "o", label="Training") -plt.plot(X_test, y_test, "o", label="Test") - -plt.plot( - np.linspace(-1, 1, 100), - [np.sin(x * np.pi) for x in np.linspace(-1, 1, 100)], - linestyle="dotted", - label=r"$\sin(x)$", -) -plt.ylabel(r"$y = \sin(\pi\cdot x) + \epsilon$") -plt.xlabel(r"$x$") -ax.xaxis.set_major_locator(ticker.MultipleLocator(0.5)) -ax.yaxis.set_major_locator(ticker.MultipleLocator(0.5)) -plt.legend() - -plt.show() - -###################################################################### -# Since our circuit is only able to provide outputs in the range :math:`[-1,1],` we rescale all the -# noisy data within this range. To do this we leverage the `MinMaxScaler` from `sklearn`. -# It is common practice to fit the scaler only from training data and then apply it also to the -# test. The reason behind this is that in general one only has knowledge about the training dataset. -# (If the training dataset is not exhaustively representative of the underlying distribution, -# this preprocessing may lead to some outliers in the test set to be scaled out of the desired range.) -# - -from sklearn.preprocessing import MinMaxScaler - -scaler = MinMaxScaler(feature_range=(-1, 1)) -y = scaler.fit_transform(y) -y_test = scaler.transform(y_test) - -# reshaping for computation -y = y.reshape(-1,) -y_test = y_test.reshape(-1,) - -###################################################################### -# Optimization -# ~~~~~~~~~~~~ -# -# At this point we have to set the hyperparameters of the optimization, namely the number of epochs, the -# learning rate, and the optimizer: -# - -import optax # optimization using jax - -epochs = 700 -optimizer = optax.adam(learning_rate=0.01) - -###################################################################### -# We define the cost function as the Mean Square Error: -# - - -@jax.jit -def calculate_mse_cost(X, y, theta, keep_rot): - yp = qnn(X, theta, keep_rot) - # depending on your version of Pennylane you may require the following line - ##### - yp = jnp.array(yp).T - ##### - cost = jnp.mean((yp - y) ** 2) - - return cost - - -# Optimization update step -@jax.jit -def optimizer_update(opt_state, params, x, y, keep_rot): - loss, grads = jax.value_and_grad(lambda theta: calculate_mse_cost(x, y, theta, keep_rot))( - params - ) - updates, opt_state = optimizer.update(grads, opt_state) - - params = optax.apply_updates(params, updates) - return params, opt_state, loss - - -###################################################################### -# Training the model -# ------------------ -# -# And now we can try to train the model. We execute different runs of the training to understand the -# average behaviour of quantum dropout. To see the effect of dropout we can set different values of -# ``layer_drop_rate`` and ``rot_drop_rate``: -# - -n_run = 3 -drop_rates = [(0.0, 0.0), (0.3, 0.2), (0.7, 0.7)] - -train_history = {} -test_history = {} -opt_params = {} - - -for layer_drop_rate, rot_drop_rate in drop_rates: - # initialization of some lists to store data - costs_per_comb = [] - test_costs_per_comb = [] - opt_params_per_comb = [] - # we execute multiple runs in order to see the average behaviour - for tmp_seed in range(seed, seed + n_run): - key = jax.random.PRNGKey(tmp_seed) - assert len(X.shape) == 2 # X must be a matrix - assert len(y.shape) == 1 # y must be an array - assert X.shape[0] == y.shape[0] # compatibility check - - # parameters initialization with gaussian ditribution - initial_params = jax.random.normal(key, shape=(layers * params_per_layer,)) - # update the random key - key = jax.random.split(key)[0] - - params = jnp.copy(initial_params) - - # optimizer initialization - opt_state = optimizer.init(initial_params) - - # lists for saving single run training and test cost trend - costs = [] - test_costs = [] - - for epoch in range(epochs): - # generate the list for dropout - keep_rot = make_dropout(key) - # update the random key - key = jax.random.split(key)[0] - - # optimization step - params, opt_state, cost = optimizer_update(opt_state, params, X, y, keep_rot) - - ############## performance evaluation ############# - # inference is done with the original model - # with all the gates - keep_rot = jnp.array( - [ - [list(range((n_qubits))) for j in range(1, inner_layers + 1)] - for i in range(layers) - ] - ) - # inference on train set - cost = calculate_mse_cost(X, y, params, keep_rot) - - costs.append(cost) - - # inference on test set - test_cost = calculate_mse_cost(X_test, y_test, params, keep_rot) - test_costs.append(test_cost) - - # we print updates every 5 iterations - if epoch % 5 == 0: - print( - f"{layer_drop_rate:.1f}-{rot_drop_rate:.1f}", - f"run {tmp_seed-seed} - epoch {epoch}/{epochs}", - f"--- Train cost:{cost:.5f}", - f"--- Test cost:{test_cost:.5f}", - end="\r", - ) - - costs_per_comb.append(costs) - test_costs_per_comb.append(test_costs) - opt_params_per_comb.append(params) - print() - costs_per_comb = np.array(costs_per_comb) - test_costs_per_comb = np.array(test_costs_per_comb) - opt_params_per_comb = np.array(opt_params_per_comb) - - train_history[(layer_drop_rate, rot_drop_rate)] = costs_per_comb - test_history[(layer_drop_rate, rot_drop_rate)] = test_costs_per_comb - opt_params[(layer_drop_rate, rot_drop_rate)] = opt_params_per_comb - -###################################################################### -# Performance evaluation -# ---------------------- -# -# Let’s compare the difference in performance with a plot: -# - -fig, axs = plt.subplots(1, 2, figsize=(12, 4)) -plt.subplots_adjust(wspace=0.05) -axs[0].set_title("MSE train") -for k, v in train_history.items(): - train_losses = np.array(v) - mean_train_history = np.mean(train_losses, axis=0) - std_train_history = np.std(train_losses, axis=0,) - - mean_train_history = mean_train_history.reshape((epochs,)) - std_train_history = std_train_history.reshape((epochs,)) - - # shadow standard deviation - axs[0].fill_between( - range(epochs), - mean_train_history - std_train_history, - mean_train_history + std_train_history, - alpha=0.2, - ) - # average trend - axs[0].plot(range(epochs), mean_train_history, label=f"{k}") # Avg Loss - -axs[1].set_title("MSE test") -for k, v in test_history.items(): - test_losses = np.array(v) - mean_test_history = np.mean(test_losses, axis=0) - std_test_history = np.std(test_losses, axis=0,) - - mean_test_history = mean_test_history.reshape((epochs,)) - std_test_history = std_test_history.reshape((epochs,)) - - # shadow standard deviation - axs[1].fill_between( - range(epochs), - mean_test_history - std_test_history, - mean_test_history + std_test_history, - alpha=0.2, - ) - # averange trend - axs[1].plot(range(epochs), mean_test_history, label=f"{k}") # Avg Loss - -axs[0].legend(loc="upper center", bbox_to_anchor=(1.01, 1.25), ncol=4, fancybox=True, shadow=True) - -for ax in axs.flat: - ax.set_xlabel("Epochs") - ax.set_ylabel("MSE") - ax.set_yscale("log") - ax.set_ylim([1e-3, 0.6]) - ax.label_outer() - -plt.subplots_adjust(bottom=0.3) - -plt.show() - -###################################################################### -# On the left you can see that without dropout there is a deep minimization of the training loss, -# moderate values of dropout converge, whereas high drop probabilities impede any learning. On -# the right, we can see the difference in generalization during the optimization process. Standard -# training without dropout initially reaches a low value of generalization error, but as the -# model starts to learn the noise in the training data (overfitting), the generalization error grows -# back. Oppositely, moderate values of dropout enable generalization errors comparable to the respective -# training ones. As the learning is not successful for elevated drop probabilities, the generalization -# error is huge. It is interesting to notice that the “not-learning” error is very close to the final -# error of the QNN trained without dropout. -# -# Hence, one can conclude that low values of dropout greatly improve the generalization performance of -# the model and remove overfitting, even if the randomness of the technique inevitably makes the -# training a little noisy. On the other hand, high drop probabilities only hinder the training -# process. -# -# Validation -# ~~~~~~~~~~ -# -# To validate the technique we can also check how the model predicts in the whole :math:`[-1,1]` range -# with and without quantum dropout. -# - -X, X_test, y, y_test = make_sin_dataset(dataset_size=20, test_size=0.25) - -# spanning the whole range -x_ax = jnp.linspace(-1, 1, 100).reshape(100, 1) - -# selecting which run we want to plot -run = 1 - -fig, ax = plt.subplots() -styles = ["dashed", "-.", "solid", "-."] -for i, k in enumerate(train_history.keys()): - if k[0] == 0.3: - alpha = 1 - else: - alpha = 0.5 - # predicting and rescaling - yp = scaler.inverse_transform(qnn(x_ax, opt_params[k][run], keep_rot).reshape(-1, 1)) - plt.plot([[i] for i in np.linspace(-1, 1, 100)], yp, label=k, alpha=alpha, linestyle=styles[i]) - -plt.scatter(X, y, label="Training", zorder=10) -plt.scatter(X_test, y_test, label="Test", zorder=10) - -ylabel = r"$y = \sin(\pi\cdot x) + \epsilon$" -plt.xlabel("x", fontsize="medium") -plt.ylabel(ylabel, fontsize="medium") -plt.legend() -ax.xaxis.set_major_locator(ticker.MultipleLocator(0.5)) -ax.yaxis.set_major_locator(ticker.MultipleLocator(0.5)) - -plt.show() - -###################################################################### -# The model without dropout overfits the noisy data by trying to exactly predict each of them, -# whereas dropout actually mitigates overfitting and makes the approximation of the underlying sinusoidal -# function way smoother. -# -# Conclusion -# ---------------------- -# In this demo, we explained the basic idea behind quantum dropout and -# how to avoid overfitting by randomly "dropping" some rotation gates -# of a QNN during the training phase. We invite you to check out the paper [#dropout]_ -# for more dropout techniques and additional analysis. Try it yourself and develop new -# dropout strategies. -# -# -# References -# ---------- -# -# .. [#dropout] Scala, F., Ceschini, A., Panella, M., & Gerace, D. (2023). -# *A General Approach to Dropout in Quantum Neural Networks*. -# `Adv. Quantum Technol., 2300220 `__. -# -# .. [#Hinton2012] Hinton, G., Srivastava, N., Krizhevsky, A., Sutskever, I., & Salakhutdinov, R. (2012). -# *Improving neural networks by preventing co-adaptation of feature detectors*. -# `arXiv:1207.0580. `__. -# -# .. [#Srivastava2014] Srivastava, N., Hinton, G., Krizhevsky, A., Sutskever, I., & Salakhutdinov, R. (2014). -# *Dropout: A Simple Way to Prevent Neural Networks from Overfitting*. -# `Journal of Machine Learning Research, 15(56):1929−1958. `__. -# -# .. [#Kiani2020] Kiani,B. T., Lloyd, S., & Maity, R. (2020). -# *Learning Unitaries by Gradient Descent*. -# `arXiv: 2001.11897. `__. -# -# .. [#Larocca2023] Larocca, M., Ju, N., García-Martín, D., Coles, P. J., & Cerezo, M. (2023). -# *Theory of overparametrization in quantum neural networks*. -# `Nat. Comp. Science, 3, 542–551. `__. -# -# About the author -# ---------------- +r"""Dropout for Quantum Neural Networks +=================================== +""" + +###################################################################### +# Are you struggling with overfitting while training Quantum Neural Networks (QNNs)? +# +# In this demo, we show how to exploit the quantum version of the dropout technique to avoid the problem of +# overfitting in overparametrized QNNs. What follows is based on the paper “A General +# Approach to Dropout in Quantum Neural Networks” by F. Scala, et al. [#dropout]_. +# +# .. figure:: ../_static/demonstration_assets/quantum_dropout/socialthumbnail_large_QuantumDropout_2024-03-07.png +# :align: center +# :width: 60% +# :target: javascript:void(0) +# +# +# What is overfitting and dropout? +# --------------------------------- +# +# Neural Networks (NNs) usually require highly flexible models with lots of trainable parameters in +# order to *learn* a certain underlying function (or data distribution). +# However, being able to learn with low in-sample error is not enough; *generalization* — the ability to provide +# good predictions on previously unseen data — is also desirable. +# +# Highly expressive models may suffer from **overfitting**, which means that +# they are trained too well on the training data, and as a result perform poorly on new, unseen +# data. This happens because the model has learned the noise in the training data, rather than the +# underlying pattern that is generalizable to new data. +# +# **Dropout** is a common technique for classical Deep Neural Networks (DNNs) preventing computational units +# from becoming too specialized and reducing the risk of overfitting [#Hinton2012]_, [#Srivastava2014]_. It consists of randomly removing +# neurons or connections *only during training* to block the flow of information. Once the +# model is trained, the DNN is employed in its original form. +# +# Why dropout for Quantum Neural Networks? +# ---------------------------------------- +# +# Recently, it has been shown that the use of overparametrized QNN models +# changes the optimization landscape by removing lots of local minima [#Kiani2020]_, [#Larocca2023]_. On the one hand, this increased number of +# parameters leads to faster and easier training, but on the other hand, it may drive +# the model to overfit the data. This is also strictly related to the `repeated encoding `__ of classical +# data to achieve nonlinearity in the computation. This is why, inspired from classical DNNs, one +# can think of applying some sort of dropout to QNNs. This would correspond to randomly dropping some +# (groups of) parameterized gates during training to achieve better generalization. +# +# Quantum dropout of rotations in a sine regression +# -------------------------------------------------- +# +# In this demo we will exploit quantum dropout to avoid overfitting during the regression of noisy +# data originally coming from the *sinusoidal* function. In particular, we will randomly “drop” +# rotations during the training phase. In practice, this will correspond to temporarily setting parameters to a value of 0. +# +# Let’s start by importing Pennylane and ``numpy`` and fixing the random seed for reproducibility: +# + +import numpy as np +import pennylane as qml + +seed = 12345 +np.random.seed(seed=seed) + +###################################################################### +# The circuit +# ~~~~~~~~~~~ +# +# Now we define the embedding of classical data and the variational ansatz that will then be combined +# to construct our QNN. Dropout will happen inside the variational ansatz. Obtaining dropout with standard +# Pennylane would be quite straightforward by means of some "if statements", but the training procedure +# will take ages. Here we will leverage JAX in order to speed up the training process with +# Just In Time (JIT) compilation. The drawback is that the definition of the variational ansatz becomes a +# little elaborated, since JAX has its own language for conditional statements. For this purpose we +# define two functions ``true_cond`` and ``false_cond`` to work with ``jax.lax.cond```, which is the JAX +# conditional statement. See this `demo `__ +# for additional insights on how to optimize QNNs with JAX. +# +# Practically speaking, rotation dropout will be performed by passing a list to the ansatz. +# The single qubit rotations are applied depending on the values stored in this list: +# if the value is negative the rotation is dropped (rotation dropout), otherwise it is applied. +# How to produce this list will be explained later in this demo (see the ``make_dropout`` function). + +import jax # require for Just In Time (JIT) compilation +import jax.numpy as jnp + +jax.config.update("jax_platform_name", "cpu") +jax.config.update("jax_enable_x64", True) + + +def embedding(x, wires): + # Encodes the datum multiple times in the register, + # employing also nonlinear functions + assert len(x) == 1 # check feature is 1-D + for i in wires: + qml.RY(jnp.arcsin(x), wires=i) + for i in wires: + qml.RZ(jnp.arccos(x ** 2), wires=i) + + +def true_cond(angle): + # necessary for using an if statement within jitted function + # exploiting jax.lax.cond + # if this function is assessed the rotation is dropped + return 0.0 + + +def false_cond(angle): + # necessary for using an if statement within jitted function + # exploiting jax.lax.cond + # if this function is assessed the rotation is kept + return angle + + +def var_ansatz( + theta, wires, rotations=[qml.RX, qml.RZ, qml.RX], entangler=qml.CNOT, keep_rotation=None +): + + """Single layer of the variational ansatz for our QNN. + We have a single qubit rotation per each qubit (wire) followed by + a linear chain of entangling gates (entangler). This structure is repeated per each rotation in `rotations` + (defining `inner_layers`). + The single qubit rotations are applied depending on the values stored in `keep_rotation`: + if the value is negative the rotation is dropped (rotation dropout), otherwise it is applied. + + Params: + - theta: variational angles that will undergo optimization + - wires: list of qubits (wires) + - rotations: list of rotation kind per each `inner_layer` + - entangler: entangling gate + - keep_rotation: list of lists. There is one list per each `inner_layer`. + In each list there are indexes of the rotations that we want to apply. + Some of these values may be substituted by -1 value + which means that the rotation gate wont be applied (dropout). + """ + + # the length of `rotations` defines the number of inner layers + N = len(wires) + assert len(theta) == 3 * N + wires = list(wires) + + counter = 0 + # keep_rotations contains a list per each inner_layer + for rots in keep_rotation: + # we cicle over the elements of the lists inside keep_rotation + for qb, keep_or_drop in enumerate(rots): + rot = rotations[counter] # each inner layer can have a different rotation + + angle = theta[counter * N + qb] + # conditional statement implementing dropout + # if `keep_or_drop` is negative the rotation is dropped + angle_drop = jax.lax.cond(keep_or_drop < 0, true_cond, false_cond, angle) + rot(angle_drop, wires=wires[qb]) + for qb in wires[:-1]: + entangler(wires=[wires[qb], wires[qb + 1]]) + counter += 1 + + +###################################################################### +# And then we define the hyperparameters of our QNN, namely the number of qubits, +# the number of sublayers in the variational ansatz (``inner_layers``) and the resulting +# number of parameters per layer: +# + +n_qubits = 5 +inner_layers = 3 +params_per_layer = n_qubits * inner_layers + +###################################################################### +# Now we actually build the QNN: +# + + +def create_circuit(n_qubits, layers): + device = qml.device("default.qubit", wires=n_qubits) + + @qml.qnode(device) + def circuit(x, theta, keep_rot): + # print(x) + # print(theta) + + for i in range(layers): + embedding(x, wires=range(n_qubits)) + + keep_rotation = keep_rot[i] + + var_ansatz( + theta[i * params_per_layer : (i + 1) * params_per_layer], + wires=range(n_qubits), + entangler=qml.CNOT, + keep_rotation=keep_rotation, + ) + + return qml.expval(qml.PauliZ(wires=0)) # we measure only the first qubit + + return circuit + + +###################################################################### +# Let’s have a look at a single layer of our QNN: +# +import matplotlib.pyplot as plt + + +plt.style.use("pennylane.drawer.plot") # set pennylane theme, which is nice to see + +# create the circuit with given number of qubits and layers +layers = 1 +circ = create_circuit(n_qubits, layers=layers) + +# for the moment let's keep all the rotations in all sublayers +keep_all_rot = [ + [list(range((n_qubits))) for j in range(1, inner_layers + 1)], +] +# we count the parameters +numbered_params = np.array(range(params_per_layer * layers), dtype=float) +# we encode a single coordinate +single_sample = np.array([0]) + +qml.draw_mpl(circ, decimals=2,)(single_sample, numbered_params, keep_all_rot) + +plt.show() + +###################################################################### +# We now build the model that we will employ for the regression task. +# Since we want to have an overparametrized QNN, we will add 10 layers and we exploit +# ``JAX`` to speed the training up: +# + +layers = 10 +qnn_tmp = create_circuit(n_qubits, layers) +qnn_tmp = jax.jit(qnn_tmp) +qnn_batched = jax.vmap( + qnn_tmp, (0, None, None) +) # we want to vmap on 0-axis of the first circuit param +# in this way we process in parallel all the inputs +# We jit for faster execution +qnn = jax.jit(qnn_batched) + + +###################################################################### +# Dropping rotations +# ~~~~~~~~~~~~~~~~~~ +# +# As anticipated, we need to set some random parameters to 0 at each optimization step. Given a layer +# dropout rate :math:`p_L` (this will be called ``layer_drop_rate``) and the gate dropout rate :math:`p_G` +# (this will be called ``rot_drop_rate``), the probability :math:`p` that a +# gate is dropped in a layer can be calculated with the conditioned probability law: +# +# .. math:: +# +# +# p=p(A\cap B)=p(A|B)p(B)=p_Gp_L +# +# where :math:`B` represents the selection of a specific layer and +# :math:`A` the selection of a specific gate within the chosen layer. +# +# In the following cell we define a function that produces the list of the indices of rotation gates that +# are kept. For gates which are dropped, the value ``-1`` is assigned to the corresponding index. The structure of the list +# is nested; we have one list per ``inner_layer`` inside one list per each layer, all contained in another list. +# This function will be called at each iteration. +# + + +def make_dropout(key): + drop_layers = [] + + for lay in range(layers): + # each layer has prob p_L=layer_drop_rate of being dropped + # according to that for every layer we sample + # if we have to appy dropout in it or not + out = jax.random.choice( + key, jnp.array(range(2)), p=jnp.array([1 - layer_drop_rate, layer_drop_rate]) + ) + key = jax.random.split(key)[0] # update the random key + + if out == 1: # if it has to be dropped + drop_layers.append(lay) + + keep_rot = [] + # we make list of indexes corresponding to the rotations gates + # that are kept in the computation during a single train step + for i in range(layers): + # each list is divded in layers and then in "inner layers" + # this is strictly related to the QNN architecture that we use + keep_rot_layer = [list(range((n_qubits))) for j in range(1, inner_layers + 1)] + + if i in drop_layers: # if dropout has to be applied in this layer + keep_rot_layer = [] # list of indexes for a single layer + inner_keep_r = [] # list of indexes for a single inner layer + for param in range(params_per_layer): + # each rotation within the layer has prob p=rot_drop_rate of being dropped + # according to that for every parameter (rotation) we sample + # if we have to drop it or not + out = jax.random.choice( + key, jnp.array(range(2)), p=jnp.array([1 - rot_drop_rate, rot_drop_rate]) + ) + key = jax.random.split(key)[0] # update the random key + + if out == 0: # if we have to keep it + inner_keep_r.append(param % n_qubits) # % is required because we work + # inner layer by inner layer + else: # if the rotation has to be dropped + inner_keep_r.append(-1) # we assign the value -1 + + if param % n_qubits == n_qubits - 1: # if it's the last qubit of the register + # append the inner layer list + keep_rot_layer.append(inner_keep_r) + # and reset it + inner_keep_r = [] + + keep_rot.append(keep_rot_layer) + + return jnp.array(keep_rot) + + +###################################################################### +# We can check the output of the ``make_dropout`` function: +# + +# setting the drop probability +layer_drop_rate, rot_drop_rate = 0.5, 0.3 # 15% probability of dropping a gate + +# JAX random key +key = jax.random.PRNGKey(12345) +# create the list of indexes, +# -1 implies we are dropping a gate +keep_rot = make_dropout(key) + +# let's just print the list for first layer +print(keep_rot[0]) + +###################################################################### +# Noisy sinusoidal function +# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# To test the effectiveness of the dropout technique, we will use a prototypical dataset +# with which it is very easy to overfit: the sinusoidal function. We produce some +# points according to the :math:`\sin` function and then we add some white Gaussian noise +# (noise that follows a normal distribution) :math:`\epsilon.` The noise is essential to obtain overfitting; +# when our model is extremely expressive, it is capable of exactly fit each point and some parameters +# become hyper-specialized in recognizing the noisy features. This makes predictions on new unseen +# data difficult, since the overfitting model did not learn the true underlying data distribution. +# The dropout technique will help in avoiding co-adaptation and hyper-specialization, +# effectively reducing overfitting. +# + +from sklearn.model_selection import train_test_split + + +def make_sin_dataset(dataset_size=100, test_size=0.4, noise_value=0.4, plot=False): + """1D regression problem y=sin(x*\pi)""" + # x-axis + x_ax = np.linspace(-1, 1, dataset_size) + y = [[np.sin(x * np.pi)] for x in x_ax] + np.random.seed(123) + # noise vector + noise = np.array([np.random.normal(0, 0.5, 1) for i in y]) * noise_value + X = np.array(x_ax) + y = np.array(y + noise) # apply noise + + # split the dataset + X_train, X_test, y_train, y_test = train_test_split( + X, y, test_size=test_size, random_state=40, shuffle=True + ) + + X_train = X_train.reshape(-1, 1) + X_test = X_test.reshape(-1, 1) + + y_train = y_train.reshape(-1, 1) + y_test = y_test.reshape(-1, 1) + + return X_train, X_test, y_train, y_test + + +from matplotlib import ticker + +X, X_test, y, y_test = make_sin_dataset(dataset_size=20, test_size=0.25) + + +fig, ax = plt.subplots() +plt.plot(X, y, "o", label="Training") +plt.plot(X_test, y_test, "o", label="Test") + +plt.plot( + np.linspace(-1, 1, 100), + [np.sin(x * np.pi) for x in np.linspace(-1, 1, 100)], + linestyle="dotted", + label=r"$\sin(x)$", +) +plt.ylabel(r"$y = \sin(\pi\cdot x) + \epsilon$") +plt.xlabel(r"$x$") +ax.xaxis.set_major_locator(ticker.MultipleLocator(0.5)) +ax.yaxis.set_major_locator(ticker.MultipleLocator(0.5)) +plt.legend() + +plt.show() + +###################################################################### +# Since our circuit is only able to provide outputs in the range :math:`[-1,1],` we rescale all the +# noisy data within this range. To do this we leverage the `MinMaxScaler` from `sklearn`. +# It is common practice to fit the scaler only from training data and then apply it also to the +# test. The reason behind this is that in general one only has knowledge about the training dataset. +# (If the training dataset is not exhaustively representative of the underlying distribution, +# this preprocessing may lead to some outliers in the test set to be scaled out of the desired range.) +# + +from sklearn.preprocessing import MinMaxScaler + +scaler = MinMaxScaler(feature_range=(-1, 1)) +y = scaler.fit_transform(y) +y_test = scaler.transform(y_test) + +# reshaping for computation +y = y.reshape(-1,) +y_test = y_test.reshape(-1,) + +###################################################################### +# Optimization +# ~~~~~~~~~~~~ +# +# At this point we have to set the hyperparameters of the optimization, namely the number of epochs, the +# learning rate, and the optimizer: +# + +import optax # optimization using jax + +epochs = 700 +optimizer = optax.adam(learning_rate=0.01) + +###################################################################### +# We define the cost function as the Mean Square Error: +# + + +@jax.jit +def calculate_mse_cost(X, y, theta, keep_rot): + yp = qnn(X, theta, keep_rot) + # depending on your version of Pennylane you may require the following line + ##### + yp = jnp.array(yp).T + ##### + cost = jnp.mean((yp - y) ** 2) + + return cost + + +# Optimization update step +@jax.jit +def optimizer_update(opt_state, params, x, y, keep_rot): + loss, grads = jax.value_and_grad(lambda theta: calculate_mse_cost(x, y, theta, keep_rot))( + params + ) + updates, opt_state = optimizer.update(grads, opt_state) + + params = optax.apply_updates(params, updates) + return params, opt_state, loss + + +###################################################################### +# Training the model +# ------------------ +# +# And now we can try to train the model. We execute different runs of the training to understand the +# average behaviour of quantum dropout. To see the effect of dropout we can set different values of +# ``layer_drop_rate`` and ``rot_drop_rate``: +# + +n_run = 3 +drop_rates = [(0.0, 0.0), (0.3, 0.2), (0.7, 0.7)] + +train_history = {} +test_history = {} +opt_params = {} + + +for layer_drop_rate, rot_drop_rate in drop_rates: + # initialization of some lists to store data + costs_per_comb = [] + test_costs_per_comb = [] + opt_params_per_comb = [] + # we execute multiple runs in order to see the average behaviour + for tmp_seed in range(seed, seed + n_run): + key = jax.random.PRNGKey(tmp_seed) + assert len(X.shape) == 2 # X must be a matrix + assert len(y.shape) == 1 # y must be an array + assert X.shape[0] == y.shape[0] # compatibility check + + # parameters initialization with gaussian ditribution + initial_params = jax.random.normal(key, shape=(layers * params_per_layer,)) + # update the random key + key = jax.random.split(key)[0] + + params = jnp.copy(initial_params) + + # optimizer initialization + opt_state = optimizer.init(initial_params) + + # lists for saving single run training and test cost trend + costs = [] + test_costs = [] + + for epoch in range(epochs): + # generate the list for dropout + keep_rot = make_dropout(key) + # update the random key + key = jax.random.split(key)[0] + + # optimization step + params, opt_state, cost = optimizer_update(opt_state, params, X, y, keep_rot) + + ############## performance evaluation ############# + # inference is done with the original model + # with all the gates + keep_rot = jnp.array( + [ + [list(range((n_qubits))) for j in range(1, inner_layers + 1)] + for i in range(layers) + ] + ) + # inference on train set + cost = calculate_mse_cost(X, y, params, keep_rot) + + costs.append(cost) + + # inference on test set + test_cost = calculate_mse_cost(X_test, y_test, params, keep_rot) + test_costs.append(test_cost) + + # we print updates every 5 iterations + if epoch % 5 == 0: + print( + f"{layer_drop_rate:.1f}-{rot_drop_rate:.1f}", + f"run {tmp_seed-seed} - epoch {epoch}/{epochs}", + f"--- Train cost:{cost:.5f}", + f"--- Test cost:{test_cost:.5f}", + end="\r", + ) + + costs_per_comb.append(costs) + test_costs_per_comb.append(test_costs) + opt_params_per_comb.append(params) + print() + costs_per_comb = np.array(costs_per_comb) + test_costs_per_comb = np.array(test_costs_per_comb) + opt_params_per_comb = np.array(opt_params_per_comb) + + train_history[(layer_drop_rate, rot_drop_rate)] = costs_per_comb + test_history[(layer_drop_rate, rot_drop_rate)] = test_costs_per_comb + opt_params[(layer_drop_rate, rot_drop_rate)] = opt_params_per_comb + +###################################################################### +# Performance evaluation +# ---------------------- +# +# Let’s compare the difference in performance with a plot: +# + +fig, axs = plt.subplots(1, 2, figsize=(12, 4)) +plt.subplots_adjust(wspace=0.05) +axs[0].set_title("MSE train") +for k, v in train_history.items(): + train_losses = np.array(v) + mean_train_history = np.mean(train_losses, axis=0) + std_train_history = np.std(train_losses, axis=0,) + + mean_train_history = mean_train_history.reshape((epochs,)) + std_train_history = std_train_history.reshape((epochs,)) + + # shadow standard deviation + axs[0].fill_between( + range(epochs), + mean_train_history - std_train_history, + mean_train_history + std_train_history, + alpha=0.2, + ) + # average trend + axs[0].plot(range(epochs), mean_train_history, label=f"{k}") # Avg Loss + +axs[1].set_title("MSE test") +for k, v in test_history.items(): + test_losses = np.array(v) + mean_test_history = np.mean(test_losses, axis=0) + std_test_history = np.std(test_losses, axis=0,) + + mean_test_history = mean_test_history.reshape((epochs,)) + std_test_history = std_test_history.reshape((epochs,)) + + # shadow standard deviation + axs[1].fill_between( + range(epochs), + mean_test_history - std_test_history, + mean_test_history + std_test_history, + alpha=0.2, + ) + # averange trend + axs[1].plot(range(epochs), mean_test_history, label=f"{k}") # Avg Loss + +axs[0].legend(loc="upper center", bbox_to_anchor=(1.01, 1.25), ncol=4, fancybox=True, shadow=True) + +for ax in axs.flat: + ax.set_xlabel("Epochs") + ax.set_ylabel("MSE") + ax.set_yscale("log") + ax.set_ylim([1e-3, 0.6]) + ax.label_outer() + +plt.subplots_adjust(bottom=0.3) + +plt.show() + +###################################################################### +# On the left you can see that without dropout there is a deep minimization of the training loss, +# moderate values of dropout converge, whereas high drop probabilities impede any learning. On +# the right, we can see the difference in generalization during the optimization process. Standard +# training without dropout initially reaches a low value of generalization error, but as the +# model starts to learn the noise in the training data (overfitting), the generalization error grows +# back. Oppositely, moderate values of dropout enable generalization errors comparable to the respective +# training ones. As the learning is not successful for elevated drop probabilities, the generalization +# error is huge. It is interesting to notice that the “not-learning” error is very close to the final +# error of the QNN trained without dropout. +# +# Hence, one can conclude that low values of dropout greatly improve the generalization performance of +# the model and remove overfitting, even if the randomness of the technique inevitably makes the +# training a little noisy. On the other hand, high drop probabilities only hinder the training +# process. +# +# Validation +# ~~~~~~~~~~ +# +# To validate the technique we can also check how the model predicts in the whole :math:`[-1,1]` range +# with and without quantum dropout. +# + +X, X_test, y, y_test = make_sin_dataset(dataset_size=20, test_size=0.25) + +# spanning the whole range +x_ax = jnp.linspace(-1, 1, 100).reshape(100, 1) + +# selecting which run we want to plot +run = 1 + +fig, ax = plt.subplots() +styles = ["dashed", "-.", "solid", "-."] +for i, k in enumerate(train_history.keys()): + if k[0] == 0.3: + alpha = 1 + else: + alpha = 0.5 + # predicting and rescaling + yp = scaler.inverse_transform(qnn(x_ax, opt_params[k][run], keep_rot).reshape(-1, 1)) + plt.plot([[i] for i in np.linspace(-1, 1, 100)], yp, label=k, alpha=alpha, linestyle=styles[i]) + +plt.scatter(X, y, label="Training", zorder=10) +plt.scatter(X_test, y_test, label="Test", zorder=10) + +ylabel = r"$y = \sin(\pi\cdot x) + \epsilon$" +plt.xlabel("x", fontsize="medium") +plt.ylabel(ylabel, fontsize="medium") +plt.legend() +ax.xaxis.set_major_locator(ticker.MultipleLocator(0.5)) +ax.yaxis.set_major_locator(ticker.MultipleLocator(0.5)) + +plt.show() + +###################################################################### +# The model without dropout overfits the noisy data by trying to exactly predict each of them, +# whereas dropout actually mitigates overfitting and makes the approximation of the underlying sinusoidal +# function way smoother. +# +# Conclusion +# ---------------------- +# In this demo, we explained the basic idea behind quantum dropout and +# how to avoid overfitting by randomly "dropping" some rotation gates +# of a QNN during the training phase. We invite you to check out the paper [#dropout]_ +# for more dropout techniques and additional analysis. Try it yourself and develop new +# dropout strategies. +# +# +# References +# ---------- +# +# .. [#dropout] Scala, F., Ceschini, A., Panella, M., & Gerace, D. (2023). +# *A General Approach to Dropout in Quantum Neural Networks*. +# `Adv. Quantum Technol., 2300220 `__. +# +# .. [#Hinton2012] Hinton, G., Srivastava, N., Krizhevsky, A., Sutskever, I., & Salakhutdinov, R. (2012). +# *Improving neural networks by preventing co-adaptation of feature detectors*. +# `arXiv:1207.0580. `__. +# +# .. [#Srivastava2014] Srivastava, N., Hinton, G., Krizhevsky, A., Sutskever, I., & Salakhutdinov, R. (2014). +# *Dropout: A Simple Way to Prevent Neural Networks from Overfitting*. +# `Journal of Machine Learning Research, 15(56):1929−1958. `__. +# +# .. [#Kiani2020] Kiani,B. T., Lloyd, S., & Maity, R. (2020). +# *Learning Unitaries by Gradient Descent*. +# `arXiv: 2001.11897. `__. +# +# .. [#Larocca2023] Larocca, M., Ju, N., García-Martín, D., Coles, P. J., & Cerezo, M. (2023). +# *Theory of overparametrization in quantum neural networks*. +# `Nat. Comp. Science, 3, 542–551. `__. +# +# About the author +# ---------------- diff --git a/demonstrations_v2/tutorial_quantum_natural_gradient/demo.py b/demonstrations_v2/tutorial_quantum_natural_gradient/demo.py index f693b3d3aa..d65bd9e2d5 100644 --- a/demonstrations_v2/tutorial_quantum_natural_gradient/demo.py +++ b/demonstrations_v2/tutorial_quantum_natural_gradient/demo.py @@ -1,499 +1,499 @@ -r""" - -.. _quantum_natural_gradient: - -Quantum natural gradient -======================== - -.. meta:: - :property="og:description": The quantum natural gradient method can achieve faster convergence for quantum machine - learning problems by taking into account the intrinsic geometry of qubits. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qng_optimization.png - -.. related:: - - tutorial_backprop Quantum gradients with backpropagation - tutorial_vqe_qng Accelerating VQE with quantum natural gradient - -*Author: Josh Izaac — Posted: 11 October 2019. Last updated: 25 January 2021.* - -This example demonstrates the quantum natural gradient optimization technique -for variational quantum circuits, originally proposed in -`Stokes et al. (2019) `__. - -Background ----------- - -The most successful class of quantum algorithms for use on near-term noisy quantum hardware -is the so-called variational quantum algorithm. As laid out in the -`Concepts section `__, in variational quantum algorithms -a low-depth parametrized quantum circuit ansatz is chosen, and a problem-specific -observable measured. A classical optimization loop is then used to find -the set of quantum parameters that *minimize* a particular measurement expectation value -of the quantum device. Examples of such algorithms include the :doc:`variational quantum -eigensolver (VQE) `, the -`quantum approximate optimization algorithm (QAOA) `__, -and :ref:`quantum neural networks (QNN) `. - -Most recent demonstrations -of variational quantum algorithms have used gradient-free classical optimization -methods, such as the Nelder-Mead algorithm. However, the parameter-shift rule -(as implemented in PennyLane) allows the user to automatically compute -analytic gradients of quantum circuits. This opens up the possibility to train -quantum computing hardware using gradient descent—the same method used to train -deep learning models. -Though one caveat has surfaced with gradient descent — how do we choose the optimal -step size for our variational quantum algorithms, to ensure successful and -efficient optimization? - -The natural gradient -^^^^^^^^^^^^^^^^^^^^ - -In standard gradient descent, each optimization step is given by - -.. math:: \theta_{t+1} = \theta_t -\eta \nabla \mathcal{L}(\theta), - -where :math:`\mathcal{L}(\theta)` is the cost as a function of -the parameters :math:`\theta,` and :math:`\eta` is the learning rate -or step size. In essence, each optimization step calculates the -steepest descent direction around the local value of :math:`\theta_t` -in the parameter space, and updates :math:`\theta_t\rightarrow \theta_{t+1}` -by this vector. - -The problem with the above approach is that each optimization step -is strongly connected to a *Euclidean geometry* on the parameter space. -The parametrization is not unique, and different parametrizations can distort -distances within the optimization landscape. - -For example, consider the following cost function :math:`\mathcal{L},` parametrized -using two different coordinate systems, :math:`(\theta_0, \theta_1),` and -:math:`(\phi_0, \phi_1):` - -| - -.. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng7.png - :align: center - :width: 90% - :target: javascript:void(0) - -| - -Performing gradient descent in the :math:`(\theta_0, \theta_1)` parameter -space, we are updating each parameter by the same Euclidean distance, -and not taking into account the fact that the cost function might vary at a different -rate with respect to each parameter. - -Instead, if we perform a change of coordinate system (re-parametrization) -of the cost function, we might find a parameter space where variations in :math:`\mathcal{L}` -are similar across different parameters. This is the case with the new parametrization -:math:`(\phi_0, \phi_1);` the cost function is unchanged, -but we now have a nicer geometry in which to perform gradient descent, and a more -informative stepsize. This leads to faster convergence, and can help avoid optimization -becoming stuck in local minima. For a more in-depth explanation, -including why the parameter space might not be best represented by a Euclidean space, -see `Yamamoto (2019) `__. - -However, what if we avoid gradient descent in the parameter space altogether? -If we instead consider the optimization problem as a -probability distribution of possible output values given an input -(i.e., `maximum likelihood estimation `_), -a better approach is to perform the gradient descent in the *distribution space*, which is -dimensionless and invariant with respect to the parametrization. As a result, -each optimization step will always choose the optimum step-size for every -parameter, regardless of the parametrization. - -In classical neural networks, the above process is known as -*natural gradient descent*, and was first introduced by -`Amari (1998) `__. -The standard gradient descent is modified as follows: - -.. math:: \theta_{t+1} = \theta_t - \eta F^{-1}\nabla \mathcal{L}(\theta), - -where :math:`F` is the `Fisher information matrix `__. -The Fisher information matrix acts as a metric tensor, transforming the -steepest descent in the Euclidean parameter space to the steepest descent in the -distribution space. - -The quantum analog -^^^^^^^^^^^^^^^^^^ - -In a similar vein, it has been shown that the standard Euclidean geometry -is sub-optimal for optimization of quantum variational algorithms -`(Harrow and Napp, 2019) `__. -The space of quantum states instead possesses a unique invariant metric -tensor known as the Fubini-Study metric tensor :math:`g_{ij},` which can be used to -construct a quantum analog to natural gradient descent: - -.. math:: \theta_{t+1} = \theta_t - \eta g^{+}(\theta_t)\nabla \mathcal{L}(\theta), - -where :math:`g^{+}` refers to the pseudo-inverse. - -.. note:: - - It can be shown that the Fubini-Study metric tensor reduces - to the Fisher information matrix in the classical limit. - - Furthermore, in the limit where :math:`\eta\rightarrow 0,` - the dynamics of the system are equivalent to imaginary-time - evolution within the variational subspace, as proposed in - `McArdle et al. (2018) `__. - -""" - -############################################################################## -# Block-diagonal metric tensor -# ---------------------------- -# -# A block-diagonal approximation to the Fubini-Study metric tensor -# of a variational quantum circuit can be evaluated on quantum hardware. -# -# Consider a variational quantum circuit -# -# .. math:: -# -# U(\mathbf{\theta})|\psi_0\rangle = V_L(\theta_L) W_L V_{L-1}(\theta_{L-1}) W_{L-1} -# \cdots V_{\ell}(\theta_{\ell}) W_{\ell} \cdots V_{0}(\theta_{0}) W_{0} |\psi_0\rangle -# -# where -# -# * :math:`|\psi_0\rangle` is the initial state, -# * :math:`W_\ell` are layers of non-parametrized quantum gates, -# * :math:`V_\ell(\theta_\ell)` are layers of parametrized quantum gates -# with :math:`n_\ell` parameters :math:`\theta_\ell = \{\theta^{(\ell)}_0, \dots, \theta^{(\ell)}_n\}.` -# -# Further, assume all parametrized gates can be written in the form -# :math:`X(\theta^{(\ell)}_{i}) = e^{i\theta^{(\ell)}_{i} K^{(\ell)}_i},` -# where :math:`K^{(\ell)}_i` is the *generator* of the parametrized operation. -# -# For each parametric layer :math:`\ell` in the variational quantum circuit -# the :math:`n_\ell\times n_\ell` block-diagonal submatrix -# of the Fubini-Study tensor :math:`g_{ij}^{(\ell)}` is calculated by: -# -# .. math:: -# -# g_{ij}^{(\ell)} = \langle \psi_{\ell-1} | K_i K_j | \psi_{\ell-1} \rangle -# - \langle \psi_{\ell-1} | K_i | \psi_{\ell-1}\rangle -# \langle \psi_{\ell-1} |K_j | \psi_{\ell-1}\rangle -# -# where -# -# .. math:: -# -# | \psi_{\ell-1}\rangle = V_{\ell-1}(\theta_{\ell-1}) W_{\ell-1} \cdots V_{0}(\theta_{0}) W_{0} |\psi_0\rangle. -# -# (that is, :math:`|\psi_{\ell-1}\rangle` is the quantum state prior to the application -# of parameterized layer :math:`\ell`), and we have :math:`K_i \equiv K_i^{(\ell)}` for brevity. -# -# Let's consider a small variational quantum circuit example coded in PennyLane: - -import numpy as np -import pennylane as qml -from pennylane import numpy as pnp - -dev = qml.device("lightning.qubit", wires=3) - - -@qml.qnode(dev, interface="autograd", diff_method="parameter-shift") -def circuit(params): - # |psi_0>: state preparation - qml.RY(np.pi / 4, wires=0) - qml.RY(np.pi / 3, wires=1) - qml.RY(np.pi / 7, wires=2) - - # V0(theta0, theta1): Parametrized layer 0 - qml.RZ(params[0], wires=0) - qml.RZ(params[1], wires=1) - - # W1: non-parametrized gates - qml.CNOT(wires=[0, 1]) - qml.CNOT(wires=[1, 2]) - - # V_1(theta2, theta3): Parametrized layer 1 - qml.RY(params[2], wires=1) - qml.RX(params[3], wires=2) - - # W2: non-parametrized gates - qml.CNOT(wires=[0, 1]) - qml.CNOT(wires=[1, 2]) - - return qml.expval(qml.PauliY(0)) - -# Use pennylane.numpy for trainable parameters -params = pnp.array([0.432, -0.123, 0.543, 0.233]) - -############################################################################## -# The above circuit consists of 4 parameters, with two distinct parametrized -# layers of 2 parameters each. -# -# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng1.png -# :align: center -# :width: 90% -# :target: javascript:void(0) -# -# | -# -# (Note that in this example, the first non-parametrized layer :math:`W_0` -# is simply the identity.) Since there are two layers, each with two parameters, -# the block-diagonal approximation consists of two -# :math:`2\times 2` matrices, :math:`g^{(0)}` and :math:`g^{(1)}.` -# -# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng2.png -# :align: center -# :width: 30% -# :target: javascript:void(0) -# -# To compute the first block-diagonal :math:`g^{(0)},` we create subcircuits consisting -# of all gates prior to the layer, and observables corresponding to -# the *generators* of the gates in the layer: -# -# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng3.png -# :align: center -# :width: 30% -# :target: javascript:void(0) - -g0 = np.zeros([2, 2]) - - -def layer0_subcircuit(params): - """This function contains all gates that - precede parametrized layer 0""" - qml.RY(np.pi / 4, wires=0) - qml.RY(np.pi / 3, wires=1) - qml.RY(np.pi / 7, wires=2) - - -############################################################################## -# We then post-process the measurement results in order to determine :math:`g^{(0)},` -# as follows. -# -# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng4.png -# :align: center -# :width: 50% -# :target: javascript:void(0) -# -# We can see that the diagonal terms are simply given by the variance: - - -@qml.qnode(dev, interface="autograd") -def layer0_diag(params): - layer0_subcircuit(params) - return qml.var(qml.PauliZ(0)), qml.var(qml.PauliZ(1)) - - -# calculate the diagonal terms -varK0, varK1 = layer0_diag(params) -g0[0, 0] = varK0 / 4 -g0[1, 1] = varK1 / 4 - -############################################################################## -# The following two subcircuits are then used to calculate the -# off-diagonal covariance terms of :math:`g^{(0)}:` - - -@qml.qnode(dev, interface="autograd") -def layer0_off_diag_single(params): - layer0_subcircuit(params) - return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)) - - -@qml.qnode(dev, interface="autograd") -def layer0_off_diag_double(params): - layer0_subcircuit(params) - ZZ = np.kron(np.diag([1, -1]), np.diag([1, -1])) - return qml.expval(qml.Hermitian(ZZ, wires=[0, 1])) - - -# calculate the off-diagonal terms -exK0, exK1 = layer0_off_diag_single(params) -exK0K1 = layer0_off_diag_double(params) - -g0[0, 1] = (exK0K1 - exK0 * exK1) / 4 -g0[1, 0] = (exK0K1 - exK0 * exK1) / 4 - -############################################################################## -# Note that, by definition, the block-diagonal matrices must be real and -# symmetric. -# -# We can repeat the above process to compute :math:`g^{(1)}.` The subcircuit -# required is given by -# -# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng8.png -# :align: center -# :width: 50% -# :target: javascript:void(0) - -g1 = np.zeros([2, 2]) - - -def layer1_subcircuit(params): - """This function contains all gates that - precede parametrized layer 1""" - # |psi_0>: state preparation - qml.RY(np.pi / 4, wires=0) - qml.RY(np.pi / 3, wires=1) - qml.RY(np.pi / 7, wires=2) - - # V0(theta0, theta1): Parametrized layer 0 - qml.RZ(params[0], wires=0) - qml.RZ(params[1], wires=1) - - # W1: non-parametrized gates - qml.CNOT(wires=[0, 1]) - qml.CNOT(wires=[1, 2]) - - -############################################################################## -# Using this subcircuit, we can now generate the submatrix :math:`g^{(1)}.` -# -# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng5.png -# :align: center -# :width: 50% -# :target: javascript:void(0) - - -@qml.qnode(dev, interface="autograd") -def layer1_diag(params): - layer1_subcircuit(params) - return qml.var(qml.PauliY(1)), qml.var(qml.PauliX(2)) - - -############################################################################## -# As previously, the diagonal terms are simply given by the variance, - -varK0, varK1 = layer1_diag(params) -g1[0, 0] = varK0 / 4 -g1[1, 1] = varK1 / 4 - - -############################################################################## -# while the off-diagonal terms require covariance between the two -# observables to be computed. - - -@qml.qnode(dev, interface="autograd") -def layer1_off_diag_single(params): - layer1_subcircuit(params) - return qml.expval(qml.PauliY(1)), qml.expval(qml.PauliX(2)) - - -@qml.qnode(dev, interface="autograd") -def layer1_off_diag_double(params): - layer1_subcircuit(params) - X = np.array([[0, 1], [1, 0]]) - Y = np.array([[0, -1j], [1j, 0]]) - YX = np.kron(Y, X) - return qml.expval(qml.Hermitian(YX, wires=[1, 2])) - - -# calculate the off-diagonal terms -exK0, exK1 = layer1_off_diag_single(params) -exK0K1 = layer1_off_diag_double(params) - -g1[0, 1] = (exK0K1 - exK0 * exK1) / 4 -g1[1, 0] = g1[0, 1] - - -############################################################################## -# Putting this altogether, the block-diagonal approximation to the Fubini-Study -# metric tensor for this variational quantum circuit is -from scipy.linalg import block_diag - -g = block_diag(g0, g1) -print(np.round(g, 8)) - - -############################################################################## -# PennyLane contains a built-in function for computing the Fubini-Study metric -# tensor, :func:`~.pennylane.metric_tensor`, which -# we can use to verify this result: -print(np.round(qml.metric_tensor(circuit, approx="block-diag")(params), 8)) - -############################################################################## -# As opposed to our manual computation, which required 6 different quantum -# evaluations, the PennyLane Fubini-Study metric tensor implementation -# requires only 2 quantum evaluations, one per layer. This is done by -# automatically detecting the layer structure, and noting that every -# observable that must be measured commutes, allowing for simultaneous measurement. -# -# Therefore, by combining the quantum natural gradient optimizer with the analytic -# parameter-shift rule to optimize a variational circuit with :math:`d` parameters -# and :math:`L` parametrized layers, a total of :math:`2d+L` quantum evaluations -# are required per optimization step. -# -# Note that the :func:`~.pennylane.metric_tensor` function also supports computing the diagonal -# approximation to the metric tensor: -print(qml.metric_tensor(circuit, approx='diag')(params)) - -############################################################################## -# Furthermore, the returned metric tensor is **full differentiable**; include it -# in your cost function, and train or optimize its value! - -############################################################################## -# Quantum natural gradient optimization -# ------------------------------------- -# -# PennyLane provides an implementation of the quantum natural gradient -# optimizer, :class:`~.pennylane.QNGOptimizer`. Let's compare the optimization convergence -# of the QNG Optimizer and the :class:`~.pennylane.GradientDescentOptimizer` for the simple variational -# circuit above. - -steps = 200 -init_params = pnp.array([0.432, -0.123, 0.543, 0.233], requires_grad=True) - -############################################################################## -# Performing vanilla gradient descent: - -gd_cost = [] -opt = qml.GradientDescentOptimizer(0.01) - -theta = init_params -for _ in range(steps): - theta = opt.step(circuit, theta) - gd_cost.append(circuit(theta)) - -############################################################################## -# Performing quantum natural gradient descent: - -qng_cost = [] -opt = qml.QNGOptimizer(0.01) - -theta = init_params -for _ in range(steps): - theta = opt.step(circuit, theta) - qng_cost.append(circuit(theta)) - - -############################################################################## -# Plotting the cost vs optimization step for both optimization strategies: -from matplotlib import pyplot as plt - -plt.style.use("seaborn") -plt.plot(gd_cost, "b", label="Vanilla gradient descent") -plt.plot(qng_cost, "g", label="Quantum natural gradient descent") - -plt.ylabel("Cost function value") -plt.xlabel("Optimization steps") -plt.legend() -plt.show() - -############################################################################## -# References -# ---------- -# -# 1. Shun-Ichi Amari. "Natural gradient works efficiently in learning." -# `Neural computation 10.2, 251-276 `__, 1998. -# -# 2. James Stokes, Josh Izaac, Nathan Killoran, Giuseppe Carleo. -# "Quantum Natural Gradient." `arXiv:1909.02108 `__, 2019. -# -# 3. Aram Harrow and John Napp. "Low-depth gradient measurements can improve -# convergence in variational hybrid quantum-classical algorithms." -# `arXiv:1901.05374 `__, 2019. -# -# 4. Naoki Yamamoto. "On the natural gradient for variational quantum eigensolver." -# `arXiv:1909.05074 `__, 2019. -# -# -# About the author -# ---------------- +r""" + +.. _quantum_natural_gradient: + +Quantum natural gradient +======================== + +.. meta:: + :property="og:description": The quantum natural gradient method can achieve faster convergence for quantum machine + learning problems by taking into account the intrinsic geometry of qubits. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qng_optimization.png + +.. related:: + + tutorial_backprop Quantum gradients with backpropagation + tutorial_vqe_qng Accelerating VQE with quantum natural gradient + +*Author: Josh Izaac — Posted: 11 October 2019. Last updated: 25 January 2021.* + +This example demonstrates the quantum natural gradient optimization technique +for variational quantum circuits, originally proposed in +`Stokes et al. (2019) `__. + +Background +---------- + +The most successful class of quantum algorithms for use on near-term noisy quantum hardware +is the so-called variational quantum algorithm. As laid out in the +`Concepts section `__, in variational quantum algorithms +a low-depth parametrized quantum circuit ansatz is chosen, and a problem-specific +observable measured. A classical optimization loop is then used to find +the set of quantum parameters that *minimize* a particular measurement expectation value +of the quantum device. Examples of such algorithms include the :doc:`variational quantum +eigensolver (VQE) `, the +`quantum approximate optimization algorithm (QAOA) `__, +and :ref:`quantum neural networks (QNN) `. + +Most recent demonstrations +of variational quantum algorithms have used gradient-free classical optimization +methods, such as the Nelder-Mead algorithm. However, the parameter-shift rule +(as implemented in PennyLane) allows the user to automatically compute +analytic gradients of quantum circuits. This opens up the possibility to train +quantum computing hardware using gradient descent—the same method used to train +deep learning models. +Though one caveat has surfaced with gradient descent — how do we choose the optimal +step size for our variational quantum algorithms, to ensure successful and +efficient optimization? + +The natural gradient +^^^^^^^^^^^^^^^^^^^^ + +In standard gradient descent, each optimization step is given by + +.. math:: \theta_{t+1} = \theta_t -\eta \nabla \mathcal{L}(\theta), + +where :math:`\mathcal{L}(\theta)` is the cost as a function of +the parameters :math:`\theta,` and :math:`\eta` is the learning rate +or step size. In essence, each optimization step calculates the +steepest descent direction around the local value of :math:`\theta_t` +in the parameter space, and updates :math:`\theta_t\rightarrow \theta_{t+1}` +by this vector. + +The problem with the above approach is that each optimization step +is strongly connected to a *Euclidean geometry* on the parameter space. +The parametrization is not unique, and different parametrizations can distort +distances within the optimization landscape. + +For example, consider the following cost function :math:`\mathcal{L},` parametrized +using two different coordinate systems, :math:`(\theta_0, \theta_1),` and +:math:`(\phi_0, \phi_1):` + +| + +.. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng7.png + :align: center + :width: 90% + :target: javascript:void(0) + +| + +Performing gradient descent in the :math:`(\theta_0, \theta_1)` parameter +space, we are updating each parameter by the same Euclidean distance, +and not taking into account the fact that the cost function might vary at a different +rate with respect to each parameter. + +Instead, if we perform a change of coordinate system (re-parametrization) +of the cost function, we might find a parameter space where variations in :math:`\mathcal{L}` +are similar across different parameters. This is the case with the new parametrization +:math:`(\phi_0, \phi_1);` the cost function is unchanged, +but we now have a nicer geometry in which to perform gradient descent, and a more +informative stepsize. This leads to faster convergence, and can help avoid optimization +becoming stuck in local minima. For a more in-depth explanation, +including why the parameter space might not be best represented by a Euclidean space, +see `Yamamoto (2019) `__. + +However, what if we avoid gradient descent in the parameter space altogether? +If we instead consider the optimization problem as a +probability distribution of possible output values given an input +(i.e., `maximum likelihood estimation `_), +a better approach is to perform the gradient descent in the *distribution space*, which is +dimensionless and invariant with respect to the parametrization. As a result, +each optimization step will always choose the optimum step-size for every +parameter, regardless of the parametrization. + +In classical neural networks, the above process is known as +*natural gradient descent*, and was first introduced by +`Amari (1998) `__. +The standard gradient descent is modified as follows: + +.. math:: \theta_{t+1} = \theta_t - \eta F^{-1}\nabla \mathcal{L}(\theta), + +where :math:`F` is the `Fisher information matrix `__. +The Fisher information matrix acts as a metric tensor, transforming the +steepest descent in the Euclidean parameter space to the steepest descent in the +distribution space. + +The quantum analog +^^^^^^^^^^^^^^^^^^ + +In a similar vein, it has been shown that the standard Euclidean geometry +is sub-optimal for optimization of quantum variational algorithms +`(Harrow and Napp, 2019) `__. +The space of quantum states instead possesses a unique invariant metric +tensor known as the Fubini-Study metric tensor :math:`g_{ij},` which can be used to +construct a quantum analog to natural gradient descent: + +.. math:: \theta_{t+1} = \theta_t - \eta g^{+}(\theta_t)\nabla \mathcal{L}(\theta), + +where :math:`g^{+}` refers to the pseudo-inverse. + +.. note:: + + It can be shown that the Fubini-Study metric tensor reduces + to the Fisher information matrix in the classical limit. + + Furthermore, in the limit where :math:`\eta\rightarrow 0,` + the dynamics of the system are equivalent to imaginary-time + evolution within the variational subspace, as proposed in + `McArdle et al. (2018) `__. + +""" + +############################################################################## +# Block-diagonal metric tensor +# ---------------------------- +# +# A block-diagonal approximation to the Fubini-Study metric tensor +# of a variational quantum circuit can be evaluated on quantum hardware. +# +# Consider a variational quantum circuit +# +# .. math:: +# +# U(\mathbf{\theta})|\psi_0\rangle = V_L(\theta_L) W_L V_{L-1}(\theta_{L-1}) W_{L-1} +# \cdots V_{\ell}(\theta_{\ell}) W_{\ell} \cdots V_{0}(\theta_{0}) W_{0} |\psi_0\rangle +# +# where +# +# * :math:`|\psi_0\rangle` is the initial state, +# * :math:`W_\ell` are layers of non-parametrized quantum gates, +# * :math:`V_\ell(\theta_\ell)` are layers of parametrized quantum gates +# with :math:`n_\ell` parameters :math:`\theta_\ell = \{\theta^{(\ell)}_0, \dots, \theta^{(\ell)}_n\}.` +# +# Further, assume all parametrized gates can be written in the form +# :math:`X(\theta^{(\ell)}_{i}) = e^{i\theta^{(\ell)}_{i} K^{(\ell)}_i},` +# where :math:`K^{(\ell)}_i` is the *generator* of the parametrized operation. +# +# For each parametric layer :math:`\ell` in the variational quantum circuit +# the :math:`n_\ell\times n_\ell` block-diagonal submatrix +# of the Fubini-Study tensor :math:`g_{ij}^{(\ell)}` is calculated by: +# +# .. math:: +# +# g_{ij}^{(\ell)} = \langle \psi_{\ell-1} | K_i K_j | \psi_{\ell-1} \rangle +# - \langle \psi_{\ell-1} | K_i | \psi_{\ell-1}\rangle +# \langle \psi_{\ell-1} |K_j | \psi_{\ell-1}\rangle +# +# where +# +# .. math:: +# +# | \psi_{\ell-1}\rangle = V_{\ell-1}(\theta_{\ell-1}) W_{\ell-1} \cdots V_{0}(\theta_{0}) W_{0} |\psi_0\rangle. +# +# (that is, :math:`|\psi_{\ell-1}\rangle` is the quantum state prior to the application +# of parameterized layer :math:`\ell`), and we have :math:`K_i \equiv K_i^{(\ell)}` for brevity. +# +# Let's consider a small variational quantum circuit example coded in PennyLane: + +import numpy as np +import pennylane as qml +from pennylane import numpy as pnp + +dev = qml.device("lightning.qubit", wires=3) + + +@qml.qnode(dev, interface="autograd", diff_method="parameter-shift") +def circuit(params): + # |psi_0>: state preparation + qml.RY(np.pi / 4, wires=0) + qml.RY(np.pi / 3, wires=1) + qml.RY(np.pi / 7, wires=2) + + # V0(theta0, theta1): Parametrized layer 0 + qml.RZ(params[0], wires=0) + qml.RZ(params[1], wires=1) + + # W1: non-parametrized gates + qml.CNOT(wires=[0, 1]) + qml.CNOT(wires=[1, 2]) + + # V_1(theta2, theta3): Parametrized layer 1 + qml.RY(params[2], wires=1) + qml.RX(params[3], wires=2) + + # W2: non-parametrized gates + qml.CNOT(wires=[0, 1]) + qml.CNOT(wires=[1, 2]) + + return qml.expval(qml.PauliY(0)) + +# Use pennylane.numpy for trainable parameters +params = pnp.array([0.432, -0.123, 0.543, 0.233]) + +############################################################################## +# The above circuit consists of 4 parameters, with two distinct parametrized +# layers of 2 parameters each. +# +# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng1.png +# :align: center +# :width: 90% +# :target: javascript:void(0) +# +# | +# +# (Note that in this example, the first non-parametrized layer :math:`W_0` +# is simply the identity.) Since there are two layers, each with two parameters, +# the block-diagonal approximation consists of two +# :math:`2\times 2` matrices, :math:`g^{(0)}` and :math:`g^{(1)}.` +# +# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng2.png +# :align: center +# :width: 30% +# :target: javascript:void(0) +# +# To compute the first block-diagonal :math:`g^{(0)},` we create subcircuits consisting +# of all gates prior to the layer, and observables corresponding to +# the *generators* of the gates in the layer: +# +# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng3.png +# :align: center +# :width: 30% +# :target: javascript:void(0) + +g0 = np.zeros([2, 2]) + + +def layer0_subcircuit(params): + """This function contains all gates that + precede parametrized layer 0""" + qml.RY(np.pi / 4, wires=0) + qml.RY(np.pi / 3, wires=1) + qml.RY(np.pi / 7, wires=2) + + +############################################################################## +# We then post-process the measurement results in order to determine :math:`g^{(0)},` +# as follows. +# +# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng4.png +# :align: center +# :width: 50% +# :target: javascript:void(0) +# +# We can see that the diagonal terms are simply given by the variance: + + +@qml.qnode(dev, interface="autograd") +def layer0_diag(params): + layer0_subcircuit(params) + return qml.var(qml.PauliZ(0)), qml.var(qml.PauliZ(1)) + + +# calculate the diagonal terms +varK0, varK1 = layer0_diag(params) +g0[0, 0] = varK0 / 4 +g0[1, 1] = varK1 / 4 + +############################################################################## +# The following two subcircuits are then used to calculate the +# off-diagonal covariance terms of :math:`g^{(0)}:` + + +@qml.qnode(dev, interface="autograd") +def layer0_off_diag_single(params): + layer0_subcircuit(params) + return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)) + + +@qml.qnode(dev, interface="autograd") +def layer0_off_diag_double(params): + layer0_subcircuit(params) + ZZ = np.kron(np.diag([1, -1]), np.diag([1, -1])) + return qml.expval(qml.Hermitian(ZZ, wires=[0, 1])) + + +# calculate the off-diagonal terms +exK0, exK1 = layer0_off_diag_single(params) +exK0K1 = layer0_off_diag_double(params) + +g0[0, 1] = (exK0K1 - exK0 * exK1) / 4 +g0[1, 0] = (exK0K1 - exK0 * exK1) / 4 + +############################################################################## +# Note that, by definition, the block-diagonal matrices must be real and +# symmetric. +# +# We can repeat the above process to compute :math:`g^{(1)}.` The subcircuit +# required is given by +# +# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng8.png +# :align: center +# :width: 50% +# :target: javascript:void(0) + +g1 = np.zeros([2, 2]) + + +def layer1_subcircuit(params): + """This function contains all gates that + precede parametrized layer 1""" + # |psi_0>: state preparation + qml.RY(np.pi / 4, wires=0) + qml.RY(np.pi / 3, wires=1) + qml.RY(np.pi / 7, wires=2) + + # V0(theta0, theta1): Parametrized layer 0 + qml.RZ(params[0], wires=0) + qml.RZ(params[1], wires=1) + + # W1: non-parametrized gates + qml.CNOT(wires=[0, 1]) + qml.CNOT(wires=[1, 2]) + + +############################################################################## +# Using this subcircuit, we can now generate the submatrix :math:`g^{(1)}.` +# +# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng5.png +# :align: center +# :width: 50% +# :target: javascript:void(0) + + +@qml.qnode(dev, interface="autograd") +def layer1_diag(params): + layer1_subcircuit(params) + return qml.var(qml.PauliY(1)), qml.var(qml.PauliX(2)) + + +############################################################################## +# As previously, the diagonal terms are simply given by the variance, + +varK0, varK1 = layer1_diag(params) +g1[0, 0] = varK0 / 4 +g1[1, 1] = varK1 / 4 + + +############################################################################## +# while the off-diagonal terms require covariance between the two +# observables to be computed. + + +@qml.qnode(dev, interface="autograd") +def layer1_off_diag_single(params): + layer1_subcircuit(params) + return qml.expval(qml.PauliY(1)), qml.expval(qml.PauliX(2)) + + +@qml.qnode(dev, interface="autograd") +def layer1_off_diag_double(params): + layer1_subcircuit(params) + X = np.array([[0, 1], [1, 0]]) + Y = np.array([[0, -1j], [1j, 0]]) + YX = np.kron(Y, X) + return qml.expval(qml.Hermitian(YX, wires=[1, 2])) + + +# calculate the off-diagonal terms +exK0, exK1 = layer1_off_diag_single(params) +exK0K1 = layer1_off_diag_double(params) + +g1[0, 1] = (exK0K1 - exK0 * exK1) / 4 +g1[1, 0] = g1[0, 1] + + +############################################################################## +# Putting this altogether, the block-diagonal approximation to the Fubini-Study +# metric tensor for this variational quantum circuit is +from scipy.linalg import block_diag + +g = block_diag(g0, g1) +print(np.round(g, 8)) + + +############################################################################## +# PennyLane contains a built-in function for computing the Fubini-Study metric +# tensor, :func:`~.pennylane.metric_tensor`, which +# we can use to verify this result: +print(np.round(qml.metric_tensor(circuit, approx="block-diag")(params), 8)) + +############################################################################## +# As opposed to our manual computation, which required 6 different quantum +# evaluations, the PennyLane Fubini-Study metric tensor implementation +# requires only 2 quantum evaluations, one per layer. This is done by +# automatically detecting the layer structure, and noting that every +# observable that must be measured commutes, allowing for simultaneous measurement. +# +# Therefore, by combining the quantum natural gradient optimizer with the analytic +# parameter-shift rule to optimize a variational circuit with :math:`d` parameters +# and :math:`L` parametrized layers, a total of :math:`2d+L` quantum evaluations +# are required per optimization step. +# +# Note that the :func:`~.pennylane.metric_tensor` function also supports computing the diagonal +# approximation to the metric tensor: +print(qml.metric_tensor(circuit, approx='diag')(params)) + +############################################################################## +# Furthermore, the returned metric tensor is **full differentiable**; include it +# in your cost function, and train or optimize its value! + +############################################################################## +# Quantum natural gradient optimization +# ------------------------------------- +# +# PennyLane provides an implementation of the quantum natural gradient +# optimizer, :class:`~.pennylane.QNGOptimizer`. Let's compare the optimization convergence +# of the QNG Optimizer and the :class:`~.pennylane.GradientDescentOptimizer` for the simple variational +# circuit above. + +steps = 200 +init_params = pnp.array([0.432, -0.123, 0.543, 0.233], requires_grad=True) + +############################################################################## +# Performing vanilla gradient descent: + +gd_cost = [] +opt = qml.GradientDescentOptimizer(0.01) + +theta = init_params +for _ in range(steps): + theta = opt.step(circuit, theta) + gd_cost.append(circuit(theta)) + +############################################################################## +# Performing quantum natural gradient descent: + +qng_cost = [] +opt = qml.QNGOptimizer(0.01) + +theta = init_params +for _ in range(steps): + theta = opt.step(circuit, theta) + qng_cost.append(circuit(theta)) + + +############################################################################## +# Plotting the cost vs optimization step for both optimization strategies: +from matplotlib import pyplot as plt + +plt.style.use("seaborn") +plt.plot(gd_cost, "b", label="Vanilla gradient descent") +plt.plot(qng_cost, "g", label="Quantum natural gradient descent") + +plt.ylabel("Cost function value") +plt.xlabel("Optimization steps") +plt.legend() +plt.show() + +############################################################################## +# References +# ---------- +# +# 1. Shun-Ichi Amari. "Natural gradient works efficiently in learning." +# `Neural computation 10.2, 251-276 `__, 1998. +# +# 2. James Stokes, Josh Izaac, Nathan Killoran, Giuseppe Carleo. +# "Quantum Natural Gradient." `arXiv:1909.02108 `__, 2019. +# +# 3. Aram Harrow and John Napp. "Low-depth gradient measurements can improve +# convergence in variational hybrid quantum-classical algorithms." +# `arXiv:1901.05374 `__, 2019. +# +# 4. Naoki Yamamoto. "On the natural gradient for variational quantum eigensolver." +# `arXiv:1909.05074 `__, 2019. +# +# +# About the author +# ---------------- # \ No newline at end of file diff --git a/demonstrations_v2/tutorial_quantum_transfer_learning/demo.py b/demonstrations_v2/tutorial_quantum_transfer_learning/demo.py index ee3790941c..031d61ed35 100644 --- a/demonstrations_v2/tutorial_quantum_transfer_learning/demo.py +++ b/demonstrations_v2/tutorial_quantum_transfer_learning/demo.py @@ -1,620 +1,620 @@ -r""" -.. _quantum_transfer_learning: - -Quantum transfer learning -========================= - -.. meta:: - :property="og:description": Combine PyTorch and PennyLane to train a hybrid quantum-classical image - classifier using transfer learning. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/transfer_images.png - -*Author: Andrea Mari — Posted: 19 December 2019. Last updated: 28 January 2021.* - -In this tutorial we apply a machine learning method, known as *transfer learning*, to an -image classifier based on a hybrid classical-quantum network. - -This example follows the general structure of the PyTorch -`tutorial on transfer learning `_ -by Sasank Chilamkurthy, with the crucial difference of using a quantum circuit to perform the -final classification task. - -More details on this topic can be found in the research paper [1] (`Mari et al. (2019) `_). - - -Introduction ------------- - -Transfer learning is a well-established technique for training artificial neural networks (see e.g., Ref. [2]), -which is based on the general intuition that if a pre-trained network is good at solving a -given problem, then, with just a bit of additional training, it can be used to also solve a different -but related problem. - -As discussed in Ref. [1], this idea can be formalized in terms of two abstract networks :math:`A` -and :math:`B,` independently from their quantum or classical physical nature. - -| - - -.. figure:: ../_static/demonstration_assets/quantum_transfer_learning/transfer_learning_general.png - :scale: 45% - :alt: transfer_general - :align: center - -| - -As sketched in the above figure, one can give the following **general definition of the -transfer learning method**: - -1. Take a network :math:`A` that has been pre-trained on a dataset :math:`D_A` and for a given - task :math:`T_A.` - -2. Remove some of the final layers. In this way, the resulting truncated network :math:`A'` - can be used as a feature extractor. - -3. Connect a new trainable network :math:`B` at the end of the pre-trained network :math:`A'.` - -4. Keep the weights of :math:`A'` constant, and train the final block :math:`B` with a - new dataset :math:`D_B` and/or for a new task of interest :math:`T_B.` - -When dealing with hybrid systems, depending on the physical nature (classical or quantum) of the -networks :math:`A` and :math:`B,` one can have different implementations of transfer learning as - -summarized in following table: - -| - -.. rst-class:: docstable - -+-----------+-----------+-----------------------------------------------------+ -| Network A | Network B | Transfer learning scheme | -+===========+===========+=====================================================+ -| Classical | Classical | CC - Standard classical method. See e.g., Ref. [2]. | -+-----------+-----------+-----------------------------------------------------+ -| Classical | Quantum | CQ - **Hybrid model presented in this tutorial.** | -+-----------+-----------+-----------------------------------------------------+ -| Quantum | Classical | QC - Model studied in Ref. [1]. | -+-----------+-----------+-----------------------------------------------------+ -| Quantum | Quantum | QQ - Model studied in Ref. [1]. | -+-----------+-----------+-----------------------------------------------------+ - -Classical-to-quantum transfer learning --------------------------------------- - -We focus on the CQ transfer learning scheme discussed in the previous section and we give a specific example. - -1. As pre-trained network :math:`A` we use **ResNet18**, a deep residual neural network introduced by - Microsoft in Ref. [3], which is pre-trained on the *ImageNet* dataset. - -2. After removing its final layer we obtain :math:`A',` a pre-processing block which maps any - input high-resolution image into 512 abstract features. - -3. Such features are classified by a 4-qubit "dressed quantum circuit" :math:`B,` i.e., a - variational quantum circuit sandwiched between two classical layers. - -4. The hybrid model is trained, keeping :math:`A'` constant, on the *Hymenoptera* dataset - (a small subclass of ImageNet) containing images of *ants* and *bees*. - -A graphical representation of the full data processing pipeline is given in the figure below. - -.. figure:: ../_static/demonstration_assets/quantum_transfer_learning/transfer_learning_c2q.png - :scale: 55% - :alt: transfer_c2q - :align: center - -""" - -############################################################################## -# General setup -# ------------------------ -# -# .. note:: -# -# To use the PyTorch interface in PennyLane, you must first -# `install PyTorch `_. -# -# In addition to *PennyLane*, we will also need some standard *PyTorch* libraries and the -# plotting library *matplotlib*. - -# Some parts of this code are based on the Python script: -# https://github.com/pytorch/tutorials/blob/master/beginner_source/transfer_learning_tutorial.py -# License: BSD - -import time -import os -import copy -import urllib.request -import shutil - -# PyTorch -import torch -import torch.nn as nn -import torch.optim as optim -from torch.optim import lr_scheduler -import torchvision -from torchvision import datasets, transforms - -# Pennylane -import pennylane as qml -from pennylane import numpy as np - -torch.manual_seed(42) -np.random.seed(42) - -# Plotting -import matplotlib.pyplot as plt - -# OpenMP: number of parallel threads. -os.environ["OMP_NUM_THREADS"] = "1" - - -############################################################################## -# Setting of the main hyper-parameters of the model -# ------------------------------------------------------------ -# -# .. note:: -# To reproduce the results of Ref. [1], ``num_epochs`` should be set to ``30`` which may take a long time. -# We suggest to first try with ``num_epochs=1`` and, if everything runs smoothly, increase it to a larger value. - - -n_qubits = 4 # Number of qubits -step = 0.0004 # Learning rate -batch_size = 4 # Number of samples for each training step -num_epochs = 3 # Number of training epochs -q_depth = 6 # Depth of the quantum circuit (number of variational layers) -gamma_lr_scheduler = 0.1 # Learning rate reduction applied every 10 epochs. -q_delta = 0.01 # Initial spread of random quantum weights -start_time = time.time() # Start of the computation timer - -############################################################################## -# We initialize a PennyLane device with a ``default.qubit`` backend. - -dev = qml.device("default.qubit", wires=n_qubits) - -############################################################################## -# We configure PyTorch to use CUDA only if available. Otherwise the CPU is used. - -device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") - -############################################################################## -# Dataset loading -# ------------------------------------------------------------ -# -# .. note:: -# The dataset containing images of *ants* and *bees* can be downloaded -# `here `_ and -# should be extracted in the subfolder ``../_data/hymenoptera_data``. -# -# This is a very small dataset (roughly 250 images), too small for training from scratch a -# classical or quantum model, however it is enough when using *transfer learning* approach. -# -# The PyTorch packages ``torchvision`` and ``torch.utils.data`` are used for loading the dataset -# and performing standard preliminary image operations: resize, center, crop, normalize, *etc.* - -data_transforms = { - "train": transforms.Compose( - [ - # transforms.RandomResizedCrop(224), # uncomment for data augmentation - # transforms.RandomHorizontalFlip(), # uncomment for data augmentation - transforms.Resize(256), - transforms.CenterCrop(224), - transforms.ToTensor(), - # Normalize input channels using mean values and standard deviations of ImageNet. - transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), - ] - ), - "val": transforms.Compose( - [ - transforms.Resize(256), - transforms.CenterCrop(224), - transforms.ToTensor(), - transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), - ] - ), -} - -data_dir = "hymenoptera_data" -if not os.path.exists(data_dir): - urllib.request.urlretrieve( - "https://download.pytorch.org/tutorial/hymenoptera_data.zip", f"{data_dir}.zip" - ) - shutil.unpack_archive(f"{data_dir}.zip") - -image_datasets = { - x if x == "train" else "validation": datasets.ImageFolder( - os.path.join(data_dir, x), data_transforms[x] - ) - for x in ["train", "val"] -} -dataset_sizes = {x: len(image_datasets[x]) for x in ["train", "validation"]} -class_names = image_datasets["train"].classes - -# Initialize dataloader -dataloaders = { - x: torch.utils.data.DataLoader(image_datasets[x], batch_size=batch_size, shuffle=True) - for x in ["train", "validation"] -} - -# function to plot images -def imshow(inp, title=None): - """Display image from tensor.""" - inp = inp.numpy().transpose((1, 2, 0)) - # Inverse of the initial normalization operation. - mean = np.array([0.485, 0.456, 0.406]) - std = np.array([0.229, 0.224, 0.225]) - inp = std * inp + mean - inp = np.clip(inp, 0, 1) - plt.imshow(inp) - if title is not None: - plt.title(title) - - -############################################################################## -# Let us show a batch of the test data, just to have an idea of the classification problem. - -# Get a batch of training data -inputs, classes = next(iter(dataloaders["validation"])) - -# Make a grid from batch -out = torchvision.utils.make_grid(inputs) - -imshow(out, title=[class_names[x] for x in classes]) - -dataloaders = { - x: torch.utils.data.DataLoader(image_datasets[x], batch_size=batch_size, shuffle=True) - for x in ["train", "validation"] -} - - -############################################################################## -# Variational quantum circuit -# ------------------------------------ -# We first define some quantum layers that will compose the quantum circuit. - - -def H_layer(nqubits): - """Layer of single-qubit Hadamard gates. - """ - for idx in range(nqubits): - qml.Hadamard(wires=idx) - - -def RY_layer(w): - """Layer of parametrized qubit rotations around the y axis. - """ - for idx, element in enumerate(w): - qml.RY(element, wires=idx) - - -def entangling_layer(nqubits): - """Layer of CNOTs followed by another shifted layer of CNOT. - """ - # In other words it should apply something like : - # CNOT CNOT CNOT CNOT... CNOT - # CNOT CNOT CNOT... CNOT - for i in range(0, nqubits - 1, 2): # Loop over even indices: i=0,2,...N-2 - qml.CNOT(wires=[i, i + 1]) - for i in range(1, nqubits - 1, 2): # Loop over odd indices: i=1,3,...N-3 - qml.CNOT(wires=[i, i + 1]) - - -############################################################################## -# Now we define the quantum circuit through the PennyLane `qnode` decorator . -# -# The structure is that of a typical variational quantum circuit: -# -# * **Embedding layer:** All qubits are first initialized in a balanced superposition -# of *up* and *down* states, then they are rotated according to the input parameters -# (local embedding). -# -# * **Variational layers:** A sequence of trainable rotation layers and constant -# entangling layers is applied. -# -# * **Measurement layer:** For each qubit, the local expectation value of the :math:`Z` -# operator is measured. This produces a classical output vector, suitable for -# additional post-processing. - - -@qml.qnode(dev) -def quantum_net(q_input_features, q_weights_flat): - """ - The variational quantum circuit. - """ - - # Reshape weights - q_weights = q_weights_flat.reshape(q_depth, n_qubits) - - # Start from state |+> , unbiased w.r.t. |0> and |1> - H_layer(n_qubits) - - # Embed features in the quantum node - RY_layer(q_input_features) - - # Sequence of trainable variational layers - for k in range(q_depth): - entangling_layer(n_qubits) - RY_layer(q_weights[k]) - - # Expectation values in the Z basis - exp_vals = [qml.expval(qml.PauliZ(position)) for position in range(n_qubits)] - return tuple(exp_vals) - - -############################################################################## -# Dressed quantum circuit -# ------------------------ -# -# We can now define a custom ``torch.nn.Module`` representing a *dressed* quantum circuit. -# -# This is a concatenation of: -# -# * A classical pre-processing layer (``nn.Linear``). -# * A classical activation function (``torch.tanh``). -# * A constant ``np.pi/2.0`` scaling. -# * The previously defined quantum circuit (``quantum_net``). -# * A classical post-processing layer (``nn.Linear``). -# -# The input of the module is a batch of vectors with 512 real parameters (features) and -# the output is a batch of vectors with two real outputs (associated with the two classes -# of images: *ants* and *bees*). - - -class DressedQuantumNet(nn.Module): - """ - Torch module implementing the *dressed* quantum net. - """ - - def __init__(self): - """ - Definition of the *dressed* layout. - """ - - super().__init__() - self.pre_net = nn.Linear(512, n_qubits) - self.q_params = nn.Parameter(q_delta * torch.randn(q_depth * n_qubits)) - self.post_net = nn.Linear(n_qubits, 2) - - def forward(self, input_features): - """ - Defining how tensors are supposed to move through the *dressed* quantum - net. - """ - - # obtain the input features for the quantum circuit - # by reducing the feature dimension from 512 to 4 - pre_out = self.pre_net(input_features) - q_in = torch.tanh(pre_out) * np.pi / 2.0 - - # Apply the quantum circuit to each element of the batch and append to q_out - q_out = torch.Tensor(0, n_qubits) - q_out = q_out.to(device) - for elem in q_in: - q_out_elem = torch.hstack(quantum_net(elem, self.q_params)).float().unsqueeze(0) - q_out = torch.cat((q_out, q_out_elem)) - - # return the two-dimensional prediction from the postprocessing layer - return self.post_net(q_out) - - -############################################################################## -# Hybrid classical-quantum model -# ------------------------------------ -# -# We are finally ready to build our full hybrid classical-quantum network. -# We follow the *transfer learning* approach: -# -# 1. First load the classical pre-trained network *ResNet18* from the ``torchvision.models`` zoo. -# 2. Freeze all the weights since they should not be trained. -# 3. Replace the last fully connected layer with our trainable dressed quantum circuit (``DressedQuantumNet``). -# -# .. note:: -# The *ResNet18* model is automatically downloaded by PyTorch and it may take several minutes (only the first time). -# - -weights = torchvision.models.ResNet18_Weights.IMAGENET1K_V1 -model_hybrid = torchvision.models.resnet18(weights=weights) - -for param in model_hybrid.parameters(): - param.requires_grad = False - - -# Notice that model_hybrid.fc is the last layer of ResNet18 -model_hybrid.fc = DressedQuantumNet() - -# Use CUDA or CPU according to the "device" object. -model_hybrid = model_hybrid.to(device) - -############################################################################## -# Training and results -# ------------------------ -# -# Before training the network we need to specify the *loss* function. -# -# We use, as usual in classification problem, the *cross-entropy* which is -# directly available within ``torch.nn``. - - -criterion = nn.CrossEntropyLoss() - -############################################################################## -# We also initialize the *Adam optimizer* which is called at each training step -# in order to update the weights of the model. - - -optimizer_hybrid = optim.Adam(model_hybrid.fc.parameters(), lr=step) - -############################################################################## -# We schedule to reduce the learning rate by a factor of ``gamma_lr_scheduler`` -# every 10 epochs. - - -exp_lr_scheduler = lr_scheduler.StepLR( - optimizer_hybrid, step_size=10, gamma=gamma_lr_scheduler -) - -############################################################################## -# What follows is a training function that will be called later. -# This function should return a trained model that can be used to make predictions -# (classifications). - - -def train_model(model, criterion, optimizer, scheduler, num_epochs): - since = time.time() - best_model_wts = copy.deepcopy(model.state_dict()) - best_acc = 0.0 - best_loss = 10000.0 # Large arbitrary number - best_acc_train = 0.0 - best_loss_train = 10000.0 # Large arbitrary number - print("Training started:") - - for epoch in range(num_epochs): - - # Each epoch has a training and validation phase - for phase in ["train", "validation"]: - if phase == "train": - # Set model to training mode - model.train() - else: - # Set model to evaluate mode - model.eval() - running_loss = 0.0 - running_corrects = 0 - - # Iterate over data. - n_batches = dataset_sizes[phase] // batch_size - it = 0 - for inputs, labels in dataloaders[phase]: - since_batch = time.time() - batch_size_ = len(inputs) - inputs = inputs.to(device) - labels = labels.to(device) - optimizer.zero_grad() - - # Track/compute gradient and make an optimization step only when training - with torch.set_grad_enabled(phase == "train"): - outputs = model(inputs) - _, preds = torch.max(outputs, 1) - loss = criterion(outputs, labels) - if phase == "train": - loss.backward() - optimizer.step() - - # Print iteration results - running_loss += loss.item() * batch_size_ - batch_corrects = torch.sum(preds == labels.data).item() - running_corrects += batch_corrects - print( - "Phase: {} Epoch: {}/{} Iter: {}/{} Batch time: {:.4f}".format( - phase, - epoch + 1, - num_epochs, - it + 1, - n_batches + 1, - time.time() - since_batch, - ), - end="\r", - flush=True, - ) - it += 1 - - # Print epoch results - epoch_loss = running_loss / dataset_sizes[phase] - epoch_acc = running_corrects / dataset_sizes[phase] - print( - "Phase: {} Epoch: {}/{} Loss: {:.4f} Acc: {:.4f} ".format( - "train" if phase == "train" else "validation ", - epoch + 1, - num_epochs, - epoch_loss, - epoch_acc, - ) - ) - - # Check if this is the best model wrt previous epochs - if phase == "validation" and epoch_acc > best_acc: - best_acc = epoch_acc - best_model_wts = copy.deepcopy(model.state_dict()) - if phase == "validation" and epoch_loss < best_loss: - best_loss = epoch_loss - if phase == "train" and epoch_acc > best_acc_train: - best_acc_train = epoch_acc - if phase == "train" and epoch_loss < best_loss_train: - best_loss_train = epoch_loss - - # Update learning rate - if phase == "train": - scheduler.step() - - # Print final results - model.load_state_dict(best_model_wts) - time_elapsed = time.time() - since - print( - "Training completed in {:.0f}m {:.0f}s".format(time_elapsed // 60, time_elapsed % 60) - ) - print("Best test loss: {:.4f} | Best test accuracy: {:.4f}".format(best_loss, best_acc)) - return model - - -############################################################################## -# We are ready to perform the actual training process. - -model_hybrid = train_model( - model_hybrid, criterion, optimizer_hybrid, exp_lr_scheduler, num_epochs=num_epochs -) - -############################################################################## -# Visualizing the model predictions -# ------------------------------------ - -############################################################################## -# We first define a visualization function for a batch of test data. - - -def visualize_model(model, num_images=6, fig_name="Predictions"): - images_so_far = 0 - _fig = plt.figure(fig_name) - model.eval() - with torch.no_grad(): - for _i, (inputs, labels) in enumerate(dataloaders["validation"]): - inputs = inputs.to(device) - labels = labels.to(device) - outputs = model(inputs) - _, preds = torch.max(outputs, 1) - for j in range(inputs.size()[0]): - images_so_far += 1 - ax = plt.subplot(num_images // 2, 2, images_so_far) - ax.axis("off") - ax.set_title("[{}]".format(class_names[preds[j]])) - imshow(inputs.cpu().data[j]) - if images_so_far == num_images: - return - - -############################################################################## -# Finally, we can run the previous function to see a batch of images -# with the corresponding predictions. -# -visualize_model(model_hybrid, num_images=batch_size) -plt.show() - -############################################################################## -# References -# ------------ -# -# [1] Andrea Mari, Thomas R. Bromley, Josh Izaac, Maria Schuld, and Nathan Killoran. -# *Transfer learning in hybrid classical-quantum neural networks*. arXiv:1912.08278 (2019). -# -# [2] Rajat Raina, Alexis Battle, Honglak Lee, Benjamin Packer, and Andrew Y Ng. -# *Self-taught learning: transfer learning from unlabeled data*. -# Proceedings of the 24th International Conference on Machine Learning*, 759–766 (2007). -# -# [3] Kaiming He, Xiangyu Zhang, Shaoqing ren and Jian Sun. *Deep residual learning for image recognition*. -# Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition, 770-778 (2016). -# -# [4] Ville Bergholm, Josh Izaac, Maria Schuld, Christian Gogolin, Carsten Blank, Keri McKiernan, and Nathan Killoran. -# *PennyLane: Automatic differentiation of hybrid quantum-classical computations*. arXiv:1811.04968 (2018). -# -# -# About the author -# ---------------- -# +r""" +.. _quantum_transfer_learning: + +Quantum transfer learning +========================= + +.. meta:: + :property="og:description": Combine PyTorch and PennyLane to train a hybrid quantum-classical image + classifier using transfer learning. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/transfer_images.png + +*Author: Andrea Mari — Posted: 19 December 2019. Last updated: 28 January 2021.* + +In this tutorial we apply a machine learning method, known as *transfer learning*, to an +image classifier based on a hybrid classical-quantum network. + +This example follows the general structure of the PyTorch +`tutorial on transfer learning `_ +by Sasank Chilamkurthy, with the crucial difference of using a quantum circuit to perform the +final classification task. + +More details on this topic can be found in the research paper [1] (`Mari et al. (2019) `_). + + +Introduction +------------ + +Transfer learning is a well-established technique for training artificial neural networks (see e.g., Ref. [2]), +which is based on the general intuition that if a pre-trained network is good at solving a +given problem, then, with just a bit of additional training, it can be used to also solve a different +but related problem. + +As discussed in Ref. [1], this idea can be formalized in terms of two abstract networks :math:`A` +and :math:`B,` independently from their quantum or classical physical nature. + +| + + +.. figure:: ../_static/demonstration_assets/quantum_transfer_learning/transfer_learning_general.png + :scale: 45% + :alt: transfer_general + :align: center + +| + +As sketched in the above figure, one can give the following **general definition of the +transfer learning method**: + +1. Take a network :math:`A` that has been pre-trained on a dataset :math:`D_A` and for a given + task :math:`T_A.` + +2. Remove some of the final layers. In this way, the resulting truncated network :math:`A'` + can be used as a feature extractor. + +3. Connect a new trainable network :math:`B` at the end of the pre-trained network :math:`A'.` + +4. Keep the weights of :math:`A'` constant, and train the final block :math:`B` with a + new dataset :math:`D_B` and/or for a new task of interest :math:`T_B.` + +When dealing with hybrid systems, depending on the physical nature (classical or quantum) of the +networks :math:`A` and :math:`B,` one can have different implementations of transfer learning as + +summarized in following table: + +| + +.. rst-class:: docstable + ++-----------+-----------+-----------------------------------------------------+ +| Network A | Network B | Transfer learning scheme | ++===========+===========+=====================================================+ +| Classical | Classical | CC - Standard classical method. See e.g., Ref. [2]. | ++-----------+-----------+-----------------------------------------------------+ +| Classical | Quantum | CQ - **Hybrid model presented in this tutorial.** | ++-----------+-----------+-----------------------------------------------------+ +| Quantum | Classical | QC - Model studied in Ref. [1]. | ++-----------+-----------+-----------------------------------------------------+ +| Quantum | Quantum | QQ - Model studied in Ref. [1]. | ++-----------+-----------+-----------------------------------------------------+ + +Classical-to-quantum transfer learning +-------------------------------------- + +We focus on the CQ transfer learning scheme discussed in the previous section and we give a specific example. + +1. As pre-trained network :math:`A` we use **ResNet18**, a deep residual neural network introduced by + Microsoft in Ref. [3], which is pre-trained on the *ImageNet* dataset. + +2. After removing its final layer we obtain :math:`A',` a pre-processing block which maps any + input high-resolution image into 512 abstract features. + +3. Such features are classified by a 4-qubit "dressed quantum circuit" :math:`B,` i.e., a + variational quantum circuit sandwiched between two classical layers. + +4. The hybrid model is trained, keeping :math:`A'` constant, on the *Hymenoptera* dataset + (a small subclass of ImageNet) containing images of *ants* and *bees*. + +A graphical representation of the full data processing pipeline is given in the figure below. + +.. figure:: ../_static/demonstration_assets/quantum_transfer_learning/transfer_learning_c2q.png + :scale: 55% + :alt: transfer_c2q + :align: center + +""" + +############################################################################## +# General setup +# ------------------------ +# +# .. note:: +# +# To use the PyTorch interface in PennyLane, you must first +# `install PyTorch `_. +# +# In addition to *PennyLane*, we will also need some standard *PyTorch* libraries and the +# plotting library *matplotlib*. + +# Some parts of this code are based on the Python script: +# https://github.com/pytorch/tutorials/blob/master/beginner_source/transfer_learning_tutorial.py +# License: BSD + +import time +import os +import copy +import urllib.request +import shutil + +# PyTorch +import torch +import torch.nn as nn +import torch.optim as optim +from torch.optim import lr_scheduler +import torchvision +from torchvision import datasets, transforms + +# Pennylane +import pennylane as qml +from pennylane import numpy as np + +torch.manual_seed(42) +np.random.seed(42) + +# Plotting +import matplotlib.pyplot as plt + +# OpenMP: number of parallel threads. +os.environ["OMP_NUM_THREADS"] = "1" + + +############################################################################## +# Setting of the main hyper-parameters of the model +# ------------------------------------------------------------ +# +# .. note:: +# To reproduce the results of Ref. [1], ``num_epochs`` should be set to ``30`` which may take a long time. +# We suggest to first try with ``num_epochs=1`` and, if everything runs smoothly, increase it to a larger value. + + +n_qubits = 4 # Number of qubits +step = 0.0004 # Learning rate +batch_size = 4 # Number of samples for each training step +num_epochs = 3 # Number of training epochs +q_depth = 6 # Depth of the quantum circuit (number of variational layers) +gamma_lr_scheduler = 0.1 # Learning rate reduction applied every 10 epochs. +q_delta = 0.01 # Initial spread of random quantum weights +start_time = time.time() # Start of the computation timer + +############################################################################## +# We initialize a PennyLane device with a ``default.qubit`` backend. + +dev = qml.device("default.qubit", wires=n_qubits) + +############################################################################## +# We configure PyTorch to use CUDA only if available. Otherwise the CPU is used. + +device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") + +############################################################################## +# Dataset loading +# ------------------------------------------------------------ +# +# .. note:: +# The dataset containing images of *ants* and *bees* can be downloaded +# `here `_ and +# should be extracted in the subfolder ``../_data/hymenoptera_data``. +# +# This is a very small dataset (roughly 250 images), too small for training from scratch a +# classical or quantum model, however it is enough when using *transfer learning* approach. +# +# The PyTorch packages ``torchvision`` and ``torch.utils.data`` are used for loading the dataset +# and performing standard preliminary image operations: resize, center, crop, normalize, *etc.* + +data_transforms = { + "train": transforms.Compose( + [ + # transforms.RandomResizedCrop(224), # uncomment for data augmentation + # transforms.RandomHorizontalFlip(), # uncomment for data augmentation + transforms.Resize(256), + transforms.CenterCrop(224), + transforms.ToTensor(), + # Normalize input channels using mean values and standard deviations of ImageNet. + transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), + ] + ), + "val": transforms.Compose( + [ + transforms.Resize(256), + transforms.CenterCrop(224), + transforms.ToTensor(), + transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), + ] + ), +} + +data_dir = "hymenoptera_data" +if not os.path.exists(data_dir): + urllib.request.urlretrieve( + "https://download.pytorch.org/tutorial/hymenoptera_data.zip", f"{data_dir}.zip" + ) + shutil.unpack_archive(f"{data_dir}.zip") + +image_datasets = { + x if x == "train" else "validation": datasets.ImageFolder( + os.path.join(data_dir, x), data_transforms[x] + ) + for x in ["train", "val"] +} +dataset_sizes = {x: len(image_datasets[x]) for x in ["train", "validation"]} +class_names = image_datasets["train"].classes + +# Initialize dataloader +dataloaders = { + x: torch.utils.data.DataLoader(image_datasets[x], batch_size=batch_size, shuffle=True) + for x in ["train", "validation"] +} + +# function to plot images +def imshow(inp, title=None): + """Display image from tensor.""" + inp = inp.numpy().transpose((1, 2, 0)) + # Inverse of the initial normalization operation. + mean = np.array([0.485, 0.456, 0.406]) + std = np.array([0.229, 0.224, 0.225]) + inp = std * inp + mean + inp = np.clip(inp, 0, 1) + plt.imshow(inp) + if title is not None: + plt.title(title) + + +############################################################################## +# Let us show a batch of the test data, just to have an idea of the classification problem. + +# Get a batch of training data +inputs, classes = next(iter(dataloaders["validation"])) + +# Make a grid from batch +out = torchvision.utils.make_grid(inputs) + +imshow(out, title=[class_names[x] for x in classes]) + +dataloaders = { + x: torch.utils.data.DataLoader(image_datasets[x], batch_size=batch_size, shuffle=True) + for x in ["train", "validation"] +} + + +############################################################################## +# Variational quantum circuit +# ------------------------------------ +# We first define some quantum layers that will compose the quantum circuit. + + +def H_layer(nqubits): + """Layer of single-qubit Hadamard gates. + """ + for idx in range(nqubits): + qml.Hadamard(wires=idx) + + +def RY_layer(w): + """Layer of parametrized qubit rotations around the y axis. + """ + for idx, element in enumerate(w): + qml.RY(element, wires=idx) + + +def entangling_layer(nqubits): + """Layer of CNOTs followed by another shifted layer of CNOT. + """ + # In other words it should apply something like : + # CNOT CNOT CNOT CNOT... CNOT + # CNOT CNOT CNOT... CNOT + for i in range(0, nqubits - 1, 2): # Loop over even indices: i=0,2,...N-2 + qml.CNOT(wires=[i, i + 1]) + for i in range(1, nqubits - 1, 2): # Loop over odd indices: i=1,3,...N-3 + qml.CNOT(wires=[i, i + 1]) + + +############################################################################## +# Now we define the quantum circuit through the PennyLane `qnode` decorator . +# +# The structure is that of a typical variational quantum circuit: +# +# * **Embedding layer:** All qubits are first initialized in a balanced superposition +# of *up* and *down* states, then they are rotated according to the input parameters +# (local embedding). +# +# * **Variational layers:** A sequence of trainable rotation layers and constant +# entangling layers is applied. +# +# * **Measurement layer:** For each qubit, the local expectation value of the :math:`Z` +# operator is measured. This produces a classical output vector, suitable for +# additional post-processing. + + +@qml.qnode(dev) +def quantum_net(q_input_features, q_weights_flat): + """ + The variational quantum circuit. + """ + + # Reshape weights + q_weights = q_weights_flat.reshape(q_depth, n_qubits) + + # Start from state |+> , unbiased w.r.t. |0> and |1> + H_layer(n_qubits) + + # Embed features in the quantum node + RY_layer(q_input_features) + + # Sequence of trainable variational layers + for k in range(q_depth): + entangling_layer(n_qubits) + RY_layer(q_weights[k]) + + # Expectation values in the Z basis + exp_vals = [qml.expval(qml.PauliZ(position)) for position in range(n_qubits)] + return tuple(exp_vals) + + +############################################################################## +# Dressed quantum circuit +# ------------------------ +# +# We can now define a custom ``torch.nn.Module`` representing a *dressed* quantum circuit. +# +# This is a concatenation of: +# +# * A classical pre-processing layer (``nn.Linear``). +# * A classical activation function (``torch.tanh``). +# * A constant ``np.pi/2.0`` scaling. +# * The previously defined quantum circuit (``quantum_net``). +# * A classical post-processing layer (``nn.Linear``). +# +# The input of the module is a batch of vectors with 512 real parameters (features) and +# the output is a batch of vectors with two real outputs (associated with the two classes +# of images: *ants* and *bees*). + + +class DressedQuantumNet(nn.Module): + """ + Torch module implementing the *dressed* quantum net. + """ + + def __init__(self): + """ + Definition of the *dressed* layout. + """ + + super().__init__() + self.pre_net = nn.Linear(512, n_qubits) + self.q_params = nn.Parameter(q_delta * torch.randn(q_depth * n_qubits)) + self.post_net = nn.Linear(n_qubits, 2) + + def forward(self, input_features): + """ + Defining how tensors are supposed to move through the *dressed* quantum + net. + """ + + # obtain the input features for the quantum circuit + # by reducing the feature dimension from 512 to 4 + pre_out = self.pre_net(input_features) + q_in = torch.tanh(pre_out) * np.pi / 2.0 + + # Apply the quantum circuit to each element of the batch and append to q_out + q_out = torch.Tensor(0, n_qubits) + q_out = q_out.to(device) + for elem in q_in: + q_out_elem = torch.hstack(quantum_net(elem, self.q_params)).float().unsqueeze(0) + q_out = torch.cat((q_out, q_out_elem)) + + # return the two-dimensional prediction from the postprocessing layer + return self.post_net(q_out) + + +############################################################################## +# Hybrid classical-quantum model +# ------------------------------------ +# +# We are finally ready to build our full hybrid classical-quantum network. +# We follow the *transfer learning* approach: +# +# 1. First load the classical pre-trained network *ResNet18* from the ``torchvision.models`` zoo. +# 2. Freeze all the weights since they should not be trained. +# 3. Replace the last fully connected layer with our trainable dressed quantum circuit (``DressedQuantumNet``). +# +# .. note:: +# The *ResNet18* model is automatically downloaded by PyTorch and it may take several minutes (only the first time). +# + +weights = torchvision.models.ResNet18_Weights.IMAGENET1K_V1 +model_hybrid = torchvision.models.resnet18(weights=weights) + +for param in model_hybrid.parameters(): + param.requires_grad = False + + +# Notice that model_hybrid.fc is the last layer of ResNet18 +model_hybrid.fc = DressedQuantumNet() + +# Use CUDA or CPU according to the "device" object. +model_hybrid = model_hybrid.to(device) + +############################################################################## +# Training and results +# ------------------------ +# +# Before training the network we need to specify the *loss* function. +# +# We use, as usual in classification problem, the *cross-entropy* which is +# directly available within ``torch.nn``. + + +criterion = nn.CrossEntropyLoss() + +############################################################################## +# We also initialize the *Adam optimizer* which is called at each training step +# in order to update the weights of the model. + + +optimizer_hybrid = optim.Adam(model_hybrid.fc.parameters(), lr=step) + +############################################################################## +# We schedule to reduce the learning rate by a factor of ``gamma_lr_scheduler`` +# every 10 epochs. + + +exp_lr_scheduler = lr_scheduler.StepLR( + optimizer_hybrid, step_size=10, gamma=gamma_lr_scheduler +) + +############################################################################## +# What follows is a training function that will be called later. +# This function should return a trained model that can be used to make predictions +# (classifications). + + +def train_model(model, criterion, optimizer, scheduler, num_epochs): + since = time.time() + best_model_wts = copy.deepcopy(model.state_dict()) + best_acc = 0.0 + best_loss = 10000.0 # Large arbitrary number + best_acc_train = 0.0 + best_loss_train = 10000.0 # Large arbitrary number + print("Training started:") + + for epoch in range(num_epochs): + + # Each epoch has a training and validation phase + for phase in ["train", "validation"]: + if phase == "train": + # Set model to training mode + model.train() + else: + # Set model to evaluate mode + model.eval() + running_loss = 0.0 + running_corrects = 0 + + # Iterate over data. + n_batches = dataset_sizes[phase] // batch_size + it = 0 + for inputs, labels in dataloaders[phase]: + since_batch = time.time() + batch_size_ = len(inputs) + inputs = inputs.to(device) + labels = labels.to(device) + optimizer.zero_grad() + + # Track/compute gradient and make an optimization step only when training + with torch.set_grad_enabled(phase == "train"): + outputs = model(inputs) + _, preds = torch.max(outputs, 1) + loss = criterion(outputs, labels) + if phase == "train": + loss.backward() + optimizer.step() + + # Print iteration results + running_loss += loss.item() * batch_size_ + batch_corrects = torch.sum(preds == labels.data).item() + running_corrects += batch_corrects + print( + "Phase: {} Epoch: {}/{} Iter: {}/{} Batch time: {:.4f}".format( + phase, + epoch + 1, + num_epochs, + it + 1, + n_batches + 1, + time.time() - since_batch, + ), + end="\r", + flush=True, + ) + it += 1 + + # Print epoch results + epoch_loss = running_loss / dataset_sizes[phase] + epoch_acc = running_corrects / dataset_sizes[phase] + print( + "Phase: {} Epoch: {}/{} Loss: {:.4f} Acc: {:.4f} ".format( + "train" if phase == "train" else "validation ", + epoch + 1, + num_epochs, + epoch_loss, + epoch_acc, + ) + ) + + # Check if this is the best model wrt previous epochs + if phase == "validation" and epoch_acc > best_acc: + best_acc = epoch_acc + best_model_wts = copy.deepcopy(model.state_dict()) + if phase == "validation" and epoch_loss < best_loss: + best_loss = epoch_loss + if phase == "train" and epoch_acc > best_acc_train: + best_acc_train = epoch_acc + if phase == "train" and epoch_loss < best_loss_train: + best_loss_train = epoch_loss + + # Update learning rate + if phase == "train": + scheduler.step() + + # Print final results + model.load_state_dict(best_model_wts) + time_elapsed = time.time() - since + print( + "Training completed in {:.0f}m {:.0f}s".format(time_elapsed // 60, time_elapsed % 60) + ) + print("Best test loss: {:.4f} | Best test accuracy: {:.4f}".format(best_loss, best_acc)) + return model + + +############################################################################## +# We are ready to perform the actual training process. + +model_hybrid = train_model( + model_hybrid, criterion, optimizer_hybrid, exp_lr_scheduler, num_epochs=num_epochs +) + +############################################################################## +# Visualizing the model predictions +# ------------------------------------ + +############################################################################## +# We first define a visualization function for a batch of test data. + + +def visualize_model(model, num_images=6, fig_name="Predictions"): + images_so_far = 0 + _fig = plt.figure(fig_name) + model.eval() + with torch.no_grad(): + for _i, (inputs, labels) in enumerate(dataloaders["validation"]): + inputs = inputs.to(device) + labels = labels.to(device) + outputs = model(inputs) + _, preds = torch.max(outputs, 1) + for j in range(inputs.size()[0]): + images_so_far += 1 + ax = plt.subplot(num_images // 2, 2, images_so_far) + ax.axis("off") + ax.set_title("[{}]".format(class_names[preds[j]])) + imshow(inputs.cpu().data[j]) + if images_so_far == num_images: + return + + +############################################################################## +# Finally, we can run the previous function to see a batch of images +# with the corresponding predictions. +# +visualize_model(model_hybrid, num_images=batch_size) +plt.show() + +############################################################################## +# References +# ------------ +# +# [1] Andrea Mari, Thomas R. Bromley, Josh Izaac, Maria Schuld, and Nathan Killoran. +# *Transfer learning in hybrid classical-quantum neural networks*. arXiv:1912.08278 (2019). +# +# [2] Rajat Raina, Alexis Battle, Honglak Lee, Benjamin Packer, and Andrew Y Ng. +# *Self-taught learning: transfer learning from unlabeled data*. +# Proceedings of the 24th International Conference on Machine Learning*, 759–766 (2007). +# +# [3] Kaiming He, Xiangyu Zhang, Shaoqing ren and Jian Sun. *Deep residual learning for image recognition*. +# Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition, 770-778 (2016). +# +# [4] Ville Bergholm, Josh Izaac, Maria Schuld, Christian Gogolin, Carsten Blank, Keri McKiernan, and Nathan Killoran. +# *PennyLane: Automatic differentiation of hybrid quantum-classical computations*. arXiv:1811.04968 (2018). +# +# +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_qubit_tapering/demo.py b/demonstrations_v2/tutorial_qubit_tapering/demo.py index 5c55be064e..a09519630b 100644 --- a/demonstrations_v2/tutorial_qubit_tapering/demo.py +++ b/demonstrations_v2/tutorial_qubit_tapering/demo.py @@ -1,330 +1,330 @@ -r""" - -Qubit tapering -============== - -.. meta:: - :property="og:description": Learn how to taper off qubits - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qubit_tapering.png - -.. related:: - tutorial_quantum_chemistry Building molecular Hamiltonians - tutorial_vqe A brief overview of VQE - tutorial_givens_rotations Givens rotations for quantum chemistry - tutorial_adaptive_circuits Adaptive circuits for quantum chemistry - tutorial_differentiable_HF Differentiable Hartree-Fock - -*Authors: Utkarsh Azad and Soran Jahangiri. Posted: 16 May 2022. Last updated: 08 Nov 2022* - -The performance of variational quantum algorithms is considerably limited by the number of qubits -required to represent wave functions. In the context of quantum chemistry, this -limitation hinders the treatment of large molecules with algorithms such as the :doc:`variational quantum -eigensolver (VQE) `. Several approaches have been developed to reduce the qubit requirements for -quantum chemistry calculations. In this tutorial, we demonstrate the symmetry-based qubit -tapering approach which allows reducing the number of qubits required to perform molecular quantum -simulations based on the :math:`\mathbb{Z}_2` symmetries present in molecular Hamiltonians -[#bravyi2017]_ [#setia2019]_. - -A molecular Hamiltonian in the qubit basis can be expressed as a linear combination of Pauli words -as - -.. math:: H = \sum_{i=1}^r h_i P_i, - -where :math:`h_i` is a real coefficient and :math:`P_i` is a tensor product of Pauli and -identity operators acting on :math:`M` qubits - -.. math:: P_i \in \pm \left \{ I, X, Y, Z \right \} ^ {\bigotimes M}. - -The main idea in the symmetry-based qubit tapering approach is to find a unitary operator :math:`U` -that transforms :math:`H` to a new Hamiltonian :math:`H'` which has the same eigenvalues as -:math:`H` - -.. math:: H' = U^{\dagger} H U = \sum_{i=1}^r c_i \mu_i, - -such that each :math:`\mu_i` term in the new Hamiltonian always acts trivially, e.g., with an -identity or a Pauli operator, on a set of qubits. This allows tapering-off those qubits from the -Hamiltonian. - -For instance, consider the following Hamiltonian - -.. math:: H' = Z_0 X_1 - X_1 + Y_0 X_1, - -where all terms in the Hamiltonian act on the second qubit with the :math:`X` operator. It is -straightforward to show that each term in the Hamiltonian commutes with :math:`I_0 X_1` and the -ground-state eigenvector of :math:`H'` is also an eigenvector of :math:`I_0 X_1` with eigenvalues -:math:`\pm 1.` We can also rewrite the Hamiltonian as - -.. math:: H' = (Z_0 I_1 - I_0 I_1 + Y_0 I_1) I_0 X_1, - -which gives us - -.. math:: H'|\psi \rangle = \pm1 (Z_0 I_1 - I_0 I_1 + Y_0 I_1)|\psi \rangle, - -where :math:`|\psi \rangle` is an eigenvector of :math:`H'.` This means that the Hamiltonian -:math:`H` can be simplified as - -.. math:: H_{tapered} = \pm1 (Z_0 - I_0 + Y_0). - -The tapered Hamiltonian :math:`H_{tapered}` has the eigenvalues - -.. math:: [-2.41421, 0.41421], - -and - -.. math:: [2.41421, -0.41421], - -depending on the value of the :math:`\pm 1` prefactor. The eigenvalues of the original Hamiltonian -:math:`H` are - -.. math:: [2.41421, -2.41421, 0.41421, -0.41421], - -which are thus reproduced by the tapered Hamiltonian. - -More generally, we can construct the unitary :math:`U` such that each :math:`\mu_i` term acts with a -Pauli-X operator on a set of qubits -:math:`\left \{ j \right \}, j \in \left \{ l, ..., k \right \}` where :math:`j` is the qubit label. -This guarantees that each term of the transformed Hamiltonian commutes with each of the Pauli-X -operators applied to the :math:`j`-th qubit: - -.. math:: [H', X^j] = 0, - -and the eigenvectors of the transformed Hamiltonian :math:`H'` are also eigenvectors of each of the -:math:`X^{j}` operators. Then we can factor out all of the :math:`X^{j}` operators from the -transformed Hamiltonian and replace them with their eigenvalues :math:`\pm 1.` This gives us a -set of tapered Hamiltonians depending on which eigenvalue :math:`\pm 1` we chose for each of the -:math:`X^{j}` operators. For instance, in the case of two tapered qubits, we have four eigenvalue -sectors: :math:`[+1, +1]`, :math:`[-1, +1]`, :math:`[+1, -1],` :math:`[-1, -1].` In these tapered -Hamiltonians, the set of :math:`\left \{ j \right \}, j \in \left \{ l, ..., k \right \}` qubits -are eliminated. For tapered molecular Hamiltonians, it is possible to determine the optimal sector -of the eigenvalues that corresponds to the ground state. This is explained in more detail in the -following sections. - -The unitary operator :math:`U` can be constructed as a -`Clifford `__ operator [#bravyi2017]_ - -.. math:: U = \Pi_j \left [\frac{1}{\sqrt{2}} \left (X^{q(j)} + \tau_j \right) \right], - -where :math:`\tau` denotes the generators of the symmetry group of :math:`H` and -:math:`X^{q}` operators act on those qubits that will be ultimately tapered off from -the Hamiltonian. The symmetry group of the Hamiltonian is defined as an Abelian group of Pauli words that commute -with each term in the Hamiltonian (excluding :math:`−I`). The -`generators `__ of the symmetry group are -those elements of the group that can be combined, along with their inverses, to create any other -member of the group. - -Let's use the qubit tapering method and obtain the ground state energy of the `Helium hydride -cation `__ :math:`\textrm{HeH}^+.` - -Tapering the molecular Hamiltonian ----------------------------------- - -In PennyLane, a :doc:`molecular Hamiltonian ` can be created by specifying the atomic symbols and -coordinates. -""" -import pennylane as qml -from jax import numpy as jnp -import jax - -jax.config.update("jax_enable_x64", True) -symbols = ["He", "H"] -geometry = jnp.array([[0.00000000, 0.00000000, -0.87818361], - [0.00000000, 0.00000000, 0.87818362]]) - -molecule = qml.qchem.Molecule(symbols, geometry, charge=1) -H, qubits = qml.qchem.molecular_hamiltonian(molecule) -H - -############################################################################## -# This Hamiltonian contains 27 terms where each term acts on up to four qubits. -# -# We can now obtain the symmetry generators and the :math:`X^{j}` operators that are -# used to construct the unitary :math:`U` operator that transforms the :math:`\textrm{HeH}^+` -# Hamiltonian. In PennyLane, these are constructed by using the -# :func:`~.pennylane.symmetry_generators` and :func:`~.pennylane.paulix_ops` functions. - -generators = qml.symmetry_generators(H) -paulixops = qml.paulix_ops(generators, qubits) - -for idx, generator in enumerate(generators): - print(f"generator {idx+1}: {generator}, paulix_op: {paulixops[idx]}") - -############################################################################## -# Once the operator :math:`U` is applied, each of the Hamiltonian terms will act on the qubits -# :math:`q_2, q_3` either with the identity or with a Pauli-X operator. For each of these qubits, -# we can simply replace the Pauli-X operator with one of its eigenvalues :math:`+1` or :math:`-1.` -# This results in a total number of :math:`2^k` Hamiltonians, where :math:`k` is the number of -# tapered-off qubits and each Hamiltonian corresponds to one eigenvalue sector. The optimal sector -# corresponding to the ground-state energy of the molecule can be obtained by using the -# :func:`~.pennylane.qchem.optimal_sector` function. - - -n_electrons = 2 -paulix_sector = qml.qchem.optimal_sector(H, generators, n_electrons) -print(paulix_sector) - -############################################################################## -# The optimal eigenvalues are :math:`-1, -1` for qubits :math:`q_2, q_3,` respectively. We can now -# build the tapered Hamiltonian with the :func:`~.pennylane.taper` function which -# constructs the operator :math:`U,` applies it to the Hamiltonian and finally tapers off the -# qubits :math:`q_2, q_3` by replacing the Pauli-X operators acting on those qubits with the optimal -# eigenvalues. - -H_tapered = qml.taper(H, generators, paulixops, paulix_sector) -H_tapered_coeffs, H_tapered_ops = H_tapered.terms() -H_tapered = qml.Hamiltonian(jnp.real(jnp.array(H_tapered_coeffs)), H_tapered_ops) -print(H_tapered) - -############################################################################## -# The new Hamiltonian has only 9 non-zero terms acting on only 2 qubits! We can verify that the -# original and the tapered Hamiltonian both give the correct ground state energy of the -# :math:`\textrm{HeH}^+` cation, which is :math:`-2.862595242378` Ha computed with the full -# configuration interaction (FCI) method. In PennyLane, it's possible to build a sparse matrix -# representation of Hamiltonians. This allows us to directly diagonalize them to obtain exact values -# of the ground-state energies. - -H_sparse = qml.SparseHamiltonian(H.sparse_matrix(), wires=H.wires) -H_tapered_sparse = qml.SparseHamiltonian(H_tapered.sparse_matrix(), wires=H_tapered.wires) - -print("Eigenvalues of H:\n", qml.eigvals(H_sparse, k=16)) -print("\nEigenvalues of H_tapered:\n", qml.eigvals(H_tapered_sparse, k=4)) - -############################################################################## -# Note that a second-quantized Hamiltonian is independent of the number of electrons and its -# eigenspectrum contains the energies of the neutral and charged molecules. Therefore, the -# smallest eigenvalue returned by :func:`~.pennylane.eigvals` for a molecular Hamiltonian -# might correspond to the neutral or charged molecule. While in the case of :math:`\textrm{HeH}^+,` -# qubit tapering allows specifying the optimal sector of the eigenvectors corresponding only to the -# correct number of electrons, it is generally guaranteed that the optimal sector covers all -# eigenvectors with the correct number of electrons, but may contain additional eigenvectors of -# different charge. Therefore, the ground-state energy of the :math:`\textrm{HeH}^+` cation is -# the smallest eigenvalue of the tapered Hamiltonian. -# -# Tapering the reference state -# ---------------------------- -# The ground state Hartree-Fock energy of :math:`\textrm{HeH}^+` can be computed by directly -# applying the Hamiltonians to the Hartree-Fock state. For the tapered Hamiltonian, this requires -# transforming the Hartree-Fock state with the same symmetries obtained for the original -# Hamiltonian. This reduces the number of qubits in the Hartree-Fock state to match that of the -# tapered Hamiltonian. It can be done with the :func:`~.pennylane.qchem.taper_hf` function. - -state_tapered = qml.qchem.taper_hf(generators, paulixops, paulix_sector, - num_electrons=n_electrons, num_wires=len(H.wires)) -print(state_tapered) - -############################################################################## -# Recall that the original Hartree-Fock state for the :math:`\textrm{HeH}^+` cation is -# :math:`[1 1 0 0].` We can now generate the qubit representation of these states and compute the -# Hartree-Fock energies for each Hamiltonian. - -dev = qml.device("default.qubit", wires=H.wires) -@qml.qnode(dev, interface="jax") -def circuit(): - qml.BasisState(jnp.array([1, 1, 0, 0]), wires=H.wires) - return qml.state() - -qubit_state = circuit() -HF_energy = qubit_state.T @ H.sparse_matrix().toarray() @ qubit_state -print(f"HF energy: {jnp.real(HF_energy):.8f} Ha") - -dev = qml.device("lightning.qubit", wires=H_tapered.wires) -@qml.qnode(dev, interface="jax") -def circuit(): - qml.BasisState(jnp.array([1, 1]), wires=H_tapered.wires) - return qml.state() - -qubit_state = circuit() -HF_energy = qubit_state.T @ H_tapered.sparse_matrix().toarray() @ qubit_state -print(f"HF energy (tapered): {jnp.real(HF_energy):.8f} Ha") - -############################################################################## -# These values are identical to the reference Hartree-Fock energy :math:`-2.8543686493` Ha. -# -# VQE simulation -# -------------- -# Finally, we can use the tapered Hamiltonian and the tapered reference state to perform a VQE -# simulation and compute the ground-state energy of the :math:`\textrm{HeH}^+` cation. We build a -# tapered variational ansatz `[3] `__ -# that prepares an entangled state by evolving the tapered Hartree-Fock state using the tapered -# particle-conserving gates, i.e., the :func:`~.pennylane.SingleExcitation` and -# :func:`~.pennylane.DoubleExcitation` operations tapered using -# :func:`~.pennylane.qchem.taper_operation`. - -singles, doubles = qml.qchem.excitations(n_electrons, len(H.wires)) -tapered_doubles = [ - qml.taper_operation(qml.DoubleExcitation, generators, paulixops, paulix_sector, - wire_order=H.wires, op_wires=double) for double in doubles -] -tapered_singles = [ - qml.taper_operation(qml.SingleExcitation, generators, paulixops, paulix_sector, - wire_order=H.wires, op_wires=single) for single in singles -] - -dev = qml.device("lightning.qubit", wires=H_tapered.wires) - -@qml.qnode(dev, interface="jax") -def tapered_circuit(params): - qml.BasisState(state_tapered, wires=H_tapered.wires) - for idx, tapered_op in enumerate(tapered_doubles + tapered_singles): - tapered_op(params[idx]) - return qml.expval(H_tapered) - -############################################################################## -# We define an optimizer and the initial values of the circuit parameters and optimize the circuit -# parameters with respect to the ground state energy. - -import optax -import catalyst - -opt = optax.sgd(learning_rate=0.8) # sgd stands for StochasticGradientDescent -init_params = jnp.zeros(len(doubles) + len(singles)) - -def update_step(i, params, opt_state): - """Perform a single gradient update step""" - grads = catalyst.grad(tapered_circuit)(params) - updates, opt_state = opt.update(grads, opt_state) - params = optax.apply_updates(params, updates) - return (params, opt_state) - -loss_history = [] - -opt_state = opt.init(init_params) -params = init_params - -for i in range(1, 41): - params, opt_state = update_step(i, params, opt_state) - energy = tapered_circuit(params) - if not i % 5: - print(f"n: {i}, E: {energy:.8f} Ha, Params: {params}") - -############################################################################## -# The computed energy matches the FCI energy, :math:`-2.862595242378` Ha, while the number of qubits -# and the number of Hamiltonian terms are significantly reduced with respect to their original -# values. -# -# Conclusions -# ----------- -# Molecular Hamiltonians possess symmetries that can be leveraged to reduce the number of qubits -# required in quantum computing simulations. This tutorial introduces a PennyLane functionality that -# can be used for qubit tapering based on :math:`\mathbb{Z}_2` symmetries. The procedure includes -# obtaining tapered Hamiltonians and tapered reference states that can be used in variational -# quantum algorithms such as VQE. -# -# References -# ---------- -# -# .. [#bravyi2017] -# -# Sergey Bravyi, Jay M. Gambetta, Antonio Mezzacapo, Kristan Temme, "Tapering off qubits to -# simulate fermionic Hamiltonians". `arXiv:1701.08213 `__ -# -# .. [#setia2019] -# -# Kanav Setia, Richard Chen, Julia E. Rice, Antonio Mezzacapo, Marco Pistoia, James Whitfield, -# "Reducing qubit requirements for quantum simulation using molecular point group symmetries". -# `arXiv:1910.14644 `__ -# -# -# -# About the author -# ---------------- -# +r""" + +Qubit tapering +============== + +.. meta:: + :property="og:description": Learn how to taper off qubits + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qubit_tapering.png + +.. related:: + tutorial_quantum_chemistry Building molecular Hamiltonians + tutorial_vqe A brief overview of VQE + tutorial_givens_rotations Givens rotations for quantum chemistry + tutorial_adaptive_circuits Adaptive circuits for quantum chemistry + tutorial_differentiable_HF Differentiable Hartree-Fock + +*Authors: Utkarsh Azad and Soran Jahangiri. Posted: 16 May 2022. Last updated: 08 Nov 2022* + +The performance of variational quantum algorithms is considerably limited by the number of qubits +required to represent wave functions. In the context of quantum chemistry, this +limitation hinders the treatment of large molecules with algorithms such as the :doc:`variational quantum +eigensolver (VQE) `. Several approaches have been developed to reduce the qubit requirements for +quantum chemistry calculations. In this tutorial, we demonstrate the symmetry-based qubit +tapering approach which allows reducing the number of qubits required to perform molecular quantum +simulations based on the :math:`\mathbb{Z}_2` symmetries present in molecular Hamiltonians +[#bravyi2017]_ [#setia2019]_. + +A molecular Hamiltonian in the qubit basis can be expressed as a linear combination of Pauli words +as + +.. math:: H = \sum_{i=1}^r h_i P_i, + +where :math:`h_i` is a real coefficient and :math:`P_i` is a tensor product of Pauli and +identity operators acting on :math:`M` qubits + +.. math:: P_i \in \pm \left \{ I, X, Y, Z \right \} ^ {\bigotimes M}. + +The main idea in the symmetry-based qubit tapering approach is to find a unitary operator :math:`U` +that transforms :math:`H` to a new Hamiltonian :math:`H'` which has the same eigenvalues as +:math:`H` + +.. math:: H' = U^{\dagger} H U = \sum_{i=1}^r c_i \mu_i, + +such that each :math:`\mu_i` term in the new Hamiltonian always acts trivially, e.g., with an +identity or a Pauli operator, on a set of qubits. This allows tapering-off those qubits from the +Hamiltonian. + +For instance, consider the following Hamiltonian + +.. math:: H' = Z_0 X_1 - X_1 + Y_0 X_1, + +where all terms in the Hamiltonian act on the second qubit with the :math:`X` operator. It is +straightforward to show that each term in the Hamiltonian commutes with :math:`I_0 X_1` and the +ground-state eigenvector of :math:`H'` is also an eigenvector of :math:`I_0 X_1` with eigenvalues +:math:`\pm 1.` We can also rewrite the Hamiltonian as + +.. math:: H' = (Z_0 I_1 - I_0 I_1 + Y_0 I_1) I_0 X_1, + +which gives us + +.. math:: H'|\psi \rangle = \pm1 (Z_0 I_1 - I_0 I_1 + Y_0 I_1)|\psi \rangle, + +where :math:`|\psi \rangle` is an eigenvector of :math:`H'.` This means that the Hamiltonian +:math:`H` can be simplified as + +.. math:: H_{tapered} = \pm1 (Z_0 - I_0 + Y_0). + +The tapered Hamiltonian :math:`H_{tapered}` has the eigenvalues + +.. math:: [-2.41421, 0.41421], + +and + +.. math:: [2.41421, -0.41421], + +depending on the value of the :math:`\pm 1` prefactor. The eigenvalues of the original Hamiltonian +:math:`H` are + +.. math:: [2.41421, -2.41421, 0.41421, -0.41421], + +which are thus reproduced by the tapered Hamiltonian. + +More generally, we can construct the unitary :math:`U` such that each :math:`\mu_i` term acts with a +Pauli-X operator on a set of qubits +:math:`\left \{ j \right \}, j \in \left \{ l, ..., k \right \}` where :math:`j` is the qubit label. +This guarantees that each term of the transformed Hamiltonian commutes with each of the Pauli-X +operators applied to the :math:`j`-th qubit: + +.. math:: [H', X^j] = 0, + +and the eigenvectors of the transformed Hamiltonian :math:`H'` are also eigenvectors of each of the +:math:`X^{j}` operators. Then we can factor out all of the :math:`X^{j}` operators from the +transformed Hamiltonian and replace them with their eigenvalues :math:`\pm 1.` This gives us a +set of tapered Hamiltonians depending on which eigenvalue :math:`\pm 1` we chose for each of the +:math:`X^{j}` operators. For instance, in the case of two tapered qubits, we have four eigenvalue +sectors: :math:`[+1, +1]`, :math:`[-1, +1]`, :math:`[+1, -1],` :math:`[-1, -1].` In these tapered +Hamiltonians, the set of :math:`\left \{ j \right \}, j \in \left \{ l, ..., k \right \}` qubits +are eliminated. For tapered molecular Hamiltonians, it is possible to determine the optimal sector +of the eigenvalues that corresponds to the ground state. This is explained in more detail in the +following sections. + +The unitary operator :math:`U` can be constructed as a +`Clifford `__ operator [#bravyi2017]_ + +.. math:: U = \Pi_j \left [\frac{1}{\sqrt{2}} \left (X^{q(j)} + \tau_j \right) \right], + +where :math:`\tau` denotes the generators of the symmetry group of :math:`H` and +:math:`X^{q}` operators act on those qubits that will be ultimately tapered off from +the Hamiltonian. The symmetry group of the Hamiltonian is defined as an Abelian group of Pauli words that commute +with each term in the Hamiltonian (excluding :math:`−I`). The +`generators `__ of the symmetry group are +those elements of the group that can be combined, along with their inverses, to create any other +member of the group. + +Let's use the qubit tapering method and obtain the ground state energy of the `Helium hydride +cation `__ :math:`\textrm{HeH}^+.` + +Tapering the molecular Hamiltonian +---------------------------------- + +In PennyLane, a :doc:`molecular Hamiltonian ` can be created by specifying the atomic symbols and +coordinates. +""" +import pennylane as qml +from jax import numpy as jnp +import jax + +jax.config.update("jax_enable_x64", True) +symbols = ["He", "H"] +geometry = jnp.array([[0.00000000, 0.00000000, -0.87818361], + [0.00000000, 0.00000000, 0.87818362]]) + +molecule = qml.qchem.Molecule(symbols, geometry, charge=1) +H, qubits = qml.qchem.molecular_hamiltonian(molecule) +H + +############################################################################## +# This Hamiltonian contains 27 terms where each term acts on up to four qubits. +# +# We can now obtain the symmetry generators and the :math:`X^{j}` operators that are +# used to construct the unitary :math:`U` operator that transforms the :math:`\textrm{HeH}^+` +# Hamiltonian. In PennyLane, these are constructed by using the +# :func:`~.pennylane.symmetry_generators` and :func:`~.pennylane.paulix_ops` functions. + +generators = qml.symmetry_generators(H) +paulixops = qml.paulix_ops(generators, qubits) + +for idx, generator in enumerate(generators): + print(f"generator {idx+1}: {generator}, paulix_op: {paulixops[idx]}") + +############################################################################## +# Once the operator :math:`U` is applied, each of the Hamiltonian terms will act on the qubits +# :math:`q_2, q_3` either with the identity or with a Pauli-X operator. For each of these qubits, +# we can simply replace the Pauli-X operator with one of its eigenvalues :math:`+1` or :math:`-1.` +# This results in a total number of :math:`2^k` Hamiltonians, where :math:`k` is the number of +# tapered-off qubits and each Hamiltonian corresponds to one eigenvalue sector. The optimal sector +# corresponding to the ground-state energy of the molecule can be obtained by using the +# :func:`~.pennylane.qchem.optimal_sector` function. + + +n_electrons = 2 +paulix_sector = qml.qchem.optimal_sector(H, generators, n_electrons) +print(paulix_sector) + +############################################################################## +# The optimal eigenvalues are :math:`-1, -1` for qubits :math:`q_2, q_3,` respectively. We can now +# build the tapered Hamiltonian with the :func:`~.pennylane.taper` function which +# constructs the operator :math:`U,` applies it to the Hamiltonian and finally tapers off the +# qubits :math:`q_2, q_3` by replacing the Pauli-X operators acting on those qubits with the optimal +# eigenvalues. + +H_tapered = qml.taper(H, generators, paulixops, paulix_sector) +H_tapered_coeffs, H_tapered_ops = H_tapered.terms() +H_tapered = qml.Hamiltonian(jnp.real(jnp.array(H_tapered_coeffs)), H_tapered_ops) +print(H_tapered) + +############################################################################## +# The new Hamiltonian has only 9 non-zero terms acting on only 2 qubits! We can verify that the +# original and the tapered Hamiltonian both give the correct ground state energy of the +# :math:`\textrm{HeH}^+` cation, which is :math:`-2.862595242378` Ha computed with the full +# configuration interaction (FCI) method. In PennyLane, it's possible to build a sparse matrix +# representation of Hamiltonians. This allows us to directly diagonalize them to obtain exact values +# of the ground-state energies. + +H_sparse = qml.SparseHamiltonian(H.sparse_matrix(), wires=H.wires) +H_tapered_sparse = qml.SparseHamiltonian(H_tapered.sparse_matrix(), wires=H_tapered.wires) + +print("Eigenvalues of H:\n", qml.eigvals(H_sparse, k=16)) +print("\nEigenvalues of H_tapered:\n", qml.eigvals(H_tapered_sparse, k=4)) + +############################################################################## +# Note that a second-quantized Hamiltonian is independent of the number of electrons and its +# eigenspectrum contains the energies of the neutral and charged molecules. Therefore, the +# smallest eigenvalue returned by :func:`~.pennylane.eigvals` for a molecular Hamiltonian +# might correspond to the neutral or charged molecule. While in the case of :math:`\textrm{HeH}^+,` +# qubit tapering allows specifying the optimal sector of the eigenvectors corresponding only to the +# correct number of electrons, it is generally guaranteed that the optimal sector covers all +# eigenvectors with the correct number of electrons, but may contain additional eigenvectors of +# different charge. Therefore, the ground-state energy of the :math:`\textrm{HeH}^+` cation is +# the smallest eigenvalue of the tapered Hamiltonian. +# +# Tapering the reference state +# ---------------------------- +# The ground state Hartree-Fock energy of :math:`\textrm{HeH}^+` can be computed by directly +# applying the Hamiltonians to the Hartree-Fock state. For the tapered Hamiltonian, this requires +# transforming the Hartree-Fock state with the same symmetries obtained for the original +# Hamiltonian. This reduces the number of qubits in the Hartree-Fock state to match that of the +# tapered Hamiltonian. It can be done with the :func:`~.pennylane.qchem.taper_hf` function. + +state_tapered = qml.qchem.taper_hf(generators, paulixops, paulix_sector, + num_electrons=n_electrons, num_wires=len(H.wires)) +print(state_tapered) + +############################################################################## +# Recall that the original Hartree-Fock state for the :math:`\textrm{HeH}^+` cation is +# :math:`[1 1 0 0].` We can now generate the qubit representation of these states and compute the +# Hartree-Fock energies for each Hamiltonian. + +dev = qml.device("default.qubit", wires=H.wires) +@qml.qnode(dev, interface="jax") +def circuit(): + qml.BasisState(jnp.array([1, 1, 0, 0]), wires=H.wires) + return qml.state() + +qubit_state = circuit() +HF_energy = qubit_state.T @ H.sparse_matrix().toarray() @ qubit_state +print(f"HF energy: {jnp.real(HF_energy):.8f} Ha") + +dev = qml.device("lightning.qubit", wires=H_tapered.wires) +@qml.qnode(dev, interface="jax") +def circuit(): + qml.BasisState(jnp.array([1, 1]), wires=H_tapered.wires) + return qml.state() + +qubit_state = circuit() +HF_energy = qubit_state.T @ H_tapered.sparse_matrix().toarray() @ qubit_state +print(f"HF energy (tapered): {jnp.real(HF_energy):.8f} Ha") + +############################################################################## +# These values are identical to the reference Hartree-Fock energy :math:`-2.8543686493` Ha. +# +# VQE simulation +# -------------- +# Finally, we can use the tapered Hamiltonian and the tapered reference state to perform a VQE +# simulation and compute the ground-state energy of the :math:`\textrm{HeH}^+` cation. We build a +# tapered variational ansatz `[3] `__ +# that prepares an entangled state by evolving the tapered Hartree-Fock state using the tapered +# particle-conserving gates, i.e., the :func:`~.pennylane.SingleExcitation` and +# :func:`~.pennylane.DoubleExcitation` operations tapered using +# :func:`~.pennylane.qchem.taper_operation`. + +singles, doubles = qml.qchem.excitations(n_electrons, len(H.wires)) +tapered_doubles = [ + qml.taper_operation(qml.DoubleExcitation, generators, paulixops, paulix_sector, + wire_order=H.wires, op_wires=double) for double in doubles +] +tapered_singles = [ + qml.taper_operation(qml.SingleExcitation, generators, paulixops, paulix_sector, + wire_order=H.wires, op_wires=single) for single in singles +] + +dev = qml.device("lightning.qubit", wires=H_tapered.wires) + +@qml.qnode(dev, interface="jax") +def tapered_circuit(params): + qml.BasisState(state_tapered, wires=H_tapered.wires) + for idx, tapered_op in enumerate(tapered_doubles + tapered_singles): + tapered_op(params[idx]) + return qml.expval(H_tapered) + +############################################################################## +# We define an optimizer and the initial values of the circuit parameters and optimize the circuit +# parameters with respect to the ground state energy. + +import optax +import catalyst + +opt = optax.sgd(learning_rate=0.8) # sgd stands for StochasticGradientDescent +init_params = jnp.zeros(len(doubles) + len(singles)) + +def update_step(i, params, opt_state): + """Perform a single gradient update step""" + grads = catalyst.grad(tapered_circuit)(params) + updates, opt_state = opt.update(grads, opt_state) + params = optax.apply_updates(params, updates) + return (params, opt_state) + +loss_history = [] + +opt_state = opt.init(init_params) +params = init_params + +for i in range(1, 41): + params, opt_state = update_step(i, params, opt_state) + energy = tapered_circuit(params) + if not i % 5: + print(f"n: {i}, E: {energy:.8f} Ha, Params: {params}") + +############################################################################## +# The computed energy matches the FCI energy, :math:`-2.862595242378` Ha, while the number of qubits +# and the number of Hamiltonian terms are significantly reduced with respect to their original +# values. +# +# Conclusions +# ----------- +# Molecular Hamiltonians possess symmetries that can be leveraged to reduce the number of qubits +# required in quantum computing simulations. This tutorial introduces a PennyLane functionality that +# can be used for qubit tapering based on :math:`\mathbb{Z}_2` symmetries. The procedure includes +# obtaining tapered Hamiltonians and tapered reference states that can be used in variational +# quantum algorithms such as VQE. +# +# References +# ---------- +# +# .. [#bravyi2017] +# +# Sergey Bravyi, Jay M. Gambetta, Antonio Mezzacapo, Kristan Temme, "Tapering off qubits to +# simulate fermionic Hamiltonians". `arXiv:1701.08213 `__ +# +# .. [#setia2019] +# +# Kanav Setia, Richard Chen, Julia E. Rice, Antonio Mezzacapo, Marco Pistoia, James Whitfield, +# "Reducing qubit requirements for quantum simulation using molecular point group symmetries". +# `arXiv:1910.14644 `__ +# +# +# +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_qutrits_bernstein_vazirani/demo.py b/demonstrations_v2/tutorial_qutrits_bernstein_vazirani/demo.py index e2da31eb6e..2664509a1f 100644 --- a/demonstrations_v2/tutorial_qutrits_bernstein_vazirani/demo.py +++ b/demonstrations_v2/tutorial_qutrits_bernstein_vazirani/demo.py @@ -1,409 +1,409 @@ -r""" - -Qutrits and quantum algorithms -============================== - -.. meta:: - :property="og:description": Learn how to interpret the Bernstein-Vazirani algorithm with qutrits - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_qutrits_bernstein_vazirani.png - -.. related:: - - -*Author: Guillermo Alonso-Linaje — Posted: 9 May 2023. Last updated: 9 May 2023.* - -A qutrit is a basic quantum unit that can exist in a superposition of three possible quantum states, represented as :math:`|0\rangle`, :math:`|1\rangle`, and :math:`|2\rangle,` which functions as a generalization of the qubit. -There are many problems to which we can apply these units, among which we can highlight an improved decomposition of the Toffoli gate. -Using only qubits, it would take at least 6 CNOTs to decompose the gate, whereas with qutrits it would be enough to use 3 [#toffoli_qutrits]_. -This is one of the reasons why it is important to develop the intuition behind this basic unit of information, to see where qutrits can provide an advantage. The goal of this demo is to start working with qutrits from an algorithmic point of view. To do so, we will start with the Bernstein–Vazirani algorithm, which we will initially explore using qubits, and later using qutrits. - - - -Bernstein–Vazirani algorithm ------------------------------- - -The Bernstein–Vazirani algorithm is a quantum algorithm developed by Ethan Bernstein and Umesh Vazirani [#bv]_. -It was one of the first examples that demonstrated an exponential advantage of a quantum computer over a traditional one. So, in this first section we will understand the problem that they tackled. - - -Suppose there is some hidden bit string "a" that we are trying to learn, and that we have access to a function :math:`f(\vec{x})` that implements the following scalar product: - -.. math:: - f(\vec{x}) := \vec{a}\cdot\vec{x} \pmod 2, - -where :math:`\vec{a}=(a_0,a_1,...,a_{n-1})` and :math:`\vec{x}=(x_0,x_1,...,x_{n-1})` are bit strings of length :math:`n` with :math:`a_i, x_i \in \{0,1\}`. Our challenge will be to discover the hidden value of :math:`\vec{a}` by using the function :math:`f.` We don't know anything about :math:`\vec{a}` so the only thing we can do is to evaluate :math:`f` at different points :math:`\vec{x}` with the idea of gaining hidden information. - - -To give an example, let's imagine that we take :math:`\vec{x}=(1,0,1)` and get the value :math:`f(\vec{x}) = 0`. Although it may not seem obvious, knowing the structure that :math:`f` has, this gives us some information about :math:`\vec{a}.` In this case, :math:`a_0` and :math:`a_2` have the same value. This is because taking that value of :math:`\vec{x}`, the function will be equivalent to :math:`a_0 + a_2 \pmod 2,` which will only take the value 0 if they are equal. -I invite you to take your time to think of a possible strategy (at the classical level) in order to determine :math:`\vec{a}` with the minimum number of evaluations of the function :math:`f.` - -The optimal solution requires only :math:`n` calls to the function! Let's see how we can do this. -Knowing the form of :math:`\vec{a}` and :math:`\vec{x},` we can rewrite :math:`f` as: - -.. math:: - f(\vec{x})=\sum_{i=0}^{n-1}a_ix_i \pmod 2. - -The strategy will be to deduce one element of :math:`\vec{a}` with each call to the function. Imagine that we want to determine the value :math:`a_i.` We can simply choose :math:`\vec{x}` as a vector of all zeros except a one in the i-th position, since in this case: - - - -.. math:: - f(\vec{x})= 0\cdot a_0 + 0\cdot a_1 + ... + 1\cdot a_i + ... + 0\cdot a_{n-1} \pmod 2 \quad= a_i. - -It is trivial to see, therefore, that :math:`n` evaluations of :math:`f` are needed. The question is: can we do it more efficiently with a quantum computer? The answer is yes, and in fact, we only need to make one call to the function! - -The first step is to see how we can represent this statement in a circuit. In this case, we will assume that we have an oracle :math:`U_f` that encodes the function, as we can see in the picture below. - - -.. figure:: ../_static/demonstration_assets/qutrits_bernstein_vazirani/oracle_qutrit.jpg - :scale: 35% - :alt: Oracle definition. - :align: center - - Oracle representation of the function. - - -In general, :math:`U_f` sends the state :math:`|\vec{x} \rangle |y\rangle` to the state :math:`| \vec{x} \rangle |y + \vec{a} \cdot \vec{x} \pmod{2} \rangle.` - -Suppose, for example, that :math:`\vec{a}=[0,1,0]`. Then :math:`U_f|111\rangle |0\rangle = |111\rangle|1\rangle`, since we are evaluating :math:`f` at the point :math:`\vec{x} = [1,1,1]`. The scalar product between the two values is :math:`1,` so the last qubit of the output will take the value :math:`1.` - -The Bernstein–Vazirani algorithm makes use of this oracle according to the following circuit: - -.. figure:: ../_static/demonstration_assets/qutrits_bernstein_vazirani/bernstein_vazirani_algorithm.jpg - :scale: 35% - :alt: Bernstein-Vazirani's algorithm - :align: center - - Bernstein–Vazirani algorithm. - - -What we can see is that, by simply using Hadamard gates before and after the oracle, after a single run, the output of the circuit is exactly the hidden value of :math:`\vec{a}.` Let's do a little math to verify that this is so. - -First, the input to our circuit is :math:`|0001\rangle.` The second step is to apply Hadamard gates to this state, and for this we must use the following property: - -.. math:: - H^{\otimes n}|\vec{x}\rangle = \frac{1}{\sqrt{2^n}}\sum_{\vec{z} \in \{0,1\}^n}(-1)^{\vec{x}\cdot\vec{z}}|\vec{z}\rangle. - -Taking as input the value :math:`|0001\rangle,` we obtain the state - -.. math:: - |\phi_1\rangle=H^{\otimes 4}|0001\rangle = H^{\otimes 3}|000\rangle\otimes H|1\rangle = \frac{1}{\sqrt{2^3}}\left(\sum_{z \in \{0,1\}^3}|\vec{z}\rangle\right)\left(\frac{|0\rangle-|1\rangle}{\sqrt{2}}\right). - -As you can see, we have separated the first three qubits from the fourth for clarity. -If we now apply our operator :math:`U_f,` - -.. math:: - |\phi_2\rangle= U_f |\phi_1\rangle = \frac{1}{\sqrt{2^3}}\left(\sum_{\vec{z} \in \{0,1\}^3}|\vec{z}\rangle\frac{|\vec{a}\cdot\vec{z} \pmod 2\rangle-|1 + \vec{a}\cdot\vec{z} \pmod 2\rangle}{\sqrt{2}}\right). - -Depending on the value of :math:`f(\vec{x}),` the final part of the expression can take two values and it can be checked that - -.. math:: - |\phi_2\rangle = \frac{1}{\sqrt{2^3}}\left(\sum_{\vec{z} \in \{0,1\}^3}|\vec{z}\rangle(-1)^{\vec{a}\cdot\vec{z}}\frac{|0\rangle-|1\rangle}{\sqrt{2}}\right). - -This is because, if :math:`\vec{a}\cdot\vec{z}` takes the value :math:`0`, we will have the :math:`\frac{|0\rangle - |1\rangle}{\sqrt{2}}`, and if it takes the value :math:`1,` the result will be :math:`\frac{|1\rangle - |0\rangle}{\sqrt{2}} = - \frac{|0\rangle - |1\rangle}{\sqrt{2}}.` Therefore, by calculating :math:`(-1)^{\vec{a}\cdot\vec{z}}` we cover both cases. -After this, we can include the :math:`(-1)^{\vec{a}\cdot\vec{z}}` factor in the :math:`|\vec{z}\rangle` term and disregard the last qubit, since we are not going to use it again: - -.. math:: - |\phi_2\rangle =\frac{1}{\sqrt{2^3}}\sum_{\vec{z} \in \{0,1\}^3}(-1)^{\vec{a}\cdot\vec{z}}|\vec{z}\rangle. - -Note that you cannot always disregard a qubit. In cases where there is entanglement with other qubits that is not possible, but in this case the state is separable. - -Finally, we will reapply the property of the first step to calculate the result after using the Hadamard: - -.. math:: - |\phi_3\rangle = H^{\otimes 3}|\phi_2\rangle = \frac{1}{2^3}\sum_{\vec{z} \in \{0,1\}^3}(-1)^{\vec{a}\cdot\vec{z}}\left(\sum_{\vec{y} \in \{0,1\}^3}(-1)^{\vec{z}\cdot\vec{y}}|\vec{y}\rangle\right). - -Rearranging this expression, we obtain: - -.. math:: - |\phi_3\rangle = \frac{1}{2^3}\sum_{\vec{y} \in \{0,1\}^3}\left(\sum_{\vec{z} \in \{0,1\}^3}(-1)^{\vec{a}\cdot\vec{z}+\vec{y}\cdot\vec{z}}\right)|\vec{y}\rangle. - -Perfect! The only thing left to check is that, in fact, the previous state is exactly :math:`|\vec{a}\rangle`. It may seem complicated, but I invite you to demonstrate it by showing that :math:`\langle \vec{a}|\phi_3\rangle = 1.` Let's go to the code and check that it works. - -Algorithm coding with qubits ------------------------------- - -We will first code the classical solution. We will do this inside a quantum circuit to understand how to use the oracle, but we are really just programming the qubits as bits. - -""" - - -import pennylane as qml - -dev = qml.device("default.qubit", wires = 4, shots = 1) - -def Uf(): - # The oracle in charge of encoding a hidden "a" value. - qml.CNOT(wires=[1, 3]) - qml.CNOT(wires=[2 ,3]) - - -@qml.qnode(dev) -def circuit0(): - """Circuit used to derive a0""" - - - # Initialize x = [1,0,0] - qml.PauliX(wires = 0) - - # Apply our oracle - - Uf() - - # We measure the last qubit - return qml.sample(wires = 3) - -@qml.qnode(dev) -def circuit1(): - # Circuit used to derive a1 - - # Initialize x = [0,1,0] - qml.PauliX(wires = 1) - - # We apply our oracle - Uf() - - # We measure the last qubit - return qml.sample(wires = 3) - -@qml.qnode(dev) -def circuit2(): - # Circuit used to derive a2 - # Initialize x = [0,0,1] - qml.PauliX(wires = 2) - - # We apply our oracle - Uf() - - # We measure the last qubit - return qml.sample(wires = 3) - -# We run for x = [1,0,0] -a0 = circuit0() - -# We run for x = [0,1,0] -a1 = circuit1() - -# We run for x = [0,0,1] -a2 = circuit2() - -print(f"The value of 'a' is [{a0},{a1},{a2}]") - -############################################################################## -# -# In this case, with 3 queries (:math:`n=3`), we have discovered the value of :math:`\vec{a}.` Let's run the Bernstein–Vazirani subroutine (using qubits as qubits this time) to check that one call is enough: - - -@qml.qnode(dev) -def circuit(): - - # We initialize to |0001> - qml.PauliX(wires = 3) - - # We run the Hadamards - for i in range(4): - qml.Hadamard(wires = i) - - # We apply our function - Uf() - - # We run the Hadamards - for i in range(3): - qml.Hadamard(wires = i) - - # We measure the first 3 qubits - return qml.sample(wires = range(3)) - -a = circuit() - -print(f"The value of a is {a}") - - -############################################################################## -# Great! Everything works as expected, and we have successfully executed the Bernstein–Vazirani algorithm. -# It is important to note that, because of how we defined our device, we are only using a single shot to find this value! -# -# Generalization to qutrits -# ------------------------------ -# -# To make things more interesting, let's imagine a new scenario. We are given a function of the form :math:`f(\vec{x}) := \vec{a}\cdot\vec{x} \pmod 3` where, :math:`\vec{a}=(a_0,a_1,...,a_{n-1})` and :math:`\vec{x}=(x_0,x_1,...,x_{n-1})` are strings of length :math:`n` with :math:`a_i, x_i \in \{0,1,2\}`. How can we minimize the number of calls to the function to discover :math:`\vec{a}?` In this case, the classical procedure to detect the value of :math:`\vec{a}` is the same as in the case of qubits: we will evaluate the output of the inputs :math:`[1,0,0],` :math:`[0,1,0]` and :math:`[0,0,1].` -# -# But how can we work with these kinds of functions in a simple way? To do this we must use a qutrit and its operators. -# By using this new unit of information and unlocking the third orthogonal state, we will have states represented with a vector of dimension :math:`3^n` and the operators will be :math:`3^n \times 3^n` matrices where :math:`n` is the number of qutrits. -# Specifically, we will use the :class:`~.pennylane.TShift` gate, which is equivalent to the :class:`~.pennylane.PauliX` gate for qutrits. It has the following property: -# -# .. math:: -# \text{TShift}|0\rangle = |1\rangle -# -# .. math:: -# \text{TShift}|1\rangle = |2\rangle -# -# .. math:: -# \text{TShift}|2\rangle = |0\rangle -# -# This means we can use this gate to initialize each of the states. -# Another gate that we will use for the oracle definition is the :class:`~.pennylane.TAdd` gate, which is the generalization of the :class:`~.pennylane.Toffoli` gate for qutrits. -# These generalizations simply adjust the addition operation to be performed in modulo 3 instead of modulo 2. -# So, with these ingredients, we are ready to go to the code. - -dev = qml.device("default.qutrit", wires=4, shots=1) - -def Uf(): - # The oracle in charge of encoding a hidden "a" value. - qml.TAdd(wires = [1,3]) - qml.TAdd(wires = [1,3]) - qml.TAdd(wires = [2,3]) - -@qml.qnode(dev) -def circuit0(): - - # Initialize x = [1,0,0] - qml.TShift(wires = 0) - - # We apply our oracle - Uf() - - # We measure the last qutrit - return qml.sample(wires = 3) - -@qml.qnode(dev) -def circuit1(): - - # Initialize x = [0,1,0] - qml.TShift(wires = 1) - - # We apply our oracle - Uf() - - # We measure the last qutrit - return qml.sample(wires = 3) - -@qml.qnode(dev) -def circuit2(): - - # Initialize x = [0,0,1] - qml.TShift(wires = 2) - - # We apply our oracle - Uf() - - # We measure the last qutrit - return qml.sample(wires = 3) - -# Run to obtain the three trits of a -a0 = circuit0() -a1 = circuit1() -a2 = circuit2() - - -print(f"The value of a is [{a0},{a1},{a2}]") - -############################################################################## -# -# The question is, can we perform the same procedure as we have done before to find :math:`\vec{a}` using a single shot? The Hadamard gate also generalizes to qutrits (also denoted as :class:`~.pennylane.THadamard`), so we could try to simply substitute it and see what happens! -# -# -# The definition of the Hadamard gate in this space is: -# -# .. math:: -# \text{THadamard}=\frac{-i}{\sqrt{3}}\begin{pmatrix} -# 1 & 1 & 1\\ -# 1 & w & w^2\\ -# 1 & w^2 & w -# \end{pmatrix}, -# -# where :math:`w = e^{\frac{2 \pi i}{3}}.` -# Let's go to the code and see how to run this in PennyLane. - - -@qml.qnode(dev) -def circuit(): - - # We initialize to |0001> - qml.TShift(wires = 3) - - # We run the THadamard - for i in range(4): - qml.THadamard(wires = i) - -# We run the oracle - Uf() - -# We run the THadamard again - for i in range(3): - qml.THadamard(wires = i) - - # We measure the first 3 qutrits - return qml.sample(wires = range(3)) - -a = circuit() - -print(f"The value of a is {a}") - -############################################################################## -# -# Awesome! The Bernstein–Vazirani algorithm generalizes perfectly to qutrits! Let's do the mathematical calculations again to check that it does indeed make sense. -# -# As before, the input of our circuit is :math:`|0001\rangle.` -# We will then use the Hadamard definition applied to qutrits: -# -# .. math:: -# H^{\otimes n}|\vec{x}\rangle = \frac{1}{\sqrt{3^n}}\sum_{\vec{z} \in \{0,1,2\}^n}w^{\vec{x}\cdot\vec{z}}|\vec{z}\rangle. -# -# In this case, we are disregarding the global phase of :math:`-i` for simplicity. -# Applying this to the state :math:`|0001\rangle,` we obtain -# -# .. math:: -# |\phi_1\rangle=H^{\otimes 4}|0001\rangle = H^{\otimes 3}|000\rangle\otimes H|1\rangle = \frac{1}{\sqrt{3^3}}\left(\sum_{z \in \{0,1,2\}^3}|\vec{z}\rangle\frac{|0\rangle+w|1\rangle+w^2|2\rangle}{\sqrt{3}}\right). -# -# After that, we apply the operator :math:`U_f` to obtain -# -# .. math:: -# |\phi_2\rangle= U_f |\phi_1\rangle = \frac{1}{\sqrt{3^3}}\left(\sum_{\vec{z} \in \{0,1,2\}^3}|\vec{z}\rangle\frac{|0 + \vec{a}\cdot\vec{z} \pmod 3 \rangle+w|1+ \vec{a}\cdot\vec{z} \pmod 3 \rangle+w^2|2+ \vec{a}\cdot\vec{z} \pmod 3 \rangle}{\sqrt{3}}\right). -# -# Depending on the value of :math:`f(\vec{x}),` as before, we obtain three possible states: -# -# - If :math:`\vec{a}\cdot\vec{z} = 0,` we have :math:`\frac{1}{\sqrt{3}}\left(|0\rangle+w|1\rangle+w^2|2\rangle\right).` -# - If :math:`\vec{a}\cdot\vec{z} = 1,` we have :math:`\frac{w^2}{\sqrt{3}}\left(|0\rangle+|1\rangle+w|2\rangle\right).` -# - If :math:`\vec{a}\cdot\vec{z} = 2,` we have :math:`\frac{w}{\sqrt{3}}\left(|0\rangle+w^2|1\rangle+|2\rangle\right).` -# -# Based on this, we can group the three states as :math:`\frac{1}{\sqrt{3}}w^{-\vec{a}\cdot\vec{z}}\left(|0\rangle+w|1\rangle+w^2|2\rangle\right).` -# -# After this, we can enter the coefficient in the :math:`|\vec{z}\rangle` term and, as before, disregard the last qutrit, since we are not going to use it again: -# -# .. math:: -# |\phi_2\rangle =\frac{1}{\sqrt{3^3}}\sum_{\vec{z} \in \{0,1,2\}^3}w^{-\vec{a}\cdot\vec{z}}|\vec{z}\rangle. -# -# Finally, we reapply the THadamard: -# -# .. math:: -# |\phi_3\rangle := H^{\otimes 3}|\phi_2\rangle = \frac{1}{3^3}\sum_{\vec{z} \in \{0,1,2\}^3}w^{-\vec{a}\cdot\vec{z}}\left(\sum_{\vec{y} \in \{0,1,2\}^3}w^{\vec{z}\cdot\vec{y}}|\vec{y}\rangle\right). -# -# Rearranging this expression, we obtain: -# -# .. math:: -# |\phi_3\rangle = \frac{1}{3^3}\sum_{\vec{y} \in \{0,1,2\}^3}\left(\sum_{\vec{z} \in \{0,1,2\}^3}w^{-\vec{a}\cdot\vec{z}+\vec{y}\cdot\vec{z}}\right)|\vec{y}\rangle. -# -# -# In the same way as before, it can be easily checked that :math:`\langle \vec{a}|\phi_3\rangle = 1` and therefore, when measuring, one shot will be enough to obtain the value of :math:`\vec{a}!` -# -# Conclusion -# ---------- -# -# In this demo, we have practised the use of basic qutrit gates such as TShift or THadamard by applying the Bernstein–Vazirani algorithm. In this case, the generalization has been straightforward and we have found that it makes mathematical sense, but we cannot always substitute qubit gates for qutrit gates as we have seen in the demo. To give an easy example of this, we know the property that :math:`X = HZH`, but it turns out that this property does not generalize! The general property is actually :math:`X = H^{\dagger}ZH.` In the case of qubits it holds that :math:`H = H^{\dagger},` but in other dimensions it does not. I invite you to continue practising with other types of algorithms. For instance, will the `Deutsch–Jozsa algorithm `__ generalize well? Take a pen and paper and check it out! -# -# References -# ---------- -# -# .. [#bv] -# -# Ethan Bernstein, Umesh Vazirani, "Quantum Complexity Theory". `SIAM Journal on Computing Vol. 26 (1997). -# `__ -# -# .. [#toffoli_qutrits] -# Alexey Galda, Michael Cubeddu, Naoki Kanazawa, Prineha Narang, Nathan Earnest-Noble, `"Implementing a Ternary Decomposition of the Toffoli Gate on Fixed-FrequencyTransmon Qutrits". -# `__ -# About the author -# ---------------- -# - +r""" + +Qutrits and quantum algorithms +============================== + +.. meta:: + :property="og:description": Learn how to interpret the Bernstein-Vazirani algorithm with qutrits + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_qutrits_bernstein_vazirani.png + +.. related:: + + +*Author: Guillermo Alonso-Linaje — Posted: 9 May 2023. Last updated: 9 May 2023.* + +A qutrit is a basic quantum unit that can exist in a superposition of three possible quantum states, represented as :math:`|0\rangle`, :math:`|1\rangle`, and :math:`|2\rangle,` which functions as a generalization of the qubit. +There are many problems to which we can apply these units, among which we can highlight an improved decomposition of the Toffoli gate. +Using only qubits, it would take at least 6 CNOTs to decompose the gate, whereas with qutrits it would be enough to use 3 [#toffoli_qutrits]_. +This is one of the reasons why it is important to develop the intuition behind this basic unit of information, to see where qutrits can provide an advantage. The goal of this demo is to start working with qutrits from an algorithmic point of view. To do so, we will start with the Bernstein–Vazirani algorithm, which we will initially explore using qubits, and later using qutrits. + + + +Bernstein–Vazirani algorithm +------------------------------ + +The Bernstein–Vazirani algorithm is a quantum algorithm developed by Ethan Bernstein and Umesh Vazirani [#bv]_. +It was one of the first examples that demonstrated an exponential advantage of a quantum computer over a traditional one. So, in this first section we will understand the problem that they tackled. + + +Suppose there is some hidden bit string "a" that we are trying to learn, and that we have access to a function :math:`f(\vec{x})` that implements the following scalar product: + +.. math:: + f(\vec{x}) := \vec{a}\cdot\vec{x} \pmod 2, + +where :math:`\vec{a}=(a_0,a_1,...,a_{n-1})` and :math:`\vec{x}=(x_0,x_1,...,x_{n-1})` are bit strings of length :math:`n` with :math:`a_i, x_i \in \{0,1\}`. Our challenge will be to discover the hidden value of :math:`\vec{a}` by using the function :math:`f.` We don't know anything about :math:`\vec{a}` so the only thing we can do is to evaluate :math:`f` at different points :math:`\vec{x}` with the idea of gaining hidden information. + + +To give an example, let's imagine that we take :math:`\vec{x}=(1,0,1)` and get the value :math:`f(\vec{x}) = 0`. Although it may not seem obvious, knowing the structure that :math:`f` has, this gives us some information about :math:`\vec{a}.` In this case, :math:`a_0` and :math:`a_2` have the same value. This is because taking that value of :math:`\vec{x}`, the function will be equivalent to :math:`a_0 + a_2 \pmod 2,` which will only take the value 0 if they are equal. +I invite you to take your time to think of a possible strategy (at the classical level) in order to determine :math:`\vec{a}` with the minimum number of evaluations of the function :math:`f.` + +The optimal solution requires only :math:`n` calls to the function! Let's see how we can do this. +Knowing the form of :math:`\vec{a}` and :math:`\vec{x},` we can rewrite :math:`f` as: + +.. math:: + f(\vec{x})=\sum_{i=0}^{n-1}a_ix_i \pmod 2. + +The strategy will be to deduce one element of :math:`\vec{a}` with each call to the function. Imagine that we want to determine the value :math:`a_i.` We can simply choose :math:`\vec{x}` as a vector of all zeros except a one in the i-th position, since in this case: + + + +.. math:: + f(\vec{x})= 0\cdot a_0 + 0\cdot a_1 + ... + 1\cdot a_i + ... + 0\cdot a_{n-1} \pmod 2 \quad= a_i. + +It is trivial to see, therefore, that :math:`n` evaluations of :math:`f` are needed. The question is: can we do it more efficiently with a quantum computer? The answer is yes, and in fact, we only need to make one call to the function! + +The first step is to see how we can represent this statement in a circuit. In this case, we will assume that we have an oracle :math:`U_f` that encodes the function, as we can see in the picture below. + + +.. figure:: ../_static/demonstration_assets/qutrits_bernstein_vazirani/oracle_qutrit.jpg + :scale: 35% + :alt: Oracle definition. + :align: center + + Oracle representation of the function. + + +In general, :math:`U_f` sends the state :math:`|\vec{x} \rangle |y\rangle` to the state :math:`| \vec{x} \rangle |y + \vec{a} \cdot \vec{x} \pmod{2} \rangle.` + +Suppose, for example, that :math:`\vec{a}=[0,1,0]`. Then :math:`U_f|111\rangle |0\rangle = |111\rangle|1\rangle`, since we are evaluating :math:`f` at the point :math:`\vec{x} = [1,1,1]`. The scalar product between the two values is :math:`1,` so the last qubit of the output will take the value :math:`1.` + +The Bernstein–Vazirani algorithm makes use of this oracle according to the following circuit: + +.. figure:: ../_static/demonstration_assets/qutrits_bernstein_vazirani/bernstein_vazirani_algorithm.jpg + :scale: 35% + :alt: Bernstein-Vazirani's algorithm + :align: center + + Bernstein–Vazirani algorithm. + + +What we can see is that, by simply using Hadamard gates before and after the oracle, after a single run, the output of the circuit is exactly the hidden value of :math:`\vec{a}.` Let's do a little math to verify that this is so. + +First, the input to our circuit is :math:`|0001\rangle.` The second step is to apply Hadamard gates to this state, and for this we must use the following property: + +.. math:: + H^{\otimes n}|\vec{x}\rangle = \frac{1}{\sqrt{2^n}}\sum_{\vec{z} \in \{0,1\}^n}(-1)^{\vec{x}\cdot\vec{z}}|\vec{z}\rangle. + +Taking as input the value :math:`|0001\rangle,` we obtain the state + +.. math:: + |\phi_1\rangle=H^{\otimes 4}|0001\rangle = H^{\otimes 3}|000\rangle\otimes H|1\rangle = \frac{1}{\sqrt{2^3}}\left(\sum_{z \in \{0,1\}^3}|\vec{z}\rangle\right)\left(\frac{|0\rangle-|1\rangle}{\sqrt{2}}\right). + +As you can see, we have separated the first three qubits from the fourth for clarity. +If we now apply our operator :math:`U_f,` + +.. math:: + |\phi_2\rangle= U_f |\phi_1\rangle = \frac{1}{\sqrt{2^3}}\left(\sum_{\vec{z} \in \{0,1\}^3}|\vec{z}\rangle\frac{|\vec{a}\cdot\vec{z} \pmod 2\rangle-|1 + \vec{a}\cdot\vec{z} \pmod 2\rangle}{\sqrt{2}}\right). + +Depending on the value of :math:`f(\vec{x}),` the final part of the expression can take two values and it can be checked that + +.. math:: + |\phi_2\rangle = \frac{1}{\sqrt{2^3}}\left(\sum_{\vec{z} \in \{0,1\}^3}|\vec{z}\rangle(-1)^{\vec{a}\cdot\vec{z}}\frac{|0\rangle-|1\rangle}{\sqrt{2}}\right). + +This is because, if :math:`\vec{a}\cdot\vec{z}` takes the value :math:`0`, we will have the :math:`\frac{|0\rangle - |1\rangle}{\sqrt{2}}`, and if it takes the value :math:`1,` the result will be :math:`\frac{|1\rangle - |0\rangle}{\sqrt{2}} = - \frac{|0\rangle - |1\rangle}{\sqrt{2}}.` Therefore, by calculating :math:`(-1)^{\vec{a}\cdot\vec{z}}` we cover both cases. +After this, we can include the :math:`(-1)^{\vec{a}\cdot\vec{z}}` factor in the :math:`|\vec{z}\rangle` term and disregard the last qubit, since we are not going to use it again: + +.. math:: + |\phi_2\rangle =\frac{1}{\sqrt{2^3}}\sum_{\vec{z} \in \{0,1\}^3}(-1)^{\vec{a}\cdot\vec{z}}|\vec{z}\rangle. + +Note that you cannot always disregard a qubit. In cases where there is entanglement with other qubits that is not possible, but in this case the state is separable. + +Finally, we will reapply the property of the first step to calculate the result after using the Hadamard: + +.. math:: + |\phi_3\rangle = H^{\otimes 3}|\phi_2\rangle = \frac{1}{2^3}\sum_{\vec{z} \in \{0,1\}^3}(-1)^{\vec{a}\cdot\vec{z}}\left(\sum_{\vec{y} \in \{0,1\}^3}(-1)^{\vec{z}\cdot\vec{y}}|\vec{y}\rangle\right). + +Rearranging this expression, we obtain: + +.. math:: + |\phi_3\rangle = \frac{1}{2^3}\sum_{\vec{y} \in \{0,1\}^3}\left(\sum_{\vec{z} \in \{0,1\}^3}(-1)^{\vec{a}\cdot\vec{z}+\vec{y}\cdot\vec{z}}\right)|\vec{y}\rangle. + +Perfect! The only thing left to check is that, in fact, the previous state is exactly :math:`|\vec{a}\rangle`. It may seem complicated, but I invite you to demonstrate it by showing that :math:`\langle \vec{a}|\phi_3\rangle = 1.` Let's go to the code and check that it works. + +Algorithm coding with qubits +------------------------------ + +We will first code the classical solution. We will do this inside a quantum circuit to understand how to use the oracle, but we are really just programming the qubits as bits. + +""" + + +import pennylane as qml + +dev = qml.device("default.qubit", wires = 4, shots = 1) + +def Uf(): + # The oracle in charge of encoding a hidden "a" value. + qml.CNOT(wires=[1, 3]) + qml.CNOT(wires=[2 ,3]) + + +@qml.qnode(dev) +def circuit0(): + """Circuit used to derive a0""" + + + # Initialize x = [1,0,0] + qml.PauliX(wires = 0) + + # Apply our oracle + + Uf() + + # We measure the last qubit + return qml.sample(wires = 3) + +@qml.qnode(dev) +def circuit1(): + # Circuit used to derive a1 + + # Initialize x = [0,1,0] + qml.PauliX(wires = 1) + + # We apply our oracle + Uf() + + # We measure the last qubit + return qml.sample(wires = 3) + +@qml.qnode(dev) +def circuit2(): + # Circuit used to derive a2 + # Initialize x = [0,0,1] + qml.PauliX(wires = 2) + + # We apply our oracle + Uf() + + # We measure the last qubit + return qml.sample(wires = 3) + +# We run for x = [1,0,0] +a0 = circuit0() + +# We run for x = [0,1,0] +a1 = circuit1() + +# We run for x = [0,0,1] +a2 = circuit2() + +print(f"The value of 'a' is [{a0},{a1},{a2}]") + +############################################################################## +# +# In this case, with 3 queries (:math:`n=3`), we have discovered the value of :math:`\vec{a}.` Let's run the Bernstein–Vazirani subroutine (using qubits as qubits this time) to check that one call is enough: + + +@qml.qnode(dev) +def circuit(): + + # We initialize to |0001> + qml.PauliX(wires = 3) + + # We run the Hadamards + for i in range(4): + qml.Hadamard(wires = i) + + # We apply our function + Uf() + + # We run the Hadamards + for i in range(3): + qml.Hadamard(wires = i) + + # We measure the first 3 qubits + return qml.sample(wires = range(3)) + +a = circuit() + +print(f"The value of a is {a}") + + +############################################################################## +# Great! Everything works as expected, and we have successfully executed the Bernstein–Vazirani algorithm. +# It is important to note that, because of how we defined our device, we are only using a single shot to find this value! +# +# Generalization to qutrits +# ------------------------------ +# +# To make things more interesting, let's imagine a new scenario. We are given a function of the form :math:`f(\vec{x}) := \vec{a}\cdot\vec{x} \pmod 3` where, :math:`\vec{a}=(a_0,a_1,...,a_{n-1})` and :math:`\vec{x}=(x_0,x_1,...,x_{n-1})` are strings of length :math:`n` with :math:`a_i, x_i \in \{0,1,2\}`. How can we minimize the number of calls to the function to discover :math:`\vec{a}?` In this case, the classical procedure to detect the value of :math:`\vec{a}` is the same as in the case of qubits: we will evaluate the output of the inputs :math:`[1,0,0],` :math:`[0,1,0]` and :math:`[0,0,1].` +# +# But how can we work with these kinds of functions in a simple way? To do this we must use a qutrit and its operators. +# By using this new unit of information and unlocking the third orthogonal state, we will have states represented with a vector of dimension :math:`3^n` and the operators will be :math:`3^n \times 3^n` matrices where :math:`n` is the number of qutrits. +# Specifically, we will use the :class:`~.pennylane.TShift` gate, which is equivalent to the :class:`~.pennylane.PauliX` gate for qutrits. It has the following property: +# +# .. math:: +# \text{TShift}|0\rangle = |1\rangle +# +# .. math:: +# \text{TShift}|1\rangle = |2\rangle +# +# .. math:: +# \text{TShift}|2\rangle = |0\rangle +# +# This means we can use this gate to initialize each of the states. +# Another gate that we will use for the oracle definition is the :class:`~.pennylane.TAdd` gate, which is the generalization of the :class:`~.pennylane.Toffoli` gate for qutrits. +# These generalizations simply adjust the addition operation to be performed in modulo 3 instead of modulo 2. +# So, with these ingredients, we are ready to go to the code. + +dev = qml.device("default.qutrit", wires=4, shots=1) + +def Uf(): + # The oracle in charge of encoding a hidden "a" value. + qml.TAdd(wires = [1,3]) + qml.TAdd(wires = [1,3]) + qml.TAdd(wires = [2,3]) + +@qml.qnode(dev) +def circuit0(): + + # Initialize x = [1,0,0] + qml.TShift(wires = 0) + + # We apply our oracle + Uf() + + # We measure the last qutrit + return qml.sample(wires = 3) + +@qml.qnode(dev) +def circuit1(): + + # Initialize x = [0,1,0] + qml.TShift(wires = 1) + + # We apply our oracle + Uf() + + # We measure the last qutrit + return qml.sample(wires = 3) + +@qml.qnode(dev) +def circuit2(): + + # Initialize x = [0,0,1] + qml.TShift(wires = 2) + + # We apply our oracle + Uf() + + # We measure the last qutrit + return qml.sample(wires = 3) + +# Run to obtain the three trits of a +a0 = circuit0() +a1 = circuit1() +a2 = circuit2() + + +print(f"The value of a is [{a0},{a1},{a2}]") + +############################################################################## +# +# The question is, can we perform the same procedure as we have done before to find :math:`\vec{a}` using a single shot? The Hadamard gate also generalizes to qutrits (also denoted as :class:`~.pennylane.THadamard`), so we could try to simply substitute it and see what happens! +# +# +# The definition of the Hadamard gate in this space is: +# +# .. math:: +# \text{THadamard}=\frac{-i}{\sqrt{3}}\begin{pmatrix} +# 1 & 1 & 1\\ +# 1 & w & w^2\\ +# 1 & w^2 & w +# \end{pmatrix}, +# +# where :math:`w = e^{\frac{2 \pi i}{3}}.` +# Let's go to the code and see how to run this in PennyLane. + + +@qml.qnode(dev) +def circuit(): + + # We initialize to |0001> + qml.TShift(wires = 3) + + # We run the THadamard + for i in range(4): + qml.THadamard(wires = i) + +# We run the oracle + Uf() + +# We run the THadamard again + for i in range(3): + qml.THadamard(wires = i) + + # We measure the first 3 qutrits + return qml.sample(wires = range(3)) + +a = circuit() + +print(f"The value of a is {a}") + +############################################################################## +# +# Awesome! The Bernstein–Vazirani algorithm generalizes perfectly to qutrits! Let's do the mathematical calculations again to check that it does indeed make sense. +# +# As before, the input of our circuit is :math:`|0001\rangle.` +# We will then use the Hadamard definition applied to qutrits: +# +# .. math:: +# H^{\otimes n}|\vec{x}\rangle = \frac{1}{\sqrt{3^n}}\sum_{\vec{z} \in \{0,1,2\}^n}w^{\vec{x}\cdot\vec{z}}|\vec{z}\rangle. +# +# In this case, we are disregarding the global phase of :math:`-i` for simplicity. +# Applying this to the state :math:`|0001\rangle,` we obtain +# +# .. math:: +# |\phi_1\rangle=H^{\otimes 4}|0001\rangle = H^{\otimes 3}|000\rangle\otimes H|1\rangle = \frac{1}{\sqrt{3^3}}\left(\sum_{z \in \{0,1,2\}^3}|\vec{z}\rangle\frac{|0\rangle+w|1\rangle+w^2|2\rangle}{\sqrt{3}}\right). +# +# After that, we apply the operator :math:`U_f` to obtain +# +# .. math:: +# |\phi_2\rangle= U_f |\phi_1\rangle = \frac{1}{\sqrt{3^3}}\left(\sum_{\vec{z} \in \{0,1,2\}^3}|\vec{z}\rangle\frac{|0 + \vec{a}\cdot\vec{z} \pmod 3 \rangle+w|1+ \vec{a}\cdot\vec{z} \pmod 3 \rangle+w^2|2+ \vec{a}\cdot\vec{z} \pmod 3 \rangle}{\sqrt{3}}\right). +# +# Depending on the value of :math:`f(\vec{x}),` as before, we obtain three possible states: +# +# - If :math:`\vec{a}\cdot\vec{z} = 0,` we have :math:`\frac{1}{\sqrt{3}}\left(|0\rangle+w|1\rangle+w^2|2\rangle\right).` +# - If :math:`\vec{a}\cdot\vec{z} = 1,` we have :math:`\frac{w^2}{\sqrt{3}}\left(|0\rangle+|1\rangle+w|2\rangle\right).` +# - If :math:`\vec{a}\cdot\vec{z} = 2,` we have :math:`\frac{w}{\sqrt{3}}\left(|0\rangle+w^2|1\rangle+|2\rangle\right).` +# +# Based on this, we can group the three states as :math:`\frac{1}{\sqrt{3}}w^{-\vec{a}\cdot\vec{z}}\left(|0\rangle+w|1\rangle+w^2|2\rangle\right).` +# +# After this, we can enter the coefficient in the :math:`|\vec{z}\rangle` term and, as before, disregard the last qutrit, since we are not going to use it again: +# +# .. math:: +# |\phi_2\rangle =\frac{1}{\sqrt{3^3}}\sum_{\vec{z} \in \{0,1,2\}^3}w^{-\vec{a}\cdot\vec{z}}|\vec{z}\rangle. +# +# Finally, we reapply the THadamard: +# +# .. math:: +# |\phi_3\rangle := H^{\otimes 3}|\phi_2\rangle = \frac{1}{3^3}\sum_{\vec{z} \in \{0,1,2\}^3}w^{-\vec{a}\cdot\vec{z}}\left(\sum_{\vec{y} \in \{0,1,2\}^3}w^{\vec{z}\cdot\vec{y}}|\vec{y}\rangle\right). +# +# Rearranging this expression, we obtain: +# +# .. math:: +# |\phi_3\rangle = \frac{1}{3^3}\sum_{\vec{y} \in \{0,1,2\}^3}\left(\sum_{\vec{z} \in \{0,1,2\}^3}w^{-\vec{a}\cdot\vec{z}+\vec{y}\cdot\vec{z}}\right)|\vec{y}\rangle. +# +# +# In the same way as before, it can be easily checked that :math:`\langle \vec{a}|\phi_3\rangle = 1` and therefore, when measuring, one shot will be enough to obtain the value of :math:`\vec{a}!` +# +# Conclusion +# ---------- +# +# In this demo, we have practised the use of basic qutrit gates such as TShift or THadamard by applying the Bernstein–Vazirani algorithm. In this case, the generalization has been straightforward and we have found that it makes mathematical sense, but we cannot always substitute qubit gates for qutrit gates as we have seen in the demo. To give an easy example of this, we know the property that :math:`X = HZH`, but it turns out that this property does not generalize! The general property is actually :math:`X = H^{\dagger}ZH.` In the case of qubits it holds that :math:`H = H^{\dagger},` but in other dimensions it does not. I invite you to continue practising with other types of algorithms. For instance, will the `Deutsch–Jozsa algorithm `__ generalize well? Take a pen and paper and check it out! +# +# References +# ---------- +# +# .. [#bv] +# +# Ethan Bernstein, Umesh Vazirani, "Quantum Complexity Theory". `SIAM Journal on Computing Vol. 26 (1997). +# `__ +# +# .. [#toffoli_qutrits] +# Alexey Galda, Michael Cubeddu, Naoki Kanazawa, Prineha Narang, Nathan Earnest-Noble, `"Implementing a Ternary Decomposition of the Toffoli Gate on Fixed-FrequencyTransmon Qutrits". +# `__ +# About the author +# ---------------- +# + diff --git a/demonstrations_v2/tutorial_resource_estimation/demo.py b/demonstrations_v2/tutorial_resource_estimation/demo.py index 0c033e4dee..8deb382e9c 100644 --- a/demonstrations_v2/tutorial_resource_estimation/demo.py +++ b/demonstrations_v2/tutorial_resource_estimation/demo.py @@ -1,321 +1,321 @@ -r""" - -Resource estimation for quantum chemistry -========================================= - -.. meta:: - :property="og:description": Learn how to estimate the number of qubits and gates needed to - implement quantum algorithms - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/resource_estimation.jpeg - -.. related:: - tutorial_quantum_chemistry Quantum chemistry with PennyLane - tutorial_vqe A brief overview of VQE - - -*Author: Soran Jahangiri — Posted: 21 November 2022.* - -Quantum algorithms such as -`quantum phase estimation `_ -(QPE) and the `variational quantum eigensolver `_ (VQE) -are widely studied in quantum chemistry as potential avenues to tackle problems that are intractable -for conventional computers. However, we currently do not have quantum computers or simulators -capable of implementing large-scale -versions of these algorithms. This makes it difficult to properly explore their accuracy and -efficiency for problem sizes where the actual advantage of quantum algorithms can potentially occur. -Despite these difficulties, it is still possible to perform **resource estimation** -to assess what we need to implement such quantum algorithms. - -In this demo, we describe how to use PennyLane's resource estimation functionality to estimate the total number of logical qubits and gates required to -implement the QPE algorithm for simulating molecular Hamiltonians represented in first and second -quantization. We focus on `non-Clifford gates `_, which -are the most expensive to implement in a fault-tolerant setting. We also explain how to estimate the -total number of measurements needed to compute expectation values using algorithms such as VQE. - -Quantum Phase Estimation ------------------------- -The QPE algorithm can be used to compute the phase associated with an eigenstate of a unitary -operator. For the purpose of quantum simulation, the unitary operator :math:`U` can be chosen to -share eigenvectors with a molecular Hamiltonian :math:`H,` for example by setting -:math:`U = e^{-iH}.` Estimating the phase of such a unitary then permits recovering the -corresponding eigenvalue of the Hamiltonian. A conceptual QPE circuit diagram is shown below. - -.. figure:: /_static/demonstration_assets/resource_estimation/qpe.png - :width: 60% - :align: center - - Circuit representing the quantum phase estimation algorithm. - -For most cases of interest, this algorithm requires more qubits and longer circuit depths than what -can be implemented on existing hardware. The PennyLane functionality in the -:mod:`qml.resource ` module allows us to estimate the number of logical qubits -and the number of non-Clifford gates that are needed to implement the algorithm. We can estimate -these resources by simply defining system specifications and a target error for estimation. Let's -see how! - -QPE cost for simulating molecules -********************************* -We study the double low-rank Hamiltonian factorization algorithm of [#vonburg2021]_ and use its cost -equations as provided in APPENDIX C of [#lee2021]_. -This algorithm requires the one- and two-electron -`integrals `_ -as input. These integrals can be obtained in different ways and here we use PennyLane to compute -them. We first need to define the atomic symbols and coordinates for the given molecule. Let's use -the water molecule at its equilibrium geometry with the -`6-31g basis set `_ as an example. -""" -import pennylane as qml -import numpy as np -import jax - -jax.config.update("jax_enable_x64", True) - -symbols = ['O', 'H', 'H'] -geometry = np.array([[0.00000000, 0.00000000, 0.28377432], - [0.00000000, 1.45278171, -1.00662237], - [0.00000000, -1.45278171, -1.00662237]]) - -############################################################################## -# Then we construct a molecule object and compute the one- and two-electron -# integrals in the molecular orbital basis. - -mol = qml.qchem.Molecule(symbols, geometry, basis_name='6-31g') -core, one, two = qml.qchem.electron_integrals(mol)() - -############################################################################## -# We now create an instance of the :class:`~.pennylane.resource.DoubleFactorization` class - -algo = qml.resource.DoubleFactorization(one, two) - -############################################################################## -# and obtain the estimated number of non-Clifford gates and logical qubits. - -print(f'Estimated gates : {algo.gates:.2e} \nEstimated qubits: {algo.qubits}') - -############################################################################## -# This estimation is for a target error that is set to the chemical accuracy, 0.0016 -# :math:`\text{Ha},` by default. We can change the target error to a larger value which leads to a -# smaller number of non-Clifford gates and logical qubits. - -chemical_accuracy = 0.0016 -error = chemical_accuracy * 10 -algo = qml.resource.DoubleFactorization(one, two, error=error) -print(f'Estimated gates : {algo.gates:.2e} \nEstimated qubits: {algo.qubits}') - -############################################################################## -# We can also estimate the number of non-Clifford gates with respect to the threshold error values -# for discarding the negligible factors in the factorized Hamiltonian [#vonburg2021]_ and plot the -# estimated numbers. - -threshold = [10**-n for n in range(10)] -n_gates = [] -n_qubits = [] - -for tol in threshold: - algo_ = qml.resource.DoubleFactorization(one, two, tol_factor=tol, tol_eigval=tol) - n_gates.append(algo_.gates) - n_qubits.append(algo_.qubits) - -import matplotlib.pyplot as plt - -fig, ax = plt.subplots(figsize=(5, 3)) -ax.plot(threshold, n_gates, ':o', markerfacecolor='none', color='teal') - -ax.set_ylabel('n gates') -ax.set_xlabel('threshold') -ax.set_xscale('log') -fig.tight_layout() - -############################################################################## -# QPE cost for simulating periodic materials -# ****************************************** -# For periodic materials, we estimate the cost of implementing the QPE algorithm of [#zini2023]_ -# using Hamiltonians represented in first quantization and in a plane wave basis. We first need to -# define the number of plane waves, the number of electrons, and the lattice vectors that construct -# the unit cell of the periodic material. Let's use dilithium iron silicate -# :math:`\text{Li}_2\text{FeSiO}_4` as an example taken from [#delgado2022]_. For this material, the -# unit cell contains 156 electrons and has dimensions :math:`9.49 \times 10.20 \times 11.83` in -# `atomic units `_. We also use :math:`10^5` plane waves. - -planewaves = 100000 -electrons = 156 -vectors = np.array([[9.49, 0.00, 0.00], - [0.00, 10.20, 0.00], - [0.00, 0.00, 11.83]]) - -############################################################################## -# We now create an instance of the :class:`~.pennylane.resource.FirstQuantization` class -algo = qml.resource.FirstQuantization(planewaves, electrons, vectors=vectors) - -############################################################################## -# and obtain the estimated number of non-Clifford gates and logical qubits. -print(f'Estimated gates : {algo.gates:.2e} \nEstimated qubits: {algo.qubits}') - -############################################################################## -# We can also plot the estimated numbers as a function of the number of plane waves for different -# target errors - -error = [0.1, 0.01, 0.001] # in atomic units -planewaves = [10 ** n for n in range(1, 10)] -n_gates = [] -n_qubits = [] - -for er in error: - n_gates_ = [] - n_qubits_ = [] - - for pw in planewaves: - algo_ = qml.resource.FirstQuantization(pw, electrons, vectors=vectors, error=er) - n_gates_.append(algo_.gates) - n_qubits_.append(algo_.qubits) - n_gates.append(n_gates_) - n_qubits.append(n_qubits_) - -fig, ax = plt.subplots(2, 1) - -for i in range(len(n_gates)): - ax[0].plot(planewaves, n_gates[i], ':o', markerfacecolor='none', label=error[i]) -ax[1].plot(planewaves, n_qubits[i], ':o', markerfacecolor='none', label=error[-1]) - -ax[0].set_ylabel('n gates') -ax[1].set_ylabel('n qubits') - -for i in [0, 1]: - ax[i].set_xlabel('n planewaves') - ax[i].tick_params(axis='x') - ax[0].set_yscale('log') - ax[i].set_xscale('log') - ax[i].legend(title='error [Ha]') - -fig.tight_layout() - -############################################################################## -# The algorithm uses a decomposition of the Hamiltonian as a linear combination of unitaries, -# -# .. math:: H=\sum_{i} c_i U_i. -# -# The parameter :math:`\lambda=\sum_i c_i,` which can be interpreted as the 1-norm of the -# Hamiltonian, plays an important role in determining the cost of implementing the QPE -# algorithm [#delgado2022]_. In PennyLane, the 1-norm of the Hamiltonian can be obtained with - -print(f'1-norm of the Hamiltonian: {algo.lamb}') - -############################################################################## -# PennyLane allows you to get more detailed information about the cost of the algorithms as -# explained in the documentation of :class:`~.pennylane.resource.FirstQuantization` -# and :class:`~.pennylane.resource.DoubleFactorization` classes. -# -# Variational quantum eigensolver -# ------------------------------------------ -# In variational quantum algorithms such as VQE, the expectation value of an observable is -# typically computed by decomposing the observable into a linear combination of Pauli words, -# which are tensor products of Pauli and Identity operators. The expectation values are calculated -# through linearity by measuring the expectation value for each of these terms and combining the -# results. The number of qubits required for the measurement is trivially determined by -# the number of qubits the observable acts on. The number of gates required to implement the -# variational algorithm is determined by a circuit ansatz that is also known a priori. However, -# estimating the number of circuit evaluations, i.e. the number of shots, required to achieve a -# certain error in computing the expectation value is not as straightforward. Let's now use -# PennyLane to estimate the number of shots needed to compute the expectation value of the water -# Hamiltonian. -# -# First, we construct the molecular Hamiltonian. - -molecule = qml.qchem.Molecule(symbols, geometry) -H = qml.qchem.molecular_hamiltonian(molecule)[0] -H_coeffs, H_ops = H.terms() - -############################################################################## -# The number of measurements needed to compute :math:`\left \langle H \right \rangle` can be -# obtained with the :func:`~.pennylane.resource.estimate_shots` function, which requires the -# Hamiltonian coefficients as input. The number of measurements required to compute -# :math:`\left \langle H \right \rangle` with a target error set to the chemical accuracy, 0.0016 -# :math:`\text{Ha},` is obtained as follows. - -m = qml.resource.estimate_shots(H_coeffs) -print(f'Shots : {m:.2e}') - -############################################################################## -# This number corresponds to the measurement process where each term in the Hamiltonian is measured -# independently. The number can be reduced by using -# :func:`~.pennylane.pauli.group_observables()`, which partitions the Pauli words into -# groups of commuting terms that can be measured simultaneously. - -ops, coeffs = qml.pauli.group_observables(H_ops, H_coeffs) -coeffs = [np.array(c) for c in coeffs] # cast as numpy array - -m = qml.resource.estimate_shots(coeffs) -print(f'Shots : {m:.2e}') - -############################################################################## -# It is also interesting to illustrate how the number of shots depends on the target error. - -error = np.array([0.02, 0.015, 0.01, 0.005, 0.001]) -m = [qml.resource.estimate_shots(H_coeffs, error=er) for er in error] - -e_ = np.linspace(error[0], error[-1], num=50) -m_ = 1.4e4 / np.linspace(error[0], error[-1], num=50)**2 - -fig, ax = plt.subplots(figsize=(5, 3)) -ax.plot(error, m, 'o', markerfacecolor='none', color='teal', label='estimated') -ax.plot(e_, m_, ':', markerfacecolor='none', color='olive', label='$ 1.4e4 * 1/\epsilon^2 $') - -ax.set_ylabel('shots') -ax.set_xlabel('error [Ha]') -ax.set_yscale('log') -ax.tick_params(axis='x', labelrotation = 90) -ax.legend() -fig.tight_layout() - -############################################################################## -# We have added a line showing the dependency of the shots to the error, as -# :math:`\text{shots} = 1.4\text{e}4 \times 1/\epsilon^2,` for comparison. Can you draw any -# interesting information form the plot? -# -# Conclusions -# ----------- -# This tutorial shows how to use the resource estimation functionality in PennyLane to compute the -# total number of non-Clifford gates and logical qubits required to simulate a Hamiltonian with -# quantum phase estimation algorithms. The estimation can be performed for second-quantized -# molecular Hamiltonians obtained with a double low-rank factorization algorithm, -# and first-quantized Hamiltonians of periodic materials in the plane wave basis. We also discussed -# the estimation of the total number of shots required to obtain the expectation value of an -# observable using the variational quantum eigensolver algorithm. The functionality allows one to -# obtain interesting results about the cost of implementing important quantum algorithms. For -# instance, we estimated the costs with respect to factors such as the target error in obtaining -# energies and the number of basis functions used to simulate a system. Can you think of other -# interesting information that can be obtained using this PennyLane functionality? -# -# References -# ---------- -# -# .. [#vonburg2021] -# -# Vera von Burg, Guang Hao Low, Thomas Haner, Damian S. Steiger, *et al.*, -# "Quantum computing enhanced computational catalysis". -# `Phys. Rev. Research 3, 033055 (2021) -# `__ -# -# .. [#lee2021] -# -# Joonho Lee, Dominic W. Berry, Craig Gidney, William J. Huggins, *et al.*, -# "Even More Efficient Quantum Computations of Chemistry Through Tensor Hypercontraction". -# `PRX Quantum 2, 030305 (2021) -# `__ -# -# .. [#zini2023] -# -# Modjtaba Shokrian Zini, Alain Delgado, Roberto dos Reis, Pablo A. M. Casares, -# Jonathan E. Mueller, Arne-Christian Voigt, Juan Miguel Arrazola, -# "Quantum simulation of battery materials using ionic pseudopotentials". -# `Quantum 7, 1049 (2023) `__ -# -# .. [#delgado2022] -# -# Alain Delgado, Pablo A. M. Casares, Roberto dos Reis, Modjtaba Shokrian Zini, *et al.*, -# "Simulating key properties of lithium-ion batteries with a fault-tolerant quantum computer". -# `Phys. Rev. A 106, 032428 (2022) -# `__ -# About the author -# ---------------- -# +r""" + +Resource estimation for quantum chemistry +========================================= + +.. meta:: + :property="og:description": Learn how to estimate the number of qubits and gates needed to + implement quantum algorithms + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/resource_estimation.jpeg + +.. related:: + tutorial_quantum_chemistry Quantum chemistry with PennyLane + tutorial_vqe A brief overview of VQE + + +*Author: Soran Jahangiri — Posted: 21 November 2022.* + +Quantum algorithms such as +`quantum phase estimation `_ +(QPE) and the `variational quantum eigensolver `_ (VQE) +are widely studied in quantum chemistry as potential avenues to tackle problems that are intractable +for conventional computers. However, we currently do not have quantum computers or simulators +capable of implementing large-scale +versions of these algorithms. This makes it difficult to properly explore their accuracy and +efficiency for problem sizes where the actual advantage of quantum algorithms can potentially occur. +Despite these difficulties, it is still possible to perform **resource estimation** +to assess what we need to implement such quantum algorithms. + +In this demo, we describe how to use PennyLane's resource estimation functionality to estimate the total number of logical qubits and gates required to +implement the QPE algorithm for simulating molecular Hamiltonians represented in first and second +quantization. We focus on `non-Clifford gates `_, which +are the most expensive to implement in a fault-tolerant setting. We also explain how to estimate the +total number of measurements needed to compute expectation values using algorithms such as VQE. + +Quantum Phase Estimation +------------------------ +The QPE algorithm can be used to compute the phase associated with an eigenstate of a unitary +operator. For the purpose of quantum simulation, the unitary operator :math:`U` can be chosen to +share eigenvectors with a molecular Hamiltonian :math:`H,` for example by setting +:math:`U = e^{-iH}.` Estimating the phase of such a unitary then permits recovering the +corresponding eigenvalue of the Hamiltonian. A conceptual QPE circuit diagram is shown below. + +.. figure:: /_static/demonstration_assets/resource_estimation/qpe.png + :width: 60% + :align: center + + Circuit representing the quantum phase estimation algorithm. + +For most cases of interest, this algorithm requires more qubits and longer circuit depths than what +can be implemented on existing hardware. The PennyLane functionality in the +:mod:`qml.resource ` module allows us to estimate the number of logical qubits +and the number of non-Clifford gates that are needed to implement the algorithm. We can estimate +these resources by simply defining system specifications and a target error for estimation. Let's +see how! + +QPE cost for simulating molecules +********************************* +We study the double low-rank Hamiltonian factorization algorithm of [#vonburg2021]_ and use its cost +equations as provided in APPENDIX C of [#lee2021]_. +This algorithm requires the one- and two-electron +`integrals `_ +as input. These integrals can be obtained in different ways and here we use PennyLane to compute +them. We first need to define the atomic symbols and coordinates for the given molecule. Let's use +the water molecule at its equilibrium geometry with the +`6-31g basis set `_ as an example. +""" +import pennylane as qml +import numpy as np +import jax + +jax.config.update("jax_enable_x64", True) + +symbols = ['O', 'H', 'H'] +geometry = np.array([[0.00000000, 0.00000000, 0.28377432], + [0.00000000, 1.45278171, -1.00662237], + [0.00000000, -1.45278171, -1.00662237]]) + +############################################################################## +# Then we construct a molecule object and compute the one- and two-electron +# integrals in the molecular orbital basis. + +mol = qml.qchem.Molecule(symbols, geometry, basis_name='6-31g') +core, one, two = qml.qchem.electron_integrals(mol)() + +############################################################################## +# We now create an instance of the :class:`~.pennylane.resource.DoubleFactorization` class + +algo = qml.resource.DoubleFactorization(one, two) + +############################################################################## +# and obtain the estimated number of non-Clifford gates and logical qubits. + +print(f'Estimated gates : {algo.gates:.2e} \nEstimated qubits: {algo.qubits}') + +############################################################################## +# This estimation is for a target error that is set to the chemical accuracy, 0.0016 +# :math:`\text{Ha},` by default. We can change the target error to a larger value which leads to a +# smaller number of non-Clifford gates and logical qubits. + +chemical_accuracy = 0.0016 +error = chemical_accuracy * 10 +algo = qml.resource.DoubleFactorization(one, two, error=error) +print(f'Estimated gates : {algo.gates:.2e} \nEstimated qubits: {algo.qubits}') + +############################################################################## +# We can also estimate the number of non-Clifford gates with respect to the threshold error values +# for discarding the negligible factors in the factorized Hamiltonian [#vonburg2021]_ and plot the +# estimated numbers. + +threshold = [10**-n for n in range(10)] +n_gates = [] +n_qubits = [] + +for tol in threshold: + algo_ = qml.resource.DoubleFactorization(one, two, tol_factor=tol, tol_eigval=tol) + n_gates.append(algo_.gates) + n_qubits.append(algo_.qubits) + +import matplotlib.pyplot as plt + +fig, ax = plt.subplots(figsize=(5, 3)) +ax.plot(threshold, n_gates, ':o', markerfacecolor='none', color='teal') + +ax.set_ylabel('n gates') +ax.set_xlabel('threshold') +ax.set_xscale('log') +fig.tight_layout() + +############################################################################## +# QPE cost for simulating periodic materials +# ****************************************** +# For periodic materials, we estimate the cost of implementing the QPE algorithm of [#zini2023]_ +# using Hamiltonians represented in first quantization and in a plane wave basis. We first need to +# define the number of plane waves, the number of electrons, and the lattice vectors that construct +# the unit cell of the periodic material. Let's use dilithium iron silicate +# :math:`\text{Li}_2\text{FeSiO}_4` as an example taken from [#delgado2022]_. For this material, the +# unit cell contains 156 electrons and has dimensions :math:`9.49 \times 10.20 \times 11.83` in +# `atomic units `_. We also use :math:`10^5` plane waves. + +planewaves = 100000 +electrons = 156 +vectors = np.array([[9.49, 0.00, 0.00], + [0.00, 10.20, 0.00], + [0.00, 0.00, 11.83]]) + +############################################################################## +# We now create an instance of the :class:`~.pennylane.resource.FirstQuantization` class +algo = qml.resource.FirstQuantization(planewaves, electrons, vectors=vectors) + +############################################################################## +# and obtain the estimated number of non-Clifford gates and logical qubits. +print(f'Estimated gates : {algo.gates:.2e} \nEstimated qubits: {algo.qubits}') + +############################################################################## +# We can also plot the estimated numbers as a function of the number of plane waves for different +# target errors + +error = [0.1, 0.01, 0.001] # in atomic units +planewaves = [10 ** n for n in range(1, 10)] +n_gates = [] +n_qubits = [] + +for er in error: + n_gates_ = [] + n_qubits_ = [] + + for pw in planewaves: + algo_ = qml.resource.FirstQuantization(pw, electrons, vectors=vectors, error=er) + n_gates_.append(algo_.gates) + n_qubits_.append(algo_.qubits) + n_gates.append(n_gates_) + n_qubits.append(n_qubits_) + +fig, ax = plt.subplots(2, 1) + +for i in range(len(n_gates)): + ax[0].plot(planewaves, n_gates[i], ':o', markerfacecolor='none', label=error[i]) +ax[1].plot(planewaves, n_qubits[i], ':o', markerfacecolor='none', label=error[-1]) + +ax[0].set_ylabel('n gates') +ax[1].set_ylabel('n qubits') + +for i in [0, 1]: + ax[i].set_xlabel('n planewaves') + ax[i].tick_params(axis='x') + ax[0].set_yscale('log') + ax[i].set_xscale('log') + ax[i].legend(title='error [Ha]') + +fig.tight_layout() + +############################################################################## +# The algorithm uses a decomposition of the Hamiltonian as a linear combination of unitaries, +# +# .. math:: H=\sum_{i} c_i U_i. +# +# The parameter :math:`\lambda=\sum_i c_i,` which can be interpreted as the 1-norm of the +# Hamiltonian, plays an important role in determining the cost of implementing the QPE +# algorithm [#delgado2022]_. In PennyLane, the 1-norm of the Hamiltonian can be obtained with + +print(f'1-norm of the Hamiltonian: {algo.lamb}') + +############################################################################## +# PennyLane allows you to get more detailed information about the cost of the algorithms as +# explained in the documentation of :class:`~.pennylane.resource.FirstQuantization` +# and :class:`~.pennylane.resource.DoubleFactorization` classes. +# +# Variational quantum eigensolver +# ------------------------------------------ +# In variational quantum algorithms such as VQE, the expectation value of an observable is +# typically computed by decomposing the observable into a linear combination of Pauli words, +# which are tensor products of Pauli and Identity operators. The expectation values are calculated +# through linearity by measuring the expectation value for each of these terms and combining the +# results. The number of qubits required for the measurement is trivially determined by +# the number of qubits the observable acts on. The number of gates required to implement the +# variational algorithm is determined by a circuit ansatz that is also known a priori. However, +# estimating the number of circuit evaluations, i.e. the number of shots, required to achieve a +# certain error in computing the expectation value is not as straightforward. Let's now use +# PennyLane to estimate the number of shots needed to compute the expectation value of the water +# Hamiltonian. +# +# First, we construct the molecular Hamiltonian. + +molecule = qml.qchem.Molecule(symbols, geometry) +H = qml.qchem.molecular_hamiltonian(molecule)[0] +H_coeffs, H_ops = H.terms() + +############################################################################## +# The number of measurements needed to compute :math:`\left \langle H \right \rangle` can be +# obtained with the :func:`~.pennylane.resource.estimate_shots` function, which requires the +# Hamiltonian coefficients as input. The number of measurements required to compute +# :math:`\left \langle H \right \rangle` with a target error set to the chemical accuracy, 0.0016 +# :math:`\text{Ha},` is obtained as follows. + +m = qml.resource.estimate_shots(H_coeffs) +print(f'Shots : {m:.2e}') + +############################################################################## +# This number corresponds to the measurement process where each term in the Hamiltonian is measured +# independently. The number can be reduced by using +# :func:`~.pennylane.pauli.group_observables()`, which partitions the Pauli words into +# groups of commuting terms that can be measured simultaneously. + +ops, coeffs = qml.pauli.group_observables(H_ops, H_coeffs) +coeffs = [np.array(c) for c in coeffs] # cast as numpy array + +m = qml.resource.estimate_shots(coeffs) +print(f'Shots : {m:.2e}') + +############################################################################## +# It is also interesting to illustrate how the number of shots depends on the target error. + +error = np.array([0.02, 0.015, 0.01, 0.005, 0.001]) +m = [qml.resource.estimate_shots(H_coeffs, error=er) for er in error] + +e_ = np.linspace(error[0], error[-1], num=50) +m_ = 1.4e4 / np.linspace(error[0], error[-1], num=50)**2 + +fig, ax = plt.subplots(figsize=(5, 3)) +ax.plot(error, m, 'o', markerfacecolor='none', color='teal', label='estimated') +ax.plot(e_, m_, ':', markerfacecolor='none', color='olive', label='$ 1.4e4 * 1/\epsilon^2 $') + +ax.set_ylabel('shots') +ax.set_xlabel('error [Ha]') +ax.set_yscale('log') +ax.tick_params(axis='x', labelrotation = 90) +ax.legend() +fig.tight_layout() + +############################################################################## +# We have added a line showing the dependency of the shots to the error, as +# :math:`\text{shots} = 1.4\text{e}4 \times 1/\epsilon^2,` for comparison. Can you draw any +# interesting information form the plot? +# +# Conclusions +# ----------- +# This tutorial shows how to use the resource estimation functionality in PennyLane to compute the +# total number of non-Clifford gates and logical qubits required to simulate a Hamiltonian with +# quantum phase estimation algorithms. The estimation can be performed for second-quantized +# molecular Hamiltonians obtained with a double low-rank factorization algorithm, +# and first-quantized Hamiltonians of periodic materials in the plane wave basis. We also discussed +# the estimation of the total number of shots required to obtain the expectation value of an +# observable using the variational quantum eigensolver algorithm. The functionality allows one to +# obtain interesting results about the cost of implementing important quantum algorithms. For +# instance, we estimated the costs with respect to factors such as the target error in obtaining +# energies and the number of basis functions used to simulate a system. Can you think of other +# interesting information that can be obtained using this PennyLane functionality? +# +# References +# ---------- +# +# .. [#vonburg2021] +# +# Vera von Burg, Guang Hao Low, Thomas Haner, Damian S. Steiger, *et al.*, +# "Quantum computing enhanced computational catalysis". +# `Phys. Rev. Research 3, 033055 (2021) +# `__ +# +# .. [#lee2021] +# +# Joonho Lee, Dominic W. Berry, Craig Gidney, William J. Huggins, *et al.*, +# "Even More Efficient Quantum Computations of Chemistry Through Tensor Hypercontraction". +# `PRX Quantum 2, 030305 (2021) +# `__ +# +# .. [#zini2023] +# +# Modjtaba Shokrian Zini, Alain Delgado, Roberto dos Reis, Pablo A. M. Casares, +# Jonathan E. Mueller, Arne-Christian Voigt, Juan Miguel Arrazola, +# "Quantum simulation of battery materials using ionic pseudopotentials". +# `Quantum 7, 1049 (2023) `__ +# +# .. [#delgado2022] +# +# Alain Delgado, Pablo A. M. Casares, Roberto dos Reis, Modjtaba Shokrian Zini, *et al.*, +# "Simulating key properties of lithium-ion batteries with a fault-tolerant quantum computer". +# `Phys. Rev. A 106, 032428 (2022) +# `__ +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_rosalin/demo.py b/demonstrations_v2/tutorial_rosalin/demo.py index aed37d6224..08b548c590 100644 --- a/demonstrations_v2/tutorial_rosalin/demo.py +++ b/demonstrations_v2/tutorial_rosalin/demo.py @@ -1,666 +1,666 @@ -r""" -Frugal shot optimization with Rosalin -===================================== - -.. meta:: - :property="og:description": The Rosalin optimizer uses a measurement-frugal optimization strategy to minimize the - number of times a quantum computer is accessed. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/sphx_glr_tutorial_rosalin_002.png - -.. related:: - - tutorial_vqe A brief overview of VQE - tutorial_quantum_natural_gradient Quantum natural gradient - tutorial_doubly_stochastic Doubly stochastic gradient descent - tutorial_rotoselect Quantum circuit structure learning - -*Author: Josh Izaac — Posted: 19 May 2020. Last updated: 30 January 2023.* - -In this tutorial we investigate and implement the Rosalin (Random Operator Sampling for -Adaptive Learning with Individual Number of shots) from -Arrasmith et al. [#arrasmith2020]_. In this paper, a strategy -is introduced for reducing the number of shots required when optimizing variational quantum -algorithms, by both: - -* Frugally adapting the number of shots used per parameter update, and -* Performing a weighted sampling of operators from the cost Hamiltonian. - -.. note:: - - The Rosalin optimizer is available in PennyLane via the - :class:`~.pennylane.ShotAdaptiveOptimizer`. - -Background ----------- - -While a large number of papers in variational quantum algorithms focus on the -choice of circuit ansatz, cost function, gradient computation, or initialization method, -the optimization strategy—an important component affecting both convergence time and -quantum resource dependence—is not as frequently considered. Instead, common -'out-of-the-box' classical optimization techniques, such as gradient-free -methods (COBLYA, Nelder-Mead), gradient-descent, and Hessian-free methods (L-BFGS) tend to be used. - -However, for variational algorithms such as :doc:`VQE `, which involve evaluating -a large number of non-commuting operators in the cost function, decreasing the number of -quantum evaluations required for convergence, while still minimizing statistical noise, can -be a delicate balance. - -Recent work has highlighted that 'quantum-aware' optimization techniques -can lead to marked improvements when training variational quantum algorithms: - -* :doc:`/demos/tutorial_quantum_natural_gradient` descent by Stokes et al. [#stokes2019]_, which - takes into account the quantum geometry during the gradient-descent update step. - -* The work of Sweke et al. [#sweke2019]_, which shows - that quantum gradient descent with a finite number of shots is equivalent to - `stochastic gradient descent `_, - and has guaranteed convergence. Furthermore, combining a finite number of shots with - weighted sampling of the cost function terms leads to :doc:`/demos/tutorial_doubly_stochastic`. - -* The iCANS (individual Coupled Adaptive Number of Shots) optimization technique by - Jonas Kuebler et al. [#kubler2020]_ adapts the number - of shots measurements during training, by maximizing the expected gain per shot. - -In this latest result by Arrasmith et al. [#arrasmith2020]_, the -idea of doubly stochastic gradient descent has been used to extend the iCANS optimizer, -resulting in faster convergence. - -Over the course of this tutorial, we will explore their results; beginning first with a -demonstration of *weighted random sampling* of the cost Hamiltonian operators, before -combining this with the shot-frugal iCANS optimizer to perform doubly stochastic -Rosalin optimization. - -Weighted random sampling ------------------------- - -Consider a Hamiltonian :math:`H` expanded as a weighted sum of operators :math:`h_i` that can -be directly measured: - -.. math:: H = \sum_{i=1}^N c_i h_i. - -Due to the linearity of expectation values, the expectation value of this Hamiltonian -can be expressed as the weighted sum of each individual term: - -.. math:: \langle H\rangle = \sum_{i=1}^N c_i \langle h_i\rangle. - -In the :doc:`doubly stochastic gradient descent demonstration `, -we estimated this expectation value by **uniformly sampling** a subset of the terms -at each optimization step, and evaluating each term by using the same finite number of shots -:math:`N.` - -However, what happens if we use a weighted approach to determine how to distribute -our samples across the terms of the Hamiltonian? In **weighted random sampling** (WRS), -the number of shots used to determine the expectation value :math:`\langle h_i\rangle` -is a discrete random variable distributed according to a -`multinomial distribution `__, - -.. math:: S \sim \text{Multinomial}(p_i), - -with event probabilities - -.. math:: p_i = \frac{|c_i|}{\sum_i |c_i|}. - -That is, the number of shots assigned to the measurement of the expectation value of the -:math:`i\text{th}` term of the Hamiltonian is drawn from a probability distribution -*proportional to the magnitude of its coefficient* :math:`c_i.` - -To see this strategy in action, consider the Hamiltonian - -.. math:: H = 2I\otimes X + 4 I\otimes Z - X\otimes X + 5Y\otimes Y + 2 Z\otimes X. - -We can solve for the ground state energy using the variational quantum eigensolver (VQE) algorithm. - -First, let's import NumPy and PennyLane, and define our Hamiltonian. -""" -import pennylane as qml -from pennylane import numpy as np - -# set the random seed -np.random.seed(4) - -coeffs = [2, 4, -1, 5, 2] - -obs = [ - qml.PauliX(1), - qml.PauliZ(1), - qml.PauliX(0) @ qml.PauliX(1), - qml.PauliY(0) @ qml.PauliY(1), - qml.PauliZ(0) @ qml.PauliZ(1) -] - - -############################################################################## -# We can now create our quantum device (let's use the ``default.qubit`` simulator). - -num_layers = 2 -num_wires = 2 - -# create a device that estimates expectation values using a finite number of shots -non_analytic_dev = qml.device("default.qubit", wires=num_wires, shots=100, seed=432423) - -# create a device that calculates exact expectation values -analytic_dev = qml.device("default.qubit", wires=num_wires, shots=None) - -############################################################################## -# Now, let's set the total number of shots, and determine the probability -# for sampling each Hamiltonian term. - -total_shots = 8000 -prob_shots = np.abs(coeffs) / np.sum(np.abs(coeffs)) -print(prob_shots) - -############################################################################## -# We can now use SciPy to create our multinomial distributed random variable -# :math:`S,` using the number of trials (total shot number) and probability values: - -from scipy.stats import multinomial - -si = multinomial(n=total_shots, p=prob_shots) - -############################################################################## -# Sampling from this distribution will provide the number of shots used to -# sample each term in the Hamiltonian: - -samples = si.rvs()[0] -print(samples) -print(sum(samples)) - -############################################################################## -# As expected, if we sum the sampled shots per term, we recover the total number of shots. -# -# Let's now create our cost function. Recall that the cost function must do the -# following: -# -# 1. It must sample from the multinomial distribution we created above, -# to determine the number of shots :math:`s_i` to use to estimate the expectation -# value of the ith Hamiltonian term. -# -# 2. It then must estimate the expectation value :math:`\langle h_i\rangle` -# by creating the required QNode. For our ansatz, we'll use the -# :class:`~.pennylane.templates.layers.StronglyEntanglingLayers`. -# -# 3. And, last but not least, estimate the expectation value -# :math:`\langle H\rangle = \sum_i c_i\langle h_i\rangle.` -# - -from pennylane.templates.layers import StronglyEntanglingLayers - - -@qml.qnode(non_analytic_dev, diff_method="parameter-shift", interface="autograd") -def qnode(weights, observable): - StronglyEntanglingLayers(weights, wires=non_analytic_dev.wires) - return qml.expval(observable) - -def cost(params): - # sample from the multinomial distribution - shots_per_term = si.rvs()[0] - - result = 0 - - for o, c, s in zip(obs, coeffs, shots_per_term): - # evaluate the QNode corresponding to - # the Hamiltonian term, and add it on to our running sum - result += c * qnode(params, o, shots=int(s)) - - return result - - -############################################################################## -# Evaluating our cost function with some initial parameters, we can test out -# that our cost function evaluates correctly. - -param_shape = StronglyEntanglingLayers.shape(n_layers=num_layers, n_wires=num_wires) -init_params = np.random.uniform(low=0.0, high=2*np.pi, size=param_shape, requires_grad=True) -print(cost(init_params)) - - -############################################################################## -# Performing the optimization, with the number of shots randomly -# determined at each optimization step: - -opt = qml.AdamOptimizer(0.05) -params = init_params - -cost_wrs = [] -shots_wrs = [] - -for i in range(100): - params, _cost = opt.step_and_cost(cost, params) - cost_wrs.append(_cost) - shots_wrs.append(total_shots*i) - print("Step {}: cost = {} shots used = {}".format(i, cost_wrs[-1], shots_wrs[-1])) - -############################################################################## -# Let's compare this against an optimization not using weighted random sampling. -# Here, we will split the 8000 total shots evenly across all Hamiltonian terms, -# also known as *uniform deterministic sampling*. - -@qml.qnode(non_analytic_dev, diff_method="parameter-shift", interface="autograd") -def qnode(weights, obs): - StronglyEntanglingLayers(weights, wires=non_analytic_dev.wires) - return qml.expval(obs) - -def cost(params): - shots_per_term = int(total_shots / len(coeffs)) - - result = 0 - - for o, c in zip(obs, coeffs): - - # evaluate the QNode corresponding to - # the Hamiltonian term, and add it on to our running sum - result += c * qnode(params, o, shots=shots_per_term) - - return result - -opt = qml.AdamOptimizer(0.05) -params = init_params - -cost_adam = [] -shots_adam = [] - -for i in range(100): - params, _cost = opt.step_and_cost(cost, params) - cost_adam.append(_cost) - shots_adam.append(total_shots*i) - print("Step {}: cost = {} shots used = {}".format(i, cost_adam[-1], shots_adam[-1])) - -############################################################################## -# Comparing these two techniques: - -from matplotlib import pyplot as plt - -plt.style.use("seaborn-v0_8") -plt.plot(shots_wrs, cost_wrs, "b", label="Adam WRS") -plt.plot(shots_adam, cost_adam, "g", label="Adam") - -plt.ylabel("Cost function value") -plt.xlabel("Number of shots") -plt.legend() -plt.show() - -############################################################################## -# We can see that weighted random sampling performs just as well as the uniform -# deterministic sampling. However, weighted random sampling begins to show a -# non-negligible improvement over deterministic sampling for large Hamiltonians -# with highly non-uniform coefficients. For example, see Fig (3) and (4) of -# Arrasmith et al. [#arrasmith2020]_, comparing weighted random sampling VQE optimization -# for both :math:`\text{H}_2` and :math:`\text{LiH}` molecules. -# -# .. note:: -# -# While not covered here, another approach that could be taken is -# *weighted deterministic sampling*. Here, the number of shots is distributed -# across terms as per -# -# .. math:: s_i = \left\lfloor N \frac{|c_i|}{\sum_i |c_i|}\right\rfloor, -# -# where :math:`N` is the total number of shots. -# - -############################################################################## -# Rosalin: Frugal shot optimization -# --------------------------------- -# -# We can see above that both methods optimize fairly well; weighted random -# sampling converges just as well as evenly distributing the shots across -# all Hamiltonian terms. However, deterministic shot distribution approaches -# will always have a minimum shot value required per expectation value, as below -# this threshold they become biased estimators. This is not the case with random -# sampling; as we saw in the -# :doc:`doubly stochastic gradient descent demonstration `, -# the introduction of randomness allows for as little -# as a single shot per expectation term, while still remaining an unbiased estimator. -# -# Using this insight, Arrasmith et al. [#arrasmith2020]_ modified the iCANS frugal -# shot-optimization technique [#kubler2020]_ to include weighted random sampling, making it -# 'doubly stochastic'. -# -# iCANS optimizer -# ~~~~~~~~~~~~~~~ -# -# Two variants of the iCANS optimizer were introduced in Kübler et al., iCANS1 and iCANS2. -# The iCANS1 optimizer, on which Rosalin is based, frugally distributes a shot budget -# across the partial derivatives of each parameter, which are computed using the -# :doc:`parameter-shift rule `. It works roughly as follows: -# -# 1. The initial step of the optimizer is performed with some specified minimum -# number of shots, :math:`s_{min},` for all partial derivatives. -# -# 2. The parameter-shift rule is then used to estimate the gradient :math:`g_i` -# for each parameter :math:`\theta_i,` parameters, as well as the *variances* -# :math:`v_i` of the estimated gradients. -# -# 3. Gradient descent is performed for each parameter :math:`\theta_i,` using -# the pre-defined learning rate :math:`\alpha` and the gradient information :math:`g_i:` -# -# .. math:: \theta_i = \theta_i - \alpha g_i. -# -# 4. The improvement in the cost function per shot, for a specific parameter value, -# is then calculated via -# -# .. math:: -# -# \gamma_i = \frac{1}{s_i} \left[ \left(\alpha - \frac{1}{2} L\alpha^2\right) -# g_i^2 - \frac{L\alpha^2}{2s_i}v_i \right], -# -# where: -# -# * :math:`L \leq \sum_i|c_i|` is the bound on the `Lipschitz constant -# `__ of the variational quantum algorithm objective function, -# -# * :math:`c_i` are the coefficients of the Hamiltonian, and -# -# * :math:`\alpha` is the learning rate, and *must* be bound such that :math:`\alpha < 2/L` -# for the above expression to hold. -# -# 5. Finally, the new values of :math:`s_i` (shots for partial derivative of parameter -# :math:`\theta_i`) is given by: -# -# .. math:: -# -# s_i = \frac{2L\alpha}{2-L\alpha}\left(\frac{v_i}{g_i^2}\right)\propto -# \frac{v_i}{g_i^2}. -# -# In addition to the above, to counteract the presence of noise in the system, a -# running average of :math:`g_i` and :math:`s_i` (:math:`\chi_i` and :math:`\xi_i` respectively) -# are used when computing :math:`\gamma_i` and :math:`s_i.` -# -# .. note:: -# -# In classical machine learning, the Lipschitz constant of the cost function is generally -# unknown. However, for a variational quantum algorithm with cost of the form -# :math:`f(x) = \langle \psi(x) | \hat{H} |\psi(x)\rangle,` -# an upper bound on the Lipschitz constant is given by :math:`L < \sum_i|c_i|,` -# where :math:`c_i` are the coefficients of :math:`\hat{H}` when decomposed -# into a linear combination of Pauli-operator tensor products. -# -# Rosalin implementation -# ~~~~~~~~~~~~~~~~~~~~~~ -# -# Let's now modify iCANS above to incorporate weighted random sampling of Hamiltonian -# terms — the Rosalin frugal shot optimizer. -# -# Rosalin takes several hyper-parameters: -# -# * ``min_shots``: the minimum number of shots used to estimate the expectations -# of each term in the Hamiltonian. Note that this must be larger than 2 for the variance -# of the gradients to be computed. -# -# * ``mu``: The running average constant :math:`\mu\in[0, 1].` Used to control how quickly the -# number of shots recommended for each gradient component changes. -# -# * ``b``: Regularization bias. The bias should be kept small, but non-zero. -# -# * ``lr``: The learning rate. Recall from above that the learning rate *must* be such -# that :math:`\alpha < 2/L = 2/\sum_i|c_i|.` -# -# Since the Rosalin optimizer has a state that must be preserved between optimization steps, -# let's use a class to create our optimizer. -# - -class Rosalin: - - def __init__(self, obs, coeffs, min_shots, mu=0.99, b=1e-6, lr=0.07): - self.obs = obs - self.coeffs = coeffs - - self.lipschitz = np.sum(np.abs(coeffs)) - - if lr > 2 / self.lipschitz: - raise ValueError("The learning rate must be less than ", 2 / self.lipschitz) - - # hyperparameters - self.min_shots = min_shots - self.mu = mu # running average constant - self.b = b # regularization bias - self.lr = lr # learning rate - - # keep track of the total number of shots used - self.shots_used = 0 - # total number of iterations - self.k = 0 - # Number of shots per parameter - self.s = np.zeros_like(params, dtype=np.float64) + min_shots - - # Running average of the parameter gradients - self.chi = None - # Running average of the variance of the parameter gradients - self.xi = None - - def estimate_hamiltonian(self, params, shots): - """Returns an array containing length ``shots`` single-shot estimates - of the Hamiltonian. The shots are distributed randomly over - the terms in the Hamiltonian, as per a Multinomial distribution. - - Since we are performing single-shot estimates, the QNodes must be - set to 'sample' mode. - """ - # note that convergence depends on seed for random number generation - rosalin_device = qml.device("default.qubit", wires=num_wires, shots=100, seed=93754352) - - # determine the shot probability per term - prob_shots = np.abs(coeffs) / np.sum(np.abs(coeffs)) - - # construct the multinomial distribution, and sample - # from it to determine how many shots to apply per term - si = multinomial(n=shots, p=prob_shots) - shots_per_term = si.rvs()[0] - - results = [] - - @qml.qnode(rosalin_device, diff_method="parameter-shift", interface="autograd") - def qnode(weights, observable): - StronglyEntanglingLayers(weights, wires=rosalin_device.wires) - return qml.sample(observable) - - for o, c, p, s in zip(self.obs, self.coeffs, prob_shots, shots_per_term): - - # if the number of shots is 0, do nothing - if s == 0: - continue - - # evaluate the QNode corresponding to - # the Hamiltonian term - res = qnode(params, o, shots=int(s)) - - if s == 1: - res = np.array([res]) - - # Note that, unlike above, we divide each term by the - # probability per shot. This is because we are sampling one at a time. - results.append(c * res / p) - - return np.concatenate(results) - - def evaluate_grad_var(self, i, params, shots): - """Evaluate the gradient, as well as the variance in the gradient, - for the ith parameter in params, using the parameter-shift rule. - """ - shift = np.zeros_like(params) - shift[i] = np.pi / 2 - - shift_forward = self.estimate_hamiltonian(params + shift, shots) - shift_backward = self.estimate_hamiltonian(params - shift, shots) - - g = np.mean(shift_forward - shift_backward) / 2 - s = np.var((shift_forward - shift_backward) / 2, ddof=1) - - return g, s - - def step(self, params): - """Perform a single step of the Rosalin optimizer.""" - # keep track of the number of shots run - self.shots_used += int(2 * np.sum(self.s)) - - # compute the gradient, as well as the variance in the gradient, - # using the number of shots determined by the array s. - grad = [] - S = [] - - p_ind = list(np.ndindex(*params.shape)) - - for l in p_ind: - # loop through each parameter, performing - # the parameter-shift rule - g_, s_ = self.evaluate_grad_var(l, params, self.s[l]) - grad.append(g_) - S.append(s_) - - grad = np.reshape(np.stack(grad), params.shape) - S = np.reshape(np.stack(S), params.shape) - - # gradient descent update - params = params - self.lr * grad - - if self.xi is None: - self.chi = np.zeros_like(params, dtype=np.float64) - self.xi = np.zeros_like(params, dtype=np.float64) - - # running average of the gradient variance - self.xi = self.mu * self.xi + (1 - self.mu) * S - xi = self.xi / (1 - self.mu ** (self.k + 1)) - - # running average of the gradient - self.chi = self.mu * self.chi + (1 - self.mu) * grad - chi = self.chi / (1 - self.mu ** (self.k + 1)) - - # determine the new optimum shots distribution for the next - # iteration of the optimizer - s = np.ceil( - (2 * self.lipschitz * self.lr * xi) - / ((2 - self.lipschitz * self.lr) * (chi ** 2 + self.b * (self.mu ** self.k))) - ) - - # apply an upper and lower bound on the new shot distributions, - # to avoid the number of shots reducing below min(2, min_shots), - # or growing too significantly. - gamma = ( - (self.lr - self.lipschitz * self.lr ** 2 / 2) * chi ** 2 - - xi * self.lipschitz * self.lr ** 2 / (2 * s) - ) / s - - argmax_gamma = np.unravel_index(np.argmax(gamma), gamma.shape) - smax = s[argmax_gamma] - self.s = np.clip(s, min(2, self.min_shots), max(2, smax)) - - self.k += 1 - return params - - -############################################################################## -# Rosalin optimization -# ~~~~~~~~~~~~~~~~~~~~ -# -# We are now ready to use our Rosalin optimizer to optimize the initial VQE problem. But first let's -# also create a separate cost function using an 'exact' quantum device, so that we can keep track of the -# *exact* cost function value at each iteration. - -@qml.qnode(analytic_dev, interface="autograd") -def cost_analytic(weights): - StronglyEntanglingLayers(weights, wires=analytic_dev.wires) - return qml.expval(qml.Hamiltonian(coeffs, obs)) - -############################################################################## -# Creating the optimizer and beginning the optimization: - - -opt = Rosalin(obs, coeffs, min_shots=10) -params = init_params - -cost_rosalin = [cost_analytic(params)] -shots_rosalin = [0] - -for i in range(60): - params = opt.step(params) - cost_rosalin.append(cost_analytic(params)) - shots_rosalin.append(opt.shots_used) - print(f"Step {i}: cost = {cost_rosalin[-1]}, shots_used = {shots_rosalin[-1]}") - - -############################################################################## -# Let's compare this to a standard Adam optimization. Using 100 shots per quantum -# evaluation, for each update step there are 2 quantum evaluations per parameter. - -adam_shots_per_eval = 100 -adam_shots_per_step = 2 * adam_shots_per_eval * len(params.flatten()) -print(adam_shots_per_step) - -############################################################################## -# Thus, Adam is using 2400 shots per update step. - -params = init_params -opt = qml.AdamOptimizer(0.07) - -adam_dev = qml.device('default.qubit', shots=adam_shots_per_eval, seed=595905) - -@qml.qnode(adam_dev, diff_method="parameter-shift", interface="autograd") -def cost(weights): - StronglyEntanglingLayers(weights, wires=non_analytic_dev.wires) - return qml.expval(qml.Hamiltonian(coeffs, obs)) - -cost_adam = [cost_analytic(params)] -shots_adam = [0] - -for i in range(100): - params = opt.step(cost, params) - cost_adam.append(cost_analytic(params)) - shots_adam.append(adam_shots_per_step * (i + 1)) - print("Step {}: cost = {} shots_used = {}".format(i, cost_adam[-1], shots_adam[-1])) - -############################################################################## -# Plotting both experiments: - -plt.style.use("seaborn-v0_8") -plt.plot(shots_rosalin, cost_rosalin, "b", label="Rosalin") -plt.plot(shots_adam, cost_adam, "g", label="Adam") - -plt.ylabel("Cost function value") -plt.xlabel("Number of shots") -plt.legend() -plt.xlim(0, 300000) -plt.show() - -############################################################################## -# The Rosalin optimizer performs significantly better than the Adam optimizer, -# approaching the ground state energy of the Hamiltonian with strikingly -# fewer shots. -# -# While beyond the scope of this demonstration, the Rosalin optimizer can be -# modified in various other ways; for instance, by incorporating *weighted hybrid -# sampling* (which distributes some shots deterministically, with the remainder -# done randomly), or by adapting the variant iCANS2 optimizer. Download -# this demonstration from the sidebar 👉 and give it a go! ⚛️ - - -############################################################################## -# References -# ---------- -# -# .. [#arrasmith2020] -# -# Andrew Arrasmith, Lukasz Cincio, Rolando D. Somma, and Patrick J. Coles. "Operator Sampling -# for Shot-frugal Optimization in Variational Algorithms." `arXiv:2004.06252 -# `__ (2020). -# -# .. [#stokes2019] -# -# James Stokes, Josh Izaac, Nathan Killoran, and Giuseppe Carleo. "Quantum Natural Gradient." -# `arXiv:1909.02108 `__ (2019). -# -# .. [#sweke2019] -# -# Ryan Sweke, Frederik Wilde, Johannes Jakob Meyer, Maria Schuld, Paul K. Fährmann, Barthélémy -# Meynard-Piganeau, and Jens Eisert. "Stochastic gradient descent for hybrid quantum-classical -# optimization." `arXiv:1910.01155 `__ (2019). -# -# .. [#kubler2020] -# -# Jonas M. Kübler, Andrew Arrasmith, Lukasz Cincio, and Patrick J. Coles. "An Adaptive Optimizer -# for Measurement-Frugal Variational Algorithms." `Quantum 4, 263 -# `__ (2020). -# -# -# About the author -# ---------------- +r""" +Frugal shot optimization with Rosalin +===================================== + +.. meta:: + :property="og:description": The Rosalin optimizer uses a measurement-frugal optimization strategy to minimize the + number of times a quantum computer is accessed. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/sphx_glr_tutorial_rosalin_002.png + +.. related:: + + tutorial_vqe A brief overview of VQE + tutorial_quantum_natural_gradient Quantum natural gradient + tutorial_doubly_stochastic Doubly stochastic gradient descent + tutorial_rotoselect Quantum circuit structure learning + +*Author: Josh Izaac — Posted: 19 May 2020. Last updated: 30 January 2023.* + +In this tutorial we investigate and implement the Rosalin (Random Operator Sampling for +Adaptive Learning with Individual Number of shots) from +Arrasmith et al. [#arrasmith2020]_. In this paper, a strategy +is introduced for reducing the number of shots required when optimizing variational quantum +algorithms, by both: + +* Frugally adapting the number of shots used per parameter update, and +* Performing a weighted sampling of operators from the cost Hamiltonian. + +.. note:: + + The Rosalin optimizer is available in PennyLane via the + :class:`~.pennylane.ShotAdaptiveOptimizer`. + +Background +---------- + +While a large number of papers in variational quantum algorithms focus on the +choice of circuit ansatz, cost function, gradient computation, or initialization method, +the optimization strategy—an important component affecting both convergence time and +quantum resource dependence—is not as frequently considered. Instead, common +'out-of-the-box' classical optimization techniques, such as gradient-free +methods (COBLYA, Nelder-Mead), gradient-descent, and Hessian-free methods (L-BFGS) tend to be used. + +However, for variational algorithms such as :doc:`VQE `, which involve evaluating +a large number of non-commuting operators in the cost function, decreasing the number of +quantum evaluations required for convergence, while still minimizing statistical noise, can +be a delicate balance. + +Recent work has highlighted that 'quantum-aware' optimization techniques +can lead to marked improvements when training variational quantum algorithms: + +* :doc:`/demos/tutorial_quantum_natural_gradient` descent by Stokes et al. [#stokes2019]_, which + takes into account the quantum geometry during the gradient-descent update step. + +* The work of Sweke et al. [#sweke2019]_, which shows + that quantum gradient descent with a finite number of shots is equivalent to + `stochastic gradient descent `_, + and has guaranteed convergence. Furthermore, combining a finite number of shots with + weighted sampling of the cost function terms leads to :doc:`/demos/tutorial_doubly_stochastic`. + +* The iCANS (individual Coupled Adaptive Number of Shots) optimization technique by + Jonas Kuebler et al. [#kubler2020]_ adapts the number + of shots measurements during training, by maximizing the expected gain per shot. + +In this latest result by Arrasmith et al. [#arrasmith2020]_, the +idea of doubly stochastic gradient descent has been used to extend the iCANS optimizer, +resulting in faster convergence. + +Over the course of this tutorial, we will explore their results; beginning first with a +demonstration of *weighted random sampling* of the cost Hamiltonian operators, before +combining this with the shot-frugal iCANS optimizer to perform doubly stochastic +Rosalin optimization. + +Weighted random sampling +------------------------ + +Consider a Hamiltonian :math:`H` expanded as a weighted sum of operators :math:`h_i` that can +be directly measured: + +.. math:: H = \sum_{i=1}^N c_i h_i. + +Due to the linearity of expectation values, the expectation value of this Hamiltonian +can be expressed as the weighted sum of each individual term: + +.. math:: \langle H\rangle = \sum_{i=1}^N c_i \langle h_i\rangle. + +In the :doc:`doubly stochastic gradient descent demonstration `, +we estimated this expectation value by **uniformly sampling** a subset of the terms +at each optimization step, and evaluating each term by using the same finite number of shots +:math:`N.` + +However, what happens if we use a weighted approach to determine how to distribute +our samples across the terms of the Hamiltonian? In **weighted random sampling** (WRS), +the number of shots used to determine the expectation value :math:`\langle h_i\rangle` +is a discrete random variable distributed according to a +`multinomial distribution `__, + +.. math:: S \sim \text{Multinomial}(p_i), + +with event probabilities + +.. math:: p_i = \frac{|c_i|}{\sum_i |c_i|}. + +That is, the number of shots assigned to the measurement of the expectation value of the +:math:`i\text{th}` term of the Hamiltonian is drawn from a probability distribution +*proportional to the magnitude of its coefficient* :math:`c_i.` + +To see this strategy in action, consider the Hamiltonian + +.. math:: H = 2I\otimes X + 4 I\otimes Z - X\otimes X + 5Y\otimes Y + 2 Z\otimes X. + +We can solve for the ground state energy using the variational quantum eigensolver (VQE) algorithm. + +First, let's import NumPy and PennyLane, and define our Hamiltonian. +""" +import pennylane as qml +from pennylane import numpy as np + +# set the random seed +np.random.seed(4) + +coeffs = [2, 4, -1, 5, 2] + +obs = [ + qml.PauliX(1), + qml.PauliZ(1), + qml.PauliX(0) @ qml.PauliX(1), + qml.PauliY(0) @ qml.PauliY(1), + qml.PauliZ(0) @ qml.PauliZ(1) +] + + +############################################################################## +# We can now create our quantum device (let's use the ``default.qubit`` simulator). + +num_layers = 2 +num_wires = 2 + +# create a device that estimates expectation values using a finite number of shots +non_analytic_dev = qml.device("default.qubit", wires=num_wires, shots=100, seed=432423) + +# create a device that calculates exact expectation values +analytic_dev = qml.device("default.qubit", wires=num_wires, shots=None) + +############################################################################## +# Now, let's set the total number of shots, and determine the probability +# for sampling each Hamiltonian term. + +total_shots = 8000 +prob_shots = np.abs(coeffs) / np.sum(np.abs(coeffs)) +print(prob_shots) + +############################################################################## +# We can now use SciPy to create our multinomial distributed random variable +# :math:`S,` using the number of trials (total shot number) and probability values: + +from scipy.stats import multinomial + +si = multinomial(n=total_shots, p=prob_shots) + +############################################################################## +# Sampling from this distribution will provide the number of shots used to +# sample each term in the Hamiltonian: + +samples = si.rvs()[0] +print(samples) +print(sum(samples)) + +############################################################################## +# As expected, if we sum the sampled shots per term, we recover the total number of shots. +# +# Let's now create our cost function. Recall that the cost function must do the +# following: +# +# 1. It must sample from the multinomial distribution we created above, +# to determine the number of shots :math:`s_i` to use to estimate the expectation +# value of the ith Hamiltonian term. +# +# 2. It then must estimate the expectation value :math:`\langle h_i\rangle` +# by creating the required QNode. For our ansatz, we'll use the +# :class:`~.pennylane.templates.layers.StronglyEntanglingLayers`. +# +# 3. And, last but not least, estimate the expectation value +# :math:`\langle H\rangle = \sum_i c_i\langle h_i\rangle.` +# + +from pennylane.templates.layers import StronglyEntanglingLayers + + +@qml.qnode(non_analytic_dev, diff_method="parameter-shift", interface="autograd") +def qnode(weights, observable): + StronglyEntanglingLayers(weights, wires=non_analytic_dev.wires) + return qml.expval(observable) + +def cost(params): + # sample from the multinomial distribution + shots_per_term = si.rvs()[0] + + result = 0 + + for o, c, s in zip(obs, coeffs, shots_per_term): + # evaluate the QNode corresponding to + # the Hamiltonian term, and add it on to our running sum + result += c * qnode(params, o, shots=int(s)) + + return result + + +############################################################################## +# Evaluating our cost function with some initial parameters, we can test out +# that our cost function evaluates correctly. + +param_shape = StronglyEntanglingLayers.shape(n_layers=num_layers, n_wires=num_wires) +init_params = np.random.uniform(low=0.0, high=2*np.pi, size=param_shape, requires_grad=True) +print(cost(init_params)) + + +############################################################################## +# Performing the optimization, with the number of shots randomly +# determined at each optimization step: + +opt = qml.AdamOptimizer(0.05) +params = init_params + +cost_wrs = [] +shots_wrs = [] + +for i in range(100): + params, _cost = opt.step_and_cost(cost, params) + cost_wrs.append(_cost) + shots_wrs.append(total_shots*i) + print("Step {}: cost = {} shots used = {}".format(i, cost_wrs[-1], shots_wrs[-1])) + +############################################################################## +# Let's compare this against an optimization not using weighted random sampling. +# Here, we will split the 8000 total shots evenly across all Hamiltonian terms, +# also known as *uniform deterministic sampling*. + +@qml.qnode(non_analytic_dev, diff_method="parameter-shift", interface="autograd") +def qnode(weights, obs): + StronglyEntanglingLayers(weights, wires=non_analytic_dev.wires) + return qml.expval(obs) + +def cost(params): + shots_per_term = int(total_shots / len(coeffs)) + + result = 0 + + for o, c in zip(obs, coeffs): + + # evaluate the QNode corresponding to + # the Hamiltonian term, and add it on to our running sum + result += c * qnode(params, o, shots=shots_per_term) + + return result + +opt = qml.AdamOptimizer(0.05) +params = init_params + +cost_adam = [] +shots_adam = [] + +for i in range(100): + params, _cost = opt.step_and_cost(cost, params) + cost_adam.append(_cost) + shots_adam.append(total_shots*i) + print("Step {}: cost = {} shots used = {}".format(i, cost_adam[-1], shots_adam[-1])) + +############################################################################## +# Comparing these two techniques: + +from matplotlib import pyplot as plt + +plt.style.use("seaborn-v0_8") +plt.plot(shots_wrs, cost_wrs, "b", label="Adam WRS") +plt.plot(shots_adam, cost_adam, "g", label="Adam") + +plt.ylabel("Cost function value") +plt.xlabel("Number of shots") +plt.legend() +plt.show() + +############################################################################## +# We can see that weighted random sampling performs just as well as the uniform +# deterministic sampling. However, weighted random sampling begins to show a +# non-negligible improvement over deterministic sampling for large Hamiltonians +# with highly non-uniform coefficients. For example, see Fig (3) and (4) of +# Arrasmith et al. [#arrasmith2020]_, comparing weighted random sampling VQE optimization +# for both :math:`\text{H}_2` and :math:`\text{LiH}` molecules. +# +# .. note:: +# +# While not covered here, another approach that could be taken is +# *weighted deterministic sampling*. Here, the number of shots is distributed +# across terms as per +# +# .. math:: s_i = \left\lfloor N \frac{|c_i|}{\sum_i |c_i|}\right\rfloor, +# +# where :math:`N` is the total number of shots. +# + +############################################################################## +# Rosalin: Frugal shot optimization +# --------------------------------- +# +# We can see above that both methods optimize fairly well; weighted random +# sampling converges just as well as evenly distributing the shots across +# all Hamiltonian terms. However, deterministic shot distribution approaches +# will always have a minimum shot value required per expectation value, as below +# this threshold they become biased estimators. This is not the case with random +# sampling; as we saw in the +# :doc:`doubly stochastic gradient descent demonstration `, +# the introduction of randomness allows for as little +# as a single shot per expectation term, while still remaining an unbiased estimator. +# +# Using this insight, Arrasmith et al. [#arrasmith2020]_ modified the iCANS frugal +# shot-optimization technique [#kubler2020]_ to include weighted random sampling, making it +# 'doubly stochastic'. +# +# iCANS optimizer +# ~~~~~~~~~~~~~~~ +# +# Two variants of the iCANS optimizer were introduced in Kübler et al., iCANS1 and iCANS2. +# The iCANS1 optimizer, on which Rosalin is based, frugally distributes a shot budget +# across the partial derivatives of each parameter, which are computed using the +# :doc:`parameter-shift rule `. It works roughly as follows: +# +# 1. The initial step of the optimizer is performed with some specified minimum +# number of shots, :math:`s_{min},` for all partial derivatives. +# +# 2. The parameter-shift rule is then used to estimate the gradient :math:`g_i` +# for each parameter :math:`\theta_i,` parameters, as well as the *variances* +# :math:`v_i` of the estimated gradients. +# +# 3. Gradient descent is performed for each parameter :math:`\theta_i,` using +# the pre-defined learning rate :math:`\alpha` and the gradient information :math:`g_i:` +# +# .. math:: \theta_i = \theta_i - \alpha g_i. +# +# 4. The improvement in the cost function per shot, for a specific parameter value, +# is then calculated via +# +# .. math:: +# +# \gamma_i = \frac{1}{s_i} \left[ \left(\alpha - \frac{1}{2} L\alpha^2\right) +# g_i^2 - \frac{L\alpha^2}{2s_i}v_i \right], +# +# where: +# +# * :math:`L \leq \sum_i|c_i|` is the bound on the `Lipschitz constant +# `__ of the variational quantum algorithm objective function, +# +# * :math:`c_i` are the coefficients of the Hamiltonian, and +# +# * :math:`\alpha` is the learning rate, and *must* be bound such that :math:`\alpha < 2/L` +# for the above expression to hold. +# +# 5. Finally, the new values of :math:`s_i` (shots for partial derivative of parameter +# :math:`\theta_i`) is given by: +# +# .. math:: +# +# s_i = \frac{2L\alpha}{2-L\alpha}\left(\frac{v_i}{g_i^2}\right)\propto +# \frac{v_i}{g_i^2}. +# +# In addition to the above, to counteract the presence of noise in the system, a +# running average of :math:`g_i` and :math:`s_i` (:math:`\chi_i` and :math:`\xi_i` respectively) +# are used when computing :math:`\gamma_i` and :math:`s_i.` +# +# .. note:: +# +# In classical machine learning, the Lipschitz constant of the cost function is generally +# unknown. However, for a variational quantum algorithm with cost of the form +# :math:`f(x) = \langle \psi(x) | \hat{H} |\psi(x)\rangle,` +# an upper bound on the Lipschitz constant is given by :math:`L < \sum_i|c_i|,` +# where :math:`c_i` are the coefficients of :math:`\hat{H}` when decomposed +# into a linear combination of Pauli-operator tensor products. +# +# Rosalin implementation +# ~~~~~~~~~~~~~~~~~~~~~~ +# +# Let's now modify iCANS above to incorporate weighted random sampling of Hamiltonian +# terms — the Rosalin frugal shot optimizer. +# +# Rosalin takes several hyper-parameters: +# +# * ``min_shots``: the minimum number of shots used to estimate the expectations +# of each term in the Hamiltonian. Note that this must be larger than 2 for the variance +# of the gradients to be computed. +# +# * ``mu``: The running average constant :math:`\mu\in[0, 1].` Used to control how quickly the +# number of shots recommended for each gradient component changes. +# +# * ``b``: Regularization bias. The bias should be kept small, but non-zero. +# +# * ``lr``: The learning rate. Recall from above that the learning rate *must* be such +# that :math:`\alpha < 2/L = 2/\sum_i|c_i|.` +# +# Since the Rosalin optimizer has a state that must be preserved between optimization steps, +# let's use a class to create our optimizer. +# + +class Rosalin: + + def __init__(self, obs, coeffs, min_shots, mu=0.99, b=1e-6, lr=0.07): + self.obs = obs + self.coeffs = coeffs + + self.lipschitz = np.sum(np.abs(coeffs)) + + if lr > 2 / self.lipschitz: + raise ValueError("The learning rate must be less than ", 2 / self.lipschitz) + + # hyperparameters + self.min_shots = min_shots + self.mu = mu # running average constant + self.b = b # regularization bias + self.lr = lr # learning rate + + # keep track of the total number of shots used + self.shots_used = 0 + # total number of iterations + self.k = 0 + # Number of shots per parameter + self.s = np.zeros_like(params, dtype=np.float64) + min_shots + + # Running average of the parameter gradients + self.chi = None + # Running average of the variance of the parameter gradients + self.xi = None + + def estimate_hamiltonian(self, params, shots): + """Returns an array containing length ``shots`` single-shot estimates + of the Hamiltonian. The shots are distributed randomly over + the terms in the Hamiltonian, as per a Multinomial distribution. + + Since we are performing single-shot estimates, the QNodes must be + set to 'sample' mode. + """ + # note that convergence depends on seed for random number generation + rosalin_device = qml.device("default.qubit", wires=num_wires, shots=100, seed=93754352) + + # determine the shot probability per term + prob_shots = np.abs(coeffs) / np.sum(np.abs(coeffs)) + + # construct the multinomial distribution, and sample + # from it to determine how many shots to apply per term + si = multinomial(n=shots, p=prob_shots) + shots_per_term = si.rvs()[0] + + results = [] + + @qml.qnode(rosalin_device, diff_method="parameter-shift", interface="autograd") + def qnode(weights, observable): + StronglyEntanglingLayers(weights, wires=rosalin_device.wires) + return qml.sample(observable) + + for o, c, p, s in zip(self.obs, self.coeffs, prob_shots, shots_per_term): + + # if the number of shots is 0, do nothing + if s == 0: + continue + + # evaluate the QNode corresponding to + # the Hamiltonian term + res = qnode(params, o, shots=int(s)) + + if s == 1: + res = np.array([res]) + + # Note that, unlike above, we divide each term by the + # probability per shot. This is because we are sampling one at a time. + results.append(c * res / p) + + return np.concatenate(results) + + def evaluate_grad_var(self, i, params, shots): + """Evaluate the gradient, as well as the variance in the gradient, + for the ith parameter in params, using the parameter-shift rule. + """ + shift = np.zeros_like(params) + shift[i] = np.pi / 2 + + shift_forward = self.estimate_hamiltonian(params + shift, shots) + shift_backward = self.estimate_hamiltonian(params - shift, shots) + + g = np.mean(shift_forward - shift_backward) / 2 + s = np.var((shift_forward - shift_backward) / 2, ddof=1) + + return g, s + + def step(self, params): + """Perform a single step of the Rosalin optimizer.""" + # keep track of the number of shots run + self.shots_used += int(2 * np.sum(self.s)) + + # compute the gradient, as well as the variance in the gradient, + # using the number of shots determined by the array s. + grad = [] + S = [] + + p_ind = list(np.ndindex(*params.shape)) + + for l in p_ind: + # loop through each parameter, performing + # the parameter-shift rule + g_, s_ = self.evaluate_grad_var(l, params, self.s[l]) + grad.append(g_) + S.append(s_) + + grad = np.reshape(np.stack(grad), params.shape) + S = np.reshape(np.stack(S), params.shape) + + # gradient descent update + params = params - self.lr * grad + + if self.xi is None: + self.chi = np.zeros_like(params, dtype=np.float64) + self.xi = np.zeros_like(params, dtype=np.float64) + + # running average of the gradient variance + self.xi = self.mu * self.xi + (1 - self.mu) * S + xi = self.xi / (1 - self.mu ** (self.k + 1)) + + # running average of the gradient + self.chi = self.mu * self.chi + (1 - self.mu) * grad + chi = self.chi / (1 - self.mu ** (self.k + 1)) + + # determine the new optimum shots distribution for the next + # iteration of the optimizer + s = np.ceil( + (2 * self.lipschitz * self.lr * xi) + / ((2 - self.lipschitz * self.lr) * (chi ** 2 + self.b * (self.mu ** self.k))) + ) + + # apply an upper and lower bound on the new shot distributions, + # to avoid the number of shots reducing below min(2, min_shots), + # or growing too significantly. + gamma = ( + (self.lr - self.lipschitz * self.lr ** 2 / 2) * chi ** 2 + - xi * self.lipschitz * self.lr ** 2 / (2 * s) + ) / s + + argmax_gamma = np.unravel_index(np.argmax(gamma), gamma.shape) + smax = s[argmax_gamma] + self.s = np.clip(s, min(2, self.min_shots), max(2, smax)) + + self.k += 1 + return params + + +############################################################################## +# Rosalin optimization +# ~~~~~~~~~~~~~~~~~~~~ +# +# We are now ready to use our Rosalin optimizer to optimize the initial VQE problem. But first let's +# also create a separate cost function using an 'exact' quantum device, so that we can keep track of the +# *exact* cost function value at each iteration. + +@qml.qnode(analytic_dev, interface="autograd") +def cost_analytic(weights): + StronglyEntanglingLayers(weights, wires=analytic_dev.wires) + return qml.expval(qml.Hamiltonian(coeffs, obs)) + +############################################################################## +# Creating the optimizer and beginning the optimization: + + +opt = Rosalin(obs, coeffs, min_shots=10) +params = init_params + +cost_rosalin = [cost_analytic(params)] +shots_rosalin = [0] + +for i in range(60): + params = opt.step(params) + cost_rosalin.append(cost_analytic(params)) + shots_rosalin.append(opt.shots_used) + print(f"Step {i}: cost = {cost_rosalin[-1]}, shots_used = {shots_rosalin[-1]}") + + +############################################################################## +# Let's compare this to a standard Adam optimization. Using 100 shots per quantum +# evaluation, for each update step there are 2 quantum evaluations per parameter. + +adam_shots_per_eval = 100 +adam_shots_per_step = 2 * adam_shots_per_eval * len(params.flatten()) +print(adam_shots_per_step) + +############################################################################## +# Thus, Adam is using 2400 shots per update step. + +params = init_params +opt = qml.AdamOptimizer(0.07) + +adam_dev = qml.device('default.qubit', shots=adam_shots_per_eval, seed=595905) + +@qml.qnode(adam_dev, diff_method="parameter-shift", interface="autograd") +def cost(weights): + StronglyEntanglingLayers(weights, wires=non_analytic_dev.wires) + return qml.expval(qml.Hamiltonian(coeffs, obs)) + +cost_adam = [cost_analytic(params)] +shots_adam = [0] + +for i in range(100): + params = opt.step(cost, params) + cost_adam.append(cost_analytic(params)) + shots_adam.append(adam_shots_per_step * (i + 1)) + print("Step {}: cost = {} shots_used = {}".format(i, cost_adam[-1], shots_adam[-1])) + +############################################################################## +# Plotting both experiments: + +plt.style.use("seaborn-v0_8") +plt.plot(shots_rosalin, cost_rosalin, "b", label="Rosalin") +plt.plot(shots_adam, cost_adam, "g", label="Adam") + +plt.ylabel("Cost function value") +plt.xlabel("Number of shots") +plt.legend() +plt.xlim(0, 300000) +plt.show() + +############################################################################## +# The Rosalin optimizer performs significantly better than the Adam optimizer, +# approaching the ground state energy of the Hamiltonian with strikingly +# fewer shots. +# +# While beyond the scope of this demonstration, the Rosalin optimizer can be +# modified in various other ways; for instance, by incorporating *weighted hybrid +# sampling* (which distributes some shots deterministically, with the remainder +# done randomly), or by adapting the variant iCANS2 optimizer. Download +# this demonstration from the sidebar 👉 and give it a go! ⚛️ + + +############################################################################## +# References +# ---------- +# +# .. [#arrasmith2020] +# +# Andrew Arrasmith, Lukasz Cincio, Rolando D. Somma, and Patrick J. Coles. "Operator Sampling +# for Shot-frugal Optimization in Variational Algorithms." `arXiv:2004.06252 +# `__ (2020). +# +# .. [#stokes2019] +# +# James Stokes, Josh Izaac, Nathan Killoran, and Giuseppe Carleo. "Quantum Natural Gradient." +# `arXiv:1909.02108 `__ (2019). +# +# .. [#sweke2019] +# +# Ryan Sweke, Frederik Wilde, Johannes Jakob Meyer, Maria Schuld, Paul K. Fährmann, Barthélémy +# Meynard-Piganeau, and Jens Eisert. "Stochastic gradient descent for hybrid quantum-classical +# optimization." `arXiv:1910.01155 `__ (2019). +# +# .. [#kubler2020] +# +# Jonas M. Kübler, Andrew Arrasmith, Lukasz Cincio, and Patrick J. Coles. "An Adaptive Optimizer +# for Measurement-Frugal Variational Algorithms." `Quantum 4, 263 +# `__ (2020). +# +# +# About the author +# ---------------- # \ No newline at end of file diff --git a/demonstrations_v2/vqe_parallel/demo.py b/demonstrations_v2/vqe_parallel/demo.py index 7c298007e8..91c47d7bf1 100644 --- a/demonstrations_v2/vqe_parallel/demo.py +++ b/demonstrations_v2/vqe_parallel/demo.py @@ -1,393 +1,393 @@ - -# coding=utf-8 -r""" -VQE with parallel QPUs with Rigetti -======================================== - -.. meta:: - :property="og:description": Using parallel QPUs to - speed up the calculation of the potential energy surface of molecular Hamiltonian. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/vqe_diagram.png - -.. related:: - - tutorial_vqe A brief overview of VQE - -*Author: Tom Bromley — Posted: 14 February 2020. Last updated: 29 August 2023.* - -.. warning:: - This demo requires Python <=3.10 and uses the PennyLane-Rigetti plugin, which is only compatible with PennyLane v0.40 or below. To run this demo with newer versions of PennyLane, you will need to use `a different simulator device `__. - -This tutorial showcases how using asynchronously-evaluated parallel QPUs can speed up the -calculation of the potential energy surface of molecular hydrogen (:math:`H_2`). - -Using a VQE setup, we task two devices from the -`PennyLane-Rigetti `__ plugin with evaluating -separate terms in the qubit Hamiltonian of :math:`H_2.` As these devices are allowed to operate -asynchronously, i.e., at the same time and without having to wait for each other, -the calculation can be performed in roughly half the time. - -We begin by importing the prerequisite libraries: -""" - -import time -import dask - -import matplotlib.pyplot as plt -from pennylane import numpy as np -import pennylane as qml -from pennylane import qchem - -############################################################################## -# -# This tutorial requires the ``pennylane-rigetti`` and ``dask`` -# packages, which are installed separately using: -# -# .. code-block:: bash -# -# pip install pennylane-rigetti -# pip install "dask[delayed]" -# -# Finding the qubit Hamiltonians of :math:`H_{2}` -# ----------------------------------------------- -# -# The objective of this tutorial is to evaluate the potential energy surface of molecular -# hydrogen. This is achieved by finding the ground state energy of :math:`H_{2}` as we increase -# the bond length between the hydrogen atoms. -# -# Each inter-atomic distance results in a different qubit Hamiltonian. Further -# details on the mapping from the electronic Hamiltonian of a molecule to a -# qubit Hamiltonian can be found in the -# :doc:`tutorial_quantum_chemistry` and :doc:`tutorial_vqe` -# tutorials. -# -# We begin by downloading a selection of datasets of :math:`H_2` molecule for -# various bond lengths using the -# `PennyLane Datasets library `__: - -bonds = [0.5, 0.58, 0.7, 0.9, 1.1, 1.3, 1.5, 1.7, 1.9, 2.1] -datasets = qml.data.load("qchem", molname="H2", bondlength=bonds, basis="STO-3G") - -############################################################################## -# We can now extract the qubit Hamiltonians from these datasets for each bond length: - -hamiltonians = [d.hamiltonian for d in datasets] - -############################################################################## -# Each Hamiltonian can be written as a linear combination of fifteen tensor products of Pauli -# matrices. Let's take a look more closely at one of the Hamiltonians: - -h = hamiltonians[0] -_, h_ops = h.terms() - -print("Number of terms: {}\n".format(len(h_ops))) -for op in h_ops: - print("Measurement {} on wires {}".format(str(op), op.wires)) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# Number of terms: 15 -# -# Measurement I(0) on wires Wires([0]) -# Measurement Z(0) on wires Wires([0]) -# Measurement Z(1) on wires Wires([1]) -# Measurement Z(0) @ Z(1) on wires Wires([0, 1]) -# Measurement Y(0) @ X(1) @ X(2) @ Y(3) on wires Wires([0, 1, 2, 3]) -# Measurement Y(0) @ Y(1) @ X(2) @ X(3) on wires Wires([0, 1, 2, 3]) -# Measurement X(0) @ X(1) @ Y(2) @ Y(3) on wires Wires([0, 1, 2, 3]) -# Measurement X(0) @ Y(1) @ Y(2) @ X(3) on wires Wires([0, 1, 2, 3]) -# Measurement Z(2) on wires Wires([2]) -# Measurement Z(0) @ Z(2) on wires Wires([0, 2]) -# Measurement Z(3) on wires Wires([3]) -# Measurement Z(0) @ Z(3) on wires Wires([0, 3]) -# Measurement Z(1) @ Z(2) on wires Wires([1, 2]) -# Measurement Z(1) @ Z(3) on wires Wires([1, 3]) -# Measurement Z(2) @ Z(3) on wires Wires([2, 3]) - -############################################################################## -# Defining the energy function -# ---------------------------- -# -# The fifteen Pauli terms comprising each Hamiltonian can conventionally be evaluated in a -# sequential manner: we evaluate one expectation value at a time before moving on to the next. -# However, this task is highly suited to parallelization. With access to multiple QPUs, -# we can split up evaluating the terms between the QPUs and gain an increase in processing speed. -# -# -# .. note:: -# Some of the Pauli terms commute, and so they can be evaluated in practice with fewer than -# fifteen quantum circuit runs. Nevertheless, these quantum circuit runs can still be -# parallelized to multiple QPUs. -# -# Let's suppose we have access to two quantum devices. In this tutorial we consider two -# simulators from Rigetti: ``4q-qvm`` and ``9q-square-qvm``, but we could also run on hardware -# devices from Rigetti or other providers. -# -# We can evaluate the expectation value of each Hamiltonian with eight terms run on -# one device and seven terms run on the other, as summarized by the diagram below: -# -# .. figure:: /_static/demonstration_assets/vqe_parallel/vqe_diagram.png -# :width: 65% -# :align: center -# -# To do this, start by instantiating a device for each term: - -dev1 = [qml.device("rigetti.qvm", device="4q-qvm") for _ in range(8)] -dev2 = [qml.device("rigetti.qvm", device="9q-square-qvm") for _ in range(7)] -devs = dev1 + dev2 - -############################################################################## -# .. note:: -# -# For the purposes of this demonstration, we are simulating the QPUs using the -# ``rigetti.qvm`` simulator. To run this demonstration on hardware, simply -# swap ``rigetti.qvm`` for ``rigetti.qpu`` and specify the hardware device to run on. -# -# Please refer to the `Rigetti website `__ for an up-to-date -# list on available QPUs. -# -# .. warning:: -# Rigetti's QVM and Quil Compiler services must be running for this tutorial to execute. They -# can be installed by consulting the `Rigetti documentation -# `__ or, for users with Docker, by running: -# -# .. code-block:: bash -# -# docker run -d -p 5555:5555 rigetti/quilc -R -p 5555 -# docker run -d -p 5000:5000 rigetti/qvm -S -p 5000 -# -# We must also define a circuit to prepare the ground state, which is a superposition of the -# Hartree-Fock (:math:`|1100\rangle`) and doubly-excited (:math:`|0011\rangle`) configurations. -# The simple circuit below is able to prepare states of the form :math:`\alpha |1100\rangle + -# \beta |0011\rangle` and hence encode the ground state wave function of the hydrogen molecule. The -# circuit has a single free parameter, which controls a Y-rotation on the third qubit. - - -def circuit(param, H): - qml.BasisState(np.array([1, 1, 0, 0], requires_grad=False), wires=[0, 1, 2, 3]) - qml.RY(param, wires=2) - qml.CNOT(wires=[2, 3]) - qml.CNOT(wires=[2, 0]) - qml.CNOT(wires=[3, 1]) - return qml.expval(H) - - -############################################################################## -# The ground state for each inter-atomic distance is characterized by a different Y-rotation angle. -# The values of these Y-rotations can be found by minimizing the ground state energy as outlined in -# :doc:`tutorial_vqe`. In this tutorial, we load pre-optimized rotations and focus on -# comparing the speed of evaluating the potential energy surface with sequential and parallel -# evaluation. These parameters can be downloaded by clicking :download:`here -# <../_static/demonstration_assets/vqe_parallel/RY_params.npy>`. - -params = np.load("vqe_parallel/RY_params.npy") - -############################################################################## -# Calculating the potential energy surface -# ---------------------------------------- -# The most vanilla execution of these 10 energy surfaces is using the standard PennyLane functionalities by executing the QNodes. -# Internally, this creates a measurement for each term in the Hamiltonian that are then sequentially computed. - -print("Evaluating the potential energy surface sequentially") -t0 = time.time() - -energies_seq = [] -for i, (h, param) in enumerate(zip(hamiltonians, params)): - print( - f"{i+1} / {len(bonds)}: Sequential execution; Running for inter-atomic distance {bonds[i]} Å" - ) - energies_seq.append(qml.QNode(circuit, devs[0])(param, h)) - -dt_seq = time.time() - t0 - -print(f"Evaluation time: {dt_seq:.2f} s") - -############################################################################## -# We can parallelize the individual evaluations using ``dask`` in the following way: We take the 15 terms of the Hamiltonian and -# distribute them to the 15 devices in ``devs``. This evaluation is delayed using ``dask.delayed`` and later computed -# in parallel using ``dask.compute``, which asynchronously executes the delayed objects in ``results``. - - -def compute_energy_parallel(H, devs, param): - H_coeffs, H_ops = H.terms() - assert len(H_ops) == len(devs) - results = [] - - for i in range(len(H_ops)): - qnode = qml.QNode(circuit, devs[i]) - results.append(dask.delayed(qnode)(param, H_ops[i])) - - results = dask.compute(*results, scheduler="threads") - result = sum(c * r for c, r in zip(H_coeffs, results)) - return result - - -############################################################################## -# We can now compute all 10 samples from the energy surface sequentially, where each execution is making use of -# parallel device execution. Curiously, in this example the overhead from doing so outweighs the speed-up -# and the execution is slower than standard execution using ``qml.expval``. For different circuits and -# different Hamiltonians, however, parallelization may provide significant speed-ups. - -print("Evaluating the potential energy surface in parallel") -t0 = time.time() - -energies_par = [] -for i, (h, param) in enumerate(zip(hamiltonians, params)): - print( - f"{i+1} / {len(bonds)}: Parallel execution; Running for inter-atomic distance {bonds[i]} Å" - ) - energies_par.append(compute_energy_parallel(h, devs, param)) - -dt_par = time.time() - t0 - -print(f"Evaluation time: {dt_par:.2f} s") - - -############################################################################## -# We can improve this procedure further by optimizing the measurement. Currently, we are measuring each term of the Hamiltonian -# in a separate measurement. This is not necessary as there are sub-groups of commuting terms in the Hamiltonian that can be measured -# simultaneously. We can utilize the grouping function :func:`~.pennylane.pauli.group_observables` to generate few measurements that -# are executed in parallel: - - -def compute_energy_parallel_optimized(H, devs, param): - H_coeffs, H_ops = H.terms() - assert len(H_ops) == len(devs) - results = [] - - obs_groupings, coeffs_groupings = qml.pauli.group_observables( - H_ops, H_coeffs, "qwc" - ) - - for i, (obs, coeffs) in enumerate(zip(obs_groupings, coeffs_groupings)): - H_part = qml.Hamiltonian(coeffs, obs) - qnode = qml.QNode(circuit, devs[i]) - results.append(dask.delayed(qnode)(param, H_part)) - - result = qml.math.sum(dask.compute(*results, scheduler="threads")) - return result - -print( - "Evaluating the potential energy surface in parallel with measurement optimization" -) -t0 = time.time() - -energies_par_opt = [] -for i, (h, param) in enumerate(zip(hamiltonians, params)): - print( - f"{i+1} / {len(bonds)}: Parallel execution and measurement optimization; Running for inter-atomic distance {bonds[i]} Å" - ) - energies_par_opt.append(compute_energy_parallel_optimized(h, devs, param)) - -dt_par_opt = time.time() - t0 - -print(f"Evaluation time: {dt_par_opt:.2f} s") - - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# Evaluating the potential energy surface sequentially -# 1 / 10: Sequential execution; Running for inter-atomic distance 0.3 Å -# 2 / 10: Sequential execution; Running for inter-atomic distance 0.5 Å -# 3 / 10: Sequential execution; Running for inter-atomic distance 0.7 Å -# 4 / 10: Sequential execution; Running for inter-atomic distance 0.9 Å -# 5 / 10: Sequential execution; Running for inter-atomic distance 1.1 Å -# 6 / 10: Sequential execution; Running for inter-atomic distance 1.3 Å -# 7 / 10: Sequential execution; Running for inter-atomic distance 1.5 Å -# 8 / 10: Sequential execution; Running for inter-atomic distance 1.7 Å -# 9 / 10: Sequential execution; Running for inter-atomic distance 1.9 Å -# 10 / 10: Sequential execution; Running for inter-atomic distance 2.1 Å -# Evaluation time: 39.33 s -# -# Evaluating the potential energy surface in parallel -# 1 / 10: Parallel execution; Running for inter-atomic distance 0.3 Å -# 2 / 10: Parallel execution; Running for inter-atomic distance 0.5 Å -# 3 / 10: Parallel execution; Running for inter-atomic distance 0.7 Å -# 4 / 10: Parallel execution; Running for inter-atomic distance 0.9 Å -# 5 / 10: Parallel execution; Running for inter-atomic distance 1.1 Å -# 6 / 10: Parallel execution; Running for inter-atomic distance 1.3 Å -# 7 / 10: Parallel execution; Running for inter-atomic distance 1.5 Å -# 8 / 10: Parallel execution; Running for inter-atomic distance 1.7 Å -# 9 / 10: Parallel execution; Running for inter-atomic distance 1.9 Å -# 10 / 10: Parallel execution; Running for inter-atomic distance 2.1 Å -# Evaluation time: 73.42 s -# -# Evaluating the potential energy surface in parallel with measurement optimization -# 1 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.3 Å -# 2 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.5 Å -# 3 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.7 Å -# 4 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.9 Å -# 5 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.1 Å -# 6 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.3 Å -# 7 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.5 Å -# 8 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.7 Å -# 9 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.9 Å -# 10 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 2.1 Å -# Evaluation time: 26.51 s - - -############################################################################## -# We have seen how Hamiltonian measurements can be parallelized and optimized at the same time. - -print("Speed up: {0:.2f}".format(dt_seq / dt_par_opt)) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# Speed up: 1.48 - -############################################################################## -# To conclude the tutorial, let's plot the calculated -# potential energy surfaces: - -np.savez( - "vqe_parallel", - energies_seq=energies_seq, - energies_par=energies_par, - energies_par_opt=energies_par_opt, -) - -plt.plot( - bonds, energies_seq, linewidth=2.2, marker="d", color="green", label="sequential" -) -plt.plot(bonds, energies_par, linewidth=2.2, marker="o", color="red", label="parallel") -plt.plot( - bonds, - energies_par_opt, - linewidth=2.2, - marker="d", - color="blue", - label="paralell and optimized", -) -plt.legend(fontsize=12) -plt.title("Potential energy surface for molecular hydrogen", fontsize=12) -plt.xlabel("Atomic separation (Å)", fontsize=16) -plt.ylabel("Ground state energy (Ha)", fontsize=16) -plt.grid(True) - -############################################################################## -# .. figure:: /_static/demonstration_assets/vqe_parallel/vqe_parallel_001.png -# :width: 80% -# :align: center -# - -############################################################################## -# These surfaces overlap, with any variation due to the limited number of shots used to evaluate the -# expectation values in the ``rigetti.qvm`` device (we are using the default value of -# ``shots=1024``). - -############################################################################## -# About the author -# ---------------- -# + +# coding=utf-8 +r""" +VQE with parallel QPUs with Rigetti +======================================== + +.. meta:: + :property="og:description": Using parallel QPUs to + speed up the calculation of the potential energy surface of molecular Hamiltonian. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/vqe_diagram.png + +.. related:: + + tutorial_vqe A brief overview of VQE + +*Author: Tom Bromley — Posted: 14 February 2020. Last updated: 29 August 2023.* + +.. warning:: + This demo requires Python <=3.10 and uses the PennyLane-Rigetti plugin, which is only compatible with PennyLane v0.40 or below. To run this demo with newer versions of PennyLane, you will need to use `a different simulator device `__. + +This tutorial showcases how using asynchronously-evaluated parallel QPUs can speed up the +calculation of the potential energy surface of molecular hydrogen (:math:`H_2`). + +Using a VQE setup, we task two devices from the +`PennyLane-Rigetti `__ plugin with evaluating +separate terms in the qubit Hamiltonian of :math:`H_2.` As these devices are allowed to operate +asynchronously, i.e., at the same time and without having to wait for each other, +the calculation can be performed in roughly half the time. + +We begin by importing the prerequisite libraries: +""" + +import time +import dask + +import matplotlib.pyplot as plt +from pennylane import numpy as np +import pennylane as qml +from pennylane import qchem + +############################################################################## +# +# This tutorial requires the ``pennylane-rigetti`` and ``dask`` +# packages, which are installed separately using: +# +# .. code-block:: bash +# +# pip install pennylane-rigetti +# pip install "dask[delayed]" +# +# Finding the qubit Hamiltonians of :math:`H_{2}` +# ----------------------------------------------- +# +# The objective of this tutorial is to evaluate the potential energy surface of molecular +# hydrogen. This is achieved by finding the ground state energy of :math:`H_{2}` as we increase +# the bond length between the hydrogen atoms. +# +# Each inter-atomic distance results in a different qubit Hamiltonian. Further +# details on the mapping from the electronic Hamiltonian of a molecule to a +# qubit Hamiltonian can be found in the +# :doc:`tutorial_quantum_chemistry` and :doc:`tutorial_vqe` +# tutorials. +# +# We begin by downloading a selection of datasets of :math:`H_2` molecule for +# various bond lengths using the +# `PennyLane Datasets library `__: + +bonds = [0.5, 0.58, 0.7, 0.9, 1.1, 1.3, 1.5, 1.7, 1.9, 2.1] +datasets = qml.data.load("qchem", molname="H2", bondlength=bonds, basis="STO-3G") + +############################################################################## +# We can now extract the qubit Hamiltonians from these datasets for each bond length: + +hamiltonians = [d.hamiltonian for d in datasets] + +############################################################################## +# Each Hamiltonian can be written as a linear combination of fifteen tensor products of Pauli +# matrices. Let's take a look more closely at one of the Hamiltonians: + +h = hamiltonians[0] +_, h_ops = h.terms() + +print("Number of terms: {}\n".format(len(h_ops))) +for op in h_ops: + print("Measurement {} on wires {}".format(str(op), op.wires)) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Number of terms: 15 +# +# Measurement I(0) on wires Wires([0]) +# Measurement Z(0) on wires Wires([0]) +# Measurement Z(1) on wires Wires([1]) +# Measurement Z(0) @ Z(1) on wires Wires([0, 1]) +# Measurement Y(0) @ X(1) @ X(2) @ Y(3) on wires Wires([0, 1, 2, 3]) +# Measurement Y(0) @ Y(1) @ X(2) @ X(3) on wires Wires([0, 1, 2, 3]) +# Measurement X(0) @ X(1) @ Y(2) @ Y(3) on wires Wires([0, 1, 2, 3]) +# Measurement X(0) @ Y(1) @ Y(2) @ X(3) on wires Wires([0, 1, 2, 3]) +# Measurement Z(2) on wires Wires([2]) +# Measurement Z(0) @ Z(2) on wires Wires([0, 2]) +# Measurement Z(3) on wires Wires([3]) +# Measurement Z(0) @ Z(3) on wires Wires([0, 3]) +# Measurement Z(1) @ Z(2) on wires Wires([1, 2]) +# Measurement Z(1) @ Z(3) on wires Wires([1, 3]) +# Measurement Z(2) @ Z(3) on wires Wires([2, 3]) + +############################################################################## +# Defining the energy function +# ---------------------------- +# +# The fifteen Pauli terms comprising each Hamiltonian can conventionally be evaluated in a +# sequential manner: we evaluate one expectation value at a time before moving on to the next. +# However, this task is highly suited to parallelization. With access to multiple QPUs, +# we can split up evaluating the terms between the QPUs and gain an increase in processing speed. +# +# +# .. note:: +# Some of the Pauli terms commute, and so they can be evaluated in practice with fewer than +# fifteen quantum circuit runs. Nevertheless, these quantum circuit runs can still be +# parallelized to multiple QPUs. +# +# Let's suppose we have access to two quantum devices. In this tutorial we consider two +# simulators from Rigetti: ``4q-qvm`` and ``9q-square-qvm``, but we could also run on hardware +# devices from Rigetti or other providers. +# +# We can evaluate the expectation value of each Hamiltonian with eight terms run on +# one device and seven terms run on the other, as summarized by the diagram below: +# +# .. figure:: /_static/demonstration_assets/vqe_parallel/vqe_diagram.png +# :width: 65% +# :align: center +# +# To do this, start by instantiating a device for each term: + +dev1 = [qml.device("rigetti.qvm", device="4q-qvm") for _ in range(8)] +dev2 = [qml.device("rigetti.qvm", device="9q-square-qvm") for _ in range(7)] +devs = dev1 + dev2 + +############################################################################## +# .. note:: +# +# For the purposes of this demonstration, we are simulating the QPUs using the +# ``rigetti.qvm`` simulator. To run this demonstration on hardware, simply +# swap ``rigetti.qvm`` for ``rigetti.qpu`` and specify the hardware device to run on. +# +# Please refer to the `Rigetti website `__ for an up-to-date +# list on available QPUs. +# +# .. warning:: +# Rigetti's QVM and Quil Compiler services must be running for this tutorial to execute. They +# can be installed by consulting the `Rigetti documentation +# `__ or, for users with Docker, by running: +# +# .. code-block:: bash +# +# docker run -d -p 5555:5555 rigetti/quilc -R -p 5555 +# docker run -d -p 5000:5000 rigetti/qvm -S -p 5000 +# +# We must also define a circuit to prepare the ground state, which is a superposition of the +# Hartree-Fock (:math:`|1100\rangle`) and doubly-excited (:math:`|0011\rangle`) configurations. +# The simple circuit below is able to prepare states of the form :math:`\alpha |1100\rangle + +# \beta |0011\rangle` and hence encode the ground state wave function of the hydrogen molecule. The +# circuit has a single free parameter, which controls a Y-rotation on the third qubit. + + +def circuit(param, H): + qml.BasisState(np.array([1, 1, 0, 0], requires_grad=False), wires=[0, 1, 2, 3]) + qml.RY(param, wires=2) + qml.CNOT(wires=[2, 3]) + qml.CNOT(wires=[2, 0]) + qml.CNOT(wires=[3, 1]) + return qml.expval(H) + + +############################################################################## +# The ground state for each inter-atomic distance is characterized by a different Y-rotation angle. +# The values of these Y-rotations can be found by minimizing the ground state energy as outlined in +# :doc:`tutorial_vqe`. In this tutorial, we load pre-optimized rotations and focus on +# comparing the speed of evaluating the potential energy surface with sequential and parallel +# evaluation. These parameters can be downloaded by clicking :download:`here +# <../_static/demonstration_assets/vqe_parallel/RY_params.npy>`. + +params = np.load("vqe_parallel/RY_params.npy") + +############################################################################## +# Calculating the potential energy surface +# ---------------------------------------- +# The most vanilla execution of these 10 energy surfaces is using the standard PennyLane functionalities by executing the QNodes. +# Internally, this creates a measurement for each term in the Hamiltonian that are then sequentially computed. + +print("Evaluating the potential energy surface sequentially") +t0 = time.time() + +energies_seq = [] +for i, (h, param) in enumerate(zip(hamiltonians, params)): + print( + f"{i+1} / {len(bonds)}: Sequential execution; Running for inter-atomic distance {bonds[i]} Å" + ) + energies_seq.append(qml.QNode(circuit, devs[0])(param, h)) + +dt_seq = time.time() - t0 + +print(f"Evaluation time: {dt_seq:.2f} s") + +############################################################################## +# We can parallelize the individual evaluations using ``dask`` in the following way: We take the 15 terms of the Hamiltonian and +# distribute them to the 15 devices in ``devs``. This evaluation is delayed using ``dask.delayed`` and later computed +# in parallel using ``dask.compute``, which asynchronously executes the delayed objects in ``results``. + + +def compute_energy_parallel(H, devs, param): + H_coeffs, H_ops = H.terms() + assert len(H_ops) == len(devs) + results = [] + + for i in range(len(H_ops)): + qnode = qml.QNode(circuit, devs[i]) + results.append(dask.delayed(qnode)(param, H_ops[i])) + + results = dask.compute(*results, scheduler="threads") + result = sum(c * r for c, r in zip(H_coeffs, results)) + return result + + +############################################################################## +# We can now compute all 10 samples from the energy surface sequentially, where each execution is making use of +# parallel device execution. Curiously, in this example the overhead from doing so outweighs the speed-up +# and the execution is slower than standard execution using ``qml.expval``. For different circuits and +# different Hamiltonians, however, parallelization may provide significant speed-ups. + +print("Evaluating the potential energy surface in parallel") +t0 = time.time() + +energies_par = [] +for i, (h, param) in enumerate(zip(hamiltonians, params)): + print( + f"{i+1} / {len(bonds)}: Parallel execution; Running for inter-atomic distance {bonds[i]} Å" + ) + energies_par.append(compute_energy_parallel(h, devs, param)) + +dt_par = time.time() - t0 + +print(f"Evaluation time: {dt_par:.2f} s") + + +############################################################################## +# We can improve this procedure further by optimizing the measurement. Currently, we are measuring each term of the Hamiltonian +# in a separate measurement. This is not necessary as there are sub-groups of commuting terms in the Hamiltonian that can be measured +# simultaneously. We can utilize the grouping function :func:`~.pennylane.pauli.group_observables` to generate few measurements that +# are executed in parallel: + + +def compute_energy_parallel_optimized(H, devs, param): + H_coeffs, H_ops = H.terms() + assert len(H_ops) == len(devs) + results = [] + + obs_groupings, coeffs_groupings = qml.pauli.group_observables( + H_ops, H_coeffs, "qwc" + ) + + for i, (obs, coeffs) in enumerate(zip(obs_groupings, coeffs_groupings)): + H_part = qml.Hamiltonian(coeffs, obs) + qnode = qml.QNode(circuit, devs[i]) + results.append(dask.delayed(qnode)(param, H_part)) + + result = qml.math.sum(dask.compute(*results, scheduler="threads")) + return result + +print( + "Evaluating the potential energy surface in parallel with measurement optimization" +) +t0 = time.time() + +energies_par_opt = [] +for i, (h, param) in enumerate(zip(hamiltonians, params)): + print( + f"{i+1} / {len(bonds)}: Parallel execution and measurement optimization; Running for inter-atomic distance {bonds[i]} Å" + ) + energies_par_opt.append(compute_energy_parallel_optimized(h, devs, param)) + +dt_par_opt = time.time() - t0 + +print(f"Evaluation time: {dt_par_opt:.2f} s") + + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Evaluating the potential energy surface sequentially +# 1 / 10: Sequential execution; Running for inter-atomic distance 0.3 Å +# 2 / 10: Sequential execution; Running for inter-atomic distance 0.5 Å +# 3 / 10: Sequential execution; Running for inter-atomic distance 0.7 Å +# 4 / 10: Sequential execution; Running for inter-atomic distance 0.9 Å +# 5 / 10: Sequential execution; Running for inter-atomic distance 1.1 Å +# 6 / 10: Sequential execution; Running for inter-atomic distance 1.3 Å +# 7 / 10: Sequential execution; Running for inter-atomic distance 1.5 Å +# 8 / 10: Sequential execution; Running for inter-atomic distance 1.7 Å +# 9 / 10: Sequential execution; Running for inter-atomic distance 1.9 Å +# 10 / 10: Sequential execution; Running for inter-atomic distance 2.1 Å +# Evaluation time: 39.33 s +# +# Evaluating the potential energy surface in parallel +# 1 / 10: Parallel execution; Running for inter-atomic distance 0.3 Å +# 2 / 10: Parallel execution; Running for inter-atomic distance 0.5 Å +# 3 / 10: Parallel execution; Running for inter-atomic distance 0.7 Å +# 4 / 10: Parallel execution; Running for inter-atomic distance 0.9 Å +# 5 / 10: Parallel execution; Running for inter-atomic distance 1.1 Å +# 6 / 10: Parallel execution; Running for inter-atomic distance 1.3 Å +# 7 / 10: Parallel execution; Running for inter-atomic distance 1.5 Å +# 8 / 10: Parallel execution; Running for inter-atomic distance 1.7 Å +# 9 / 10: Parallel execution; Running for inter-atomic distance 1.9 Å +# 10 / 10: Parallel execution; Running for inter-atomic distance 2.1 Å +# Evaluation time: 73.42 s +# +# Evaluating the potential energy surface in parallel with measurement optimization +# 1 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.3 Å +# 2 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.5 Å +# 3 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.7 Å +# 4 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.9 Å +# 5 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.1 Å +# 6 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.3 Å +# 7 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.5 Å +# 8 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.7 Å +# 9 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.9 Å +# 10 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 2.1 Å +# Evaluation time: 26.51 s + + +############################################################################## +# We have seen how Hamiltonian measurements can be parallelized and optimized at the same time. + +print("Speed up: {0:.2f}".format(dt_seq / dt_par_opt)) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Speed up: 1.48 + +############################################################################## +# To conclude the tutorial, let's plot the calculated +# potential energy surfaces: + +np.savez( + "vqe_parallel", + energies_seq=energies_seq, + energies_par=energies_par, + energies_par_opt=energies_par_opt, +) + +plt.plot( + bonds, energies_seq, linewidth=2.2, marker="d", color="green", label="sequential" +) +plt.plot(bonds, energies_par, linewidth=2.2, marker="o", color="red", label="parallel") +plt.plot( + bonds, + energies_par_opt, + linewidth=2.2, + marker="d", + color="blue", + label="paralell and optimized", +) +plt.legend(fontsize=12) +plt.title("Potential energy surface for molecular hydrogen", fontsize=12) +plt.xlabel("Atomic separation (Å)", fontsize=16) +plt.ylabel("Ground state energy (Ha)", fontsize=16) +plt.grid(True) + +############################################################################## +# .. figure:: /_static/demonstration_assets/vqe_parallel/vqe_parallel_001.png +# :width: 80% +# :align: center +# + +############################################################################## +# These surfaces overlap, with any variation due to the limited number of shots used to evaluate the +# expectation values in the ``rigetti.qvm`` device (we are using the default value of +# ``shots=1024``). + +############################################################################## +# About the author +# ---------------- +# diff --git a/notebook_converter/notebook_to_demo.py b/notebook_converter/notebook_to_demo.py old mode 100755 new mode 100644 diff --git a/sg_execution_times.rst b/sg_execution_times.rst new file mode 100644 index 0000000000..3c1cd012f9 --- /dev/null +++ b/sg_execution_times.rst @@ -0,0 +1,568 @@ + +:orphan: + +.. _sphx_glr_sg_execution_times: + + +Computation times +================= +**03:09.592** total execution time for 178 files **from all galleries**: + +.. container:: + + .. raw:: html + + + + + + + + .. list-table:: + :header-rows: 1 + :class: table table-striped sg-datatable + + * - Example + - Time + - Mem (MB) + * - :ref:`sphx_glr_demos_tutorial_quantum_phase_transitions.py` (``demonstrations\tutorial_quantum_phase_transitions.py``) + - 03:09.592 + - 0.0 + * - :ref:`sphx_glr_demos_adjoint_diff_benchmarking.py` (``demonstrations\adjoint_diff_benchmarking.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_ahs_aquila.py` (``demonstrations\ahs_aquila.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_braket-parallel-gradients.py` (``demonstrations\braket-parallel-gradients.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_circuits_as_fourier_series.py` (``demonstrations\circuits_as_fourier_series.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_covalent_cloud_gpu.py` (``demonstrations\covalent_cloud_gpu.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_ensemble_multi_qpu.py` (``demonstrations\ensemble_multi_qpu.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_function_fitting_qsp.py` (``demonstrations\function_fitting_qsp.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_gbs.py` (``demonstrations\gbs.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_getting_started_with_hybrid_jobs.py` (``demonstrations\getting_started_with_hybrid_jobs.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_gqe_training.py` (``demonstrations\gqe_training.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_how_to_catalyst_lightning_gpu.py` (``demonstrations\how_to_catalyst_lightning_gpu.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_how_to_use_qiskit1_with_pennylane.py` (``demonstrations\how_to_use_qiskit1_with_pennylane.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_ibm_pennylane.py` (``demonstrations\ibm_pennylane.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_learning2learn.py` (``demonstrations\learning2learn.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_linear_equations_hhl_qrisp_catalyst.py` (``demonstrations\linear_equations_hhl_qrisp_catalyst.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_ml_classical_shadows.py` (``demonstrations\ml_classical_shadows.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_oqc_pulse.py` (``demonstrations\oqc_pulse.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_plugins_hybrid.py` (``demonstrations\plugins_hybrid.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_pytorch_noise.py` (``demonstrations\pytorch_noise.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_qnn_module_tf.py` (``demonstrations\qnn_module_tf.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_qnspsa.py` (``demonstrations\qnspsa.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_qonn.py` (``demonstrations\qonn.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_qrack.py` (``demonstrations\qrack.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_qsim_beyond_classical.py` (``demonstrations\qsim_beyond_classical.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_quantum_neural_net.py` (``demonstrations\quantum_neural_net.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_quantum_volume.py` (``demonstrations\quantum_volume.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt.py` (``demonstrations\tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_How_to_optimize_QML_model_using_JAX_and_Optax.py` (``demonstrations\tutorial_How_to_optimize_QML_model_using_JAX_and_Optax.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax.py` (``demonstrations\tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_How_to_simulate_quantum_circuits_with_tensor_networks.py` (``demonstrations\tutorial_How_to_simulate_quantum_circuits_with_tensor_networks.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_QGAN.py` (``demonstrations\tutorial_QGAN.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_QUBO.py` (``demonstrations\tutorial_QUBO.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_adaptive_circuits.py` (``demonstrations\tutorial_adaptive_circuits.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_adjoint_diff.py` (``demonstrations\tutorial_adjoint_diff.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_adversarial_attacks_QML.py` (``demonstrations\tutorial_adversarial_attacks_QML.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_annni.py` (``demonstrations\tutorial_annni.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_apply_qsvt.py` (``demonstrations\tutorial_apply_qsvt.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_backprop.py` (``demonstrations\tutorial_backprop.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_barren_gadgets.py` (``demonstrations\tutorial_barren_gadgets.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_barren_plateaus.py` (``demonstrations\tutorial_barren_plateaus.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_block_encoding.py` (``demonstrations\tutorial_block_encoding.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_bluequbit.py` (``demonstrations\tutorial_bluequbit.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_chemical_reactions.py` (``demonstrations\tutorial_chemical_reactions.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_circuit_compilation.py` (``demonstrations\tutorial_circuit_compilation.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_classical_expval_estimation.py` (``demonstrations\tutorial_classical_expval_estimation.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_classical_kernels.py` (``demonstrations\tutorial_classical_kernels.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_classical_shadows.py` (``demonstrations\tutorial_classical_shadows.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_classically_boosted_vqe.py` (``demonstrations\tutorial_classically_boosted_vqe.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_clifford_circuit_simulations.py` (``demonstrations\tutorial_clifford_circuit_simulations.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_coherent_vqls.py` (``demonstrations\tutorial_coherent_vqls.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_constant_depth_mps_prep.py` (``demonstrations\tutorial_constant_depth_mps_prep.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_contextuality.py` (``demonstrations\tutorial_contextuality.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_data_reuploading_classifier.py` (``demonstrations\tutorial_data_reuploading_classifier.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_diffable-mitigation.py` (``demonstrations\tutorial_diffable-mitigation.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_diffable_shadows.py` (``demonstrations\tutorial_diffable_shadows.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_differentiable_HF.py` (``demonstrations\tutorial_differentiable_HF.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_doubly_stochastic.py` (``demonstrations\tutorial_doubly_stochastic.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_eqnn_force_field.py` (``demonstrations\tutorial_eqnn_force_field.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_equivariant_graph_embedding.py` (``demonstrations\tutorial_equivariant_graph_embedding.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_error_mitigation.py` (``demonstrations\tutorial_error_mitigation.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_error_prop.py` (``demonstrations\tutorial_error_prop.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_expressivity_fourier_series.py` (``demonstrations\tutorial_expressivity_fourier_series.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_falqon.py` (``demonstrations\tutorial_falqon.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_fermionic_operators.py` (``demonstrations\tutorial_fermionic_operators.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition.py` (``demonstrations\tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_game_of_surface_codes.py` (``demonstrations\tutorial_game_of_surface_codes.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_gaussian_transformation.py` (``demonstrations\tutorial_gaussian_transformation.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_general_parshift.py` (``demonstrations\tutorial_general_parshift.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_geometric_qml.py` (``demonstrations\tutorial_geometric_qml.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_givens_rotations.py` (``demonstrations\tutorial_givens_rotations.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_grovers_algorithm.py` (``demonstrations\tutorial_grovers_algorithm.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_guide_to_pennylane_knowing_qiskit.py` (``demonstrations\tutorial_guide_to_pennylane_knowing_qiskit.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_haar_measure.py` (``demonstrations\tutorial_haar_measure.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_here_comes_the_sun.py` (``demonstrations\tutorial_here_comes_the_sun.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_how_to_build_compressed_double_factorized_hamiltonians.py` (``demonstrations\tutorial_how_to_build_compressed_double_factorized_hamiltonians.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_how_to_build_spin_hamiltonians.py` (``demonstrations\tutorial_how_to_build_spin_hamiltonians.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_how_to_collect_mcm_stats.py` (``demonstrations\tutorial_how_to_collect_mcm_stats.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_how_to_create_dynamic_mcm_circuits.py` (``demonstrations\tutorial_how_to_create_dynamic_mcm_circuits.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_how_to_import_qiskit_noise_models.py` (``demonstrations\tutorial_how_to_import_qiskit_noise_models.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst.py` (``demonstrations\tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_how_to_use_noise_models.py` (``demonstrations\tutorial_how_to_use_noise_models.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_how_to_use_quantum_arithmetic_operators.py` (``demonstrations\tutorial_how_to_use_quantum_arithmetic_operators.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_how_to_use_registers.py` (``demonstrations\tutorial_how_to_use_registers.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_implicit_diff_susceptibility.py` (``demonstrations\tutorial_implicit_diff_susceptibility.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_initial_state_preparation.py` (``demonstrations\tutorial_initial_state_preparation.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_intro_amplitude_amplification.py` (``demonstrations\tutorial_intro_amplitude_amplification.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_intro_qrom.py` (``demonstrations\tutorial_intro_qrom.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_intro_qsvt.py` (``demonstrations\tutorial_intro_qsvt.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_iqp_circuit_optimization_jax.py` (``demonstrations\tutorial_iqp_circuit_optimization_jax.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_isingmodel_PyTorch.py` (``demonstrations\tutorial_isingmodel_PyTorch.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_jax_transformations.py` (``demonstrations\tutorial_jax_transformations.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_kak_decomposition.py` (``demonstrations\tutorial_kak_decomposition.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_kernel_based_training.py` (``demonstrations\tutorial_kernel_based_training.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_kernels_module.py` (``demonstrations\tutorial_kernels_module.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_lcu_blockencoding.py` (``demonstrations\tutorial_lcu_blockencoding.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_learning_dynamics_incoherently.py` (``demonstrations\tutorial_learning_dynamics_incoherently.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_learning_few_data.py` (``demonstrations\tutorial_learning_few_data.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_learning_from_experiments.py` (``demonstrations\tutorial_learning_from_experiments.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_learningshallow.py` (``demonstrations\tutorial_learningshallow.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_liealgebra.py` (``demonstrations\tutorial_liealgebra.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_liesim.py` (``demonstrations\tutorial_liesim.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_liesim_extension.py` (``demonstrations\tutorial_liesim_extension.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_local_cost_functions.py` (``demonstrations\tutorial_local_cost_functions.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_magic_state_distillation.py` (``demonstrations\tutorial_magic_state_distillation.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_mapping.py` (``demonstrations\tutorial_mapping.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_mbqc.py` (``demonstrations\tutorial_mbqc.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_mcm_introduction.py` (``demonstrations\tutorial_mcm_introduction.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_measurement_optimize.py` (``demonstrations\tutorial_measurement_optimize.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_mitigation_advantage.py` (``demonstrations\tutorial_mitigation_advantage.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_mol_geo_opt.py` (``demonstrations\tutorial_mol_geo_opt.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_mps.py` (``demonstrations\tutorial_mps.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_multiclass_classification.py` (``demonstrations\tutorial_multiclass_classification.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_neutral_atoms.py` (``demonstrations\tutorial_neutral_atoms.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_noisy_circuit_optimization.py` (``demonstrations\tutorial_noisy_circuit_optimization.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_noisy_circuits.py` (``demonstrations\tutorial_noisy_circuits.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_odegen.py` (``demonstrations\tutorial_odegen.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_optimal_control.py` (``demonstrations\tutorial_optimal_control.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_pasqal.py` (``demonstrations\tutorial_pasqal.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_period_finding.py` (``demonstrations\tutorial_period_finding.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_phase_kickback.py` (``demonstrations\tutorial_phase_kickback.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_photonics.py` (``demonstrations\tutorial_photonics.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_post-variational_quantum_neural_networks.py` (``demonstrations\tutorial_post-variational_quantum_neural_networks.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_pulse_programming101.py` (``demonstrations\tutorial_pulse_programming101.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_qaoa_intro.py` (``demonstrations\tutorial_qaoa_intro.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_qaoa_maxcut.py` (``demonstrations\tutorial_qaoa_maxcut.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_qcbm.py` (``demonstrations\tutorial_qcbm.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_qchem_external.py` (``demonstrations\tutorial_qchem_external.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_qft.py` (``demonstrations\tutorial_qft.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_qft_arithmetics.py` (``demonstrations\tutorial_qft_arithmetics.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_qgrnn.py` (``demonstrations\tutorial_qgrnn.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_qjit_compile_grovers_algorithm_with_catalyst.py` (``demonstrations\tutorial_qjit_compile_grovers_algorithm_with_catalyst.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_qnn_module_torch.py` (``demonstrations\tutorial_qnn_module_torch.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_qnn_multivariate_regression.py` (``demonstrations\tutorial_qnn_multivariate_regression.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_qpe.py` (``demonstrations\tutorial_qpe.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_qsvt_hardware.py` (``demonstrations\tutorial_qsvt_hardware.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_quantum_analytic_descent.py` (``demonstrations\tutorial_quantum_analytic_descent.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_quantum_chemistry.py` (``demonstrations\tutorial_quantum_chemistry.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_quantum_circuit_cutting.py` (``demonstrations\tutorial_quantum_circuit_cutting.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_quantum_dropout.py` (``demonstrations\tutorial_quantum_dropout.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_quantum_gans.py` (``demonstrations\tutorial_quantum_gans.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_quantum_metrology.py` (``demonstrations\tutorial_quantum_metrology.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_quantum_natural_gradient.py` (``demonstrations\tutorial_quantum_natural_gradient.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_quantum_transfer_learning.py` (``demonstrations\tutorial_quantum_transfer_learning.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_quanvolution.py` (``demonstrations\tutorial_quanvolution.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_qubit_rotation.py` (``demonstrations\tutorial_qubit_rotation.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_qubit_tapering.py` (``demonstrations\tutorial_qubit_tapering.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_qubitization.py` (``demonstrations\tutorial_qubitization.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_qutrits_bernstein_vazirani.py` (``demonstrations\tutorial_qutrits_bernstein_vazirani.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_resource_estimation.py` (``demonstrations\tutorial_resource_estimation.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_rl_pulse.py` (``demonstrations\tutorial_rl_pulse.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_rosalin.py` (``demonstrations\tutorial_rosalin.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_rotoselect.py` (``demonstrations\tutorial_rotoselect.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_sc_qubits.py` (``demonstrations\tutorial_sc_qubits.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_shadow_hamiltonian_simulation.py` (``demonstrations\tutorial_shadow_hamiltonian_simulation.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_shors_algorithm_catalyst.py` (``demonstrations\tutorial_shors_algorithm_catalyst.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_spsa.py` (``demonstrations\tutorial_spsa.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_state_preparation.py` (``demonstrations\tutorial_state_preparation.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_stochastic_parameter_shift.py` (``demonstrations\tutorial_stochastic_parameter_shift.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_teleportation.py` (``demonstrations\tutorial_teleportation.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_tensor_network_basics.py` (``demonstrations\tutorial_tensor_network_basics.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_testing_symmetry.py` (``demonstrations\tutorial_testing_symmetry.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_tn_circuits.py` (``demonstrations\tutorial_tn_circuits.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_toric_code.py` (``demonstrations\tutorial_toric_code.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_trapped_ions.py` (``demonstrations\tutorial_trapped_ions.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_unitary_designs.py` (``demonstrations\tutorial_unitary_designs.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_unitary_synthesis_kak.py` (``demonstrations\tutorial_unitary_synthesis_kak.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_univariate_qvr.py` (``demonstrations\tutorial_univariate_qvr.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_variational_classifier.py` (``demonstrations\tutorial_variational_classifier.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_vqe.py` (``demonstrations\tutorial_vqe.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_vqe_qng.py` (``demonstrations\tutorial_vqe_qng.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_vqe_spin_sectors.py` (``demonstrations\tutorial_vqe_spin_sectors.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_vqe_vqd.py` (``demonstrations\tutorial_vqe_vqd.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_vqls.py` (``demonstrations\tutorial_vqls.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_vqt.py` (``demonstrations\tutorial_vqt.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_zne_catalyst.py` (``demonstrations\tutorial_zne_catalyst.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_tutorial_zx_calculus.py` (``demonstrations\tutorial_zx_calculus.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_demos_vqe_parallel.py` (``demonstrations\vqe_parallel.py``) + - 00:00.000 + - 0.0 diff --git a/test-results/sphinx-gallery/junit.xml b/test-results/sphinx-gallery/junit.xml new file mode 100644 index 0000000000..7fd1edd634 --- /dev/null +++ b/test-results/sphinx-gallery/junit.xml @@ -0,0 +1 @@ + \ No newline at end of file From 34efe8ccdd6c82bd8742c39ebd68ba978f00b156 Mon Sep 17 00:00:00 2001 From: Damian Pope Date: Wed, 9 Jul 2025 23:47:16 -0400 Subject: [PATCH 2/4] Revert "Added new tutorial on quantum phase transitions" This reverts commit 9689b01eac7319c8303adc32a2a8334d3bf804e4.# Please enter the commit message for your changes. Lines starting# #committer: Damian Pope --- .pylintrc | 4 +- _static/authors/Damian_Pope.png | Bin 69001 -> 0 bytes _static/authors/Tirth_Shah.jpg | Bin 180784 -> 0 bytes _static/authors/damian_pope.txt | 4 - _static/authors/tirth_shah.txt | 4 - _static/css/light-slider.css | 186 +- ..._model_using_JAX_and_JAXopt_2024-01-16.png | Bin 0 -> 111175 bytes ...L_model_using_JAX_and_Optax_2024-01-16.png | Bin 0 -> 96621 bytes .../barren_gadgets/barren_gadgets.py | 260 +-- .../barren_gadgets/layered_ansatz.py | 108 +- .../Fig_1_Ising_chain.png | Bin 12045 -> 0 bytes .../Fig_2_transverse_Ising.png | Bin 18196 -> 0 bytes .../Fig_3_ground_state_J_large.png | Bin 32872 -> 0 bytes .../Fig_4_ground_state_h_large.png | Bin 36408 -> 0 bytes .../Fig_5_2D_Ising_model.png | Bin 24708 -> 0 bytes .../spsa/spsa_thumbnail.png | Bin _static/thumbs/NOON.png | Bin 0 -> 82085 bytes _static/thumbs/bloch.png | Bin 0 -> 14494 bytes _static/thumbs/classifier_output_59_0.png | Bin 0 -> 72226 bytes _static/thumbs/gauss-circuit.png | Bin 0 -> 13467 bytes _static/thumbs/isingspins.png | Bin 0 -> 93231 bytes _static/thumbs/photon_redirection.png | Bin 0 -> 7304 bytes _static/thumbs/qaoa_maxcut_partition.png | Bin 0 -> 42223 bytes _static/thumbs/qgan3.png | Bin 0 -> 19379 bytes _static/thumbs/qng_optimization.png | Bin 0 -> 42026 bytes _static/thumbs/qnn_output_28_0.png | Bin 0 -> 21546 bytes _static/thumbs/rotoselect_structure.png | Bin 0 -> 7147 bytes _static/thumbs/spsa_mntn.png | Bin 0 -> 38903 bytes _static/thumbs/surface.png | Bin 0 -> 61458 bytes _static/thumbs/universal_dnn.png | Bin 0 -> 40857 bytes _static/thumbs/vqls_zoom.png | Bin 0 -> 12068 bytes _templates/filters.html | 230 +-- _templates/page.html | 60 +- .../barren_gadgets/barren_gadgets.py | 260 +-- .../barren_gadgets/layered_ansatz.py | 108 +- demonstrations/ensemble_multi_qpu.py | 1162 ++++++------ demonstrations/gbs.py | 976 +++++----- demonstrations/tutorial_adaptive_circuits.py | 852 ++++----- ...rial_adversarial_attacks_QML.metadata.json | 158 +- demonstrations/tutorial_backprop.py | 912 ++++----- demonstrations/tutorial_barren_gadgets.py | 778 ++++---- demonstrations/tutorial_differentiable_HF.py | 786 ++++---- demonstrations/tutorial_doubly_stochastic.py | 812 ++++---- .../tutorial_fermionic_operators.py | 462 ++--- demonstrations/tutorial_givens_rotations.py | 1012 +++++----- demonstrations/tutorial_haar_measure.py | 1622 ++++++++-------- demonstrations/tutorial_here_comes_the_sun.py | 1152 +++++------ .../tutorial_initial_state_preparation.py | 728 +++---- .../tutorial_jax_transformations.py | 622 +++--- demonstrations/tutorial_mapping.py | 696 +++---- .../tutorial_measurement_optimize.py | 1688 ++++++++--------- demonstrations/tutorial_pasqal.py | 722 +++---- demonstrations/tutorial_qchem_external.py | 460 ++--- demonstrations/tutorial_qft_arithmetics.py | 866 ++++----- demonstrations/tutorial_quantum_chemistry.py | 662 +++---- demonstrations/tutorial_quantum_dropout.py | 1404 +++++++------- .../tutorial_quantum_natural_gradient.py | 996 +++++----- ...al_quantum_phase_transitions.metadata.json | 138 -- .../tutorial_quantum_phase_transitions.py | 1156 ----------- .../tutorial_quantum_transfer_learning.py | 1222 ++++++------ demonstrations/tutorial_qubit_tapering.py | 660 +++---- .../tutorial_qutrits_bernstein_vazirani.py | 818 ++++---- .../tutorial_resource_estimation.py | 642 +++---- demonstrations/tutorial_rosalin.py | 1330 ++++++------- demonstrations/vqe_parallel.py | 786 ++++---- demonstrations_v2/ensemble_multi_qpu/demo.py | 1162 ++++++------ demonstrations_v2/gbs/demo.py | 976 +++++----- .../tutorial_adaptive_circuits/demo.py | 852 ++++----- .../metadata.json | 158 +- demonstrations_v2/tutorial_backprop/demo.py | 912 ++++----- .../barren_gadgets/barren_gadgets.py | 260 +-- .../barren_gadgets/layered_ansatz.py | 108 +- .../tutorial_barren_gadgets/demo.py | 778 ++++---- .../tutorial_differentiable_HF/demo.py | 786 ++++---- .../tutorial_doubly_stochastic/demo.py | 812 ++++---- .../tutorial_fermionic_operators/demo.py | 462 ++--- .../tutorial_givens_rotations/demo.py | 1012 +++++----- .../tutorial_haar_measure/demo.py | 1622 ++++++++-------- .../tutorial_here_comes_the_sun/demo.py | 1152 +++++------ .../demo.py | 728 +++---- .../tutorial_jax_transformations/demo.py | 622 +++--- demonstrations_v2/tutorial_mapping/demo.py | 696 +++---- .../tutorial_measurement_optimize/demo.py | 1688 ++++++++--------- demonstrations_v2/tutorial_pasqal/demo.py | 722 +++---- .../tutorial_qchem_external/demo.py | 460 ++--- .../tutorial_qft_arithmetics/demo.py | 866 ++++----- .../tutorial_quantum_chemistry/demo.py | 662 +++---- .../tutorial_quantum_dropout/demo.py | 1404 +++++++------- .../tutorial_quantum_natural_gradient/demo.py | 996 +++++----- .../demo.py | 1240 ++++++------ .../tutorial_qubit_tapering/demo.py | 660 +++---- .../demo.py | 818 ++++---- .../tutorial_resource_estimation/demo.py | 642 +++---- demonstrations_v2/tutorial_rosalin/demo.py | 1330 ++++++------- demonstrations_v2/vqe_parallel/demo.py | 786 ++++---- notebook_converter/notebook_to_demo.py | 0 sg_execution_times.rst | 568 ------ test-results/sphinx-gallery/junit.xml | 1 - 98 files changed, 25787 insertions(+), 27658 deletions(-) delete mode 100644 _static/authors/Damian_Pope.png delete mode 100644 _static/authors/Tirth_Shah.jpg delete mode 100644 _static/authors/damian_pope.txt delete mode 100644 _static/authors/tirth_shah.txt create mode 100644 _static/demonstration_assets/How_to_optimize_QML_model_using_JAX_and_JAXopt/socialthumbnail_large_How_to_optimize_QML_model_using_JAX_and_JAXopt_2024-01-16.png create mode 100644 _static/demonstration_assets/How_to_optimize_QML_model_using_JAX_and_Optax/socialsthumbnail_large_How_to_optimize_QML_model_using_JAX_and_Optax_2024-01-16.png delete mode 100644 _static/demonstration_assets/quantum_phase_transitions/Fig_1_Ising_chain.png delete mode 100644 _static/demonstration_assets/quantum_phase_transitions/Fig_2_transverse_Ising.png delete mode 100644 _static/demonstration_assets/quantum_phase_transitions/Fig_3_ground_state_J_large.png delete mode 100644 _static/demonstration_assets/quantum_phase_transitions/Fig_4_ground_state_h_large.png delete mode 100644 _static/demonstration_assets/quantum_phase_transitions/Fig_5_2D_Ising_model.png mode change 100644 => 100755 _static/demonstration_assets/spsa/spsa_thumbnail.png create mode 100644 _static/thumbs/NOON.png create mode 100644 _static/thumbs/bloch.png create mode 100644 _static/thumbs/classifier_output_59_0.png create mode 100644 _static/thumbs/gauss-circuit.png create mode 100644 _static/thumbs/isingspins.png create mode 100644 _static/thumbs/photon_redirection.png create mode 100644 _static/thumbs/qaoa_maxcut_partition.png create mode 100644 _static/thumbs/qgan3.png create mode 100644 _static/thumbs/qng_optimization.png create mode 100644 _static/thumbs/qnn_output_28_0.png create mode 100644 _static/thumbs/rotoselect_structure.png create mode 100644 _static/thumbs/spsa_mntn.png create mode 100644 _static/thumbs/surface.png create mode 100644 _static/thumbs/universal_dnn.png create mode 100644 _static/thumbs/vqls_zoom.png delete mode 100644 demonstrations/tutorial_quantum_phase_transitions.metadata.json delete mode 100644 demonstrations/tutorial_quantum_phase_transitions.py mode change 100644 => 100755 notebook_converter/notebook_to_demo.py delete mode 100644 sg_execution_times.rst delete mode 100644 test-results/sphinx-gallery/junit.xml diff --git a/.pylintrc b/.pylintrc index 8d0c91f1ce..be17d653e2 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,2 +1,2 @@ -[MESSAGES CONTROL] -disable=invalid-name,missing-function-docstring,unused-argument +[MESSAGES CONTROL] +disable=invalid-name,missing-function-docstring,unused-argument diff --git a/_static/authors/Damian_Pope.png b/_static/authors/Damian_Pope.png deleted file mode 100644 index 7b9e395e1e9471c4836bc65e44dc548966c8deb1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 69001 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

*vV9&vLsh_IZvSmR3R&RPn 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

PUCV$3P1T~E59wr#L`*kQlh%m1N<<$LspoA!YH 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

3I@dXdTXRHcyY8rxGErD0Lvf81enN$d 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 z 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 LP9?&0Eu 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 DDz6v= diff --git a/_static/authors/damian_pope.txt b/_static/authors/damian_pope.txt deleted file mode 100644 index 793dc4ee30..0000000000 --- a/_static/authors/damian_pope.txt +++ /dev/null @@ -1,4 +0,0 @@ -.. bio:: Damian Pope - :photo: ../_static/authors/Damian_Pope.png - - Damian has a PhD in theoretical quantum information. He works at Perimeter Institute for Theoretical Physics, Canada in Educational Outreach. He’s interested in quantum computing education, quantum error correction, and using quantum computers in condensed matter physics, high-energy physics and cosmology. \ No newline at end of file diff --git a/_static/authors/tirth_shah.txt b/_static/authors/tirth_shah.txt deleted file mode 100644 index 3139b4d7a1..0000000000 --- a/_static/authors/tirth_shah.txt +++ /dev/null @@ -1,4 +0,0 @@ -.. bio:: Tirth Shah - :photo: ../_static/authors/Tirth_Shah.jpg - - Tirth is an undergraduate student at the University of Alberta studying Physics and Math. His interests include quantum computing, black hole physics, and the application of machine learning to space science. \ No newline at end of file diff --git a/_static/css/light-slider.css b/_static/css/light-slider.css index 7826c4c0e0..0dac390d5f 100644 --- a/_static/css/light-slider.css +++ b/_static/css/light-slider.css @@ -1,93 +1,93 @@ -.light-slider { - height: 100%!important; -} - -.light-slider li { - margin-bottom: 20px; - margin-top: 20px; -} - -.light-slider .card h4 { - margin-top: 10px; - font-weight: 300; - color: #535353; -} - -.light-slider .card { - margin-left: 30px; - margin-right: 30px; - overflow: hidden; - min-height: 430px; -} - -.light-slider .card .card-body { - z-index: 2; - /*border-radius: 30% 70% 75% 25% / 30% 52% 48% 70% ;*/ - box-sizing: content-box; - padding-top: 20px; - position: relative; - background-image: linear-gradient(to right,#fff6ea 0,#ffefe9 100%); -} - -.lSSlideOuter { - margin-left: calc(100% - 980px); - width: 150%; -} - -.light-slider .card .card-body:after { - content: ""; - position: absolute; - left: 0; - top: 0; - width: 0; - height: 0; - border-top: 20px solid #fff; - border-left: 200px solid transparent; - border-right: 150px solid transparent; -} - -.light-slider .card:hover { - box-shadow: 0 2px 5px 0 rgba(0,0,0,.05),0 2px 25px 2px rgba(0,0,0,.05)!important; - border: 0; - transition-duration: 0.4s; - border-radius: 10px!important; -} - - -.light-slider .card img { - transform: scale(1); - transition-duration: 1s; -} - -.light-slider p { - font-weight: 300; - color: #535353; -} - -.light-slider .card:hover img { - transform: scale(1.10); - transition-duration: 1s; -} - -.lSPrev { - left: -1px !important; -} - -.lSNext { - left: calc(100% - 19px); -} - -@media screen and (max-width: 768px) { - .lSSlideOuter { - margin-left: 0px !important; - width: 100% !important; - } -} - - -@media screen and (max-width: 1400px) and (min-width: 768px){ - .lSSlideOuter { - margin-left: 0px !important; - width: 100% !important; - } -} +.light-slider { + height: 100%!important; +} + +.light-slider li { + margin-bottom: 20px; + margin-top: 20px; +} + +.light-slider .card h4 { + margin-top: 10px; + font-weight: 300; + color: #535353; +} + +.light-slider .card { + margin-left: 30px; + margin-right: 30px; + overflow: hidden; + min-height: 430px; +} + +.light-slider .card .card-body { + z-index: 2; + /*border-radius: 30% 70% 75% 25% / 30% 52% 48% 70% ;*/ + box-sizing: content-box; + padding-top: 20px; + position: relative; + background-image: linear-gradient(to right,#fff6ea 0,#ffefe9 100%); +} + +.lSSlideOuter { + margin-left: calc(100% - 980px); + width: 150%; +} + +.light-slider .card .card-body:after { + content: ""; + position: absolute; + left: 0; + top: 0; + width: 0; + height: 0; + border-top: 20px solid #fff; + border-left: 200px solid transparent; + border-right: 150px solid transparent; +} + +.light-slider .card:hover { + box-shadow: 0 2px 5px 0 rgba(0,0,0,.05),0 2px 25px 2px rgba(0,0,0,.05)!important; + border: 0; + transition-duration: 0.4s; + border-radius: 10px!important; +} + + +.light-slider .card img { + transform: scale(1); + transition-duration: 1s; +} + +.light-slider p { + font-weight: 300; + color: #535353; +} + +.light-slider .card:hover img { + transform: scale(1.10); + transition-duration: 1s; +} + +.lSPrev { + left: -1px !important; +} + +.lSNext { + left: calc(100% - 19px); +} + +@media screen and (max-width: 768px) { + .lSSlideOuter { + margin-left: 0px !important; + width: 100% !important; + } +} + + +@media screen and (max-width: 1400px) and (min-width: 768px){ + .lSSlideOuter { + margin-left: 0px !important; + width: 100% !important; + } +} diff --git a/_static/demonstration_assets/How_to_optimize_QML_model_using_JAX_and_JAXopt/socialthumbnail_large_How_to_optimize_QML_model_using_JAX_and_JAXopt_2024-01-16.png b/_static/demonstration_assets/How_to_optimize_QML_model_using_JAX_and_JAXopt/socialthumbnail_large_How_to_optimize_QML_model_using_JAX_and_JAXopt_2024-01-16.png new file mode 100644 index 0000000000000000000000000000000000000000..54f3038d81f6ce76ae875e65c2861a68f39fc2be GIT binary patch literal 111175 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 G0sjZ)807o_ literal 0 HcmV?d00001 diff --git a/_static/demonstration_assets/How_to_optimize_QML_model_using_JAX_and_Optax/socialsthumbnail_large_How_to_optimize_QML_model_using_JAX_and_Optax_2024-01-16.png b/_static/demonstration_assets/How_to_optimize_QML_model_using_JAX_and_Optax/socialsthumbnail_large_How_to_optimize_QML_model_using_JAX_and_Optax_2024-01-16.png new file mode 100644 index 0000000000000000000000000000000000000000..7f03b489853d8dd431ce0a6aa2701c097e4a9a4e GIT binary patch literal 96621 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 T`O1Bq=qTP*yHkA2IOzWZnWC6n literal 0 HcmV?d00001 diff --git a/_static/demonstration_assets/barren_gadgets/barren_gadgets.py b/_static/demonstration_assets/barren_gadgets/barren_gadgets.py index 09d3c89df7..0e2929dc41 100644 --- a/_static/demonstration_assets/barren_gadgets/barren_gadgets.py +++ b/_static/demonstration_assets/barren_gadgets/barren_gadgets.py @@ -1,130 +1,130 @@ -import pennylane as qml -from pennylane import numpy as np - -def non_identity_obs(obs): - return [o for o in obs if not isinstance(o, qml.Identity)] - -class PerturbativeGadgets: - """ Class to generate the gadget Hamiltonian corresponding to a given - computational hamiltonian according to the gadget construction derived - by Faehrmann & Cichy - - Args: - perturbation_factor (float) : parameter controlling the magnitude of the - perturbation (aa pre-factor to \lambda_max) - """ - def __init__(self, perturbation_factor=1): - self.perturbation_factor = perturbation_factor - - def gadgetize(self, Hamiltonian, target_locality=3): - """Generation of the perturbative gadget equivalent of the given - Hamiltonian according to the proceedure in Cichy, Fährmann et al. - Args: - Hamiltonian (qml.Hamiltonian) : target Hamiltonian to decompose - into more local terms - target_locality (int > 2) : desired locality of the resulting - gadget Hamiltonian - Returns: - Hgad (qml.Hamiltonian) : gadget Hamiltonian - """ - # checking for unaccounted for situations - self.run_checks(Hamiltonian, target_locality) - computational_qubits, computational_locality, computational_terms = self.get_params(Hamiltonian) - Hamiltonian_coeffs, Hamiltonian_ops = Hamiltonian.terms() - - # total qubit count, updated progressively when adding ancillaries - total_qubits = computational_qubits - #TODO: check proper convergence guarantee - gap = 1 - perturbation_norm = np.sum(np.abs(Hamiltonian_coeffs)) \ - + computational_terms * (computational_locality - 1) - lambda_max = gap / (4 * perturbation_norm) - l = self.perturbation_factor * lambda_max - sign_correction = (-1)**(computational_locality % 2 + 1) - # creating the gadget Hamiltonian - coeffs_anc = [] - coeffs_pert = [] - obs_anc = [] - obs_pert = [] - ancillary_register_size = int(computational_locality / (target_locality - 2)) - for str_count, string in enumerate(Hamiltonian_ops): - previous_total = total_qubits - total_qubits += ancillary_register_size - # Generating the ancillary part - for anc_q in range(previous_total, total_qubits): - coeffs_anc += [0.5, -0.5] - obs_anc += [qml.Identity(anc_q), qml.PauliZ(anc_q)] - # Generating the perturbative part - for anc_q in range(ancillary_register_size): - term = qml.PauliX(previous_total+anc_q) @ qml.PauliX(previous_total+(anc_q+1)%ancillary_register_size) - term = qml.prod(term, *non_identity_obs(string.operands)[ - (target_locality-2)*anc_q:(target_locality-2)*(anc_q+1)]) - obs_pert.append(term) - coeffs_pert += [l * sign_correction * Hamiltonian_coeffs[str_count]] \ - + [l] * (ancillary_register_size - 1) - coeffs = coeffs_anc + coeffs_pert - obs = obs_anc + obs_pert - Hgad = qml.Hamiltonian(coeffs, obs) - return Hgad - - def get_params(self, Hamiltonian): - """ retrieving the parameters n, k and r from the given Hamiltonian - Args: - Hamiltonian (qml.Hamiltonian) : Hamiltonian from which to get the - relevant parameters - Returns: - computational_qubits (int) : total number of qubits acted upon by - the Hamiltonian - computational_locality (int) : maximum number of qubits acted upon - by a single term of the Hamiltonian - computational_terms (int) : number of terms in the sum - composing the Hamiltonian - """ - _, Hamiltonian_ops = Hamiltonian.terms() - # checking how many qubits the Hamiltonian acts on - computational_qubits = len(Hamiltonian.wires) - # getting the number of terms in the Hamiltonian - computational_terms = len(Hamiltonian_ops) - # getting the locality, assuming all terms have the same - computational_locality = max([len(non_identity_obs(Hamiltonian_ops[s])) - for s in range(computational_terms)]) - return computational_qubits, computational_locality, computational_terms - - def run_checks(self, Hamiltonian, target_locality): - """ method to check a few conditions for the correct application of - the methods - Args: - Hamiltonian (qml.Hamiltonian) : Hamiltonian of interest - target_locality (int > 2) : desired locality of the resulting - gadget Hamiltonian - Returns: - None - """ - _, Hamiltonian_ops = Hamiltonian.terms() - computational_qubits, computational_locality, _ = self.get_params(Hamiltonian) - computational_qubits = len(Hamiltonian.wires) - if computational_qubits != Hamiltonian.wires[-1] + 1: - raise Exception('The studied computational Hamiltonian is not acting on ' + - 'the first {} qubits. '.format(computational_qubits) + - 'Decomposition not implemented for this case') - # Check for same string lengths - localities=[] - for string in Hamiltonian_ops: - localities.append(len(non_identity_obs(string))) - if len(np.unique(localities)) > 1: - raise Exception('The given Hamiltonian has terms with different locality.' + - ' Gadgetization not implemented for this case') - # validity of the target locality given the computational locality - if target_locality < 3: - raise Exception('The target locality can not be smaller than 3') - ancillary_register_size = computational_locality / (target_locality - 2) - if int(ancillary_register_size) != ancillary_register_size: - raise Exception('The locality of the Hamiltonian and the target' + - ' locality are not compatible. The gadgetization' + - ' with "unfull" ancillary registers is not' + - ' supported yet. Please choose such that the' + - ' computational locality is divisible by the' + - ' target locality - 2') - - - +import pennylane as qml +from pennylane import numpy as np + +def non_identity_obs(obs): + return [o for o in obs if not isinstance(o, qml.Identity)] + +class PerturbativeGadgets: + """ Class to generate the gadget Hamiltonian corresponding to a given + computational hamiltonian according to the gadget construction derived + by Faehrmann & Cichy + + Args: + perturbation_factor (float) : parameter controlling the magnitude of the + perturbation (aa pre-factor to \lambda_max) + """ + def __init__(self, perturbation_factor=1): + self.perturbation_factor = perturbation_factor + + def gadgetize(self, Hamiltonian, target_locality=3): + """Generation of the perturbative gadget equivalent of the given + Hamiltonian according to the proceedure in Cichy, Fährmann et al. + Args: + Hamiltonian (qml.Hamiltonian) : target Hamiltonian to decompose + into more local terms + target_locality (int > 2) : desired locality of the resulting + gadget Hamiltonian + Returns: + Hgad (qml.Hamiltonian) : gadget Hamiltonian + """ + # checking for unaccounted for situations + self.run_checks(Hamiltonian, target_locality) + computational_qubits, computational_locality, computational_terms = self.get_params(Hamiltonian) + Hamiltonian_coeffs, Hamiltonian_ops = Hamiltonian.terms() + + # total qubit count, updated progressively when adding ancillaries + total_qubits = computational_qubits + #TODO: check proper convergence guarantee + gap = 1 + perturbation_norm = np.sum(np.abs(Hamiltonian_coeffs)) \ + + computational_terms * (computational_locality - 1) + lambda_max = gap / (4 * perturbation_norm) + l = self.perturbation_factor * lambda_max + sign_correction = (-1)**(computational_locality % 2 + 1) + # creating the gadget Hamiltonian + coeffs_anc = [] + coeffs_pert = [] + obs_anc = [] + obs_pert = [] + ancillary_register_size = int(computational_locality / (target_locality - 2)) + for str_count, string in enumerate(Hamiltonian_ops): + previous_total = total_qubits + total_qubits += ancillary_register_size + # Generating the ancillary part + for anc_q in range(previous_total, total_qubits): + coeffs_anc += [0.5, -0.5] + obs_anc += [qml.Identity(anc_q), qml.PauliZ(anc_q)] + # Generating the perturbative part + for anc_q in range(ancillary_register_size): + term = qml.PauliX(previous_total+anc_q) @ qml.PauliX(previous_total+(anc_q+1)%ancillary_register_size) + term = qml.prod(term, *non_identity_obs(string.operands)[ + (target_locality-2)*anc_q:(target_locality-2)*(anc_q+1)]) + obs_pert.append(term) + coeffs_pert += [l * sign_correction * Hamiltonian_coeffs[str_count]] \ + + [l] * (ancillary_register_size - 1) + coeffs = coeffs_anc + coeffs_pert + obs = obs_anc + obs_pert + Hgad = qml.Hamiltonian(coeffs, obs) + return Hgad + + def get_params(self, Hamiltonian): + """ retrieving the parameters n, k and r from the given Hamiltonian + Args: + Hamiltonian (qml.Hamiltonian) : Hamiltonian from which to get the + relevant parameters + Returns: + computational_qubits (int) : total number of qubits acted upon by + the Hamiltonian + computational_locality (int) : maximum number of qubits acted upon + by a single term of the Hamiltonian + computational_terms (int) : number of terms in the sum + composing the Hamiltonian + """ + _, Hamiltonian_ops = Hamiltonian.terms() + # checking how many qubits the Hamiltonian acts on + computational_qubits = len(Hamiltonian.wires) + # getting the number of terms in the Hamiltonian + computational_terms = len(Hamiltonian_ops) + # getting the locality, assuming all terms have the same + computational_locality = max([len(non_identity_obs(Hamiltonian_ops[s])) + for s in range(computational_terms)]) + return computational_qubits, computational_locality, computational_terms + + def run_checks(self, Hamiltonian, target_locality): + """ method to check a few conditions for the correct application of + the methods + Args: + Hamiltonian (qml.Hamiltonian) : Hamiltonian of interest + target_locality (int > 2) : desired locality of the resulting + gadget Hamiltonian + Returns: + None + """ + _, Hamiltonian_ops = Hamiltonian.terms() + computational_qubits, computational_locality, _ = self.get_params(Hamiltonian) + computational_qubits = len(Hamiltonian.wires) + if computational_qubits != Hamiltonian.wires[-1] + 1: + raise Exception('The studied computational Hamiltonian is not acting on ' + + 'the first {} qubits. '.format(computational_qubits) + + 'Decomposition not implemented for this case') + # Check for same string lengths + localities=[] + for string in Hamiltonian_ops: + localities.append(len(non_identity_obs(string))) + if len(np.unique(localities)) > 1: + raise Exception('The given Hamiltonian has terms with different locality.' + + ' Gadgetization not implemented for this case') + # validity of the target locality given the computational locality + if target_locality < 3: + raise Exception('The target locality can not be smaller than 3') + ancillary_register_size = computational_locality / (target_locality - 2) + if int(ancillary_register_size) != ancillary_register_size: + raise Exception('The locality of the Hamiltonian and the target' + + ' locality are not compatible. The gadgetization' + + ' with "unfull" ancillary registers is not' + + ' supported yet. Please choose such that the' + + ' computational locality is divisible by the' + + ' target locality - 2') + + + diff --git a/_static/demonstration_assets/barren_gadgets/layered_ansatz.py b/_static/demonstration_assets/barren_gadgets/layered_ansatz.py index 22f324835e..7a26d9e059 100644 --- a/_static/demonstration_assets/barren_gadgets/layered_ansatz.py +++ b/_static/demonstration_assets/barren_gadgets/layered_ansatz.py @@ -1,54 +1,54 @@ -import pennylane as qml -from pennylane import numpy as np - -""" Based on the SimplifiedTwoDesign template from pennylane -https://docs.pennylane.ai/en/latest/code/api/pennylane.SimplifiedTwoDesign.html -as proposed in `Cerezo et al. (2021) `_. -but changin the Y-rotations for a random choice of {X, Y, Z}-rotations. -""" - -def build_ansatz(initial_layer_weights, weights, wires, gate_sequence=None): - - n_layers = qml.math.shape(weights)[0] - op_list = [] - - # initial rotations - for i in range(len(wires)): - op_list.append(qml.RY(initial_layer_weights[i], wires=wires[i])) - - # generating the rotation sequence - if gate_sequence is None: - gate_sequence = generate_random_gate_sequence(qml.math.shape(weights)) - - # repeated layers - for layer in range(n_layers): - - # even layer of entanglers - even_wires = [wires[i : i + 2] for i in range(0, len(wires) - 1, 2)] - for i, wire_pair in enumerate(even_wires): - op_list.append(qml.CZ(wires=wire_pair)) - op_list.append(gate_sequence[layer, i, 0].item()(weights[layer, i, 0], wires=wire_pair[0])) - op_list.append(gate_sequence[layer, i, 1].item()(weights[layer, i, 1], wires=wire_pair[1])) - # op_list.append(qml.RX(weights[layer, i, 0], wires=wire_pair[0])) - # op_list.append(qml.RX(weights[layer, i, 1], wires=wire_pair[1])) - - # odd layer of entanglers - odd_wires = [wires[i : i + 2] for i in range(1, len(wires) - 1, 2)] - for i, wire_pair in enumerate(odd_wires): - op_list.append(qml.CZ(wires=wire_pair)) - op_list.append(gate_sequence[layer, len(wires) // 2 + i, 0].item()(weights[layer, len(wires) // 2 + i, 0], wires=wire_pair[0])) - op_list.append(gate_sequence[layer, len(wires) // 2 + i, 1].item()(weights[layer, len(wires) // 2 + i, 1], wires=wire_pair[1])) - # op_list.append(qml.RX(weights[layer, len(wires) // 2 + i, 0], wires=wire_pair[0])) - # op_list.append(qml.RX(weights[layer, len(wires) // 2 + i, 1], wires=wire_pair[1])) - - return op_list - -def generate_random_gate_sequence(shape): - gate_set = [qml.RX, qml.RY, qml.RZ] - return np.random.choice(gate_set, size=shape) - -def get_parameter_shape(n_layers, n_wires): - if n_wires == 1: - return [(n_wires,), (n_layers,)] - return [(n_wires,), (n_layers, n_wires - 1, 2)] - +import pennylane as qml +from pennylane import numpy as np + +""" Based on the SimplifiedTwoDesign template from pennylane +https://docs.pennylane.ai/en/latest/code/api/pennylane.SimplifiedTwoDesign.html +as proposed in `Cerezo et al. (2021) `_. +but changin the Y-rotations for a random choice of {X, Y, Z}-rotations. +""" + +def build_ansatz(initial_layer_weights, weights, wires, gate_sequence=None): + + n_layers = qml.math.shape(weights)[0] + op_list = [] + + # initial rotations + for i in range(len(wires)): + op_list.append(qml.RY(initial_layer_weights[i], wires=wires[i])) + + # generating the rotation sequence + if gate_sequence is None: + gate_sequence = generate_random_gate_sequence(qml.math.shape(weights)) + + # repeated layers + for layer in range(n_layers): + + # even layer of entanglers + even_wires = [wires[i : i + 2] for i in range(0, len(wires) - 1, 2)] + for i, wire_pair in enumerate(even_wires): + op_list.append(qml.CZ(wires=wire_pair)) + op_list.append(gate_sequence[layer, i, 0].item()(weights[layer, i, 0], wires=wire_pair[0])) + op_list.append(gate_sequence[layer, i, 1].item()(weights[layer, i, 1], wires=wire_pair[1])) + # op_list.append(qml.RX(weights[layer, i, 0], wires=wire_pair[0])) + # op_list.append(qml.RX(weights[layer, i, 1], wires=wire_pair[1])) + + # odd layer of entanglers + odd_wires = [wires[i : i + 2] for i in range(1, len(wires) - 1, 2)] + for i, wire_pair in enumerate(odd_wires): + op_list.append(qml.CZ(wires=wire_pair)) + op_list.append(gate_sequence[layer, len(wires) // 2 + i, 0].item()(weights[layer, len(wires) // 2 + i, 0], wires=wire_pair[0])) + op_list.append(gate_sequence[layer, len(wires) // 2 + i, 1].item()(weights[layer, len(wires) // 2 + i, 1], wires=wire_pair[1])) + # op_list.append(qml.RX(weights[layer, len(wires) // 2 + i, 0], wires=wire_pair[0])) + # op_list.append(qml.RX(weights[layer, len(wires) // 2 + i, 1], wires=wire_pair[1])) + + return op_list + +def generate_random_gate_sequence(shape): + gate_set = [qml.RX, qml.RY, qml.RZ] + return np.random.choice(gate_set, size=shape) + +def get_parameter_shape(n_layers, n_wires): + if n_wires == 1: + return [(n_wires,), (n_layers,)] + return [(n_wires,), (n_layers, n_wires - 1, 2)] + diff --git a/_static/demonstration_assets/quantum_phase_transitions/Fig_1_Ising_chain.png b/_static/demonstration_assets/quantum_phase_transitions/Fig_1_Ising_chain.png deleted file mode 100644 index 39abd30b8f827992d0af3f90b35bea799c563fb7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12045 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 Un|&n(Z2$lO07*qoM6N<$f?KTii~s-t literal 0 HcmV?d00001 diff --git a/_static/thumbs/bloch.png b/_static/thumbs/bloch.png new file mode 100644 index 0000000000000000000000000000000000000000..85d5844fb4e3e78771d1731a499934803497487f GIT binary patch literal 14494 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

l}Cm72zSa3@axZgqrhCLTn1wod``3xcPwlF*B&> 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 V|0lrSpooZI`r5`?^^ct6{tsp8Wyk;k literal 0 HcmV?d00001 diff --git a/_static/thumbs/gauss-circuit.png b/_static/thumbs/gauss-circuit.png new file mode 100644 index 0000000000000000000000000000000000000000..d61703b4f5dd2948461cdafaf2132c7efb04b8a2 GIT binary patch literal 13467 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

R?&tj0B|4fn_-t?vN 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 L)#S=vnFReGm>HP{ literal 0 HcmV?d00001 diff --git a/_static/thumbs/qgan3.png b/_static/thumbs/qgan3.png new file mode 100644 index 0000000000000000000000000000000000000000..0985937bed3491990a6ee5ed6d206c5bbbb250da GIT binary patch literal 19379 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�kO`BQBAMvLa?-U2ICjY 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 literal 0 HcmV?d00001 diff --git a/_static/thumbs/qng_optimization.png b/_static/thumbs/qng_optimization.png new file mode 100644 index 0000000000000000000000000000000000000000..5ebf981af0a16629a1d0d3a9ecc38d5d39a840db GIT binary patch literal 42026 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 Bk6i!& literal 0 HcmV?d00001 diff --git a/_static/thumbs/qnn_output_28_0.png b/_static/thumbs/qnn_output_28_0.png new file mode 100644 index 0000000000000000000000000000000000000000..4fd4a104b9c2d1300dc782b0da659395876b714d GIT binary patch literal 21546 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 J4?S>M{{a`0p0)r0 literal 0 HcmV?d00001 diff --git a/_static/thumbs/rotoselect_structure.png b/_static/thumbs/rotoselect_structure.png new file mode 100644 index 0000000000000000000000000000000000000000..a38f3318e3e7980742e3f08d78de6f68732c3356 GIT binary patch literal 7147 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%0DXDS<-Xr4o0d9gEKp^N$4*EEvcF1)gKc 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

KDF=w{}qpy>(lDXw7JKc-I?fpx*{1YjW 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�F5Tb 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

+IjyZf*$U4v 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

h(ic>EG~?C+3L_xOW?^IsbOCJwj|xEMBL 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 literal 0 HcmV?d00001 diff --git a/_static/thumbs/universal_dnn.png b/_static/thumbs/universal_dnn.png new file mode 100644 index 0000000000000000000000000000000000000000..bdfdd16edff347a9a348a4ccea6897d5e0ac1f52 GIT binary patch literal 40857 zcmd>F1ydZ$7RDhs1b1g~cMC4T`~*$G!xltw`$K!kvRK#`S^P=$bij01k~5a58nIO^tkKtSXg%1Vf;yJwwbyLn@2 zEdI;?++|1VbPJugLcB;xCLJ zFZ$sBpI;OL1ec?Mbtqd6!}QY;+xye&+CXS%q@qKI6_LWqnAyrL0zvBYK>oGoCBbdoCsm_Tv(f6-YTf&o>2H0>wR^ zp~rT#LIdUE`hWesB{F&iuLHe#BpCw2U{dbz_~X_F&zyXnVb%tEE+ z^$@NB#OAGMI+qvs_5S3}@1R^Iu-)U8#@P2lLG~I#eapJb8Y8`tDn> z>cy^+hbv%y5vY+n)A*f9e&#(DEJ@IJFLoKXQtg1=0QQb4qPKSvK3}3xR8&+4?A#Dk;)uLEkw3HvVPOaaoEE)Td*jQU9vnVTH(L=5HF>_TPerAr z*SnvU;!-lk^xVLvw>tRNE3NUsjpbKZmA{oEy=oj|he3FU#7IwvI_?Vwp#vuldEm=@ z6XG593^EIT4i*6nPeneC&L7hhW}V+|8$Qw-zOTI36CCO6F7%5AZp7xfKHSPYB>z6k zDl4NarbdqPfCo=oz#$bCL&73EQ5>xg+mUToV+@uvdHxH2)Mcc&!=#n}LH-%-lg+j_ z@^InF$cNqMh@c4SJ-EKS8fR(ref6|%E8SI4-Ag{5 zJ(y#fHxP$Or@{4fB@x`D9SWl^nJ=nKgnxA8uj0Igl&AN6M+W0{e=6XCQauz!z!f3X z;l|;xErCiRh&KXHcdoHo_8F0CIyI0ynq5RqBiZNq?(*s@OTGP;7^eaQ7b1PcmrV|D zi#biT$zNrG2!`kDUEX&`rGk&UiG#z#@Ii*5sKnn|Y{8?}`qjU&u?Ej5{NW^n!NBUk ztIicjMkX$FYDdBlP*Yt@wrnJ1Wy$Au`=s+aP~`gD;d|Yk75AQ^T4$!D;2iF(NL0HS z$x?Y}E4CF=scYt8;3pRBYc0(X@PJfxIRQjegt zLyO9du`f`QnOr-K)51Hc2EJRjiv|a09p^N(w4s}uhW-8hb<5U7y!Jo;tJc1}<=HS~ zFzMd?u2P8|mj1joQ0O(rwNaF0z1*G*)TK^g;89j@15ZulUw5^=eq;i$v7Jr_!$A?M zCufJtjkF;~bzb+jD=k0V=2kRULCBVEw|{j!9`ZvE!5uuPp@C6R$c|1UWTV^XWVI!>sEA6?mdD7*$oced^~C61VmND2mBJ}q z2n1r2x2u^Q)3X!#3}>{CT~~5k9D2Lk`P4^M$l`NC`1GldbdI;G9fr2(s ze!;&Eofg|-*+d!`SlH|lnuSj=bw1Cof8QQyZCf%Q9^8RDmM)Ys%Zs>;A|`by|6*O; z{6!v7sG_(-Q3Wd@F;Vubg6Y3M<(#%_X|*X#COf~QZ;s|m;6_BG8WU&qMnlLRU56Ko= zG^LWeaMUkc(&B&4o?I0SNxH5&wYSYDW&Z4ki^kjYxmLd;?UN)+(-ucsHHAg z8No>c6Za2#>A#A{XN~)`e!Nfpa1}`OTDR>|Y@M5vVxDuonJAu+GJ!v#REQ1H#Dh5D zyp~37Pw7OnRmkkASibw6*{*p)iiLggVU$z>PQhV&%=E#)mOUAk|Ji)`K}4axa@H@3 zEx#l&$xW`#-Dk&30utwPN_1Xz+P>yEW205c5F9*JV;Lc`L6q`Lk$5uG`a_YecLM}i z*rCq%Al@tA`oC!#NpC)Qb6--0fqi@B&1VtPlcX5$)d~vXv@b&iiY-k)h8FaFbx?r% zh{(@BjxU8C@}kyAVy(vHyzA;Tv`Cf@$x_^{iSaqkGhi6ibGfN-Ilq(C?Jp8d!J8pm ztHOH2BV!U~=Hxl&Z}57$aCIdgHSwn!IT22LxIE4v9+KI`k1|Z;R1P>q&65Qj}2AjFeF8?-x|?1n&8NZ zbwt=ug}(o)gs#HKnu96CGSmJG-zqHV0KW`gAblOM!^e7KY+thRz!K`VB~iM^s*eH9 zPDQZB(o(F5%Alx&Q!sZfM5A^GMgjsFTI!c4U6Xi7y*PsmMCmK&Hf^|jC{$uV*d4@1 zErDR8w;Mm(Cn=YrX?T4!b{4esL>u}x$1wqH5;flJGNx5BvDnFgN+(2^m^HDCKNp9v zCo4Tbx7!%8>#3!p;zZ~$rrojUuFh!Q8z%KD64lkMKiAw3LKz@4xt05qnkzmzy4`zZ zj^VZiD)^kKCAUjO{?nT&7P`$#POFHTjv@AN#Mi24Lr}Qrlgdy~G5+TAuXCA24hal%dW;-OB>M-Wr z|C%b}sgz85f6x^;5-wt&)N#TG^Z6}F*5NYiG-Q*;ClumZ&he6l{UAR@e8yO<^wf1w zEwI~&h)*$&xqGj~gQK9p)XBUnQLXsNU-)J0Ga5V^alwyN=0CNh!{>f4iWV{m5P@(g z`?WpX=p-bHuTQtKa*DtA9A^t8kvwk>MHiX%EH?6?9UL4afvJieLX_L7Zb^5x6i?VnH$XNDL2c^S}MH&L}63bYb#mYCyhiuhEw zF8}?Xb_8mwRr@P;cik?70vathcgmpIqy@O03xL5z05ajUSxF*$7Yy}U zMEzqYJWSnt?418Y*m=Ov-5I1G`dNzwHS%*PgriT}?o$JXSl`Yq1;g%YM#84&y3~vUrt=( zmiA2oc9@?AR`q7K{7A)h*5vhdTBJ&^F(jEmv%&V&)Aj9{CEv*CqhI#CKSfqmF-{5D zAqaWi84j0-EWaHrA{=d<@)*#nuLdjeQb`} zAwXk{7N@eS9`NrfgGRpQc(SuTQFtCpvYxXr+-#Lw&boI*&k50>Bbo|lWU+f zlfjZd`1;|zrGKP^0u8(+9?E-s!g2WL7sGFrryADpkKa_Vf&tVh{JNVsHzteF(AW?b z6}6jjTr2Xgo*{7C{^F`Y^1G?23Jdq|3H)-X1~L}(`jQHIhX1PaG%Eiaq@^-cZ^SOH zpa-Cs*`8g`Y?pMbF`|Tq#@*TSKn}Qr&+kx~v$jP$7KIEeI9B8-Ot|!(hMSU@U@-N# znL*9}<@J8U`{r^OxP-%H7{*JTHy@hF4f;SR+QCk&2=o1ZbriGw^oZf`-D#CpM~Z=F z)n;waI}Hm)t4QWFpS6W}B5wdtLzE$!7?rry6*MBQf*l4xgvX9B|hgS_r=0 zBznKzw(K4peE$4d{9r2k19Br`YQ-Vv$cI^nBCvxM_Ob&9Y)gS+{=pfY4is%WUR{|x z4=JSdJn5q6Rq?`7^G@WwRfl~&{d5Od>XU{){jNSJ{7Ov7jU`FWOBFOTGee+4CWdf= zJvh{%%Pb}?UU}OZ46ww20DyDo7#rewQX`=!cYoE;pr)b%L8^%Qd%3OK1;ubE4o;HrA`^d{zUe8X|8ua!0#v0*i0@S zy*sb|Ro=#;!i9!6_X=gqJ*djD`o&aNbbLZfL%e}c`U7y+({I zgt*?#@nQSJVLPpp>p7<-8Rph|(WDE6ul`wvYucjWa?tD-6}BTF*E{x!B6c zMjQrjM4bi0d2myrM90EE%HAbLedvdCh%{+TC)_MDLe8!=o&{s7XY{^7w$}tsYn}$u zK>V^05)BtgEwzrIi|c3hyP1`4d%asdS}Xs&sOyB1>+{B^8AVFER8vJD1m(URPV0U- zgsY;FoR>%TA+h1G=udQuBCT+4>_r&DhweB4hw`u)h-LvUQ6STdP_ z2)R35sec&4m9<_$-8fpn#6U_3F=y8}25Tq2i#;NhmjV7;F|N zvY&Q6uPF{YzuA7=S<~oyF?G0}??+tjc%0M^A1>CCVp^z+G*8@VXW=4Nu8!VBX2=mJ!g#N<>evUJ)kxs599$fcyOUs321)!`1qY~D?h%Q zo6i2y;prb3h@?Z)+SX`7fpFTNFd7WQkk8=^HyuxjUuvS0TG=qP!19U1%8dS~N}(j) zFy_kWrT1r}EIls)``55Zy0cy5F9XF>j{q3no%_nNS3gW*vi)mu`_dC%#FhwG*B+7w z?5vjOWBTh}LhZlDbG3Y;RzQft&E0xdqD4(n!af%?$KpNKI81m@q7b#x$dnj16OaDU zOUGr61y!W7MWm5F?=WAgbg&&y>G2+j=5Dqw=z0HdXpPUweRnIg4GIl!_*F`i*Z66jt|x)0U&zLimqOu;KUKb>y1YRQLIdtv-pSVp=aC z9=P9UyR{zVcmZTk#tpG`9#pA%*cS+YwAzB(qREESG z(({8{KeOGDc%Uu?jwaGVU+g9-CB(<~aaT~ATI|=hI;xpM zFE>x;bN!PpF|mL^zT=a|t!)-F{qxm zCk7;3=3PLp+@Hx8U-Q16l#r5g+==F4cifff?~lPcTWK^$Gx;4Qs-ofhxQCm;ZR7d< z#vo2YixR>TZ_VQ%TaeWRZgtUaGhk~#R(@?_zR^4}F7Au*^}gix3@o0ROrdf%qd|Ap zSE=aj9Ix}eyHz{*&5dk17`eFR))xXViqHBMMK_K)62c2UbL!nCuFBCo{7Y8{FlwTBBFraoxuz2y%!4b zQg~GrTv@}jwYL3%|F&PRrvw14=pnFpJf+_6-M6OWj2|x73ALgCK!7YWO_pjOs_@%= z&pRZm*PS(q*9qJ2Skmdi@XvhChv>jstFl8}=# zl};&zzUzE7ue7n_%s(C_j(dkL{QBZllf&wd&tYc2YSRKM?E4}1ix@(f=Qv1JWAp|? z!UC{PW`K;BF{_r#UMqL=l;k^sO!t*se^mN?~^p7Q6KwE6=&>~V*!bd1ji zs#<_0wQOV2O)$aOQht~bqb$L8Ur}?kbi%E!F|@;M*Q85~IyR<9oDZTZHM_ds&&~qZ zRzBvSBa}iT7yeF)*sim3-)wbOg2G4XYz2ToI>fAwP15|xEy{D035NYWA#Lbi+;yuSmRq`KozF)56CN3rwe9p5k2zglj~9Vx z4=<4{V}kdE3+q*~RIIjZyuYIf-6mESVV>{KhmV{w34g$0A&>#=?ta5B7ie9;zWqB= zPn~zPP9LtkP}1OY?Yh0a6BZWn1;KydX!AbzKeqS!gI3F@=$pfNVgoLO!34BlU?cOs z30youZ!!mza4#;?2E) zSy`Q-jnvxg_8BCC;GFJ$o`GbpkOUdEU&!?Yns!e(+B5(2rDpC#hsvkN%h2EUQLATm zKXtNgbA^$si=6J!8eM-KrQ(h(Rf@gqd>@*AIlpt@RE#?CU#^{}$`{5(5otu;Qvdc} zH=WeyjOCi1<% zT(@loAdQTV@2ojZn*v+mgY2<%KS1S{B)eOT@l~L^3z8rrB7SSSp!R*bq&1uJ4B(jN zD{Z~9=UwXYBW0QJOBp8hNm?}U%Lbl%#W#V2iFEOM5+U9$w{!NwenhPnp~ywyjfn&$ zKS}}Gj(=!(Y!qK#1ZNR8)6H{?7AiD`o~WAjg@8+>3H$IL9UXDakz$6^_+Yo!PVkHF zi+^)WI=AQl*7o9d)@BKpex&u%;X=FNdnfc2r{}A>Jk{cwC$^Hv1+o_Ya&Z6)mL%P^ z24=@VjPk_Ykt9sPq^-&P)8tFDH?=|r*ZzUse-}4VZEIfR(7J!@IDcr?P;%Fh*mYlU z`@J*}gzkiq)vuoN5}^{I(z^Ny57fh`COtZ?s1Tvr^U5tWV^;K6aAnHKj*B@gd04%z zR_S(O3>}dPP-gnM5%1=#4(Bh~$XL>V|Hek|kD71nmz!@$l?PpjBRF0oEnS}bzUM*> znQo@!4f4HBs8km07kNpNg|gy4LnI&G#BFKs>-=t_ZS4mm`3s=e=<0 zH6MfsLX5aLm{rd|$9jL0;B#UTLzru9Sn@ioYeQkZpKRev%ZR9;#|zo5E*?KzYft98 zx-TUll2b5Uu4PDnSxU4R*2 zlA=4CM{v|gHjJUIB<@gD4!n3N@EY^;VXzCAuZVAhgpmN`Fa2iOeaXu^ul>b$vr zcfzOb*?QLU>_DT?_u0elem)ZbT?wa!3*4`HvAo-^$`8!j?!RJ_Ag&2>8bGGjjGOie z1==tD=%%j%UCtjX(abp$51gmaO(+0lwJQDvA$B#oqQWBM_gb41pF7ZQm^-2b-p}9N z0mRe#8YA$q@%|>g%576|Vhfm7d1Z*MM$(36VYt`{w6h%V9!coTe4-KW|Xwgq7L1-=FpK4ZvSMdKomh?&TsAc)Io? z&T>XiOiUODLt;ykGcZ&xjVP&LulwDKKcSsTd+&q@3huEaChImUq5CNh9}I zgYu2?v%F>vx3`ZQ_i}BTtTr$uF`R{}IX=@wn=eZn%-R{|_JWKi|1ALQn1bs;?Hwt2 zOvk-X3NimTWF6nqFDF{Pc4xY~M@pyl-7QN^H5Tp&+>NJ%x;V0@tmVL>B;{S_8Sp6b0U-EGY?hP|SsLir=$9?37-Apc?JP%IY|tuPtd!+nsUL zns*R7XGAPDCC4xk6T|+mv5^rnK`s<%!VEhRRdxN{@Bw%X%km{6#Wh>E^5+%XmPXHc zGvSvrczxdoNI!hdy@^0>9Sxi2kSF-7^q;)H8dR2LC?}#Q<1gW!xC>qzQ*JoYh*-p!>%QexZfWWM`qv+w0)2fI5LQ1hPKkW>SjTzyI1)=|??k#1o7qJ1 z8gILRLdKv!dHhOz@&X-%O=ND^@k%y}- zJo6<=eJYc=Gm2ca@pczVPReD>)2h6v-Esu8G!cZ;3eoTM8c`Sp`fWp zTO$fB{B%gE&1t89YEywr7+Fza4Q`HZNnzZ2JjgM*+#Ui>H`|H`$o()sG!0O~>g(%Y z8XYJtEiE&M0WU=FSNf0< zsU;%!h;IjC2%WYp!U~k!G*IB<0o#^&K{gVqq9Ria43Yrc*5mjgi6Gd|Bsl#Xv0yRx zSCFTxp86RlnM1Eys0PYM`1I?E+uZdNLk|$Ehs^lGNzyivr^7AKbWEw=1v(cl)k*v z++a1CLji=9mb{<(fZEYv0>7?*$Hfy%JkSRX@9z5Mt;>uHC0oJ#xCnZE`LFCt1U*Dw zeTM>K&%r#Wv)=HYGo0`|N?@DQ!4(j>u)pP6w7TRC(QNx{{31($R&VR53Q&tcH|WzT6$x zW{G@9P%KRZc*OS3j^#?xCn2x9-JFi|?Y<$xr8F>(KWOL4;E{!4$&aaP%t3U0`zgNONWQw?xN3rGctkh_2ozQ%KHAB7j-BIsfzM3hM(%KVJNt@%DtSQRqF1W2 z^Lx#4oT->G4tU(_UiaW$uMABNc=4_LH(z@db>fQ!q~6k_!w^vkcZ4<%TW4^dUL_60?p{azs?8v`ZEw-10esgXp-y2xe(q3W z#Aiq6Tf%f?0T-EFBAqE9gv+RzFLk0drs!`w zd@r#dPaB_cL0K@Ptj&W+ov*SZ465>33<64V@3H7L z?#smFG%W=umO7!O)-v#^{zi3X^JW=5a(R-@oacV`LK)L;5;Z>vaC1E1kVGEBbbjk8 zHkwVg?!3RnE-ig){Pta$Rjxzv?bmXwvJuxh;N$>&RO=9T=TJ_fW0|7+)j;06R45zH2bpIOyB@J?E74iEKnr^fxaYAC(;3)sxunE z}rS$yw3u$$QS$43Ly4oYSczJPhp?B56HJ=03#e#Ji)CCmg_1D8TjL^XgzhCt%+ zAnQylCWiI4QkUcR_Lf<9VbA_2B}kff2H4BZb{kWG(J(ykw9jgNsN7FU5f&B(DtSQE zYGx028Aqyvhu=}=q6#2O4g4F9A$^oyLFo_Y;NboKdcV83he1s2qkgZf074&N1$29= z!QuHXX|$T2EjVU8{v_sXfy9)Q-;}X}{n>8ovhvQHnC@q72N&w%vpnJ7{rZ?Ww_ra3 z=k>!+05Vm3XRepgHt3a0Nl&`nm0n5?kflVu^MmLqYd#%KpuS*2$}lCVT5M`9BusR9Q`;=4P@ z&gVg>v%Ji-r(A@v`sls+Vz z%GTgQT_Ff@1$oq_4BRCaQAD~ zYE$sp>1w^pJxL9LF!Iar(M&${4y|HP0k+$z>zj601_~i}+At?F!SCM*OL^oRINe{q z$*}sJKcwDGW;$KWkY9mq)nf250WC{Yxi@1nlg_?lmUGe>@@u#e-r$f#qNeiCt!v<* z0hJAiSgHA!(Q~IlpA0dzsv=4lW=wFHO>E99iBPlCM>tLKh3<*G^e)w#M4FnW-hu&B zjl=!Iu-O(Ymn-a(#^J^csIgm7kkCM6sNlz9jrT<#JQX)XcgjPn!;Xyg-zY#02TcX#)Cn-yoFNbc}_s`J-WQv;uYg5XH4hFu#1?x+DXO+216M=?qYC=6&Z1OOA; ztTgY=%@$fNw`0$$V8BRHeYuGjOa)Se4!|CDe70#xc5K8V*&xcdBwA@eUKJG;{pN)z zrn7~Jx>cf5ZMn|68zgDmmNoU$Q2;;j2pS%Rf`$g}ClIm%z$2bI*~Z_1Znf^YWy4+d80ph-S8p|B zMYHb8RV~m=b7aUa&~jcNdAxtJ)}4tfqTkyt!X1~G9GH}#x9ZAfG}pSLPbDW8&M`6s zu$ik~Vzk7i0y=<37oIPdn&83R{u>7VTin%*JDe#aZiY7}euvrkwE{(AF_MO2MLpmQ zil@C~3(=9hSOTNJDrG!gc2kZL#ZN4|Eww-+gu3L1<=-em59hPY!QT>BU@wlg^HEKQ zB^zXcS{Ae^-{`%Ks_!e+CP&MSLWwlGVfIUH2l~$r@H~P)yiXw*42<0TJwbi5F_;$|y*A6V@ zzd8wC)Sb*~NvU&shdk?g(}wu3O6%)z3KQN;H3PABGug<|?(@cbZ%Ku-#jN0rCSWHG zf40AcUP38Uhm;&(b$|Jr`R$=k8YH&W*w&bSv-EjHGV z8595@byLhE=+~kxkWwkT3x^mP%2?_Tym(7Uz#PDA`192^{%e1EA$(MXYGr#GiR6AeTm*`b5Ti2gV`j()BX8}NfAxJ)xUF@ z_KXcxI^bVp`H`}<>wXLY5_?5SX5I~)r1>)D!Zw75OT?(w+)EGlINWcIuo}UD36UUW z*VzX+uQ5!ZFw?K84$G#LZBI|flcUZG;spHcMV6c1xPbiyaHK~kaJy<7ljJisV8(_o z>J4xBy(&xx0a(JIPp_p@ELR_VILo}>s_U&$T(Fab#kv5d`2={_ZkukBVLz{y+J4|Z z-=CTQ@j$LQILNZ4mm0+DK02*r9uRNXv29Pty}@&wS5B7VGqa5>f0~4ImqkGVCBD<7 z#aYju=zf=Qrk9x_0Ptsu&CRozY3-PG#kyKOshZ^m4{nL?v!WMwOecP&&H?YLCeHKT z7dU-ucp#qYI3(K69GtIm;aKzc=+wo z3a#CL@#m@L`8faaoXytQkVQQ&gqr0&%JVEh^XO0iX`7zuvLRGFp2^YA7Pvfazg@e3 zp&;onb!=nc{V&J}4*)1m8_|oIHZz>UxzC&`TSisut>MKzZ*aC~Zc&8eLeoOA6j&rV z-ju?V$Slmrh3n03q$vM}$WHF&rmK?7;#V;3QX9Ukk)Y{yxbCfcol^s43a|z3_tVWC zffi3XKgye>2&6Rmyf(iSyas%{eRyc$q;I^!nPX7*Q^W5?G?7Ljjnfhn$RxRU>H}$N z4Gne4t?HKbZ6G0R`UG~HueICg4a!9i0O`-N|Z4EQ|1a0G(P%!EwT0 z8*TQx2!^^(Mkzk2{6SpOybAt)ymaASXKkUh=2Jx;pCZf9AwP1ul{M`VxxP=~z!~sn zWAlDKZ2>~`cQWrJYUk=`(x1t-mX|+#*Cwlle`VAU4C13Ob7B;fl#d%i_nV+&xGI_e z>ppBm*W798T1cTIr~mAb!N?#4JKPu}yjG;OC_Fdv^rQ4OVI#!T_9m0VGV&&?)r13c z=I!BH-~I65ShDfYUxt6y95tPD|sxAO@`AGepl;e0=auvxA9->v}AY5KWC`H%$(g0}1V;p$lQ`weVz+L|!TL4$ zZjgR&UD-ydN;~VT!yI`AniuY~oPm&C0`*p4oSH5hnl=yEERG9KC;g`HGWFP%)b zOEHu0)NEbY)NJ0uPtIq9&m5!&rN8{<8XQcJ=^lTqa@%%Wb3%J%o3-;Z)Q{yG;kkpQ z5ruwBNkYw(YkXCm<5+q+S6peb?t8IR`-FhYqm2 zk8nU_Dzy7G=^UYe)97@&pqZ();~RoPs`!jb z>~1tm@pN}OPE9F|BNCT`x-2kp;`~0ieDn%C5Q0KYNB2oXMb;weX8{N=0}UO01sP+V zy)F`{lvzLxbeiN^O!}85hH*Pwp&{RTCHDb9fTFwzxYcSU^5h`UVbPxP3AiOzOM9J5q~eUOb;LB9H4z}X z`(oh&RLlKN?nqNoUrE>hn(?_xk#c-D@#j?M9=;DfN9EXmB5^x%8KcGo(ucEi8Zlm2 z`c1?ki_$W6iB530+(J<8Q)Q9x6{{_y*-I@TIu2l7M`pz+)u&sU0j+2+{85;ohIFZu_1->%B>|FQ~? zt20`4J?pWhXy}wfHvJ}LTG1{UiNogeX$SbPQte4r%L|!>#1)o4Xx^@}gTr;C*3#FZA!!W!b z`rs#WgfdHWeKQE{I^%!Tbft`hA;8$b``ph~X>rtKu=)dK2Zm;m`X>Pj1~`xGQ+fw{ zlkS&IM?P8W6bswZA9(FB(o__T7`pozlu#ezTD9F*i057JzI2c}?$=$9`a_w+p22`d2pIi-wQD<1R+Lf96Dqhl7owrK!~Oi#m*CKr7NY8L`5Q zy644`*Dyu4?M%hP!;_2p6P-h(+m8ILZ)013B4c_%Y7er?S$Qd473=jBSv8KSu0kY$ zr^QmhIL1J(N(@lc-#H>d4@=(?=H}b-3=F&+5gM7LYVF(uKUZfjEu# zrWYp1AOP>v<?LBLR+5(F8-Gq`o6S)Jdz(5RykR zV~}f6S6ux&6_D&vx;C4{6L4h&@=ZIB2e~5bPnsv_b0bx({}iR#)#9~)YwgBK6K&%TM^LT8m?~x@B5jK?x#tnPX6jTmcgI(z1w9vu}@n1DK zmb+b)Y^FK{U0+|+1M~{-=~p)0qKpp6!Kz5?qLd)z}6)YiI;uCW5}Xr z%oRl3W>{Xu_zGevQp{|$MkZi=o9F)h{4pa$jsjge&=(X>$?JT99?R(I@An2C4NSC? zxb_@(#X$16PwsvntNJYfOmo5AoRhjw+cwUQid-?hK`Rcc*BM zs9+%0(AQMJr;YP<{0od%x%J8Zjy&+wMCB!+klDmXzKp)#4El8NKgt?ZIc+{wIp^5W zi8Z2tEM}aIdy)Qtz}TQWoDIAaSBV)m5PI3S3z#-t@4IF5HNapW{8NeRzH#chb1|I! zm27E`VkuYFi&GRwnTne*RnHZllBF#Oh(@GoDG3er2P3s@{U&U?3?X~~MuyrP`(dU8 zujSW04v0PPPP9BXg2N>YrSvS{5SSN2?0H##dY!k00;4#;qwr09hu#Ot~yi1eJNwgGSmTOaDpMYGpqjuIk}L%P&P-8AlUQ92zH|Sb*yFjkA7$QS~)PMV+x`?SzABapcHemIjac)#WZ%%W8+xtaQs zNXfCY^AL9G7l~9rOZH*l4*~E%3$VQ06uTmW9^895FLS)FWIyIU+nta4Gwr%Z@y)YT ztb1^N!Al|-2NERgf!=(AN00UdT zPy3nMvLOq>lfTb^sOxhThx~D2#5HL!h^;;@HFElZQiv%!E-sE99z99o*RqU~63e}B ziYhP|CGKfO-)$u`!xUan0mxz4B*-VINkKHGwhU1_o!2M>hHo#AL`yxSv5S$@$Hjm! zp5^ja>*jc)r|D4*7ST>XVg3QgOQmuBzyVyD4?`9d(f?UVu#c%_tzXYA-Qjp;T@Oz- zbh2SQn3?f?vXUNAcyf3$k*1M@E}fdew@~yN$9lOT7=S~Kp0`#&j`TON+nT7%NVqIw zQ<)$Xude-1Ki6vW*LIZsE>&nsE%8NJ8JRqt`?H~fwjgxCXdO#t7@cC)>uTYTQ#EcZ zM<#V=V=~s!d=_eR+fjOXT`i@S3s5HwVYd7$7=K61kHuoTU7Pa?gr`-NH&NADY7~pe z9okNA1e_429@dpnuBCsr;enf(yf4=~fWSXS>AcqIqNJgx^T&u9-$Ax!6;4V;#RrZD z#$q)nhvA%jN6i`?0P`TtG(4k4EWBKEzj?U&OWuoMPzGi@q-4Rd_diW4AU@V)F&jXd z!g5eC&T>$$W7i*_Yn~#0w`fGqD3Aw2Y_GX~dp^IO=rnh?|NWZ9;mwOAVlWmsJyGh{ z<16^~u;br}`qkqP3bMGl!JA^IyRwyixk_Qw$%Ys(I1sHa2-n!GTEr3ZbQi+qsDub{ z4mt9c8nKU38B~L-h0-)qAI$2+WmRyFxcFwj5W0yYQyek-gPN6nZfnD4Kfl6dS{l?@ zI;M*&753A+k`Bwy^Jl^mQL%5})v6XgtYZVHbHQ2fAW=zV?;~C@@NePI^z=c0kJE^FI z{Oa(B3ubdx=wx)+U)pW443lpbPnC8AN6grr!MY$sydDXavavZZ=iF~VBo;3$hR);W zN}Dp`U7yjiIKf;wKY;F1D}TEVmQpNJR7UK`rs9*&BI^9A3UtcBsANX^i>rW@vD3V% zCZvm;KNe=43?kygWAc#q)7|Pz^Of65ljY>n<&OLPsoRHZrjWsz4|o&+U%5VQ*)VQg zwpeMR0~&H6pw_Jk`S3?~xCWLi7+kGw50XK&oSFHZF(Wp;RHQ!hhRaZ?%BkmL?uX_1 z+6yWco2_9L&jY7CGkESZ4TSkEA5k8@^BE=%L1&6;tm&5}- z*#%@-qG2=W680dX;jsi-Z^{0D{W8K)Iq@-PEC;i8Gi0bF{6R?xfTy)(&%fMmisW=K zv2_jv_Sx9t0sj9Hv<<_cd@=2sOqgKjfM$eMHIM(PP_H6VMqV$9azUUk*iKk`Cd%Or z%>GeKS~b71+CdKa^4yzfL$v?7#MkE>IM9@k!szqcZLLtQr*l1tfDZjPSln)3#%vwY zW^AmrCxcuLQ&cCvtYJ^QGPPle`6gH_e3eR5AF5g6 zzYt*0tDO1#l@hxI*pm-~>-o4R^X`f6AF6}!U9s7t?}r8ZpMrfp3VT?^p>Q>a z|13}8A)qbkNi7WxAJ8aXWAObcu+)ce(4RDl8@A4IenDhT%Cid&l_i=pIyJ_h1#CkZ z$o-GGv+$~N`@TLRDj+SXbO=aFi6AK59nwg5cXvy7NvF~vQi34Dr9-3yq(Qnt>bK7K z{U=_>xB~_kIGpD^XYaMwob$6Dzpx#Ex+r?hl?MNw?l|(p6Vg}qNQKP&89&xf*n%z= zNa?aHRa0>YQC0J(pAkoB3ok6G7R!g?5Nm1{JuTvrE4cN)LrYJ8^v|lwpmA2oh9{-< zgwX4Z;wj(L;=l;z{=;ep3e9i*o$yn(T9g+aMf04_*X{l}SrY_8$q@xQNetnr0H-f|Z^?V8a^Ql?C)-Dr*I@37o zFc@)JQ&&4{5kDaR_XF}BQFr6AGu6y!im$gHe#m9mk9HF+GcllQkU??RdCyN&bn{wM z1OBPXW~)+4t8($`c30p~O+nt`#K;Jj=ZR~{BT%Ji7{cW;dE&FObhIKMC&*j)d*bu| z_$qxRyQ=}`&p9|p8f~!fh3cnw5J@vAQpF%C$XoH24G#|wTz{|k+Rw<(hSP*%$pvD5 zb{@X%uW#7#N9Fe*5MY7d^Y@vHdH@nJ<9{11t|&?2bG8Wad=C-{p!US2r%OqiGZ4iR z3*Fwi8)KY*+4xqwp6_p@HX-7liAo6ps(*Y&;MYt2I;?g-7Uv05k;nHliXllNth7%c zU;O0$ly&K#mPTI}T={(TpC0#+DV=IZ-S$M+Rx|8SIiFXbCk(9pHKnG-=dKA^{fxMF zrPQ}*u;50?7L`zQa>iX;to?l_7DcRW|rqoL9VGTrA3ZY=J368|9ZR2-;Z8~l5(QnSc4xPB1SM( z@4>m(cF77Ja{3lVmq_7m14)we+5BAt{%K6OVEx_@gb5A{0}r->tIA&tWQ{sgOt!Db zcv4~5AP9AZVy`zxJc_4paUksZAG_yS01^i$=Y>l*`p^@O?|9xl;Yi9VD(M_1=qh{7 zQCMVrig7u^0%yNPmc^rq4Npw6JC!L336oc#!CNFT{6Vs-!?rO8uqPz6xm$eSgoL1( zhu#r)G;9V|4Ga>Fl@*?$r7zWMfD4LwbO+*v`{31Q_dd5DiB%6^+?{e4jvP6iAf(I@4L5o@84W{pN~p-pD%{} zeruokxBDE}A7ze|>qfyqc=l35QF^CT2QE48-L>@q}Xg4?<%6`Q7`%O(v zKVzd;v%oeCol^J|)BCH%k?GCNdQQg6FS|VYmh7by{H45VxS>fY`HiK^lE%B_z0)RI z>ydKQh2J7*EMw@uCy(I^hgStPV2dBuHCLx-+2HJJ@e@ zb$;-k)B_ES3LQ|wZ;TlHs?vw{>Boqen5D83(^1Mw-_^>rB!2^~9LXa-N-8@F4!1@R zKL#v&QA!<$Kat#3iDE_7_~ByN5~emGzvt*RG%hKgv^y$WyW+evZ=S}2NLb`8-FEKO zH^N{=-L<{QyTO0`7$s)dZd&dMq9nYYtXxY@)u-mBBVoieG&J0ps-d}iA0yW&wGs)m z9d`;zKmDzD8^zS@t&($ATA9t0w;!+zZJ*&#b&%ZE`f8DUe>b+%$3UxuDjJt6dSL6F zgN_FJ_k7z#Kcw`UQ8rS`M`8rB5oeLlB7|3!j?WAuzqhw-s&FPJ{1q(~diM~S8x@^E zxk4x~kIaF@;zE*T;c9L_x75GffB1oL;NM0hF3d6u)70djIb*l_c_vEdoc;_qR(bbI zy{`HSvn~rp3?0O&sQJ@*XE{eCQzA!lFt2!j_0yzVhnT1k%-@WE`tNvYVAijz5WJ8*x+{R^XIP99cr3j=x-^4PH7zjZA8WSD~~nVGj4T)sSG3@)PO z&yo8eRYBgHq-m4P#&D^t%p9brL6IA!q9!~=-rZiHt!zfbBTq6i>tmP z4aRJBQ>=)HXcD&xHDW!xh8;+{o$k%{I1LNRco&HGY_9RS!pis^ao)DR{?~meujT=| z(|`Sg^t@mGJ>m?(DJ(7=FOYZ}s_%#NU{69Lu>5 z6DhSqmI+XWo_3?(YnfEmRoDn{={s(1l?3m+Zmvw-vZ_Ll)UQrn|M(Wa`;{#wss6!U zUGbVb^15n}&)d;g5^`tRNwwQwnFi)+c%L$(y&e3dQ4`pHdvh5qI}dxZDvff62c-*N z`QCR!sU}PDaV~0VYI#>q1SU>|n`r*33U+5e$8rB>th6J^^zSckjpxS1Cdi33%hU7*^BJV0&BR46aCIBVs8Axi96Ey} z@Wt|SbCGn%W9q*d(6hZr?Fz%Xm(=^5DXDq-njZFK)d0|bq76jC4p*Q--F|6Xn54t& z%?le;_I7bLS6@{ABxXTI&Y&=n`pK?Z&9tI*Q5P@g*m%71FL(3L;5@pQH)s3xO86xu zDHCV;ef-SGCC6NpmrA4)Dou@w(`X2YjrYNS^rU>w3?mYe`MJE?CXLhV)w4SrbdCU~ zB5sbctRv(0ehc=#gar3@KQORJ`9uSHx>}$));jIveq7U&#%F@=b-Mj+`GDPRi1f3~ z+~Y5w#hWG%&#UaD6uy@g2EnmR>J>Tm<>@ZyUlGu8>2{ga4Z~SVIvsdO$;6+T<9E~a z^Dg&oy4m?bNly9R9VVTlJoF^e9VVV9{BGNSa@ub=LK?;+@Qh7q+1zhDKKTB#;JTG; zEJvk^jQ&Ls7DT|LzW*P6&L2*U8=ba{iT2x}a(zgY@MjLZ$51GNe;~Y6p>9y!z@$CqzZH_L=9&g3lRQ)CcX3 zyr2jDs(r%Y9Gu5RN!|Z6YG&{Hs4=^JH#O>xB&fHUj47_rijPkbS9j&{xjZr1s5bZ# z7&rx|yUKlg@MwOYl}X7}OH=gx)J+lPb60LsE(GP1cKe5uCp;*-N40vlbZ~*tP~s9Z z`=N7&Xu<*9tg^-t+a($*l0%4b-jN_@^^aovn$``raBZ0s6v2;pp|R!VfAhI(gY)C! zKMZA+h}4T#lxGvF~h?^)h~5Q0WyOo#~nv9GcW5AWx4aabT|w()x5=*o5Cz zz>;GPGzIlE>KWeYf(#H55Og1RIya#GSAMS3`VbTcCa2re#du}GC$vSza~a$q#!|uY zbaE8E?jL3lpzOwqWQc;>F)vTa-*peaeuPIlfRJwM$K^%Ny)k%~F}yRxEgd=V9juaD zZ6muh^Y+;;q~+z;@}pP{p#%Jz+vYK}zgL!UY0huLC=f>!5a$aEi)*dUY;UH>$Z zzq}hXBU>TY^l6Yzh*pv9X{ggMf2=9k%tpnCp8MT+uMK5;)n>YmIZW-`Sv*>jVLO0k zJRsqo+_;_1AwrcBG)0y{Oyodfs#rn}^Nsi;U z{lx$uX~t>L|9HagY8L85R8RNz_M(#N*UV}j^-zFWhr@V9QvK$f2EpkVV`)UH-i>_? zUdCLPFf2*A0>9fkuq-_lk7*5e-I+Ba3WPAgAIQ|vUyM*K;o&K&B}4VJzspob9bMb+ zgZf5^0B{R(8sU%B{Cl?6hAuy>&Gi2(xRmx&P{PNekfvtnB~euEG&O<)d|^?sK z9dNvO?B=bpvM_?cCk@X*?A>`l0Q8g%6s)cHmy+PUUT{h2#uYo( zy5o?7GwKXR+n#A_`Stlo6px%dX0GbZy!$fdZrhpO*w~o+zs-`Svw1I%!~f`;*5M=g z0@5KViO53eL#mgTg;yxNmonil@hb6XWPaR-4??c4t{rswR*!+^mJcEEd$upU&UOWs zghyS#O8x`l!ZGoQ0P8T@Co$Xaa$CS7e2k*3%*K;)b>;0GOV6IcgAwH7SGf-Opx%5z zFR?|CIwd~gz{hVvaC-;l=U#Yk&(-z3vJ*2HoM|`~ymq$dyEul}fa3t{T%n+*R(J7I+R<;iOIekDlUTDA-iqW}>o-fyO zy76I5jF>>Jdlj9U?&W5$OMJriw)Nlc9~tWvTU=K2q}Dtjhe0efKRUWs#o@Kgfjb5yTyS%uA0E8~xuwN*x6tPD^bcER{&~9*xn8x~p5Bc{(^$JV zD=MwUj68=AZjY+B3ev*uzQ~_F5LiJgIk@ot{;Ns>-n|jg(M8~O({Y_P0lpokn%b?3 zWBxD3IN`<~U?p_!>b4+MUC2b0C1(Q!p}BI|p4WW5*`>mIIRgCd__yPeB?=Tk?BL`? zc>R$aj}9gIywmbT!jOw+7o%Uy^V;6SBhq@47>?|$d78-Tf91_KI=UFpEb)?rWP7F| zs(M5bilje(a{ZI@FTFwN2HSHSLeJ?sE*m%h$?$>csm{HOP(6+IvT!SDH8oZe?u1|2 z{6uVN=8Z>Vb;E?2v_t*j}|c7{#a@C?0d!Om9T z$NX)AFI_(?B#lKrrTg7MBW$S3{^47QAkU_?A+uIx~pcZ`@w&<%ISaz z;SP9m+3sqjnj(A$$$nh!g#*Hl@m1u3ZOe%|61n%j(0xpD*#$wLxQ=cbaeIKs^!Vjo z>Be2#UARQ4Ca(lNKqQw3I$_f>mc`I*aS=iN1{j>)p&>d>&Zvc$G5*!dEdh(-DKj%h_hQGs=iS{GIY2#Q6UV)mFRA&gvzGv06u2qx zh@pPQdHmS&-*Ob8_;GCM(c#erFe4F-IU+_W6U?82+2D)+C1T%>~O#jftB*_M3vu)4^>Kw+1)E zREV|&yTzd~(&HDF-yq-&;#PH77Ihn+VgonUMZ})eTDpcv=G<$Sot;MKIUT}+seHd&)|kP%}r^P zMsmpU1wfGM(qEWi-L_oI*K%;;pc|KzJkqNo2Q#Ae!q@DSxn_qMi;^19cpr0H4(w+0 zZ)%v&@n)04YGFIq@*p^O(Db3`t=ey-yHyG_ub6}7Z8E7)hzY#QjEi)-)|cz`+TxUJ z;ZR8KyMp`cliuKr+DCJscg}eo|4DwUw+JwfmzUdh4xyoeNmu*bM2NMeX8A|*rYR@J zT?`ub4F*zwe7zTWw*6^IqfN5%)8To za{|-AAW!Gjq2aGjTHWA)Lu}FZEpzZbb?SI)>t2oI;7v7zhgcv}W6AnKuZGBK)z^QK zt*xyc>$fJBMaa`Fb`&Zl@+J@;FHpugc^{eCK2zlOu)sAR971k&OBc}8ifKE5I;RTuHyi{ zfWOMP8CihbGsl+?`~;X*z84{`WPI$doyoc7+w-*{_l939ILiv;Q_~z+JchnGEidyY zWy-rDLAj<85lGA|OV2K=RG0`Py}Lr;go5P{d9wKAWVO$6&s!Da-X9kRs3Q?W zM~ghvzB_=kTx*8LpUVwh>f*sj(E|4F?Sprc**f}!O<%34vq!lwEZ#r6(M~k!k#1UN zMu9{&g;IR{#80;SJytD%zbJb~r0>Zn8L@b>WY3En&&OJl*s}XIv&3w0X_#?*^Jfi< z5n!N9pl(Zr*ebzWJt5UL&t$#ps*wi^Um{3O1$;YE3X2W!@kd(Kn8e=`e=EQi_@3)D zw3x3{Fc7g;&!wmRCGL5uzC6&_NO-(?S;Gnfm7FgaBHt(*lI<@Ggyr|umGVoYipP?4 zy0;t7F{;AxZ-_l>MeD{ zE;F6|C7By&{&+_}m{m4bD1_Ip9%_+yAHAV-AB*?6>` zOv4>*DwpYacDwsDPb%>l8yV9zjq8!WM3VpF3CI3#CwOHs(@98($rUJ@>__uoY=B=e z+w1Ip<7^h&%X!?&<8=W^b_~rO_1Yx7%_CeA5-DR<{4w?c1{TJTg|E;Vn^!8B4_g+G zMr)kR$UV;uj_mvv%;7|3`7Azubjlh%@u@AvJ5FFt)iZ~6XXT%xBr7kmB94Rz zF8a%P=F|0^JSE9GKJD4kr={v9S|Zo*yy5Cs3N&XX&Nj=>Vv)%TX*}v*A{6*MDFZtb zEtl>4JPS!(vz-j?_v8XutFLb)OLpg?!q6$`BMnc}lM*rx>j+t1Q=crmajO4K^NqTD zHLBh$53cYp06(6-*s}#=oz|%+%Z3tdXcG1rUV3o&1j1K%bdl`;w6^yV*HUrO|fn&7acAYJ2X^^5Hy7 zLH##**OO)1MY0%d96fd(q#tj!OaJr-wzl=Sly*uTd9$2-Ns{<;DIB*Z|HI0iiNM6z z0l)m)gd)+#bju5a#YN}cnTEbgj-$8bJP(vp*|{*gwpfaj`X$u+WRwU6bJ*f!FZD~* zwJ2AW<1n;*FKTXAsql?&ghVA1murO&8(Nn?3nGV!0&si9y)N9LoBZsI<(YVCrBUr` z2$EWfO^B+#AGa_id`lTeh85; zKm0iEs5DW~4ACwAsU3z1o9ELzQ`VpHCgoJCkY9>1$MF@tDWp&CGr>gVlGSD9vX zlQLX76I_OLSg&JPjW5|yaNnWc>zipEVwarvSXDD7U^+_)QB=FRxwM0WpXQG+Q_VwI zO@pWYJ)hX+QN|BhSy9azG9pqn#U~7?r6O1a>PWrCqrKKcbmd9hG!wD%!3@GcfRLw> zRH&xu-W>Gg;Ng;M?GPp^B5;C0nm!PlSH+nqeDmpL+)OyvRbF#)TL|X92}LUMb7KPf zJ0Trr?s44Jw%v@e%0g6P15LrDr3`>3&^kOkv@#yjm6lqMA+yjb#FzNPDQC1omukP? zL50J;%waj+%z_rZ#Plgh4KC*0VG}Wu;547qxVVnskChT%f*u3hY; zcZA$1Qi-c#dh|HCoGo(6t7ZrD4e`S^`;s27GMb4gna_Q;i59aapysYPrmZwke9ap3 z@8&AxahLx6)zaQhMJESmu|I4jW0gidmKKI$J&ab4+R{=5KcX#ug0+Ucy?T$f*s3LC z4Ln1QcxYBECcS~3wGaQ3l7hv--JLgYagoke{*KmkgI~*<3;dkc6yJSH*cN~;**WQ0lwqu^? z4gJb+1uMzBIg8zD;v7bc^VlUt2mh7ijOD9Y-N)YWleOVr#YN|({+4<->v2I_ zdH(qWt%=RZe|D6(F};?vnZD?Zam@rjq8N%qg6D>2xUDp9mSef>M&D06<(R^WAK~K8 zEnjm?1DI7QYS)8ZBW>oV_N|W4B8A>ZEv&xEGWIE==rq-h2{PLAJSVJrd}kP>*b2*y zPpW8*HcJ{ze`?L*3OQ&v$@Ii1My>^DT$@tMO{#H=N)w)zu6CA(pypBH6Ieeh{Y>L) z6Baa>$unAE^)?iXLjL;PJ)fHTe7_4@<7Q$KFbcRTY@u)uLV{Cud1_9Bwd7%osl>a= zIh}K0>w@k1+ zr1j^bEWI-&^3f)>B;lU&HZzmwm3$#P(?_zggnJM5$iVSUQS1XlluJ}B^r!!i)((`s3#T0|J>Lrx+bpgKR^4Xy)ggWaiA_EX(J#!f zk&&U{XL=Yr8I;24;3)#fks_r6aY+5eK=0>)T!VvoU%P0p_l=|I$cP2~`G{}5oS$aj zavDzfeu`u_*7PhBLz3AYi8XW=5v^uIi)Ywo7}TxY6Vp_ol{ud1h1HjbhevR{6-R>U z5*&V4H~a{{LqTvbdWe?V(;yk&$+(37VOhtYLe@FJX7+P@Qj+L$9M=-H8g)T_8HIKZ zA&VB`4f)mQ@(@mwk8pAO`qVyN9Cu-FOtfG(HKk*a^2V_Wc*bR>OSzifPq$4D+Px2= zZ9;aUoV+~N^vjn}hhubg>`#szc=)V*e2G8hwEtz0U>FIDp038FpAh5@^U?f!6h6ne z2n!ALb17fYhw<3VARZMy;HXrQFXAq8l})}kRbr9JcKe`U6j|@^wRn7BQEbHv7flYe zdH2`yhQLGI8GLJ@-T{_eBGp;r4L?k(0gt#_L&n5q;m2zNcw@x4^Z7F$A8(XoAmetx z)A_PCV>JzaJhX!Y1WMIxeV861CD2YXj4S`{fb0jaHIgCTBMfj^ zobRmbe>tx0-Hv$7{hk5U;bGxWzym8nZLOl`PK9DWs|W3r5PK~MCjy} z33MvbnF_=t5~n_q0OAV!CyRgS zu!6m*AM-YM5+;o>AADrKtjni=TrGJ0`tE-PVzs*=mh((`a}66F~Na4@w~F8cohDx~>0KwmW*|MZTF0`Fzsuc?kJB ztt!3uWUuFSI8>xLzqaQ3yOQxAZY;+h{mQgEY_}Lsv!6g|#!5#e)wM_O*n#mLhy%Wd z(>fjj*`P|DX?#XTAA$$_Lf>x?Itbkmei#PXU=h08MIvEX?vcq4UyR7m(xM}p&Fdgw z{OLF8M?LVs9n_*aIoFdz}M>jA{>X2wYs+!TCoNEJegl7ppKGsQ7V= zx{P8FjuIJFJz6d0fBJtZ1~hOjDk+T2eZLu!Y6gGWrzgE&l)2K)d&Bf!&D7tX>S!F( z*@x?H!AlAsn6I?)7kEZ0JDoV*{0%rFAV%$A*uGoliw*Tavi zk$5?11XQYus<&P@Hr_8K6`DOLPzka|QQtE2Qd=J5LnK8YnHdL-%kI~EICLRZJ592Z zr#**zhaDpb-s7X?E?deIPAJt^Jrqr#L^eTa==`*0FOARTnWm zakNk)j9wE)v%P5IM~L%D%#TnBuP8}c$zUM<4EZvl`;0(QeoaPtj_v!ydmaXA$nA71 zM1lFGe^jj3_U;61_z^ry^j~7&SYjAhXZ@Iq_T4{Iuj@xpqDi1o7sj_Q zJd9PU!%sV8K;c?ZgO|-3fSEB*3$_Hwik|YTtY(NoUMcs{t!8D93sQ@!~agV z`a&}dNpfh?K?Di9U9X@k&tW~q4xMe@<@cO`3oG=9A_O9D5bQ!Dr5LGGqd$x}tRD3X zy2nE|By`;K#Bill1{Ligf3cV^eppaoD=E3p8jY`RVPdI$eA z0QHQUy7k`vC57MqX#!on`E&wLh4whR;0N#wKO!NC$Y>!Z97&b_uQZY zR6asHW65{{et^n9i3)-UB0}U&$JPlRE{UdPPx~hgA-H?PBO<;nIy)j-tI+f!m|fH0 zYgA0D(!Hdw8qSpNVhCv|Rwz)aGyQWyeuVB2Iw=#^^^FFCpDh%(2H@%j@_aa;MxKCY zUzQR1)#=o-cZ(qj_JB{EI9l{GRdOV-Wk6Q)XrCsQ-DypB?e!4`98tv?69QQMK~mkn zc3U`rSBYGug((O{es3;w-0O_h?TL&>RtD;wX5T}Of!i%fMuoChIQL`&#-Z~9V9{+QeL z{i6QNkvlua-SpJKKSFmepg9Ka1GA}WgHM`uPygV+bTG4}?~t1M68~aBM;L^*s6h+W zV2FSK?)!50f3t@GksMKM5s)b*jnHd*Z|6Mdp#{tRtYh_gE3>=+Iurd*HA}D03I<4n zWlNpt#`!Y2u$*#PkL1bI^VtA z+XOEYIL6@q_Wn0*CY!|@|5ncJVqg@)ArNJ&Z#(X=lDtfKZuA33^E+lr zYHANaZ-&@j-ww2$&GkUo@2-FRXTskWs-DwQWtlY#@jFosOfa2ku`8_3aaFolA!i7# zGU}VpY*B45Z>gU-d_<=LKym9~U;p-ebE?E%)_)C?di}pWjyA@cj?k1S#k6-$E)XMz zV7Y8;5n$#sY$cScBBE3Sf-NILwzcWOhG>CZN#9RHrQW@4VmmQ)vzCw2p z@5rK5y?hvHQekD};xv%(g?cg!Wt-;-i*`NbSA#B&My7vyKqqluO@5okX@-^g!VUpI zes%Iw1*r4;vwg|$iJv1^^>h*Gdw+xH=d{u?;lLfq%uLHpuk?W@A3ye^Wh%;rU;Y!K=Dv39@PNBOVNhqqvg+j53g<>GtJp?jp>314+)t|>k3Aj-`Lb2ESU7(94mr33kE;wQnR*;lcFj!m265P?yJ`5l-85hn!+3a|I_*?jTvqQLe0a++b)H)Ypg9@in#X4Hda+rEs2nCb;J z3COMp`VfM@*b|ORbo6JL6V;A9xM!#AXSunubRpHt3Nr=5|57#DeFb0)$oF6Sz3uEX zTY!041qZqF&P=jU&;tZI84j>sXWItg-`M`$^r(wnJ{_(7QM4^FA~p*vc^dba`$z@ejJVv4bdxXYz;(CvHK_z-V# zAWt<|5smLQM*-12|Cw~f#(Fbv1gk&Lr4SNK`%QyaQ&8vE27F06hJGEktvtrxwv&~* z2vRn%`4OP?7wxsgFKkKo+Rn&97Nvou4*}pHxqCmCH(Ro*^f;*Wiw-!1!Z#ZSXqlK2 z>A-gG+ zhOO;?AXrmwVriy;@csRcJ;ks5MaF@pViOG8>u^w5Z#^2~?4Qm0Nk|4D?Bm2Qy}w2o z_QiLuefYneTVQ?dUSFUz)i^wz<+A;W>JQcD5!68sEG?w|fY7#naqr>A&$365vJr$~ zkpaW#j8}7DM){8}mHjY5=KQ8*@BH-orT5V$KL(!}``!D;Jp_d4dirndnaa^!`H6qT zi=|uhj3p*|Y<3g<*C)ANLvNNaTxV$YKbtgx~gEn6$LC zps4{~7e;ERC@Q{z2}qRxEMd=UJ%NCGix*!WmBg>daUE&>tTflbTe1wN5tl`eP{=w~oNr>tlxk*jX zyuc1b3j~q2%hQ^J96$GiqEu8&v@*48t;}_>e)M)>Cju}JIK>FR(7D;NIJw{UbH4f) zH1t*~`Y9pB7N~w61u6ml$Y2;n>{$weH)$Y~9v&UR7r+Xte)W&3LhJLV{Uw2e?~f33 zDKTE&dau2@<^q}^LX!Zu2`9|jYt>@|{=EQeY83E1Kfq#2cK6x4bd{cA`O1G2ZMNYE*@Ym8CpQbQS!g9*O%^l*={e7KGhKm zI--bj@fRB*GccfbEIW)>8;BrI9dNwPdO+bF7f&O>0TT)YFOOB!Z*KTQE5V$f&gH=Y z(`g3$)<|rdFH8aC8B=Uk6!QXn>xkJc#hFi!fVDezQU9&=>{?JyyE(QMnIfv#&FEe1 zzhvKcw8al?!1DI}`*$1|hoYlHI;x~3Dk^TshD)bBOEEALi3IU9Hda!>ELb4Z@7%pV zQX}$!kXZxi=T~1f)dqEO1A<~?C_P@cR=YpPe8TkyZpL+RtG|Q^j$M3scwW-Y)hO&R zv*GLIsdmckISt4JDJ)w1WBqL7#V%G$F^fwse}6_mC0%R1Io9FjcDv{IzG<67&-*|O zW;-4N!QpokCnA*sJ|P&KQE1@5Z>Dw5-!>w)a^wjLTE=kLaLFhoZ{mFeJz8{~^#0=*w!E;nH)TcztqV|in7j`z))Ahw_M4l`J7SjI z6XKS2_wvslip{(2wf%P5YXgtg_f^OE%tV;xHR1VbxlG9W6@GKIJmW)AWN0HA0(KH| z6n(=mVIrRa(b5t3B~nr9`}^l-eZAo>!=1vyw5`~lo*qQyn89U%EId-`V1!`8hdeH7 zNAY@B`F-!cBQI${DnxxW6gpSu9!6qj4tjg|2>R+CFunG^g^haCdG~irkJ<3r2^7~5 zcI0<-T06*PJri$B#nj8HsOhwUA^7irGLg+fZ!=0e z^WJnsZgH^a==J*gI-)?_&EScKC_qJM+b`w@7py||l83K}|OX6C@BF0wl!3 z0VfiJ@wGwO3@$X0Ni-}p#_=j`u3C|Ip=*N2e}Z1Ubb5;5P>A@sdM57|z40_JlRZw! z<{K??qF)>K!%Afeuljl(cpi*0#Ak(+s^f6)-1|m(_Hl6IeX{~|?8MX@1$A|ZewSIx zU4|WJaRI}cD@QY!73S>DwYWL;vowyBIf2lAstJX)CQOJ%j10KBd!LehD=H)`Y?PKY zekj^A>B)@SsOGfyu@%qmm8|Qow%qk;!G7^y_lD-`eT5xSq>P`vN2`jtFw6@xAW+2x z*ATlakN57ANa=~k>=!qe6y`Hr@y}DE4Za;c)M)heq0Fd`nTqvdDH1W<<~i1XuM4s&dOt4ERATA7g-|5yHLS7M+keysQ-;at>vVM~R+iXxq4 z@jX5}Typ%}x*G7VMPL{paejWVeDQayD*d&eAY%9ww5zUQ5&G@ARpGojlMFf&jzolg zGd#&^^HFL<^$!b-##ir`jq{#NZgSb^s^cPs51j^M@z6yX87PbO1i-48=6g2x>EAN8 z;O&)Dv0M(8?OFVcrn1Djb1CIF9VDu6--6F&YDHHbEc2k_2&4Ue5BUMOMG@!Ebm*QH z#0vHyyf_LJ-W|Z---KDMf)h#^Vwk3D=o3EJ z2sc^VcSjE8b0M?G6*ebIxt07GZ|lVqsCvdj8aZdjn7O4|DlA6ujHf&1nRV$+0~`J= zV2Y7%g9D%8gOKHJJL(MsNBVB(TpsYB?f$XvqvDz#rJ!O!-ZDh>+ltvP@wQZ6EN!5D z-`IMz_q&X<-|sv?;PUivS)qS(s)=UIbpMYL_Wt+8*zvErUhNa_PV=dQJ=*ZKYkZUa zSzWlg!iM*kexciMUErHvxVyyqc`S_CK4qk{t?<8YYKcHhopkY*YA^Zp@FN7zy&=5~ z^%4p-`HF$)^{FyJCwJ|H6*4Fch5)1O9I==EW z`CV3S170>=^?c4)E4xo;Gf7aZSQ#sexa_y+`i?HGR+H4$2knaSJrrQx&G|{A{>zPN zxgJv+#H28Q@_sJ570C=-bH7WF6~%z5#Bhv{-`=Slf|+U9Q#n=h5es6-E!C+kzstv7 z`dahMqGgM+$tc0paVbnW%X4Qoy{;(R^EdILJP)5TkB0EmW=?+fjxSAP=+w#EcH3Jj zX>X^4uQ&E`vRt8me+uJnACxOTXD0}=P3sxC8)a%R)JWdVha>#%ojtGL>qy0L#~-9l z3pur{%v30CS6ePg$*J}Sz42Uj^I6apNK8-H+^*}?QbTM8KTDMY_S9SUA3a+wY9q<` zvC+?J)Zu>zT8WSF$n{ROs3epqyRpLjlJGQq2SueL%FA`!=3F2#oCpGlxA!YpT)S;| zX)epoO%wce34VLIv?%Xslk(qB2x{``JvB{X^p8^hn-R{jz;b&jyboRD=ev;y@1MND z%2^W`_W@#UY8KQb{&sD@AKTL5s~g}*1N%UGnA5`srGjl(BdO$%j;4 zK-AcnHj~G0?lkUiGxcv+(_-x8IwD^Wb<#d2w1 z0Ur!p<{ajWf{GL5wK(N5aQ&v-RwFRnumnBFD7cc*kLlzn1SZ4sC_3_4Ff|IGWHngU z_Ckc^sjpeqWcl-zlOS2SFEG)!`<>)n z-M_@j5~+O6KSwYye?QmN#Eso9(o%C#382-b0N;a*PhY6Gff+H4^pn z{^!Vv^)mIc`hPS1D)m*7fiqDRTJB6G#^wqvaE^5Ptj71eLk2S(k4!ORkfL}o#}3!X>XlH4I(d9#Y;)aCPsU0 z-x%(t+Dz-%e{=s4LjqI5m`1oTb;pUHJJPx>xDefqikf}DKs!T^_qzFBKu5h?Oz>ZZ z1MIDz&r=@V-TbVcHRt>+IRZ&fwe=WH7ulp1AFutUlfZC{95_h-r-v2A2DRJ+t+76b8wyXf#JYyx9bhRy^e!g_eXL&ZI&F^Zj0EjP)!WT0wPuU^ zW15EgrN={~k2YmJIgwE{?og}m*GIbauVg6qkrPx`#?;?K)_qZ3F;zQ}RA*(}x}PC? zK_%;olkHi%)wcJ=AN}D&Bb$if<6+w3_jv4?y-sIa`wb~3CnRSCh4HdA`g-J`I9Z-} zyG8WCaYLiQl0dLM2Vjo%Z7U5Xe>hoaQSTlP^}Na5LQdFyiBh990KIlK-*T>S4FhY& z;qwi)#QNKkhm1m#{O#B)vV&PgNoH!Vr5Vv&)!!t@Xq?%%4Bh8rp%j=P37nGLZQiOZ z`1I+0TXJ$za`M6I=D4=5O2(b$mF1svxkYbjrDdFcjIEiB^M+YyUj4aTSPz74{~+7# zGy>beZN&3h)e56)0^1*#S7$bP~&U&xa6Wt zxOTr;!C(bkVVqjw?0>A{1*3v0vgktzo|IlpOf}3=VdS3S-BCm(GkNx`>LljAzJ_YNcq4+z_%0-p!T3 zCV$Kdvl$^pmUNaXh1hH0yO*FdYy77pKv#=0u3Qc=^AnIQ&}OdDymZzz(>I~x=H>>3 z9{*0hZT2@VIfPjDvrL7Nfx(=P5*c^4(NN;~%rp*$<$XtS#8d`i)P29(WBR{aTL`LC zqu-tv{>nOinxPB~X3g4Q;7+Aa>iK0u<1usLcGqoCBDqorb;86IH?KQJ z;ZftRkjPe2Y#Uud8j{DciUZLdZ-|^AI=LvwcMu2oVjWM5Z9n1ajd?GA(p3hnW$Sv* z9rG4p!yk_fTpRtTKF=+_w3&VSI2vz|^8V;FPkpQ`Q}{ey$zHSeo5Ccr(`*w>fibrj ze`jL-_=IW#l*1XH^ywZ`O0!o6Q7E<7zEB*Qn_C;hW%FKsvXCqI;lCHt_~D=KSg>_P z>wb@k2xFq(`P{_)f?R;9Mw@4D%M+7AoO(}rK;mE9o?lL)oE&1E>OPy3kdwni(r@0N zuFMe7j*!TZ>=<#@(ap*`bv680?QAVWw<$}67Q7QaLI>PMb8)itG4l_$u#tn8sj80lF9Vl z6?W8VilCu=u73iV9K;0oQ3;Cfodb5`UK~17-43Zaa+&FQHy#I*K+d}_qdse9af6{T-Bjq`oEONPD|QThyZlVY;70|SFoJ>lJN zR9_cGQ4&fvTG!3(mE*;623sGP6Lp0#WS}$^ygB<36Zfm^fqx4{{yXISg$t6V${9=j zypq=nu#IfueQnTYG}~4 z)z|N-rx|>7$~d#)+*F;V`mf|kiE`Qa{dGIt$w!%ziGF*psN;3N2*BGQCo&+bMVS#p zbVza^JH(tOO-|B+itELf+T}Y(yVpoEls(B3>K}3n@;v2(WAPejHZlU<4pYMyV_GP7 z-Ps!}X7K1AKX$*lSSNuBz|UhD(^TJu0J5_-RW1e%$=>bsjy{^7Cv3<1WgT1@>6eKM zV_bdQ2vYl#9Yf+Ow>If*lJHVzqy0Y&PSc@`nkOkTh(bDR>7^OAw8BZc0WqiqiOtP? zrUPk04ERf+{{?p$%=>P!RAzBU9W3{#C@Dd{B`GDPc)~BnEl=Q+AkW>GXM3tn0VPPU z?v-?7t}qXh#i_~WxxEp7qtj`AnxWc5xUzl}mdvERYW=qY881duEBYL3;-{j%{z3)Z z51OWP(DHZ`EsB$oQhM;mv1#h74?h5h87Fn!bYqLApa_JNsN-S?5yXWOzl0i{e=ING zzr2R*h_k17-X`Bk*w%ET+3UP_sB+`Y^^EKn=f*XUg!fy_K77l`9t5ykpv^2wkWhJf zIbxQBU`~}xf9Wiq3l$2KKrpWrp@sr1Ou_HpqvYgdV~@7&xt7%OyAKdYU^O+h)_;9W z%;0jUKOB@|RB4vVwgY6}e?wAs4eLzghPVRWti-SFqHMhw}B?Agjv_K=Z1ODIesWM2wN zNMsu|MfN=^lkGjv`yaf&zVpjmm+Klc&+~jf=bX>E?{nXT2JeN```q(C`qBq(vRK)y zS}CE_x(lvZTE^jf2U+6OEgF3V%M~cQ{+^K1@yrFdx;0zLGI9(gk;@jK!R~7j3%d7`55nAYgIeeStBd(?B9TCGGqZ765acx>cN!R@egU449cHnlL%X_${x2D9CvNSXxjvu%12* z#S-!y#alMX?LxPx`f*}&dg+2bvkh$-w+fTkB=0S&-Dmc^^|w_z1y^7k2`Q=9xc%V5 zU6n}Wq4eU!g_z?QkKd<_Ri#ubv_o<(dl-VCvDG00gMHvJ~J^3 z1y-tsguk0SQw25=!v)`&w5?FXKkiJW~zIS&wGg&~e^APghA z3EffD_5`_D`^}+iJ_JaV`?-JfEThss@_bhDt0AoY)=Bfo89-x4qiCQC&{=5idc;GY> zc2GACQY-G^VLUXlG3C#aJuPH0ESZFEAei*^{lOB>Zghc~84*2;ro><{GIDZo>s{U4 zx)L?FNCz}L<;uR%MMb>7*T3{5(C6ZCahzsx=yneA2g0iGj@aXX?l%_70$Ot%g2Oz-`Fy9y_Y@U|$)pQj@2LeVlshaU6>fqCYR~Hd<~g9ce;dv}mZX_2DI4 zB%UVK>*8*#8r*QVf( zGQX~vkWf~K-x#?`r0qPiXluz@5k@5ytz3qYX&@qv0AsIn%lFe=m17P4-Qa-&^bZP= z{LnO_!r?{694GJ;9+{Y!@YoTL{`M}FFBJ0fz>HL~yW8&OcN(6H!9V%ofX-4Y)=uQU zL!3Ft<`e-PFRiRx9X%99_rd)2SfwR|gR(?A9arfN zW7T?G&SPDgC(9jVwh$Ws{tXEYSMI#_0EiX7+xsN_oXZ(dYmSbMF(0Sy>%%DLeVR;U zX4FXGoqd|pt`K(l1O0*E^G_z zlai8>K$?nwhE^+?Yi^Qw#5E#Q?U@s0Y8q5qv+3-N%@)Ktb<5H8>`m3Lncana-5Nqs#ctadGI^W0sPb6Zw?TqZ9ns+@WN0H4qC zmI~Q%fgEqqVv&t4&wH&I9DE6A94io#@uJHh~lD#0>?&Nx@lH!>t> zBmzABElw!^8@Pr#i8ZfE@k=(OrCUf{KM_;mLc@I(t$=KsD*^n|5;!MATid~TOwO*y zxLCmYblgXofTI<=4|!KIKcz7v*;lx_si7uqk2@$bka#Brj?C&ZsJRIUNvp30{hvOn zS`MV+$z~To3qTs*t*HrGPl+f2*~3*rgSh1L0L{gEPh*Q~9q*X<9s_g$HYz7%@1I1l zeSDUD3IKybH;oN?rcIW{&K8SDpW|E(y?z!g0N_=McyElwrydA{H*JFjQV#x(v;Nu` zsw3`FBl1TQQ?{(>kyHD&c)NdE?CR>N1Oz{;=RE4$dMS42W|3SDX*$>f_^zd8G%aF$l6>Bt43jd-T%~(!Z!U?yhUdR8O82< z^EOp1mXAmp3}lmZpxp4D*0wh^tQ|_&ZhOkicvodAa?FMjOs;_6C6sc(ZV>w8NJWjP zX$uGlwCajZsnR|u>V4SJ6=W|;OV4pxMv`|AfD@A6xLA0*i(v&;iihn4v#hc6jK9OV z6Yw~!tlq!;{wDZGs+I*Pr<16tOw7$$=&2iDw&a@Ig=GLcDHT!2ZwCGf2pilS%1IP>-#=W+9m-3LO)L~3CbAM?Sa=e+JyC6cG~8;-SrBCHKL&T{^k+d zc#{+g!T+XB_$8{C7|0igP-$OP+M+c7klP3BFLs3>3GudFUhegYPN0fq8ghvC7nPJm zb9lYJ3wuibRV3bl5_q$K zf0##<`QJVEl1GS0Gs2w!wPyibg=VoK{E0Nnz1?rOPu?%Xe#wm$2xNjFfdi+68lZ$j zV9QTrsPVL9Wj+1#i?z1~&A$?Cjzjs17B~$rFEOATVM*8&A40_bKMkT59e{%@LU9PC z(2KBxmDR7UIS@+`0^eiT;}(HXqX1f}M>7r&mWBLx-bQ%l8h};+WPx!;u0n7ch^`~B zS$urF)YpF;vIXHGOTu)MPVK^3rgq5E!C<~V0iO3DyKn#T$-gu_6Tg#EyDTuF!8}2$Az-_^ujsdNOY{=F*b+talbZC3w9b zeWstIq{!54`TCaq`=*7Zhs!}DGH4NzSuN$iOwh&Zncd>Id6+gKw&f!8zUkMBWsM^( zjO10l5{sR8Yk2E#$CTvwC{lQk=F2Z9mdFcLi7e>)g)vD?I>?QM?-v|p35S>%S-rms zCzwdD3tpo&Zd$ilDTwYNzTbkqDv+^t8PVnTqs_x|Th>+(G z&7?|QrQAk^K?7b*289tF$pZ#DQsxP$#cmF8c-;V&py#@VPRt`ITN{HNokHyxX2$MI zDHl9FD!Fv#ya)jmQqL6)Gn+4@G2lyG5z0T{K#+L${%n*%I<7qRP+F(|Pj8JVaL1sS zNr#7CjZ$#M^_+?A&vV4UBlcDcLfqYs1V2`r?NC_UF4CCisFX9SaQU3n^=!1PDT@(s+ z5i}lC-jah7k|P$|P;f?jb@auvXF4Ckn}GQTQ4@2iIL*j+bwUzE<|o#|grYlSFxdwA z9)J96t~$Qd1R-K|l zmnWh0WjRedk+|OaOT91h_F4Ns0Snbd=6sQ{i?qMx&HNU-e1B^E8rAyh6ZJ|qEx<-X zk>t;k9h*^f%+%b+=;@9lv2MDWn04fTX3*>K_qun`I@^A%U7t3y8+Fmeg{@^vwyuse z!8MGUffjn);8DvI{CtbA}j zxrc0)lJioc#Lnny*_&co_Jf;oKavkyr#`RiO=PlboRp+ zQ>yz6@{wiEJ3R4zm*o{-l*tpfrZTk6n!8k*n}HInhTJQ382EYl zR^j@CyAonB@R20z(3Q{p` zWsH0pYbU$WuwO$itg)MNWJw4Px=c+D;srEUlkXaEbrvY*E{U&Cj-JWuG&tQLN6k>( z+4=0{X4X3KHW}<9B|T?`Cx7qlh#9qREQc9zpHes9v3%6Im{b0zb?;h#(5d4ws;Nv6 z-v6g}O`%U9b`KvJJ}ygPD9n0XLlc(R|Kfe@Vzhm&y(J)ZQtKPWxAJDN{ Al>h($ literal 0 HcmV?d00001 diff --git a/_static/thumbs/vqls_zoom.png b/_static/thumbs/vqls_zoom.png new file mode 100644 index 0000000000000000000000000000000000000000..1bce7b344c31f78aca573bacc8453affbaeffdd9 GIT binary patch literal 12068 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 N|KgQ&;WItI{{;%dZU6uP literal 0 HcmV?d00001 diff --git a/_templates/filters.html b/_templates/filters.html index 4a5f65f74e..3d1f538d09 100644 --- a/_templates/filters.html +++ b/_templates/filters.html @@ -1,115 +1,115 @@ -

-
-
-
- -
-

Filters

-
- - - - - - - - - - - -
    -
  1. -
  2. -
  3. -
  4. -
  5. -
  6. - -
  7. -
  8. -
  9. -
  10. -
  11. -
-
-
-
- - - - +
+
+
+
+ +
+

Filters

+
+ + + + + + + + + + + +
    +
  1. +
  2. +
  3. +
  4. +
  5. +
  6. + +
  7. +
  8. +
  9. +
  10. +
  11. +
+
+
+
+ + + + diff --git a/_templates/page.html b/_templates/page.html index 2b0b379ec5..1880e4aedd 100644 --- a/_templates/page.html +++ b/_templates/page.html @@ -1,30 +1,30 @@ -{% extends "layout.html" %} - -{%- block comments -%} - {% if pagename in ["demos_getting-started", "demos_qml", "demos_optimization", "demos_quantum-computing", "demos_quantum-chemistry"] %} - {% include "filters.html" %} - {% elif pagename == "videos" %} - {% else %} - {% include "localtoc.html" %} - {% endif %} -{%- endblock %} - -{% block body %} - {{ body }} - - -{% endblock %} +{% extends "layout.html" %} + +{%- block comments -%} + {% if pagename in ["demos_getting-started", "demos_qml", "demos_optimization", "demos_quantum-computing", "demos_quantum-chemistry"] %} + {% include "filters.html" %} + {% elif pagename == "videos" %} + {% else %} + {% include "localtoc.html" %} + {% endif %} +{%- endblock %} + +{% block body %} + {{ body }} + + +{% endblock %} diff --git a/demonstrations/barren_gadgets/barren_gadgets.py b/demonstrations/barren_gadgets/barren_gadgets.py index 09d3c89df7..0e2929dc41 100644 --- a/demonstrations/barren_gadgets/barren_gadgets.py +++ b/demonstrations/barren_gadgets/barren_gadgets.py @@ -1,130 +1,130 @@ -import pennylane as qml -from pennylane import numpy as np - -def non_identity_obs(obs): - return [o for o in obs if not isinstance(o, qml.Identity)] - -class PerturbativeGadgets: - """ Class to generate the gadget Hamiltonian corresponding to a given - computational hamiltonian according to the gadget construction derived - by Faehrmann & Cichy - - Args: - perturbation_factor (float) : parameter controlling the magnitude of the - perturbation (aa pre-factor to \lambda_max) - """ - def __init__(self, perturbation_factor=1): - self.perturbation_factor = perturbation_factor - - def gadgetize(self, Hamiltonian, target_locality=3): - """Generation of the perturbative gadget equivalent of the given - Hamiltonian according to the proceedure in Cichy, Fährmann et al. - Args: - Hamiltonian (qml.Hamiltonian) : target Hamiltonian to decompose - into more local terms - target_locality (int > 2) : desired locality of the resulting - gadget Hamiltonian - Returns: - Hgad (qml.Hamiltonian) : gadget Hamiltonian - """ - # checking for unaccounted for situations - self.run_checks(Hamiltonian, target_locality) - computational_qubits, computational_locality, computational_terms = self.get_params(Hamiltonian) - Hamiltonian_coeffs, Hamiltonian_ops = Hamiltonian.terms() - - # total qubit count, updated progressively when adding ancillaries - total_qubits = computational_qubits - #TODO: check proper convergence guarantee - gap = 1 - perturbation_norm = np.sum(np.abs(Hamiltonian_coeffs)) \ - + computational_terms * (computational_locality - 1) - lambda_max = gap / (4 * perturbation_norm) - l = self.perturbation_factor * lambda_max - sign_correction = (-1)**(computational_locality % 2 + 1) - # creating the gadget Hamiltonian - coeffs_anc = [] - coeffs_pert = [] - obs_anc = [] - obs_pert = [] - ancillary_register_size = int(computational_locality / (target_locality - 2)) - for str_count, string in enumerate(Hamiltonian_ops): - previous_total = total_qubits - total_qubits += ancillary_register_size - # Generating the ancillary part - for anc_q in range(previous_total, total_qubits): - coeffs_anc += [0.5, -0.5] - obs_anc += [qml.Identity(anc_q), qml.PauliZ(anc_q)] - # Generating the perturbative part - for anc_q in range(ancillary_register_size): - term = qml.PauliX(previous_total+anc_q) @ qml.PauliX(previous_total+(anc_q+1)%ancillary_register_size) - term = qml.prod(term, *non_identity_obs(string.operands)[ - (target_locality-2)*anc_q:(target_locality-2)*(anc_q+1)]) - obs_pert.append(term) - coeffs_pert += [l * sign_correction * Hamiltonian_coeffs[str_count]] \ - + [l] * (ancillary_register_size - 1) - coeffs = coeffs_anc + coeffs_pert - obs = obs_anc + obs_pert - Hgad = qml.Hamiltonian(coeffs, obs) - return Hgad - - def get_params(self, Hamiltonian): - """ retrieving the parameters n, k and r from the given Hamiltonian - Args: - Hamiltonian (qml.Hamiltonian) : Hamiltonian from which to get the - relevant parameters - Returns: - computational_qubits (int) : total number of qubits acted upon by - the Hamiltonian - computational_locality (int) : maximum number of qubits acted upon - by a single term of the Hamiltonian - computational_terms (int) : number of terms in the sum - composing the Hamiltonian - """ - _, Hamiltonian_ops = Hamiltonian.terms() - # checking how many qubits the Hamiltonian acts on - computational_qubits = len(Hamiltonian.wires) - # getting the number of terms in the Hamiltonian - computational_terms = len(Hamiltonian_ops) - # getting the locality, assuming all terms have the same - computational_locality = max([len(non_identity_obs(Hamiltonian_ops[s])) - for s in range(computational_terms)]) - return computational_qubits, computational_locality, computational_terms - - def run_checks(self, Hamiltonian, target_locality): - """ method to check a few conditions for the correct application of - the methods - Args: - Hamiltonian (qml.Hamiltonian) : Hamiltonian of interest - target_locality (int > 2) : desired locality of the resulting - gadget Hamiltonian - Returns: - None - """ - _, Hamiltonian_ops = Hamiltonian.terms() - computational_qubits, computational_locality, _ = self.get_params(Hamiltonian) - computational_qubits = len(Hamiltonian.wires) - if computational_qubits != Hamiltonian.wires[-1] + 1: - raise Exception('The studied computational Hamiltonian is not acting on ' + - 'the first {} qubits. '.format(computational_qubits) + - 'Decomposition not implemented for this case') - # Check for same string lengths - localities=[] - for string in Hamiltonian_ops: - localities.append(len(non_identity_obs(string))) - if len(np.unique(localities)) > 1: - raise Exception('The given Hamiltonian has terms with different locality.' + - ' Gadgetization not implemented for this case') - # validity of the target locality given the computational locality - if target_locality < 3: - raise Exception('The target locality can not be smaller than 3') - ancillary_register_size = computational_locality / (target_locality - 2) - if int(ancillary_register_size) != ancillary_register_size: - raise Exception('The locality of the Hamiltonian and the target' + - ' locality are not compatible. The gadgetization' + - ' with "unfull" ancillary registers is not' + - ' supported yet. Please choose such that the' + - ' computational locality is divisible by the' + - ' target locality - 2') - - - +import pennylane as qml +from pennylane import numpy as np + +def non_identity_obs(obs): + return [o for o in obs if not isinstance(o, qml.Identity)] + +class PerturbativeGadgets: + """ Class to generate the gadget Hamiltonian corresponding to a given + computational hamiltonian according to the gadget construction derived + by Faehrmann & Cichy + + Args: + perturbation_factor (float) : parameter controlling the magnitude of the + perturbation (aa pre-factor to \lambda_max) + """ + def __init__(self, perturbation_factor=1): + self.perturbation_factor = perturbation_factor + + def gadgetize(self, Hamiltonian, target_locality=3): + """Generation of the perturbative gadget equivalent of the given + Hamiltonian according to the proceedure in Cichy, Fährmann et al. + Args: + Hamiltonian (qml.Hamiltonian) : target Hamiltonian to decompose + into more local terms + target_locality (int > 2) : desired locality of the resulting + gadget Hamiltonian + Returns: + Hgad (qml.Hamiltonian) : gadget Hamiltonian + """ + # checking for unaccounted for situations + self.run_checks(Hamiltonian, target_locality) + computational_qubits, computational_locality, computational_terms = self.get_params(Hamiltonian) + Hamiltonian_coeffs, Hamiltonian_ops = Hamiltonian.terms() + + # total qubit count, updated progressively when adding ancillaries + total_qubits = computational_qubits + #TODO: check proper convergence guarantee + gap = 1 + perturbation_norm = np.sum(np.abs(Hamiltonian_coeffs)) \ + + computational_terms * (computational_locality - 1) + lambda_max = gap / (4 * perturbation_norm) + l = self.perturbation_factor * lambda_max + sign_correction = (-1)**(computational_locality % 2 + 1) + # creating the gadget Hamiltonian + coeffs_anc = [] + coeffs_pert = [] + obs_anc = [] + obs_pert = [] + ancillary_register_size = int(computational_locality / (target_locality - 2)) + for str_count, string in enumerate(Hamiltonian_ops): + previous_total = total_qubits + total_qubits += ancillary_register_size + # Generating the ancillary part + for anc_q in range(previous_total, total_qubits): + coeffs_anc += [0.5, -0.5] + obs_anc += [qml.Identity(anc_q), qml.PauliZ(anc_q)] + # Generating the perturbative part + for anc_q in range(ancillary_register_size): + term = qml.PauliX(previous_total+anc_q) @ qml.PauliX(previous_total+(anc_q+1)%ancillary_register_size) + term = qml.prod(term, *non_identity_obs(string.operands)[ + (target_locality-2)*anc_q:(target_locality-2)*(anc_q+1)]) + obs_pert.append(term) + coeffs_pert += [l * sign_correction * Hamiltonian_coeffs[str_count]] \ + + [l] * (ancillary_register_size - 1) + coeffs = coeffs_anc + coeffs_pert + obs = obs_anc + obs_pert + Hgad = qml.Hamiltonian(coeffs, obs) + return Hgad + + def get_params(self, Hamiltonian): + """ retrieving the parameters n, k and r from the given Hamiltonian + Args: + Hamiltonian (qml.Hamiltonian) : Hamiltonian from which to get the + relevant parameters + Returns: + computational_qubits (int) : total number of qubits acted upon by + the Hamiltonian + computational_locality (int) : maximum number of qubits acted upon + by a single term of the Hamiltonian + computational_terms (int) : number of terms in the sum + composing the Hamiltonian + """ + _, Hamiltonian_ops = Hamiltonian.terms() + # checking how many qubits the Hamiltonian acts on + computational_qubits = len(Hamiltonian.wires) + # getting the number of terms in the Hamiltonian + computational_terms = len(Hamiltonian_ops) + # getting the locality, assuming all terms have the same + computational_locality = max([len(non_identity_obs(Hamiltonian_ops[s])) + for s in range(computational_terms)]) + return computational_qubits, computational_locality, computational_terms + + def run_checks(self, Hamiltonian, target_locality): + """ method to check a few conditions for the correct application of + the methods + Args: + Hamiltonian (qml.Hamiltonian) : Hamiltonian of interest + target_locality (int > 2) : desired locality of the resulting + gadget Hamiltonian + Returns: + None + """ + _, Hamiltonian_ops = Hamiltonian.terms() + computational_qubits, computational_locality, _ = self.get_params(Hamiltonian) + computational_qubits = len(Hamiltonian.wires) + if computational_qubits != Hamiltonian.wires[-1] + 1: + raise Exception('The studied computational Hamiltonian is not acting on ' + + 'the first {} qubits. '.format(computational_qubits) + + 'Decomposition not implemented for this case') + # Check for same string lengths + localities=[] + for string in Hamiltonian_ops: + localities.append(len(non_identity_obs(string))) + if len(np.unique(localities)) > 1: + raise Exception('The given Hamiltonian has terms with different locality.' + + ' Gadgetization not implemented for this case') + # validity of the target locality given the computational locality + if target_locality < 3: + raise Exception('The target locality can not be smaller than 3') + ancillary_register_size = computational_locality / (target_locality - 2) + if int(ancillary_register_size) != ancillary_register_size: + raise Exception('The locality of the Hamiltonian and the target' + + ' locality are not compatible. The gadgetization' + + ' with "unfull" ancillary registers is not' + + ' supported yet. Please choose such that the' + + ' computational locality is divisible by the' + + ' target locality - 2') + + + diff --git a/demonstrations/barren_gadgets/layered_ansatz.py b/demonstrations/barren_gadgets/layered_ansatz.py index 22f324835e..7a26d9e059 100644 --- a/demonstrations/barren_gadgets/layered_ansatz.py +++ b/demonstrations/barren_gadgets/layered_ansatz.py @@ -1,54 +1,54 @@ -import pennylane as qml -from pennylane import numpy as np - -""" Based on the SimplifiedTwoDesign template from pennylane -https://docs.pennylane.ai/en/latest/code/api/pennylane.SimplifiedTwoDesign.html -as proposed in `Cerezo et al. (2021) `_. -but changin the Y-rotations for a random choice of {X, Y, Z}-rotations. -""" - -def build_ansatz(initial_layer_weights, weights, wires, gate_sequence=None): - - n_layers = qml.math.shape(weights)[0] - op_list = [] - - # initial rotations - for i in range(len(wires)): - op_list.append(qml.RY(initial_layer_weights[i], wires=wires[i])) - - # generating the rotation sequence - if gate_sequence is None: - gate_sequence = generate_random_gate_sequence(qml.math.shape(weights)) - - # repeated layers - for layer in range(n_layers): - - # even layer of entanglers - even_wires = [wires[i : i + 2] for i in range(0, len(wires) - 1, 2)] - for i, wire_pair in enumerate(even_wires): - op_list.append(qml.CZ(wires=wire_pair)) - op_list.append(gate_sequence[layer, i, 0].item()(weights[layer, i, 0], wires=wire_pair[0])) - op_list.append(gate_sequence[layer, i, 1].item()(weights[layer, i, 1], wires=wire_pair[1])) - # op_list.append(qml.RX(weights[layer, i, 0], wires=wire_pair[0])) - # op_list.append(qml.RX(weights[layer, i, 1], wires=wire_pair[1])) - - # odd layer of entanglers - odd_wires = [wires[i : i + 2] for i in range(1, len(wires) - 1, 2)] - for i, wire_pair in enumerate(odd_wires): - op_list.append(qml.CZ(wires=wire_pair)) - op_list.append(gate_sequence[layer, len(wires) // 2 + i, 0].item()(weights[layer, len(wires) // 2 + i, 0], wires=wire_pair[0])) - op_list.append(gate_sequence[layer, len(wires) // 2 + i, 1].item()(weights[layer, len(wires) // 2 + i, 1], wires=wire_pair[1])) - # op_list.append(qml.RX(weights[layer, len(wires) // 2 + i, 0], wires=wire_pair[0])) - # op_list.append(qml.RX(weights[layer, len(wires) // 2 + i, 1], wires=wire_pair[1])) - - return op_list - -def generate_random_gate_sequence(shape): - gate_set = [qml.RX, qml.RY, qml.RZ] - return np.random.choice(gate_set, size=shape) - -def get_parameter_shape(n_layers, n_wires): - if n_wires == 1: - return [(n_wires,), (n_layers,)] - return [(n_wires,), (n_layers, n_wires - 1, 2)] - +import pennylane as qml +from pennylane import numpy as np + +""" Based on the SimplifiedTwoDesign template from pennylane +https://docs.pennylane.ai/en/latest/code/api/pennylane.SimplifiedTwoDesign.html +as proposed in `Cerezo et al. (2021) `_. +but changin the Y-rotations for a random choice of {X, Y, Z}-rotations. +""" + +def build_ansatz(initial_layer_weights, weights, wires, gate_sequence=None): + + n_layers = qml.math.shape(weights)[0] + op_list = [] + + # initial rotations + for i in range(len(wires)): + op_list.append(qml.RY(initial_layer_weights[i], wires=wires[i])) + + # generating the rotation sequence + if gate_sequence is None: + gate_sequence = generate_random_gate_sequence(qml.math.shape(weights)) + + # repeated layers + for layer in range(n_layers): + + # even layer of entanglers + even_wires = [wires[i : i + 2] for i in range(0, len(wires) - 1, 2)] + for i, wire_pair in enumerate(even_wires): + op_list.append(qml.CZ(wires=wire_pair)) + op_list.append(gate_sequence[layer, i, 0].item()(weights[layer, i, 0], wires=wire_pair[0])) + op_list.append(gate_sequence[layer, i, 1].item()(weights[layer, i, 1], wires=wire_pair[1])) + # op_list.append(qml.RX(weights[layer, i, 0], wires=wire_pair[0])) + # op_list.append(qml.RX(weights[layer, i, 1], wires=wire_pair[1])) + + # odd layer of entanglers + odd_wires = [wires[i : i + 2] for i in range(1, len(wires) - 1, 2)] + for i, wire_pair in enumerate(odd_wires): + op_list.append(qml.CZ(wires=wire_pair)) + op_list.append(gate_sequence[layer, len(wires) // 2 + i, 0].item()(weights[layer, len(wires) // 2 + i, 0], wires=wire_pair[0])) + op_list.append(gate_sequence[layer, len(wires) // 2 + i, 1].item()(weights[layer, len(wires) // 2 + i, 1], wires=wire_pair[1])) + # op_list.append(qml.RX(weights[layer, len(wires) // 2 + i, 0], wires=wire_pair[0])) + # op_list.append(qml.RX(weights[layer, len(wires) // 2 + i, 1], wires=wire_pair[1])) + + return op_list + +def generate_random_gate_sequence(shape): + gate_set = [qml.RX, qml.RY, qml.RZ] + return np.random.choice(gate_set, size=shape) + +def get_parameter_shape(n_layers, n_wires): + if n_wires == 1: + return [(n_wires,), (n_layers,)] + return [(n_wires,), (n_layers, n_wires - 1, 2)] + diff --git a/demonstrations/ensemble_multi_qpu.py b/demonstrations/ensemble_multi_qpu.py index 8b68a43741..eaf0bc3ea3 100644 --- a/demonstrations/ensemble_multi_qpu.py +++ b/demonstrations/ensemble_multi_qpu.py @@ -1,581 +1,581 @@ -r""" -Ensemble classification with Rigetti and Qiskit devices -======================================================= - -.. meta:: - :property="og:description": We demonstrate how two QPUs can be - combined in parallel to help solve a machine learning classification problem, - using PyTorch and PennyLane. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/ensemble_diagram.png - -.. related - - tutorial_variational_classifier Variational classifier - -*Author: Tom Bromley — Posted: 14 February 2020. Last updated: 13 December 2021.* - -This tutorial outlines how two QPUs can be combined in parallel to help solve a machine learning -classification problem. - -.. warning:: - This demo does not work with the latest version of Qiskit or the Pennylane-Qiskit plugin and - is compatible with Python versions 3.10 and lower. It is compatible with ``qiskit==0.46`` and - ``pennylane-qiskit==0.35.1``. Older versions of Qiskit and the Pennylane-Qiskit plugin should - not be installed in environments with an existing installation of Qiskit 1.0 or above. - -We use the ``rigetti.qvm`` device to simulate one QPU and the ``qiskit.aer`` device to -simulate another. Each QPU makes an independent prediction, and an ensemble model is -formed by choosing the prediction of the most confident QPU. The iris dataset is used in this -tutorial, consisting of three classes of iris flower. Using a pre-trained model and the PyTorch -interface, we'll see that ensembling allows the QPUs to specialize towards -different classes. - -Let's begin by importing the prerequisite libraries: -""" - -from collections import Counter - -import dask -import matplotlib.pyplot as plt -import numpy as np -import pennylane as qml -import sklearn.datasets -import sklearn.decomposition -import torch -from matplotlib.lines import Line2D -from matplotlib.patches import Patch - -############################################################################## -# This tutorial requires the ``pennylane-rigetti`` and ``pennylane-qiskit`` packages, which can be -# installed by following the instructions `here `__. We also -# make use of the `PyTorch interface `_, which can be installed from `here -# `__. -# -# .. warning:: -# Rigetti's QVM and Quil Compiler services must be running for this tutorial to execute. They -# can be installed by consulting the `Rigetti documentation -# `__ or, for users with Docker, by running: -# -# .. code-block:: bash -# -# docker run -d -p 5555:5555 rigetti/quilc -R -p 5555 -# docker run -d -p 5000:5000 rigetti/qvm -S -p 5000 -# -# Load data -# --------- -# -# The next step is to load the iris dataset. - -n_features = 2 -n_classes = 3 -n_samples = 150 - -data = sklearn.datasets.load_iris() -x = data["data"] -y = data["target"] - -############################################################################## -# We shuffle the data and then embed the four features into a two-dimensional space for ease of -# plotting later on. The first two principal components of the data are used. - -np.random.seed(1967) - -data_order = np.random.permutation(np.arange(n_samples)) -x, y = x[data_order], y[data_order] - -pca = sklearn.decomposition.PCA(n_components=n_features) -pca.fit(x) -x = pca.transform(x) - -############################################################################## -# We will be encoding these two features into quantum circuits using :class:`~.pennylane.RX` -# rotations, and hence renormalize our features to be between :math:`[-\pi, \pi].` - - -x_min = np.min(x, axis=0) -x_max = np.max(x, axis=0) - -x = 2 * np.pi * (x - x_min) / (x_max - x_min) - np.pi - -############################################################################## -# The data is split between a training and a test set. This tutorial uses a model that is -# pre-trained on the training set. - - -split = 125 - -x_train = x[:split] -x_test = x[split:] -y_train = y[:split] -y_test = y[split:] - -############################################################################## -# Finally, let's take a quick look at our data: - - -colours = ["#ec6f86", "#4573e7", "#ad61ed"] - - -def plot_points(x_train, y_train, x_test, y_test): - c_train = [] - c_test = [] - - for y in y_train: - c_train.append(colours[y]) - - for y in y_test: - c_test.append(colours[y]) - - plt.scatter(x_train[:, 0], x_train[:, 1], c=c_train) - plt.scatter(x_test[:, 0], x_test[:, 1], c=c_test, marker="x") - - plt.xlabel("Feature 1", fontsize=16) - plt.ylabel("Feature 2", fontsize=16) - - ax = plt.gca() - ax.set_aspect(1) - - c_transparent = "#00000000" - - custom_lines = [ - Patch(facecolor=colours[0], edgecolor=c_transparent, label="Class 0"), - Patch(facecolor=colours[1], edgecolor=c_transparent, label="Class 1"), - Patch(facecolor=colours[2], edgecolor=c_transparent, label="Class 2"), - Line2D([0], [0], marker="o", color=c_transparent, label="Train", - markerfacecolor="black", markersize=10), - Line2D([0], [0], marker="x", color=c_transparent, label="Test", - markerfacecolor="black", markersize=10), - ] - - ax.legend(handles=custom_lines, bbox_to_anchor=(1.0, 0.75)) - - -plot_points(x_train, y_train, x_test, y_test) -plt.show() - -############################################################################## -# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_001.png -# :width: 80% -# :align: center -# - -############################################################################## -# This plot shows us that class 0 points can be nicely separated, but that there is an overlap -# between points from classes 1 and 2. -# -# Define model -# ------------ -# -# Our model is summarized in the figure below. We use two 4-qubit devices: ``4q-qvm`` -# from the pennyLane-rigetti plugin and ``qiskit.aer`` from the PennyLane-Qiskit plugin. -# -# Data is input using :class:`~.pennylane.RX` rotations and then a different circuit is enacted -# for each device with a unique set of trainable parameters. The output of both circuits is a -# :class:`~.pennylane.PauliZ` measurement on three of the qubits. This is then fed through a -# softmax function, resulting in two 3-dimensional probability vectors corresponding to the 3 -# classes. -# -# Finally, the ensemble model chooses the QPU which is most confident about its prediction -# (i.e., the class with the highest overall probability over all QPUs) and uses that to make a -# prediction. -# -# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_diagram.png -# :width: 80% -# :align: center -# -# Quantum nodes -# ^^^^^^^^^^^^^ -# -# We begin by defining the two quantum devices and the circuits to be run on them. - -n_wires = 4 - -dev0 = qml.device("rigetti.qvm", device="4q-qvm") -dev1 = qml.device("qiskit.aer", wires=4) -devs = [dev0, dev1] - -############################################################################## -# .. note:: -# If you have access to Rigetti hardware, you can swap out ``rigetti.qvm`` for ``rigetti.qpu`` -# and specify the hardware device to run on. Users with access to the IBM hardware can -# swap ``qiskit.aer`` for ``qiskit.remote`` and specify their chosen backend (see `here -# `__). -# -# -# The circuits for both QPUs are shown in the figure below: -# -# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/diagram_circuits.png -# :width: 80% -# :align: center - - -def circuit0(params, x=None): - for i in range(n_wires): - qml.RX(x[i % n_features], wires=i) - qml.Rot(*params[1, 0, i], wires=i) - - qml.CZ(wires=[1, 0]) - qml.CZ(wires=[1, 2]) - qml.CZ(wires=[3, 0]) - - for i in range(n_wires): - qml.Rot(*params[1, 1, i], wires=i) - return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)), qml.expval(qml.PauliZ(2)) - - -def circuit1(params, x=None): - for i in range(n_wires): - qml.RX(x[i % n_features], wires=i) - qml.Rot(*params[0, 0, i], wires=i) - - qml.CZ(wires=[0, 1]) - qml.CZ(wires=[1, 2]) - qml.CZ(wires=[1, 3]) - - for i in range(n_wires): - qml.Rot(*params[0, 1, i], wires=i) - return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)), qml.expval(qml.PauliZ(2)) - - -############################################################################## -# We finally combine the two devices into a :class:`~.pennylane.QNode` list: - - -qnodes = [ - qml.QNode(circuit0, dev0), - qml.QNode(circuit1, dev1), -] - -############################################################################## -# Postprocessing into a prediction -# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# -# The ``predict_point`` function below allows us to find the ensemble prediction, as well as keeping -# track of the individual predictions from each QPU. -# -# We include a ``parallel`` keyword argument for evaluating the :class:`~.pennylane.QNode` list -# in a parallel asynchronous manner. This feature requires the ``dask`` library, which can be -# installed using ``pip install "dask[delayed]"``. When ``parallel=True``, we are able to make -# predictions faster because we do not need to wait for one QPU to output before running on the -# other. - -def decision(softmax): - return int(torch.argmax(softmax)) - - -def predict_point(params, x_point=None, parallel=True): - if parallel: - results = tuple(dask.delayed(q)(params, x=torch.from_numpy(x_point)) for q in qnodes) - results = torch.tensor(dask.compute(*results, scheduler="threads")) - else: - results = tuple(q(params, x=x_point) for q in qnodes) - results = torch.tensor(results) - softmax = torch.nn.functional.softmax(results, dim=1) - choice = torch.where(softmax == torch.max(softmax))[0][0] - chosen_softmax = softmax[choice] - return decision(chosen_softmax), decision(softmax[0]), decision(softmax[1]), int(choice) - - -############################################################################## -# Next, let's define a function to make a predictions over multiple data points. - - -def predict(params, x=None, parallel=True): - predictions_ensemble = [] - predictions_0 = [] - predictions_1 = [] - choices = [] - - for i, x_point in enumerate(x): - if i % 10 == 0 and i > 0: - print("Completed up to iteration {}".format(i)) - results = predict_point(params, x_point=x_point, parallel=parallel) - predictions_ensemble.append(results[0]) - predictions_0.append(results[1]) - predictions_1.append(results[2]) - choices.append(results[3]) - - return predictions_ensemble, predictions_0, predictions_1, choices - - -############################################################################## -# Make predictions -# ---------------- -# -# To test our model, we first load a pre-trained set of parameters which can also be downloaded -# by clicking :download:`here <../_static/demonstration_assets/ensemble_multi_qpu/params.npy>`. - - -params = np.load("../_static/demonstration_assets/ensemble_multi_qpu/params.npy") - -############################################################################## -# We can then make predictions for the training and test datasets. - - -print("Predicting on training dataset") -p_train, p_train_0, p_train_1, choices_train = predict(params, x=x_train) -print("Predicting on test dataset") -p_test, p_test_0, p_test_1, choices_test = predict(params, x=x_test) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# Predicting on training dataset -# Completed up to iteration 10 -# Completed up to iteration 20 -# Completed up to iteration 30 -# Completed up to iteration 40 -# Completed up to iteration 50 -# Completed up to iteration 60 -# Completed up to iteration 70 -# Completed up to iteration 80 -# Completed up to iteration 90 -# Completed up to iteration 100 -# Completed up to iteration 110 -# Completed up to iteration 120 -# Predicting on test dataset -# Completed up to iteration 10 -# Completed up to iteration 20 - -############################################################################## -# Analyze performance -# ------------------- -# -# The last thing to do is test how well the model performs. We begin by looking at the accuracy. -# -# Accuracy -# ^^^^^^^^ - - -def accuracy(predictions, actuals): - count = 0 - - for i in range(len(predictions)): - if predictions[i] == actuals[i]: - count += 1 - - accuracy = count / (len(predictions)) - return accuracy - -############################################################################## - -print("Training accuracy (ensemble): {}".format(accuracy(p_train, y_train))) -print("Training accuracy (QPU0): {}".format(accuracy(p_train_0, y_train))) -print("Training accuracy (QPU1): {}".format(accuracy(p_train_1, y_train))) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# Training accuracy (ensemble): 0.824 -# Training accuracy (QPU0): 0.648 -# Training accuracy (QPU1): 0.296 - -############################################################################## - -print("Test accuracy (ensemble): {}".format(accuracy(p_test, y_test))) -print("Test accuracy (QPU0): {}".format(accuracy(p_test_0, y_test))) -print("Test accuracy (QPU1): {}".format(accuracy(p_test_1, y_test))) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# Test accuracy (ensemble): 0.72 -# Test accuracy (QPU0): 0.56 -# Test accuracy (QPU1): 0.24 - -############################################################################## -# These numbers tell us a few things: -# -# - On both training and test datasets, the ensemble model outperforms the predictions from each -# QPU. This provides a nice example of how QPUs can be used in parallel to gain a performance -# advantage. -# -# - The accuracy of QPU0 is much higher than the accuracy of QPU1. This does not mean that one -# device is intrinsically better than the other. In fact, another set of parameters can lead to -# QPU1 becoming more accurate. We will see in the next section that the difference in accuracy -# is due to specialization of each QPU, which leads to overall better performance of the -# ensemble model. -# -# - The test accuracy is lower than the training accuracy. Here our focus is on analyzing the -# performance of the ensemble model, rather than minimizing the generalization error. -# -# Choice of QPU -# ^^^^^^^^^^^^^ -# -# Is there a link between the class of a datapoint and the QPU chosen to make the prediction in -# the ensemble model? Let's investigate. - - -# Combine choices_train and choices_test to simplify analysis -choices = np.append(choices_train, choices_test) -print("Choices: {}".format(choices)) -print("Choices counts: {}".format(Counter(choices))) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# Choices: [0 0 1 1 0 0 1 0 0 0 1 0 0 0 0 0 0 1 1 0 0 1 0 1 1 0 0 0 1 0 0 1 0 1 0 0 0 -# 0 1 1 0 0 0 0 0 0 0 1 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 1 1 0 0 0 0 1 0 0 0 -# 0 0 0 0 0 1 1 1 1 0 0 0 1 0 1 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 -# 1 0 0 0 1 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 1 1 0 1 0 0 -# 0 0] -# Choices counts: Counter({0: 110, 1: 40}) - -############################################################################## -# The following lines keep track of choices and corresponding predictions in the ensemble model. - - -predictions = np.append(p_train, p_test) -choice_vs_prediction = np.array([(choices[i], predictions[i]) for i in range(n_samples)]) - -############################################################################## -# We can hence find the predictions each QPU was responsible for. - - -choices_vs_prediction_0 = choice_vs_prediction[choice_vs_prediction[:, 0] == 0] -choices_vs_prediction_1 = choice_vs_prediction[choice_vs_prediction[:, 0] == 1] -predictions_0 = choices_vs_prediction_0[:, 1] -predictions_1 = choices_vs_prediction_1[:, 1] - - -expl = "When QPU{} was chosen by the ensemble, it made the following distribution of " \ - "predictions:\n{}" -print(expl.format("0", Counter(predictions_0))) -print("\n" + expl.format("1", Counter(predictions_1))) -print("\nDistribution of classes in iris dataset: {}".format(Counter(y))) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# When QPU0 was chosen by the ensemble, it made the following distribution of predictions: -# Counter({0: 55, 2: 55}) -# -# When QPU1 was chosen by the ensemble, it made the following distribution of predictions: -# Counter({1: 37, 0: 3}) -# -# Distribution of classes in iris dataset: Counter({0: 50, 2: 50, 1: 50}) - -############################################################################## -# These results show us that QPU0 specializes to making predictions on classes 0 and 2, -# while QPU1 specializes to class 1. -# -# Visualization -# ^^^^^^^^^^^^^ -# -# We conclude by visualizing the correct and incorrect predictions on the dataset. The following -# function plots correctly predicted points in green and incorrectly predicted points in red. - - -colours_prediction = {"correct": "#83b5b9", "incorrect": "#f98d91"} -markers = ["o", "v", "d"] - - -def plot_points_prediction(x, y, p, title): - c = {0: [], 1: [], 2: []} - x_ = {0: [], 1: [], 2: []} - - for i in range(n_samples): - x_[y[i]].append(x[i]) - if p[i] == y[i]: - c[y[i]].append(colours_prediction["correct"]) - else: - c[y[i]].append(colours_prediction["incorrect"]) - - for i in range(n_classes): - x_class = np.array(x_[i]) - plt.scatter(x_class[:, 0], x_class[:, 1], c=c[i], marker=markers[i]) - - plt.xlabel("Feature 1", fontsize=16) - plt.ylabel("Feature 2", fontsize=16) - plt.title("Predictions from {} model".format(title)) - - ax = plt.gca() - ax.set_aspect(1) - - c_transparent = "#00000000" - - custom_lines = [ - Patch( - facecolor=colours_prediction["correct"], - edgecolor=c_transparent, label="Correct" - ), - Patch( - facecolor=colours_prediction["incorrect"], - edgecolor=c_transparent, label="Incorrect" - ), - Line2D([0], [0], marker=markers[0], color=c_transparent, label="Class 0", - markerfacecolor="black", markersize=10), - Line2D([0], [0], marker=markers[1], color=c_transparent, label="Class 1", - markerfacecolor="black", markersize=10), - Line2D([0], [0], marker=markers[2], color=c_transparent, label="Class 2", - markerfacecolor="black", markersize=10), - ] - - ax.legend(handles=custom_lines, bbox_to_anchor=(1.0, 0.75)) - - -############################################################################## -# We can again compare the ensemble model with the individual models from each QPU. - - -plot_points_prediction(x, y, predictions, "ensemble") # ensemble -plt.show() - -############################################################################## -# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_002.png -# :width: 80% -# :align: center -# - -############################################################################## - -plot_points_prediction(x, y, np.append(p_train_0, p_test_0), "QPU0") # QPU 0 -plt.show() - -############################################################################## -# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_003.png -# :width: 80% -# :align: center -# - -############################################################################## - -plot_points_prediction(x, y, np.append(p_train_1, p_test_1), "QPU1") # QPU 1 -plt.show() - -############################################################################## -# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_004.png -# :width: 80% -# :align: center -# - -############################################################################## -# These plots reinforce the specialization of the two QPUs. QPU1 concentrates on doing a good job -# at predicting class 1, while QPU0 is focused on classes 0 and 2. By combining together, -# the resultant ensemble performs better. -# -# This tutorial shows how QPUs can work in parallel to realize a performance advantage. Check out -# our :doc:`vqe_parallel` tutorial to see how multiple QPUs can be -# evaluated asynchronously to speed up calculating the potential energy surface of molecular -# hydrogen! - -############################################################################## -# About the author -# ---------------- -# +r""" +Ensemble classification with Rigetti and Qiskit devices +======================================================= + +.. meta:: + :property="og:description": We demonstrate how two QPUs can be + combined in parallel to help solve a machine learning classification problem, + using PyTorch and PennyLane. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/ensemble_diagram.png + +.. related + + tutorial_variational_classifier Variational classifier + +*Author: Tom Bromley — Posted: 14 February 2020. Last updated: 13 December 2021.* + +This tutorial outlines how two QPUs can be combined in parallel to help solve a machine learning +classification problem. + +.. warning:: + This demo does not work with the latest version of Qiskit or the Pennylane-Qiskit plugin and + is compatible with Python versions 3.10 and lower. It is compatible with ``qiskit==0.46`` and + ``pennylane-qiskit==0.35.1``. Older versions of Qiskit and the Pennylane-Qiskit plugin should + not be installed in environments with an existing installation of Qiskit 1.0 or above. + +We use the ``rigetti.qvm`` device to simulate one QPU and the ``qiskit.aer`` device to +simulate another. Each QPU makes an independent prediction, and an ensemble model is +formed by choosing the prediction of the most confident QPU. The iris dataset is used in this +tutorial, consisting of three classes of iris flower. Using a pre-trained model and the PyTorch +interface, we'll see that ensembling allows the QPUs to specialize towards +different classes. + +Let's begin by importing the prerequisite libraries: +""" + +from collections import Counter + +import dask +import matplotlib.pyplot as plt +import numpy as np +import pennylane as qml +import sklearn.datasets +import sklearn.decomposition +import torch +from matplotlib.lines import Line2D +from matplotlib.patches import Patch + +############################################################################## +# This tutorial requires the ``pennylane-rigetti`` and ``pennylane-qiskit`` packages, which can be +# installed by following the instructions `here `__. We also +# make use of the `PyTorch interface `_, which can be installed from `here +# `__. +# +# .. warning:: +# Rigetti's QVM and Quil Compiler services must be running for this tutorial to execute. They +# can be installed by consulting the `Rigetti documentation +# `__ or, for users with Docker, by running: +# +# .. code-block:: bash +# +# docker run -d -p 5555:5555 rigetti/quilc -R -p 5555 +# docker run -d -p 5000:5000 rigetti/qvm -S -p 5000 +# +# Load data +# --------- +# +# The next step is to load the iris dataset. + +n_features = 2 +n_classes = 3 +n_samples = 150 + +data = sklearn.datasets.load_iris() +x = data["data"] +y = data["target"] + +############################################################################## +# We shuffle the data and then embed the four features into a two-dimensional space for ease of +# plotting later on. The first two principal components of the data are used. + +np.random.seed(1967) + +data_order = np.random.permutation(np.arange(n_samples)) +x, y = x[data_order], y[data_order] + +pca = sklearn.decomposition.PCA(n_components=n_features) +pca.fit(x) +x = pca.transform(x) + +############################################################################## +# We will be encoding these two features into quantum circuits using :class:`~.pennylane.RX` +# rotations, and hence renormalize our features to be between :math:`[-\pi, \pi].` + + +x_min = np.min(x, axis=0) +x_max = np.max(x, axis=0) + +x = 2 * np.pi * (x - x_min) / (x_max - x_min) - np.pi + +############################################################################## +# The data is split between a training and a test set. This tutorial uses a model that is +# pre-trained on the training set. + + +split = 125 + +x_train = x[:split] +x_test = x[split:] +y_train = y[:split] +y_test = y[split:] + +############################################################################## +# Finally, let's take a quick look at our data: + + +colours = ["#ec6f86", "#4573e7", "#ad61ed"] + + +def plot_points(x_train, y_train, x_test, y_test): + c_train = [] + c_test = [] + + for y in y_train: + c_train.append(colours[y]) + + for y in y_test: + c_test.append(colours[y]) + + plt.scatter(x_train[:, 0], x_train[:, 1], c=c_train) + plt.scatter(x_test[:, 0], x_test[:, 1], c=c_test, marker="x") + + plt.xlabel("Feature 1", fontsize=16) + plt.ylabel("Feature 2", fontsize=16) + + ax = plt.gca() + ax.set_aspect(1) + + c_transparent = "#00000000" + + custom_lines = [ + Patch(facecolor=colours[0], edgecolor=c_transparent, label="Class 0"), + Patch(facecolor=colours[1], edgecolor=c_transparent, label="Class 1"), + Patch(facecolor=colours[2], edgecolor=c_transparent, label="Class 2"), + Line2D([0], [0], marker="o", color=c_transparent, label="Train", + markerfacecolor="black", markersize=10), + Line2D([0], [0], marker="x", color=c_transparent, label="Test", + markerfacecolor="black", markersize=10), + ] + + ax.legend(handles=custom_lines, bbox_to_anchor=(1.0, 0.75)) + + +plot_points(x_train, y_train, x_test, y_test) +plt.show() + +############################################################################## +# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_001.png +# :width: 80% +# :align: center +# + +############################################################################## +# This plot shows us that class 0 points can be nicely separated, but that there is an overlap +# between points from classes 1 and 2. +# +# Define model +# ------------ +# +# Our model is summarized in the figure below. We use two 4-qubit devices: ``4q-qvm`` +# from the pennyLane-rigetti plugin and ``qiskit.aer`` from the PennyLane-Qiskit plugin. +# +# Data is input using :class:`~.pennylane.RX` rotations and then a different circuit is enacted +# for each device with a unique set of trainable parameters. The output of both circuits is a +# :class:`~.pennylane.PauliZ` measurement on three of the qubits. This is then fed through a +# softmax function, resulting in two 3-dimensional probability vectors corresponding to the 3 +# classes. +# +# Finally, the ensemble model chooses the QPU which is most confident about its prediction +# (i.e., the class with the highest overall probability over all QPUs) and uses that to make a +# prediction. +# +# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_diagram.png +# :width: 80% +# :align: center +# +# Quantum nodes +# ^^^^^^^^^^^^^ +# +# We begin by defining the two quantum devices and the circuits to be run on them. + +n_wires = 4 + +dev0 = qml.device("rigetti.qvm", device="4q-qvm") +dev1 = qml.device("qiskit.aer", wires=4) +devs = [dev0, dev1] + +############################################################################## +# .. note:: +# If you have access to Rigetti hardware, you can swap out ``rigetti.qvm`` for ``rigetti.qpu`` +# and specify the hardware device to run on. Users with access to the IBM hardware can +# swap ``qiskit.aer`` for ``qiskit.remote`` and specify their chosen backend (see `here +# `__). +# +# +# The circuits for both QPUs are shown in the figure below: +# +# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/diagram_circuits.png +# :width: 80% +# :align: center + + +def circuit0(params, x=None): + for i in range(n_wires): + qml.RX(x[i % n_features], wires=i) + qml.Rot(*params[1, 0, i], wires=i) + + qml.CZ(wires=[1, 0]) + qml.CZ(wires=[1, 2]) + qml.CZ(wires=[3, 0]) + + for i in range(n_wires): + qml.Rot(*params[1, 1, i], wires=i) + return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)), qml.expval(qml.PauliZ(2)) + + +def circuit1(params, x=None): + for i in range(n_wires): + qml.RX(x[i % n_features], wires=i) + qml.Rot(*params[0, 0, i], wires=i) + + qml.CZ(wires=[0, 1]) + qml.CZ(wires=[1, 2]) + qml.CZ(wires=[1, 3]) + + for i in range(n_wires): + qml.Rot(*params[0, 1, i], wires=i) + return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)), qml.expval(qml.PauliZ(2)) + + +############################################################################## +# We finally combine the two devices into a :class:`~.pennylane.QNode` list: + + +qnodes = [ + qml.QNode(circuit0, dev0), + qml.QNode(circuit1, dev1), +] + +############################################################################## +# Postprocessing into a prediction +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# +# The ``predict_point`` function below allows us to find the ensemble prediction, as well as keeping +# track of the individual predictions from each QPU. +# +# We include a ``parallel`` keyword argument for evaluating the :class:`~.pennylane.QNode` list +# in a parallel asynchronous manner. This feature requires the ``dask`` library, which can be +# installed using ``pip install "dask[delayed]"``. When ``parallel=True``, we are able to make +# predictions faster because we do not need to wait for one QPU to output before running on the +# other. + +def decision(softmax): + return int(torch.argmax(softmax)) + + +def predict_point(params, x_point=None, parallel=True): + if parallel: + results = tuple(dask.delayed(q)(params, x=torch.from_numpy(x_point)) for q in qnodes) + results = torch.tensor(dask.compute(*results, scheduler="threads")) + else: + results = tuple(q(params, x=x_point) for q in qnodes) + results = torch.tensor(results) + softmax = torch.nn.functional.softmax(results, dim=1) + choice = torch.where(softmax == torch.max(softmax))[0][0] + chosen_softmax = softmax[choice] + return decision(chosen_softmax), decision(softmax[0]), decision(softmax[1]), int(choice) + + +############################################################################## +# Next, let's define a function to make a predictions over multiple data points. + + +def predict(params, x=None, parallel=True): + predictions_ensemble = [] + predictions_0 = [] + predictions_1 = [] + choices = [] + + for i, x_point in enumerate(x): + if i % 10 == 0 and i > 0: + print("Completed up to iteration {}".format(i)) + results = predict_point(params, x_point=x_point, parallel=parallel) + predictions_ensemble.append(results[0]) + predictions_0.append(results[1]) + predictions_1.append(results[2]) + choices.append(results[3]) + + return predictions_ensemble, predictions_0, predictions_1, choices + + +############################################################################## +# Make predictions +# ---------------- +# +# To test our model, we first load a pre-trained set of parameters which can also be downloaded +# by clicking :download:`here <../_static/demonstration_assets/ensemble_multi_qpu/params.npy>`. + + +params = np.load("../_static/demonstration_assets/ensemble_multi_qpu/params.npy") + +############################################################################## +# We can then make predictions for the training and test datasets. + + +print("Predicting on training dataset") +p_train, p_train_0, p_train_1, choices_train = predict(params, x=x_train) +print("Predicting on test dataset") +p_test, p_test_0, p_test_1, choices_test = predict(params, x=x_test) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Predicting on training dataset +# Completed up to iteration 10 +# Completed up to iteration 20 +# Completed up to iteration 30 +# Completed up to iteration 40 +# Completed up to iteration 50 +# Completed up to iteration 60 +# Completed up to iteration 70 +# Completed up to iteration 80 +# Completed up to iteration 90 +# Completed up to iteration 100 +# Completed up to iteration 110 +# Completed up to iteration 120 +# Predicting on test dataset +# Completed up to iteration 10 +# Completed up to iteration 20 + +############################################################################## +# Analyze performance +# ------------------- +# +# The last thing to do is test how well the model performs. We begin by looking at the accuracy. +# +# Accuracy +# ^^^^^^^^ + + +def accuracy(predictions, actuals): + count = 0 + + for i in range(len(predictions)): + if predictions[i] == actuals[i]: + count += 1 + + accuracy = count / (len(predictions)) + return accuracy + +############################################################################## + +print("Training accuracy (ensemble): {}".format(accuracy(p_train, y_train))) +print("Training accuracy (QPU0): {}".format(accuracy(p_train_0, y_train))) +print("Training accuracy (QPU1): {}".format(accuracy(p_train_1, y_train))) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Training accuracy (ensemble): 0.824 +# Training accuracy (QPU0): 0.648 +# Training accuracy (QPU1): 0.296 + +############################################################################## + +print("Test accuracy (ensemble): {}".format(accuracy(p_test, y_test))) +print("Test accuracy (QPU0): {}".format(accuracy(p_test_0, y_test))) +print("Test accuracy (QPU1): {}".format(accuracy(p_test_1, y_test))) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Test accuracy (ensemble): 0.72 +# Test accuracy (QPU0): 0.56 +# Test accuracy (QPU1): 0.24 + +############################################################################## +# These numbers tell us a few things: +# +# - On both training and test datasets, the ensemble model outperforms the predictions from each +# QPU. This provides a nice example of how QPUs can be used in parallel to gain a performance +# advantage. +# +# - The accuracy of QPU0 is much higher than the accuracy of QPU1. This does not mean that one +# device is intrinsically better than the other. In fact, another set of parameters can lead to +# QPU1 becoming more accurate. We will see in the next section that the difference in accuracy +# is due to specialization of each QPU, which leads to overall better performance of the +# ensemble model. +# +# - The test accuracy is lower than the training accuracy. Here our focus is on analyzing the +# performance of the ensemble model, rather than minimizing the generalization error. +# +# Choice of QPU +# ^^^^^^^^^^^^^ +# +# Is there a link between the class of a datapoint and the QPU chosen to make the prediction in +# the ensemble model? Let's investigate. + + +# Combine choices_train and choices_test to simplify analysis +choices = np.append(choices_train, choices_test) +print("Choices: {}".format(choices)) +print("Choices counts: {}".format(Counter(choices))) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Choices: [0 0 1 1 0 0 1 0 0 0 1 0 0 0 0 0 0 1 1 0 0 1 0 1 1 0 0 0 1 0 0 1 0 1 0 0 0 +# 0 1 1 0 0 0 0 0 0 0 1 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 1 1 0 0 0 0 1 0 0 0 +# 0 0 0 0 0 1 1 1 1 0 0 0 1 0 1 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 +# 1 0 0 0 1 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 1 1 0 1 0 0 +# 0 0] +# Choices counts: Counter({0: 110, 1: 40}) + +############################################################################## +# The following lines keep track of choices and corresponding predictions in the ensemble model. + + +predictions = np.append(p_train, p_test) +choice_vs_prediction = np.array([(choices[i], predictions[i]) for i in range(n_samples)]) + +############################################################################## +# We can hence find the predictions each QPU was responsible for. + + +choices_vs_prediction_0 = choice_vs_prediction[choice_vs_prediction[:, 0] == 0] +choices_vs_prediction_1 = choice_vs_prediction[choice_vs_prediction[:, 0] == 1] +predictions_0 = choices_vs_prediction_0[:, 1] +predictions_1 = choices_vs_prediction_1[:, 1] + + +expl = "When QPU{} was chosen by the ensemble, it made the following distribution of " \ + "predictions:\n{}" +print(expl.format("0", Counter(predictions_0))) +print("\n" + expl.format("1", Counter(predictions_1))) +print("\nDistribution of classes in iris dataset: {}".format(Counter(y))) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# When QPU0 was chosen by the ensemble, it made the following distribution of predictions: +# Counter({0: 55, 2: 55}) +# +# When QPU1 was chosen by the ensemble, it made the following distribution of predictions: +# Counter({1: 37, 0: 3}) +# +# Distribution of classes in iris dataset: Counter({0: 50, 2: 50, 1: 50}) + +############################################################################## +# These results show us that QPU0 specializes to making predictions on classes 0 and 2, +# while QPU1 specializes to class 1. +# +# Visualization +# ^^^^^^^^^^^^^ +# +# We conclude by visualizing the correct and incorrect predictions on the dataset. The following +# function plots correctly predicted points in green and incorrectly predicted points in red. + + +colours_prediction = {"correct": "#83b5b9", "incorrect": "#f98d91"} +markers = ["o", "v", "d"] + + +def plot_points_prediction(x, y, p, title): + c = {0: [], 1: [], 2: []} + x_ = {0: [], 1: [], 2: []} + + for i in range(n_samples): + x_[y[i]].append(x[i]) + if p[i] == y[i]: + c[y[i]].append(colours_prediction["correct"]) + else: + c[y[i]].append(colours_prediction["incorrect"]) + + for i in range(n_classes): + x_class = np.array(x_[i]) + plt.scatter(x_class[:, 0], x_class[:, 1], c=c[i], marker=markers[i]) + + plt.xlabel("Feature 1", fontsize=16) + plt.ylabel("Feature 2", fontsize=16) + plt.title("Predictions from {} model".format(title)) + + ax = plt.gca() + ax.set_aspect(1) + + c_transparent = "#00000000" + + custom_lines = [ + Patch( + facecolor=colours_prediction["correct"], + edgecolor=c_transparent, label="Correct" + ), + Patch( + facecolor=colours_prediction["incorrect"], + edgecolor=c_transparent, label="Incorrect" + ), + Line2D([0], [0], marker=markers[0], color=c_transparent, label="Class 0", + markerfacecolor="black", markersize=10), + Line2D([0], [0], marker=markers[1], color=c_transparent, label="Class 1", + markerfacecolor="black", markersize=10), + Line2D([0], [0], marker=markers[2], color=c_transparent, label="Class 2", + markerfacecolor="black", markersize=10), + ] + + ax.legend(handles=custom_lines, bbox_to_anchor=(1.0, 0.75)) + + +############################################################################## +# We can again compare the ensemble model with the individual models from each QPU. + + +plot_points_prediction(x, y, predictions, "ensemble") # ensemble +plt.show() + +############################################################################## +# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_002.png +# :width: 80% +# :align: center +# + +############################################################################## + +plot_points_prediction(x, y, np.append(p_train_0, p_test_0), "QPU0") # QPU 0 +plt.show() + +############################################################################## +# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_003.png +# :width: 80% +# :align: center +# + +############################################################################## + +plot_points_prediction(x, y, np.append(p_train_1, p_test_1), "QPU1") # QPU 1 +plt.show() + +############################################################################## +# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_004.png +# :width: 80% +# :align: center +# + +############################################################################## +# These plots reinforce the specialization of the two QPUs. QPU1 concentrates on doing a good job +# at predicting class 1, while QPU0 is focused on classes 0 and 2. By combining together, +# the resultant ensemble performs better. +# +# This tutorial shows how QPUs can work in parallel to realize a performance advantage. Check out +# our :doc:`vqe_parallel` tutorial to see how multiple QPUs can be +# evaluated asynchronously to speed up calculating the potential energy surface of molecular +# hydrogen! + +############################################################################## +# About the author +# ---------------- +# diff --git a/demonstrations/gbs.py b/demonstrations/gbs.py index f2e1984e33..044c2f6c56 100644 --- a/demonstrations/gbs.py +++ b/demonstrations/gbs.py @@ -1,488 +1,488 @@ -r""" -.. role:: html(raw) - :format: html - -Quantum advantage with Gaussian Boson Sampling -============================================== - -.. meta:: - :property="og:description": Using light to perform tasks beyond the reach of classical computers. - - :property="og:image": https://pennylane.ai/qml/_static/demo_thumbnails/opengraph_demo_thumbnails/gbs_expt2.png - -.. related:: - - tutorial_gaussian_transformation Gaussian transformation - qsim_beyond_classical Beyond classical computing with qsim - qonn Optimizing a quantum optical neural network - tutorial_photonics Photonic quantum computers - -*Authors: Josh Izaac and Nathan Killoran — Posted: 04 December 2020. Last updated: 04 December 2020.* - -.. warning:: - This demo is only compatible with PennyLane version ``0.29`` or below. - -On the journey to large-scale fault-tolerant quantum computers, one of the first major -milestones is to demonstrate a quantum device carrying out tasks that are beyond the reach of -any classical algorithm. The launch of Xanadu's Borealis device marked an important milestone -within the quantum computing community, wherein our very own quantum computational advantage -experiment using quantum photonics was demonstrated in our `Nature paper `__. -Among other quantum advantage achievements are the Google Quantum team's experiment as can be seen in their paper -`Quantum supremacy using a programmable superconducting processor `__ [#Arute2019]_, -and the experiment from the team led by Chao-Yang Lu and Jian-Wei as can be seen in their paper -`Quantum computational advantage using photons `__ -[#Zhong2020]_. - -While Google's experiment performed the task of :doc:`random circuit sampling ` -using a superconducting processor, both Chao-Yang Lu and Jian-Wei's team and Xanadu leveraged the -quantum properties of light to tackle a task called -`Gaussian Boson Sampling `__ (GBS). - -This tutorial will walk you through the basic elements of GBS, motivate why it is -classically challenging, and show you how to explore GBS using PennyLane and the photonic -quantum devices accessible via the -`PennyLane-Strawberry Fields plugin `__. If you are -interested in possible applications of GBS, or want to access programmable GBS hardware -via the cloud, check out the -`Strawberry Fields website `__ for more details. - -| - -.. image:: /_static/demonstration_assets/gbs/gbs_expt2.png - :align: center - :width: 80% - :target: javascript:void(0); - -.. figure:: /_static/demonstration_assets/gbs/gbs_expt1.png - :align: center - :width: 80% - :target: javascript:void(0); - - *Illustration of the experimental setup used by Zhong et al. in Quantum computational advantage - using photons* [#Zhong2020]_. - -The origins of GBS ------------------- - -Let's first explain the name. `Boson `__ refers to bosonic -matter, which, along with fermions, makes up one of the two elementary classes of particles. -The most prevalent bosonic system in our everyday lives is light, which is made of particles -called photons. Another famous example, though much harder to find, is the Higgs boson. -The distinguishing characteristic of bosons is that they follow "Bose-Einstein statistics", -which very loosely means that the particles like to bunch together (contrast this to fermionic -matter like electrons, which must follow the Pauli Exclusion Principle and keep apart). - -This property can be observed in simple interference experiments such as the -`Hong-Ou Mandel setup `__. -If two single photons are interfered on a balanced beamsplitter, they will both emerge at -the same output port—there is zero probability that they will emerge at separate outputs. -This is a simple but notable quantum property of light; if electrons were brought -together in a similar experiement, they would always appear at separate output ports. - -Gaussian Boson Sampling [#hamilton2017]_ is, in fact, a member of a larger family of -"Boson Sampling" algorithms, -stemming back to the initial proposal of Aaronson and Arkhipov [#aaronson2013]_ in 2013. -Boson Sampling is quantum interferometry writ large. Aaronson and Arkhipov's original proposal -was to inject many single photons into distinct input ports of a large interferometer, then -measure which output ports they appear at. The natural interference properties of bosons -means that photons will appear at the output ports in very unique and specific ways. Boson -Sampling was not proposed with any kind of practical real-world use-case in mind. Like -the random circuit sampling, it's just a quantum system being its best self. With sufficient -size and quality, it is strongly believed to be hard for a classical computer to simulate this efficiently. - -Finally, the "Gaussian" in GBS refers to the fact that we modify the original Boson Sampling -proposal slightly: instead of injecting single photons—which are hard to jointly create in the -size and quality needed to demonstrate Boson Sampling conclusively—we instead use states of -light that are experimentally less demanding (though still challenging!). -These states of light are called Gaussian states, -because they bear strong connections to the -`Gaussian (or Normal) distribution `__ -from statistics. In practice, we use a particular Gaussian state called a -`squeezed state `__ for the inputs, -since these are arguably the most non-classical of Gaussian states. - - -.. note:: While computationally hard to simulate, Boson Sampling devices, on their own, - are not capable of universal quantum computing. However, in combination with other - components, GBS is a key building block for a - universal device [#Bourassa2020]_. - - -Coding a GBS algorithm ----------------------- - -The researchers in [#Zhong2020]_ experimentally demonstrate a GBS device by preparing 50 -squeezed states and injecting them into a 100-mode interferometer. In this demo, -in order to keep things classically simulable, we will stick to a much simpler setting -consisting of 4 squeezed states injected into a 4-mode interferometer. At a high level, -an interferometer on :math:`N` modes can be represented using an :math:`N\times N` unitary -matrix :math:`U.` When decomposed into a quantum optical circuit, the interferometer will -be made up of beamsplitters and phase shifters. - -.. image:: /_static/demonstration_assets/gbs/gbs_circuit2.png - :align: center - :width: 90% - :target: javascript:void(0); - -.. raw:: html - -
- -Simulating this circuit using PennyLane is easy; we can simply read off the gates from left -to right, and convert it into a QNode. -""" - -import numpy as np - -# set the random seed -np.random.seed(42) - -# import PennyLane -import pennylane as qml - -############################################################################## -# We must define the unitary matrix we would like to embed in the circuit. -# We will use SciPy to generate a Haar-random unitary: - -from scipy.stats import unitary_group - -# define the linear interferometer -U = unitary_group.rvs(4) -print(U) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# [[ 0.23648826-0.48221431j 0.06829648+0.04447898j 0.51150074-0.09529866j -# 0.55205719-0.35974699j] -# [-0.11148167+0.69780321j -0.24943828+0.08410701j 0.46705929-0.43192981j -# 0.16220654-0.01817602j] -# [-0.22351926-0.25918352j 0.24364996-0.05375623j -0.09259829-0.53810588j -# 0.27267708+0.66941977j] -# [ 0.11519953-0.28596729j -0.90164923-0.22099186j -0.09627758-0.13105595j -# -0.0200152 +0.12766128j]] -# -# We can now use this to construct the circuit, choosing a compatible -# device. For the simulation, we can use the Strawberry Fields -# Gaussian backend. This backend is perfectly suited for simulation of GBS, -# as the initial states are Gaussian, and all gates transform Gaussian states to other -# Gaussian states. - -n_wires = 4 -cutoff = 10 - -dev = qml.device("strawberryfields.gaussian", wires=n_wires, cutoff_dim=cutoff) - - -@qml.qnode(dev) -def gbs_circuit(): - # prepare the input squeezed states - for i in range(n_wires): - qml.Squeezing(1.0, 0.0, wires=i) - - # linear interferometer - qml.InterferometerUnitary(U, wires=range(n_wires)) - return qml.probs(wires=range(n_wires)) - - -############################################################################## -# A couple of things to note in this particular example: -# -# 1. To prepare the input single mode squeezed vacuum state :math:`|re^{i\phi}\rangle,` -# where :math:`r = 1` and :math:`\phi=0,` we -# apply a squeezing gate (:class:`~pennylane.Squeezing`) to each of the wires (initially in -# the vacuum state). -# -# 2. Next we apply the linear interferometer to all four wires using -# :class:`~pennylane.Interferometer` and the unitary matrix ``U``. This operator -# decomposes the unitary matrix representing the linear interferometer into single-mode -# rotation gates (:class:`~pennylane.PhaseShift`) and two-mode beamsplitters -# (:class:`~pennylane.Beamsplitter`). After applying the interferometer, we will denote the -# output state by :math:`|\psi'\rangle.` -# -# 3. GBS takes place physically in an infinite-dimensional Hilbert space, -# which is not practical for simulation. We need to set an upper limit on the maximum -# number of photons we can detect. This is the -# ``cutoff`` value we defined above; we will only be considering detection events -# containing 0 to 9 photons per mode. -# -# We can now execute the QNode, and extract the resulting probability distribution: - -probs = gbs_circuit().reshape([cutoff] * n_wires) -print(probs.shape) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# (10, 10, 10, 10) -# - -############################################################################## -# For example, element ``[1,2,0,1]`` represents the probability of -# detecting 1 photon on wire -# ``0`` and wire ``3``, and 2 photons at wire ``1``, i.e., the value -# -# .. math:: \text{prob}(1,2,0,1) = \left|\langle{1,2,0,1} \mid \psi'\rangle \right|^2. -# -# Let's extract and view the probabilities of measuring various Fock states. - -# Fock states to measure at output -measure_states = [(0, 0, 0, 0), (1, 1, 0, 0), (0, 1, 0, 1), (1, 1, 1, 1), (2, 0, 0, 0)] - -# extract the probabilities of calculating several -# different Fock states at the output, and print them out -for i in measure_states: - print(f"|{''.join(str(j) for j in i)}>: {probs[i]}") - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# |0000>: 0.17637844761413496 -# |1100>: 0.03473293649420282 -# |0101>: 0.011870900427255589 -# |1111>: 0.005957399165336106 -# |2000>: 0.02957384308320549 -# - -############################################################################## -# The GBS Distribution -# -------------------- -# -# Hamilton et al. [#hamilton2017]_ showed that the probability of -# measuring a final state containing only 0 or 1 photons per mode is given by -# -# .. math:: -# -# \left|\langle{n_1,n_2,\dots,n_N}\middle|{\psi'}\rangle\right|^2 = -# \frac{\left|\text{Haf}[(U(\bigoplus_i\mathrm{tanh}(r_i))U^T)]_{st}\right|^2}{\prod_{i=1}^N \cosh(r_i)} -# -# i.e., the sampled single-photon probability distribution is proportional to the **hafnian** of a -# submatrix of :math:`U(\bigoplus_i\mathrm{tanh}(r_i))U^T.` -# -# .. note:: -# -# The hafnian of a matrix is defined by -# -# .. math:: \text{Haf}(A) = \sum_{\sigma \in \text{PMP}_{2N}}\prod_{i=1}^N A_{\sigma(2i-1)\sigma(2i)}, -# -# where :math:`\text{PMP}_{2N}` is the set of all perfect matching permutations of :math:`2N` elements. In graph theory, the -# hafnian calculates the number of perfect `matchings -# `_ in a graph with -# adjacency matrix :math:`A.` -# -# Compare this to the permanent, which calculates the number of perfect matchings on a *bipartite* -# graph. Notably, the permanent appears in vanilla Boson Sampling in a similar way -# that the hafnian appears in GBS. -# The hafnian turns out to be a generalization of the permanent, with the relationship -# -# .. math:: -# -# \text{Per(A)} = \text{Haf}\left(\left[\begin{matrix} -# 0&A\\ A^T&0 -# \end{matrix}\right]\right). -# -# As any algorithm that could calculate (or even approximate) the hafnian could also calculate the -# permanent—a `#P-hard problem `__---it follows that -# calculating or approximating the hafnian must also be a classically hard problem. This lies behind -# the classical hardness of GBS. -# -# In this demo, we will use the same squeezing parameter, :math:`z=r,` for -# all input states; this allows us to simplify this equation. To start with, the hafnian expression -# simply becomes :math:`\text{Haf}[(UU^T\mathrm{tanh}(r))]_{st},` removing the need for the direct sum. -# -# Thus, we have -# -# .. math:: -# -# \left|\left\langle{n_1,n_2,\dots,n_N}\middle|{\psi'}\right\rangle\right|^2 = -# \frac{\left|\text{Haf}[(UU^T\tanh(r))]_{st}\right|^2}{n_1!n_2!\cdots n_N!\cosh^N(r)}. -# -# Now that we have the theoretical formulas, as well as the probabilities from our simulated GBS -# QNode, we can compare the two and see whether they agree. -# -# In order to calculate the probability of different GBS events classically, we need a -# method for calculating the hafnian. -# For this, we will use `The Walrus -# `_ library (which is installed as a dependency of the -# PennyLane-SF plugin): - -from thewalrus import hafnian as haf - -############################################################################## -# Now, for the right-hand side numerator, we first calculate the submatrix -# :math:`A = [(UU^T\mathrm{tanh}(r))]_{st}:` - -A = np.dot(U, U.T) * np.tanh(1) - -############################################################################## -# In GBS, we determine the submatrix by taking the -# rows and columns corresponding to the measured Fock state. For example, to calculate the submatrix -# in the case of the output measurement :math:`\left|{1,1,0,0}\right\rangle,` -# we have - -print(A[:, [0, 1]][[0, 1]]) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# [[ 0.19343159-0.54582922j 0.43418269-0.09169615j] -# [ 0.43418269-0.09169615j -0.27554025-0.46222197j]] -# - -############################################################################## -# i.e., we consider only the rows and columns where a photon was detected, which gives us -# the submatrix corresponding to indices :math:`0` and :math:`1.` - -############################################################################## -# Comparing to simulation -# ----------------------- -# -# Now that we have a method for calculating the hafnian, let's compare the output to that provided by -# the PennyLane QNode. -# -# **Measuring** :math:`|0,0,0,0\rangle` **at the output** -# -# This corresponds to the hafnian of an *empty* matrix, which is simply 1: - -print(1 / np.cosh(1) ** 4) -print(probs[0, 0, 0, 0]) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# 0.1763784476141347 -# 0.17637844761413496 -# - -############################################################################## -# **Measuring** :math:`|1,1,0,0\rangle` **at the output** - -A = (np.dot(U, U.T) * np.tanh(1))[:, [0, 1]][[0, 1]] -print(np.abs(haf(A)) ** 2 / np.cosh(1) ** 4) -print(probs[1, 1, 0, 0]) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# 0.03473293649420271 -# 0.03473293649420282 -# - -############################################################################## -# **Measuring** :math:`|0,1,0,1\rangle` **at the output** - -A = (np.dot(U, U.T) * np.tanh(1))[:, [1, 3]][[1, 3]] -print(np.abs(haf(A)) ** 2 / np.cosh(1) ** 4) -print(probs[0, 1, 0, 1]) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# 0.011870900427255558 -# 0.011870900427255589 -# - -############################################################################## -# **Measuring** :math:`|1,1,1,1\rangle` **at the output** -# -# This corresponds to the hafnian of the full matrix :math:`A=UU^T\mathrm{tanh}(r):` - -A = np.dot(U, U.T) * np.tanh(1) -print(np.abs(haf(A)) ** 2 / np.cosh(1) ** 4) -print(probs[1, 1, 1, 1]) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# 0.005957399165336081 -# 0.005957399165336106 -# - -############################################################################## -# **Measuring** :math:`|2,0,0,0\rangle` **at the output** -# -# Since we have two photons in mode ``q[0]``, we take two copies of the -# first row and first column, making sure to divide by :math:`2!:` - -A = (np.dot(U, U.T) * np.tanh(1))[:, [0, 0]][[0, 0]] -print(np.abs(haf(A)) ** 2 / (2 * np.cosh(1) ** 4)) -print(probs[2, 0, 0, 0]) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# 0.029573843083205383 -# 0.02957384308320549 -# -# The PennyLane simulation results agree (with almost negligible numerical error) to the -# expected result from the Gaussian boson sampling equation! -# -# This demo provides an entry-level walkthrough to the ideas behind GBS, -# providing you with the basic code needed for exploring the ideas behind -# the photonic quantum advantage paper. Try changing the number of modes, -# the number of injected squeezed states, or the cutoff dimension, and -# see how each of these affect the classical computation time. If you're -# interested in learning more about GBS, or about photonic quantum -# computing in general, the -# `Strawberry Fields website `__ is a great resource. -# -# References -# ---------- -# -# .. [#Arute2019] -# -# Arute, F., Arya, K., Babbush, R., et al. "Quantum supremacy using a programmable -# superconducting processor" -# `Nature 574, 505-510 (2019) `__. -# -# .. [#Zhong2020] -# -# Zhong, H.-S., Wang, H., Deng, Y.-H., et al. (2020). Quantum computational advantage using photons. Science, 10.1126/science.abe8770. -# -# .. [#hamilton2017] -# -# Craig S. Hamilton, Regina Kruse, Linda Sansoni, Sonja Barkhofen, Christine Silberhorn, -# and Igor Jex. Gaussian boson sampling. Physical Review Letters, 119:170501, Oct 2017. -# arXiv:1612.01199, doi:10.1103/PhysRevLett.119.170501. -# -# .. [#aaronson2013] -# -# Scott Aaronson and Alex Arkhipov. The computational complexity of linear optics. Theory of -# Computing, 9(1):143–252, 2013. doi:10.4086/toc.2013.v009a004. -# -# .. [#Bourassa2020] -# -# Bourassa, J. E., Alexander, R. N., Vasmer, et al. (2020). Blueprint for a scalable -# photonic fault-tolerant quantum computer. arXiv preprint arXiv:2010.02905. -# -# -# About the author -# ---------------- -# +r""" +.. role:: html(raw) + :format: html + +Quantum advantage with Gaussian Boson Sampling +============================================== + +.. meta:: + :property="og:description": Using light to perform tasks beyond the reach of classical computers. + + :property="og:image": https://pennylane.ai/qml/_static/demo_thumbnails/opengraph_demo_thumbnails/gbs_expt2.png + +.. related:: + + tutorial_gaussian_transformation Gaussian transformation + qsim_beyond_classical Beyond classical computing with qsim + qonn Optimizing a quantum optical neural network + tutorial_photonics Photonic quantum computers + +*Authors: Josh Izaac and Nathan Killoran — Posted: 04 December 2020. Last updated: 04 December 2020.* + +.. warning:: + This demo is only compatible with PennyLane version ``0.29`` or below. + +On the journey to large-scale fault-tolerant quantum computers, one of the first major +milestones is to demonstrate a quantum device carrying out tasks that are beyond the reach of +any classical algorithm. The launch of Xanadu's Borealis device marked an important milestone +within the quantum computing community, wherein our very own quantum computational advantage +experiment using quantum photonics was demonstrated in our `Nature paper `__. +Among other quantum advantage achievements are the Google Quantum team's experiment as can be seen in their paper +`Quantum supremacy using a programmable superconducting processor `__ [#Arute2019]_, +and the experiment from the team led by Chao-Yang Lu and Jian-Wei as can be seen in their paper +`Quantum computational advantage using photons `__ +[#Zhong2020]_. + +While Google's experiment performed the task of :doc:`random circuit sampling ` +using a superconducting processor, both Chao-Yang Lu and Jian-Wei's team and Xanadu leveraged the +quantum properties of light to tackle a task called +`Gaussian Boson Sampling `__ (GBS). + +This tutorial will walk you through the basic elements of GBS, motivate why it is +classically challenging, and show you how to explore GBS using PennyLane and the photonic +quantum devices accessible via the +`PennyLane-Strawberry Fields plugin `__. If you are +interested in possible applications of GBS, or want to access programmable GBS hardware +via the cloud, check out the +`Strawberry Fields website `__ for more details. + +| + +.. image:: /_static/demonstration_assets/gbs/gbs_expt2.png + :align: center + :width: 80% + :target: javascript:void(0); + +.. figure:: /_static/demonstration_assets/gbs/gbs_expt1.png + :align: center + :width: 80% + :target: javascript:void(0); + + *Illustration of the experimental setup used by Zhong et al. in Quantum computational advantage + using photons* [#Zhong2020]_. + +The origins of GBS +------------------ + +Let's first explain the name. `Boson `__ refers to bosonic +matter, which, along with fermions, makes up one of the two elementary classes of particles. +The most prevalent bosonic system in our everyday lives is light, which is made of particles +called photons. Another famous example, though much harder to find, is the Higgs boson. +The distinguishing characteristic of bosons is that they follow "Bose-Einstein statistics", +which very loosely means that the particles like to bunch together (contrast this to fermionic +matter like electrons, which must follow the Pauli Exclusion Principle and keep apart). + +This property can be observed in simple interference experiments such as the +`Hong-Ou Mandel setup `__. +If two single photons are interfered on a balanced beamsplitter, they will both emerge at +the same output port—there is zero probability that they will emerge at separate outputs. +This is a simple but notable quantum property of light; if electrons were brought +together in a similar experiement, they would always appear at separate output ports. + +Gaussian Boson Sampling [#hamilton2017]_ is, in fact, a member of a larger family of +"Boson Sampling" algorithms, +stemming back to the initial proposal of Aaronson and Arkhipov [#aaronson2013]_ in 2013. +Boson Sampling is quantum interferometry writ large. Aaronson and Arkhipov's original proposal +was to inject many single photons into distinct input ports of a large interferometer, then +measure which output ports they appear at. The natural interference properties of bosons +means that photons will appear at the output ports in very unique and specific ways. Boson +Sampling was not proposed with any kind of practical real-world use-case in mind. Like +the random circuit sampling, it's just a quantum system being its best self. With sufficient +size and quality, it is strongly believed to be hard for a classical computer to simulate this efficiently. + +Finally, the "Gaussian" in GBS refers to the fact that we modify the original Boson Sampling +proposal slightly: instead of injecting single photons—which are hard to jointly create in the +size and quality needed to demonstrate Boson Sampling conclusively—we instead use states of +light that are experimentally less demanding (though still challenging!). +These states of light are called Gaussian states, +because they bear strong connections to the +`Gaussian (or Normal) distribution `__ +from statistics. In practice, we use a particular Gaussian state called a +`squeezed state `__ for the inputs, +since these are arguably the most non-classical of Gaussian states. + + +.. note:: While computationally hard to simulate, Boson Sampling devices, on their own, + are not capable of universal quantum computing. However, in combination with other + components, GBS is a key building block for a + universal device [#Bourassa2020]_. + + +Coding a GBS algorithm +---------------------- + +The researchers in [#Zhong2020]_ experimentally demonstrate a GBS device by preparing 50 +squeezed states and injecting them into a 100-mode interferometer. In this demo, +in order to keep things classically simulable, we will stick to a much simpler setting +consisting of 4 squeezed states injected into a 4-mode interferometer. At a high level, +an interferometer on :math:`N` modes can be represented using an :math:`N\times N` unitary +matrix :math:`U.` When decomposed into a quantum optical circuit, the interferometer will +be made up of beamsplitters and phase shifters. + +.. image:: /_static/demonstration_assets/gbs/gbs_circuit2.png + :align: center + :width: 90% + :target: javascript:void(0); + +.. raw:: html + +
+ +Simulating this circuit using PennyLane is easy; we can simply read off the gates from left +to right, and convert it into a QNode. +""" + +import numpy as np + +# set the random seed +np.random.seed(42) + +# import PennyLane +import pennylane as qml + +############################################################################## +# We must define the unitary matrix we would like to embed in the circuit. +# We will use SciPy to generate a Haar-random unitary: + +from scipy.stats import unitary_group + +# define the linear interferometer +U = unitary_group.rvs(4) +print(U) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# [[ 0.23648826-0.48221431j 0.06829648+0.04447898j 0.51150074-0.09529866j +# 0.55205719-0.35974699j] +# [-0.11148167+0.69780321j -0.24943828+0.08410701j 0.46705929-0.43192981j +# 0.16220654-0.01817602j] +# [-0.22351926-0.25918352j 0.24364996-0.05375623j -0.09259829-0.53810588j +# 0.27267708+0.66941977j] +# [ 0.11519953-0.28596729j -0.90164923-0.22099186j -0.09627758-0.13105595j +# -0.0200152 +0.12766128j]] +# +# We can now use this to construct the circuit, choosing a compatible +# device. For the simulation, we can use the Strawberry Fields +# Gaussian backend. This backend is perfectly suited for simulation of GBS, +# as the initial states are Gaussian, and all gates transform Gaussian states to other +# Gaussian states. + +n_wires = 4 +cutoff = 10 + +dev = qml.device("strawberryfields.gaussian", wires=n_wires, cutoff_dim=cutoff) + + +@qml.qnode(dev) +def gbs_circuit(): + # prepare the input squeezed states + for i in range(n_wires): + qml.Squeezing(1.0, 0.0, wires=i) + + # linear interferometer + qml.InterferometerUnitary(U, wires=range(n_wires)) + return qml.probs(wires=range(n_wires)) + + +############################################################################## +# A couple of things to note in this particular example: +# +# 1. To prepare the input single mode squeezed vacuum state :math:`|re^{i\phi}\rangle,` +# where :math:`r = 1` and :math:`\phi=0,` we +# apply a squeezing gate (:class:`~pennylane.Squeezing`) to each of the wires (initially in +# the vacuum state). +# +# 2. Next we apply the linear interferometer to all four wires using +# :class:`~pennylane.Interferometer` and the unitary matrix ``U``. This operator +# decomposes the unitary matrix representing the linear interferometer into single-mode +# rotation gates (:class:`~pennylane.PhaseShift`) and two-mode beamsplitters +# (:class:`~pennylane.Beamsplitter`). After applying the interferometer, we will denote the +# output state by :math:`|\psi'\rangle.` +# +# 3. GBS takes place physically in an infinite-dimensional Hilbert space, +# which is not practical for simulation. We need to set an upper limit on the maximum +# number of photons we can detect. This is the +# ``cutoff`` value we defined above; we will only be considering detection events +# containing 0 to 9 photons per mode. +# +# We can now execute the QNode, and extract the resulting probability distribution: + +probs = gbs_circuit().reshape([cutoff] * n_wires) +print(probs.shape) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# (10, 10, 10, 10) +# + +############################################################################## +# For example, element ``[1,2,0,1]`` represents the probability of +# detecting 1 photon on wire +# ``0`` and wire ``3``, and 2 photons at wire ``1``, i.e., the value +# +# .. math:: \text{prob}(1,2,0,1) = \left|\langle{1,2,0,1} \mid \psi'\rangle \right|^2. +# +# Let's extract and view the probabilities of measuring various Fock states. + +# Fock states to measure at output +measure_states = [(0, 0, 0, 0), (1, 1, 0, 0), (0, 1, 0, 1), (1, 1, 1, 1), (2, 0, 0, 0)] + +# extract the probabilities of calculating several +# different Fock states at the output, and print them out +for i in measure_states: + print(f"|{''.join(str(j) for j in i)}>: {probs[i]}") + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# |0000>: 0.17637844761413496 +# |1100>: 0.03473293649420282 +# |0101>: 0.011870900427255589 +# |1111>: 0.005957399165336106 +# |2000>: 0.02957384308320549 +# + +############################################################################## +# The GBS Distribution +# -------------------- +# +# Hamilton et al. [#hamilton2017]_ showed that the probability of +# measuring a final state containing only 0 or 1 photons per mode is given by +# +# .. math:: +# +# \left|\langle{n_1,n_2,\dots,n_N}\middle|{\psi'}\rangle\right|^2 = +# \frac{\left|\text{Haf}[(U(\bigoplus_i\mathrm{tanh}(r_i))U^T)]_{st}\right|^2}{\prod_{i=1}^N \cosh(r_i)} +# +# i.e., the sampled single-photon probability distribution is proportional to the **hafnian** of a +# submatrix of :math:`U(\bigoplus_i\mathrm{tanh}(r_i))U^T.` +# +# .. note:: +# +# The hafnian of a matrix is defined by +# +# .. math:: \text{Haf}(A) = \sum_{\sigma \in \text{PMP}_{2N}}\prod_{i=1}^N A_{\sigma(2i-1)\sigma(2i)}, +# +# where :math:`\text{PMP}_{2N}` is the set of all perfect matching permutations of :math:`2N` elements. In graph theory, the +# hafnian calculates the number of perfect `matchings +# `_ in a graph with +# adjacency matrix :math:`A.` +# +# Compare this to the permanent, which calculates the number of perfect matchings on a *bipartite* +# graph. Notably, the permanent appears in vanilla Boson Sampling in a similar way +# that the hafnian appears in GBS. +# The hafnian turns out to be a generalization of the permanent, with the relationship +# +# .. math:: +# +# \text{Per(A)} = \text{Haf}\left(\left[\begin{matrix} +# 0&A\\ A^T&0 +# \end{matrix}\right]\right). +# +# As any algorithm that could calculate (or even approximate) the hafnian could also calculate the +# permanent—a `#P-hard problem `__---it follows that +# calculating or approximating the hafnian must also be a classically hard problem. This lies behind +# the classical hardness of GBS. +# +# In this demo, we will use the same squeezing parameter, :math:`z=r,` for +# all input states; this allows us to simplify this equation. To start with, the hafnian expression +# simply becomes :math:`\text{Haf}[(UU^T\mathrm{tanh}(r))]_{st},` removing the need for the direct sum. +# +# Thus, we have +# +# .. math:: +# +# \left|\left\langle{n_1,n_2,\dots,n_N}\middle|{\psi'}\right\rangle\right|^2 = +# \frac{\left|\text{Haf}[(UU^T\tanh(r))]_{st}\right|^2}{n_1!n_2!\cdots n_N!\cosh^N(r)}. +# +# Now that we have the theoretical formulas, as well as the probabilities from our simulated GBS +# QNode, we can compare the two and see whether they agree. +# +# In order to calculate the probability of different GBS events classically, we need a +# method for calculating the hafnian. +# For this, we will use `The Walrus +# `_ library (which is installed as a dependency of the +# PennyLane-SF plugin): + +from thewalrus import hafnian as haf + +############################################################################## +# Now, for the right-hand side numerator, we first calculate the submatrix +# :math:`A = [(UU^T\mathrm{tanh}(r))]_{st}:` + +A = np.dot(U, U.T) * np.tanh(1) + +############################################################################## +# In GBS, we determine the submatrix by taking the +# rows and columns corresponding to the measured Fock state. For example, to calculate the submatrix +# in the case of the output measurement :math:`\left|{1,1,0,0}\right\rangle,` +# we have + +print(A[:, [0, 1]][[0, 1]]) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# [[ 0.19343159-0.54582922j 0.43418269-0.09169615j] +# [ 0.43418269-0.09169615j -0.27554025-0.46222197j]] +# + +############################################################################## +# i.e., we consider only the rows and columns where a photon was detected, which gives us +# the submatrix corresponding to indices :math:`0` and :math:`1.` + +############################################################################## +# Comparing to simulation +# ----------------------- +# +# Now that we have a method for calculating the hafnian, let's compare the output to that provided by +# the PennyLane QNode. +# +# **Measuring** :math:`|0,0,0,0\rangle` **at the output** +# +# This corresponds to the hafnian of an *empty* matrix, which is simply 1: + +print(1 / np.cosh(1) ** 4) +print(probs[0, 0, 0, 0]) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# 0.1763784476141347 +# 0.17637844761413496 +# + +############################################################################## +# **Measuring** :math:`|1,1,0,0\rangle` **at the output** + +A = (np.dot(U, U.T) * np.tanh(1))[:, [0, 1]][[0, 1]] +print(np.abs(haf(A)) ** 2 / np.cosh(1) ** 4) +print(probs[1, 1, 0, 0]) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# 0.03473293649420271 +# 0.03473293649420282 +# + +############################################################################## +# **Measuring** :math:`|0,1,0,1\rangle` **at the output** + +A = (np.dot(U, U.T) * np.tanh(1))[:, [1, 3]][[1, 3]] +print(np.abs(haf(A)) ** 2 / np.cosh(1) ** 4) +print(probs[0, 1, 0, 1]) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# 0.011870900427255558 +# 0.011870900427255589 +# + +############################################################################## +# **Measuring** :math:`|1,1,1,1\rangle` **at the output** +# +# This corresponds to the hafnian of the full matrix :math:`A=UU^T\mathrm{tanh}(r):` + +A = np.dot(U, U.T) * np.tanh(1) +print(np.abs(haf(A)) ** 2 / np.cosh(1) ** 4) +print(probs[1, 1, 1, 1]) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# 0.005957399165336081 +# 0.005957399165336106 +# + +############################################################################## +# **Measuring** :math:`|2,0,0,0\rangle` **at the output** +# +# Since we have two photons in mode ``q[0]``, we take two copies of the +# first row and first column, making sure to divide by :math:`2!:` + +A = (np.dot(U, U.T) * np.tanh(1))[:, [0, 0]][[0, 0]] +print(np.abs(haf(A)) ** 2 / (2 * np.cosh(1) ** 4)) +print(probs[2, 0, 0, 0]) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# 0.029573843083205383 +# 0.02957384308320549 +# +# The PennyLane simulation results agree (with almost negligible numerical error) to the +# expected result from the Gaussian boson sampling equation! +# +# This demo provides an entry-level walkthrough to the ideas behind GBS, +# providing you with the basic code needed for exploring the ideas behind +# the photonic quantum advantage paper. Try changing the number of modes, +# the number of injected squeezed states, or the cutoff dimension, and +# see how each of these affect the classical computation time. If you're +# interested in learning more about GBS, or about photonic quantum +# computing in general, the +# `Strawberry Fields website `__ is a great resource. +# +# References +# ---------- +# +# .. [#Arute2019] +# +# Arute, F., Arya, K., Babbush, R., et al. "Quantum supremacy using a programmable +# superconducting processor" +# `Nature 574, 505-510 (2019) `__. +# +# .. [#Zhong2020] +# +# Zhong, H.-S., Wang, H., Deng, Y.-H., et al. (2020). Quantum computational advantage using photons. Science, 10.1126/science.abe8770. +# +# .. [#hamilton2017] +# +# Craig S. Hamilton, Regina Kruse, Linda Sansoni, Sonja Barkhofen, Christine Silberhorn, +# and Igor Jex. Gaussian boson sampling. Physical Review Letters, 119:170501, Oct 2017. +# arXiv:1612.01199, doi:10.1103/PhysRevLett.119.170501. +# +# .. [#aaronson2013] +# +# Scott Aaronson and Alex Arkhipov. The computational complexity of linear optics. Theory of +# Computing, 9(1):143–252, 2013. doi:10.4086/toc.2013.v009a004. +# +# .. [#Bourassa2020] +# +# Bourassa, J. E., Alexander, R. N., Vasmer, et al. (2020). Blueprint for a scalable +# photonic fault-tolerant quantum computer. arXiv preprint arXiv:2010.02905. +# +# +# About the author +# ---------------- +# diff --git a/demonstrations/tutorial_adaptive_circuits.py b/demonstrations/tutorial_adaptive_circuits.py index 3e29579ccd..79d3041fb6 100644 --- a/demonstrations/tutorial_adaptive_circuits.py +++ b/demonstrations/tutorial_adaptive_circuits.py @@ -1,426 +1,426 @@ -r""" - -Adaptive circuits for quantum chemistry -======================================= - -.. meta:: - :property="og:description": Learn how to build quantum chemistry circuits adaptively - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_adaptive_circuits.png - -.. related:: - tutorial_quantum_chemistry Building molecular Hamiltonians - tutorial_vqe A brief overview of VQE - tutorial_givens_rotations Givens rotations for quantum chemistry - - -*Author: Soran Jahangiri — Posted: 13 September 2021. Last updated: 10 April 2023* - -The key component of variational quantum algorithms for quantum chemistry is the circuit used to -prepare electronic ground states of a molecule. The variational quantum eigensolver (VQE) -[#peruzzo2014]_, [#yudong2019]_ is the method of choice for performing such quantum chemistry -simulations on quantum devices with few qubits. For a given molecule, the appropriate circuit can -be generated by using a pre-selected wavefunction ansatz, for example, the unitary coupled cluster -with single and double excitations (UCCSD) [#romero2017]_. In this approach, we include all -possible single and double excitations of electrons from the occupied spin-orbitals of a reference -state to the unoccupied spin-orbitals [#givenstutorial]_. This makes the construction of the ansatz -straightforward for any given molecule. However, using a pre-constructed ansatz has the disadvantage -of reducing performance in favour of generality: the approach may work well in many cases, but it -will not be optimized for a specific problem. - -In practical applications, including all possible excitations usually increases the cost of the -simulations without improving the accuracy of the results. This motivates implementing a strategy -that allows for approximation of the contribution of the excitations and selects only those -excitations that are found to be important for the given molecule. This can be done by using -adaptive methods to construct a circuit for each given problem [#grimsley2019]_. Using adaptive -circuits helps improve performance at the cost of reducing generality. - -.. figure:: /_static/demonstration_assets/adaptive_circuits/main.png - :width: 75% - :align: center - - Examples of selecting specific gates to generate adaptive circuits. - -In this tutorial, you will learn how to **adaptively** build customized quantum chemistry circuits -to perform ADAPT-VQE [#grimsley2019]_ simulations. This includes a recipe to adaptively select gates -that have a significant contribution to the desired state, while neglecting those that have a small -contribution. You will also learn how to use PennyLane to leverage the sparsity of a molecular -Hamiltonian to make the computation of the expectation values even more efficient. Let's get -started! - -Adaptive circuits ------------------ - -The main idea behind building adaptive circuits is to compute the gradients with respect to all -possible excitation gates and then select gates based on the magnitude of the computed gradients. - -There are different ways to make use of the gradient information and here we discuss one of -these strategies and apply it to compute the ground state energy of LiH. This method requires constructing the -Hamiltonian and determining all possible excitations, which we can do with functionality built into PennyLane. -But we first need to define the molecular parameters, including atomic symbols and coordinates. -Note that the atomic coordinates are in `Bohr `_. -""" - -import pennylane as qml -import jax -import numpy as np -import time - -from pennylane import qchem -from jax import numpy as jnp - -jax.config.update("jax_enable_x64", True) - -symbols = ["Li", "H"] -geometry = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 2.969280527]]) -molecule = qchem.Molecule(symbols, geometry) - -############################################################################## -# We now compute the molecular Hamiltonian in the -# `STO-3G `_ basis and obtain the electronic -# excitations. We restrict ourselves to single and double excitations, but higher-level ones such -# as triple and quadruple excitations can be considered as well. Each of these electronic excitations -# is represented by a gate that excites electrons from the occupied orbitals of a reference state to -# the unoccupied ones. This allows us to prepare a state that is a superposition of the reference -# state and all of the excited states. - -H, qubits = qchem.molecular_hamiltonian( - molecule, - active_electrons=2, - active_orbitals=5 -) - -active_electrons = 2 - -singles, doubles = qchem.excitations(active_electrons, qubits) - -print(f"Total number of excitations = {len(singles) + len(doubles)}") - -############################################################################## -# Note that we have a total of 24 excitations which can be represented by the same number of -# excitation gates [#givenstutorial]_. Let's now use an :class:`~.pennylane.AdaptiveOptimizer` -# implemented in PennyLane to construct an adaptive circuit. -# -# Adaptive Optimizer -# ~~~~~~~~~~~~~~~~~~ -# The adaptive optimizer -# grows an input quantum circuit by adding and optimizing gates selected from a user-defined -# collection of operators. The algorithm first appends all of the gates provided in the initial -# operator pool and computes the circuit gradients with respect to the gate parameters. It retains -# the gate which has the largest gradient and then optimizes its parameter. -# The process of growing the circuit can be repeated until the computed gradients converge to zero. -# Let's use :class:`~.pennylane.AdaptiveOptimizer` to perform an ADAPT-VQE [#grimsley2019]_ -# simulation and build an adaptive circuit for LiH. -# -# We first create the operator pool which contains all single and double excitations. - -singles_excitations = [qml.SingleExcitation(0.0, x) for x in singles] -doubles_excitations = [qml.DoubleExcitation(0.0, x) for x in doubles] -operator_pool = doubles_excitations + singles_excitations - -############################################################################## -# We now define an initial circuit that prepares a Hartree-Fock state and returns the expectation -# value of the Hamiltonian. We also need to define a device. - -hf_state = qchem.hf_state(active_electrons, qubits) -dev = qml.device("default.qubit", wires=qubits) -@qml.qnode(dev) -def circuit(): - [qml.PauliX(i) for i in np.nonzero(hf_state)[0]] - return qml.expval(H) - -############################################################################## -# We instantiate the optimizer and use it to build the circuit adaptively. - -opt = qml.optimize.AdaptiveOptimizer() -for i in range(len(operator_pool)): - circuit, energy, gradient = opt.step_and_cost(circuit, operator_pool) - if i % 3 == 0: - print("n = {:}, E = {:.8f} H, Largest Gradient = {:.3f}".format(i, energy, gradient)) - print(qml.draw(circuit, decimals=None)()) - print() - if gradient < 3e-3: - break - -############################################################################## -# The resulting energy matches the exact energy of the ground electronic state of LiH, which is -# -7.8825378193 Ha, within chemical accuracy. Note that some of the gates appear more than once in -# the circuit. By default, :class:`~.pennylane.AdaptiveOptimizer` does not eliminate the selected -# gates from the pool. We can set ``drain_pool=True`` to prevent repetition of the gates by -# removing the selected gate from the operator pool. - -@qml.qnode(dev) -def circuit(): - [qml.PauliX(i) for i in np.nonzero(hf_state)[0]] - return qml.expval(H) - -opt = qml.optimize.AdaptiveOptimizer() -for i in range(len(operator_pool)): - circuit, energy, gradient = opt.step_and_cost(circuit, operator_pool, drain_pool=True) - if i % 2 == 0: - print("n = {:}, E = {:.8f} H, Largest Gradient = {:.3f}".format(i, energy, gradient)) - print(qml.draw(circuit, decimals=None)()) - print() - if gradient < 3e-3: - break - -############################################################################## -# Manual construction -# ~~~~~~~~~~~~~~~~~~~ -# We can also build adaptive circuits manually by adding groups of gates one at a time. We follow -# these steps: -# -# 1. Compute gradients for all double excitations. -# 2. Select the double excitations with gradients larger than a pre-defined threshold. -# 3. Perform VQE to obtain the optimized parameters for the selected double excitations. -# 4. Repeat steps 1 and 2 for the single excitations. -# 5. Perform the final VQE optimization with all the selected excitations. -# -# We create a circuit that applies a selected group of gates to the reference Hartree-Fock state. - - -# Re-define H using Jax Arrays -molecule = qchem.Molecule(symbols, jnp.array(geometry)) -H, qubits = qchem.molecular_hamiltonian( - molecule, - active_electrons=2, - active_orbitals=5 -) - -def circuit_1(params, excitations): - qml.BasisState(jnp.array(hf_state), wires=range(qubits)) - - for i, excitation in enumerate(excitations): - if len(excitation) == 4: - qml.DoubleExcitation(params[i], wires=excitation) - else: - qml.SingleExcitation(params[i], wires=excitation) - return qml.expval(H) - -############################################################################## -# We now construct our first group of gates by including all the double excitations and compute the -# gradient for each one. We also need to define a cost -# function. We initialize the parameter values to zero such that the gradients are computed -# with respect to the Hartree-Fock state. - - -dev = qml.device("lightning.qubit", wires=qubits) -cost_fn = qml.QNode(circuit_1, dev, interface="jax") - -circuit_gradient = jax.grad(cost_fn, argnums=0) - -params = [0.0] * len(doubles) -grads = circuit_gradient(params, excitations=doubles) - -for i in range(len(doubles)): - print(f"Excitation : {doubles[i]}, Gradient: {grads[i]}") - -############################################################################## -# The computed gradients have different values, reflecting the contribution of each gate -# in the final state prepared by the circuit. Many of the gradient values are zero and we select -# those gates that have a gradient above a pre-defined threshold, which we set to :math:`10^{-5}.` - -doubles_select = [doubles[i] for i in range(len(doubles)) if abs(grads[i]) > 1.0e-5] -doubles_select - -############################################################################## -# There are only 6 double excitation gates, out of the original 16, that have gradients above the -# threshold. We add the selected gates to the circuit and optimize it to determine -# the updated parameters for the selected gates. We also need to define an optimizer. Note that the -# optimization is not very costly as we only have six gates in our circuit. - -import optax - -params_doubles = jnp.zeros(len(doubles_select)) - -opt = optax.sgd(learning_rate=0.5) # sgd stands for StochasticGradientDescent -opt_state = opt.init(params_doubles) - -for n in range(10): - gradient = jax.grad(cost_fn, argnums=0)(params_doubles, excitations=doubles_select) - updates, opt_state = opt.update(gradient, opt_state) - params_doubles = optax.apply_updates(params_doubles, updates) - -############################################################################## -# Now, we keep the selected gates in the circuit and compute the gradients with respect to all of -# the single excitation gates, selecting those that have a non-negligible gradient. To do that, we -# need to slightly modify our circuit such that parameters of the double excitation gates are kept -# fixed while the gradients are computed for the single excitation gates. - - -def circuit_2(params, excitations, gates_select, params_select): - qml.BasisState(hf_state, wires=range(qubits)) - - for i, gate in enumerate(gates_select): - if len(gate) == 4: - qml.DoubleExcitation(params_select[i], wires=gate) - elif len(gate) == 2: - qml.SingleExcitation(params_select[i], wires=gate) - - for i, gate in enumerate(excitations): - if len(gate) == 4: - qml.DoubleExcitation(params[i], wires=gate) - elif len(gate) == 2: - qml.SingleExcitation(params[i], wires=gate) - return qml.expval(H) - - -############################################################################## -# We now compute the gradients for the single excitation gates. - -cost_fn = qml.QNode(circuit_2, dev, interface="jax") -circuit_gradient = jax.grad(cost_fn, argnums=0) -params = [0.0] * len(singles) - -grads = circuit_gradient( - params, - excitations=singles, - gates_select=doubles_select, - params_select=params_doubles -) - -for i in range(len(singles)): - print(f"Excitation : {singles[i]}, Gradient: {grads[i]}") - -############################################################################## -# Similar to the double excitation gates, we select those single excitations that have a gradient -# larger than a predefined threshold. - -singles_select = [singles[i] for i in range(len(singles)) if abs(grads[i]) > 1.0e-5] -singles_select - -############################################################################## -# We now have all of the gates we need to build our circuit. The selected single and double -# excitation gates are highlighted in the figure below. -# -# .. figure:: /_static/demonstration_assets/adaptive_circuits/adapted_circuit.png -# :width: 90% -# :align: center -# -# We perform a final circuit optimization to get the ground-state energy. The resulting energy -# should match the exact energy of the ground electronic state of LiH which is -7.8825378193 Ha. - -cost_fn = qml.QNode(circuit_1, dev, interface="jax") - -params = jnp.zeros(len(doubles_select + singles_select)) - -gates_select = doubles_select + singles_select -opt_state = opt.init(params) - -for n in range(10): - t1 = time.time() - gradient = jax.grad(cost_fn, argnums=0)(params, excitations=doubles_select) - updates, opt_state = opt.update(gradient, opt_state) - params = optax.apply_updates(params, updates) - energy = cost_fn(params, doubles_select) - t2 = time.time() - print("n = {:}, E = {:.8f} H, t = {:.2f} s".format(n, energy, t2 - t1)) - -############################################################################## -# Success! We obtained the ground state energy of LiH, within chemical accuracy, by having -# only 10 gates in our circuit. This is less than half of the total number of single and double -# excitations of LiH (24). - -############################################################################## -# Sparse Hamiltonians -# ------------------- -# -# Molecular Hamiltonians and quantum states are sparse. For instance, let’s look at the Hamiltonian -# we built for LiH. We can compute its matrix representation in the computational basis using the -# Hamiltonian function :meth:`~.pennylane.Hamiltonian.sparse_matrix`. This function -# returns the matrix in the SciPy `sparse coordinate `_ format. - -H_sparse = H.sparse_matrix() -H_sparse - -############################################################################## -# The matrix has :math:`1024^2=1,048,576` entries, but only :math:`11264` of them are non-zero. -# -# .. figure:: /_static/demonstration_assets/adaptive_circuits/h_sparse.png -# :width: 65% -# :align: center -# -# Matrix representation of the LiH Hamiltonian in the computational basis. -# -# Leveraging this sparsity can significantly reduce the -# simulation times. We use the implemented functionality in PennyLane for computing the expectation -# value of the sparse Hamiltonian observable. This can reduce the cost of simulations by -# orders of magnitude depending on the size of the molecule. We use the selected gates obtained in -# the previous steps and perform the final optimization step with the sparse method. Note that the -# sparse method currently only works with the parameter-shift differentiation method. - -excitations = doubles_select + singles_select - -params = jnp.zeros(len(excitations)) - -@qml.qnode(dev, diff_method="parameter-shift", interface="jax") -def circuit(params): - qml.BasisState(hf_state, wires=range(qubits)) - - for i, excitation in enumerate(excitations): - if len(excitation) == 4: - qml.DoubleExcitation(params[i], wires=excitation) - elif len(excitation) == 2: - qml.SingleExcitation(params[i], wires=excitation) - - return qml.expval(qml.SparseHamiltonian(H_sparse, wires=range(qubits))) - - -for n in range(10): - t1 = time.time() - gradient = jax.grad(cost_fn, argnums=0)(params, excitations=doubles_select) - updates, opt_state = opt.update(gradient, opt_state) - params = optax.apply_updates(params, updates) - energy = cost_fn(params, doubles_select) - t2 = time.time() - print("n = {:}, E = {:.8f} H, t = {:.2f} s".format(n, energy, t2 - t1)) - -############################################################################## -# Using the sparse method reproduces the ground state energy while the optimization time is -# much shorter. The average iteration time for the sparse method is about 18 times smaller than that -# of the original non-sparse approach. The performance of the sparse optimization will be even -# better for larger molecules. -# -# Conclusions -# ----------- -# We have learned that building quantum chemistry circuits adaptively and using the -# functionality for sparse objects makes molecular simulations significantly more efficient. We -# learned how to use an adaptive optimizer implemented in PennyLane, that selects the gates one at -# time, to perform ADAPT-VQE [#grimsley2019]_ simulations. We also followed an adaptive strategy -# that selects a group of gates based on information about the gradients. -# -# References -# ---------- -# -# .. [#peruzzo2014] -# -# A. Peruzzo, J. McClean *et al.*, "A variational eigenvalue solver on a photonic -# quantum processor". `Nat. Commun. 5, 4213 (2014). -# `__ -# -# .. [#yudong2019] -# -# Y. Cao, J. Romero, *et al.*, "Quantum Chemistry in the Age of Quantum Computing". -# `Chem. Rev. 2019, 119, 19, 10856-10915. -# `__ -# -# .. [#romero2017] -# -# J. Romero, R. Babbush, *et al.*, "Strategies for quantum computing molecular -# energies using the unitary coupled cluster ansatz". `arXiv:1701.02691 -# `_ -# -# .. [#givenstutorial] -# -# :doc:`tutorial_givens_rotations` -# -# .. [#grimsley2019] -# -# H. R. Grimsley, S. E. Economou, E. Barnes, N. J. Mayhall, "An adaptive -# variational algorithm for exact molecular simulations on a quantum computer". -# `Nat. Commun. 2019, 10, 3007. -# `__ -# -# -# About the author -# ---------------- -# +r""" + +Adaptive circuits for quantum chemistry +======================================= + +.. meta:: + :property="og:description": Learn how to build quantum chemistry circuits adaptively + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_adaptive_circuits.png + +.. related:: + tutorial_quantum_chemistry Building molecular Hamiltonians + tutorial_vqe A brief overview of VQE + tutorial_givens_rotations Givens rotations for quantum chemistry + + +*Author: Soran Jahangiri — Posted: 13 September 2021. Last updated: 10 April 2023* + +The key component of variational quantum algorithms for quantum chemistry is the circuit used to +prepare electronic ground states of a molecule. The variational quantum eigensolver (VQE) +[#peruzzo2014]_, [#yudong2019]_ is the method of choice for performing such quantum chemistry +simulations on quantum devices with few qubits. For a given molecule, the appropriate circuit can +be generated by using a pre-selected wavefunction ansatz, for example, the unitary coupled cluster +with single and double excitations (UCCSD) [#romero2017]_. In this approach, we include all +possible single and double excitations of electrons from the occupied spin-orbitals of a reference +state to the unoccupied spin-orbitals [#givenstutorial]_. This makes the construction of the ansatz +straightforward for any given molecule. However, using a pre-constructed ansatz has the disadvantage +of reducing performance in favour of generality: the approach may work well in many cases, but it +will not be optimized for a specific problem. + +In practical applications, including all possible excitations usually increases the cost of the +simulations without improving the accuracy of the results. This motivates implementing a strategy +that allows for approximation of the contribution of the excitations and selects only those +excitations that are found to be important for the given molecule. This can be done by using +adaptive methods to construct a circuit for each given problem [#grimsley2019]_. Using adaptive +circuits helps improve performance at the cost of reducing generality. + +.. figure:: /_static/demonstration_assets/adaptive_circuits/main.png + :width: 75% + :align: center + + Examples of selecting specific gates to generate adaptive circuits. + +In this tutorial, you will learn how to **adaptively** build customized quantum chemistry circuits +to perform ADAPT-VQE [#grimsley2019]_ simulations. This includes a recipe to adaptively select gates +that have a significant contribution to the desired state, while neglecting those that have a small +contribution. You will also learn how to use PennyLane to leverage the sparsity of a molecular +Hamiltonian to make the computation of the expectation values even more efficient. Let's get +started! + +Adaptive circuits +----------------- + +The main idea behind building adaptive circuits is to compute the gradients with respect to all +possible excitation gates and then select gates based on the magnitude of the computed gradients. + +There are different ways to make use of the gradient information and here we discuss one of +these strategies and apply it to compute the ground state energy of LiH. This method requires constructing the +Hamiltonian and determining all possible excitations, which we can do with functionality built into PennyLane. +But we first need to define the molecular parameters, including atomic symbols and coordinates. +Note that the atomic coordinates are in `Bohr `_. +""" + +import pennylane as qml +import jax +import numpy as np +import time + +from pennylane import qchem +from jax import numpy as jnp + +jax.config.update("jax_enable_x64", True) + +symbols = ["Li", "H"] +geometry = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 2.969280527]]) +molecule = qchem.Molecule(symbols, geometry) + +############################################################################## +# We now compute the molecular Hamiltonian in the +# `STO-3G `_ basis and obtain the electronic +# excitations. We restrict ourselves to single and double excitations, but higher-level ones such +# as triple and quadruple excitations can be considered as well. Each of these electronic excitations +# is represented by a gate that excites electrons from the occupied orbitals of a reference state to +# the unoccupied ones. This allows us to prepare a state that is a superposition of the reference +# state and all of the excited states. + +H, qubits = qchem.molecular_hamiltonian( + molecule, + active_electrons=2, + active_orbitals=5 +) + +active_electrons = 2 + +singles, doubles = qchem.excitations(active_electrons, qubits) + +print(f"Total number of excitations = {len(singles) + len(doubles)}") + +############################################################################## +# Note that we have a total of 24 excitations which can be represented by the same number of +# excitation gates [#givenstutorial]_. Let's now use an :class:`~.pennylane.AdaptiveOptimizer` +# implemented in PennyLane to construct an adaptive circuit. +# +# Adaptive Optimizer +# ~~~~~~~~~~~~~~~~~~ +# The adaptive optimizer +# grows an input quantum circuit by adding and optimizing gates selected from a user-defined +# collection of operators. The algorithm first appends all of the gates provided in the initial +# operator pool and computes the circuit gradients with respect to the gate parameters. It retains +# the gate which has the largest gradient and then optimizes its parameter. +# The process of growing the circuit can be repeated until the computed gradients converge to zero. +# Let's use :class:`~.pennylane.AdaptiveOptimizer` to perform an ADAPT-VQE [#grimsley2019]_ +# simulation and build an adaptive circuit for LiH. +# +# We first create the operator pool which contains all single and double excitations. + +singles_excitations = [qml.SingleExcitation(0.0, x) for x in singles] +doubles_excitations = [qml.DoubleExcitation(0.0, x) for x in doubles] +operator_pool = doubles_excitations + singles_excitations + +############################################################################## +# We now define an initial circuit that prepares a Hartree-Fock state and returns the expectation +# value of the Hamiltonian. We also need to define a device. + +hf_state = qchem.hf_state(active_electrons, qubits) +dev = qml.device("default.qubit", wires=qubits) +@qml.qnode(dev) +def circuit(): + [qml.PauliX(i) for i in np.nonzero(hf_state)[0]] + return qml.expval(H) + +############################################################################## +# We instantiate the optimizer and use it to build the circuit adaptively. + +opt = qml.optimize.AdaptiveOptimizer() +for i in range(len(operator_pool)): + circuit, energy, gradient = opt.step_and_cost(circuit, operator_pool) + if i % 3 == 0: + print("n = {:}, E = {:.8f} H, Largest Gradient = {:.3f}".format(i, energy, gradient)) + print(qml.draw(circuit, decimals=None)()) + print() + if gradient < 3e-3: + break + +############################################################################## +# The resulting energy matches the exact energy of the ground electronic state of LiH, which is +# -7.8825378193 Ha, within chemical accuracy. Note that some of the gates appear more than once in +# the circuit. By default, :class:`~.pennylane.AdaptiveOptimizer` does not eliminate the selected +# gates from the pool. We can set ``drain_pool=True`` to prevent repetition of the gates by +# removing the selected gate from the operator pool. + +@qml.qnode(dev) +def circuit(): + [qml.PauliX(i) for i in np.nonzero(hf_state)[0]] + return qml.expval(H) + +opt = qml.optimize.AdaptiveOptimizer() +for i in range(len(operator_pool)): + circuit, energy, gradient = opt.step_and_cost(circuit, operator_pool, drain_pool=True) + if i % 2 == 0: + print("n = {:}, E = {:.8f} H, Largest Gradient = {:.3f}".format(i, energy, gradient)) + print(qml.draw(circuit, decimals=None)()) + print() + if gradient < 3e-3: + break + +############################################################################## +# Manual construction +# ~~~~~~~~~~~~~~~~~~~ +# We can also build adaptive circuits manually by adding groups of gates one at a time. We follow +# these steps: +# +# 1. Compute gradients for all double excitations. +# 2. Select the double excitations with gradients larger than a pre-defined threshold. +# 3. Perform VQE to obtain the optimized parameters for the selected double excitations. +# 4. Repeat steps 1 and 2 for the single excitations. +# 5. Perform the final VQE optimization with all the selected excitations. +# +# We create a circuit that applies a selected group of gates to the reference Hartree-Fock state. + + +# Re-define H using Jax Arrays +molecule = qchem.Molecule(symbols, jnp.array(geometry)) +H, qubits = qchem.molecular_hamiltonian( + molecule, + active_electrons=2, + active_orbitals=5 +) + +def circuit_1(params, excitations): + qml.BasisState(jnp.array(hf_state), wires=range(qubits)) + + for i, excitation in enumerate(excitations): + if len(excitation) == 4: + qml.DoubleExcitation(params[i], wires=excitation) + else: + qml.SingleExcitation(params[i], wires=excitation) + return qml.expval(H) + +############################################################################## +# We now construct our first group of gates by including all the double excitations and compute the +# gradient for each one. We also need to define a cost +# function. We initialize the parameter values to zero such that the gradients are computed +# with respect to the Hartree-Fock state. + + +dev = qml.device("lightning.qubit", wires=qubits) +cost_fn = qml.QNode(circuit_1, dev, interface="jax") + +circuit_gradient = jax.grad(cost_fn, argnums=0) + +params = [0.0] * len(doubles) +grads = circuit_gradient(params, excitations=doubles) + +for i in range(len(doubles)): + print(f"Excitation : {doubles[i]}, Gradient: {grads[i]}") + +############################################################################## +# The computed gradients have different values, reflecting the contribution of each gate +# in the final state prepared by the circuit. Many of the gradient values are zero and we select +# those gates that have a gradient above a pre-defined threshold, which we set to :math:`10^{-5}.` + +doubles_select = [doubles[i] for i in range(len(doubles)) if abs(grads[i]) > 1.0e-5] +doubles_select + +############################################################################## +# There are only 6 double excitation gates, out of the original 16, that have gradients above the +# threshold. We add the selected gates to the circuit and optimize it to determine +# the updated parameters for the selected gates. We also need to define an optimizer. Note that the +# optimization is not very costly as we only have six gates in our circuit. + +import optax + +params_doubles = jnp.zeros(len(doubles_select)) + +opt = optax.sgd(learning_rate=0.5) # sgd stands for StochasticGradientDescent +opt_state = opt.init(params_doubles) + +for n in range(10): + gradient = jax.grad(cost_fn, argnums=0)(params_doubles, excitations=doubles_select) + updates, opt_state = opt.update(gradient, opt_state) + params_doubles = optax.apply_updates(params_doubles, updates) + +############################################################################## +# Now, we keep the selected gates in the circuit and compute the gradients with respect to all of +# the single excitation gates, selecting those that have a non-negligible gradient. To do that, we +# need to slightly modify our circuit such that parameters of the double excitation gates are kept +# fixed while the gradients are computed for the single excitation gates. + + +def circuit_2(params, excitations, gates_select, params_select): + qml.BasisState(hf_state, wires=range(qubits)) + + for i, gate in enumerate(gates_select): + if len(gate) == 4: + qml.DoubleExcitation(params_select[i], wires=gate) + elif len(gate) == 2: + qml.SingleExcitation(params_select[i], wires=gate) + + for i, gate in enumerate(excitations): + if len(gate) == 4: + qml.DoubleExcitation(params[i], wires=gate) + elif len(gate) == 2: + qml.SingleExcitation(params[i], wires=gate) + return qml.expval(H) + + +############################################################################## +# We now compute the gradients for the single excitation gates. + +cost_fn = qml.QNode(circuit_2, dev, interface="jax") +circuit_gradient = jax.grad(cost_fn, argnums=0) +params = [0.0] * len(singles) + +grads = circuit_gradient( + params, + excitations=singles, + gates_select=doubles_select, + params_select=params_doubles +) + +for i in range(len(singles)): + print(f"Excitation : {singles[i]}, Gradient: {grads[i]}") + +############################################################################## +# Similar to the double excitation gates, we select those single excitations that have a gradient +# larger than a predefined threshold. + +singles_select = [singles[i] for i in range(len(singles)) if abs(grads[i]) > 1.0e-5] +singles_select + +############################################################################## +# We now have all of the gates we need to build our circuit. The selected single and double +# excitation gates are highlighted in the figure below. +# +# .. figure:: /_static/demonstration_assets/adaptive_circuits/adapted_circuit.png +# :width: 90% +# :align: center +# +# We perform a final circuit optimization to get the ground-state energy. The resulting energy +# should match the exact energy of the ground electronic state of LiH which is -7.8825378193 Ha. + +cost_fn = qml.QNode(circuit_1, dev, interface="jax") + +params = jnp.zeros(len(doubles_select + singles_select)) + +gates_select = doubles_select + singles_select +opt_state = opt.init(params) + +for n in range(10): + t1 = time.time() + gradient = jax.grad(cost_fn, argnums=0)(params, excitations=doubles_select) + updates, opt_state = opt.update(gradient, opt_state) + params = optax.apply_updates(params, updates) + energy = cost_fn(params, doubles_select) + t2 = time.time() + print("n = {:}, E = {:.8f} H, t = {:.2f} s".format(n, energy, t2 - t1)) + +############################################################################## +# Success! We obtained the ground state energy of LiH, within chemical accuracy, by having +# only 10 gates in our circuit. This is less than half of the total number of single and double +# excitations of LiH (24). + +############################################################################## +# Sparse Hamiltonians +# ------------------- +# +# Molecular Hamiltonians and quantum states are sparse. For instance, let’s look at the Hamiltonian +# we built for LiH. We can compute its matrix representation in the computational basis using the +# Hamiltonian function :meth:`~.pennylane.Hamiltonian.sparse_matrix`. This function +# returns the matrix in the SciPy `sparse coordinate `_ format. + +H_sparse = H.sparse_matrix() +H_sparse + +############################################################################## +# The matrix has :math:`1024^2=1,048,576` entries, but only :math:`11264` of them are non-zero. +# +# .. figure:: /_static/demonstration_assets/adaptive_circuits/h_sparse.png +# :width: 65% +# :align: center +# +# Matrix representation of the LiH Hamiltonian in the computational basis. +# +# Leveraging this sparsity can significantly reduce the +# simulation times. We use the implemented functionality in PennyLane for computing the expectation +# value of the sparse Hamiltonian observable. This can reduce the cost of simulations by +# orders of magnitude depending on the size of the molecule. We use the selected gates obtained in +# the previous steps and perform the final optimization step with the sparse method. Note that the +# sparse method currently only works with the parameter-shift differentiation method. + +excitations = doubles_select + singles_select + +params = jnp.zeros(len(excitations)) + +@qml.qnode(dev, diff_method="parameter-shift", interface="jax") +def circuit(params): + qml.BasisState(hf_state, wires=range(qubits)) + + for i, excitation in enumerate(excitations): + if len(excitation) == 4: + qml.DoubleExcitation(params[i], wires=excitation) + elif len(excitation) == 2: + qml.SingleExcitation(params[i], wires=excitation) + + return qml.expval(qml.SparseHamiltonian(H_sparse, wires=range(qubits))) + + +for n in range(10): + t1 = time.time() + gradient = jax.grad(cost_fn, argnums=0)(params, excitations=doubles_select) + updates, opt_state = opt.update(gradient, opt_state) + params = optax.apply_updates(params, updates) + energy = cost_fn(params, doubles_select) + t2 = time.time() + print("n = {:}, E = {:.8f} H, t = {:.2f} s".format(n, energy, t2 - t1)) + +############################################################################## +# Using the sparse method reproduces the ground state energy while the optimization time is +# much shorter. The average iteration time for the sparse method is about 18 times smaller than that +# of the original non-sparse approach. The performance of the sparse optimization will be even +# better for larger molecules. +# +# Conclusions +# ----------- +# We have learned that building quantum chemistry circuits adaptively and using the +# functionality for sparse objects makes molecular simulations significantly more efficient. We +# learned how to use an adaptive optimizer implemented in PennyLane, that selects the gates one at +# time, to perform ADAPT-VQE [#grimsley2019]_ simulations. We also followed an adaptive strategy +# that selects a group of gates based on information about the gradients. +# +# References +# ---------- +# +# .. [#peruzzo2014] +# +# A. Peruzzo, J. McClean *et al.*, "A variational eigenvalue solver on a photonic +# quantum processor". `Nat. Commun. 5, 4213 (2014). +# `__ +# +# .. [#yudong2019] +# +# Y. Cao, J. Romero, *et al.*, "Quantum Chemistry in the Age of Quantum Computing". +# `Chem. Rev. 2019, 119, 19, 10856-10915. +# `__ +# +# .. [#romero2017] +# +# J. Romero, R. Babbush, *et al.*, "Strategies for quantum computing molecular +# energies using the unitary coupled cluster ansatz". `arXiv:1701.02691 +# `_ +# +# .. [#givenstutorial] +# +# :doc:`tutorial_givens_rotations` +# +# .. [#grimsley2019] +# +# H. R. Grimsley, S. E. Economou, E. Barnes, N. J. Mayhall, "An adaptive +# variational algorithm for exact molecular simulations on a quantum computer". +# `Nat. Commun. 2019, 10, 3007. +# `__ +# +# +# About the author +# ---------------- +# diff --git a/demonstrations/tutorial_adversarial_attacks_QML.metadata.json b/demonstrations/tutorial_adversarial_attacks_QML.metadata.json index ba6ee618b5..79f6a3580b 100644 --- a/demonstrations/tutorial_adversarial_attacks_QML.metadata.json +++ b/demonstrations/tutorial_adversarial_attacks_QML.metadata.json @@ -1,79 +1,79 @@ -{ - "title": "Adversarial attacks and robustness for quantum machine learning", - "authors": [ - { - "username": "mxw" - }, - { - "username": "kil" - - } - ], - "dateOfPublication": "2024-09-16T00:00:00+00:00", - "dateOfLastModification": "2024-11-08T00:00:00+00:00", - "categories": ["Quantum Machine Learning", "Quantum Computing"], - "tags": ["Quantum machine learning", "QML", "Adversarial attacks", "Quantum security"], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_adversarial_attacks_qml.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_adversarial_attacks_qml.png" - } - ], - "seoDescription": "Learn how to construct adversarial attacks on classification networks and how to make quantum machine learning (QML) models more robust.", - "doi": "", - "references": [ - { - "id": "Wendlinger2024", - "type": "preprint", - "title": "A Comparative Analysis of Adversarial Robustness for Quantum and Classical Machine Learning Models", - "authors": "Maximilian Wendlinger, Kilian Tscharke, Pascal Debus", - "year": "2024", - "doi": "10.48550/arXiv.2404.16154", - "url": "https://arxiv.org/abs/2404.16154" - }, - { - "id": "Goodfellow2014", - "type": "preprint", - "title": "Explaining and harnessing adversarial examples", - "authors": "Ian J. Goodfellow, Jonathon Shlens, Christian Szegedy", - "year": "2014", - "doi": "10.48550/arXiv.1412.6572", - "url": "https://arxiv.org/abs/1412.6572" - - }, - { - "id": "Liu2020", - "type": "preprint", - "title": "A rigorous and robust quantum speed-up in supervised machine learning", - "authors": "Yunchao Liu, Srinivasan Arunachalam, Kristan Temme", - "year": "2020", - "doi": "10.48550/arXiv.2010.02174", - "url": "https://arxiv.org/abs/2010.02174" - - }, - { - "id": "Lu2019", - "type": "preprint", - "title": "Quantum Adversarial Machine Learning", - "authors": "Sirui Lu, Lu-Ming Duan, Dong-Ling Deng", - "year": "2019", - "doi": "10.48550/arXiv.2001.00030", - "url": "https://arxiv.org/abs/2001.00030" - - } - ], - "basedOnPapers": ["10.48550/arXiv.2404.16154"], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_variational_classifier", - "weight": 1.0 - } - ], - "hardware": [] -} +{ + "title": "Adversarial attacks and robustness for quantum machine learning", + "authors": [ + { + "username": "mxw" + }, + { + "username": "kil" + + } + ], + "dateOfPublication": "2024-09-16T00:00:00+00:00", + "dateOfLastModification": "2024-11-08T00:00:00+00:00", + "categories": ["Quantum Machine Learning", "Quantum Computing"], + "tags": ["Quantum machine learning", "QML", "Adversarial attacks", "Quantum security"], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_adversarial_attacks_qml.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_adversarial_attacks_qml.png" + } + ], + "seoDescription": "Learn how to construct adversarial attacks on classification networks and how to make quantum machine learning (QML) models more robust.", + "doi": "", + "references": [ + { + "id": "Wendlinger2024", + "type": "preprint", + "title": "A Comparative Analysis of Adversarial Robustness for Quantum and Classical Machine Learning Models", + "authors": "Maximilian Wendlinger, Kilian Tscharke, Pascal Debus", + "year": "2024", + "doi": "10.48550/arXiv.2404.16154", + "url": "https://arxiv.org/abs/2404.16154" + }, + { + "id": "Goodfellow2014", + "type": "preprint", + "title": "Explaining and harnessing adversarial examples", + "authors": "Ian J. Goodfellow, Jonathon Shlens, Christian Szegedy", + "year": "2014", + "doi": "10.48550/arXiv.1412.6572", + "url": "https://arxiv.org/abs/1412.6572" + + }, + { + "id": "Liu2020", + "type": "preprint", + "title": "A rigorous and robust quantum speed-up in supervised machine learning", + "authors": "Yunchao Liu, Srinivasan Arunachalam, Kristan Temme", + "year": "2020", + "doi": "10.48550/arXiv.2010.02174", + "url": "https://arxiv.org/abs/2010.02174" + + }, + { + "id": "Lu2019", + "type": "preprint", + "title": "Quantum Adversarial Machine Learning", + "authors": "Sirui Lu, Lu-Ming Duan, Dong-Ling Deng", + "year": "2019", + "doi": "10.48550/arXiv.2001.00030", + "url": "https://arxiv.org/abs/2001.00030" + + } + ], + "basedOnPapers": ["10.48550/arXiv.2404.16154"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_variational_classifier", + "weight": 1.0 + } + ], + "hardware": [] +} diff --git a/demonstrations/tutorial_backprop.py b/demonstrations/tutorial_backprop.py index 9db4a40766..c55bbf280b 100644 --- a/demonstrations/tutorial_backprop.py +++ b/demonstrations/tutorial_backprop.py @@ -1,456 +1,456 @@ -r""" -Quantum gradients with backpropagation -====================================== - -.. meta:: - :property="og:description": Using backpropagation can speed up training of quantum circuits compared to the parameter-shift rule—if you are using a simulator. - - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/sphx_glr_tutorial_backprop_002.png - -.. related:: - - tutorial_quantum_natural_gradient Quantum natural gradient - -*Author: Josh Izaac — Posted: 11 August 2020. Last updated: 31 January 2021.* - -In PennyLane, any quantum device, whether a hardware device or a simulator, can be -trained using the :doc:`parameter-shift rule ` to compute quantum -gradients. Indeed, the parameter-shift rule is ideally suited to hardware devices, as it does -not require any knowledge about the internal workings of the device; it is sufficient to treat -the device as a 'black box', and to query it with different input values in order to determine the gradient. - -When working with simulators, however, we *do* have access to the internal (classical) -computations being performed. This allows us to take advantage of other methods of computing the -gradient, such as backpropagation, which may be advantageous in certain regimes. In this tutorial, -we will compare and contrast the parameter-shift rule against backpropagation, using -the PennyLane :class:`default.qubit ` -device. - -The parameter-shift rule ------------------------- - -The parameter-shift rule states that, given a variational quantum circuit :math:`U(\boldsymbol -\theta)` composed of parametrized Pauli rotations, and some measured observable :math:`\hat{B},` the -derivative of the expectation value - -.. math:: - - \langle \hat{B} \rangle (\boldsymbol\theta) = - \langle 0 \vert U(\boldsymbol\theta)^\dagger \hat{B} U(\boldsymbol\theta) \vert 0\rangle - -with respect to the input circuit parameters :math:`\boldsymbol{\theta}` is given by - -.. math:: - - \nabla_{\theta_i}\langle \hat{B} \rangle(\boldsymbol\theta) - = \frac{1}{2} - \left[ - \langle \hat{B} \rangle\left(\boldsymbol\theta + \frac{\pi}{2}\hat{\mathbf{e}}_i\right) - - \langle \hat{B} \rangle\left(\boldsymbol\theta - \frac{\pi}{2}\hat{\mathbf{e}}_i\right) - \right]. - -Thus, the gradient of the expectation value can be calculated by evaluating the same variational -quantum circuit, but with shifted parameter values (hence the name, parameter-shift rule!). - -Let's have a go implementing the parameter-shift rule manually in PennyLane. -""" -import pennylane as qml -from jax import numpy as jnp -from matplotlib import pyplot as plt -import jax - -jax.config.update("jax_platform_name", "cpu") -jax.config.update('jax_enable_x64', True) - -# set the random seed -key = jax.random.PRNGKey(42) - - -# create a device to execute the circuit on -dev = qml.device("default.qubit", wires=3) - - -def CNOT_ring(wires): - """Apply CNOTs in a ring pattern""" - n_wires = len(wires) - - for w in wires: - qml.CNOT([w % n_wires, (w + 1) % n_wires]) - - -@qml.qnode(dev, diff_method="parameter-shift") -def circuit(params): - qml.RX(params[0], wires=0) - qml.RY(params[1], wires=1) - qml.RZ(params[2], wires=2) - - CNOT_ring(wires=[0, 1, 2]) - - qml.RX(params[3], wires=0) - qml.RY(params[4], wires=1) - qml.RZ(params[5], wires=2) - - CNOT_ring(wires=[0, 1, 2]) - return qml.expval(qml.PauliY(0) @ qml.PauliZ(2)) - - -############################################################################## -# Let's test the variational circuit evaluation with some parameter input: - -# initial parameters -params = jax.random.normal(key, [6]) - - -print("Parameters:", params) -print("Expectation value:", circuit(params)) - -############################################################################## -# We can also draw the executed quantum circuit: - -fig, ax = qml.draw_mpl(circuit, decimals=2)(params) -plt.show() - - -############################################################################## -# Now that we have defined our variational circuit QNode, we can construct -# a function that computes the gradient of the :math:`i\text{th}` parameter -# using the parameter-shift rule. - -def parameter_shift_term(qnode, params, i): - shifted = params.copy() - shifted = shifted.at[i].add(jnp.pi/2) - forward = qnode(shifted) # forward evaluation - - shifted = shifted.at[i].add(-jnp.pi) - backward = qnode(shifted) # backward evaluation - - return 0.5 * (forward - backward) - -# gradient with respect to the first parameter -print(parameter_shift_term(circuit, params, 0)) - -############################################################################## -# In order to compute the gradient with respect to *all* parameters, we need -# to loop over the index ``i``: - -def parameter_shift(qnode, params): - gradients = jnp.zeros([len(params)]) - - for i in range(len(params)): - gradients = gradients.at[i].set(parameter_shift_term(qnode, params, i)) - - return gradients - -print(parameter_shift(circuit, params)) - -############################################################################## -# We can compare this to PennyLane's *built-in* quantum gradient support by using -# the ``jax.grad`` function. Remember, when we defined the -# QNode, we specified that we wanted it to be differentiable using the parameter-shift -# method (``diff_method="parameter-shift"``). - -grad_function = jax.grad(circuit) -print(grad_function(params)[0]) - -############################################################################## -# Alternatively, we can directly compute quantum gradients of QNodes using -# PennyLane's built in :mod:`qml.gradients ` module: - -print(jnp.stack(qml.gradients.param_shift(circuit)(params))) - -############################################################################## -# If you count the number of quantum evaluations, you will notice that we had to evaluate the circuit -# ``2*len(params)`` number of times in order to compute the quantum gradient with respect to all -# parameters. While reasonably fast for a small number of parameters, as the number of parameters in -# our quantum circuit grows, so does both -# -# 1. the circuit depth (and thus the time taken to evaluate each expectation value or 'forward' pass), and -# -# 2. the number of parameter-shift evaluations required. -# -# Both of these factors increase the time taken to compute the gradient with -# respect to all parameters. -# -# Benchmarking -# ~~~~~~~~~~~~ -# -# Let's consider an example with a significantly larger number of parameters. -# We'll make use of the :class:`~pennylane.StronglyEntanglingLayers` template -# to make a more complicated QNode. - -dev = qml.device("default.qubit", wires=4) - -@qml.qnode(dev, diff_method="parameter-shift") -def circuit(params): - qml.StronglyEntanglingLayers(params, wires=[0, 1, 2, 3]) - return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3)) - -# initialize circuit parameters -param_shape = qml.StronglyEntanglingLayers.shape(n_wires=4, n_layers=15) -params = jax.random.normal(key, param_shape) * 0.1 - -print(params.size) -print(circuit(params)) - -############################################################################## -# This circuit has 180 parameters. Let's see how long it takes to perform a forward -# pass of the circuit. - -import timeit - -reps = 3 -num = 10 -times = timeit.repeat("circuit(params)", globals=globals(), number=num, repeat=reps) -forward_time = min(times) / num - -print(f"Forward pass (best of {reps}): {forward_time} sec per loop") - - -############################################################################## -# We can now estimate the time taken to compute the full gradient vector, -# and see how this compares. - -# create the gradient function -grad_fn = jax.grad(circuit) - -times = timeit.repeat("grad_fn(params)", globals=globals(), number=num, repeat=reps) -backward_time = min(times) / num - -print(f"Gradient computation (best of {reps}): {backward_time} sec per loop") - - -############################################################################## -# Based on the parameter-shift rule, we expect that the amount of time to compute the quantum -# gradients should be approximately :math:`2p\Delta t_{f}` where :math:`p` is the number of -# parameters and :math:`\Delta t_{f}` if the time taken for the forward pass. Let's verify this: - -print(2 * forward_time * params.size) - - -############################################################################## -# Backpropagation -# --------------- -# -# An alternative to the parameter-shift rule for computing gradients is -# `reverse-mode autodifferentiation `__. -# Unlike the parameter-shift method, which requires :math:`2p` circuit evaluations for -# :math:`p` parameters, reverse-mode requires only a *single* forward pass of the -# differentiable function to compute -# the gradient of all variables, at the expense of increased memory usage. -# During the forward pass, the results of all intermediate subexpressions are stored; -# the computation is then traversed *in reverse*, with the gradient computed by repeatedly -# applying the chain rule. -# In most classical machine learning settings (where we are training scalar loss functions -# consisting of a large number of parameters), -# reverse-mode autodifferentiation is the -# preferred method of autodifferentiation—the reduction in computational time enables larger and -# more complex models to be successfully trained. The backpropagation algorithm is a particular -# special-case of reverse-mode autodifferentiation, which has helped lead to the machine learning -# explosion we see today. -# -# In quantum machine learning, however, the inability to store and utilize the results of -# *intermediate* quantum operations on hardware remains a barrier to using backprop; -# while reverse-mode -# autodifferentiation works fine for small quantum simulations, only the -# parameter-shift rule can be used to compute gradients on quantum hardware directly. Nevertheless, -# when training quantum models via classical simulation, it's useful to explore the regimes where -# reverse-mode differentiation may be a better choice than the parameter-shift rule. -# -# Benchmarking -# ~~~~~~~~~~~~ -# -# When creating a QNode, :doc:`PennyLane supports various methods of differentiation -# `, including ``"parameter-shift"`` (which we used previously), -# ``"finite-diff"``, ``"reversible"``, and ``"backprop"``. While ``"parameter-shift"`` works with all devices -# (simulator or hardware), ``"backprop"`` will only work for specific simulator devices that are -# designed to support backpropagation. -# -# One such device is :class:`default.qubit `. It -# has backends written using TensorFlow, JAX, and Autograd, so when used with the -# TensorFlow, JAX, and Autograd interfaces respectively, supports backpropagation. -# In this demo, we will use the JAX interface. - -dev = qml.device("default.qubit", wires=4) - -############################################################################## -# When defining the QNode, we specify ``diff_method="backprop"`` to ensure that -# we are using backpropagation mode. Note that this is the *default differentiation -# mode* for the ``default.qubit`` device. - - -@qml.qnode(dev, diff_method="backprop") -def circuit(params): - qml.StronglyEntanglingLayers(params, wires=[0, 1, 2, 3]) - return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3)) - -# initialize circuit parameters -param_shape = qml.StronglyEntanglingLayers.shape(n_wires=4, n_layers=15) -params = jax.random.normal(key, param_shape) * 0.1 - -print(circuit(params)) - -############################################################################## -# Let's see how long it takes to perform a forward pass of the circuit. - -import timeit - -reps = 3 -num = 10 -times = timeit.repeat("circuit(params)", globals=globals(), number=num, repeat=reps) -forward_time = min(times) / num -print(f"Forward pass (best of {reps}): {forward_time} sec per loop") - - -############################################################################## -# Comparing this to the forward pass from ``default.qubit``, we note that there is some potential -# overhead from using backpropagation. We can now estimate the time required to perform a -# gradient computation via backpropagation: - -times = timeit.repeat("jax.grad(circuit)(params)", globals=globals(), number=num, repeat=reps) -backward_time = min(times) / num -print(f"Backward pass (best of {reps}): {backward_time} sec per loop") - -############################################################################## -# Unlike with the parameter-shift rule, the time taken to perform the backwards pass appears -# of the order of a single forward pass! This can significantly speed up training of simulated -# circuits with many parameters. -# -# Time comparison -# --------------- -# -# Let's compare the two differentiation approaches as the number of trainable parameters -# in the variational circuit increases, by timing both the forward pass and the gradient -# computation as the number of layers is allowed to increase. - -dev = qml.device("default.qubit", wires=4) - -def circuit(params): - qml.StronglyEntanglingLayers(params, wires=[0, 1, 2, 3]) - return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3)) - -############################################################################## -# We'll continue to use the same ansatz as before, but to reduce the time taken -# to collect the data, we'll reduce the number and repetitions of timings per data -# point. Below, we loop over a variational circuit depth ranging from 0 (no gates/ -# trainable parameters) to 20. Each layer will contain :math:`3N` parameters, where -# :math:`N` is the number of wires (in this case, we have :math:`N=4`). - -reps = 2 -num = 3 - -forward_shift = [] -gradient_shift = [] -forward_backprop = [] -gradient_backprop = [] - -qnode_shift = jax.jit(qml.QNode(circuit, dev, diff_method="parameter-shift")) -qnode_backprop = jax.jit(qml.QNode(circuit, dev, diff_method="backprop")) - -grad_qnode_shift = jax.jit(jax.grad(qml.QNode(circuit, dev, diff_method="parameter-shift"))) -grad_qnode_backprop = jax.jit(jax.grad(qml.QNode(circuit, dev, diff_method="backprop"))) - -for depth in range(0, 21): - param_shape = qml.StronglyEntanglingLayers.shape(n_wires=4, n_layers=depth) - params = jax.random.normal(key, param_shape) * 0.1 - - num_params = params.size - - # forward pass timing - # =================== - - - # parameter-shift - t = timeit.repeat("qnode_shift(params)", globals=globals(), number=num, repeat=reps) - forward_shift.append([num_params, min(t) / num]) - - # backprop - t = timeit.repeat("qnode_backprop(params)", globals=globals(), number=num, repeat=reps) - forward_backprop.append([num_params, min(t) / num]) - - if num_params == 0: - continue - - # Gradient timing - # =============== - - # parameter-shift - t = timeit.repeat("grad_qnode_shift(params)", globals=globals(), number=num, repeat=reps) - gradient_shift.append([num_params, min(t) / num]) - - # backprop - t = timeit.repeat("grad_qnode_backprop(params)", globals=globals(), number=num, repeat=reps) - gradient_backprop.append([num_params, min(t) / num]) - -gradient_shift = jnp.array(gradient_shift).T -gradient_backprop = jnp.array(gradient_backprop).T -forward_shift = jnp.array(forward_shift).T -forward_backprop = jnp.array(forward_backprop).T - -############################################################################## -# We now import matplotlib, and plot the results. - -plt.style.use("bmh") - -fig, ax = plt.subplots(1, 1, figsize=(6, 4)) - -ax.plot(*gradient_shift, '.-', label="Parameter-shift") -ax.plot(*gradient_backprop, '.-', label="Backprop") -ax.set_ylabel("Time (s)") -ax.set_xlabel("Number of parameters") -ax.legend() - -plt.show() - -############################################################################## -# .. raw:: html -# -#
-# -# We can see that the computational time for the parameter-shift rule increases with -# increasing number of parameters, as expected, whereas the computational time -# for backpropagation appears much more constant, with perhaps a minute linear increase -# with :math:`p.` Note that the plots are not perfectly linear, with some 'bumpiness' or -# noisiness. This is likely due to low-level operating system jitter, and -# other environmental fluctuations—increasing the number of repeats can help smooth -# out the plot. -# -# For a better comparison, we can scale the time required for computing the quantum -# gradients against the time taken for the corresponding forward pass: - -gradient_shift = gradient_shift.at[1].divide(forward_shift[1, 1:]) -gradient_backprop = gradient_backprop.at[1].divide(forward_backprop[1, 1:]) - -fig, ax = plt.subplots(1, 1, figsize=(6, 4)) - -ax.plot(*gradient_shift, '.-', label="Parameter-shift") -ax.plot(*gradient_backprop, '.-', label="Backprop") - -# perform a least squares regression to determine the linear best fit/gradient -# for the normalized time vs. number of parameters -x = gradient_shift[0] -m_shift, c_shift = jnp.polyfit(*gradient_shift, deg=1) -m_back, c_back = jnp.polyfit(*gradient_backprop, deg=1) - -ax.plot(x, m_shift * x + c_shift, '--', label=f"{m_shift:.2f}p{c_shift:+.2f}") -ax.plot(x, m_back * x + c_back, '--', label=f"{m_back:.2f}p{c_back:+.2f}") - -ax.set_ylabel("Normalized time") -ax.set_xlabel("Number of parameters") -ax.set_xscale("log") -ax.set_yscale("log") -ax.legend() - -plt.show() - -############################################################################## -# .. raw:: html -# -#
-# -# We can now see clearly that there is constant overhead for backpropagation with -# ``default.qubit``, but the parameter-shift rule scales as :math:`\sim 2p.` -# -# -# About the author -# ---------------- -# +r""" +Quantum gradients with backpropagation +====================================== + +.. meta:: + :property="og:description": Using backpropagation can speed up training of quantum circuits compared to the parameter-shift rule—if you are using a simulator. + + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/sphx_glr_tutorial_backprop_002.png + +.. related:: + + tutorial_quantum_natural_gradient Quantum natural gradient + +*Author: Josh Izaac — Posted: 11 August 2020. Last updated: 31 January 2021.* + +In PennyLane, any quantum device, whether a hardware device or a simulator, can be +trained using the :doc:`parameter-shift rule ` to compute quantum +gradients. Indeed, the parameter-shift rule is ideally suited to hardware devices, as it does +not require any knowledge about the internal workings of the device; it is sufficient to treat +the device as a 'black box', and to query it with different input values in order to determine the gradient. + +When working with simulators, however, we *do* have access to the internal (classical) +computations being performed. This allows us to take advantage of other methods of computing the +gradient, such as backpropagation, which may be advantageous in certain regimes. In this tutorial, +we will compare and contrast the parameter-shift rule against backpropagation, using +the PennyLane :class:`default.qubit ` +device. + +The parameter-shift rule +------------------------ + +The parameter-shift rule states that, given a variational quantum circuit :math:`U(\boldsymbol +\theta)` composed of parametrized Pauli rotations, and some measured observable :math:`\hat{B},` the +derivative of the expectation value + +.. math:: + + \langle \hat{B} \rangle (\boldsymbol\theta) = + \langle 0 \vert U(\boldsymbol\theta)^\dagger \hat{B} U(\boldsymbol\theta) \vert 0\rangle + +with respect to the input circuit parameters :math:`\boldsymbol{\theta}` is given by + +.. math:: + + \nabla_{\theta_i}\langle \hat{B} \rangle(\boldsymbol\theta) + = \frac{1}{2} + \left[ + \langle \hat{B} \rangle\left(\boldsymbol\theta + \frac{\pi}{2}\hat{\mathbf{e}}_i\right) + - \langle \hat{B} \rangle\left(\boldsymbol\theta - \frac{\pi}{2}\hat{\mathbf{e}}_i\right) + \right]. + +Thus, the gradient of the expectation value can be calculated by evaluating the same variational +quantum circuit, but with shifted parameter values (hence the name, parameter-shift rule!). + +Let's have a go implementing the parameter-shift rule manually in PennyLane. +""" +import pennylane as qml +from jax import numpy as jnp +from matplotlib import pyplot as plt +import jax + +jax.config.update("jax_platform_name", "cpu") +jax.config.update('jax_enable_x64', True) + +# set the random seed +key = jax.random.PRNGKey(42) + + +# create a device to execute the circuit on +dev = qml.device("default.qubit", wires=3) + + +def CNOT_ring(wires): + """Apply CNOTs in a ring pattern""" + n_wires = len(wires) + + for w in wires: + qml.CNOT([w % n_wires, (w + 1) % n_wires]) + + +@qml.qnode(dev, diff_method="parameter-shift") +def circuit(params): + qml.RX(params[0], wires=0) + qml.RY(params[1], wires=1) + qml.RZ(params[2], wires=2) + + CNOT_ring(wires=[0, 1, 2]) + + qml.RX(params[3], wires=0) + qml.RY(params[4], wires=1) + qml.RZ(params[5], wires=2) + + CNOT_ring(wires=[0, 1, 2]) + return qml.expval(qml.PauliY(0) @ qml.PauliZ(2)) + + +############################################################################## +# Let's test the variational circuit evaluation with some parameter input: + +# initial parameters +params = jax.random.normal(key, [6]) + + +print("Parameters:", params) +print("Expectation value:", circuit(params)) + +############################################################################## +# We can also draw the executed quantum circuit: + +fig, ax = qml.draw_mpl(circuit, decimals=2)(params) +plt.show() + + +############################################################################## +# Now that we have defined our variational circuit QNode, we can construct +# a function that computes the gradient of the :math:`i\text{th}` parameter +# using the parameter-shift rule. + +def parameter_shift_term(qnode, params, i): + shifted = params.copy() + shifted = shifted.at[i].add(jnp.pi/2) + forward = qnode(shifted) # forward evaluation + + shifted = shifted.at[i].add(-jnp.pi) + backward = qnode(shifted) # backward evaluation + + return 0.5 * (forward - backward) + +# gradient with respect to the first parameter +print(parameter_shift_term(circuit, params, 0)) + +############################################################################## +# In order to compute the gradient with respect to *all* parameters, we need +# to loop over the index ``i``: + +def parameter_shift(qnode, params): + gradients = jnp.zeros([len(params)]) + + for i in range(len(params)): + gradients = gradients.at[i].set(parameter_shift_term(qnode, params, i)) + + return gradients + +print(parameter_shift(circuit, params)) + +############################################################################## +# We can compare this to PennyLane's *built-in* quantum gradient support by using +# the ``jax.grad`` function. Remember, when we defined the +# QNode, we specified that we wanted it to be differentiable using the parameter-shift +# method (``diff_method="parameter-shift"``). + +grad_function = jax.grad(circuit) +print(grad_function(params)[0]) + +############################################################################## +# Alternatively, we can directly compute quantum gradients of QNodes using +# PennyLane's built in :mod:`qml.gradients ` module: + +print(jnp.stack(qml.gradients.param_shift(circuit)(params))) + +############################################################################## +# If you count the number of quantum evaluations, you will notice that we had to evaluate the circuit +# ``2*len(params)`` number of times in order to compute the quantum gradient with respect to all +# parameters. While reasonably fast for a small number of parameters, as the number of parameters in +# our quantum circuit grows, so does both +# +# 1. the circuit depth (and thus the time taken to evaluate each expectation value or 'forward' pass), and +# +# 2. the number of parameter-shift evaluations required. +# +# Both of these factors increase the time taken to compute the gradient with +# respect to all parameters. +# +# Benchmarking +# ~~~~~~~~~~~~ +# +# Let's consider an example with a significantly larger number of parameters. +# We'll make use of the :class:`~pennylane.StronglyEntanglingLayers` template +# to make a more complicated QNode. + +dev = qml.device("default.qubit", wires=4) + +@qml.qnode(dev, diff_method="parameter-shift") +def circuit(params): + qml.StronglyEntanglingLayers(params, wires=[0, 1, 2, 3]) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3)) + +# initialize circuit parameters +param_shape = qml.StronglyEntanglingLayers.shape(n_wires=4, n_layers=15) +params = jax.random.normal(key, param_shape) * 0.1 + +print(params.size) +print(circuit(params)) + +############################################################################## +# This circuit has 180 parameters. Let's see how long it takes to perform a forward +# pass of the circuit. + +import timeit + +reps = 3 +num = 10 +times = timeit.repeat("circuit(params)", globals=globals(), number=num, repeat=reps) +forward_time = min(times) / num + +print(f"Forward pass (best of {reps}): {forward_time} sec per loop") + + +############################################################################## +# We can now estimate the time taken to compute the full gradient vector, +# and see how this compares. + +# create the gradient function +grad_fn = jax.grad(circuit) + +times = timeit.repeat("grad_fn(params)", globals=globals(), number=num, repeat=reps) +backward_time = min(times) / num + +print(f"Gradient computation (best of {reps}): {backward_time} sec per loop") + + +############################################################################## +# Based on the parameter-shift rule, we expect that the amount of time to compute the quantum +# gradients should be approximately :math:`2p\Delta t_{f}` where :math:`p` is the number of +# parameters and :math:`\Delta t_{f}` if the time taken for the forward pass. Let's verify this: + +print(2 * forward_time * params.size) + + +############################################################################## +# Backpropagation +# --------------- +# +# An alternative to the parameter-shift rule for computing gradients is +# `reverse-mode autodifferentiation `__. +# Unlike the parameter-shift method, which requires :math:`2p` circuit evaluations for +# :math:`p` parameters, reverse-mode requires only a *single* forward pass of the +# differentiable function to compute +# the gradient of all variables, at the expense of increased memory usage. +# During the forward pass, the results of all intermediate subexpressions are stored; +# the computation is then traversed *in reverse*, with the gradient computed by repeatedly +# applying the chain rule. +# In most classical machine learning settings (where we are training scalar loss functions +# consisting of a large number of parameters), +# reverse-mode autodifferentiation is the +# preferred method of autodifferentiation—the reduction in computational time enables larger and +# more complex models to be successfully trained. The backpropagation algorithm is a particular +# special-case of reverse-mode autodifferentiation, which has helped lead to the machine learning +# explosion we see today. +# +# In quantum machine learning, however, the inability to store and utilize the results of +# *intermediate* quantum operations on hardware remains a barrier to using backprop; +# while reverse-mode +# autodifferentiation works fine for small quantum simulations, only the +# parameter-shift rule can be used to compute gradients on quantum hardware directly. Nevertheless, +# when training quantum models via classical simulation, it's useful to explore the regimes where +# reverse-mode differentiation may be a better choice than the parameter-shift rule. +# +# Benchmarking +# ~~~~~~~~~~~~ +# +# When creating a QNode, :doc:`PennyLane supports various methods of differentiation +# `, including ``"parameter-shift"`` (which we used previously), +# ``"finite-diff"``, ``"reversible"``, and ``"backprop"``. While ``"parameter-shift"`` works with all devices +# (simulator or hardware), ``"backprop"`` will only work for specific simulator devices that are +# designed to support backpropagation. +# +# One such device is :class:`default.qubit `. It +# has backends written using TensorFlow, JAX, and Autograd, so when used with the +# TensorFlow, JAX, and Autograd interfaces respectively, supports backpropagation. +# In this demo, we will use the JAX interface. + +dev = qml.device("default.qubit", wires=4) + +############################################################################## +# When defining the QNode, we specify ``diff_method="backprop"`` to ensure that +# we are using backpropagation mode. Note that this is the *default differentiation +# mode* for the ``default.qubit`` device. + + +@qml.qnode(dev, diff_method="backprop") +def circuit(params): + qml.StronglyEntanglingLayers(params, wires=[0, 1, 2, 3]) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3)) + +# initialize circuit parameters +param_shape = qml.StronglyEntanglingLayers.shape(n_wires=4, n_layers=15) +params = jax.random.normal(key, param_shape) * 0.1 + +print(circuit(params)) + +############################################################################## +# Let's see how long it takes to perform a forward pass of the circuit. + +import timeit + +reps = 3 +num = 10 +times = timeit.repeat("circuit(params)", globals=globals(), number=num, repeat=reps) +forward_time = min(times) / num +print(f"Forward pass (best of {reps}): {forward_time} sec per loop") + + +############################################################################## +# Comparing this to the forward pass from ``default.qubit``, we note that there is some potential +# overhead from using backpropagation. We can now estimate the time required to perform a +# gradient computation via backpropagation: + +times = timeit.repeat("jax.grad(circuit)(params)", globals=globals(), number=num, repeat=reps) +backward_time = min(times) / num +print(f"Backward pass (best of {reps}): {backward_time} sec per loop") + +############################################################################## +# Unlike with the parameter-shift rule, the time taken to perform the backwards pass appears +# of the order of a single forward pass! This can significantly speed up training of simulated +# circuits with many parameters. +# +# Time comparison +# --------------- +# +# Let's compare the two differentiation approaches as the number of trainable parameters +# in the variational circuit increases, by timing both the forward pass and the gradient +# computation as the number of layers is allowed to increase. + +dev = qml.device("default.qubit", wires=4) + +def circuit(params): + qml.StronglyEntanglingLayers(params, wires=[0, 1, 2, 3]) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3)) + +############################################################################## +# We'll continue to use the same ansatz as before, but to reduce the time taken +# to collect the data, we'll reduce the number and repetitions of timings per data +# point. Below, we loop over a variational circuit depth ranging from 0 (no gates/ +# trainable parameters) to 20. Each layer will contain :math:`3N` parameters, where +# :math:`N` is the number of wires (in this case, we have :math:`N=4`). + +reps = 2 +num = 3 + +forward_shift = [] +gradient_shift = [] +forward_backprop = [] +gradient_backprop = [] + +qnode_shift = jax.jit(qml.QNode(circuit, dev, diff_method="parameter-shift")) +qnode_backprop = jax.jit(qml.QNode(circuit, dev, diff_method="backprop")) + +grad_qnode_shift = jax.jit(jax.grad(qml.QNode(circuit, dev, diff_method="parameter-shift"))) +grad_qnode_backprop = jax.jit(jax.grad(qml.QNode(circuit, dev, diff_method="backprop"))) + +for depth in range(0, 21): + param_shape = qml.StronglyEntanglingLayers.shape(n_wires=4, n_layers=depth) + params = jax.random.normal(key, param_shape) * 0.1 + + num_params = params.size + + # forward pass timing + # =================== + + + # parameter-shift + t = timeit.repeat("qnode_shift(params)", globals=globals(), number=num, repeat=reps) + forward_shift.append([num_params, min(t) / num]) + + # backprop + t = timeit.repeat("qnode_backprop(params)", globals=globals(), number=num, repeat=reps) + forward_backprop.append([num_params, min(t) / num]) + + if num_params == 0: + continue + + # Gradient timing + # =============== + + # parameter-shift + t = timeit.repeat("grad_qnode_shift(params)", globals=globals(), number=num, repeat=reps) + gradient_shift.append([num_params, min(t) / num]) + + # backprop + t = timeit.repeat("grad_qnode_backprop(params)", globals=globals(), number=num, repeat=reps) + gradient_backprop.append([num_params, min(t) / num]) + +gradient_shift = jnp.array(gradient_shift).T +gradient_backprop = jnp.array(gradient_backprop).T +forward_shift = jnp.array(forward_shift).T +forward_backprop = jnp.array(forward_backprop).T + +############################################################################## +# We now import matplotlib, and plot the results. + +plt.style.use("bmh") + +fig, ax = plt.subplots(1, 1, figsize=(6, 4)) + +ax.plot(*gradient_shift, '.-', label="Parameter-shift") +ax.plot(*gradient_backprop, '.-', label="Backprop") +ax.set_ylabel("Time (s)") +ax.set_xlabel("Number of parameters") +ax.legend() + +plt.show() + +############################################################################## +# .. raw:: html +# +#
+# +# We can see that the computational time for the parameter-shift rule increases with +# increasing number of parameters, as expected, whereas the computational time +# for backpropagation appears much more constant, with perhaps a minute linear increase +# with :math:`p.` Note that the plots are not perfectly linear, with some 'bumpiness' or +# noisiness. This is likely due to low-level operating system jitter, and +# other environmental fluctuations—increasing the number of repeats can help smooth +# out the plot. +# +# For a better comparison, we can scale the time required for computing the quantum +# gradients against the time taken for the corresponding forward pass: + +gradient_shift = gradient_shift.at[1].divide(forward_shift[1, 1:]) +gradient_backprop = gradient_backprop.at[1].divide(forward_backprop[1, 1:]) + +fig, ax = plt.subplots(1, 1, figsize=(6, 4)) + +ax.plot(*gradient_shift, '.-', label="Parameter-shift") +ax.plot(*gradient_backprop, '.-', label="Backprop") + +# perform a least squares regression to determine the linear best fit/gradient +# for the normalized time vs. number of parameters +x = gradient_shift[0] +m_shift, c_shift = jnp.polyfit(*gradient_shift, deg=1) +m_back, c_back = jnp.polyfit(*gradient_backprop, deg=1) + +ax.plot(x, m_shift * x + c_shift, '--', label=f"{m_shift:.2f}p{c_shift:+.2f}") +ax.plot(x, m_back * x + c_back, '--', label=f"{m_back:.2f}p{c_back:+.2f}") + +ax.set_ylabel("Normalized time") +ax.set_xlabel("Number of parameters") +ax.set_xscale("log") +ax.set_yscale("log") +ax.legend() + +plt.show() + +############################################################################## +# .. raw:: html +# +#
+# +# We can now see clearly that there is constant overhead for backpropagation with +# ``default.qubit``, but the parameter-shift rule scales as :math:`\sim 2p.` +# +# +# About the author +# ---------------- +# diff --git a/demonstrations/tutorial_barren_gadgets.py b/demonstrations/tutorial_barren_gadgets.py index f0d19253c1..0eacb1fff7 100644 --- a/demonstrations/tutorial_barren_gadgets.py +++ b/demonstrations/tutorial_barren_gadgets.py @@ -1,390 +1,390 @@ -r""" -Perturbative Gadgets for Variational Quantum Algorithms -========================================== - -.. meta:: - :property="og:description": Use perturbative gadgets to avoid cost-function-dependent barren plateaus - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_barren_gadgets.png - - -.. related:: - tutorial_barren_plateaus Barren plateaus in quantum neural networks¶ - tutorial_local_cost_functions Alleviating barren plateaus with local cost functions - -*Author: Simon Cichy — Posted: 09 December 2022. Last updated: 09 December 2022.* - -Variational quantum algorithms are seen as one of the most primising candidates -for useful applications of quantum computers in the near term, but there are -still a few hurdles to overcome when it comes to practical implementation. -One of them, is the trainability. -In other words, one needs to ensure that the cost function is not flat. -In this tutorial, we will explore the application of perturbative gadgets in -variational quantum algorithms to outgo the issue of cost-function-dependent -barren plateaus, as proposed in Ref. [#cichy2022]_ - -Some context ------------- - -Barren plateaus refer to the phenomenon where the gradients of the cost function -decay exponentially with the size of the problem. Essentially, the cost -landscape becomes flat, with exception of some small regions, e.g., around -the minimum. -That is a problem because increasing the precision of the cost -function requires more measurements from the quantum device due to shot noise, -and an exponential number of measurements would render the algorithm impractical. -If you are not familiar yet with the concept of barren plateaus, I recommend you -first check out the demonstrations on :doc:`barren plateaus ` -and :doc:`avoiding barren plateaus with local cost functions `. - -As presented in the second aforementioned demo, barren plateaus are more severe when using global -cost functions compared to local ones. -A global cost function requires the simultaneous measurement of all -qubits at once. In contrast, a local one is constructed from terms that only -act on a small subset of qubits. - -We want to explore this topic further and learn about one possible mitigation -strategy. -Thinking about Variational Quantum Eigensolver (VQE) applications, let us consider cost functions that are -expectation values of Hamiltonians such as - -.. math:: C(\theta) = \operatorname{Tr} \left[ H V(\theta) |00\ldots 0\rangle \! \langle 00\ldots 0| V(\theta)^\dagger\right]. - -Here :math:`|00\ldots 0\rangle` is our initial state, -:math:`V(\theta)` is the circuit ansatz and :math:`H` the Hamiltonian -whose expectation value we need to minimize. -In some cases, it is easy to find a local cost function which can substitute a global one with the same ground state. -Take, for instance, the following Hamiltonians that induce global and local cost functions, respectively. - - -.. math:: H_G = \mathbb{I} - |00\ldots 0\rangle \! \langle 00\ldots 0| \quad \textrm{ and } \quad H_L = \mathbb{I} - \frac{1}{n} \sum_j |0\rangle \! \langle 0|_j. - -Those are two different Hamiltonians (not just different formulations of the -same one), but they share the same ground state: - - -.. math:: |\psi_{\textrm{min}} \rangle = |00\ldots 0\rangle. - -Therefore, one can work with either Hamiltonian to perform the VQE routine. -However, it is not always so simple. -What if we want to find the minimum eigenenergy of -:math:`H = X \otimes X \otimes Y \otimes Z + Z \otimes Y \otimes X \otimes X` ? -It is not always trivial to construct a local cost -function that has the same minimum as the cost function of interest. -This is where perturbative gadgets come into play! - - -The definitions ---------------- -Perturbative gadgets are a common tool in adiabatic quantum computing. -Their goal is to find a Hamiltonian with local interactions that mimics -another Hamiltonian with more complex couplings. - -Ideally, they would want to implement the target Hamiltonian with complex couplings, but since it's hard to implement more than few-body interactions on hardware, they cannot do so. Perturbative gadgets work by increasing the dimension of the Hilbert space (i.e., the number -of qubits) and "encoding" the target Hamiltonian in the low-energy -subspace of a so-called "gadget" Hamiltonian. - -Let us now construct such a gadget Hamiltonian tailored for VQE applications. -First, we start from a target Hamiltonian that is a linear combination of -Pauli words acting on :math:`k` qubits each: - -.. math:: H^\text{target} = \sum_i c_i h_i, - -where :math:`h_i = \sigma_{i,1} \otimes \sigma_{i,2} \otimes \ldots \otimes \sigma_{i,k},` -:math:`\sigma_{i,j} \in \{ X, Y, Z \},` and :math:`c_i \in \mathbb{R}.` -Now we construct the gadget Hamiltonian. -For each term :math:`h_i,` we will need :math:`k` additional qubits, which we -call auxiliary qubits, and to add two terms to the Hamiltonian: -an "unperturbed" part :math:`H^\text{aux}_i` and a perturbation :math:`V_i` -of strength :math:`\lambda.` -The unperturbed part penalizes each of the newly added qubits for not being in -the :math:`|0\rangle` state - -.. math:: H^\text{aux}_i = \sum_{j=1}^k |1\rangle \! \langle 1|_{i,j} = \sum_{j=1}^k \frac{1}{2}(\mathbb{I} - Z_{i,j}). - -On the other hand, the perturbation part implements one of the operators in the Pauli word -:math:`\sigma_{i,j}` on the corresponding qubit of the target register and a -pair of Pauli :math:`X` gates on two of the auxiliary qubits: - -.. math:: V_i = \sum_{j=1}^k c_{i,j} \sigma_{i,j} \otimes X_{i,j} \otimes X_{i,(j+1) \mathrm{mod }k}. - -In the end, - -.. math:: H^\text{gad} = \sum_{i} \left( H^\text{aux}_i + \lambda V_i \right). - - - -To grasp this idea better, this is what would result from working with a Hamiltonian -acting on a total of :math:`8` qubits and having :math:`3` terms, each of them being a -:math:`4`-body interaction. - -.. figure:: ../_static/demonstration_assets/barren_gadgets/gadget-terms-tutorial.png - :align: center - :width: 90% - -For each of the terms :math:`h_1`, :math:`h_2,` and :math:`h_3` we add :math:`4` auxiliary qubits. -In the end, our gadget Hamiltonian acts on :math:`8+3\cdot 4 = 20` qubits. - -The penalization (red) acts only on the auxiliary registers, penalizing each -qubit individually, while the perturbations couple the target with the auxiliary qubits. - -As shown in Ref. [#cichy2022]_, this construction results in a spectrum that, for low energies, is similar -to that of the original Hamiltonian. -This means that by minimizing the gadget Hamiltonian and reaching its global -minimum, the resulting state will be close to the global minimum of -:math:`H^\text{target}.` - -Since it is a local cost function, it is better behaved with respect to -barren plateaus than the global cost function, making it more trainable. -As a result, one can mitigate the onset of cost-function-dependent barren -plateaus by substituting the global cost function with the resulting gadget -and using that for training instead. That is what we will do in the rest of this tutorial. -""" - -############################################################################## -# First, a few imports. PennyLane and NumPy of course, and a few -# functions specific to our tutorial. -# The ``PerturbativeGadget`` class allows the user to generate the gadget Hamiltonian -# from a user-given target Hamiltonian in an automated way. -# For those who want to check its inner workings, -# you can find the code here: -# :download:`barren_gadgets.py `. -# The functions ``get_parameter_shape``, ``generate_random_gate_sequence``, and -# ``build_ansatz`` (for the details: -# :download:`layered_ansatz.py <../_static/demonstration_assets/barren_gadgets/layered_ansatz.py>` -# ) are there to build the parameterized quantum circuit we use in this demo. -# The first computes the shape of the array of trainable parameters that the -# circuit will need. The second generates a random sequence of Pauli rotations -# from :math:`\{R_X, R_Y, R_Z\}` with the right dimension. -# Finally, ``build_ansatz`` puts the pieces together. - -import pennylane as qml -from pennylane import numpy as np -from barren_gadgets.barren_gadgets import PerturbativeGadgets -from barren_gadgets.layered_ansatz import ( - generate_random_gate_sequence, - get_parameter_shape, - build_ansatz, -) - -np.random.seed(3) - -############################################################################## -# Now, let's take the example given above: -# -# .. math:: H = X \otimes X \otimes Y \otimes Z + Z \otimes Y \otimes X \otimes X. -# -# First, we construct our target Hamiltonian in PennyLane. -# For this, we use the -# :class:`~pennylane.Hamiltonian` class. - - -H_target = qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliY(2) @ qml.PauliZ(3) \ - + qml.PauliZ(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliX(3) - -############################################################################## -# Now we can check that we constructed what we wanted. - -print(H_target) - -############################################################################## -# We indeed have a Hamiltonian composed of two terms with the expected Pauli -# words. -# Next, we can construct the corresponding gadget Hamiltonian. -# Using the class ``PerturbativeGadgets``, we can automatically -# generate the gadget Hamiltonian from the target Hamiltonian. -# The object ``gadgetizer`` will contain all the information about the settings of -# the gadgetization procedure (there are quite a few knobs one can tweak, -# but we'll skip that for now). -# Then, the method ``gadgetize`` takes a -# :class:`~pennylane.Hamiltonian` -# object and generates the -# corresponding gadget Hamiltonian. - -gadgetizer = PerturbativeGadgets() -H_gadget = gadgetizer.gadgetize(H_target) -H_gadget - -############################################################################## -# So, let's see what we got. -# We started with 4 target qubits (labelled ``0`` to ``3``) and two 4-body terms. -# Thus we get 4 additional qubits twice (``4`` to ``11``). -# The first 16 elements of our Hamiltonian correspond to the unperturbed part. -# The last 8 are the perturbation. They are a little scrambled, but one can -# recognize the 8 Paulis from the target Hamiltonian on the qubits ``0`` to -# ``3`` and the cyclic pairwise :math:`X` structure on the auxiliaries. -# Indeed, they are :math:`(X_4X_5, X_5X_6, X_6X_7, X_7X_4)` and -# :math:`(X_8X_9, X_9X_{10}, X_{10}X_{11}, X_{11}X_8).` - -############################################################################## -# Training with the gadget Hamiltonian -# ----------------------------------- -# Now that we have a little intuition on how the gadget Hamiltonian construction -# works, we will use it to train. -# Classical simulations of qubit systems are expensive, so we will simplify further -# to a target Hamiltonian with a single term, and show that using the -# gadget Hamiltonian for training allows us to minimize the target Hamiltonian. -# So, let us construct the two Hamiltonians of interest. - - -H_target = 1 * qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliY(2) @ qml.PauliZ(3) -gadgetizer = PerturbativeGadgets(perturbation_factor=10) -H_gadget = gadgetizer.gadgetize(H_target) - -############################################################################## -# Then we need to set up our variational quantum algorithm. -# That is, we choose a circuit ansatz with randomly initialized weights, -# the cost function, the optimizer with its step size, the number of -# optimization steps, and the device to run the circuit on. -# For an ansatz, we will use a variation of the -# `qml.SimplifiedTwoDesign `_, -# which was proposed in previous -# works on cost-function-dependent barren plateaus [#cerezo2021]_. -# I will skip the details of the construction, since it is not our focus here, -# and just show what it looks like. -# Here is the circuit for a small example - -shapes = get_parameter_shape(n_layers=3, n_wires=5) -init_weights = [np.pi / 4] * shapes[0][0] -weights = np.random.uniform(0, np.pi, size=shapes[1]) - - -@qml.qnode(qml.device("default.qubit", wires=range(5))) -def display_circuit(weights): - build_ansatz(initial_layer_weights=init_weights, weights=weights, wires=range(5)) - return qml.expval(qml.PauliZ(wires=0)) - -import matplotlib.pyplot as plt -qml.draw_mpl(display_circuit)(weights) -plt.show() - -############################################################################## -# Now we build the circuit for our actual experiment. - - -# Total number of qubits: target + auxiliary -num_qubits = 4 + 1 * 4 - -# Other parameters of the ansatz: weights and gate sequence -shapes = get_parameter_shape(n_layers=num_qubits, n_wires=num_qubits) -init_weights = [np.pi / 4] * shapes[0][0] -weights = np.random.uniform(0, np.pi, size=shapes[1]) -random_gate_sequence = generate_random_gate_sequence(qml.math.shape(weights)) - -############################################################################## -# For the classical optimization, we will use the standard gradient descent -# algorithm and perform 500 iterations. For the quantum part, we will simulate -# our circuit using the -# `default.qubit `_ -# simulator. - -opt = qml.GradientDescentOptimizer(stepsize=0.1) -max_iter = 500 -dev = qml.device("default.qubit", wires=range(num_qubits)) - -############################################################################## -# Finally, we will use two cost functions and create a -# `QNode `_ for each. -# The first cost function, the training cost, is the loss function of the optimization. -# For the training, we use the gadget Hamiltonian. To ensure -# that our gadget optimization is proceeding as intended, -# we also define another cost function based on the target Hamiltonian. -# We will evaluate its value at each iteration for monitoring purposes, but it -# will not be used in the optimization. - - - -@qml.qnode(dev) -def training_cost(weights): - build_ansatz( - initial_layer_weights=init_weights, - weights=weights, - wires=range(num_qubits), - gate_sequence=random_gate_sequence, - ) - return qml.expval(H_gadget) - - -@qml.qnode(dev) -def monitoring_cost(weights): - build_ansatz( - initial_layer_weights=init_weights, - weights=weights, - wires=range(num_qubits), - gate_sequence=random_gate_sequence, - ) - return qml.expval(H_target) - - -############################################################################## -# The idea is that if we reach the global minimum for the gadget Hamiltonian, we -# should also be close to the global minimum of the target Hamiltonian, which is -# what we are ultimately interested in. -# To see the results and plot them, we will save the cost values -# at each iteration. - -costs_lists = {} -costs_lists["training"] = [training_cost(weights)] -costs_lists["monitoring"] = [monitoring_cost(weights)] - -############################################################################## -# Now everything is set up, let's run the optimization and see how it goes. -# Be careful, this will take a while. - -for it in range(max_iter): - weights = opt.step(training_cost, weights) - costs_lists["training"].append(training_cost(weights)) - costs_lists["monitoring"].append(monitoring_cost(weights)) - - -plt.style.use("seaborn") - -plt.figure() -plt.plot(costs_lists["training"]) -plt.plot(costs_lists["monitoring"]) -plt.legend(["training", "monitoring"]) -plt.xlabel("Number of iterations") -plt.ylabel("Cost values") -plt.show() - -############################################################################## -# -# Since our example target Hamiltonian is a single Pauli string, we know -# without needing any training that it has only :math:`\pm 1` eigenvalues. -# It is a very simple example, but we see that the training of our circuit using -# the gadget Hamiltonian as a cost function did indeed allow us to reach the -# global minimum of the target cost function. -# -# Now that you have an idea of how you can use perturbative gadgets in -# variational quantum algorithms, you can try applying them to more complex -# problems! However, be aware of the exponential scaling of classical -# simulations of quantum systems; adding linearly many auxiliary qubits -# quickly becomes hard to simulate. -# For those interested in the theory behind it or more formal statements of -# "how close" the results using the gadget are from the targeted ones, -# check out the original paper [#cichy2022]_. -# There you will also find further discussions on the advantages and limits of -# this proposal, -# as well as a more general recipe to design other gadget -# constructions with similar properties. -# Also, the complete code with explanations on how to reproduce the -# figures from the paper can be found in -# `this repository `_. -# -# References -# ---------- -# -# .. [#cichy2022] -# -# Cichy, S., Faehrmann, P.K., Khatri, S., Eisert, J. -# "A perturbative gadget for delaying the onset of barren plateaus in variational quantum algorithms." `arXiv:2210.03099 -# `__, 2022. -# -# .. [#cerezo2021] -# -# Cerezo, M., Sone, A., Volkoff, T. et al. -# "Cost function dependent barren plateaus in shallow parametrized quantum circuits." `Nat Commun 12, 1791 -# `__, 2021. -# -# About the author -# ---------------- +r""" +Perturbative Gadgets for Variational Quantum Algorithms +========================================== + +.. meta:: + :property="og:description": Use perturbative gadgets to avoid cost-function-dependent barren plateaus + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_barren_gadgets.png + + +.. related:: + tutorial_barren_plateaus Barren plateaus in quantum neural networks¶ + tutorial_local_cost_functions Alleviating barren plateaus with local cost functions + +*Author: Simon Cichy — Posted: 09 December 2022. Last updated: 09 December 2022.* + +Variational quantum algorithms are seen as one of the most primising candidates +for useful applications of quantum computers in the near term, but there are +still a few hurdles to overcome when it comes to practical implementation. +One of them, is the trainability. +In other words, one needs to ensure that the cost function is not flat. +In this tutorial, we will explore the application of perturbative gadgets in +variational quantum algorithms to outgo the issue of cost-function-dependent +barren plateaus, as proposed in Ref. [#cichy2022]_ + +Some context +------------ + +Barren plateaus refer to the phenomenon where the gradients of the cost function +decay exponentially with the size of the problem. Essentially, the cost +landscape becomes flat, with exception of some small regions, e.g., around +the minimum. +That is a problem because increasing the precision of the cost +function requires more measurements from the quantum device due to shot noise, +and an exponential number of measurements would render the algorithm impractical. +If you are not familiar yet with the concept of barren plateaus, I recommend you +first check out the demonstrations on :doc:`barren plateaus ` +and :doc:`avoiding barren plateaus with local cost functions `. + +As presented in the second aforementioned demo, barren plateaus are more severe when using global +cost functions compared to local ones. +A global cost function requires the simultaneous measurement of all +qubits at once. In contrast, a local one is constructed from terms that only +act on a small subset of qubits. + +We want to explore this topic further and learn about one possible mitigation +strategy. +Thinking about Variational Quantum Eigensolver (VQE) applications, let us consider cost functions that are +expectation values of Hamiltonians such as + +.. math:: C(\theta) = \operatorname{Tr} \left[ H V(\theta) |00\ldots 0\rangle \! \langle 00\ldots 0| V(\theta)^\dagger\right]. + +Here :math:`|00\ldots 0\rangle` is our initial state, +:math:`V(\theta)` is the circuit ansatz and :math:`H` the Hamiltonian +whose expectation value we need to minimize. +In some cases, it is easy to find a local cost function which can substitute a global one with the same ground state. +Take, for instance, the following Hamiltonians that induce global and local cost functions, respectively. + + +.. math:: H_G = \mathbb{I} - |00\ldots 0\rangle \! \langle 00\ldots 0| \quad \textrm{ and } \quad H_L = \mathbb{I} - \frac{1}{n} \sum_j |0\rangle \! \langle 0|_j. + +Those are two different Hamiltonians (not just different formulations of the +same one), but they share the same ground state: + + +.. math:: |\psi_{\textrm{min}} \rangle = |00\ldots 0\rangle. + +Therefore, one can work with either Hamiltonian to perform the VQE routine. +However, it is not always so simple. +What if we want to find the minimum eigenenergy of +:math:`H = X \otimes X \otimes Y \otimes Z + Z \otimes Y \otimes X \otimes X` ? +It is not always trivial to construct a local cost +function that has the same minimum as the cost function of interest. +This is where perturbative gadgets come into play! + + +The definitions +--------------- +Perturbative gadgets are a common tool in adiabatic quantum computing. +Their goal is to find a Hamiltonian with local interactions that mimics +another Hamiltonian with more complex couplings. + +Ideally, they would want to implement the target Hamiltonian with complex couplings, but since it's hard to implement more than few-body interactions on hardware, they cannot do so. Perturbative gadgets work by increasing the dimension of the Hilbert space (i.e., the number +of qubits) and "encoding" the target Hamiltonian in the low-energy +subspace of a so-called "gadget" Hamiltonian. + +Let us now construct such a gadget Hamiltonian tailored for VQE applications. +First, we start from a target Hamiltonian that is a linear combination of +Pauli words acting on :math:`k` qubits each: + +.. math:: H^\text{target} = \sum_i c_i h_i, + +where :math:`h_i = \sigma_{i,1} \otimes \sigma_{i,2} \otimes \ldots \otimes \sigma_{i,k},` +:math:`\sigma_{i,j} \in \{ X, Y, Z \},` and :math:`c_i \in \mathbb{R}.` +Now we construct the gadget Hamiltonian. +For each term :math:`h_i,` we will need :math:`k` additional qubits, which we +call auxiliary qubits, and to add two terms to the Hamiltonian: +an "unperturbed" part :math:`H^\text{aux}_i` and a perturbation :math:`V_i` +of strength :math:`\lambda.` +The unperturbed part penalizes each of the newly added qubits for not being in +the :math:`|0\rangle` state + +.. math:: H^\text{aux}_i = \sum_{j=1}^k |1\rangle \! \langle 1|_{i,j} = \sum_{j=1}^k \frac{1}{2}(\mathbb{I} - Z_{i,j}). + +On the other hand, the perturbation part implements one of the operators in the Pauli word +:math:`\sigma_{i,j}` on the corresponding qubit of the target register and a +pair of Pauli :math:`X` gates on two of the auxiliary qubits: + +.. math:: V_i = \sum_{j=1}^k c_{i,j} \sigma_{i,j} \otimes X_{i,j} \otimes X_{i,(j+1) \mathrm{mod }k}. + +In the end, + +.. math:: H^\text{gad} = \sum_{i} \left( H^\text{aux}_i + \lambda V_i \right). + + + +To grasp this idea better, this is what would result from working with a Hamiltonian +acting on a total of :math:`8` qubits and having :math:`3` terms, each of them being a +:math:`4`-body interaction. + +.. figure:: ../_static/demonstration_assets/barren_gadgets/gadget-terms-tutorial.png + :align: center + :width: 90% + +For each of the terms :math:`h_1`, :math:`h_2,` and :math:`h_3` we add :math:`4` auxiliary qubits. +In the end, our gadget Hamiltonian acts on :math:`8+3\cdot 4 = 20` qubits. + +The penalization (red) acts only on the auxiliary registers, penalizing each +qubit individually, while the perturbations couple the target with the auxiliary qubits. + +As shown in Ref. [#cichy2022]_, this construction results in a spectrum that, for low energies, is similar +to that of the original Hamiltonian. +This means that by minimizing the gadget Hamiltonian and reaching its global +minimum, the resulting state will be close to the global minimum of +:math:`H^\text{target}.` + +Since it is a local cost function, it is better behaved with respect to +barren plateaus than the global cost function, making it more trainable. +As a result, one can mitigate the onset of cost-function-dependent barren +plateaus by substituting the global cost function with the resulting gadget +and using that for training instead. That is what we will do in the rest of this tutorial. +""" + +############################################################################## +# First, a few imports. PennyLane and NumPy of course, and a few +# functions specific to our tutorial. +# The ``PerturbativeGadget`` class allows the user to generate the gadget Hamiltonian +# from a user-given target Hamiltonian in an automated way. +# For those who want to check its inner workings, +# you can find the code here: +# :download:`barren_gadgets.py `. +# The functions ``get_parameter_shape``, ``generate_random_gate_sequence``, and +# ``build_ansatz`` (for the details: +# :download:`layered_ansatz.py <../_static/demonstration_assets/barren_gadgets/layered_ansatz.py>` +# ) are there to build the parameterized quantum circuit we use in this demo. +# The first computes the shape of the array of trainable parameters that the +# circuit will need. The second generates a random sequence of Pauli rotations +# from :math:`\{R_X, R_Y, R_Z\}` with the right dimension. +# Finally, ``build_ansatz`` puts the pieces together. + +import pennylane as qml +from pennylane import numpy as np +from barren_gadgets.barren_gadgets import PerturbativeGadgets +from barren_gadgets.layered_ansatz import ( + generate_random_gate_sequence, + get_parameter_shape, + build_ansatz, +) + +np.random.seed(3) + +############################################################################## +# Now, let's take the example given above: +# +# .. math:: H = X \otimes X \otimes Y \otimes Z + Z \otimes Y \otimes X \otimes X. +# +# First, we construct our target Hamiltonian in PennyLane. +# For this, we use the +# :class:`~pennylane.Hamiltonian` class. + + +H_target = qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliY(2) @ qml.PauliZ(3) \ + + qml.PauliZ(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliX(3) + +############################################################################## +# Now we can check that we constructed what we wanted. + +print(H_target) + +############################################################################## +# We indeed have a Hamiltonian composed of two terms with the expected Pauli +# words. +# Next, we can construct the corresponding gadget Hamiltonian. +# Using the class ``PerturbativeGadgets``, we can automatically +# generate the gadget Hamiltonian from the target Hamiltonian. +# The object ``gadgetizer`` will contain all the information about the settings of +# the gadgetization procedure (there are quite a few knobs one can tweak, +# but we'll skip that for now). +# Then, the method ``gadgetize`` takes a +# :class:`~pennylane.Hamiltonian` +# object and generates the +# corresponding gadget Hamiltonian. + +gadgetizer = PerturbativeGadgets() +H_gadget = gadgetizer.gadgetize(H_target) +H_gadget + +############################################################################## +# So, let's see what we got. +# We started with 4 target qubits (labelled ``0`` to ``3``) and two 4-body terms. +# Thus we get 4 additional qubits twice (``4`` to ``11``). +# The first 16 elements of our Hamiltonian correspond to the unperturbed part. +# The last 8 are the perturbation. They are a little scrambled, but one can +# recognize the 8 Paulis from the target Hamiltonian on the qubits ``0`` to +# ``3`` and the cyclic pairwise :math:`X` structure on the auxiliaries. +# Indeed, they are :math:`(X_4X_5, X_5X_6, X_6X_7, X_7X_4)` and +# :math:`(X_8X_9, X_9X_{10}, X_{10}X_{11}, X_{11}X_8).` + +############################################################################## +# Training with the gadget Hamiltonian +# ----------------------------------- +# Now that we have a little intuition on how the gadget Hamiltonian construction +# works, we will use it to train. +# Classical simulations of qubit systems are expensive, so we will simplify further +# to a target Hamiltonian with a single term, and show that using the +# gadget Hamiltonian for training allows us to minimize the target Hamiltonian. +# So, let us construct the two Hamiltonians of interest. + + +H_target = 1 * qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliY(2) @ qml.PauliZ(3) +gadgetizer = PerturbativeGadgets(perturbation_factor=10) +H_gadget = gadgetizer.gadgetize(H_target) + +############################################################################## +# Then we need to set up our variational quantum algorithm. +# That is, we choose a circuit ansatz with randomly initialized weights, +# the cost function, the optimizer with its step size, the number of +# optimization steps, and the device to run the circuit on. +# For an ansatz, we will use a variation of the +# `qml.SimplifiedTwoDesign `_, +# which was proposed in previous +# works on cost-function-dependent barren plateaus [#cerezo2021]_. +# I will skip the details of the construction, since it is not our focus here, +# and just show what it looks like. +# Here is the circuit for a small example + +shapes = get_parameter_shape(n_layers=3, n_wires=5) +init_weights = [np.pi / 4] * shapes[0][0] +weights = np.random.uniform(0, np.pi, size=shapes[1]) + + +@qml.qnode(qml.device("default.qubit", wires=range(5))) +def display_circuit(weights): + build_ansatz(initial_layer_weights=init_weights, weights=weights, wires=range(5)) + return qml.expval(qml.PauliZ(wires=0)) + +import matplotlib.pyplot as plt +qml.draw_mpl(display_circuit)(weights) +plt.show() + +############################################################################## +# Now we build the circuit for our actual experiment. + + +# Total number of qubits: target + auxiliary +num_qubits = 4 + 1 * 4 + +# Other parameters of the ansatz: weights and gate sequence +shapes = get_parameter_shape(n_layers=num_qubits, n_wires=num_qubits) +init_weights = [np.pi / 4] * shapes[0][0] +weights = np.random.uniform(0, np.pi, size=shapes[1]) +random_gate_sequence = generate_random_gate_sequence(qml.math.shape(weights)) + +############################################################################## +# For the classical optimization, we will use the standard gradient descent +# algorithm and perform 500 iterations. For the quantum part, we will simulate +# our circuit using the +# `default.qubit `_ +# simulator. + +opt = qml.GradientDescentOptimizer(stepsize=0.1) +max_iter = 500 +dev = qml.device("default.qubit", wires=range(num_qubits)) + +############################################################################## +# Finally, we will use two cost functions and create a +# `QNode `_ for each. +# The first cost function, the training cost, is the loss function of the optimization. +# For the training, we use the gadget Hamiltonian. To ensure +# that our gadget optimization is proceeding as intended, +# we also define another cost function based on the target Hamiltonian. +# We will evaluate its value at each iteration for monitoring purposes, but it +# will not be used in the optimization. + + + +@qml.qnode(dev) +def training_cost(weights): + build_ansatz( + initial_layer_weights=init_weights, + weights=weights, + wires=range(num_qubits), + gate_sequence=random_gate_sequence, + ) + return qml.expval(H_gadget) + + +@qml.qnode(dev) +def monitoring_cost(weights): + build_ansatz( + initial_layer_weights=init_weights, + weights=weights, + wires=range(num_qubits), + gate_sequence=random_gate_sequence, + ) + return qml.expval(H_target) + + +############################################################################## +# The idea is that if we reach the global minimum for the gadget Hamiltonian, we +# should also be close to the global minimum of the target Hamiltonian, which is +# what we are ultimately interested in. +# To see the results and plot them, we will save the cost values +# at each iteration. + +costs_lists = {} +costs_lists["training"] = [training_cost(weights)] +costs_lists["monitoring"] = [monitoring_cost(weights)] + +############################################################################## +# Now everything is set up, let's run the optimization and see how it goes. +# Be careful, this will take a while. + +for it in range(max_iter): + weights = opt.step(training_cost, weights) + costs_lists["training"].append(training_cost(weights)) + costs_lists["monitoring"].append(monitoring_cost(weights)) + + +plt.style.use("seaborn") + +plt.figure() +plt.plot(costs_lists["training"]) +plt.plot(costs_lists["monitoring"]) +plt.legend(["training", "monitoring"]) +plt.xlabel("Number of iterations") +plt.ylabel("Cost values") +plt.show() + +############################################################################## +# +# Since our example target Hamiltonian is a single Pauli string, we know +# without needing any training that it has only :math:`\pm 1` eigenvalues. +# It is a very simple example, but we see that the training of our circuit using +# the gadget Hamiltonian as a cost function did indeed allow us to reach the +# global minimum of the target cost function. +# +# Now that you have an idea of how you can use perturbative gadgets in +# variational quantum algorithms, you can try applying them to more complex +# problems! However, be aware of the exponential scaling of classical +# simulations of quantum systems; adding linearly many auxiliary qubits +# quickly becomes hard to simulate. +# For those interested in the theory behind it or more formal statements of +# "how close" the results using the gadget are from the targeted ones, +# check out the original paper [#cichy2022]_. +# There you will also find further discussions on the advantages and limits of +# this proposal, +# as well as a more general recipe to design other gadget +# constructions with similar properties. +# Also, the complete code with explanations on how to reproduce the +# figures from the paper can be found in +# `this repository `_. +# +# References +# ---------- +# +# .. [#cichy2022] +# +# Cichy, S., Faehrmann, P.K., Khatri, S., Eisert, J. +# "A perturbative gadget for delaying the onset of barren plateaus in variational quantum algorithms." `arXiv:2210.03099 +# `__, 2022. +# +# .. [#cerezo2021] +# +# Cerezo, M., Sone, A., Volkoff, T. et al. +# "Cost function dependent barren plateaus in shallow parametrized quantum circuits." `Nat Commun 12, 1791 +# `__, 2021. +# +# About the author +# ---------------- # \ No newline at end of file diff --git a/demonstrations/tutorial_differentiable_HF.py b/demonstrations/tutorial_differentiable_HF.py index d1820613d1..e06dc3fa4d 100644 --- a/demonstrations/tutorial_differentiable_HF.py +++ b/demonstrations/tutorial_differentiable_HF.py @@ -1,393 +1,393 @@ -r""" - -Differentiable Hartree-Fock -=========================== - -.. meta:: - :property="og:description": Learn how to use the differentiable Hartree-Fock solver - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/differentiable_HF.png - -.. related:: - tutorial_quantum_chemistry Building molecular Hamiltonians - tutorial_vqe A brief overview of VQE - tutorial_givens_rotations Givens rotations for quantum chemistry - tutorial_adaptive_circuits Adaptive circuits for quantum chemistry - - -*Author: Soran Jahangiri — Posted: 09 May 2022. Last updated: 09 May 2022.* - -In this tutorial, you will learn how to use PennyLane's differentiable Hartree-Fock solver -[#arrazola2021]_. The quantum chemistry module in PennyLane, :mod:`qml.qchem `, -provides built-in methods for constructing -atomic and molecular orbitals, building Fock matrices and solving the self-consistent field -equations to obtain optimized orbitals which can be used to construct fully-differentiable -molecular Hamiltonians. PennyLane allows users to natively compute derivatives of all these objects -with respect to the underlying parameters using the methods of -`automatic differentiation `_. We -introduce a workflow to jointly optimize circuit parameters, nuclear coordinates and basis set -parameters in a variational quantum eigensolver algorithm. You will also learn how to visualize the -atomic and molecular orbitals which can be used to create an animation like this: - -.. figure:: /_static/demonstration_assets/differentiable_HF/h2.gif - :width: 60% - :align: center - - The bonding molecular orbital of hydrogen visualized during a full geometry, circuit and - basis set optimization. - -Let's get started! - -Differentiable Hamiltonians ---------------------------- - -Variational quantum algorithms aim to calculate the energy of a molecule by constructing a -parameterized quantum circuit and finding a set of parameters that minimize the expectation value of -the electronic `molecular Hamiltonian `_. The -optimization can be carried out by computing the gradients of the expectation value with respect to -these parameters and iteratively updating them until convergence is achieved. In principle, the -optimization process is not limited to the circuit parameters and can be extended to include the -parameters of the Hamiltonian that can be optimized concurrently with the circuit parameters. The -aim is now to obtain the set of parameters that minimize the following expectation value - -.. math:: \left \langle \Psi(\theta) | H(\beta) | \Psi(\theta) \right \rangle, - -where :math:`\theta` and :math:`\beta` represent the circuit and Hamiltonian parameters, -respectively. - -Computing the gradient of a molecular Hamiltonian is challenging because the dependency of the -Hamiltonian on the molecular parameters is typically not very straightforward. This makes symbolic -differentiation methods, which obtain derivatives of an input function by direct mathematical -manipulation, of limited scope. Furthermore, numerical differentiation methods based on -`finite differences `_ are not always -reliable due to their intrinsic instability, especially when the number of -differentiable parameters is large. These limitations can be alleviated by using automatic -differentiation methods which can be used to compute exact gradients of a function, implemented with -computer code, using resources comparable to those required to evaluate the function itself. - -Efficient optimization of the molecular Hamiltonian parameters in a variational quantum algorithm -is essential for tackling problems such as -`geometry optimization `_ and vibrational -frequency -calculations. These problems require computing the first- and second-order derivatives of the -molecular energy with respect to nuclear coordinates which can be efficiently obtained if the -variational workflow is automatically differentiable. Another important example is the simultaneous -optimization of the parameters of the basis set used to construct the atomic orbitals which can in -principle increase the accuracy of the computed energy without increasing the number of qubits in a -quantum simulation. The joint optimization of the circuit and Hamiltonian parameters can also be -used when the chemical problem involves optimizing the parameters of external potentials. - -The Hartree-Fock method ------------------------ - -The main goal of the Hartree-Fock method is to obtain molecular orbitals that minimize the -energy of a system where electrons are treated as independent particles that experience a mean field -generated by the other electrons. These optimized molecular orbitals are then used to -construct one- and two-body electron integrals in the basis of molecular orbitals, :math:`\phi,` - -.. math:: h_{pq} =\int dx \,\phi_p^*(x)\left(-\frac{\nabla^2}{2}-\sum_{i=1}^N\frac{Z_i}{|r-R_i|}\right)\phi_q(x),\\\\ -.. math:: h_{pqrs} = \int dx_1 dx_2\, \frac{\phi_p^*(x_1)\phi_q^*(x_2)\phi_r(x_2)\phi_s(x_1)}{|r_1-r_2|}. - -These integrals are used to generate a differentiable -`second-quantized `_ molecular Hamiltonian as - - -.. math:: H=\sum_{pq} h_{pq}a_p^\dagger a_q +\frac{1}{2}\sum_{pqrs}h_{pqrs}a_p^\dagger a_q^\dagger a_r a_s, - -where :math:`a^\dagger` and :math:`a` are the fermionic creation and annihilation operators, -respectively. This Hamiltonian is then transformed to the qubit basis. Let's see how this can be -done in PennyLane. - -To get started, we need to define the atomic symbols and the nuclear coordinates of the molecule. -For the hydrogen molecule we have -""" - -import pennylane as qml -import numpy as np -import jax -import jax.numpy as jnp -import matplotlib.pyplot as plt -np.set_printoptions(precision=5) -jax.config.update("jax_enable_x64", True) - -symbols = ["H", "H"] -# optimized geometry at the Hartree-Fock level -geometry = jnp.array([[-0.672943567415407, 0.0, 0.0], - [ 0.672943567415407, 0.0, 0.0]]) - -############################################################################## -# We can now compute the Hartree-Fock energy and its gradient with respect to the -# nuclear coordinates. To do that, we create a molecule object that stores all the molecular -# parameters needed to perform a Hartree-Fock calculation. - -mol = qml.qchem.Molecule(symbols, geometry) - -############################################################################## -# The Hartree-Fock energy can now be computed with the -# :func:`~.pennylane.qchem.hf_energy` function which is a function transform - -qml.qchem.hf_energy(mol)() - -############################################################################## -# We now compute the gradient of the energy with respect to the nuclear coordinates - -jax.grad(qml.qchem.hf_energy(mol), argnums=0)(geometry, mol.coeff, mol.alpha) - -############################################################################## -# The obtained gradients are equal or very close to zero because the geometry we used here has been -# previously optimized at the Hartree-Fock level. You can use a different geometry and verify that -# the newly computed gradients are not all zero. -# -# We can also compute the values and gradients of several other quantities that are obtained during -# the Hartree-Fock procedure. These include all integrals over basis functions, matrices formed from -# these integrals and the one- and two-body integrals over molecular orbitals [#arrazola2021]_. -# Let's look at a few examples. -# -# We first compute the overlap integral between the two S-type atomic orbitals of the hydrogen -# atoms. Here we are using the `STO-3G `_ -# basis set in which each of the atomic orbitals is represented by one basis function composed of -# three primitive Gaussian functions. These basis functions can be accessed from the molecule -# object as - -S1 = mol.basis_set[0] -S2 = mol.basis_set[1] - -############################################################################## -# We can check the parameters of the basis functions as - -for param in S1.params: - print(param) - -############################################################################## -# This returns the exponents, contraction coefficients and the centres of the three Gaussian -# functions of the STO-3G basis set. These parameters can be also obtained individually by using -# ``S1.alpha``, ``S1.coeff`` and ``S1.r``, respectively. You can verify that both of the ``S1`` an -# ``S2`` atomic orbitals have the same exponents and contraction coefficients but are centred on -# different hydrogen atoms. You can also verify that the orbitals are S-type by printing the angular -# momentum quantum numbers with - -S1.l - -############################################################################## -# This gives us a tuple of three integers, representing the exponents of the :math:`x,` :math:`y` -# and :math:`z` components in the Gaussian functions [#arrazola2021]_. -# -# We can now compute the overlap integral, -# -# .. math:: -# -# S_{\mu \nu} = \int \chi_\mu^* (r) \chi_\nu (r) dr -# -# between the atomic orbitals :math:`\chi,` by passing the orbitals and the initial values of their -# centres to the :func:`~.pennylane.qchem.overlap_integral` function. The centres of the orbitals -# are those of the hydrogen atoms by default and are therefore treated as differentiable parameters -# by PennyLane. - -qml.qchem.overlap_integral(S1, S2)() - -############################################################################## -# You can verify that the overlap integral between two identical atomic orbitals is equal to one. -# We can now compute the gradient of the overlap integral with respect to the orbital centres - -jax.grad(qml.qchem.overlap_integral(S1, S2))(geometry, mol.coeff, mol.alpha) - -############################################################################## -# Can you explain why some of the computed gradients are zero? -# -# Let's now plot the atomic orbitals and their overlap. We can do it by using -# the :py:meth:`~.pennylane.qchem.Molecule.atomic_orbital` function, which evaluates the -# atomic orbital at a given coordinate. For instance, the value of the S orbital on the first -# hydrogen atom can be computed at the origin as - -V1 = mol.atomic_orbital(0) -V1(0.0, 0.0, 0.0) - -############################################################################## -# We can evaluate this orbital at different points along the :math:`x` axis and plot it. - -x = np.linspace(-5, 5, 1000) -plt.plot(x, V1(x, 0.0, 0.0), color='teal') -plt.xlabel('X [Bohr]') -plt.show() - -############################################################################## -# We can also plot the second S orbital and visualize the overlap between them - -V2 = mol.atomic_orbital(1) -plt.plot(x, V1(x, 0.0, 0.0), color='teal') -plt.plot(x, V2(x, 0.0, 0.0), color='teal') -plt.fill_between( - x, np.minimum(V1(x, 0.0, 0.0), V2(x, 0.0, 0.0)), color = 'red', alpha = 0.5, hatch = '||') -plt.xlabel('X [Bohr]') -plt.show() - -############################################################################## -# By looking at the orbitals, can you guess at what distance the value of the overlap becomes -# negligible? Can you verify your guess by computing the overlap at that distance? -# -# Similarly, we can plot the molecular orbitals of the hydrogen molecule obtained from the -# Hartree-Fock calculations. We plot the cross-section of the bonding orbital on the :math:`x-y` -# plane. - -n = 30 # number of grid points along each axis -qml.qchem.hf_energy(mol)() -mol.mo_coefficients = mol.mo_coefficients.T -mo = mol.molecular_orbital(0) -x, y = np.meshgrid(np.linspace(-2, 2, n), - np.linspace(-2, 2, n)) -val = np.vectorize(mo)(x, y, 0) -val = np.array([val[i][j] for i in range(n) for j in range(n)]).reshape(n, n) - -fig, ax = plt.subplots() -co = ax.contour(x, y, val, 10, cmap='summer_r', zorder=0) -ax.clabel(co, inline=2, fontsize=10) -plt.scatter(mol.coordinates[:,0], mol.coordinates[:,1], s = 80, color='black') -ax.set_xlabel('X [Bohr]') -ax.set_ylabel('Y [Bohr]') -plt.show() - -############################################################################## -# VQE simulations -# --------------- -# -# By performing the Hartree-Fock calculations, we obtain a set of one- and two-body integrals -# over molecular orbitals that can be used to construct the molecular Hamiltonian with the -# :func:`~.pennylane.qchem.molecular_hamiltonian` function. - -hamiltonian, qubits = qml.qchem.molecular_hamiltonian(mol) -print(hamiltonian) - -############################################################################## -# The Hamiltonian contains 15 terms and, importantly, the coefficients of the Hamiltonian are all -# differentiable. We can construct a circuit and perform a VQE simulation in which both of the -# circuit parameters and the nuclear coordinates are optimized simultaneously by using the computed -# gradients. We will have two sets of differentiable parameters: the first set is the -# rotation angles of the excitation gates which are applied to the reference Hartree-Fock state -# to construct the exact ground state. The second set contains the nuclear coordinates of the -# hydrogen atoms. - -dev = qml.device("default.qubit", wires=4) -def energy(): - @qml.qnode(dev, interface="jax") - def circuit(*args): - qml.BasisState(np.array([1, 1, 0, 0]), wires=range(4)) - qml.DoubleExcitation(*args[0], wires=[0, 1, 2, 3]) - mol = qml.qchem.Molecule(symbols, geometry, alpha=args[3], coeff=args[2]) - H = qml.qchem.molecular_hamiltonian(mol, args=args[1:])[0] - return qml.expval(H) - return circuit - -############################################################################## -# Note that we only use the :class:`~.pennylane.DoubleExcitation` gate as the -# :class:`~.pennylane.SingleExcitation` ones can be neglected in this particular example -# [#szabo1996]_. We now compute the gradients of the energy with respect to the circuit parameter -# and the nuclear coordinates and update the parameters iteratively. Note that the nuclear -# coordinate gradients are simply the forces on the atomic nuclei. - -# initial value of the circuit parameter -circuit_param = jnp.array([0.0]) - -geometry = jnp.array([[0.0, 0.02, -0.672943567415407], - [0.1, 0.0, 0.672943567415407]]) - -for n in range(36): - mol = qml.qchem.Molecule(symbols, geometry) - args = [circuit_param, geometry, mol.coeff, mol.alpha] - # gradient for circuit parameters - g_param = jax.grad(energy(), argnums = 0)(*args) - circuit_param = circuit_param - 0.25 * g_param[0] - - # gradient for nuclear coordinates - forces = jax.grad(energy(), argnums = 1)(*args) - geometry = geometry - 0.5 * forces - - if n % 5 == 0: - print(f'n: {n}, E: {energy()(*args):.8f}, Force-max: {abs(forces).max():.8f}') - -############################################################################## -# After 35 steps of optimization, the forces on the atomic nuclei and the gradient of the -# circuit parameter are both approaching zero, and the energy of the molecule is that of the -# optimized geometry at the -# `full-CI `_ level: -# :math:`-1.1373060483` Ha. You can print the optimized geometry and verify that the final bond -# length of hydrogen is identical to the one computed with full-CI which is :math:`1.389` -# `Bohr `_. -# -# We are now ready to perform a full optimization where the circuit parameters, the nuclear -# coordinates and the basis set parameters are all differentiable parameters that can be optimized -# simultaneously. - -symbols = ["H", "H"] -# initial values of the nuclear coordinates -geometry = jnp.array([[0.0, 0.0, -0.672943567415407], - [0.0, 0.0, 0.672943567415407]]) - -# initial values of the basis set contraction coefficients -coeff = jnp.array([[0.1543289673, 0.5353281423, 0.4446345422], - [0.1543289673, 0.5353281423, 0.4446345422]]) - -# initial values of the basis set exponents -alpha = jnp.array([[3.42525091, 0.62391373, 0.1688554], - [3.42525091, 0.62391373, 0.1688554]]) - -# initial value of the circuit parameter -circuit_param = jnp.array([0.0]) - -mol = qml.qchem.Molecule(symbols, geometry, coeff=coeff, alpha=alpha) -args = [circuit_param, geometry, coeff, alpha] - -for n in range(36): - args = [circuit_param, geometry, coeff, alpha] - mol = qml.qchem.Molecule(symbols, geometry, alpha=alpha, coeff=coeff) - - # gradient for circuit parameters - g_param = jax.grad(energy(), argnums=[0, 1, 2, 3])(*args)[0] - circuit_param = circuit_param - 0.25 * g_param[0] - - # gradient for nuclear coordinates - value, gradients = jax.value_and_grad(energy(), argnums=[1, 2, 3])(*args) - geometry = geometry - 0.5 * gradients[0] - alpha = alpha - 0.25 * gradients[2] - coeff = coeff - 0.25 * gradients[1] - - if n % 5 == 0: - print(f'n: {n}, E: {value:.8f}, Force-max: {abs(gradients[0]).max():.8f}') - -############################################################################## -# You can also print the gradients of the circuit and basis set parameters and confirm that they are -# approaching zero. The computed energy of :math:`-1.14040160` Ha is -# lower than the full-CI energy, :math:`-1.1373060483` Ha (obtained with the STO-3G basis set for -# the hydrogen molecule) because we have optimized the basis set parameters in our example. This -# means that we can reach a lower energy for hydrogen without increasing the basis set size, which -# would otherwise lead to a larger number of qubits. -# -# Conclusions -# ----------- -# This tutorial introduces an important feature of PennyLane that allows performing -# fully-differentiable Hartree-Fock and subsequently VQE simulations. This feature provides two -# major benefits: i) All gradient computations needed for parameter optimization can be carried out -# with the elegant methods of automatic differentiation which facilitates simultaneous optimizations -# of circuit and Hamiltonian parameters in applications such as VQE molecular geometry -# optimizations. ii) By optimizing the molecular parameters such as the exponent and contraction -# coefficients of Gaussian functions of the basis set, one can reach a lower energy without -# increasing the number of basis functions. Can you think of other interesting molecular parameters -# that can be optimized along with the nuclear coordinates and basis set parameters that we -# optimized in this tutorial? -# -# References -# ---------- -# -# .. [#arrazola2021] -# -# Juan Miguel Arrazola, Soran Jahangiri, Alain Delgado, Jack Ceroni *et al.*, "Differentiable -# quantum computational chemistry with PennyLane". `arXiv:2111.09967 -# `__ -# -# .. [#szabo1996] -# -# Attila Szabo, Neil S. Ostlund, "Modern Quantum Chemistry: Introduction to Advanced Electronic -# Structure Theory". Dover Publications, 1996. -# -# -# About the author -# ---------------- -# +r""" + +Differentiable Hartree-Fock +=========================== + +.. meta:: + :property="og:description": Learn how to use the differentiable Hartree-Fock solver + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/differentiable_HF.png + +.. related:: + tutorial_quantum_chemistry Building molecular Hamiltonians + tutorial_vqe A brief overview of VQE + tutorial_givens_rotations Givens rotations for quantum chemistry + tutorial_adaptive_circuits Adaptive circuits for quantum chemistry + + +*Author: Soran Jahangiri — Posted: 09 May 2022. Last updated: 09 May 2022.* + +In this tutorial, you will learn how to use PennyLane's differentiable Hartree-Fock solver +[#arrazola2021]_. The quantum chemistry module in PennyLane, :mod:`qml.qchem `, +provides built-in methods for constructing +atomic and molecular orbitals, building Fock matrices and solving the self-consistent field +equations to obtain optimized orbitals which can be used to construct fully-differentiable +molecular Hamiltonians. PennyLane allows users to natively compute derivatives of all these objects +with respect to the underlying parameters using the methods of +`automatic differentiation `_. We +introduce a workflow to jointly optimize circuit parameters, nuclear coordinates and basis set +parameters in a variational quantum eigensolver algorithm. You will also learn how to visualize the +atomic and molecular orbitals which can be used to create an animation like this: + +.. figure:: /_static/demonstration_assets/differentiable_HF/h2.gif + :width: 60% + :align: center + + The bonding molecular orbital of hydrogen visualized during a full geometry, circuit and + basis set optimization. + +Let's get started! + +Differentiable Hamiltonians +--------------------------- + +Variational quantum algorithms aim to calculate the energy of a molecule by constructing a +parameterized quantum circuit and finding a set of parameters that minimize the expectation value of +the electronic `molecular Hamiltonian `_. The +optimization can be carried out by computing the gradients of the expectation value with respect to +these parameters and iteratively updating them until convergence is achieved. In principle, the +optimization process is not limited to the circuit parameters and can be extended to include the +parameters of the Hamiltonian that can be optimized concurrently with the circuit parameters. The +aim is now to obtain the set of parameters that minimize the following expectation value + +.. math:: \left \langle \Psi(\theta) | H(\beta) | \Psi(\theta) \right \rangle, + +where :math:`\theta` and :math:`\beta` represent the circuit and Hamiltonian parameters, +respectively. + +Computing the gradient of a molecular Hamiltonian is challenging because the dependency of the +Hamiltonian on the molecular parameters is typically not very straightforward. This makes symbolic +differentiation methods, which obtain derivatives of an input function by direct mathematical +manipulation, of limited scope. Furthermore, numerical differentiation methods based on +`finite differences `_ are not always +reliable due to their intrinsic instability, especially when the number of +differentiable parameters is large. These limitations can be alleviated by using automatic +differentiation methods which can be used to compute exact gradients of a function, implemented with +computer code, using resources comparable to those required to evaluate the function itself. + +Efficient optimization of the molecular Hamiltonian parameters in a variational quantum algorithm +is essential for tackling problems such as +`geometry optimization `_ and vibrational +frequency +calculations. These problems require computing the first- and second-order derivatives of the +molecular energy with respect to nuclear coordinates which can be efficiently obtained if the +variational workflow is automatically differentiable. Another important example is the simultaneous +optimization of the parameters of the basis set used to construct the atomic orbitals which can in +principle increase the accuracy of the computed energy without increasing the number of qubits in a +quantum simulation. The joint optimization of the circuit and Hamiltonian parameters can also be +used when the chemical problem involves optimizing the parameters of external potentials. + +The Hartree-Fock method +----------------------- + +The main goal of the Hartree-Fock method is to obtain molecular orbitals that minimize the +energy of a system where electrons are treated as independent particles that experience a mean field +generated by the other electrons. These optimized molecular orbitals are then used to +construct one- and two-body electron integrals in the basis of molecular orbitals, :math:`\phi,` + +.. math:: h_{pq} =\int dx \,\phi_p^*(x)\left(-\frac{\nabla^2}{2}-\sum_{i=1}^N\frac{Z_i}{|r-R_i|}\right)\phi_q(x),\\\\ +.. math:: h_{pqrs} = \int dx_1 dx_2\, \frac{\phi_p^*(x_1)\phi_q^*(x_2)\phi_r(x_2)\phi_s(x_1)}{|r_1-r_2|}. + +These integrals are used to generate a differentiable +`second-quantized `_ molecular Hamiltonian as + + +.. math:: H=\sum_{pq} h_{pq}a_p^\dagger a_q +\frac{1}{2}\sum_{pqrs}h_{pqrs}a_p^\dagger a_q^\dagger a_r a_s, + +where :math:`a^\dagger` and :math:`a` are the fermionic creation and annihilation operators, +respectively. This Hamiltonian is then transformed to the qubit basis. Let's see how this can be +done in PennyLane. + +To get started, we need to define the atomic symbols and the nuclear coordinates of the molecule. +For the hydrogen molecule we have +""" + +import pennylane as qml +import numpy as np +import jax +import jax.numpy as jnp +import matplotlib.pyplot as plt +np.set_printoptions(precision=5) +jax.config.update("jax_enable_x64", True) + +symbols = ["H", "H"] +# optimized geometry at the Hartree-Fock level +geometry = jnp.array([[-0.672943567415407, 0.0, 0.0], + [ 0.672943567415407, 0.0, 0.0]]) + +############################################################################## +# We can now compute the Hartree-Fock energy and its gradient with respect to the +# nuclear coordinates. To do that, we create a molecule object that stores all the molecular +# parameters needed to perform a Hartree-Fock calculation. + +mol = qml.qchem.Molecule(symbols, geometry) + +############################################################################## +# The Hartree-Fock energy can now be computed with the +# :func:`~.pennylane.qchem.hf_energy` function which is a function transform + +qml.qchem.hf_energy(mol)() + +############################################################################## +# We now compute the gradient of the energy with respect to the nuclear coordinates + +jax.grad(qml.qchem.hf_energy(mol), argnums=0)(geometry, mol.coeff, mol.alpha) + +############################################################################## +# The obtained gradients are equal or very close to zero because the geometry we used here has been +# previously optimized at the Hartree-Fock level. You can use a different geometry and verify that +# the newly computed gradients are not all zero. +# +# We can also compute the values and gradients of several other quantities that are obtained during +# the Hartree-Fock procedure. These include all integrals over basis functions, matrices formed from +# these integrals and the one- and two-body integrals over molecular orbitals [#arrazola2021]_. +# Let's look at a few examples. +# +# We first compute the overlap integral between the two S-type atomic orbitals of the hydrogen +# atoms. Here we are using the `STO-3G `_ +# basis set in which each of the atomic orbitals is represented by one basis function composed of +# three primitive Gaussian functions. These basis functions can be accessed from the molecule +# object as + +S1 = mol.basis_set[0] +S2 = mol.basis_set[1] + +############################################################################## +# We can check the parameters of the basis functions as + +for param in S1.params: + print(param) + +############################################################################## +# This returns the exponents, contraction coefficients and the centres of the three Gaussian +# functions of the STO-3G basis set. These parameters can be also obtained individually by using +# ``S1.alpha``, ``S1.coeff`` and ``S1.r``, respectively. You can verify that both of the ``S1`` an +# ``S2`` atomic orbitals have the same exponents and contraction coefficients but are centred on +# different hydrogen atoms. You can also verify that the orbitals are S-type by printing the angular +# momentum quantum numbers with + +S1.l + +############################################################################## +# This gives us a tuple of three integers, representing the exponents of the :math:`x,` :math:`y` +# and :math:`z` components in the Gaussian functions [#arrazola2021]_. +# +# We can now compute the overlap integral, +# +# .. math:: +# +# S_{\mu \nu} = \int \chi_\mu^* (r) \chi_\nu (r) dr +# +# between the atomic orbitals :math:`\chi,` by passing the orbitals and the initial values of their +# centres to the :func:`~.pennylane.qchem.overlap_integral` function. The centres of the orbitals +# are those of the hydrogen atoms by default and are therefore treated as differentiable parameters +# by PennyLane. + +qml.qchem.overlap_integral(S1, S2)() + +############################################################################## +# You can verify that the overlap integral between two identical atomic orbitals is equal to one. +# We can now compute the gradient of the overlap integral with respect to the orbital centres + +jax.grad(qml.qchem.overlap_integral(S1, S2))(geometry, mol.coeff, mol.alpha) + +############################################################################## +# Can you explain why some of the computed gradients are zero? +# +# Let's now plot the atomic orbitals and their overlap. We can do it by using +# the :py:meth:`~.pennylane.qchem.Molecule.atomic_orbital` function, which evaluates the +# atomic orbital at a given coordinate. For instance, the value of the S orbital on the first +# hydrogen atom can be computed at the origin as + +V1 = mol.atomic_orbital(0) +V1(0.0, 0.0, 0.0) + +############################################################################## +# We can evaluate this orbital at different points along the :math:`x` axis and plot it. + +x = np.linspace(-5, 5, 1000) +plt.plot(x, V1(x, 0.0, 0.0), color='teal') +plt.xlabel('X [Bohr]') +plt.show() + +############################################################################## +# We can also plot the second S orbital and visualize the overlap between them + +V2 = mol.atomic_orbital(1) +plt.plot(x, V1(x, 0.0, 0.0), color='teal') +plt.plot(x, V2(x, 0.0, 0.0), color='teal') +plt.fill_between( + x, np.minimum(V1(x, 0.0, 0.0), V2(x, 0.0, 0.0)), color = 'red', alpha = 0.5, hatch = '||') +plt.xlabel('X [Bohr]') +plt.show() + +############################################################################## +# By looking at the orbitals, can you guess at what distance the value of the overlap becomes +# negligible? Can you verify your guess by computing the overlap at that distance? +# +# Similarly, we can plot the molecular orbitals of the hydrogen molecule obtained from the +# Hartree-Fock calculations. We plot the cross-section of the bonding orbital on the :math:`x-y` +# plane. + +n = 30 # number of grid points along each axis +qml.qchem.hf_energy(mol)() +mol.mo_coefficients = mol.mo_coefficients.T +mo = mol.molecular_orbital(0) +x, y = np.meshgrid(np.linspace(-2, 2, n), + np.linspace(-2, 2, n)) +val = np.vectorize(mo)(x, y, 0) +val = np.array([val[i][j] for i in range(n) for j in range(n)]).reshape(n, n) + +fig, ax = plt.subplots() +co = ax.contour(x, y, val, 10, cmap='summer_r', zorder=0) +ax.clabel(co, inline=2, fontsize=10) +plt.scatter(mol.coordinates[:,0], mol.coordinates[:,1], s = 80, color='black') +ax.set_xlabel('X [Bohr]') +ax.set_ylabel('Y [Bohr]') +plt.show() + +############################################################################## +# VQE simulations +# --------------- +# +# By performing the Hartree-Fock calculations, we obtain a set of one- and two-body integrals +# over molecular orbitals that can be used to construct the molecular Hamiltonian with the +# :func:`~.pennylane.qchem.molecular_hamiltonian` function. + +hamiltonian, qubits = qml.qchem.molecular_hamiltonian(mol) +print(hamiltonian) + +############################################################################## +# The Hamiltonian contains 15 terms and, importantly, the coefficients of the Hamiltonian are all +# differentiable. We can construct a circuit and perform a VQE simulation in which both of the +# circuit parameters and the nuclear coordinates are optimized simultaneously by using the computed +# gradients. We will have two sets of differentiable parameters: the first set is the +# rotation angles of the excitation gates which are applied to the reference Hartree-Fock state +# to construct the exact ground state. The second set contains the nuclear coordinates of the +# hydrogen atoms. + +dev = qml.device("default.qubit", wires=4) +def energy(): + @qml.qnode(dev, interface="jax") + def circuit(*args): + qml.BasisState(np.array([1, 1, 0, 0]), wires=range(4)) + qml.DoubleExcitation(*args[0], wires=[0, 1, 2, 3]) + mol = qml.qchem.Molecule(symbols, geometry, alpha=args[3], coeff=args[2]) + H = qml.qchem.molecular_hamiltonian(mol, args=args[1:])[0] + return qml.expval(H) + return circuit + +############################################################################## +# Note that we only use the :class:`~.pennylane.DoubleExcitation` gate as the +# :class:`~.pennylane.SingleExcitation` ones can be neglected in this particular example +# [#szabo1996]_. We now compute the gradients of the energy with respect to the circuit parameter +# and the nuclear coordinates and update the parameters iteratively. Note that the nuclear +# coordinate gradients are simply the forces on the atomic nuclei. + +# initial value of the circuit parameter +circuit_param = jnp.array([0.0]) + +geometry = jnp.array([[0.0, 0.02, -0.672943567415407], + [0.1, 0.0, 0.672943567415407]]) + +for n in range(36): + mol = qml.qchem.Molecule(symbols, geometry) + args = [circuit_param, geometry, mol.coeff, mol.alpha] + # gradient for circuit parameters + g_param = jax.grad(energy(), argnums = 0)(*args) + circuit_param = circuit_param - 0.25 * g_param[0] + + # gradient for nuclear coordinates + forces = jax.grad(energy(), argnums = 1)(*args) + geometry = geometry - 0.5 * forces + + if n % 5 == 0: + print(f'n: {n}, E: {energy()(*args):.8f}, Force-max: {abs(forces).max():.8f}') + +############################################################################## +# After 35 steps of optimization, the forces on the atomic nuclei and the gradient of the +# circuit parameter are both approaching zero, and the energy of the molecule is that of the +# optimized geometry at the +# `full-CI `_ level: +# :math:`-1.1373060483` Ha. You can print the optimized geometry and verify that the final bond +# length of hydrogen is identical to the one computed with full-CI which is :math:`1.389` +# `Bohr `_. +# +# We are now ready to perform a full optimization where the circuit parameters, the nuclear +# coordinates and the basis set parameters are all differentiable parameters that can be optimized +# simultaneously. + +symbols = ["H", "H"] +# initial values of the nuclear coordinates +geometry = jnp.array([[0.0, 0.0, -0.672943567415407], + [0.0, 0.0, 0.672943567415407]]) + +# initial values of the basis set contraction coefficients +coeff = jnp.array([[0.1543289673, 0.5353281423, 0.4446345422], + [0.1543289673, 0.5353281423, 0.4446345422]]) + +# initial values of the basis set exponents +alpha = jnp.array([[3.42525091, 0.62391373, 0.1688554], + [3.42525091, 0.62391373, 0.1688554]]) + +# initial value of the circuit parameter +circuit_param = jnp.array([0.0]) + +mol = qml.qchem.Molecule(symbols, geometry, coeff=coeff, alpha=alpha) +args = [circuit_param, geometry, coeff, alpha] + +for n in range(36): + args = [circuit_param, geometry, coeff, alpha] + mol = qml.qchem.Molecule(symbols, geometry, alpha=alpha, coeff=coeff) + + # gradient for circuit parameters + g_param = jax.grad(energy(), argnums=[0, 1, 2, 3])(*args)[0] + circuit_param = circuit_param - 0.25 * g_param[0] + + # gradient for nuclear coordinates + value, gradients = jax.value_and_grad(energy(), argnums=[1, 2, 3])(*args) + geometry = geometry - 0.5 * gradients[0] + alpha = alpha - 0.25 * gradients[2] + coeff = coeff - 0.25 * gradients[1] + + if n % 5 == 0: + print(f'n: {n}, E: {value:.8f}, Force-max: {abs(gradients[0]).max():.8f}') + +############################################################################## +# You can also print the gradients of the circuit and basis set parameters and confirm that they are +# approaching zero. The computed energy of :math:`-1.14040160` Ha is +# lower than the full-CI energy, :math:`-1.1373060483` Ha (obtained with the STO-3G basis set for +# the hydrogen molecule) because we have optimized the basis set parameters in our example. This +# means that we can reach a lower energy for hydrogen without increasing the basis set size, which +# would otherwise lead to a larger number of qubits. +# +# Conclusions +# ----------- +# This tutorial introduces an important feature of PennyLane that allows performing +# fully-differentiable Hartree-Fock and subsequently VQE simulations. This feature provides two +# major benefits: i) All gradient computations needed for parameter optimization can be carried out +# with the elegant methods of automatic differentiation which facilitates simultaneous optimizations +# of circuit and Hamiltonian parameters in applications such as VQE molecular geometry +# optimizations. ii) By optimizing the molecular parameters such as the exponent and contraction +# coefficients of Gaussian functions of the basis set, one can reach a lower energy without +# increasing the number of basis functions. Can you think of other interesting molecular parameters +# that can be optimized along with the nuclear coordinates and basis set parameters that we +# optimized in this tutorial? +# +# References +# ---------- +# +# .. [#arrazola2021] +# +# Juan Miguel Arrazola, Soran Jahangiri, Alain Delgado, Jack Ceroni *et al.*, "Differentiable +# quantum computational chemistry with PennyLane". `arXiv:2111.09967 +# `__ +# +# .. [#szabo1996] +# +# Attila Szabo, Neil S. Ostlund, "Modern Quantum Chemistry: Introduction to Advanced Electronic +# Structure Theory". Dover Publications, 1996. +# +# +# About the author +# ---------------- +# diff --git a/demonstrations/tutorial_doubly_stochastic.py b/demonstrations/tutorial_doubly_stochastic.py index 58a18ea688..b75b365834 100644 --- a/demonstrations/tutorial_doubly_stochastic.py +++ b/demonstrations/tutorial_doubly_stochastic.py @@ -1,407 +1,407 @@ -r""" -Doubly stochastic gradient descent -================================== - -.. meta:: - :property="og:description": Minimize a Hamiltonian via an adaptive shot optimization - strategy with doubly stochastic gradient descent. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/single_shot.png - -.. related:: - - tutorial_backprop Quantum gradients with backpropagation - tutorial_quantum_natural_gradient Quantum natural gradient - tutorial_rosalin Frugal shot optimization with Rosalin - -*Author: Josh Izaac — Posted: 16 October 2019. Last updated: 20 January 2021.* - -In this tutorial we investigate and implement the doubly stochastic gradient descent -paper from `Ryan Sweke et al. (2019) `__. In this paper, -it is shown that quantum gradient descent, where a finite number of measurement samples -(or *shots*) are used to estimate the gradient, is a form of stochastic gradient descent. -Furthermore, if the optimization involves a linear combination of expectation values -(such as VQE), sampling from the terms in this linear combination can further reduce required -resources, allowing for "doubly stochastic gradient descent". - -Note that based on very similar observations, `Jonas Kuebler et al. (2019) `_ -recently proposed an optimizer (which they call the *individual Coupled Adaptive -Number of Shots (iCANS)* optimizer) that adapts the shot number of -measurements during training. - -Background ----------- - -In classical machine learning, `stochastic gradient descent -`_ is a common optimization strategy -where the standard gradient descent parameter update rule, - -.. math:: \theta^{(t+1)} = \theta^{(t)} - \eta \nabla \mathcal{L}(\theta^{(t)}), - -is modified such that - -.. math:: \theta^{(t+1)} = \theta^{(t)} - \eta g^{(t)}(\theta^{(t)}) - -where :math:`\eta` is the step-size, and :math:`\{g^{(t)}(\theta)\}` is a sequence of random -variables such that - -.. math:: \mathbb{E}[g^{(t)}(\theta)] = \nabla\mathcal{L}(\theta). - -In general, stochastic gradient descent is preferred over standard gradient -descent for several reasons: - -1. Samples of the gradient estimator :math:`g^{(t)}(\theta)` can typically - be computed much more efficiently than :math:`\mathcal{L}(\theta),` - -2. Stochasticity can help to avoid local minima and saddle points, - -3. Numerical evidence shows that convergence properties are superior to regular gradient descent. - -In variational quantum algorithms, a parametrized quantum circuit :math:`U(\theta)` -is optimized by a classical optimization loop in order to minimize a function of the expectation -values. For example, consider the expectation values - -.. math:: \langle A_i \rangle = \langle 0 | U(\theta)^\dagger A_i U(\theta) | 0\rangle - -for a set of observables :math:`\{A_i\},` and loss function - -.. math:: \mathcal{L}(\theta, \langle A_1 \rangle, \dots, \langle A_M \rangle). - -While the expectation values can be calculated analytically in classical simulations, -on quantum hardware we are limited to *sampling* from the expectation values; as the -number of samples (or shots) increase, we converge on the analytic expectation value, but can -never recover the exact expression. Furthermore, the parameter-shift rule -(`Schuld et al., 2018 `__) allows for analytic -quantum gradients to be computed from a linear combination of the variational circuits' -expectation values. - -Putting these two results together, `Sweke et al. (2019) `__ -show that samples of the expectation value fed into the parameter-shift rule provide -unbiased estimators of the quantum gradient—resulting in a form of stochastic gradient descent -(referred to as QSGD). Moreover, they show that convergence of the stochastic gradient -descent is guaranteed in sufficiently simplified settings, even in the case where the number -of shots is 1! - -.. note:: - - It is worth noting that the smaller the number of shots used, the larger the - variance in the estimated expectation value. As a result, it may take - more optimization steps for convergence than using a larger number of shots, - or an exact value. - - At the same time, a reduced number of shots may significantly reduce the - wall time of each optimization step, leading to a reduction in the overall - optimization time. - -""" - -############################################################################## -# Let's consider a simple example in PennyLane, comparing analytic gradient -# descent (with exact expectation values) to stochastic gradient descent -# using a finite number of shots. -# -# A single-shot stochastic gradient descent -# ----------------------------------------- -# -# Consider the Hamiltonian -# -# .. math:: -# -# H = \begin{bmatrix} -# 8 & 4 & 0 & -6\\ -# 4 & 0 & 4 & 0\\ -# 0 & 4 & 8 & 0\\ -# -6 & 0 & 0 & 0 -# \end{bmatrix}. -# -# We can solve for the ground state energy using -# the variational quantum eigensolver (VQE) algorithm. -# -# Let's use the ``lightning.qubit`` simulator for both the analytic gradient, -# as well as the estimated gradient using number of shots :math:`N\in\{1, 100\}.` - -import pennylane as qml -import numpy as np -from pennylane import numpy as pnp - -np.random.seed(3) - -from pennylane import expval -from pennylane.templates.layers import StronglyEntanglingLayers - -num_layers = 2 -num_wires = 2 -eta = 0.01 -steps = 200 - -dev_analytic = qml.device("lightning.qubit", wires=num_wires, shots=None) -dev_stochastic = qml.device("lightning.qubit", wires=num_wires, shots=1000) - -############################################################################## -# We can use ``qml.Hermitian`` to directly specify that we want to measure -# the expectation value of the matrix :math:`H:` - -H = np.array([[8, 4, 0, -6], [4, 0, 4, 0], [0, 4, 8, 0], [-6, 0, 0, 0]]) - - -def circuit(params): - StronglyEntanglingLayers(weights=params, wires=[0, 1]) - return expval(qml.Hermitian(H, wires=[0, 1])) - - -############################################################################## -# Now, we create three QNodes, each corresponding to a device above, -# and optimize them using gradient descent via the parameter-shift rule. - -qnode_analytic = qml.QNode(circuit, dev_analytic, interface="autograd", diff_method="parameter-shift") -qnode_stochastic = qml.QNode(circuit, dev_stochastic, interface="autograd", diff_method="parameter-shift") - -param_shape = StronglyEntanglingLayers.shape(n_layers=num_layers, n_wires=num_wires) -init_params = pnp.random.uniform(low=0, high=2 * np.pi, size=param_shape, requires_grad=True) - -# Optimizing using exact gradient descent - -cost_GD = [] -params_GD = init_params -opt = qml.GradientDescentOptimizer(eta) - -for _ in range(steps): - cost_GD.append(qnode_analytic(params_GD)) - params_GD = opt.step(qnode_analytic, params_GD) - -# Optimizing using stochastic gradient descent with shots=1 - -cost_SGD1 = [] -params_SGD1 = init_params -opt = qml.GradientDescentOptimizer(eta) - -for _ in range(steps): - cost_SGD1.append(qnode_stochastic(params_SGD1, shots=1)) - params_SGD1 = opt.step(qnode_stochastic, params_SGD1, shots=1) - -# Optimizing using stochastic gradient descent with shots=100 - -cost_SGD100 = [] -params_SGD100 = init_params -opt = qml.GradientDescentOptimizer(eta) - -for _ in range(steps): - cost_SGD100.append(qnode_stochastic(params_SGD100, shots=100)) - params_SGD100 = opt.step(qnode_stochastic, params_SGD100, shots=100) - -############################################################################## -# Note that in the latter two cases we are sampling from an unbiased -# estimator of the cost function, not the analytic cost function. -# -# To track optimization convergence, approaches could include: -# -# * Evaluating the cost function with a larger number of samples at specified -# intervals, -# -# * Keeping track of the *moving average* of the low-shot cost evaluations. -# -# We can now plot the cost against optimization step for the three cases above. - -from matplotlib import pyplot as plt - -plt.style.use("seaborn") -plt.plot(cost_GD[:100], label="Vanilla gradient descent") -plt.plot(cost_SGD100[:100], "--", label="QSGD (100 shots)") -plt.plot(cost_SGD1[:100], ".", label="QSGD (1 shot)") - -# analytic ground state -min_energy = min(np.linalg.eigvalsh(H)) -plt.hlines(min_energy, 0, 100, linestyles=":", label="Ground-state energy") - -plt.ylabel("Cost function value") -plt.xlabel("Optimization steps") -plt.legend() -plt.show() - -############################################################################## -# Using the trained parameters from each optimization strategy, we can -# evaluate the analytic quantum device: - -print("Vanilla gradient descent min energy = ", qnode_analytic(params_GD)) -print( - "Stochastic gradient descent (shots=100) min energy = ", - qnode_analytic(params_SGD100), -) -print( - "Stochastic gradient descent (shots=1) min energy = ", qnode_analytic(params_SGD1) -) - - -############################################################################## -# Amazingly, we see that even the ``shots=1`` optimization converged -# to a reasonably close approximation of the ground-state energy! - -############################################################################## -# Doubly stochastic gradient descent for VQE -# ------------------------------------------ -# -# As noted in `Sweke et al. (2019) `__, -# variational quantum algorithms often include terms consisting of linear combinations -# of expectation values. This is true of the parameter-shift rule (where the -# gradient of each parameter is determined by shifting the parameter by macroscopic -# amounts and taking the difference), as well as VQE, where the Hamiltonian -# is usually decomposed into a sum of Pauli expectation values. -# -# Consider the Hamiltonian from the previous section. As this Hamiltonian is a -# Hermitian observable, we can always express it as a sum of Pauli matrices using -# the relation -# -# .. math:: -# -# H = \sum_{i,j=0,1,2,3} a_{i,j} (\sigma_i\otimes \sigma_j), -# -# where -# -# .. math:: -# -# a_{i,j} = \frac{1}{4}\text{tr}[(\sigma_i\otimes \sigma_j )H], ~~ \sigma = \{I, X, Y, Z\}. -# -# Applying this, we can see that -# -# .. math:: -# -# H = 4 + 2I\otimes X + 4I \otimes Z - X\otimes X + 5 Y\otimes Y + 2Z\otimes X. -# -# To perform "doubly stochastic" gradient descent, we simply apply the stochastic -# gradient descent approach from above, but in addition also uniformly sample -# a subset of the terms for the Hamiltonian expectation at each optimization step. -# This inserts another element of stochasticity into the system—all the while -# convergence continues to be guaranteed! -# -# Let's create a QNode that randomly samples a single term from the above -# Hamiltonian as the observable to be measured. - -I = np.identity(2) -X = np.array([[0, 1], [1, 0]]) -Y = np.array([[0, -1j], [1j, 0]]) -Z = np.array([[1, 0], [0, -1]]) - -terms = np.array( - [ - 2 * np.kron(I, X), - 4 * np.kron(I, Z), - -np.kron(X, X), - 5 * np.kron(Y, Y), - 2 * np.kron(Z, X), - ] -) - - -@qml.qnode(dev_stochastic, interface="autograd") -def circuit(params, n=None): - StronglyEntanglingLayers(weights=params, wires=[0, 1]) - idx = np.random.choice(np.arange(5), size=n, replace=False) - A = np.sum(terms[idx], axis=0) - return expval(qml.Hermitian(A, wires=[0, 1])) - - -def loss(params, shots=None): - return 4 + (5 / 1) * circuit(params, shots=shots, n=1) - - -############################################################################## -# Optimizing the circuit using gradient descent via the parameter-shift rule: - -cost = [] -params = init_params -opt = qml.GradientDescentOptimizer(0.005) - -for _ in range(250): - cost.append(loss(params, shots=100)) - params = opt.step(loss, params, shots=100) - - -############################################################################## -# During doubly stochastic gradient descent, we are sampling from terms of the -# analytic cost function, so it is not entirely instructive to plot the cost -# versus optimization step—partial sums of the terms in the Hamiltonian -# may have minimum energy below the ground state energy of the total Hamiltonian. -# Nevertheless, we can keep track of the cost value moving average during doubly -# stochastic gradient descent as an indicator of convergence. - - -def moving_average(data, n=3): - ret = np.cumsum(data, dtype=np.float64) - ret[n:] = ret[n:] - ret[:-n] - return ret[n - 1:] / n - - -average = np.vstack([np.arange(25, 200), moving_average(cost, n=50)[:-26]]) - -plt.plot(cost_GD, label="Vanilla gradient descent") -plt.plot(cost, ".", label="Doubly QSGD") -plt.plot(average[0], average[1], "--", label="Doubly QSGD (moving average)") -plt.hlines(min_energy, 0, 200, linestyles=":", label="Ground state energy") - -plt.ylabel("Cost function value") -plt.xlabel("Optimization steps") -plt.xlim(-2, 200) -plt.legend() -plt.show() - -############################################################################## -# Finally, verifying that the doubly stochastic gradient descent optimization -# correctly provides the ground state energy when evaluated for a larger -# number of shots: - -print("Doubly stochastic gradient descent min energy = ", qnode_analytic(params)) - -############################################################################## -# While stochastic gradient descent requires more optimization steps to achieve -# convergence, it is worth noting that it requires significantly fewer quantum -# device evaluations, and thus may as a result take less time overall. - -############################################################################## -# Adaptive stochasticity -# ---------------------- -# -# To improve on the convergence, we may even consider a crude "adaptive" modification -# of the doubly stochastic gradient descent optimization performed above. In this -# approach, we successively increase the number of terms we are sampling from as -# the optimization proceeds, as well as increasing the number of shots. - -cost = [] -params = init_params -opt = qml.GradientDescentOptimizer(0.005) - -for i in range(250): - n = min(i // 25 + 1, 5) - - def loss(params, shots=None): - return 4 + (5 / n) * circuit(params, shots=shots, n=n) - - cost.append(loss(params, shots=int(1 + (n - 1) ** 2))) - params = opt.step(loss, params, shots=int(1 + (n - 1) ** 2)) - -average = np.vstack([np.arange(25, 200), moving_average(cost, n=50)[:-26]]) - -plt.plot(cost_GD, label="Vanilla gradient descent") -plt.plot(cost, ".", label="Adaptive QSGD") -plt.plot(average[0], average[1], "--", label="Adaptive QSGD (moving average)") -plt.hlines(min_energy, 0, 250, linestyles=":", label="Ground state energy") - -plt.ylabel("Cost function value") -plt.xlabel("Optimization steps") -plt.xlim(-2, 200) -plt.legend() -plt.show() - -print("Adaptive QSGD min energy = ", qnode_analytic(params)) - -############################################################################## -# References -# ---------- -# -# 1. Ryan Sweke, Frederik Wilde, Johannes Jakob Meyer, Maria Schuld, Paul K. Fährmann, -# Barthélémy Meynard-Piganeau, Jens Eisert. "Stochastic gradient descent for -# hybrid quantum-classical optimization." `arXiv:1910.01155 -# `__, 2019. -# -# -# About the author -# ---------------- +r""" +Doubly stochastic gradient descent +================================== + +.. meta:: + :property="og:description": Minimize a Hamiltonian via an adaptive shot optimization + strategy with doubly stochastic gradient descent. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/single_shot.png + +.. related:: + + tutorial_backprop Quantum gradients with backpropagation + tutorial_quantum_natural_gradient Quantum natural gradient + tutorial_rosalin Frugal shot optimization with Rosalin + +*Author: Josh Izaac — Posted: 16 October 2019. Last updated: 20 January 2021.* + +In this tutorial we investigate and implement the doubly stochastic gradient descent +paper from `Ryan Sweke et al. (2019) `__. In this paper, +it is shown that quantum gradient descent, where a finite number of measurement samples +(or *shots*) are used to estimate the gradient, is a form of stochastic gradient descent. +Furthermore, if the optimization involves a linear combination of expectation values +(such as VQE), sampling from the terms in this linear combination can further reduce required +resources, allowing for "doubly stochastic gradient descent". + +Note that based on very similar observations, `Jonas Kuebler et al. (2019) `_ +recently proposed an optimizer (which they call the *individual Coupled Adaptive +Number of Shots (iCANS)* optimizer) that adapts the shot number of +measurements during training. + +Background +---------- + +In classical machine learning, `stochastic gradient descent +`_ is a common optimization strategy +where the standard gradient descent parameter update rule, + +.. math:: \theta^{(t+1)} = \theta^{(t)} - \eta \nabla \mathcal{L}(\theta^{(t)}), + +is modified such that + +.. math:: \theta^{(t+1)} = \theta^{(t)} - \eta g^{(t)}(\theta^{(t)}) + +where :math:`\eta` is the step-size, and :math:`\{g^{(t)}(\theta)\}` is a sequence of random +variables such that + +.. math:: \mathbb{E}[g^{(t)}(\theta)] = \nabla\mathcal{L}(\theta). + +In general, stochastic gradient descent is preferred over standard gradient +descent for several reasons: + +1. Samples of the gradient estimator :math:`g^{(t)}(\theta)` can typically + be computed much more efficiently than :math:`\mathcal{L}(\theta),` + +2. Stochasticity can help to avoid local minima and saddle points, + +3. Numerical evidence shows that convergence properties are superior to regular gradient descent. + +In variational quantum algorithms, a parametrized quantum circuit :math:`U(\theta)` +is optimized by a classical optimization loop in order to minimize a function of the expectation +values. For example, consider the expectation values + +.. math:: \langle A_i \rangle = \langle 0 | U(\theta)^\dagger A_i U(\theta) | 0\rangle + +for a set of observables :math:`\{A_i\},` and loss function + +.. math:: \mathcal{L}(\theta, \langle A_1 \rangle, \dots, \langle A_M \rangle). + +While the expectation values can be calculated analytically in classical simulations, +on quantum hardware we are limited to *sampling* from the expectation values; as the +number of samples (or shots) increase, we converge on the analytic expectation value, but can +never recover the exact expression. Furthermore, the parameter-shift rule +(`Schuld et al., 2018 `__) allows for analytic +quantum gradients to be computed from a linear combination of the variational circuits' +expectation values. + +Putting these two results together, `Sweke et al. (2019) `__ +show that samples of the expectation value fed into the parameter-shift rule provide +unbiased estimators of the quantum gradient—resulting in a form of stochastic gradient descent +(referred to as QSGD). Moreover, they show that convergence of the stochastic gradient +descent is guaranteed in sufficiently simplified settings, even in the case where the number +of shots is 1! + +.. note:: + + It is worth noting that the smaller the number of shots used, the larger the + variance in the estimated expectation value. As a result, it may take + more optimization steps for convergence than using a larger number of shots, + or an exact value. + + At the same time, a reduced number of shots may significantly reduce the + wall time of each optimization step, leading to a reduction in the overall + optimization time. + +""" + +############################################################################## +# Let's consider a simple example in PennyLane, comparing analytic gradient +# descent (with exact expectation values) to stochastic gradient descent +# using a finite number of shots. +# +# A single-shot stochastic gradient descent +# ----------------------------------------- +# +# Consider the Hamiltonian +# +# .. math:: +# +# H = \begin{bmatrix} +# 8 & 4 & 0 & -6\\ +# 4 & 0 & 4 & 0\\ +# 0 & 4 & 8 & 0\\ +# -6 & 0 & 0 & 0 +# \end{bmatrix}. +# +# We can solve for the ground state energy using +# the variational quantum eigensolver (VQE) algorithm. +# +# Let's use the ``lightning.qubit`` simulator for both the analytic gradient, +# as well as the estimated gradient using number of shots :math:`N\in\{1, 100\}.` + +import pennylane as qml +import numpy as np +from pennylane import numpy as pnp + +np.random.seed(3) + +from pennylane import expval +from pennylane.templates.layers import StronglyEntanglingLayers + +num_layers = 2 +num_wires = 2 +eta = 0.01 +steps = 200 + +dev_analytic = qml.device("lightning.qubit", wires=num_wires, shots=None) +dev_stochastic = qml.device("lightning.qubit", wires=num_wires, shots=1000) + +############################################################################## +# We can use ``qml.Hermitian`` to directly specify that we want to measure +# the expectation value of the matrix :math:`H:` + +H = np.array([[8, 4, 0, -6], [4, 0, 4, 0], [0, 4, 8, 0], [-6, 0, 0, 0]]) + + +def circuit(params): + StronglyEntanglingLayers(weights=params, wires=[0, 1]) + return expval(qml.Hermitian(H, wires=[0, 1])) + + +############################################################################## +# Now, we create three QNodes, each corresponding to a device above, +# and optimize them using gradient descent via the parameter-shift rule. + +qnode_analytic = qml.QNode(circuit, dev_analytic, interface="autograd", diff_method="parameter-shift") +qnode_stochastic = qml.QNode(circuit, dev_stochastic, interface="autograd", diff_method="parameter-shift") + +param_shape = StronglyEntanglingLayers.shape(n_layers=num_layers, n_wires=num_wires) +init_params = pnp.random.uniform(low=0, high=2 * np.pi, size=param_shape, requires_grad=True) + +# Optimizing using exact gradient descent + +cost_GD = [] +params_GD = init_params +opt = qml.GradientDescentOptimizer(eta) + +for _ in range(steps): + cost_GD.append(qnode_analytic(params_GD)) + params_GD = opt.step(qnode_analytic, params_GD) + +# Optimizing using stochastic gradient descent with shots=1 + +cost_SGD1 = [] +params_SGD1 = init_params +opt = qml.GradientDescentOptimizer(eta) + +for _ in range(steps): + cost_SGD1.append(qnode_stochastic(params_SGD1, shots=1)) + params_SGD1 = opt.step(qnode_stochastic, params_SGD1, shots=1) + +# Optimizing using stochastic gradient descent with shots=100 + +cost_SGD100 = [] +params_SGD100 = init_params +opt = qml.GradientDescentOptimizer(eta) + +for _ in range(steps): + cost_SGD100.append(qnode_stochastic(params_SGD100, shots=100)) + params_SGD100 = opt.step(qnode_stochastic, params_SGD100, shots=100) + +############################################################################## +# Note that in the latter two cases we are sampling from an unbiased +# estimator of the cost function, not the analytic cost function. +# +# To track optimization convergence, approaches could include: +# +# * Evaluating the cost function with a larger number of samples at specified +# intervals, +# +# * Keeping track of the *moving average* of the low-shot cost evaluations. +# +# We can now plot the cost against optimization step for the three cases above. + +from matplotlib import pyplot as plt + +plt.style.use("seaborn") +plt.plot(cost_GD[:100], label="Vanilla gradient descent") +plt.plot(cost_SGD100[:100], "--", label="QSGD (100 shots)") +plt.plot(cost_SGD1[:100], ".", label="QSGD (1 shot)") + +# analytic ground state +min_energy = min(np.linalg.eigvalsh(H)) +plt.hlines(min_energy, 0, 100, linestyles=":", label="Ground-state energy") + +plt.ylabel("Cost function value") +plt.xlabel("Optimization steps") +plt.legend() +plt.show() + +############################################################################## +# Using the trained parameters from each optimization strategy, we can +# evaluate the analytic quantum device: + +print("Vanilla gradient descent min energy = ", qnode_analytic(params_GD)) +print( + "Stochastic gradient descent (shots=100) min energy = ", + qnode_analytic(params_SGD100), +) +print( + "Stochastic gradient descent (shots=1) min energy = ", qnode_analytic(params_SGD1) +) + + +############################################################################## +# Amazingly, we see that even the ``shots=1`` optimization converged +# to a reasonably close approximation of the ground-state energy! + +############################################################################## +# Doubly stochastic gradient descent for VQE +# ------------------------------------------ +# +# As noted in `Sweke et al. (2019) `__, +# variational quantum algorithms often include terms consisting of linear combinations +# of expectation values. This is true of the parameter-shift rule (where the +# gradient of each parameter is determined by shifting the parameter by macroscopic +# amounts and taking the difference), as well as VQE, where the Hamiltonian +# is usually decomposed into a sum of Pauli expectation values. +# +# Consider the Hamiltonian from the previous section. As this Hamiltonian is a +# Hermitian observable, we can always express it as a sum of Pauli matrices using +# the relation +# +# .. math:: +# +# H = \sum_{i,j=0,1,2,3} a_{i,j} (\sigma_i\otimes \sigma_j), +# +# where +# +# .. math:: +# +# a_{i,j} = \frac{1}{4}\text{tr}[(\sigma_i\otimes \sigma_j )H], ~~ \sigma = \{I, X, Y, Z\}. +# +# Applying this, we can see that +# +# .. math:: +# +# H = 4 + 2I\otimes X + 4I \otimes Z - X\otimes X + 5 Y\otimes Y + 2Z\otimes X. +# +# To perform "doubly stochastic" gradient descent, we simply apply the stochastic +# gradient descent approach from above, but in addition also uniformly sample +# a subset of the terms for the Hamiltonian expectation at each optimization step. +# This inserts another element of stochasticity into the system—all the while +# convergence continues to be guaranteed! +# +# Let's create a QNode that randomly samples a single term from the above +# Hamiltonian as the observable to be measured. + +I = np.identity(2) +X = np.array([[0, 1], [1, 0]]) +Y = np.array([[0, -1j], [1j, 0]]) +Z = np.array([[1, 0], [0, -1]]) + +terms = np.array( + [ + 2 * np.kron(I, X), + 4 * np.kron(I, Z), + -np.kron(X, X), + 5 * np.kron(Y, Y), + 2 * np.kron(Z, X), + ] +) + + +@qml.qnode(dev_stochastic, interface="autograd") +def circuit(params, n=None): + StronglyEntanglingLayers(weights=params, wires=[0, 1]) + idx = np.random.choice(np.arange(5), size=n, replace=False) + A = np.sum(terms[idx], axis=0) + return expval(qml.Hermitian(A, wires=[0, 1])) + + +def loss(params, shots=None): + return 4 + (5 / 1) * circuit(params, shots=shots, n=1) + + +############################################################################## +# Optimizing the circuit using gradient descent via the parameter-shift rule: + +cost = [] +params = init_params +opt = qml.GradientDescentOptimizer(0.005) + +for _ in range(250): + cost.append(loss(params, shots=100)) + params = opt.step(loss, params, shots=100) + + +############################################################################## +# During doubly stochastic gradient descent, we are sampling from terms of the +# analytic cost function, so it is not entirely instructive to plot the cost +# versus optimization step—partial sums of the terms in the Hamiltonian +# may have minimum energy below the ground state energy of the total Hamiltonian. +# Nevertheless, we can keep track of the cost value moving average during doubly +# stochastic gradient descent as an indicator of convergence. + + +def moving_average(data, n=3): + ret = np.cumsum(data, dtype=np.float64) + ret[n:] = ret[n:] - ret[:-n] + return ret[n - 1:] / n + + +average = np.vstack([np.arange(25, 200), moving_average(cost, n=50)[:-26]]) + +plt.plot(cost_GD, label="Vanilla gradient descent") +plt.plot(cost, ".", label="Doubly QSGD") +plt.plot(average[0], average[1], "--", label="Doubly QSGD (moving average)") +plt.hlines(min_energy, 0, 200, linestyles=":", label="Ground state energy") + +plt.ylabel("Cost function value") +plt.xlabel("Optimization steps") +plt.xlim(-2, 200) +plt.legend() +plt.show() + +############################################################################## +# Finally, verifying that the doubly stochastic gradient descent optimization +# correctly provides the ground state energy when evaluated for a larger +# number of shots: + +print("Doubly stochastic gradient descent min energy = ", qnode_analytic(params)) + +############################################################################## +# While stochastic gradient descent requires more optimization steps to achieve +# convergence, it is worth noting that it requires significantly fewer quantum +# device evaluations, and thus may as a result take less time overall. + +############################################################################## +# Adaptive stochasticity +# ---------------------- +# +# To improve on the convergence, we may even consider a crude "adaptive" modification +# of the doubly stochastic gradient descent optimization performed above. In this +# approach, we successively increase the number of terms we are sampling from as +# the optimization proceeds, as well as increasing the number of shots. + +cost = [] +params = init_params +opt = qml.GradientDescentOptimizer(0.005) + +for i in range(250): + n = min(i // 25 + 1, 5) + + def loss(params, shots=None): + return 4 + (5 / n) * circuit(params, shots=shots, n=n) + + cost.append(loss(params, shots=int(1 + (n - 1) ** 2))) + params = opt.step(loss, params, shots=int(1 + (n - 1) ** 2)) + +average = np.vstack([np.arange(25, 200), moving_average(cost, n=50)[:-26]]) + +plt.plot(cost_GD, label="Vanilla gradient descent") +plt.plot(cost, ".", label="Adaptive QSGD") +plt.plot(average[0], average[1], "--", label="Adaptive QSGD (moving average)") +plt.hlines(min_energy, 0, 250, linestyles=":", label="Ground state energy") + +plt.ylabel("Cost function value") +plt.xlabel("Optimization steps") +plt.xlim(-2, 200) +plt.legend() +plt.show() + +print("Adaptive QSGD min energy = ", qnode_analytic(params)) + +############################################################################## +# References +# ---------- +# +# 1. Ryan Sweke, Frederik Wilde, Johannes Jakob Meyer, Maria Schuld, Paul K. Fährmann, +# Barthélémy Meynard-Piganeau, Jens Eisert. "Stochastic gradient descent for +# hybrid quantum-classical optimization." `arXiv:1910.01155 +# `__, 2019. +# +# +# About the author +# ---------------- # \ No newline at end of file diff --git a/demonstrations/tutorial_fermionic_operators.py b/demonstrations/tutorial_fermionic_operators.py index 673c5a705a..d06f98aafb 100644 --- a/demonstrations/tutorial_fermionic_operators.py +++ b/demonstrations/tutorial_fermionic_operators.py @@ -1,231 +1,231 @@ -r""" - -Fermionic operators -=================== - -.. meta:: - :property="og:description": Learn how to work with fermionic operators - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_fermionic_operators.png - -.. related:: - tutorial_quantum_chemistry Building molecular Hamiltonians - tutorial_vqe A brief overview of VQE - -*Author: Soran Jahangiri — Posted: 27 June 2023. Last updated: 27 June 2023.* - -Fermionic creation and annihilation operators are commonly used to construct -`Hamiltonians `_ and other observables of molecules and spin -systems [#surjan]_. In this demo, you will learn how to use PennyLane to create fermionic operators -and map them to a qubit representation for use in quantum algorithms. - -Constructing fermionic operators --------------------------------- -The fermionic `creation and annihilation `_ -operators can be constructed in PennyLane similarly to Pauli operators by using -:class:`~.pennylane.FermiC` and :class:`~.pennylane.FermiA` for creation and annihilation operators, -respectively. -""" - -from pennylane import FermiC, FermiA - -a0_dag = FermiC(0) -a1 = FermiA(1) - -############################################################################## -# We used the compact notations ``a0_dag`` to denote a creation operator applied to -# the :math:`0\text{th}` orbital and ``a1`` to denote an annihilation operator applied to the -# :math:`1\text{st}` orbital. Once created, these operators can be multiplied or added to each other -# to create new operators. A product of fermionic operators will be called a *Fermi word* and a -# linear combination of Fermi words will be called a *Fermi sentence*. - -fermi_word = a0_dag * a1 -fermi_sentence = 1.3 * a0_dag * a1 + 2.4 * a1 -print(fermi_sentence) - -############################################################################## -# In this simple example, we first created the operator :math:`a^{\dagger}_0 a_1` and then created -# the linear combination :math:`1.3 a^{\dagger}_0 a_1 + 2.4 a_1.` We can also perform -# arithmetic operations between Fermi words and Fermi sentences. - -fermi_sentence = fermi_sentence * fermi_word + 2.3 * fermi_word -print(fermi_sentence) - -############################################################################## -# Beyond multiplication, summation, and subtraction, we can exponentiate fermionic operators in -# PennyLane to an integer power. For instance, we can create a more complicated operator -# -# .. math:: -# -# 1.2 \times a_0^{\dagger} + 0.5 \times a_1 - 2.3 \times \left ( a_0^{\dagger} a_1 \right )^2, -# -# in the same way that you would write down the operator on a piece of paper: - -fermi_sentence = 1.2 * a0_dag + 0.5 * a1 - 2.3 * (a0_dag * a1) ** 2 -print(fermi_sentence) - -############################################################################## -# This Fermi sentence can be mapped to the qubit basis using the -# `Jordan-Wigner `_ -# transformation to get a linear combination of Pauli operators. - -from pennylane import jordan_wigner - -pauli_sentence = jordan_wigner(fermi_sentence) -pauli_sentence - -############################################################################## -# Fermionic Hamiltonians -# ---------------------- -# Now that we have nice tools to create and manipulate fermionic operators, we can build some -# interesting fermionic Hamiltonians. -# -# A toy model -# ^^^^^^^^^^^ -# Our first example is a toy Hamiltonian inspired by the -# `Hückel method `_, which is a method for -# describing molecules with alternating single and double bonds. Our toy model is a simplified -# version of the Hückel Hamiltonian and assumes only two orbitals and a single electron. -# -# .. math:: -# -# H = \alpha \left (a^{\dagger}_0 a_0 + a^{\dagger}_1 a_1 \right ) + -# \beta \left (a^{\dagger}_0 a_1 + a^{\dagger}_1 a_0 \right ). -# -# This Hamiltonian can be constructed with pre-defined values :math:`\alpha = 0.01` and -# :math:`\beta = -0.02.` - -h1 = 0.01 * (FermiC(0) * FermiA(0) + FermiC(1) * FermiA(1)) -h2 = -0.02 * (FermiC(0) * FermiA(1) + FermiC(1) * FermiA(0)) -h = h1 + h2 -print(h) - -############################################################################## -# The fermionic Hamiltonian can be converted to the qubit Hamiltonian with: - -h = jordan_wigner(h) - -############################################################################## -# The matrix representation of the qubit Hamiltonian in the computational basis can be diagonalized -# to get its eigenpairs. - -import numpy as np - -val, vec = np.linalg.eigh(h.sparse_matrix().toarray()) -print(f"eigenvalues:\n{val}") -print() -print(f"eigenvectors:\n{np.real(vec.T)}") - -############################################################################## -# -# Hydrogen molecule -# ^^^^^^^^^^^^^^^^^ -# The `second quantized `_ molecular electronic -# Hamiltonian is usually constructed as -# -# .. math:: -# H = \sum_{\alpha \in \{\uparrow, \downarrow \} } \sum_{pq} c_{pq} a_{p,\alpha}^{\dagger} -# a_{q, \alpha} + \frac{1}{2} \sum_{\alpha, \beta \in \{\uparrow, \downarrow \} } \sum_{pqrs} -# c_{pqrs} a_{p, \alpha}^{\dagger} a_{q, \beta}^{\dagger} a_{r, \beta} a_{s, \alpha}, -# -# where :math:`\alpha` and :math:`\beta` denote the electron spin and :math:`p, q, r, s` are the -# orbital indices. The coefficients :math:`c` are integrals over -# molecular orbitals that are obtained from -# `Hartree-Fock `_ -# calculations. These integrals can be computed with PennyLane using the -# :func:`~.pennylane.qchem.electron_integrals` function. We can build the molecular Hamiltonian for -# the hydrogen molecule as an example. We first define the atom types and the atomic coordinates. - -import pennylane as qml -from jax import numpy as jnp - -symbols = ["H", "H"] -geometry = jnp.array([[-0.67294, 0.0, 0.0], [0.67294, 0.0, 0.0]]) - -############################################################################## -# Then we compute the one- and two-electron integrals, which are the coefficients :math:`c` in the -# second quantized molecular Hamiltonian defined above. We also obtain the core constant, which is -# later used to calculate the contribution of the nuclear energy to the Hamiltonian. - -mol = qml.qchem.Molecule(symbols, geometry) -core, one, two = qml.qchem.electron_integrals(mol)() - -############################################################################## -# These integrals are computed over molecular orbitals. Each molecular orbital contains a pair of -# electrons with different spins. We have assumed that the spatial distribution of these electron -# pairs is the same to simplify the calculation of the integrals. However, to properly account for -# all electrons, we need to duplicate the integrals for electrons with the same spin. For example, -# the :math:`pq` integral, which is the integral over the orbital :math:`p` and the orbital -# :math:`q,` can be used -# for both spin-up and spin-down electrons. Then, if we have a :math:`2 \times 2` matrix of such -# integrals, it will become a :math:`4 \times 4` matrix. The code block below simply extends the -# integrals by duplicating terms to account for both spin-up and spin-down electrons. - -for i in range(4): - if i < 2: - one = one.repeat(2, axis=i) - two = two.repeat(2, axis=i) - -############################################################################## -# We can now construct the fermionic Hamiltonian for the hydrogen molecule. The one-body terms, -# which are the first part in the Hamiltonian above, can be added first. We will use -# `itertools `_ to efficiently -# create all the combinations we need. Some of these combinations are not allowed because of spin -# restrictions and we need to exclude them. You can find more details about -# constructing a molecular Hamiltonian in reference [#surjan]_. - -import itertools - -n = one.shape[0] - -h = 0.0 - -for p, q in itertools.product(range(n), repeat=2): - if p % 2 == q % 2: # to account for spin-forbidden terms - h += one[p, q] * FermiC(p) * FermiA(q) - -############################################################################## -# The two-body terms can be added with: - -for p, q, r, s in itertools.product(range(n), repeat=4): - if p % 2 == s % 2 and q % 2 == r % 2: # to account for spin-forbidden terms - h += two[p, q, r, s] / 2 * FermiC(p) * FermiC(q) * FermiA(r) * FermiA(s) - -############################################################################## -# We then simplify the Hamiltonian to remove terms with negligible coefficients and then map it to -# the qubit basis. - -h.simplify() -h = jordan_wigner(h) - -############################################################################## -# We also need to include the contribution of the nuclear energy. - -h += np.sum(core * qml.Identity(0)) - -############################################################################## -# This gives us the qubit Hamiltonian which can be used as an input for quantum algorithms. We can -# also compute the ground-state energy by diagonalizing the matrix representation of the -# Hamiltonian in the computational basis. - -np.linalg.eigh(h.sparse_matrix().toarray())[0].min() - -############################################################################## -# Summary -# ------- -# This demo explains how to create and manipulate fermionic operators in PennyLane, which is as -# easy as writing the operators on paper. PennyLane supports several arithmetic operations between -# fermionic operators and provides tools for mapping them to the qubit basis. This makes it easy and -# intuitive to construct complicated fermionic Hamiltonians such as -# `molecular Hamiltonians `_. -# -# References -# ---------- -# -# .. [#surjan] -# -# Peter R. Surjan, "Second Quantized Approach to Quantum Chemistry". Springer-Verlag, 1989. -# -# -# About the author -# ---------------- -# +r""" + +Fermionic operators +=================== + +.. meta:: + :property="og:description": Learn how to work with fermionic operators + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_fermionic_operators.png + +.. related:: + tutorial_quantum_chemistry Building molecular Hamiltonians + tutorial_vqe A brief overview of VQE + +*Author: Soran Jahangiri — Posted: 27 June 2023. Last updated: 27 June 2023.* + +Fermionic creation and annihilation operators are commonly used to construct +`Hamiltonians `_ and other observables of molecules and spin +systems [#surjan]_. In this demo, you will learn how to use PennyLane to create fermionic operators +and map them to a qubit representation for use in quantum algorithms. + +Constructing fermionic operators +-------------------------------- +The fermionic `creation and annihilation `_ +operators can be constructed in PennyLane similarly to Pauli operators by using +:class:`~.pennylane.FermiC` and :class:`~.pennylane.FermiA` for creation and annihilation operators, +respectively. +""" + +from pennylane import FermiC, FermiA + +a0_dag = FermiC(0) +a1 = FermiA(1) + +############################################################################## +# We used the compact notations ``a0_dag`` to denote a creation operator applied to +# the :math:`0\text{th}` orbital and ``a1`` to denote an annihilation operator applied to the +# :math:`1\text{st}` orbital. Once created, these operators can be multiplied or added to each other +# to create new operators. A product of fermionic operators will be called a *Fermi word* and a +# linear combination of Fermi words will be called a *Fermi sentence*. + +fermi_word = a0_dag * a1 +fermi_sentence = 1.3 * a0_dag * a1 + 2.4 * a1 +print(fermi_sentence) + +############################################################################## +# In this simple example, we first created the operator :math:`a^{\dagger}_0 a_1` and then created +# the linear combination :math:`1.3 a^{\dagger}_0 a_1 + 2.4 a_1.` We can also perform +# arithmetic operations between Fermi words and Fermi sentences. + +fermi_sentence = fermi_sentence * fermi_word + 2.3 * fermi_word +print(fermi_sentence) + +############################################################################## +# Beyond multiplication, summation, and subtraction, we can exponentiate fermionic operators in +# PennyLane to an integer power. For instance, we can create a more complicated operator +# +# .. math:: +# +# 1.2 \times a_0^{\dagger} + 0.5 \times a_1 - 2.3 \times \left ( a_0^{\dagger} a_1 \right )^2, +# +# in the same way that you would write down the operator on a piece of paper: + +fermi_sentence = 1.2 * a0_dag + 0.5 * a1 - 2.3 * (a0_dag * a1) ** 2 +print(fermi_sentence) + +############################################################################## +# This Fermi sentence can be mapped to the qubit basis using the +# `Jordan-Wigner `_ +# transformation to get a linear combination of Pauli operators. + +from pennylane import jordan_wigner + +pauli_sentence = jordan_wigner(fermi_sentence) +pauli_sentence + +############################################################################## +# Fermionic Hamiltonians +# ---------------------- +# Now that we have nice tools to create and manipulate fermionic operators, we can build some +# interesting fermionic Hamiltonians. +# +# A toy model +# ^^^^^^^^^^^ +# Our first example is a toy Hamiltonian inspired by the +# `Hückel method `_, which is a method for +# describing molecules with alternating single and double bonds. Our toy model is a simplified +# version of the Hückel Hamiltonian and assumes only two orbitals and a single electron. +# +# .. math:: +# +# H = \alpha \left (a^{\dagger}_0 a_0 + a^{\dagger}_1 a_1 \right ) + +# \beta \left (a^{\dagger}_0 a_1 + a^{\dagger}_1 a_0 \right ). +# +# This Hamiltonian can be constructed with pre-defined values :math:`\alpha = 0.01` and +# :math:`\beta = -0.02.` + +h1 = 0.01 * (FermiC(0) * FermiA(0) + FermiC(1) * FermiA(1)) +h2 = -0.02 * (FermiC(0) * FermiA(1) + FermiC(1) * FermiA(0)) +h = h1 + h2 +print(h) + +############################################################################## +# The fermionic Hamiltonian can be converted to the qubit Hamiltonian with: + +h = jordan_wigner(h) + +############################################################################## +# The matrix representation of the qubit Hamiltonian in the computational basis can be diagonalized +# to get its eigenpairs. + +import numpy as np + +val, vec = np.linalg.eigh(h.sparse_matrix().toarray()) +print(f"eigenvalues:\n{val}") +print() +print(f"eigenvectors:\n{np.real(vec.T)}") + +############################################################################## +# +# Hydrogen molecule +# ^^^^^^^^^^^^^^^^^ +# The `second quantized `_ molecular electronic +# Hamiltonian is usually constructed as +# +# .. math:: +# H = \sum_{\alpha \in \{\uparrow, \downarrow \} } \sum_{pq} c_{pq} a_{p,\alpha}^{\dagger} +# a_{q, \alpha} + \frac{1}{2} \sum_{\alpha, \beta \in \{\uparrow, \downarrow \} } \sum_{pqrs} +# c_{pqrs} a_{p, \alpha}^{\dagger} a_{q, \beta}^{\dagger} a_{r, \beta} a_{s, \alpha}, +# +# where :math:`\alpha` and :math:`\beta` denote the electron spin and :math:`p, q, r, s` are the +# orbital indices. The coefficients :math:`c` are integrals over +# molecular orbitals that are obtained from +# `Hartree-Fock `_ +# calculations. These integrals can be computed with PennyLane using the +# :func:`~.pennylane.qchem.electron_integrals` function. We can build the molecular Hamiltonian for +# the hydrogen molecule as an example. We first define the atom types and the atomic coordinates. + +import pennylane as qml +from jax import numpy as jnp + +symbols = ["H", "H"] +geometry = jnp.array([[-0.67294, 0.0, 0.0], [0.67294, 0.0, 0.0]]) + +############################################################################## +# Then we compute the one- and two-electron integrals, which are the coefficients :math:`c` in the +# second quantized molecular Hamiltonian defined above. We also obtain the core constant, which is +# later used to calculate the contribution of the nuclear energy to the Hamiltonian. + +mol = qml.qchem.Molecule(symbols, geometry) +core, one, two = qml.qchem.electron_integrals(mol)() + +############################################################################## +# These integrals are computed over molecular orbitals. Each molecular orbital contains a pair of +# electrons with different spins. We have assumed that the spatial distribution of these electron +# pairs is the same to simplify the calculation of the integrals. However, to properly account for +# all electrons, we need to duplicate the integrals for electrons with the same spin. For example, +# the :math:`pq` integral, which is the integral over the orbital :math:`p` and the orbital +# :math:`q,` can be used +# for both spin-up and spin-down electrons. Then, if we have a :math:`2 \times 2` matrix of such +# integrals, it will become a :math:`4 \times 4` matrix. The code block below simply extends the +# integrals by duplicating terms to account for both spin-up and spin-down electrons. + +for i in range(4): + if i < 2: + one = one.repeat(2, axis=i) + two = two.repeat(2, axis=i) + +############################################################################## +# We can now construct the fermionic Hamiltonian for the hydrogen molecule. The one-body terms, +# which are the first part in the Hamiltonian above, can be added first. We will use +# `itertools `_ to efficiently +# create all the combinations we need. Some of these combinations are not allowed because of spin +# restrictions and we need to exclude them. You can find more details about +# constructing a molecular Hamiltonian in reference [#surjan]_. + +import itertools + +n = one.shape[0] + +h = 0.0 + +for p, q in itertools.product(range(n), repeat=2): + if p % 2 == q % 2: # to account for spin-forbidden terms + h += one[p, q] * FermiC(p) * FermiA(q) + +############################################################################## +# The two-body terms can be added with: + +for p, q, r, s in itertools.product(range(n), repeat=4): + if p % 2 == s % 2 and q % 2 == r % 2: # to account for spin-forbidden terms + h += two[p, q, r, s] / 2 * FermiC(p) * FermiC(q) * FermiA(r) * FermiA(s) + +############################################################################## +# We then simplify the Hamiltonian to remove terms with negligible coefficients and then map it to +# the qubit basis. + +h.simplify() +h = jordan_wigner(h) + +############################################################################## +# We also need to include the contribution of the nuclear energy. + +h += np.sum(core * qml.Identity(0)) + +############################################################################## +# This gives us the qubit Hamiltonian which can be used as an input for quantum algorithms. We can +# also compute the ground-state energy by diagonalizing the matrix representation of the +# Hamiltonian in the computational basis. + +np.linalg.eigh(h.sparse_matrix().toarray())[0].min() + +############################################################################## +# Summary +# ------- +# This demo explains how to create and manipulate fermionic operators in PennyLane, which is as +# easy as writing the operators on paper. PennyLane supports several arithmetic operations between +# fermionic operators and provides tools for mapping them to the qubit basis. This makes it easy and +# intuitive to construct complicated fermionic Hamiltonians such as +# `molecular Hamiltonians `_. +# +# References +# ---------- +# +# .. [#surjan] +# +# Peter R. Surjan, "Second Quantized Approach to Quantum Chemistry". Springer-Verlag, 1989. +# +# +# About the author +# ---------------- +# diff --git a/demonstrations/tutorial_givens_rotations.py b/demonstrations/tutorial_givens_rotations.py index 9413006ffb..1c312bf402 100644 --- a/demonstrations/tutorial_givens_rotations.py +++ b/demonstrations/tutorial_givens_rotations.py @@ -1,506 +1,506 @@ -r""" - -Givens rotations for quantum chemistry -====================================== - -.. meta:: - :property="og:description": Discover the building blocks of quantum circuits for - quantum chemistry - - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/Givens_rotations.png - -.. related:: - tutorial_quantum_chemistry Building molecular Hamiltonians - tutorial_vqe A brief overview of VQE - - -*Author: Juan Miguel Arrazola — Posted: 30 June 2021. Last updated: 30 June 2021.* - -In the book `"Sophie's world" `_, the young -protagonist receives a white envelope containing a letter -with an intriguing question: "Why is Lego the most ingenious toy in the world?" At first baffled by -this curious message, she decides to reflect on the question. As told by the book's narrator, -she arrives at a conclusion: - -*The best thing about them was that with Lego she could construct any kind of object. And then -she could separate the blocks and construct something new. What more could one ask of a toy? -Sophie decided that Lego really could be called the most ingenious toy in the world.* - - - -.. figure:: ../_static/demonstration_assets/givens_rotations/lego-circuit.svg - :align: center - :width: 50% - - -In this tutorial, you will learn about the building blocks of quantum circuits for quantum -chemistry: Givens rotations. These are operations that can be used to construct any kind of -particle-conserving circuit. We discuss single and double excitation gates, which are particular -types of Givens rotations that play an important role in quantum chemistry. Notably, -controlled single excitation gates are universal for particle-conserving unitaries. You will also -learn how to use these gates to build arbitrary states of a fixed number of particles. - -Particle-conserving unitaries ------------------------------ - -Understanding the electronic structure of molecules is arguably the central problem in quantum chemistry. -Quantum computers tackle this problem by using systems of qubits to -represent the quantum states of the electrons. One method is to consider a -collection of `molecular orbitals `_, which -capture the three-dimensional region of space occupied by the electrons. Each orbital can be -occupied by at most two electrons, each with a different spin orientation. In this case we refer to -*spin orbitals* that can be occupied by a single electron. - -The state of electrons in a molecule can then be described by specifying how the -orbitals are occupied. The `Jordan-Wigner representation -`_ provides a -convenient way to do this: we associate a qubit with each spin orbital and -use its states to represent occupied :math:`|1\rangle` or unoccupied -:math:`|0\rangle` spin orbitals. - -An :math:`n`-qubit state with `Hamming weight `_ -:math:`k`, i.e., with :math:`k` qubits in state :math:`|1\rangle,` represents a state of -:math:`k` electrons in :math:`n` spin orbitals. For example :math:`|1010\rangle` is a state of -two electrons in two spin orbitals. More generally, superpositions over all basis states with a -fixed number of particles are valid states of the electrons in a molecule. These are states such as - -.. math:: - - |\psi\rangle = c_1|1100\rangle + c_2|1010\rangle + c_3|1001\rangle + c_4|0110\rangle + - c_5|0101\rangle + c_6|0011\rangle, - -for some coefficients :math:`c_i.` - -.. figure:: ../_static/demonstration_assets/givens_rotations/orbitals_states.png - :align: center - :width: 50% - - States of a system with six spin orbitals and three electrons. Orbitals are for illustration; - they correspond to carbon dioxide, which has more electrons and orbitals. - -Because the number of electrons in a molecule is -fixed, any transformation must conserve the number of particles. We refer to these as -**particle-conserving unitaries**. When designing quantum circuits and algorithms for quantum -chemistry, it is desirable to employ only particle-conserving gates that guarantee that the -states of the system remain valid. This raises the questions: what are the simplest -particle-conserving unitaries? Like Legos, how can they be used to construct any quantum circuit for -quantum chemistry applications? - -Givens rotations ----------------- - -Consider single-qubit gates. In their most general form, they perform the transformation - -.. math:: - - U|0\rangle &= a |0\rangle + b |1\rangle,\\ - U|1\rangle &= c |1\rangle + d |0\rangle, - -where :math:`|a|^2+|b|^2=|c|^2+|d|^2=1` and :math:`ab^* + cd^*=0.` This gate is -particle-conserving only if :math:`b=d=0,` which means that the only single-qubit gates that -preserve particle number are diagonal gates of the form - -.. math:: - - U = \begin{pmatrix} - e^{i\theta} & 0\\ - 0 & e^{i\phi} - \end{pmatrix}. - -On their own, these gates are not very interesting. They can only be used to change the -relative phases of states in a superposition; they cannot be used to create and control such -superpositions. So let's take a look at two-qubit gates. - -Basis states of two qubits can be categorized depending on -their number of particles. - -We have: - -- :math:`|00\rangle` with zero particles, -- :math:`|01\rangle,|10\rangle` with one particle, and -- :math:`|11\rangle` with two particles. - -We can now consider transformations that couple the states :math:`|01\rangle,|10\rangle.` These -are gates of the form - -.. math:: - - U|01\rangle &= a |01\rangle + b |10\rangle\\ - U|10\rangle &= c |10\rangle + d |01\rangle. - -This should be familiar: the unitary has the same form as a single-qubit gate, except that the -states :math:`|01\rangle, |10\rangle` respectively take the place of :math:`|0\rangle, -|1\rangle`. This correspondence has a name: the `dual-rail qubit -`_, where a two-level system is constructed -by specifying in which of two possible systems a single particle is located. The -difference compared to single-qubit gates is that any values of the parameters :math:`a, -b,c,d` give rise to a valid particle-conserving unitary. Take for instance the two-qubit gate - -.. math:: - - G(\theta)=\begin{pmatrix} - 1 & 0 & 0 & 0\\ - 0 & \cos (\theta/2) & -\sin (\theta/2) & 0\\ - 0 & \sin(\theta/2) & \cos(\theta/2) & 0\\ - 0 & 0 & 0 & 1 - \end{pmatrix}. - - -This is an example of a `Givens rotation `_: a -rotation in a two-dimensional subspace of a larger Hilbert space. In this case, we are performing a -Givens rotation in a two-dimensional subspace of the four-dimensional space of two-qubit states. -This gate allows us to create superpositions by exchanging the particle -between the two qubits. Such transformations can be interpreted as a **single excitation**, -where we view the exchange from :math:`|10\rangle` to :math:`|01\rangle` as exciting the electron -from the first to the second qubit. - -.. figure:: ../_static/demonstration_assets/givens_rotations/Givens_rotations_1.png - :align: center - :width: 35% - - A Givens rotation can be used to couple states that differ by a single excitation. - -This gate is implemented in PennyLane as the :class:`~.pennylane.SingleExcitation` operation. -We can use it to prepare an equal superposition of three-qubit states with a single particle -:math:`\frac{1}{\sqrt{3}}(|001\rangle + |010\rangle + |100\rangle).` We apply two single -excitation gates with parameters :math:`\theta, \phi` to the initial state :math:`|100\rangle.` -The excitation gates act on qubits 0,1 and 0,2 respectively. This yields the state - -.. math:: - - |\psi\rangle = \cos(\theta/2)\cos(\phi/2)|100\rangle - \sin(\theta/2)|010\rangle - - \cos(\theta/2)\sin(\phi/2)|001\rangle. - -Since the amplitude of :math:`|010\rangle` must be :math:`1/\sqrt{3},` we conclude that -:math:`-\sin(\theta/2)=1/\sqrt{3}.` This in turn implies that :math:`\cos(\theta/2)=\sqrt{2/3}` and -therefore :math:`-\sin(\phi/2)=1/\sqrt{2}.` Thus, to prepare an equal superposition state we -choose the angles of rotation to be - -.. math:: - - \theta &= - 2 \arcsin(1/\sqrt{3}),\\ - \phi &= - 2 \arcsin(1/\sqrt{2}). - -This can be implemented in PennyLane as follows: -""" - -import pennylane as qml -from jax import numpy as jnp -import jax - -jax.config.update("jax_enable_x64", True) -dev = qml.device('lightning.qubit', wires=3) - -@qml.qnode(dev, interface="jax") -def circuit(x, y): - # prepares the reference state |100> - qml.BasisState(jnp.array([1, 0, 0]), wires=[0, 1, 2]) - # applies the single excitations - qml.SingleExcitation(x, wires=[0, 1]) - qml.SingleExcitation(y, wires=[0, 2]) - return qml.state() - -x = -2 * jnp.arcsin(jnp.sqrt(1/3)) -y = -2 * jnp.arcsin(jnp.sqrt(1/2)) -print(circuit(x, y)) - -############################################################################## -# The components of the output state are ordered according to their binary -# representation, so entry 1 is :math:`|001\rangle`, entry 2 is :math:`|010\rangle,` and entry 4 is -# :math:`|100\rangle,` meaning we indeed prepared the desired state. We can check this by -# reshaping the output state - -tensor_state = circuit(x, y).reshape(2, 2, 2) -print("Amplitude of state |001> = ", tensor_state[0, 0, 1]) -print("Amplitude of state |010> = ", tensor_state[0, 1, 0]) -print("Amplitude of state |100> = ", tensor_state[1, 0, 0]) - -############################################################################## -# We can also study **double excitations** involving the transfer of two particles. For example, -# consider a Givens rotation in the subspace spanned by the states -# :math:`|1100\rangle` and :math:`|0011\rangle.` These -# states differ by a double excitation since we can map :math:`|1100\rangle` to -# :math:`|0011\rangle` by exciting the particles from the first two qubits to the last two. -# Mathematically, this gate can be represented by a unitary :math:`G^{(2)}(\theta)` that performs -# the mapping -# -# .. math:: -# -# G^{(2)}|0011\rangle &= \cos (\theta/2)|0011\rangle + \sin (\theta/2)|1100\rangle\\ -# G^{(2)}|1100\rangle &= \cos (\theta/2)|1100\rangle - \sin (\theta/2)|0011\rangle, -# -# while leaving all other basis states unchanged. This gate is implemented in PennyLane as the -# :class:`~.pennylane.DoubleExcitation` operation. -# -# -# .. figure:: ../_static/demonstration_assets/givens_rotations/Givens_rotations_2.png -# :align: center -# :width: 35% -# -# A Givens rotation can also be used to couple states that differ by a double excitation. -# -# -# In the context of quantum chemistry, it is common to consider excitations on a fixed reference -# state and include only the excitations that preserve the spin orientation of the electron. -# For a system with :math:`n` qubits and :math:`k` particles, this reference state is typically -# chosen as the state with the first :math:`k` qubits in state :math:`|1\rangle,` and the remainder -# in state :math:`|0\rangle.` -# PennyLane allows you to obtain all such excitations using the function -# :func:`~.pennylane.qchem.excitations`. Let's employ it to build a circuit that includes -# all single and double excitations acting on a reference state of three particles in six qubits. -# We apply a random rotation for each gate: - -nr_particles = 3 -nr_qubits = 6 - -singles, doubles = qml.qchem.excitations(3, 6) -print(f"Single excitations = {singles}") -print(f"Double excitations = {doubles}") - -############################################################################## -# Now we continue to build the circuit: - -from jax import random - -dev2 = qml.device('lightning.qubit', wires=6) - -@qml.qnode(dev2, interface="jax") -def circuit2(x, y): - # prepares reference state - qml.BasisState(jnp.array([1, 1, 1, 0, 0, 0]), wires=[0, 1, 2, 3, 4, 5]) - # apply all single excitations - for i, s in enumerate(singles): - qml.SingleExcitation(x[i], wires=s) - # apply all double excitations - for j, d in enumerate(doubles): - qml.DoubleExcitation(y[j], wires=d) - return qml.state() - -# random angles of rotation -key = random.PRNGKey(0) -key_x, key_y = random.split(key) -x = random.normal(key_x, shape=(len(singles),)) -y = random.normal(key_y, shape=(len(singles),)) - -output = circuit2(x, y) - -############################################################################## -# We can check which basis states appear in the resulting superposition to confirm that they -# involve only states with three particles. - -# constructs binary representation of states with non-zero amplitude -import numpy as np - -states = [np.binary_repr(i, width=6) for i in range(len(output)) if output[i] != 0] -print(states) - -############################################################################## -# Besides these Givens rotations, there are other versions that have been -# reported in the literature and used to construct circuits for quantum chemistry. For instance, -# Ref. [#anselmetti]_ considers a different sign convention for single-excitation gates, -# -# .. math:: -# G(\theta)=\begin{pmatrix} -# 1 & 0 & 0 & 0\\ -# 0 & \cos (\theta/2) & \sin (\theta/2) & 0\\ -# 0 & -\sin(\theta/2) & \cos(\theta/2) & 0\\ -# 0 & 0 & 0 & 1 -# \end{pmatrix}, -# -# and Ref. [#barkoutsos]_ introduces the particle-conserving gates listed below, which are all -# Givens rotations -# -# .. math:: -# U_1(\theta, \phi) &= \begin{pmatrix} -# 1 & 0 & 0 & 0\\ -# 0 & \cos (\theta) & e^{i\phi}\sin (\theta) & 0\\ -# 0 & e^{-i\phi}\sin(\theta) & -\cos(\theta) & 0\\ -# 0 & 0 & 0 & 1 -# \end{pmatrix},\\ -# -# U_2(\theta) &= \begin{pmatrix} -# 1 & 0 & 0 & 0\\ -# 0 & \cos (2\theta) & -i\sin (2\theta) & 0\\ -# 0 & -i\sin(2\theta) & \cos(2\theta) & 0\\ -# 0 & 0 & 0 & 1 -# \end{pmatrix}. -# -# Givens rotations are a powerful abstraction for understanding -# quantum circuits for quantum chemistry. Instead of thinking of single-qubit gates and CNOTs as the -# building-blocks of quantum circuits, we can be more clever and select two-dimensional subspaces -# spanned by states with an equal number of particles, and use Givens rotations in that subspace -# to construct the circuits. 🧠 -# -# Controlled excitation gates -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# -# Single-qubit gates and CNOT gates are universal for quantum -# computing: they can be used to implement any conceivable quantum computation. If Givens -# rotations are analogous to single-qubit gates, then **controlled** Givens rotations are -# analogous to two-qubit gates. In universality constructions, the ability to control operations -# based on the states of other qubits is essential, so for this reason it's natural to study -# controlled Givens rotations. The simplest of these are controlled single-excitation gates, -# which are three-qubit gates that perform the mapping -# -# .. math:: -# CG(\theta) |101\rangle &= \cos (\theta/2)|101\rangle + \sin (\theta/2)|110\rangle,\\ -# CG(\theta) |110\rangle &= \cos (\theta/2)|110\rangle - \sin (\theta/2)|101\rangle, -# -# while leaving all other basis states unchanged. This gate only excites a particle -# from the second to third qubit, and vice versa, if the first (control) qubit is in state -# :math:`|1\rangle.` This is a useful property: as the name suggests, it provides us with better -# control over the transformations we want to apply. Suppose we aim to prepare the state -# -# .. math:: -# |\psi\rangle = \frac{1}{2}(|110000\rangle + |001100\rangle + |000011\rangle + |100100\rangle). -# -# Some inspection is enough to see that the states :math:`|001100\rangle` and :math:`|000011\rangle` -# differ by a double excitation from the reference state :math:`|110000\rangle.` Meanwhile, the state -# :math:`|100100\rangle` differs by a single excitation. It is thus tempting to think that applying -# two double-excitation gates and a single-excitation gate can be used to prepare the target state. -# It won't work! Applying the single-excitation gate on qubits 1 and 3 will also lead to an -# undesired contribution for the state :math:`|011000\rangle` through a coupling with -# :math:`|001100\rangle.` Let's check that this is the case: - -dev = qml.device('default.qubit', wires=6) - -@qml.qnode(dev, interface="jax") -def circuit3(x, y, z): - qml.BasisState(jnp.array([1, 1, 0, 0, 0, 0]), wires=[i for i in range(6)]) - qml.DoubleExcitation(x, wires=[0, 1, 2, 3]) - qml.DoubleExcitation(y, wires=[0, 1, 4, 5]) - qml.SingleExcitation(z, wires=[1, 3]) - return qml.state() - -x = -2 * jnp.arcsin(jnp.sqrt(1/4)) -y = -2 * jnp.arcsin(jnp.sqrt(1/3)) -z = -2 * jnp.arcsin(jnp.sqrt(1/2)) - -output = circuit3(x, y, z) -states = [np.binary_repr(i, width=6) for i in range(len(output)) if output[i] != 0] -print(states) - -############################################################################## -# Indeed, we have a non-zero coefficient for :math:`|011000\rangle.` To address this problem, -# we can instead apply the single-excitation gate controlled on the -# state of the first qubit. This ensures that there is no coupling with the state :math:`| -# 001100\rangle` since here the first qubit is in state :math:`|0\rangle.` Let's implement the circuit -# above, this time controlling on the state of the first qubit and verify that we can prepare the -# desired state. To perform the control, we use the :func:`~.pennylane.ctrl` transform: - -@qml.qnode(dev, interface="jax") -def circuit4(x, y, z): - qml.BasisState(jnp.array([1, 1, 0, 0, 0, 0]), wires=[i for i in range(6)]) - qml.DoubleExcitation(x, wires=[0, 1, 2, 3]) - qml.DoubleExcitation(y, wires=[0, 1, 4, 5]) - # single excitation controlled on qubit 0 - qml.ctrl(qml.SingleExcitation, control=0)(z, wires=[1, 3]) - return qml.state() - -output = circuit4(x, y, z) -states = [np.binary_repr(i, width=6) for i in range(len(output)) if output[i] != 0] -print(states) - -############################################################################## -# It was proven in Ref. [#arrazola]_ that controlled single-excitation gates are universal for -# particle-conserving unitaries. With enough ingenuity, you can use these operations to construct -# any kind of circuit for quantum chemistry applications. What more could you ask from a gate? -# -# State preparation -# ----------------- -# -# We can bring all these pieces together and implement a circuit capable of preparing -# four-qubit states of two particles with real coefficients. The main idea is that we can -# perform the construction one basis state at a time by applying a suitable excitation gate, -# which may need to be controlled. -# -# -# .. figure:: ../_static/demonstration_assets/givens_rotations/circuit.png -# :align: center -# :width: 70% -# -# A circuit for preparing four-qubit states with two particles. -# -# -# Starting from the reference state :math:`|1100\rangle,` we create a superposition -# with the state :math:`|1010\rangle` by applying a single-excitation gate on qubits 1 and 2. -# Similarly, we create a superposition with the state :math:`|1001\rangle` with a single -# excitation between qubits 1 and 3. This leaves us with a state of the form -# -# .. math:: -# |\psi\rangle = a |1100\rangle + b |1010\rangle + c |1001\rangle. -# -# We can now perform two single excitations from qubit 0 to qubits 2 and 3. These will have -# to be controlled on the state of qubit 1. Finally, applying a double-excitation gate on all qubits -# can create a superposition of the form -# -# .. math:: -# -# |\psi\rangle = c_1|1100\rangle + c_2|1010\rangle + c_3|1001\rangle + c_4|0110\rangle + -# c_5|0101\rangle + c_6|0011\rangle, -# -# which is our intended outcome. Let's use this approach to create an equal superposition over -# all two-particle states on four qubits. We follow the same strategy as before, setting the angle -# of the :math:`k`-th Givens rotation as :math:`-2 \arcsin(1/\sqrt{n-k}),` where :math:`n` is the -# number of basis states in the superposition. - -dev = qml.device('default.qubit', wires=4) - -@qml.qnode(dev, interface="jax") -def state_preparation(params): - qml.BasisState(jnp.array([1, 1, 0, 0]), wires=[0, 1, 2, 3]) - qml.SingleExcitation(params[0], wires=[1, 2]) - qml.SingleExcitation(params[1], wires=[1, 3]) - # single excitations controlled on qubit 1 - qml.ctrl(qml.SingleExcitation, control=1)(params[2], wires=[0, 2]) - qml.ctrl(qml.SingleExcitation, control=1)(params[3], wires=[0, 3]) - qml.DoubleExcitation(params[4], wires=[0, 1, 2, 3]) - return qml.state() - -n = 6 -params = jnp.array([-2 * jnp.arcsin(1/jnp.sqrt(n-i)) for i in range(n-1)]) - -output = state_preparation(params) -# sets very small coefficients to zero -output = jnp.where(jnp.abs(output) < 1e-10, 0.0, output) -states = [np.binary_repr(i, width=4) for i in range(len(output)) if output[i] != 0] -print("Basis states = ", states) -print("Output state =", output) - -############################################################################## -# Success! This is the equal superposition state we wanted to prepare. 🚀 -# -# When it comes to quantum circuits for quantum chemistry, a wide variety of -# architectures have been proposed. Researchers in the field are faced with the apparent -# choice of making a selection among these circuits to conduct their computations and benchmark new -# algorithms. Like a kid in a toy store, it is challenging to pick just one. -# -# Ultimately, the aim of this tutorial is to provide you with the conceptual and software tools -# to implement any of these proposed circuits, and **also to design your own**. It's not only fun -# to play with toys; it's also fun to build them. -# -# -# References -# ---------- -# -# .. [#anselmetti] -# -# G-L. R. Anselmetti, D. Wierichs, C. Gogolin, Christian, and R. M. Parrish, -# "Local, Expressive, Quantum-Number-Preserving VQE Ansatze -# for Fermionic Systems", arXiv:2104.05695, (2021). -# -# .. [#barkoutsos] -# -# P. KL. Barkoutsos, Panagiotis, et al., "Quantum algorithms for electronic structure -# calculations: Particle-hole Hamiltonian and optimized wave-function expansions", Physical -# Review A 98(2), 022322, (2018). -# -# .. [#arrazola] -# -# J. M. Arrazola, O. Di Matteo, N. Quesada, S. Jahangiri, A. Delgado, N. Killoran, "Universal -# quantum circuits for quantum chemistry", arXiv:2106.13839, (2021) -# -# -# About the author -# ---------------- -# +r""" + +Givens rotations for quantum chemistry +====================================== + +.. meta:: + :property="og:description": Discover the building blocks of quantum circuits for + quantum chemistry + + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/Givens_rotations.png + +.. related:: + tutorial_quantum_chemistry Building molecular Hamiltonians + tutorial_vqe A brief overview of VQE + + +*Author: Juan Miguel Arrazola — Posted: 30 June 2021. Last updated: 30 June 2021.* + +In the book `"Sophie's world" `_, the young +protagonist receives a white envelope containing a letter +with an intriguing question: "Why is Lego the most ingenious toy in the world?" At first baffled by +this curious message, she decides to reflect on the question. As told by the book's narrator, +she arrives at a conclusion: + +*The best thing about them was that with Lego she could construct any kind of object. And then +she could separate the blocks and construct something new. What more could one ask of a toy? +Sophie decided that Lego really could be called the most ingenious toy in the world.* + + + +.. figure:: ../_static/demonstration_assets/givens_rotations/lego-circuit.svg + :align: center + :width: 50% + + +In this tutorial, you will learn about the building blocks of quantum circuits for quantum +chemistry: Givens rotations. These are operations that can be used to construct any kind of +particle-conserving circuit. We discuss single and double excitation gates, which are particular +types of Givens rotations that play an important role in quantum chemistry. Notably, +controlled single excitation gates are universal for particle-conserving unitaries. You will also +learn how to use these gates to build arbitrary states of a fixed number of particles. + +Particle-conserving unitaries +----------------------------- + +Understanding the electronic structure of molecules is arguably the central problem in quantum chemistry. +Quantum computers tackle this problem by using systems of qubits to +represent the quantum states of the electrons. One method is to consider a +collection of `molecular orbitals `_, which +capture the three-dimensional region of space occupied by the electrons. Each orbital can be +occupied by at most two electrons, each with a different spin orientation. In this case we refer to +*spin orbitals* that can be occupied by a single electron. + +The state of electrons in a molecule can then be described by specifying how the +orbitals are occupied. The `Jordan-Wigner representation +`_ provides a +convenient way to do this: we associate a qubit with each spin orbital and +use its states to represent occupied :math:`|1\rangle` or unoccupied +:math:`|0\rangle` spin orbitals. + +An :math:`n`-qubit state with `Hamming weight `_ +:math:`k`, i.e., with :math:`k` qubits in state :math:`|1\rangle,` represents a state of +:math:`k` electrons in :math:`n` spin orbitals. For example :math:`|1010\rangle` is a state of +two electrons in two spin orbitals. More generally, superpositions over all basis states with a +fixed number of particles are valid states of the electrons in a molecule. These are states such as + +.. math:: + + |\psi\rangle = c_1|1100\rangle + c_2|1010\rangle + c_3|1001\rangle + c_4|0110\rangle + + c_5|0101\rangle + c_6|0011\rangle, + +for some coefficients :math:`c_i.` + +.. figure:: ../_static/demonstration_assets/givens_rotations/orbitals_states.png + :align: center + :width: 50% + + States of a system with six spin orbitals and three electrons. Orbitals are for illustration; + they correspond to carbon dioxide, which has more electrons and orbitals. + +Because the number of electrons in a molecule is +fixed, any transformation must conserve the number of particles. We refer to these as +**particle-conserving unitaries**. When designing quantum circuits and algorithms for quantum +chemistry, it is desirable to employ only particle-conserving gates that guarantee that the +states of the system remain valid. This raises the questions: what are the simplest +particle-conserving unitaries? Like Legos, how can they be used to construct any quantum circuit for +quantum chemistry applications? + +Givens rotations +---------------- + +Consider single-qubit gates. In their most general form, they perform the transformation + +.. math:: + + U|0\rangle &= a |0\rangle + b |1\rangle,\\ + U|1\rangle &= c |1\rangle + d |0\rangle, + +where :math:`|a|^2+|b|^2=|c|^2+|d|^2=1` and :math:`ab^* + cd^*=0.` This gate is +particle-conserving only if :math:`b=d=0,` which means that the only single-qubit gates that +preserve particle number are diagonal gates of the form + +.. math:: + + U = \begin{pmatrix} + e^{i\theta} & 0\\ + 0 & e^{i\phi} + \end{pmatrix}. + +On their own, these gates are not very interesting. They can only be used to change the +relative phases of states in a superposition; they cannot be used to create and control such +superpositions. So let's take a look at two-qubit gates. + +Basis states of two qubits can be categorized depending on +their number of particles. + +We have: + +- :math:`|00\rangle` with zero particles, +- :math:`|01\rangle,|10\rangle` with one particle, and +- :math:`|11\rangle` with two particles. + +We can now consider transformations that couple the states :math:`|01\rangle,|10\rangle.` These +are gates of the form + +.. math:: + + U|01\rangle &= a |01\rangle + b |10\rangle\\ + U|10\rangle &= c |10\rangle + d |01\rangle. + +This should be familiar: the unitary has the same form as a single-qubit gate, except that the +states :math:`|01\rangle, |10\rangle` respectively take the place of :math:`|0\rangle, +|1\rangle`. This correspondence has a name: the `dual-rail qubit +`_, where a two-level system is constructed +by specifying in which of two possible systems a single particle is located. The +difference compared to single-qubit gates is that any values of the parameters :math:`a, +b,c,d` give rise to a valid particle-conserving unitary. Take for instance the two-qubit gate + +.. math:: + + G(\theta)=\begin{pmatrix} + 1 & 0 & 0 & 0\\ + 0 & \cos (\theta/2) & -\sin (\theta/2) & 0\\ + 0 & \sin(\theta/2) & \cos(\theta/2) & 0\\ + 0 & 0 & 0 & 1 + \end{pmatrix}. + + +This is an example of a `Givens rotation `_: a +rotation in a two-dimensional subspace of a larger Hilbert space. In this case, we are performing a +Givens rotation in a two-dimensional subspace of the four-dimensional space of two-qubit states. +This gate allows us to create superpositions by exchanging the particle +between the two qubits. Such transformations can be interpreted as a **single excitation**, +where we view the exchange from :math:`|10\rangle` to :math:`|01\rangle` as exciting the electron +from the first to the second qubit. + +.. figure:: ../_static/demonstration_assets/givens_rotations/Givens_rotations_1.png + :align: center + :width: 35% + + A Givens rotation can be used to couple states that differ by a single excitation. + +This gate is implemented in PennyLane as the :class:`~.pennylane.SingleExcitation` operation. +We can use it to prepare an equal superposition of three-qubit states with a single particle +:math:`\frac{1}{\sqrt{3}}(|001\rangle + |010\rangle + |100\rangle).` We apply two single +excitation gates with parameters :math:`\theta, \phi` to the initial state :math:`|100\rangle.` +The excitation gates act on qubits 0,1 and 0,2 respectively. This yields the state + +.. math:: + + |\psi\rangle = \cos(\theta/2)\cos(\phi/2)|100\rangle - \sin(\theta/2)|010\rangle - + \cos(\theta/2)\sin(\phi/2)|001\rangle. + +Since the amplitude of :math:`|010\rangle` must be :math:`1/\sqrt{3},` we conclude that +:math:`-\sin(\theta/2)=1/\sqrt{3}.` This in turn implies that :math:`\cos(\theta/2)=\sqrt{2/3}` and +therefore :math:`-\sin(\phi/2)=1/\sqrt{2}.` Thus, to prepare an equal superposition state we +choose the angles of rotation to be + +.. math:: + + \theta &= - 2 \arcsin(1/\sqrt{3}),\\ + \phi &= - 2 \arcsin(1/\sqrt{2}). + +This can be implemented in PennyLane as follows: +""" + +import pennylane as qml +from jax import numpy as jnp +import jax + +jax.config.update("jax_enable_x64", True) +dev = qml.device('lightning.qubit', wires=3) + +@qml.qnode(dev, interface="jax") +def circuit(x, y): + # prepares the reference state |100> + qml.BasisState(jnp.array([1, 0, 0]), wires=[0, 1, 2]) + # applies the single excitations + qml.SingleExcitation(x, wires=[0, 1]) + qml.SingleExcitation(y, wires=[0, 2]) + return qml.state() + +x = -2 * jnp.arcsin(jnp.sqrt(1/3)) +y = -2 * jnp.arcsin(jnp.sqrt(1/2)) +print(circuit(x, y)) + +############################################################################## +# The components of the output state are ordered according to their binary +# representation, so entry 1 is :math:`|001\rangle`, entry 2 is :math:`|010\rangle,` and entry 4 is +# :math:`|100\rangle,` meaning we indeed prepared the desired state. We can check this by +# reshaping the output state + +tensor_state = circuit(x, y).reshape(2, 2, 2) +print("Amplitude of state |001> = ", tensor_state[0, 0, 1]) +print("Amplitude of state |010> = ", tensor_state[0, 1, 0]) +print("Amplitude of state |100> = ", tensor_state[1, 0, 0]) + +############################################################################## +# We can also study **double excitations** involving the transfer of two particles. For example, +# consider a Givens rotation in the subspace spanned by the states +# :math:`|1100\rangle` and :math:`|0011\rangle.` These +# states differ by a double excitation since we can map :math:`|1100\rangle` to +# :math:`|0011\rangle` by exciting the particles from the first two qubits to the last two. +# Mathematically, this gate can be represented by a unitary :math:`G^{(2)}(\theta)` that performs +# the mapping +# +# .. math:: +# +# G^{(2)}|0011\rangle &= \cos (\theta/2)|0011\rangle + \sin (\theta/2)|1100\rangle\\ +# G^{(2)}|1100\rangle &= \cos (\theta/2)|1100\rangle - \sin (\theta/2)|0011\rangle, +# +# while leaving all other basis states unchanged. This gate is implemented in PennyLane as the +# :class:`~.pennylane.DoubleExcitation` operation. +# +# +# .. figure:: ../_static/demonstration_assets/givens_rotations/Givens_rotations_2.png +# :align: center +# :width: 35% +# +# A Givens rotation can also be used to couple states that differ by a double excitation. +# +# +# In the context of quantum chemistry, it is common to consider excitations on a fixed reference +# state and include only the excitations that preserve the spin orientation of the electron. +# For a system with :math:`n` qubits and :math:`k` particles, this reference state is typically +# chosen as the state with the first :math:`k` qubits in state :math:`|1\rangle,` and the remainder +# in state :math:`|0\rangle.` +# PennyLane allows you to obtain all such excitations using the function +# :func:`~.pennylane.qchem.excitations`. Let's employ it to build a circuit that includes +# all single and double excitations acting on a reference state of three particles in six qubits. +# We apply a random rotation for each gate: + +nr_particles = 3 +nr_qubits = 6 + +singles, doubles = qml.qchem.excitations(3, 6) +print(f"Single excitations = {singles}") +print(f"Double excitations = {doubles}") + +############################################################################## +# Now we continue to build the circuit: + +from jax import random + +dev2 = qml.device('lightning.qubit', wires=6) + +@qml.qnode(dev2, interface="jax") +def circuit2(x, y): + # prepares reference state + qml.BasisState(jnp.array([1, 1, 1, 0, 0, 0]), wires=[0, 1, 2, 3, 4, 5]) + # apply all single excitations + for i, s in enumerate(singles): + qml.SingleExcitation(x[i], wires=s) + # apply all double excitations + for j, d in enumerate(doubles): + qml.DoubleExcitation(y[j], wires=d) + return qml.state() + +# random angles of rotation +key = random.PRNGKey(0) +key_x, key_y = random.split(key) +x = random.normal(key_x, shape=(len(singles),)) +y = random.normal(key_y, shape=(len(singles),)) + +output = circuit2(x, y) + +############################################################################## +# We can check which basis states appear in the resulting superposition to confirm that they +# involve only states with three particles. + +# constructs binary representation of states with non-zero amplitude +import numpy as np + +states = [np.binary_repr(i, width=6) for i in range(len(output)) if output[i] != 0] +print(states) + +############################################################################## +# Besides these Givens rotations, there are other versions that have been +# reported in the literature and used to construct circuits for quantum chemistry. For instance, +# Ref. [#anselmetti]_ considers a different sign convention for single-excitation gates, +# +# .. math:: +# G(\theta)=\begin{pmatrix} +# 1 & 0 & 0 & 0\\ +# 0 & \cos (\theta/2) & \sin (\theta/2) & 0\\ +# 0 & -\sin(\theta/2) & \cos(\theta/2) & 0\\ +# 0 & 0 & 0 & 1 +# \end{pmatrix}, +# +# and Ref. [#barkoutsos]_ introduces the particle-conserving gates listed below, which are all +# Givens rotations +# +# .. math:: +# U_1(\theta, \phi) &= \begin{pmatrix} +# 1 & 0 & 0 & 0\\ +# 0 & \cos (\theta) & e^{i\phi}\sin (\theta) & 0\\ +# 0 & e^{-i\phi}\sin(\theta) & -\cos(\theta) & 0\\ +# 0 & 0 & 0 & 1 +# \end{pmatrix},\\ +# +# U_2(\theta) &= \begin{pmatrix} +# 1 & 0 & 0 & 0\\ +# 0 & \cos (2\theta) & -i\sin (2\theta) & 0\\ +# 0 & -i\sin(2\theta) & \cos(2\theta) & 0\\ +# 0 & 0 & 0 & 1 +# \end{pmatrix}. +# +# Givens rotations are a powerful abstraction for understanding +# quantum circuits for quantum chemistry. Instead of thinking of single-qubit gates and CNOTs as the +# building-blocks of quantum circuits, we can be more clever and select two-dimensional subspaces +# spanned by states with an equal number of particles, and use Givens rotations in that subspace +# to construct the circuits. 🧠 +# +# Controlled excitation gates +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Single-qubit gates and CNOT gates are universal for quantum +# computing: they can be used to implement any conceivable quantum computation. If Givens +# rotations are analogous to single-qubit gates, then **controlled** Givens rotations are +# analogous to two-qubit gates. In universality constructions, the ability to control operations +# based on the states of other qubits is essential, so for this reason it's natural to study +# controlled Givens rotations. The simplest of these are controlled single-excitation gates, +# which are three-qubit gates that perform the mapping +# +# .. math:: +# CG(\theta) |101\rangle &= \cos (\theta/2)|101\rangle + \sin (\theta/2)|110\rangle,\\ +# CG(\theta) |110\rangle &= \cos (\theta/2)|110\rangle - \sin (\theta/2)|101\rangle, +# +# while leaving all other basis states unchanged. This gate only excites a particle +# from the second to third qubit, and vice versa, if the first (control) qubit is in state +# :math:`|1\rangle.` This is a useful property: as the name suggests, it provides us with better +# control over the transformations we want to apply. Suppose we aim to prepare the state +# +# .. math:: +# |\psi\rangle = \frac{1}{2}(|110000\rangle + |001100\rangle + |000011\rangle + |100100\rangle). +# +# Some inspection is enough to see that the states :math:`|001100\rangle` and :math:`|000011\rangle` +# differ by a double excitation from the reference state :math:`|110000\rangle.` Meanwhile, the state +# :math:`|100100\rangle` differs by a single excitation. It is thus tempting to think that applying +# two double-excitation gates and a single-excitation gate can be used to prepare the target state. +# It won't work! Applying the single-excitation gate on qubits 1 and 3 will also lead to an +# undesired contribution for the state :math:`|011000\rangle` through a coupling with +# :math:`|001100\rangle.` Let's check that this is the case: + +dev = qml.device('default.qubit', wires=6) + +@qml.qnode(dev, interface="jax") +def circuit3(x, y, z): + qml.BasisState(jnp.array([1, 1, 0, 0, 0, 0]), wires=[i for i in range(6)]) + qml.DoubleExcitation(x, wires=[0, 1, 2, 3]) + qml.DoubleExcitation(y, wires=[0, 1, 4, 5]) + qml.SingleExcitation(z, wires=[1, 3]) + return qml.state() + +x = -2 * jnp.arcsin(jnp.sqrt(1/4)) +y = -2 * jnp.arcsin(jnp.sqrt(1/3)) +z = -2 * jnp.arcsin(jnp.sqrt(1/2)) + +output = circuit3(x, y, z) +states = [np.binary_repr(i, width=6) for i in range(len(output)) if output[i] != 0] +print(states) + +############################################################################## +# Indeed, we have a non-zero coefficient for :math:`|011000\rangle.` To address this problem, +# we can instead apply the single-excitation gate controlled on the +# state of the first qubit. This ensures that there is no coupling with the state :math:`| +# 001100\rangle` since here the first qubit is in state :math:`|0\rangle.` Let's implement the circuit +# above, this time controlling on the state of the first qubit and verify that we can prepare the +# desired state. To perform the control, we use the :func:`~.pennylane.ctrl` transform: + +@qml.qnode(dev, interface="jax") +def circuit4(x, y, z): + qml.BasisState(jnp.array([1, 1, 0, 0, 0, 0]), wires=[i for i in range(6)]) + qml.DoubleExcitation(x, wires=[0, 1, 2, 3]) + qml.DoubleExcitation(y, wires=[0, 1, 4, 5]) + # single excitation controlled on qubit 0 + qml.ctrl(qml.SingleExcitation, control=0)(z, wires=[1, 3]) + return qml.state() + +output = circuit4(x, y, z) +states = [np.binary_repr(i, width=6) for i in range(len(output)) if output[i] != 0] +print(states) + +############################################################################## +# It was proven in Ref. [#arrazola]_ that controlled single-excitation gates are universal for +# particle-conserving unitaries. With enough ingenuity, you can use these operations to construct +# any kind of circuit for quantum chemistry applications. What more could you ask from a gate? +# +# State preparation +# ----------------- +# +# We can bring all these pieces together and implement a circuit capable of preparing +# four-qubit states of two particles with real coefficients. The main idea is that we can +# perform the construction one basis state at a time by applying a suitable excitation gate, +# which may need to be controlled. +# +# +# .. figure:: ../_static/demonstration_assets/givens_rotations/circuit.png +# :align: center +# :width: 70% +# +# A circuit for preparing four-qubit states with two particles. +# +# +# Starting from the reference state :math:`|1100\rangle,` we create a superposition +# with the state :math:`|1010\rangle` by applying a single-excitation gate on qubits 1 and 2. +# Similarly, we create a superposition with the state :math:`|1001\rangle` with a single +# excitation between qubits 1 and 3. This leaves us with a state of the form +# +# .. math:: +# |\psi\rangle = a |1100\rangle + b |1010\rangle + c |1001\rangle. +# +# We can now perform two single excitations from qubit 0 to qubits 2 and 3. These will have +# to be controlled on the state of qubit 1. Finally, applying a double-excitation gate on all qubits +# can create a superposition of the form +# +# .. math:: +# +# |\psi\rangle = c_1|1100\rangle + c_2|1010\rangle + c_3|1001\rangle + c_4|0110\rangle + +# c_5|0101\rangle + c_6|0011\rangle, +# +# which is our intended outcome. Let's use this approach to create an equal superposition over +# all two-particle states on four qubits. We follow the same strategy as before, setting the angle +# of the :math:`k`-th Givens rotation as :math:`-2 \arcsin(1/\sqrt{n-k}),` where :math:`n` is the +# number of basis states in the superposition. + +dev = qml.device('default.qubit', wires=4) + +@qml.qnode(dev, interface="jax") +def state_preparation(params): + qml.BasisState(jnp.array([1, 1, 0, 0]), wires=[0, 1, 2, 3]) + qml.SingleExcitation(params[0], wires=[1, 2]) + qml.SingleExcitation(params[1], wires=[1, 3]) + # single excitations controlled on qubit 1 + qml.ctrl(qml.SingleExcitation, control=1)(params[2], wires=[0, 2]) + qml.ctrl(qml.SingleExcitation, control=1)(params[3], wires=[0, 3]) + qml.DoubleExcitation(params[4], wires=[0, 1, 2, 3]) + return qml.state() + +n = 6 +params = jnp.array([-2 * jnp.arcsin(1/jnp.sqrt(n-i)) for i in range(n-1)]) + +output = state_preparation(params) +# sets very small coefficients to zero +output = jnp.where(jnp.abs(output) < 1e-10, 0.0, output) +states = [np.binary_repr(i, width=4) for i in range(len(output)) if output[i] != 0] +print("Basis states = ", states) +print("Output state =", output) + +############################################################################## +# Success! This is the equal superposition state we wanted to prepare. 🚀 +# +# When it comes to quantum circuits for quantum chemistry, a wide variety of +# architectures have been proposed. Researchers in the field are faced with the apparent +# choice of making a selection among these circuits to conduct their computations and benchmark new +# algorithms. Like a kid in a toy store, it is challenging to pick just one. +# +# Ultimately, the aim of this tutorial is to provide you with the conceptual and software tools +# to implement any of these proposed circuits, and **also to design your own**. It's not only fun +# to play with toys; it's also fun to build them. +# +# +# References +# ---------- +# +# .. [#anselmetti] +# +# G-L. R. Anselmetti, D. Wierichs, C. Gogolin, Christian, and R. M. Parrish, +# "Local, Expressive, Quantum-Number-Preserving VQE Ansatze +# for Fermionic Systems", arXiv:2104.05695, (2021). +# +# .. [#barkoutsos] +# +# P. KL. Barkoutsos, Panagiotis, et al., "Quantum algorithms for electronic structure +# calculations: Particle-hole Hamiltonian and optimized wave-function expansions", Physical +# Review A 98(2), 022322, (2018). +# +# .. [#arrazola] +# +# J. M. Arrazola, O. Di Matteo, N. Quesada, S. Jahangiri, A. Delgado, N. Killoran, "Universal +# quantum circuits for quantum chemistry", arXiv:2106.13839, (2021) +# +# +# About the author +# ---------------- +# diff --git a/demonstrations/tutorial_haar_measure.py b/demonstrations/tutorial_haar_measure.py index edde00d2d7..5807b5ab34 100644 --- a/demonstrations/tutorial_haar_measure.py +++ b/demonstrations/tutorial_haar_measure.py @@ -1,812 +1,812 @@ -r""".. role:: html(raw) - :format: html - -Understanding the Haar measure -============================== - -.. meta:: - :property="og:description": Learn all about the Haar measure and how to randomly sample quantum states. - - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/spherical_int_dtheta.png - -.. related:: - - tutorial_unitary_designs Unitary designs - quantum_volume Quantum volume - qsim_beyond_classical Beyond classical computing with qsim - tutorial_barren_plateaus Barren plateaus in quantum neural networks - - -*Author: Olivia Di Matteo — Posted: 22 March 2021. Last updated: 22 March 2021.* - -If you've ever dug into the literature about random quantum circuits, -variational ansatz structure, or anything related to the structure and -properties of unitary operations, you've likely come across a statement like the -following: "Assume that :math:`U` is sampled uniformly at random from the Haar -measure". In this demo, we're going to unravel this cryptic statement and take -an in-depth look at what it means. You'll gain an understanding of the general -concept of a *measure*, the Haar measure and its special properties, and you'll -learn how to sample from it using tools available in PennyLane and other -scientific computing frameworks. By the end of this demo, you'll be able to -include that important statement in your own work with confidence! - -.. note:: - - To get the most out of this demo, it is helpful if you are familiar with - `integration of multi-dimensional functions - `__, the `Bloch sphere - `__, and the conceptual ideas - behind `decompositions - `__ and factorizations of - unitary matrices (see, e.g., 4.5.1 and 4.5.2 of [#NandC2000]_). - -Measure -------- - -`Measure theory `__ is a -branch of mathematics that studies things that are measurable—think length, -area, or volume, but generalized to mathematical spaces and even higher -dimensions. Loosely, the measure tells you about how "stuff" is distributed and -concentrated in a mathematical set or space. An intuitive way to understand -the measure is to think about a sphere. An arbitrary point on a sphere can be -parametrized by three numbers—depending on what you're doing, you may use -Cartesian coordinates :math:`(x, y, z),` or it may be more convenient to use -spherical coordinates :math:`(\rho, \phi, \theta).` - -Suppose you wanted to compute the volume of a solid sphere with radius -:math:`r.` This can be done by integrating over the three coordinates -:math:`\rho, \phi,` and :math:`\theta.` Your first thought here may be to simply -integrate each parameter over its full range, like so: - -.. math:: - - V = \int_0^{r} \int_0^{2\pi} \int_0^{\pi} d\rho~ d\phi~ d\theta = 2\pi^2 r - -But, we know that the volume of a sphere of radius :math:`r` is -:math:`\frac{4}{3}\pi r^3,` so what we got from this integral is clearly wrong! -Taking the integral naively like this doesn't take into account the structure of -the sphere with respect to the parameters. For example, consider -two small, infinitesimal elements of area with the same difference in -:math:`\theta` and :math:`\phi,` but at different values of :math:`\theta:` - -.. figure:: /_static/demonstration_assets/haar_measure/spherical_int_dtheta.png - :align: center - :width: 50% - - -Even though the differences :math:`d\theta` and :math:`d\phi` themselves are the -same, there is way more "stuff" near the equator of the sphere than there is -near the poles. We must take into account the value of :math:`\theta` when -computing the integral! Specifically, we multiply by the function -:math:`\sin\theta`---the properties of the :math:`\sin` function mean that the -most weight will occur around the equator where :math:`\theta=\pi/2,` and the -least weight near the poles where :math:`\theta=0` and :math:`\theta=\pi.` - -Similar care must be taken for :math:`\rho.` The contribution to volume of -parts of the sphere with a large :math:`\rho` is far more than for a small -:math:`\rho`---we should expect the contribution to be proportional to -:math:`\rho^2,` given that the surface area of a sphere of radius :math:`r` is -:math:`4\pi r^2.` - -On the other hand, for a fixed :math:`\rho` and :math:`\theta,` the length of -the :math:`d\phi` is the same all around the circle. If put all these facts -together, we find that the actual expression for the integral should look like -this: - -.. math:: - - V = \int_0^r \int_0^{2\pi} \int_0^{\pi} \rho^2 \sin \theta~ d\rho~ d\phi~ - d\theta = \frac{4}{3}\pi r^3 - -These extra terms that we had to add to the integral, :math:`\rho^2 \sin -\theta`, constitute the *measure*. The measure weights portions of the sphere -differently depending on where they are in the space. While we need to know the -measure to properly integrate over the sphere, knowledge of the measure also -gives us the means to perform another important task, that of sampling points in -the space uniformly at random. We can't simply sample each parameter from the -uniform distribution over its domain—as we experienced already, this doesn't -take into account how the sphere is spread out over space. The measure describes -the distribution of each parameter and gives a recipe for sampling them in order -to obtain something properly uniform. - -The Haar measure ----------------- - -Operations in quantum computing are described by unitary matrices. -Unitary matrices, like points on a sphere, can be expressed in terms of a fixed -set of coordinates, or parameters. For example, the most general single-qubit rotation -implemented in PennyLane (:class:`~.pennylane.Rot`) is expressed in terms of three -parameters like so, - -.. math:: - - U(\phi, \theta, \omega) = \begin{pmatrix} e^{-i(\phi + \omega)/2} - \cos(\theta/2) & -e^{i(\phi - \omega)/2} \sin(\theta/2) - \\ e^{-i(\phi - \omega)/2} \sin(\theta/2) & e^{i(\phi + - \omega)/2} \cos(\theta/2) \end{pmatrix}. - -For every dimension :math:`N,` the unitary matrices of size :math:`N \times N` -constitute the *unitary group* :math:`U(N).` We can perform operations on -elements of this group, such as apply functions to them, integrate over them, or -sample uniformly over them, just as we can do to points on a sphere. When we do -such tasks with respect to the sphere, we have to add the measure in order to -properly weight the different regions of space. The *Haar measure* provides the -analogous terms we need for working with the unitary group. - -For an :math:`N`-dimensional system, the Haar measure, often denoted by -:math:`\mu_N,` tells us how to weight the elements of :math:`U(N).` For -example, suppose :math:`f` is a function that acts on elements of :math:`U(N),` -and we would like to take its integral over the group. We must write this -integral with respect to the Haar measure, like so: - -.. math:: - - \int_{V \in U(N)} f(V) d\mu_N(V). - -As with the measure term of the sphere, :math:`d\mu_N` itself can be broken down -into components depending on individual parameters. While the Haar -measure can be defined for every dimension :math:`N,` the mathematical form gets -quite hairy for larger dimensions—in general, an :math:`N`-dimensional unitary -requires at least :math:`N^2 - 1` parameters, which is a lot to keep track of! -Therefore we'll start with the case of a single qubit :math:`(N=2),` then show -how things generalize. - -Single-qubit Haar measure -~~~~~~~~~~~~~~~~~~~~~~~~~ - -The single-qubit case provides a particularly nice entry point because we can -continue our comparison to spheres by visualizing single-qubit states on the -Bloch sphere. As expressed above, the measure provides a recipe for sampling -elements of the unitary group in a properly uniform manner, given the structure -of the group. One useful consequence of this is that it provides a method to -sample quantum *states* uniformly at random—we simply generate Haar-random -unitaries, and apply them to a fixed basis state such as :math:`\vert 0\rangle.` - -We'll see how this works in good time. First, we'll take a look at what happens -when we ignore the measure and do things *wrong*. Suppose we sample quantum -states by applying unitaries obtained by the parametrization above, but sample -the angles :math:`\omega, \phi,` and :math:`\theta` from the flat uniform -distribution between :math:`[0, 2\pi)` (fun fact: there is a measure implicit in -this kind of sampling too! It just has a constant value, because each point is -equally likely to be sampled). - -""" - -import pennylane as qml -import numpy as np -import matplotlib.pyplot as plt - -# set the random seed -np.random.seed(42) - -# Use the mixed state simulator to save some steps in plotting later -dev = qml.device('default.mixed', wires=1) - -@qml.qnode(dev) -def not_a_haar_random_unitary(): - # Sample all parameters from their flat uniform distribution - phi, theta, omega = 2 * np.pi * np.random.uniform(size=3) - qml.Rot(phi, theta, omega, wires=0) - return qml.state() - -num_samples = 2021 - -not_haar_samples = [not_a_haar_random_unitary() for _ in range(num_samples)] - -###################################################################### -# In order to plot these on the Bloch sphere, we'll need to do one more -# step, and convert the quantum states into Bloch vectors. -# - -X = np.array([[0, 1], [1, 0]]) -Y = np.array([[0, -1j], [1j, 0]]) -Z = np.array([[1, 0], [0, -1]]) - -# Used the mixed state simulator so we could have the density matrix for this part! -def convert_to_bloch_vector(rho): - """Convert a density matrix to a Bloch vector.""" - ax = np.trace(np.dot(rho, X)).real - ay = np.trace(np.dot(rho, Y)).real - az = np.trace(np.dot(rho, Z)).real - return [ax, ay, az] - -not_haar_bloch_vectors = np.array([convert_to_bloch_vector(s) for s in not_haar_samples]) - -###################################################################### -# With this done, let's find out where our "uniformly random" states ended up: - -def plot_bloch_sphere(bloch_vectors): - """ Helper function to plot vectors on a sphere.""" - fig = plt.figure(figsize=(6, 6)) - ax = fig.add_subplot(111, projection='3d') - fig.subplots_adjust(left=0, right=1, bottom=0, top=1) - - ax.grid(False) - ax.set_axis_off() - ax.view_init(30, 45) - - # Draw the axes (source: https://github.com/matplotlib/matplotlib/issues/13575) - x, y, z = np.array([[-1.5,0,0], [0,-1.5,0], [0,0,-1.5]]) - u, v, w = np.array([[3,0,0], [0,3,0], [0,0,3]]) - ax.quiver(x, y, z, u, v, w, arrow_length_ratio=0.05, color="black", linewidth=0.5) - - ax.text(0, 0, 1.7, r"|0⟩", color="black", fontsize=16) - ax.text(0, 0, -1.9, r"|1⟩", color="black", fontsize=16) - ax.text(1.9, 0, 0, r"|+⟩", color="black", fontsize=16) - ax.text(-1.7, 0, 0, r"|–⟩", color="black", fontsize=16) - ax.text(0, 1.7, 0, r"|i+⟩", color="black", fontsize=16) - ax.text(0,-1.9, 0, r"|i–⟩", color="black", fontsize=16) - - ax.scatter( - bloch_vectors[:,0], bloch_vectors[:,1], bloch_vectors[:, 2], c='#e29d9e', alpha=0.3 - ) - plt.show() - -plot_bloch_sphere(not_haar_bloch_vectors) - -###################################################################### -# You can see from this plot that even though our parameters were sampled from a -# uniform distribution, there is a noticeable amount of clustering around the poles -# of the sphere. Despite the input parameters being uniform, the output is very -# much *not* uniform. Just like the regular sphere, the measure is larger near -# the equator, and if we just sample uniformly, we won't end up populating that -# area as much. To take that into account we will need to sample from the proper -# Haar measure, and weight the different parameters appropriately. -# -# For a single qubit, the Haar measure looks much like the case of a sphere, -# minus the radial component. Intuitively, all qubit state vectors have length -# 1, so it makes sense that this wouldn't play a role here. The parameter that -# we will have to weight differently is :math:`\theta,` and in fact the -# adjustment in measure is identical to that we had to do with the polar axis of -# the sphere, i.e., :math:`\sin \theta.` In order to sample the :math:`\theta` -# uniformly at random in this context, we must sample from the distribution -# :math:`\hbox{Pr}(\theta) = \sin \theta.` We can accomplish this by setting up -# a custom probability distribution with -# `rv_continuous `__ -# in ``scipy``. - -from scipy.stats import rv_continuous - -class sin_prob_dist(rv_continuous): - def _pdf(self, theta): - # The 0.5 is so that the distribution is normalized - return 0.5 * np.sin(theta) - -# Samples of theta should be drawn from between 0 and pi -sin_sampler = sin_prob_dist(a=0, b=np.pi) - -@qml.qnode(dev) -def haar_random_unitary(): - phi, omega = 2 * np.pi * np.random.uniform(size=2) # Sample phi and omega as normal - theta = sin_sampler.rvs(size=1)[0] # Sample theta from our new distribution - qml.Rot(phi, theta, omega, wires=0) - return qml.state() - -haar_samples = [haar_random_unitary() for _ in range(num_samples)] -haar_bloch_vectors = np.array([convert_to_bloch_vector(s) for s in haar_samples]) - -plot_bloch_sphere(haar_bloch_vectors) - -###################################################################### -# We see that when we use the correct measure, our qubit states are now -# much better distributed over the sphere. Putting this information together, -# we can now write the explicit form for the single-qubit Haar measure: -# -# .. math:: -# -# d\mu_2 = \sin \theta d\theta \cdot d\omega \cdot d\phi. -# -# Show me more math! -# ~~~~~~~~~~~~~~~~~~ -# -# While we can easily visualize the single-qubit case, this is no longer -# possible when we increase the number of qubits. Regardless, we can still -# obtain a mathematical expression for the Haar measure in arbitrary -# dimensions. In the previous section, we expressed the Haar measure in terms of -# a set of parameters that can be used to specify the unitary group -# :math:`U(2).` Such a parametrization is not unique, and in fact there are -# multiple ways to *factorize*, or decompose an :math:`N`-dimensional unitary -# operation into a set of parameters. -# -# Many of these parametrizations come to us from the study of photonics. Here, -# arbitrary operations are broken down into elementary operations involving only -# a few parameters which correspond directly to parameters of the physical -# apparatus used to implement them (beamsplitters and phase shifts). Rather than -# qubits, such operations act on modes, or *qumodes*. They are expressed as -# elements of the :math:`N`-dimensional `special unitary group -# `__. This group, written -# as :math:`SU(N),` is the continuous group consisting of all :math:`N \times N` -# unitary operations with determinant 1 (essentially like :math:`U(N),` minus -# a potential global phase). -# -# -# .. note:: -# -# Elements of :math:`SU(N)` and :math:`U(N)` can still be considered as -# multi-qubit operations in the cases where :math:`N` is a power of 2, but -# they must be translated from continuous-variable operations into qubit -# operations. (In PennyLane, this can be done by feeding the unitaries to -# the :class:`~.pennylane.QubitUnitary` operation directly. Alternatively, -# one can use *quantum compilation* to express the operations as a sequence -# of elementary gates such as Pauli rotations and CNOTs.) -# -# .. admonition:: Tip -# -# If you haven't had many opportunities to work in terms of qumodes, the -# `Strawberry Fields documentation -# `__ is a -# good starting point. -# -# For example, we saw already above that for :math:`N=2,` we can write -# -# .. math:: -# -# U(\phi, \theta, \omega) = \begin{pmatrix} e^{-i(\phi + \omega)/2} -# \cos(\theta/2) & -e^{i(\phi - \omega)/2} \sin(\theta/2) -# \\ e^{-i(\phi - \omega)/2} \sin(\theta/2) & e^{i(\phi + -# \omega)/2} \cos(\theta/2) \end{pmatrix}. -# -# -# This unitary can be factorized as follows: -# -# .. math:: -# -# U(\phi, \theta, \omega) = -# \begin{pmatrix} -# e^{-i\omega/2} & 0 \\ 0 & e^{i\omega/2} -# \end{pmatrix} -# \begin{pmatrix} -# \cos(\theta/2) & -\sin(\theta/2) \\ \sin(\theta/2) & \cos(\theta/2) -# \end{pmatrix} -# \begin{pmatrix} -# e^{-i\phi/2} & 0 \\ 0 & e^{i\phi/2} -# \end{pmatrix} -# -# The middle operation is a beamsplitter; the other two operations are phase -# shifts. We saw earlier that for :math:`N=2,` :math:`d\mu_2 = \sin\theta -# d\theta d\omega d\phi`---note how the parameter in the beamsplitter -# contributes to the measure in a different way than those of the phase -# shifts. As mentioned above, for larger values of :math:`N` there are multiple -# ways to decompose the unitary. Such decompositions rewrite elements in -# :math:`SU(N)` acting on :math:`N` modes as a sequence of operations acting -# only on 2 modes, :math:`SU(2),` and single-mode phase shifts. Shown below are -# three examples [#deGuise2018]_, [#Clements2016]_, [#Reck1994]_: -# -# .. figure:: /_static/demonstration_assets/haar_measure/unitaries.png -# :align: center -# :width: 95% -# -# -# In these graphics, every wire is a different mode. Every box represents an -# operation on one or more modes, and the number in the box indicates the number -# of parameters. The boxes containing a ``1`` are simply phase shifts on -# individual modes. The blocks containing a ``3`` are :math:`SU(2)` transforms -# with 3 parameters, such as the :math:`U(\phi, \theta, \omega)` above. Those -# containing a ``2`` are :math:`SU(2)` transforms on pairs of modes with 2 -# parameters, similar to the 3-parameter ones but with :math:`\omega = \phi.` -# -# Although the decompositions all produce the same set of operations, their -# structure and parametrization may have consequences in practice. The first [#deGuise2018]_ -# has a particularly convenient form that leads to a recursive definition -# of the Haar measure. The decomposition is formulated recursively such that an -# :math:`SU(N)` operation can be implemented by sandwiching an :math:`SU(2)` -# transformation between two :math:`SU(N-1)` transformations, like so: -# -# | -# -# .. figure:: /_static/demonstration_assets/haar_measure/sun.svg -# :align: center -# :width: 80% -# -# | -# -# The Haar measure is then constructed recursively as a product of 3 -# terms. The first term depends on the parameters in the first :math:`SU(N-1)` -# transformation; the second depends on the parameters in the lone :math:`SU(2)` -# transformation; and the third term depends on the parameters in the other -# :math:`SU(N-1)` transformation. -# -# :math:`SU(2)` is the "base case" of the recursion—we simply have the Haar measure -# as expressed above. -# -# | -# -# .. figure:: /_static/demonstration_assets/haar_measure/su2_haar.svg -# :align: center -# :width: 25% -# -# | -# -# Moving on up, we can write elements of :math:`SU(3)` as a sequence of three -# :math:`SU(2)` transformations. The Haar measure :math:`d\mu_3` then consists -# of two copies of :math:`d\mu_2,` with an extra term in between to take into -# account the middle transformation. -# -# | -# -# .. figure:: /_static/demonstration_assets/haar_measure/su3_haar.svg -# :align: center -# :width: 80% -# -# | -# -# For :math:`SU(4)` and upwards, the form changes slightly, but still follows -# the pattern of two copies of :math:`d\mu_{N-1}` with a term in between. -# -# | -# -# .. figure:: /_static/demonstration_assets/haar_measure/su4_premerge.svg -# :align: center -# :width: 90% -# -# | -# -# For larger systems, however, the recursive composition allows for some of the -# :math:`SU(2)` transformations on the lower modes to be grouped. We can take -# advantage of this and aggregate some of the parameters: -# -# | -# -# .. figure:: /_static/demonstration_assets/haar_measure/su4_triangle_merge.svg -# :align: center -# :width: 100% -# -# | -# -# This leads to one copy of :math:`d\mu_{N-1},` which we'll denote as -# :math:`d\mu_{N-1}^\prime,` containing only a portion of the full set of terms -# (as detailed in [#deGuise2018]_, this is called a *coset measure*). -# -# | -# -# .. figure:: /_static/demonstration_assets/haar_measure/su4_haar.svg -# :align: center -# :width: 100% -# -# | -# -# Putting everything together, we have that -# -# .. math:: -# -# d\mu_N = d\mu_{N-1}^\prime \times \sin \theta_{N-1} -# \sin^{2(N-2)}\left(\frac{\theta_{N-1}}{2}\right) d\theta_{N-1} d\omega_{N-1} \times d\mu_{N-1} -# -# The middle portion depends on the value of :math:`N,` and the parameters -# :math:`\theta_{N-1}` and :math:`\omega_{N-1}` contained in the :math:`(N-1)`'th -# :math:`SU(N)` transformation. This is thus a convenient, systematic way to -# construct the :math:`N`-dimensional Haar measure for the unitary group. As a -# final note, even though unitaries can be parametrized in different ways, the -# underlying Haar measure is *unique*. This is a consequence of it being an -# invariant measure, as will be shown later. -# -# Haar-random matrices from the :math:`QR` decomposition -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# -# Nice-looking math aside, sometimes you just need to generate a large number of -# high-dimensional Haar-random matrices. It would be very cumbersome to sample -# and keep track of the distributions of so many parameters; furthermore, the -# measure above requires you to parametrize your operations in a fixed way. -# There is a much quicker way to perform the sampling by taking a (slightly -# modified) `QR decomposition -# `__ of complex-valued -# matrices. This algorithm is detailed in [#Mezzadri2006]_, and consists of the -# following steps: -# -# 1. Generate an :math:`N \times N` matrix :math:`Z` with complex numbers :math:`a+bi` -# where both :math:`a` and :math:`b` are normally distributed with mean 0 and variance 1 -# (this is sampling from the distribution known as the *Ginibre ensemble*). -# 2. Compute a QR decomposition :math:`Z = QR.` -# 3. Compute the diagonal matrix :math:`\Lambda = \hbox{diag}(R_{ii}/|R_{ii}|).` -# 4. Compute :math:`Q^\prime = Q \Lambda,` which will be Haar-random. -# -# - -from numpy.linalg import qr - -def qr_haar(N): - """Generate a Haar-random matrix using the QR decomposition.""" - # Step 1 - A, B = np.random.normal(size=(N, N)), np.random.normal(size=(N, N)) - Z = A + 1j * B - - # Step 2 - Q, R = qr(Z) - - # Step 3 - Lambda = np.diag([R[i, i] / np.abs(R[i, i]) for i in range(N)]) - - # Step 4 - return np.dot(Q, Lambda) - -###################################################################### -# Let's check that this method actually generates Haar-random unitaries -# by trying it out for :math:`N=2` and plotting on the Bloch sphere. -# - -@qml.qnode(dev) -def qr_haar_random_unitary(): - qml.QubitUnitary(qr_haar(2), wires=0) - return qml.state() - -qr_haar_samples = [qr_haar_random_unitary() for _ in range(num_samples)] -qr_haar_bloch_vectors = np.array([convert_to_bloch_vector(s) for s in qr_haar_samples]) -plot_bloch_sphere(qr_haar_bloch_vectors) - -###################################################################### -# As expected, we find our qubit states are distributed uniformly over the -# sphere. This particular method is what's implemented in packages like -# ``scipy``'s `unitary_group -# `__ -# function. -# -# Now, it's clear that this method works, but it is also important to -# understand *why* it works. Step 1 is fairly straightforward—the base of our -# samples is a matrix full of complex values chosen from a typical -# distribution. This isn't enough by itself, since unitary matrices also -# have constraints—their rows and columns must be orthonormal. -# These constraints are where step 2 comes in—the outcome of a generic -# QR decomposition consists of an *orthonormal* matrix :math:`Q,` and and upper -# triangular matrix :math:`R.` Since our original matrix was complex-valued, we end -# up with a :math:`Q` that is in fact already unitary. But why not stop there? Why -# do we then perform steps 3 and 4? -# -# Steps 3 and 4 are needed because, while the QR decomposition yields a unitary, -# it is not a unitary that is properly Haar-random. In [#Mezzadri2006]_, it is -# explained that a uniform distribution over unitary matrices should also yield -# a uniform distribution over the *eigenvalues* of those matrices, i.e., every -# eigenvalue should be equally likely. Just using the QR decomposition out of -# the box produces an *uneven* distribution of eigenvalues of the unitaries! -# This discrepancy stems from the fact that the QR decomposition is not unique. -# We can take any unitary diagonal matrix :math:`\Lambda,` and re-express the decomposition -# as :math:`QR = Q\Lambda \Lambda^\dagger R = Q^\prime R^\prime.` Step 3 removes this -# redundancy by fixing a :math:`\Lambda` that depends on :math:`R,` leading to a unique -# value of :math:`Q^\prime = Q \Lambda,` and a uniform distribution of eigenvalues. -# -# .. admonition:: Try it! -# -# Use the ``qr_haar`` function above to generate random unitaries and construct -# a distribution of their eigenvalues. Then, comment out the lines for steps 3 and -# 4 and do the same—you'll find that the distribution is no longer uniform. -# Check out reference [#Mezzadri2006]_ for additional details and examples. - -###################################################################### -# Fun (and not-so-fun) facts -# -------------------------- -# -# We've now learned what the Haar measure is, and both an analytical and -# numerical means of sampling quantum states and unitary operations uniformly at -# random. The Haar measure also has many neat properties that play a role in -# quantum computing. -# -# Invariance of the Haar measure -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# -# Earlier, we showed that the Haar measure is used when integrating functions over -# the unitary group: -# -# .. math:: -# -# \int_{V \in U(N)} f(V) d\mu_N(V). -# -# One of the defining features of the Haar measure is that it is both left and -# right *invariant* under unitary transformations. That is, -# -# .. math:: -# -# \int_{V \in U(N)} f(\color{red}{W}V) d\mu_N(V) = \int_{V \in U(N)} f(V\color{red}{W}) d\mu_N(V) = \int_{V \in U(N)} f(V) d\mu_N(V). -# -# This holds true for *any* other :math:`N\times N` unitary :math:`W!` A -# consequence of such invariance is that if :math:`V` is Haar-random, then so is -# :math:`V^T,` :math:`V^\dagger,` and any product of another unitary matrix and -# :math:`V` (where the product may be taken on either side). -# -# Another consequence of this invariance has to do with the structure of the entries -# themselves: they must all come from the same distribution. This is because the -# measure remains invariant under permutations, since permutations are unitary--- -# the whole thing still has to be Haar random no matter how the entries are ordered, -# so all distributions must be the same. The specific distribution is complex -# numbers :math:`a+bi` where both :math:`a` and :math:`b` has mean 0 and variance -# :math:`1/N` [#Meckes2014]_ (so, much like Ginibre ensemble we used in the QR decomposition -# above, but with a different variance and constraints due to orthonormality). -# -# Concentration of measure -# ~~~~~~~~~~~~~~~~~~~~~~~~ -# -# An unfortunate (although interesting) property of the Haar measure is that it -# suffers from the phenomenon of `concentration of measure -# `__. Most of the -# "stuff" in the space concentrates around a certain area, and this gets worse -# as the size of the system increases. You can see the beginnings of by looking -# at the sphere. For the 3-dimensional sphere, we saw graphically how there is -# concentration around the equator, and how the measure takes that into account -# with the additional factor of :math:`\sin \theta.` This property becomes -# increasingly prominent for `higher-dimensional spheres -# `__. -# -# .. important:: -# -# The concentration described here is not referring to what we witnessed -# earlier on, when we sampled quantum states (points on the Bloch sphere) -# incorrectly and found that they clustered around the poles. However, that -# is not unrelated. Concentration of measure refers to where the measure -# itself is concentrated, and which parts of the space should be more heavily -# weighted. For the case of the sphere, it is the equatorial area, and when -# we didn't sample properly and take that concentration into account, we -# obtained an uneven distribution. -# -# Let's consider an :math:`N`-dimensional unit sphere. Points on the sphere, or -# vectors in this space, are parametrized by :math:`N-1` real coordinates. -# Suppose we have some function :math:`f` that maps points on that sphere to -# real numbers. Sample a point :math:`x` on that sphere from the uniform -# measure, and compute the value of :math:`f(x).` How close do you think the -# result will be to the mean value of the function, :math:`E[f],` over the -# entire sphere? -# -# A result called `Levy's lemma -# `__ -# [#Gerken2013]_, [#Hayden2006]_ expresses how likely it is that :math:`f(x)` is a specific -# distance away from the mean. It states that, for an :math:`x` selected -# uniformly at random, the probability that :math:`f(x)` deviates from -# :math:`E[f]` by some amount :math:`\epsilon` is bounded by: -# -# .. math:: -# -# \hbox{Pr}(|f(x) - E[f]| \ge \epsilon) \leq 2 \exp\left[-\frac{N\epsilon^2}{9\pi^3 \eta^2}\right]. -# -# A constraint on the function :math:`f` is that it must be `Lipschitz -# continuous `__, where -# :math:`\eta` is the *Lipschitz constant* of the function. The important aspect -# here is the likelihood of deviating significantly from the mean by an amount -# :math:`\epsilon` decreases exponentially with :math:`\epsilon.` Furthermore, -# increasing the dimension :math:`N` also makes the deviation exponentially less -# likely. -# -# Now, this result seems unrelated to quantum states—it concerns higher- -# dimensional spheres. However, recall that a quantum state vector is a complex -# vector whose squared values sum to 1, similar to vectors on a sphere. If you -# "unroll" a quantum state vector of dimension :math:`N = 2^n` by stacking its -# real and complex parts, you end with a vector of length :math:`2 \cdot 2^{n}` -# which ends up behaving just like a unit vector on the sphere in this -# dimension. Given that measure concentrates on spheres, and quantum state -# vectors can be converted to vectors on spheres, functions on random quantum -# states will also demonstrate concentration. -# -# This is bad news! To do useful things in quantum computing, we need a lot of -# qubits. But the more qubits we have, the more our randomly sampled states will -# look the same (specifically, random states will concentrate around the -# maximally entangled state [#Hayden2006]_). This has important consequences for -# near-term algorithms (as detailed in the next section), and any algorithm that -# involves uniform sampling of quantum states and operations. -# -# Haar measure and barren plateaus -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# -# Suppose you are venturing out to solve a new problem using an algorithm such -# as the :doc:`variational quantum eigensolver `. A -# critical component of such methods is the choice of :doc:`variational ansatz -# `. Having now learned a bit about the properties of -# the Haar measure, you may think it would make sense to use this for the -# parametrization. Variational ansaetze are, after all, parametrized quantum -# circuits, so why not choose an ansatz that corresponds directly to a -# parametrization for Haar-random unitaries? The initial parameter selection -# will give you a state in the Hilbert space uniformly at random. Then, since -# this ansatz spans the entire Hilbert space, you're guaranteed to be able to -# represent the target ground state with your ansatz, and it should be able to -# find it with no issue ... right? -# -# Unfortunately, while such an ansatz is extremely *expressive* (i.e., it is -# capable of representing any possible state), these ansaetze actually suffer -# the most from the barren plateau problem [#McClean2018]_, [#Holmes2021]_. -# :doc:`Barren plateaus ` are regions in the -# cost landscape of a parametrized circuit where both the gradient and its -# variance approach 0, leading the optimizer to get stuck in a local minimum. -# This was explored recently in the work of [#Holmes2021]_, wherein closeness to -# the Haar measure was actually used as a metric for expressivity. The closer -# things are to the Haar measure, the more expressive they are, but they are -# also more prone to exhibiting barren plateaus. -# -# -# .. figure:: /_static/demonstration_assets/haar_measure/holmes-costlandscapes.png -# :align: center -# :width: 50% -# -# Image source: [#Holmes2021]_. A highly expressive ansatz that can access -# much of the space of possible unitaries (i.e., an ansatz capable of -# producing unitaries in something close to a Haar-random manner) is very -# likely to have flat cost landscapes and suffer from the barren plateau -# problem. -# -# It turns out that the types of ansaetze know as *hardware-efficient ansaetze* -# also suffer from this problem if they are "random enough" (this notion will be -# formalized in a future demo). It was shown in [#McClean2018]_ that this is a -# consequence of the concentration of measure phenomenon described above. The -# values of gradients and variances can be computed for classes of circuits on -# average by integrating with respect to the Haar measure, and it is shown that -# these values decrease exponentially in the number of qubits, and thus huge -# swaths of the cost landscape are simply and fundamentally flat. -# -# Conclusion -# ---------- -# -# The Haar measure plays an important role in quantum computing—anywhere -# you might be dealing with sampling random circuits, or averaging over -# all possible unitary operations, you'll want to do so with respect -# to the Haar measure. -# -# There are two important aspects of this that we have yet to touch upon, -# however. The first is whether it is efficient to sample from the Haar measure—given -# that the number of parameters to keep track of is exponential in the -# number of qubits, certainly not. But a more interesting question is do we -# *need* to always sample from the full Haar measure? The answer to this is -# "no" in a very interesting way. Depending on the task at hand, you may be able -# to take a shortcut using something called a *unitary design*. In an upcoming -# demo, we will explore the amazing world of unitary designs and their -# applications! -# -# References -# ---------- -# -# .. [#NandC2000] -# -# M. A. Nielsen, and I. L. Chuang (2000) "Quantum Computation and Quantum Information", -# Cambridge University Press. -# -# .. [#deGuise2018] -# -# H. de Guise, O. Di Matteo, and L. L. Sánchez-Soto. (2018) "Simple factorization -# of unitary transformations", `Phys. Rev. A 97 022328 -# `__. -# (`arXiv `__) -# -# .. [#Clements2016] -# -# W. R. Clements, P. C. Humphreys, B. J. Metcalf, W. S. Kolthammer, and -# I. A. Walmsley (2016) “Optimal design for universal multiport -# interferometers”, \ `Optica 3, 1460–1465 -# `__. -# (`arXiv `__) -# -# .. [#Reck1994] -# -# M. Reck, A. Zeilinger, H. J. Bernstein, and P. Bertani (1994) “Experimental -# realization of any discrete unitary operator”, `Phys. Rev. Lett.73, 58–61 -# `__. -# -# .. [#Mezzadri2006] -# -# F. Mezzadri (2006) "How to generate random matrices from the classical compact groups". -# (`arXiv `__) -# -# .. [#Meckes2014] -# -# E. Meckes (2019) `"The Random Matrix Theory of the Classical Compact Groups" -# `_, Cambridge University Press. -# -# .. [#Gerken2013] -# -# M. Gerken (2013) "Measure concentration: Levy's Lemma" -# (`lecture notes `__). -# -# -# .. [#Hayden2006] -# -# P. Hayden, D. W. Leung, and A. Winter (2006) "Aspects of generic -# entanglement", `Comm. Math. Phys. Vol. 265, No. 1, pp. 95-117 -# `__. -# (`arXiv `__) -# -# .. [#McClean2018] -# -# J. R. McClean, S. Boixo, V. N. Smelyanskiy, R. Babbush, and H. Neven -# (2018) "Barren plateaus in quantum neural network training -# landscapes", `Nature Communications, 9(1) -# `__. -# (`arXiv `__) -# -# .. [#Holmes2021] -# -# Z. Holmes, K. Sharma, M. Cerezo, and P. J. Coles (2021) "Connecting ansatz -# expressibility to gradient magnitudes and barren plateaus". (`arXiv -# `__) +r""".. role:: html(raw) + :format: html + +Understanding the Haar measure +============================== + +.. meta:: + :property="og:description": Learn all about the Haar measure and how to randomly sample quantum states. + + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/spherical_int_dtheta.png + +.. related:: + + tutorial_unitary_designs Unitary designs + quantum_volume Quantum volume + qsim_beyond_classical Beyond classical computing with qsim + tutorial_barren_plateaus Barren plateaus in quantum neural networks + + +*Author: Olivia Di Matteo — Posted: 22 March 2021. Last updated: 22 March 2021.* + +If you've ever dug into the literature about random quantum circuits, +variational ansatz structure, or anything related to the structure and +properties of unitary operations, you've likely come across a statement like the +following: "Assume that :math:`U` is sampled uniformly at random from the Haar +measure". In this demo, we're going to unravel this cryptic statement and take +an in-depth look at what it means. You'll gain an understanding of the general +concept of a *measure*, the Haar measure and its special properties, and you'll +learn how to sample from it using tools available in PennyLane and other +scientific computing frameworks. By the end of this demo, you'll be able to +include that important statement in your own work with confidence! + +.. note:: + + To get the most out of this demo, it is helpful if you are familiar with + `integration of multi-dimensional functions + `__, the `Bloch sphere + `__, and the conceptual ideas + behind `decompositions + `__ and factorizations of + unitary matrices (see, e.g., 4.5.1 and 4.5.2 of [#NandC2000]_). + +Measure +------- + +`Measure theory `__ is a +branch of mathematics that studies things that are measurable—think length, +area, or volume, but generalized to mathematical spaces and even higher +dimensions. Loosely, the measure tells you about how "stuff" is distributed and +concentrated in a mathematical set or space. An intuitive way to understand +the measure is to think about a sphere. An arbitrary point on a sphere can be +parametrized by three numbers—depending on what you're doing, you may use +Cartesian coordinates :math:`(x, y, z),` or it may be more convenient to use +spherical coordinates :math:`(\rho, \phi, \theta).` + +Suppose you wanted to compute the volume of a solid sphere with radius +:math:`r.` This can be done by integrating over the three coordinates +:math:`\rho, \phi,` and :math:`\theta.` Your first thought here may be to simply +integrate each parameter over its full range, like so: + +.. math:: + + V = \int_0^{r} \int_0^{2\pi} \int_0^{\pi} d\rho~ d\phi~ d\theta = 2\pi^2 r + +But, we know that the volume of a sphere of radius :math:`r` is +:math:`\frac{4}{3}\pi r^3,` so what we got from this integral is clearly wrong! +Taking the integral naively like this doesn't take into account the structure of +the sphere with respect to the parameters. For example, consider +two small, infinitesimal elements of area with the same difference in +:math:`\theta` and :math:`\phi,` but at different values of :math:`\theta:` + +.. figure:: /_static/demonstration_assets/haar_measure/spherical_int_dtheta.png + :align: center + :width: 50% + + +Even though the differences :math:`d\theta` and :math:`d\phi` themselves are the +same, there is way more "stuff" near the equator of the sphere than there is +near the poles. We must take into account the value of :math:`\theta` when +computing the integral! Specifically, we multiply by the function +:math:`\sin\theta`---the properties of the :math:`\sin` function mean that the +most weight will occur around the equator where :math:`\theta=\pi/2,` and the +least weight near the poles where :math:`\theta=0` and :math:`\theta=\pi.` + +Similar care must be taken for :math:`\rho.` The contribution to volume of +parts of the sphere with a large :math:`\rho` is far more than for a small +:math:`\rho`---we should expect the contribution to be proportional to +:math:`\rho^2,` given that the surface area of a sphere of radius :math:`r` is +:math:`4\pi r^2.` + +On the other hand, for a fixed :math:`\rho` and :math:`\theta,` the length of +the :math:`d\phi` is the same all around the circle. If put all these facts +together, we find that the actual expression for the integral should look like +this: + +.. math:: + + V = \int_0^r \int_0^{2\pi} \int_0^{\pi} \rho^2 \sin \theta~ d\rho~ d\phi~ + d\theta = \frac{4}{3}\pi r^3 + +These extra terms that we had to add to the integral, :math:`\rho^2 \sin +\theta`, constitute the *measure*. The measure weights portions of the sphere +differently depending on where they are in the space. While we need to know the +measure to properly integrate over the sphere, knowledge of the measure also +gives us the means to perform another important task, that of sampling points in +the space uniformly at random. We can't simply sample each parameter from the +uniform distribution over its domain—as we experienced already, this doesn't +take into account how the sphere is spread out over space. The measure describes +the distribution of each parameter and gives a recipe for sampling them in order +to obtain something properly uniform. + +The Haar measure +---------------- + +Operations in quantum computing are described by unitary matrices. +Unitary matrices, like points on a sphere, can be expressed in terms of a fixed +set of coordinates, or parameters. For example, the most general single-qubit rotation +implemented in PennyLane (:class:`~.pennylane.Rot`) is expressed in terms of three +parameters like so, + +.. math:: + + U(\phi, \theta, \omega) = \begin{pmatrix} e^{-i(\phi + \omega)/2} + \cos(\theta/2) & -e^{i(\phi - \omega)/2} \sin(\theta/2) + \\ e^{-i(\phi - \omega)/2} \sin(\theta/2) & e^{i(\phi + + \omega)/2} \cos(\theta/2) \end{pmatrix}. + +For every dimension :math:`N,` the unitary matrices of size :math:`N \times N` +constitute the *unitary group* :math:`U(N).` We can perform operations on +elements of this group, such as apply functions to them, integrate over them, or +sample uniformly over them, just as we can do to points on a sphere. When we do +such tasks with respect to the sphere, we have to add the measure in order to +properly weight the different regions of space. The *Haar measure* provides the +analogous terms we need for working with the unitary group. + +For an :math:`N`-dimensional system, the Haar measure, often denoted by +:math:`\mu_N,` tells us how to weight the elements of :math:`U(N).` For +example, suppose :math:`f` is a function that acts on elements of :math:`U(N),` +and we would like to take its integral over the group. We must write this +integral with respect to the Haar measure, like so: + +.. math:: + + \int_{V \in U(N)} f(V) d\mu_N(V). + +As with the measure term of the sphere, :math:`d\mu_N` itself can be broken down +into components depending on individual parameters. While the Haar +measure can be defined for every dimension :math:`N,` the mathematical form gets +quite hairy for larger dimensions—in general, an :math:`N`-dimensional unitary +requires at least :math:`N^2 - 1` parameters, which is a lot to keep track of! +Therefore we'll start with the case of a single qubit :math:`(N=2),` then show +how things generalize. + +Single-qubit Haar measure +~~~~~~~~~~~~~~~~~~~~~~~~~ + +The single-qubit case provides a particularly nice entry point because we can +continue our comparison to spheres by visualizing single-qubit states on the +Bloch sphere. As expressed above, the measure provides a recipe for sampling +elements of the unitary group in a properly uniform manner, given the structure +of the group. One useful consequence of this is that it provides a method to +sample quantum *states* uniformly at random—we simply generate Haar-random +unitaries, and apply them to a fixed basis state such as :math:`\vert 0\rangle.` + +We'll see how this works in good time. First, we'll take a look at what happens +when we ignore the measure and do things *wrong*. Suppose we sample quantum +states by applying unitaries obtained by the parametrization above, but sample +the angles :math:`\omega, \phi,` and :math:`\theta` from the flat uniform +distribution between :math:`[0, 2\pi)` (fun fact: there is a measure implicit in +this kind of sampling too! It just has a constant value, because each point is +equally likely to be sampled). + +""" + +import pennylane as qml +import numpy as np +import matplotlib.pyplot as plt + +# set the random seed +np.random.seed(42) + +# Use the mixed state simulator to save some steps in plotting later +dev = qml.device('default.mixed', wires=1) + +@qml.qnode(dev) +def not_a_haar_random_unitary(): + # Sample all parameters from their flat uniform distribution + phi, theta, omega = 2 * np.pi * np.random.uniform(size=3) + qml.Rot(phi, theta, omega, wires=0) + return qml.state() + +num_samples = 2021 + +not_haar_samples = [not_a_haar_random_unitary() for _ in range(num_samples)] + +###################################################################### +# In order to plot these on the Bloch sphere, we'll need to do one more +# step, and convert the quantum states into Bloch vectors. +# + +X = np.array([[0, 1], [1, 0]]) +Y = np.array([[0, -1j], [1j, 0]]) +Z = np.array([[1, 0], [0, -1]]) + +# Used the mixed state simulator so we could have the density matrix for this part! +def convert_to_bloch_vector(rho): + """Convert a density matrix to a Bloch vector.""" + ax = np.trace(np.dot(rho, X)).real + ay = np.trace(np.dot(rho, Y)).real + az = np.trace(np.dot(rho, Z)).real + return [ax, ay, az] + +not_haar_bloch_vectors = np.array([convert_to_bloch_vector(s) for s in not_haar_samples]) + +###################################################################### +# With this done, let's find out where our "uniformly random" states ended up: + +def plot_bloch_sphere(bloch_vectors): + """ Helper function to plot vectors on a sphere.""" + fig = plt.figure(figsize=(6, 6)) + ax = fig.add_subplot(111, projection='3d') + fig.subplots_adjust(left=0, right=1, bottom=0, top=1) + + ax.grid(False) + ax.set_axis_off() + ax.view_init(30, 45) + + # Draw the axes (source: https://github.com/matplotlib/matplotlib/issues/13575) + x, y, z = np.array([[-1.5,0,0], [0,-1.5,0], [0,0,-1.5]]) + u, v, w = np.array([[3,0,0], [0,3,0], [0,0,3]]) + ax.quiver(x, y, z, u, v, w, arrow_length_ratio=0.05, color="black", linewidth=0.5) + + ax.text(0, 0, 1.7, r"|0⟩", color="black", fontsize=16) + ax.text(0, 0, -1.9, r"|1⟩", color="black", fontsize=16) + ax.text(1.9, 0, 0, r"|+⟩", color="black", fontsize=16) + ax.text(-1.7, 0, 0, r"|–⟩", color="black", fontsize=16) + ax.text(0, 1.7, 0, r"|i+⟩", color="black", fontsize=16) + ax.text(0,-1.9, 0, r"|i–⟩", color="black", fontsize=16) + + ax.scatter( + bloch_vectors[:,0], bloch_vectors[:,1], bloch_vectors[:, 2], c='#e29d9e', alpha=0.3 + ) + plt.show() + +plot_bloch_sphere(not_haar_bloch_vectors) + +###################################################################### +# You can see from this plot that even though our parameters were sampled from a +# uniform distribution, there is a noticeable amount of clustering around the poles +# of the sphere. Despite the input parameters being uniform, the output is very +# much *not* uniform. Just like the regular sphere, the measure is larger near +# the equator, and if we just sample uniformly, we won't end up populating that +# area as much. To take that into account we will need to sample from the proper +# Haar measure, and weight the different parameters appropriately. +# +# For a single qubit, the Haar measure looks much like the case of a sphere, +# minus the radial component. Intuitively, all qubit state vectors have length +# 1, so it makes sense that this wouldn't play a role here. The parameter that +# we will have to weight differently is :math:`\theta,` and in fact the +# adjustment in measure is identical to that we had to do with the polar axis of +# the sphere, i.e., :math:`\sin \theta.` In order to sample the :math:`\theta` +# uniformly at random in this context, we must sample from the distribution +# :math:`\hbox{Pr}(\theta) = \sin \theta.` We can accomplish this by setting up +# a custom probability distribution with +# `rv_continuous `__ +# in ``scipy``. + +from scipy.stats import rv_continuous + +class sin_prob_dist(rv_continuous): + def _pdf(self, theta): + # The 0.5 is so that the distribution is normalized + return 0.5 * np.sin(theta) + +# Samples of theta should be drawn from between 0 and pi +sin_sampler = sin_prob_dist(a=0, b=np.pi) + +@qml.qnode(dev) +def haar_random_unitary(): + phi, omega = 2 * np.pi * np.random.uniform(size=2) # Sample phi and omega as normal + theta = sin_sampler.rvs(size=1)[0] # Sample theta from our new distribution + qml.Rot(phi, theta, omega, wires=0) + return qml.state() + +haar_samples = [haar_random_unitary() for _ in range(num_samples)] +haar_bloch_vectors = np.array([convert_to_bloch_vector(s) for s in haar_samples]) + +plot_bloch_sphere(haar_bloch_vectors) + +###################################################################### +# We see that when we use the correct measure, our qubit states are now +# much better distributed over the sphere. Putting this information together, +# we can now write the explicit form for the single-qubit Haar measure: +# +# .. math:: +# +# d\mu_2 = \sin \theta d\theta \cdot d\omega \cdot d\phi. +# +# Show me more math! +# ~~~~~~~~~~~~~~~~~~ +# +# While we can easily visualize the single-qubit case, this is no longer +# possible when we increase the number of qubits. Regardless, we can still +# obtain a mathematical expression for the Haar measure in arbitrary +# dimensions. In the previous section, we expressed the Haar measure in terms of +# a set of parameters that can be used to specify the unitary group +# :math:`U(2).` Such a parametrization is not unique, and in fact there are +# multiple ways to *factorize*, or decompose an :math:`N`-dimensional unitary +# operation into a set of parameters. +# +# Many of these parametrizations come to us from the study of photonics. Here, +# arbitrary operations are broken down into elementary operations involving only +# a few parameters which correspond directly to parameters of the physical +# apparatus used to implement them (beamsplitters and phase shifts). Rather than +# qubits, such operations act on modes, or *qumodes*. They are expressed as +# elements of the :math:`N`-dimensional `special unitary group +# `__. This group, written +# as :math:`SU(N),` is the continuous group consisting of all :math:`N \times N` +# unitary operations with determinant 1 (essentially like :math:`U(N),` minus +# a potential global phase). +# +# +# .. note:: +# +# Elements of :math:`SU(N)` and :math:`U(N)` can still be considered as +# multi-qubit operations in the cases where :math:`N` is a power of 2, but +# they must be translated from continuous-variable operations into qubit +# operations. (In PennyLane, this can be done by feeding the unitaries to +# the :class:`~.pennylane.QubitUnitary` operation directly. Alternatively, +# one can use *quantum compilation* to express the operations as a sequence +# of elementary gates such as Pauli rotations and CNOTs.) +# +# .. admonition:: Tip +# +# If you haven't had many opportunities to work in terms of qumodes, the +# `Strawberry Fields documentation +# `__ is a +# good starting point. +# +# For example, we saw already above that for :math:`N=2,` we can write +# +# .. math:: +# +# U(\phi, \theta, \omega) = \begin{pmatrix} e^{-i(\phi + \omega)/2} +# \cos(\theta/2) & -e^{i(\phi - \omega)/2} \sin(\theta/2) +# \\ e^{-i(\phi - \omega)/2} \sin(\theta/2) & e^{i(\phi + +# \omega)/2} \cos(\theta/2) \end{pmatrix}. +# +# +# This unitary can be factorized as follows: +# +# .. math:: +# +# U(\phi, \theta, \omega) = +# \begin{pmatrix} +# e^{-i\omega/2} & 0 \\ 0 & e^{i\omega/2} +# \end{pmatrix} +# \begin{pmatrix} +# \cos(\theta/2) & -\sin(\theta/2) \\ \sin(\theta/2) & \cos(\theta/2) +# \end{pmatrix} +# \begin{pmatrix} +# e^{-i\phi/2} & 0 \\ 0 & e^{i\phi/2} +# \end{pmatrix} +# +# The middle operation is a beamsplitter; the other two operations are phase +# shifts. We saw earlier that for :math:`N=2,` :math:`d\mu_2 = \sin\theta +# d\theta d\omega d\phi`---note how the parameter in the beamsplitter +# contributes to the measure in a different way than those of the phase +# shifts. As mentioned above, for larger values of :math:`N` there are multiple +# ways to decompose the unitary. Such decompositions rewrite elements in +# :math:`SU(N)` acting on :math:`N` modes as a sequence of operations acting +# only on 2 modes, :math:`SU(2),` and single-mode phase shifts. Shown below are +# three examples [#deGuise2018]_, [#Clements2016]_, [#Reck1994]_: +# +# .. figure:: /_static/demonstration_assets/haar_measure/unitaries.png +# :align: center +# :width: 95% +# +# +# In these graphics, every wire is a different mode. Every box represents an +# operation on one or more modes, and the number in the box indicates the number +# of parameters. The boxes containing a ``1`` are simply phase shifts on +# individual modes. The blocks containing a ``3`` are :math:`SU(2)` transforms +# with 3 parameters, such as the :math:`U(\phi, \theta, \omega)` above. Those +# containing a ``2`` are :math:`SU(2)` transforms on pairs of modes with 2 +# parameters, similar to the 3-parameter ones but with :math:`\omega = \phi.` +# +# Although the decompositions all produce the same set of operations, their +# structure and parametrization may have consequences in practice. The first [#deGuise2018]_ +# has a particularly convenient form that leads to a recursive definition +# of the Haar measure. The decomposition is formulated recursively such that an +# :math:`SU(N)` operation can be implemented by sandwiching an :math:`SU(2)` +# transformation between two :math:`SU(N-1)` transformations, like so: +# +# | +# +# .. figure:: /_static/demonstration_assets/haar_measure/sun.svg +# :align: center +# :width: 80% +# +# | +# +# The Haar measure is then constructed recursively as a product of 3 +# terms. The first term depends on the parameters in the first :math:`SU(N-1)` +# transformation; the second depends on the parameters in the lone :math:`SU(2)` +# transformation; and the third term depends on the parameters in the other +# :math:`SU(N-1)` transformation. +# +# :math:`SU(2)` is the "base case" of the recursion—we simply have the Haar measure +# as expressed above. +# +# | +# +# .. figure:: /_static/demonstration_assets/haar_measure/su2_haar.svg +# :align: center +# :width: 25% +# +# | +# +# Moving on up, we can write elements of :math:`SU(3)` as a sequence of three +# :math:`SU(2)` transformations. The Haar measure :math:`d\mu_3` then consists +# of two copies of :math:`d\mu_2,` with an extra term in between to take into +# account the middle transformation. +# +# | +# +# .. figure:: /_static/demonstration_assets/haar_measure/su3_haar.svg +# :align: center +# :width: 80% +# +# | +# +# For :math:`SU(4)` and upwards, the form changes slightly, but still follows +# the pattern of two copies of :math:`d\mu_{N-1}` with a term in between. +# +# | +# +# .. figure:: /_static/demonstration_assets/haar_measure/su4_premerge.svg +# :align: center +# :width: 90% +# +# | +# +# For larger systems, however, the recursive composition allows for some of the +# :math:`SU(2)` transformations on the lower modes to be grouped. We can take +# advantage of this and aggregate some of the parameters: +# +# | +# +# .. figure:: /_static/demonstration_assets/haar_measure/su4_triangle_merge.svg +# :align: center +# :width: 100% +# +# | +# +# This leads to one copy of :math:`d\mu_{N-1},` which we'll denote as +# :math:`d\mu_{N-1}^\prime,` containing only a portion of the full set of terms +# (as detailed in [#deGuise2018]_, this is called a *coset measure*). +# +# | +# +# .. figure:: /_static/demonstration_assets/haar_measure/su4_haar.svg +# :align: center +# :width: 100% +# +# | +# +# Putting everything together, we have that +# +# .. math:: +# +# d\mu_N = d\mu_{N-1}^\prime \times \sin \theta_{N-1} +# \sin^{2(N-2)}\left(\frac{\theta_{N-1}}{2}\right) d\theta_{N-1} d\omega_{N-1} \times d\mu_{N-1} +# +# The middle portion depends on the value of :math:`N,` and the parameters +# :math:`\theta_{N-1}` and :math:`\omega_{N-1}` contained in the :math:`(N-1)`'th +# :math:`SU(N)` transformation. This is thus a convenient, systematic way to +# construct the :math:`N`-dimensional Haar measure for the unitary group. As a +# final note, even though unitaries can be parametrized in different ways, the +# underlying Haar measure is *unique*. This is a consequence of it being an +# invariant measure, as will be shown later. +# +# Haar-random matrices from the :math:`QR` decomposition +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Nice-looking math aside, sometimes you just need to generate a large number of +# high-dimensional Haar-random matrices. It would be very cumbersome to sample +# and keep track of the distributions of so many parameters; furthermore, the +# measure above requires you to parametrize your operations in a fixed way. +# There is a much quicker way to perform the sampling by taking a (slightly +# modified) `QR decomposition +# `__ of complex-valued +# matrices. This algorithm is detailed in [#Mezzadri2006]_, and consists of the +# following steps: +# +# 1. Generate an :math:`N \times N` matrix :math:`Z` with complex numbers :math:`a+bi` +# where both :math:`a` and :math:`b` are normally distributed with mean 0 and variance 1 +# (this is sampling from the distribution known as the *Ginibre ensemble*). +# 2. Compute a QR decomposition :math:`Z = QR.` +# 3. Compute the diagonal matrix :math:`\Lambda = \hbox{diag}(R_{ii}/|R_{ii}|).` +# 4. Compute :math:`Q^\prime = Q \Lambda,` which will be Haar-random. +# +# + +from numpy.linalg import qr + +def qr_haar(N): + """Generate a Haar-random matrix using the QR decomposition.""" + # Step 1 + A, B = np.random.normal(size=(N, N)), np.random.normal(size=(N, N)) + Z = A + 1j * B + + # Step 2 + Q, R = qr(Z) + + # Step 3 + Lambda = np.diag([R[i, i] / np.abs(R[i, i]) for i in range(N)]) + + # Step 4 + return np.dot(Q, Lambda) + +###################################################################### +# Let's check that this method actually generates Haar-random unitaries +# by trying it out for :math:`N=2` and plotting on the Bloch sphere. +# + +@qml.qnode(dev) +def qr_haar_random_unitary(): + qml.QubitUnitary(qr_haar(2), wires=0) + return qml.state() + +qr_haar_samples = [qr_haar_random_unitary() for _ in range(num_samples)] +qr_haar_bloch_vectors = np.array([convert_to_bloch_vector(s) for s in qr_haar_samples]) +plot_bloch_sphere(qr_haar_bloch_vectors) + +###################################################################### +# As expected, we find our qubit states are distributed uniformly over the +# sphere. This particular method is what's implemented in packages like +# ``scipy``'s `unitary_group +# `__ +# function. +# +# Now, it's clear that this method works, but it is also important to +# understand *why* it works. Step 1 is fairly straightforward—the base of our +# samples is a matrix full of complex values chosen from a typical +# distribution. This isn't enough by itself, since unitary matrices also +# have constraints—their rows and columns must be orthonormal. +# These constraints are where step 2 comes in—the outcome of a generic +# QR decomposition consists of an *orthonormal* matrix :math:`Q,` and and upper +# triangular matrix :math:`R.` Since our original matrix was complex-valued, we end +# up with a :math:`Q` that is in fact already unitary. But why not stop there? Why +# do we then perform steps 3 and 4? +# +# Steps 3 and 4 are needed because, while the QR decomposition yields a unitary, +# it is not a unitary that is properly Haar-random. In [#Mezzadri2006]_, it is +# explained that a uniform distribution over unitary matrices should also yield +# a uniform distribution over the *eigenvalues* of those matrices, i.e., every +# eigenvalue should be equally likely. Just using the QR decomposition out of +# the box produces an *uneven* distribution of eigenvalues of the unitaries! +# This discrepancy stems from the fact that the QR decomposition is not unique. +# We can take any unitary diagonal matrix :math:`\Lambda,` and re-express the decomposition +# as :math:`QR = Q\Lambda \Lambda^\dagger R = Q^\prime R^\prime.` Step 3 removes this +# redundancy by fixing a :math:`\Lambda` that depends on :math:`R,` leading to a unique +# value of :math:`Q^\prime = Q \Lambda,` and a uniform distribution of eigenvalues. +# +# .. admonition:: Try it! +# +# Use the ``qr_haar`` function above to generate random unitaries and construct +# a distribution of their eigenvalues. Then, comment out the lines for steps 3 and +# 4 and do the same—you'll find that the distribution is no longer uniform. +# Check out reference [#Mezzadri2006]_ for additional details and examples. + +###################################################################### +# Fun (and not-so-fun) facts +# -------------------------- +# +# We've now learned what the Haar measure is, and both an analytical and +# numerical means of sampling quantum states and unitary operations uniformly at +# random. The Haar measure also has many neat properties that play a role in +# quantum computing. +# +# Invariance of the Haar measure +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Earlier, we showed that the Haar measure is used when integrating functions over +# the unitary group: +# +# .. math:: +# +# \int_{V \in U(N)} f(V) d\mu_N(V). +# +# One of the defining features of the Haar measure is that it is both left and +# right *invariant* under unitary transformations. That is, +# +# .. math:: +# +# \int_{V \in U(N)} f(\color{red}{W}V) d\mu_N(V) = \int_{V \in U(N)} f(V\color{red}{W}) d\mu_N(V) = \int_{V \in U(N)} f(V) d\mu_N(V). +# +# This holds true for *any* other :math:`N\times N` unitary :math:`W!` A +# consequence of such invariance is that if :math:`V` is Haar-random, then so is +# :math:`V^T,` :math:`V^\dagger,` and any product of another unitary matrix and +# :math:`V` (where the product may be taken on either side). +# +# Another consequence of this invariance has to do with the structure of the entries +# themselves: they must all come from the same distribution. This is because the +# measure remains invariant under permutations, since permutations are unitary--- +# the whole thing still has to be Haar random no matter how the entries are ordered, +# so all distributions must be the same. The specific distribution is complex +# numbers :math:`a+bi` where both :math:`a` and :math:`b` has mean 0 and variance +# :math:`1/N` [#Meckes2014]_ (so, much like Ginibre ensemble we used in the QR decomposition +# above, but with a different variance and constraints due to orthonormality). +# +# Concentration of measure +# ~~~~~~~~~~~~~~~~~~~~~~~~ +# +# An unfortunate (although interesting) property of the Haar measure is that it +# suffers from the phenomenon of `concentration of measure +# `__. Most of the +# "stuff" in the space concentrates around a certain area, and this gets worse +# as the size of the system increases. You can see the beginnings of by looking +# at the sphere. For the 3-dimensional sphere, we saw graphically how there is +# concentration around the equator, and how the measure takes that into account +# with the additional factor of :math:`\sin \theta.` This property becomes +# increasingly prominent for `higher-dimensional spheres +# `__. +# +# .. important:: +# +# The concentration described here is not referring to what we witnessed +# earlier on, when we sampled quantum states (points on the Bloch sphere) +# incorrectly and found that they clustered around the poles. However, that +# is not unrelated. Concentration of measure refers to where the measure +# itself is concentrated, and which parts of the space should be more heavily +# weighted. For the case of the sphere, it is the equatorial area, and when +# we didn't sample properly and take that concentration into account, we +# obtained an uneven distribution. +# +# Let's consider an :math:`N`-dimensional unit sphere. Points on the sphere, or +# vectors in this space, are parametrized by :math:`N-1` real coordinates. +# Suppose we have some function :math:`f` that maps points on that sphere to +# real numbers. Sample a point :math:`x` on that sphere from the uniform +# measure, and compute the value of :math:`f(x).` How close do you think the +# result will be to the mean value of the function, :math:`E[f],` over the +# entire sphere? +# +# A result called `Levy's lemma +# `__ +# [#Gerken2013]_, [#Hayden2006]_ expresses how likely it is that :math:`f(x)` is a specific +# distance away from the mean. It states that, for an :math:`x` selected +# uniformly at random, the probability that :math:`f(x)` deviates from +# :math:`E[f]` by some amount :math:`\epsilon` is bounded by: +# +# .. math:: +# +# \hbox{Pr}(|f(x) - E[f]| \ge \epsilon) \leq 2 \exp\left[-\frac{N\epsilon^2}{9\pi^3 \eta^2}\right]. +# +# A constraint on the function :math:`f` is that it must be `Lipschitz +# continuous `__, where +# :math:`\eta` is the *Lipschitz constant* of the function. The important aspect +# here is the likelihood of deviating significantly from the mean by an amount +# :math:`\epsilon` decreases exponentially with :math:`\epsilon.` Furthermore, +# increasing the dimension :math:`N` also makes the deviation exponentially less +# likely. +# +# Now, this result seems unrelated to quantum states—it concerns higher- +# dimensional spheres. However, recall that a quantum state vector is a complex +# vector whose squared values sum to 1, similar to vectors on a sphere. If you +# "unroll" a quantum state vector of dimension :math:`N = 2^n` by stacking its +# real and complex parts, you end with a vector of length :math:`2 \cdot 2^{n}` +# which ends up behaving just like a unit vector on the sphere in this +# dimension. Given that measure concentrates on spheres, and quantum state +# vectors can be converted to vectors on spheres, functions on random quantum +# states will also demonstrate concentration. +# +# This is bad news! To do useful things in quantum computing, we need a lot of +# qubits. But the more qubits we have, the more our randomly sampled states will +# look the same (specifically, random states will concentrate around the +# maximally entangled state [#Hayden2006]_). This has important consequences for +# near-term algorithms (as detailed in the next section), and any algorithm that +# involves uniform sampling of quantum states and operations. +# +# Haar measure and barren plateaus +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Suppose you are venturing out to solve a new problem using an algorithm such +# as the :doc:`variational quantum eigensolver `. A +# critical component of such methods is the choice of :doc:`variational ansatz +# `. Having now learned a bit about the properties of +# the Haar measure, you may think it would make sense to use this for the +# parametrization. Variational ansaetze are, after all, parametrized quantum +# circuits, so why not choose an ansatz that corresponds directly to a +# parametrization for Haar-random unitaries? The initial parameter selection +# will give you a state in the Hilbert space uniformly at random. Then, since +# this ansatz spans the entire Hilbert space, you're guaranteed to be able to +# represent the target ground state with your ansatz, and it should be able to +# find it with no issue ... right? +# +# Unfortunately, while such an ansatz is extremely *expressive* (i.e., it is +# capable of representing any possible state), these ansaetze actually suffer +# the most from the barren plateau problem [#McClean2018]_, [#Holmes2021]_. +# :doc:`Barren plateaus ` are regions in the +# cost landscape of a parametrized circuit where both the gradient and its +# variance approach 0, leading the optimizer to get stuck in a local minimum. +# This was explored recently in the work of [#Holmes2021]_, wherein closeness to +# the Haar measure was actually used as a metric for expressivity. The closer +# things are to the Haar measure, the more expressive they are, but they are +# also more prone to exhibiting barren plateaus. +# +# +# .. figure:: /_static/demonstration_assets/haar_measure/holmes-costlandscapes.png +# :align: center +# :width: 50% +# +# Image source: [#Holmes2021]_. A highly expressive ansatz that can access +# much of the space of possible unitaries (i.e., an ansatz capable of +# producing unitaries in something close to a Haar-random manner) is very +# likely to have flat cost landscapes and suffer from the barren plateau +# problem. +# +# It turns out that the types of ansaetze know as *hardware-efficient ansaetze* +# also suffer from this problem if they are "random enough" (this notion will be +# formalized in a future demo). It was shown in [#McClean2018]_ that this is a +# consequence of the concentration of measure phenomenon described above. The +# values of gradients and variances can be computed for classes of circuits on +# average by integrating with respect to the Haar measure, and it is shown that +# these values decrease exponentially in the number of qubits, and thus huge +# swaths of the cost landscape are simply and fundamentally flat. +# +# Conclusion +# ---------- +# +# The Haar measure plays an important role in quantum computing—anywhere +# you might be dealing with sampling random circuits, or averaging over +# all possible unitary operations, you'll want to do so with respect +# to the Haar measure. +# +# There are two important aspects of this that we have yet to touch upon, +# however. The first is whether it is efficient to sample from the Haar measure—given +# that the number of parameters to keep track of is exponential in the +# number of qubits, certainly not. But a more interesting question is do we +# *need* to always sample from the full Haar measure? The answer to this is +# "no" in a very interesting way. Depending on the task at hand, you may be able +# to take a shortcut using something called a *unitary design*. In an upcoming +# demo, we will explore the amazing world of unitary designs and their +# applications! +# +# References +# ---------- +# +# .. [#NandC2000] +# +# M. A. Nielsen, and I. L. Chuang (2000) "Quantum Computation and Quantum Information", +# Cambridge University Press. +# +# .. [#deGuise2018] +# +# H. de Guise, O. Di Matteo, and L. L. Sánchez-Soto. (2018) "Simple factorization +# of unitary transformations", `Phys. Rev. A 97 022328 +# `__. +# (`arXiv `__) +# +# .. [#Clements2016] +# +# W. R. Clements, P. C. Humphreys, B. J. Metcalf, W. S. Kolthammer, and +# I. A. Walmsley (2016) “Optimal design for universal multiport +# interferometers”, \ `Optica 3, 1460–1465 +# `__. +# (`arXiv `__) +# +# .. [#Reck1994] +# +# M. Reck, A. Zeilinger, H. J. Bernstein, and P. Bertani (1994) “Experimental +# realization of any discrete unitary operator”, `Phys. Rev. Lett.73, 58–61 +# `__. +# +# .. [#Mezzadri2006] +# +# F. Mezzadri (2006) "How to generate random matrices from the classical compact groups". +# (`arXiv `__) +# +# .. [#Meckes2014] +# +# E. Meckes (2019) `"The Random Matrix Theory of the Classical Compact Groups" +# `_, Cambridge University Press. +# +# .. [#Gerken2013] +# +# M. Gerken (2013) "Measure concentration: Levy's Lemma" +# (`lecture notes `__). +# +# +# .. [#Hayden2006] +# +# P. Hayden, D. W. Leung, and A. Winter (2006) "Aspects of generic +# entanglement", `Comm. Math. Phys. Vol. 265, No. 1, pp. 95-117 +# `__. +# (`arXiv `__) +# +# .. [#McClean2018] +# +# J. R. McClean, S. Boixo, V. N. Smelyanskiy, R. Babbush, and H. Neven +# (2018) "Barren plateaus in quantum neural network training +# landscapes", `Nature Communications, 9(1) +# `__. +# (`arXiv `__) +# +# .. [#Holmes2021] +# +# Z. Holmes, K. Sharma, M. Cerezo, and P. J. Coles (2021) "Connecting ansatz +# expressibility to gradient magnitudes and barren plateaus". (`arXiv +# `__) # \ No newline at end of file diff --git a/demonstrations/tutorial_here_comes_the_sun.py b/demonstrations/tutorial_here_comes_the_sun.py index 5278e01fd1..88da27d3e5 100644 --- a/demonstrations/tutorial_here_comes_the_sun.py +++ b/demonstrations/tutorial_here_comes_the_sun.py @@ -1,576 +1,576 @@ -r""" - -Here comes the SU(N): multivariate quantum gates and gradients -============================================================== - -.. meta:: - :property="og:description": Learn about multivariate quantum gates for optimization - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_here_comes_the_sun.png - -.. related:: - - tutorial_vqe A brief overview of VQE - tutorial_general_parshift General parameter-shift rules for quantum gradients - tutorial_unitary_designs Unitary designs and their uses in quantum computing - - -*Author: David Wierichs — Posted: 03 April 2023.* - -How do we choose an ansatz when designing a quantum circuit for a variational -quantum algorithm? And what happens if we do not start with elementary hardware-friendly -gates and compose them, but we instead use a more complex building block for local qubit -interactions and allow for multi-parameter gates from the start? -Can we differentiate such circuits, and how do they perform in optimization tasks? - -Let's find out! - -In this tutorial, you will learn about the :math:`\mathrm{SU}(N)` gate -:class:`~pennylane.SpecialUnitary`, a particular quantum gate which -can act like *any* gate on its qubits by choosing the parameters accordingly. -We will look at a custom derivative rule [#wiersema]_ for this gate and compare it to two -alternative differentiation strategies, namely finite differences and the `stochastic -parameter-shift rule `_. -Finally, we will compare the performance of -``qml.SpecialUnitary`` for a toy minimization problem to that of two other general -local gates. That is, we compare the trainability of equally expressive ansätze. - -Ansätze, so many ansätze ------------------------- - -Variational quantum algorithms have been promoted to be useful for many applications. -When designing these algorithms, a central task is to choose the quantum circuit ansatz, -which provides a parameterization of quantum states. In the course of a variational algorithm, -the circuit parameters are then optimized in order to minimize some cost function. -The choice of the ansatz can have a big impact on the quantum states that can be found -by the algorithm (expressivity) and on the optimization's behaviour (trainability). - -Typically, it also affects the -computational cost of executing the algorithm on quantum hardware and the strength of the noise -that enters the computation. Finally, the application itself influences, or -even fixes, the choice of ansatz for some variational quantum algorithms, -which can lead to constraints in the ansatz design. - -.. figure:: ../_static/demonstration_assets/here_comes_the_sun/SUN_demo_Ansatz.png - :align: center - :width: 90% - -While a number of best practices for ansatz design have been developed, -a lot is still unknown about the connection between circuit structures and the -resulting properties. Therefore, circuit design is often also based on intuition or heuristics; -an ansatz reported in the literature might just have turned out -to work particularly well for a given problem or might fall into a "standard" -category of circuits. - -If the application does not constrain the choice of ansatz, we may want to avoid choosing -somewhat arbitrary circuit ansätze that may introduce undesirable biases. -Instead, we will want to reflect the generic structure of the problem by performing a -fully general operation on the qubit register. -However, if we were to do so, the number of parameters required to produce such a general -operation would grow much too quickly. Instead, we want to consider fully general operations -*on a few qubits* and compose them into a fabric of local gates. For two-qubit operations, -the fabric could look like this: - -.. figure:: ../_static/demonstration_assets/here_comes_the_sun/SUN_demo_SU4.png - :align: center - :width: 60% - -The general local operation can be implemented by composing a suitable combination -of elementary gates, like single-qubit rotations and CNOT gates. Alternatively, we may -choose a canonical parameterization of the group that contains all local operations, and we will -see that this is an advantageous approach for the trainability of the ansatz. - -.. figure:: ../_static/demonstration_assets/here_comes_the_sun/SUN_demo_optimization.png - :align: center - :width: 60% - -Before we can use the :math:`\mathrm{SU}(N)` gate in training, we will need to -learn how to differentiate it in a quantum circuit. But first things first: -let's start with a brief math intro — no really, just a *Liettle* bit. - -The special unitary group SU(N) and its Lie algebra ---------------------------------------------------- - -The gate we will look at is given by a specific parameterization of the -`special unitary group `__ -:math:`\mathrm{SU}(N),` where :math:`N=2^n` is the Hilbert space dimension of the gate -for :math:`n` qubits. Mathematically, the group can be defined as the set of operators -(or matrices) that can be inverted by taking their adjoint and that have -determinant :math:`1.` In general, all quantum gates acting on :math:`n` qubits are -elements of :math:`\mathrm{SU}(N)` up to a global phase. - -The group :math:`\mathrm{SU}(N)` is a `Lie group `__, -and its associated `Lie algebra `__ -is :math:`\mathfrak{su}(N).` For our purposes, it will be sufficient to look at a matrix -representation of the algebra and we may define it as - -.. math:: - - \mathfrak{su}(N) = - \{\Omega \in \mathbb{C}^{N\times N}: \Omega^\dagger=-\Omega, \operatorname{Tr}[\Omega]=0\}. - -The conditions are that the elements :math:`\Omega` are *skew-Hermitian* and that their trace vanishes. -We will use so-called canonical coordinates for the algebra which are simply the coefficients -in the Pauli basis. That is, we consider the Pauli basis elements multiplied with the -imaginary unit :math:`i,` except for the identity: - -.. math:: - - G_m \in \mathcal{P}^{(n)} = i \left\{I,X,Y,Z\right\}^n \setminus \{i I^n\}. - -A Lie algebra element :math:`\Omega` can be written as - -.. math:: - - \Omega = \sum_{m=1}^d \theta_m G_m,\quad \theta_m \in \mathbb{R} - -and those coefficients :math:`\theta` are precisely the canonical coordinates. -You may ask why we included the prefactor :math:`i` in the definition of :math:`G_m` and why we excluded -the identity (times :math:`i`). This was done to match the properties of :math:`\mathfrak{su}(N);` -the prefactor makes the basis elements skew-Hermitian and the identity would not have a -vanishing trace. Indeed, one can check that the dimension of :math:`\mathfrak{su}(N)` is -:math:`4^n-1` and that there are :math:`4^n` Pauli words, so that one Pauli word — the identity — had to go -in any case... We can use the canonical coordinates of the algebra to express a *group element* in -:math:`\mathrm{SU}(N)` as well, and the ``qml.SpecialUnitary`` gate we will use is defined as - -.. math:: - - U(\boldsymbol{\theta}) = \exp\left\{\sum_{m=1}^d \theta_m G_m \right\}. - -The number of coordinates and Pauli words in :math:`\mathcal{P}^{(n)}` is :math:`d=4^n-1.` -Therefore, this will be the number of parameters that a single ``qml.SpecialUnitary`` gate acting on -:math:`n` qubits will take. For example, it takes just three parameters for a single qubit, which -is why :class:`~pennylane.Rot` and :class:`~pennylane.U3` take three parameters and may -produce *any* single-qubit rotation. It takes a modest 15 parameters for two qubits, -but it already requires 63 parameters for three qubits. - -For unitaries generated by a single operator, i.e. of the form :math:`\exp(i\theta G),` -there is a plethora of differentiation techniques that allow us to compute its derivative. -However, a standard parameter-shift rule, for example, will not do the job if there are -non-commuting terms :math:`G_m` in the multi-parameter gate :math:`U(\boldsymbol{\theta})` above. -So how *do* we compute the derivative? - -Obtaining the gradient ----------------------- - -In variational quantum algorithms, we typically use the circuit to prepare a quantum state and -then we measure some observable :math:`H.` The resulting real-valued output is considered to be the -cost function :math:`C` that should be minimized. If we want to use gradient-based optimization for -this task, we need a method to compute the gradient :math:`\nabla C` in addition to the cost -function itself. As derived in the publication [#wiersema]_, this is possible on quantum hardware -for :math:`\mathrm{SU}(N)` gates as long as the gates themselves can be implemented. -The implementation in PennyLane follows the decomposition idea described in App. F3, but the -main text of [#wiersema]_ proposes an additional method that scales better in some scenarios -(the caveat being that this method requires additional gates to be available on the quantum hardware). -Here, we will focus on the former method. -We will not go through the entire derivation, but note the following key points: - -- The gradient with respect to all :math:`d` parameters of an :math:`\mathrm{SU}(N)` gate can be - computed using :math:`2d` auxiliary circuits. Each of the circuits contains one additional - operation compared to the original circuit, namely a ``qml.PauliRot`` gate with rotation - angles of :math:`\pm\frac{\pi}{2}.` Note that these Pauli rotations act on up to :math:`n` - qubits. -- This differentiation method uses automatic differentiation during compilation and - classical coprocessing steps, but is compatible with quantum hardware. For large :math:`n,` - the classical processing steps can quickly become prohibitively expensive. -- The computed gradient is not an approximative technique but allows for an exact computation - of the gradient on simulators. On quantum hardware, this leads to unbiased gradient - estimators. - -The implementation in PennyLane takes care of creating the additional circuits and evaluating -them, and with adequate post-processing we get the gradient :math:`\nabla C.` - -Comparing gradient methods --------------------------- - -Before we dive into using ``qml.SpecialUnitary`` in an optimization task, let's compare -a few methods to compute the gradient with respect to the parameters of such a gate. -In particular, we will look at a finite difference (FD) approach, the stochastic parameter-shift -rule, and the custom gradient method we described above. - -For the first approach, we will use the standard central difference recipe given by - -.. math:: - - \partial_{\text{FD},\theta_j}C(\boldsymbol{\theta}) - =\left[C\left(\boldsymbol{\theta}+\frac{\delta}{2}\boldsymbol{e}_j\right) - -C\left(\boldsymbol{\theta}-\frac{\delta}{2}\boldsymbol{e}_j\right)\right] / \delta. - -Here, :math:`\delta` is a shift parameter that we need to choose and :math:`\boldsymbol{e}_j` is the -:math:`j`-th canonical basis vector, i.e. the all-zeros vector with a one in the -:math:`j`-th entry. This approach is agnostic to the differentiated function and does -not exploit its structure. - -In contrast, the stochastic parameter-shift rule is a differentiation recipe developed particularly -for multi-parameter gates like the :math:`\mathrm{SU}(N)` gates [#banchi]_. It involves the -approximate evaluation of an integral by sampling *splitting times* :math:`\tau` and -evaluating an expression close to the non-stochastic parameter-shift rule for each sample. -For more details, also consider the -:doc:`demo on the stochastic parameter-shift rule `. - -So, let's dive into a toy example and explore the three gradient methods! -We start by defining a simple one-qubit circuit that contains a single :math:`\mathrm{SU}(2)` -gate and measures the expectation value of :math:`H=\frac{3}{5} Z - \frac{4}{5} Y.` -As ``qml.SpecialUnitary`` requires automatic differentiation subroutines even for the -hardware-ready derivative recipe, we will make use of JAX. -""" - -import pennylane as qml -import numpy as np -import jax - -jax.config.update("jax_enable_x64", True) -jax.config.update("jax_platform_name", "cpu") -jnp = jax.numpy - -dev = qml.device("default.qubit", wires=1) -H = 0.6 * qml.PauliZ(0) - 0.8 * qml.PauliY(0) - - -def qfunc(theta): - qml.SpecialUnitary(theta, wires=0) - return qml.expval(H) - - -circuit = qml.QNode(qfunc, dev, interface="jax", diff_method="parameter-shift") - -theta = jnp.array([0.4, 0.2, -0.5]) - -############################################################################## -# Now we need to set up the differentiation methods. For this demonstration, we will -# keep the first and last entry of ``theta`` fixed and only compute the gradient for the -# second parameter. This allows us to visualize the results easily and keeps the -# computational effort to a minimum. -# -# We start with the finite-difference -# recipe, using a shift scale of :math:`\delta=0.75.` This choice of :math:`\delta,` -# which is much larger than usual for numerical differentiation on classical computers, -# is adapted to the scenario of shot-based gradients (see App. F2 of [#wiersema]_). -# We compute the derivative with respect to the second entry of theta, so we will use -# the unit vector :math:`e_2:` - -unit_vector = np.array([0.0, 1.0, 0.0]) - - -def central_diff_grad(theta, delta): - plus_eval = circuit(theta + delta / 2 * unit_vector) - minus_eval = circuit(theta - delta / 2 * unit_vector) - return (plus_eval - minus_eval) / delta - - -delta = 0.75 -print(f"Central difference: {central_diff_grad(theta, delta):.5f}") - -############################################################################## -# Next up, we implement the stochastic parameter-shift rule. Of course we do not do -# so in full generality, but for the particular circuit in this example. We will -# sample ten splitting times to obtain the gradient entry. For each splitting time, -# we need to insert a Pauli-:math:`Y` rotation because :math:`\theta_2` belongs to -# the Pauli-:math:`Y` component of :math:`A(\boldsymbol{\theta}).` For this, we define -# an auxiliary circuit. - - -@jax.jit -@qml.qnode(dev, interface="jax") -def aux_circuit(theta, tau, sign): - qml.SpecialUnitary(tau * theta, wires=0) - # This corresponds to the parameter-shift evaluations of RY at 0 - qml.RY(-sign * np.pi / 2, wires=0) - qml.SpecialUnitary((1 - tau) * theta, wires=0) - return qml.expval(H) - - -def stochastic_parshift_grad(theta, num_samples): - grad = 0 - splitting_times = np.random.random(size=num_samples) - for tau in splitting_times: - # Evaluate the two-term parameter-shift rule of the auxiliar circuit - grad += aux_circuit(theta, tau, 1.0) - aux_circuit(theta, tau, -1.0) - return grad / num_samples - - -num_samples = 10 -print(f"Stochastic parameter-shift: {stochastic_parshift_grad(theta, num_samples):.5f}") - -############################################################################## -# Finally, we can make use of the custom parameter-shift rule introduced in -# [#wiersema]_, which is readily available in PennyLane. Due to the implementation -# chosen internally, the full gradient is returned; we need to pick the second -# gradient entry manually. For this small toy problem, this is -# not an issue. - -sun_grad = jax.grad(circuit) -print(f"Custom SU(N) gradient: {sun_grad(theta)[1]:.5f}") - -############################################################################## -# We obtained three values for the gradient of interest, and they do not agree. -# So what is going on here? First, let's use automatic differentiation to compute -# the exact value and see which method agrees with it (we again need to extract the -# corresponding entry from the full gradient). - -autodiff_circuit = qml.QNode(qfunc, dev, interface="jax", diff_method="parameter-shift") -exact_grad = jax.grad(autodiff_circuit)(theta)[1] -print(f"Exact gradient: {exact_grad:.5f}") - -############################################################################## -# As we can see, automatic differentiation confirmed that the custom differentiation method -# gave us the correct result. Why do the other methods disagree? -# This is because the finite difference recipe is an *approximate* gradient -# method. This means it has an error even if all circuit evaluations are -# made exact (up to numerical precision) like in the example above. -# As for the stochastic parameter-shift rule, you may already guess why there is -# a deviation: indeed, the *stochastic* nature of this method leads to derivative -# values that are scattered around the true value. It is an unbiased estimator, -# so the average will approach the exact value with increasingly many evaluations. -# To demonstrate this, let's compute the same derivative many times and plot -# a histogram of what we get. We'll do so for ``num_samples=2``, ``10`` and ``100``. - -import matplotlib.pyplot as plt - -plt.rcParams.update({"font.size": 12}) - -fig, ax = plt.subplots(1, 1, figsize=(6, 4)) -colors = ["#ACE3FF", "#FF87EB", "#FFE096"] -for num_samples, color in zip([2, 10, 100], colors): - grads = [stochastic_parshift_grad(theta, num_samples) for _ in range(1000)] - ax.hist(grads, label=f"{num_samples} samples", alpha=0.9, color=color) -ylim = ax.get_ylim() -ax.plot([exact_grad] * 2, ylim, ls="--", c="k", label="Exact") -ax.set(xlabel=r"$\partial_{SPS,\theta_2}C(\theta)$", ylabel="Frequency", ylim=ylim) -ax.legend(loc="upper left") -plt.tight_layout() -plt.show() - -############################################################################## -# As we can see, the stochastic parameter-shift rule comes with a variance -# that can be reduced at the additional cost of evaluating the auxiliary circuit -# for more splitting times. -# -# On quantum hardware, all measurement results are statistical in nature anyway. -# So how does this stochasticity combine with the -# three differentiation methods? We will not go into detail here, but refer -# to [#wiersema]_ to see how the custom differentiation rule proposed in the -# main text leads to the lowest mean squared error. For a single-qubit circuit -# similar to the one above, but with the single gate :math:`U(\boldsymbol{\theta})=\exp(iaX+ibY),` -# the derivative and its expected variance are shown in the following -# (recoloured) plot from the manuscript: -# -# .. figure:: ../_static/demonstration_assets/here_comes_the_sun/sampled_grad.png -# :align: center -# :width: 70% -# -# As we can see, the custom :math:`\mathrm{SU}(N)` parameter-shift rule produces the -# gradient estimates with the smallest variance. For small values of the parameter -# :math:`b,` which is fixed for each panel, the custom shift rule and the stochastic -# shift rule approach the standard two-term parameter-shift rule, which would be exact -# for :math:`b=0.` -# The finite difference gradient shown here was obtained using the shift -# scale :math:`\delta=0.75,` as well. As we can see, this suppresses the variance down to -# a level comparable to those of the shift rule derivatives and this shift scale is a -# reasonable trade-off between the variance and the systematic error we observed earlier. -# As shown in App. F3 of [#wiersema]_, this scale is indeed close to the optimal choice -# if we were to compute the gradient with 100 shots per circuit. -# -# Comparing ansatz structures -# --------------------------- -# -# We discussed above that there are many circuit architectures available and that choosing -# a suitable ansatz is important but can be difficult. Here, we will compare a simple ansatz -# based on the ``qml.SpecialUnitary`` gate discussed above to other approaches that fully -# parametrize the special unitary group for the respective number of qubits. -# In particular, we will compare ``qml.SpecialUnitary`` to standard decompositions from the -# literature that parametrize :math:`\mathrm{SU}(N)` with elementary gates, as well as to a -# sequence of Pauli rotation gates that also allows us to create any special unitary. -# Let us start by defining the decomposition of a two-qubit unitary. -# We choose the decomposition, which is optimal but not unique, from [#vatan]_. -# The Pauli rotation sequence is available in PennyLane -# via ``qml.ArbitraryUnitary`` and we will not need to implement it ourselves. - - -def two_qubit_decomp(params, wires): - """Implement an arbitrary SU(4) gate on two qubits - using the decomposition from Theorem 5 in - https://arxiv.org/pdf/quant-ph/0308006.pdf""" - i, j = wires - # Single U(2) parameterization on both qubits separately - qml.Rot(*params[:3], wires=i) - qml.Rot(*params[3:6], wires=j) - qml.CNOT(wires=[j, i]) # First CNOT - qml.RZ(params[6], wires=i) - qml.RY(params[7], wires=j) - qml.CNOT(wires=[i, j]) # Second CNOT - qml.RY(params[8], wires=j) - qml.CNOT(wires=[j, i]) # Third CNOT - # Single U(2) parameterization on both qubits separately - qml.Rot(*params[9:12], wires=i) - qml.Rot(*params[12:15], wires=j) - - -# The three building blocks on two qubits we will compare are: -operations = { - ("Decomposition", "decomposition"): two_qubit_decomp, - ("PauliRot sequence",) * 2: qml.ArbitraryUnitary, - ("$\mathrm{SU}(N)$ gate", "SU(N) gate"): qml.SpecialUnitary, -} - -############################################################################## -# Now that we have the template for the composition approach in place, we construct a toy -# problem to solve using the ansätze. We will sample a random Hamiltonian in the Pauli basis -# (this time without the prefactor :math:`i,` as we want to construct a Hermitian operator) -# with independent coefficients that follow a normal distribution: -# -# .. math:: -# -# H = \sum_{m=1}^d h_m G_m,\quad h_m\sim \mathcal{N}(0,1). -# -# We will work with six qubits. - -num_wires = 6 -wires = list(range(num_wires)) -np.random.seed(62213) - -coefficients = np.random.randn(4**num_wires - 1) -# Create the matrices for the entire Pauli basis -basis = qml.ops.qubit.special_unitary.pauli_basis_matrices(num_wires) -# Construct the Hamiltonian from the normal random coefficients and the basis -H_matrix = qml.math.tensordot(coefficients, basis, axes=[[0], [0]]) -H = qml.Hermitian(H_matrix, wires=wires) -# Compute the ground state energy -E_min = min(qml.eigvals(H)) -print(f"Ground state energy: {E_min:.5f}") - -############################################################################## -# Using the toy problem Hamiltonian and the three ansätze for :math:`\mathrm{SU}(N)` operations -# from above, we create a circuit template that applies these operations in a brick-layer -# architecture with two blocks and each operation acting on ``loc=2`` qubits. -# For this we define a ``QNode``: - -loc = 2 -d = loc**4 - 1 # d = 15 for two-qubit operations -dev = qml.device("default.qubit", wires=num_wires) -# two blocks with two layers. Each layer contains three operations with d parameters -param_shape = (2, 2, 3, d) -init_params = np.zeros(param_shape) - - -def circuit(params, operation=None): - """Apply an operation in a brickwall-like pattern to a qubit register and measure H. - Parameters are assumed to have the dimensions (number of blocks, number of - wires per operation, number of operations per layer, and number of parameters - per operation), in that order. - """ - for params_block in params: - for i, params_layer in enumerate(params_block): - for j, params_op in enumerate(params_layer): - wires_op = [w % num_wires for w in range(loc * j + i, loc * (j + 1) + i)] - operation(params_op, wires_op) - return qml.expval(H) - - -qnode = qml.QNode(circuit, dev, interface="jax") -print(qml.draw(qnode)(init_params, qml.SpecialUnitary)) - -############################################################################## -# We can now proceed to prepare the optimization task using this circuit -# and an optimization routine of our choice. For simplicity, we run a vanilla gradient -# descent optimization with a fixed learning rate for 500 steps. Again, we use JAX - -# for auto-differentiation. - -learning_rate = 5e-4 -num_steps = 500 -init_params = jax.numpy.array(init_params) -grad_fn = jax.jit(jax.jacobian(qnode), static_argnums=1) -qnode = jax.jit(qnode, static_argnums=1) - -############################################################################## -# With this configuration, let's run the optimization! - -energies = {} -for (name, print_name), operation in operations.items(): - print(f"Running the optimization for the {print_name}") - params = init_params.copy() - energy = [] - for step in range(num_steps): - cost = qnode(params, operation) - params = params - learning_rate * grad_fn(params, operation) - energy.append(cost) # Store energy value - if step % 50 == 0: # Report current energy - print(f"{step:3d} Steps: {cost:.6f}") - - energy.append(qnode(params, operation)) # Final energy value - energies[name] = energy - -############################################################################## -# So, did it work? Judging from the intermediate energy values, it seems that the optimization -# outcomes differ notably. But let's take a look at the relative error in energy across the -# optimization process. - -fig, ax = plt.subplots(1, 1) -styles = [":", "--", "-"] -colors = ["#70CEFF", "#C756B2", "#FFE096"] -for (name, energy), c, ls in zip(energies.items(), colors, styles): - error = (energy - E_min) / abs(E_min) - ax.plot(list(range(len(error))), error, label=name, c=c, ls=ls, lw=2.5) - -ax.set(xlabel="Iteration", ylabel="Relative error") -ax.legend() -plt.show() - -############################################################################## -# We find that the optimization indeed performs significantly better for ``qml.SpecialUnitary`` -# than for the other two general unitaries, while using the same number of parameters and -# preserving the expressibility of the circuit ansatz. This -# means that we found a particularly well-trainable parameterization of the local unitaries which -# allows us to reduce the energy of the prepared quantum state more easily while maintaining the -# number of parameters. -# -# -# Conclusion -# ---------- -# -# To summarize, in this tutorial we introduced ``qml.SpecialUnitary``, a multi-parameter -# gate that can act like *any* gate on the qubits it is applied to and that is constructed -# with Lie theory in mind. We discussed three methods of differentiating quantum circuits -# that use this gate, showing that a new custom parameter-shift rule presented in -# [#wiersema]_ is particularly suitable to produce unbiased gradient estimates with the -# lowest variance. Afterwards, we used this differentiation technique when comparing -# the performance of ``qml.SpecialUnitary`` to that of other gates that can act -# like *any* gate locally. For this, we ran a gradient-based optimization for a toy model -# Hamiltonian and found that ``qml.SpecialUnitary`` is particularly well-trainable, achieving -# lower energies significantly quicker than the other tested gates. -# -# There are still exciting questions to answer about ``qml.SpecialUnitary``: How can the -# custom parameter-shift rule be used for other gates, and what does the so-called -# *Dynamical Lie algebra* of these gates have to do with it? How can we implement -# the ``qml.SpecialUnitary`` gate on hardware? Is the unitary time evolution implemented -# by this gate special in a physical sense? -# -# The answers to some, but not all, of these questions can be found in [#wiersema]_. -# We are certain that there are many more interesting aspects of this gate to be uncovered! -# If you want to learn more, consider the other literature references below, -# as well as the documentation of :class:`~pennylane.SpecialUnitary`. -# -# References -# ---------- -# -# .. [#vatan] -# -# Farrokh Vatan and Colin Williams, -# "Optimal Quantum Circuits for General Two-Qubit Gates", -# `arXiv:quant-ph/0308006 `__ (2003). -# -# .. [#wiersema] -# -# R. Wiersema, D. Lewis, D. Wierichs, J. F. Carrasquilla, and N. Killoran. -# "Here comes the SU(N): multivariate quantum gates and gradients" -# `arXiv:2303.11355 `__ (2023). -# -# .. [#banchi] -# -# Leonardo Banchi and Gavin E. Crooks. "Measuring Analytic Gradients of -# General Quantum Evolution with the Stochastic Parameter Shift Rule." -# `Quantum 5, 386 `__ (2021). -# -# About the author -# ---------------- -# +r""" + +Here comes the SU(N): multivariate quantum gates and gradients +============================================================== + +.. meta:: + :property="og:description": Learn about multivariate quantum gates for optimization + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_here_comes_the_sun.png + +.. related:: + + tutorial_vqe A brief overview of VQE + tutorial_general_parshift General parameter-shift rules for quantum gradients + tutorial_unitary_designs Unitary designs and their uses in quantum computing + + +*Author: David Wierichs — Posted: 03 April 2023.* + +How do we choose an ansatz when designing a quantum circuit for a variational +quantum algorithm? And what happens if we do not start with elementary hardware-friendly +gates and compose them, but we instead use a more complex building block for local qubit +interactions and allow for multi-parameter gates from the start? +Can we differentiate such circuits, and how do they perform in optimization tasks? + +Let's find out! + +In this tutorial, you will learn about the :math:`\mathrm{SU}(N)` gate +:class:`~pennylane.SpecialUnitary`, a particular quantum gate which +can act like *any* gate on its qubits by choosing the parameters accordingly. +We will look at a custom derivative rule [#wiersema]_ for this gate and compare it to two +alternative differentiation strategies, namely finite differences and the `stochastic +parameter-shift rule `_. +Finally, we will compare the performance of +``qml.SpecialUnitary`` for a toy minimization problem to that of two other general +local gates. That is, we compare the trainability of equally expressive ansätze. + +Ansätze, so many ansätze +------------------------ + +Variational quantum algorithms have been promoted to be useful for many applications. +When designing these algorithms, a central task is to choose the quantum circuit ansatz, +which provides a parameterization of quantum states. In the course of a variational algorithm, +the circuit parameters are then optimized in order to minimize some cost function. +The choice of the ansatz can have a big impact on the quantum states that can be found +by the algorithm (expressivity) and on the optimization's behaviour (trainability). + +Typically, it also affects the +computational cost of executing the algorithm on quantum hardware and the strength of the noise +that enters the computation. Finally, the application itself influences, or +even fixes, the choice of ansatz for some variational quantum algorithms, +which can lead to constraints in the ansatz design. + +.. figure:: ../_static/demonstration_assets/here_comes_the_sun/SUN_demo_Ansatz.png + :align: center + :width: 90% + +While a number of best practices for ansatz design have been developed, +a lot is still unknown about the connection between circuit structures and the +resulting properties. Therefore, circuit design is often also based on intuition or heuristics; +an ansatz reported in the literature might just have turned out +to work particularly well for a given problem or might fall into a "standard" +category of circuits. + +If the application does not constrain the choice of ansatz, we may want to avoid choosing +somewhat arbitrary circuit ansätze that may introduce undesirable biases. +Instead, we will want to reflect the generic structure of the problem by performing a +fully general operation on the qubit register. +However, if we were to do so, the number of parameters required to produce such a general +operation would grow much too quickly. Instead, we want to consider fully general operations +*on a few qubits* and compose them into a fabric of local gates. For two-qubit operations, +the fabric could look like this: + +.. figure:: ../_static/demonstration_assets/here_comes_the_sun/SUN_demo_SU4.png + :align: center + :width: 60% + +The general local operation can be implemented by composing a suitable combination +of elementary gates, like single-qubit rotations and CNOT gates. Alternatively, we may +choose a canonical parameterization of the group that contains all local operations, and we will +see that this is an advantageous approach for the trainability of the ansatz. + +.. figure:: ../_static/demonstration_assets/here_comes_the_sun/SUN_demo_optimization.png + :align: center + :width: 60% + +Before we can use the :math:`\mathrm{SU}(N)` gate in training, we will need to +learn how to differentiate it in a quantum circuit. But first things first: +let's start with a brief math intro — no really, just a *Liettle* bit. + +The special unitary group SU(N) and its Lie algebra +--------------------------------------------------- + +The gate we will look at is given by a specific parameterization of the +`special unitary group `__ +:math:`\mathrm{SU}(N),` where :math:`N=2^n` is the Hilbert space dimension of the gate +for :math:`n` qubits. Mathematically, the group can be defined as the set of operators +(or matrices) that can be inverted by taking their adjoint and that have +determinant :math:`1.` In general, all quantum gates acting on :math:`n` qubits are +elements of :math:`\mathrm{SU}(N)` up to a global phase. + +The group :math:`\mathrm{SU}(N)` is a `Lie group `__, +and its associated `Lie algebra `__ +is :math:`\mathfrak{su}(N).` For our purposes, it will be sufficient to look at a matrix +representation of the algebra and we may define it as + +.. math:: + + \mathfrak{su}(N) = + \{\Omega \in \mathbb{C}^{N\times N}: \Omega^\dagger=-\Omega, \operatorname{Tr}[\Omega]=0\}. + +The conditions are that the elements :math:`\Omega` are *skew-Hermitian* and that their trace vanishes. +We will use so-called canonical coordinates for the algebra which are simply the coefficients +in the Pauli basis. That is, we consider the Pauli basis elements multiplied with the +imaginary unit :math:`i,` except for the identity: + +.. math:: + + G_m \in \mathcal{P}^{(n)} = i \left\{I,X,Y,Z\right\}^n \setminus \{i I^n\}. + +A Lie algebra element :math:`\Omega` can be written as + +.. math:: + + \Omega = \sum_{m=1}^d \theta_m G_m,\quad \theta_m \in \mathbb{R} + +and those coefficients :math:`\theta` are precisely the canonical coordinates. +You may ask why we included the prefactor :math:`i` in the definition of :math:`G_m` and why we excluded +the identity (times :math:`i`). This was done to match the properties of :math:`\mathfrak{su}(N);` +the prefactor makes the basis elements skew-Hermitian and the identity would not have a +vanishing trace. Indeed, one can check that the dimension of :math:`\mathfrak{su}(N)` is +:math:`4^n-1` and that there are :math:`4^n` Pauli words, so that one Pauli word — the identity — had to go +in any case... We can use the canonical coordinates of the algebra to express a *group element* in +:math:`\mathrm{SU}(N)` as well, and the ``qml.SpecialUnitary`` gate we will use is defined as + +.. math:: + + U(\boldsymbol{\theta}) = \exp\left\{\sum_{m=1}^d \theta_m G_m \right\}. + +The number of coordinates and Pauli words in :math:`\mathcal{P}^{(n)}` is :math:`d=4^n-1.` +Therefore, this will be the number of parameters that a single ``qml.SpecialUnitary`` gate acting on +:math:`n` qubits will take. For example, it takes just three parameters for a single qubit, which +is why :class:`~pennylane.Rot` and :class:`~pennylane.U3` take three parameters and may +produce *any* single-qubit rotation. It takes a modest 15 parameters for two qubits, +but it already requires 63 parameters for three qubits. + +For unitaries generated by a single operator, i.e. of the form :math:`\exp(i\theta G),` +there is a plethora of differentiation techniques that allow us to compute its derivative. +However, a standard parameter-shift rule, for example, will not do the job if there are +non-commuting terms :math:`G_m` in the multi-parameter gate :math:`U(\boldsymbol{\theta})` above. +So how *do* we compute the derivative? + +Obtaining the gradient +---------------------- + +In variational quantum algorithms, we typically use the circuit to prepare a quantum state and +then we measure some observable :math:`H.` The resulting real-valued output is considered to be the +cost function :math:`C` that should be minimized. If we want to use gradient-based optimization for +this task, we need a method to compute the gradient :math:`\nabla C` in addition to the cost +function itself. As derived in the publication [#wiersema]_, this is possible on quantum hardware +for :math:`\mathrm{SU}(N)` gates as long as the gates themselves can be implemented. +The implementation in PennyLane follows the decomposition idea described in App. F3, but the +main text of [#wiersema]_ proposes an additional method that scales better in some scenarios +(the caveat being that this method requires additional gates to be available on the quantum hardware). +Here, we will focus on the former method. +We will not go through the entire derivation, but note the following key points: + +- The gradient with respect to all :math:`d` parameters of an :math:`\mathrm{SU}(N)` gate can be + computed using :math:`2d` auxiliary circuits. Each of the circuits contains one additional + operation compared to the original circuit, namely a ``qml.PauliRot`` gate with rotation + angles of :math:`\pm\frac{\pi}{2}.` Note that these Pauli rotations act on up to :math:`n` + qubits. +- This differentiation method uses automatic differentiation during compilation and + classical coprocessing steps, but is compatible with quantum hardware. For large :math:`n,` + the classical processing steps can quickly become prohibitively expensive. +- The computed gradient is not an approximative technique but allows for an exact computation + of the gradient on simulators. On quantum hardware, this leads to unbiased gradient + estimators. + +The implementation in PennyLane takes care of creating the additional circuits and evaluating +them, and with adequate post-processing we get the gradient :math:`\nabla C.` + +Comparing gradient methods +-------------------------- + +Before we dive into using ``qml.SpecialUnitary`` in an optimization task, let's compare +a few methods to compute the gradient with respect to the parameters of such a gate. +In particular, we will look at a finite difference (FD) approach, the stochastic parameter-shift +rule, and the custom gradient method we described above. + +For the first approach, we will use the standard central difference recipe given by + +.. math:: + + \partial_{\text{FD},\theta_j}C(\boldsymbol{\theta}) + =\left[C\left(\boldsymbol{\theta}+\frac{\delta}{2}\boldsymbol{e}_j\right) + -C\left(\boldsymbol{\theta}-\frac{\delta}{2}\boldsymbol{e}_j\right)\right] / \delta. + +Here, :math:`\delta` is a shift parameter that we need to choose and :math:`\boldsymbol{e}_j` is the +:math:`j`-th canonical basis vector, i.e. the all-zeros vector with a one in the +:math:`j`-th entry. This approach is agnostic to the differentiated function and does +not exploit its structure. + +In contrast, the stochastic parameter-shift rule is a differentiation recipe developed particularly +for multi-parameter gates like the :math:`\mathrm{SU}(N)` gates [#banchi]_. It involves the +approximate evaluation of an integral by sampling *splitting times* :math:`\tau` and +evaluating an expression close to the non-stochastic parameter-shift rule for each sample. +For more details, also consider the +:doc:`demo on the stochastic parameter-shift rule `. + +So, let's dive into a toy example and explore the three gradient methods! +We start by defining a simple one-qubit circuit that contains a single :math:`\mathrm{SU}(2)` +gate and measures the expectation value of :math:`H=\frac{3}{5} Z - \frac{4}{5} Y.` +As ``qml.SpecialUnitary`` requires automatic differentiation subroutines even for the +hardware-ready derivative recipe, we will make use of JAX. +""" + +import pennylane as qml +import numpy as np +import jax + +jax.config.update("jax_enable_x64", True) +jax.config.update("jax_platform_name", "cpu") +jnp = jax.numpy + +dev = qml.device("default.qubit", wires=1) +H = 0.6 * qml.PauliZ(0) - 0.8 * qml.PauliY(0) + + +def qfunc(theta): + qml.SpecialUnitary(theta, wires=0) + return qml.expval(H) + + +circuit = qml.QNode(qfunc, dev, interface="jax", diff_method="parameter-shift") + +theta = jnp.array([0.4, 0.2, -0.5]) + +############################################################################## +# Now we need to set up the differentiation methods. For this demonstration, we will +# keep the first and last entry of ``theta`` fixed and only compute the gradient for the +# second parameter. This allows us to visualize the results easily and keeps the +# computational effort to a minimum. +# +# We start with the finite-difference +# recipe, using a shift scale of :math:`\delta=0.75.` This choice of :math:`\delta,` +# which is much larger than usual for numerical differentiation on classical computers, +# is adapted to the scenario of shot-based gradients (see App. F2 of [#wiersema]_). +# We compute the derivative with respect to the second entry of theta, so we will use +# the unit vector :math:`e_2:` + +unit_vector = np.array([0.0, 1.0, 0.0]) + + +def central_diff_grad(theta, delta): + plus_eval = circuit(theta + delta / 2 * unit_vector) + minus_eval = circuit(theta - delta / 2 * unit_vector) + return (plus_eval - minus_eval) / delta + + +delta = 0.75 +print(f"Central difference: {central_diff_grad(theta, delta):.5f}") + +############################################################################## +# Next up, we implement the stochastic parameter-shift rule. Of course we do not do +# so in full generality, but for the particular circuit in this example. We will +# sample ten splitting times to obtain the gradient entry. For each splitting time, +# we need to insert a Pauli-:math:`Y` rotation because :math:`\theta_2` belongs to +# the Pauli-:math:`Y` component of :math:`A(\boldsymbol{\theta}).` For this, we define +# an auxiliary circuit. + + +@jax.jit +@qml.qnode(dev, interface="jax") +def aux_circuit(theta, tau, sign): + qml.SpecialUnitary(tau * theta, wires=0) + # This corresponds to the parameter-shift evaluations of RY at 0 + qml.RY(-sign * np.pi / 2, wires=0) + qml.SpecialUnitary((1 - tau) * theta, wires=0) + return qml.expval(H) + + +def stochastic_parshift_grad(theta, num_samples): + grad = 0 + splitting_times = np.random.random(size=num_samples) + for tau in splitting_times: + # Evaluate the two-term parameter-shift rule of the auxiliar circuit + grad += aux_circuit(theta, tau, 1.0) - aux_circuit(theta, tau, -1.0) + return grad / num_samples + + +num_samples = 10 +print(f"Stochastic parameter-shift: {stochastic_parshift_grad(theta, num_samples):.5f}") + +############################################################################## +# Finally, we can make use of the custom parameter-shift rule introduced in +# [#wiersema]_, which is readily available in PennyLane. Due to the implementation +# chosen internally, the full gradient is returned; we need to pick the second +# gradient entry manually. For this small toy problem, this is +# not an issue. + +sun_grad = jax.grad(circuit) +print(f"Custom SU(N) gradient: {sun_grad(theta)[1]:.5f}") + +############################################################################## +# We obtained three values for the gradient of interest, and they do not agree. +# So what is going on here? First, let's use automatic differentiation to compute +# the exact value and see which method agrees with it (we again need to extract the +# corresponding entry from the full gradient). + +autodiff_circuit = qml.QNode(qfunc, dev, interface="jax", diff_method="parameter-shift") +exact_grad = jax.grad(autodiff_circuit)(theta)[1] +print(f"Exact gradient: {exact_grad:.5f}") + +############################################################################## +# As we can see, automatic differentiation confirmed that the custom differentiation method +# gave us the correct result. Why do the other methods disagree? +# This is because the finite difference recipe is an *approximate* gradient +# method. This means it has an error even if all circuit evaluations are +# made exact (up to numerical precision) like in the example above. +# As for the stochastic parameter-shift rule, you may already guess why there is +# a deviation: indeed, the *stochastic* nature of this method leads to derivative +# values that are scattered around the true value. It is an unbiased estimator, +# so the average will approach the exact value with increasingly many evaluations. +# To demonstrate this, let's compute the same derivative many times and plot +# a histogram of what we get. We'll do so for ``num_samples=2``, ``10`` and ``100``. + +import matplotlib.pyplot as plt + +plt.rcParams.update({"font.size": 12}) + +fig, ax = plt.subplots(1, 1, figsize=(6, 4)) +colors = ["#ACE3FF", "#FF87EB", "#FFE096"] +for num_samples, color in zip([2, 10, 100], colors): + grads = [stochastic_parshift_grad(theta, num_samples) for _ in range(1000)] + ax.hist(grads, label=f"{num_samples} samples", alpha=0.9, color=color) +ylim = ax.get_ylim() +ax.plot([exact_grad] * 2, ylim, ls="--", c="k", label="Exact") +ax.set(xlabel=r"$\partial_{SPS,\theta_2}C(\theta)$", ylabel="Frequency", ylim=ylim) +ax.legend(loc="upper left") +plt.tight_layout() +plt.show() + +############################################################################## +# As we can see, the stochastic parameter-shift rule comes with a variance +# that can be reduced at the additional cost of evaluating the auxiliary circuit +# for more splitting times. +# +# On quantum hardware, all measurement results are statistical in nature anyway. +# So how does this stochasticity combine with the +# three differentiation methods? We will not go into detail here, but refer +# to [#wiersema]_ to see how the custom differentiation rule proposed in the +# main text leads to the lowest mean squared error. For a single-qubit circuit +# similar to the one above, but with the single gate :math:`U(\boldsymbol{\theta})=\exp(iaX+ibY),` +# the derivative and its expected variance are shown in the following +# (recoloured) plot from the manuscript: +# +# .. figure:: ../_static/demonstration_assets/here_comes_the_sun/sampled_grad.png +# :align: center +# :width: 70% +# +# As we can see, the custom :math:`\mathrm{SU}(N)` parameter-shift rule produces the +# gradient estimates with the smallest variance. For small values of the parameter +# :math:`b,` which is fixed for each panel, the custom shift rule and the stochastic +# shift rule approach the standard two-term parameter-shift rule, which would be exact +# for :math:`b=0.` +# The finite difference gradient shown here was obtained using the shift +# scale :math:`\delta=0.75,` as well. As we can see, this suppresses the variance down to +# a level comparable to those of the shift rule derivatives and this shift scale is a +# reasonable trade-off between the variance and the systematic error we observed earlier. +# As shown in App. F3 of [#wiersema]_, this scale is indeed close to the optimal choice +# if we were to compute the gradient with 100 shots per circuit. +# +# Comparing ansatz structures +# --------------------------- +# +# We discussed above that there are many circuit architectures available and that choosing +# a suitable ansatz is important but can be difficult. Here, we will compare a simple ansatz +# based on the ``qml.SpecialUnitary`` gate discussed above to other approaches that fully +# parametrize the special unitary group for the respective number of qubits. +# In particular, we will compare ``qml.SpecialUnitary`` to standard decompositions from the +# literature that parametrize :math:`\mathrm{SU}(N)` with elementary gates, as well as to a +# sequence of Pauli rotation gates that also allows us to create any special unitary. +# Let us start by defining the decomposition of a two-qubit unitary. +# We choose the decomposition, which is optimal but not unique, from [#vatan]_. +# The Pauli rotation sequence is available in PennyLane +# via ``qml.ArbitraryUnitary`` and we will not need to implement it ourselves. + + +def two_qubit_decomp(params, wires): + """Implement an arbitrary SU(4) gate on two qubits + using the decomposition from Theorem 5 in + https://arxiv.org/pdf/quant-ph/0308006.pdf""" + i, j = wires + # Single U(2) parameterization on both qubits separately + qml.Rot(*params[:3], wires=i) + qml.Rot(*params[3:6], wires=j) + qml.CNOT(wires=[j, i]) # First CNOT + qml.RZ(params[6], wires=i) + qml.RY(params[7], wires=j) + qml.CNOT(wires=[i, j]) # Second CNOT + qml.RY(params[8], wires=j) + qml.CNOT(wires=[j, i]) # Third CNOT + # Single U(2) parameterization on both qubits separately + qml.Rot(*params[9:12], wires=i) + qml.Rot(*params[12:15], wires=j) + + +# The three building blocks on two qubits we will compare are: +operations = { + ("Decomposition", "decomposition"): two_qubit_decomp, + ("PauliRot sequence",) * 2: qml.ArbitraryUnitary, + ("$\mathrm{SU}(N)$ gate", "SU(N) gate"): qml.SpecialUnitary, +} + +############################################################################## +# Now that we have the template for the composition approach in place, we construct a toy +# problem to solve using the ansätze. We will sample a random Hamiltonian in the Pauli basis +# (this time without the prefactor :math:`i,` as we want to construct a Hermitian operator) +# with independent coefficients that follow a normal distribution: +# +# .. math:: +# +# H = \sum_{m=1}^d h_m G_m,\quad h_m\sim \mathcal{N}(0,1). +# +# We will work with six qubits. + +num_wires = 6 +wires = list(range(num_wires)) +np.random.seed(62213) + +coefficients = np.random.randn(4**num_wires - 1) +# Create the matrices for the entire Pauli basis +basis = qml.ops.qubit.special_unitary.pauli_basis_matrices(num_wires) +# Construct the Hamiltonian from the normal random coefficients and the basis +H_matrix = qml.math.tensordot(coefficients, basis, axes=[[0], [0]]) +H = qml.Hermitian(H_matrix, wires=wires) +# Compute the ground state energy +E_min = min(qml.eigvals(H)) +print(f"Ground state energy: {E_min:.5f}") + +############################################################################## +# Using the toy problem Hamiltonian and the three ansätze for :math:`\mathrm{SU}(N)` operations +# from above, we create a circuit template that applies these operations in a brick-layer +# architecture with two blocks and each operation acting on ``loc=2`` qubits. +# For this we define a ``QNode``: + +loc = 2 +d = loc**4 - 1 # d = 15 for two-qubit operations +dev = qml.device("default.qubit", wires=num_wires) +# two blocks with two layers. Each layer contains three operations with d parameters +param_shape = (2, 2, 3, d) +init_params = np.zeros(param_shape) + + +def circuit(params, operation=None): + """Apply an operation in a brickwall-like pattern to a qubit register and measure H. + Parameters are assumed to have the dimensions (number of blocks, number of + wires per operation, number of operations per layer, and number of parameters + per operation), in that order. + """ + for params_block in params: + for i, params_layer in enumerate(params_block): + for j, params_op in enumerate(params_layer): + wires_op = [w % num_wires for w in range(loc * j + i, loc * (j + 1) + i)] + operation(params_op, wires_op) + return qml.expval(H) + + +qnode = qml.QNode(circuit, dev, interface="jax") +print(qml.draw(qnode)(init_params, qml.SpecialUnitary)) + +############################################################################## +# We can now proceed to prepare the optimization task using this circuit +# and an optimization routine of our choice. For simplicity, we run a vanilla gradient +# descent optimization with a fixed learning rate for 500 steps. Again, we use JAX + +# for auto-differentiation. + +learning_rate = 5e-4 +num_steps = 500 +init_params = jax.numpy.array(init_params) +grad_fn = jax.jit(jax.jacobian(qnode), static_argnums=1) +qnode = jax.jit(qnode, static_argnums=1) + +############################################################################## +# With this configuration, let's run the optimization! + +energies = {} +for (name, print_name), operation in operations.items(): + print(f"Running the optimization for the {print_name}") + params = init_params.copy() + energy = [] + for step in range(num_steps): + cost = qnode(params, operation) + params = params - learning_rate * grad_fn(params, operation) + energy.append(cost) # Store energy value + if step % 50 == 0: # Report current energy + print(f"{step:3d} Steps: {cost:.6f}") + + energy.append(qnode(params, operation)) # Final energy value + energies[name] = energy + +############################################################################## +# So, did it work? Judging from the intermediate energy values, it seems that the optimization +# outcomes differ notably. But let's take a look at the relative error in energy across the +# optimization process. + +fig, ax = plt.subplots(1, 1) +styles = [":", "--", "-"] +colors = ["#70CEFF", "#C756B2", "#FFE096"] +for (name, energy), c, ls in zip(energies.items(), colors, styles): + error = (energy - E_min) / abs(E_min) + ax.plot(list(range(len(error))), error, label=name, c=c, ls=ls, lw=2.5) + +ax.set(xlabel="Iteration", ylabel="Relative error") +ax.legend() +plt.show() + +############################################################################## +# We find that the optimization indeed performs significantly better for ``qml.SpecialUnitary`` +# than for the other two general unitaries, while using the same number of parameters and +# preserving the expressibility of the circuit ansatz. This +# means that we found a particularly well-trainable parameterization of the local unitaries which +# allows us to reduce the energy of the prepared quantum state more easily while maintaining the +# number of parameters. +# +# +# Conclusion +# ---------- +# +# To summarize, in this tutorial we introduced ``qml.SpecialUnitary``, a multi-parameter +# gate that can act like *any* gate on the qubits it is applied to and that is constructed +# with Lie theory in mind. We discussed three methods of differentiating quantum circuits +# that use this gate, showing that a new custom parameter-shift rule presented in +# [#wiersema]_ is particularly suitable to produce unbiased gradient estimates with the +# lowest variance. Afterwards, we used this differentiation technique when comparing +# the performance of ``qml.SpecialUnitary`` to that of other gates that can act +# like *any* gate locally. For this, we ran a gradient-based optimization for a toy model +# Hamiltonian and found that ``qml.SpecialUnitary`` is particularly well-trainable, achieving +# lower energies significantly quicker than the other tested gates. +# +# There are still exciting questions to answer about ``qml.SpecialUnitary``: How can the +# custom parameter-shift rule be used for other gates, and what does the so-called +# *Dynamical Lie algebra* of these gates have to do with it? How can we implement +# the ``qml.SpecialUnitary`` gate on hardware? Is the unitary time evolution implemented +# by this gate special in a physical sense? +# +# The answers to some, but not all, of these questions can be found in [#wiersema]_. +# We are certain that there are many more interesting aspects of this gate to be uncovered! +# If you want to learn more, consider the other literature references below, +# as well as the documentation of :class:`~pennylane.SpecialUnitary`. +# +# References +# ---------- +# +# .. [#vatan] +# +# Farrokh Vatan and Colin Williams, +# "Optimal Quantum Circuits for General Two-Qubit Gates", +# `arXiv:quant-ph/0308006 `__ (2003). +# +# .. [#wiersema] +# +# R. Wiersema, D. Lewis, D. Wierichs, J. F. Carrasquilla, and N. Killoran. +# "Here comes the SU(N): multivariate quantum gates and gradients" +# `arXiv:2303.11355 `__ (2023). +# +# .. [#banchi] +# +# Leonardo Banchi and Gavin E. Crooks. "Measuring Analytic Gradients of +# General Quantum Evolution with the Stochastic Parameter Shift Rule." +# `Quantum 5, 386 `__ (2021). +# +# About the author +# ---------------- +# diff --git a/demonstrations/tutorial_initial_state_preparation.py b/demonstrations/tutorial_initial_state_preparation.py index dc50c939c0..8a2cb89e5c 100644 --- a/demonstrations/tutorial_initial_state_preparation.py +++ b/demonstrations/tutorial_initial_state_preparation.py @@ -1,364 +1,364 @@ -r""" - -Initial state preparation for quantum chemistry -=============================================== - -A high-quality initial state can significantly reduce the runtime of many quantum algorithms. From -the variational quantum eigensolver (VQE) to quantum phase estimation (QPE) and even the recent -`intermediate-scale quantum (ISQ) `_ algorithms, obtaining the ground state of a chemical system requires -a good initial state. For instance, in the case of VQE, a good initial state directly translates into fewer -optimization steps. In QPE, the probability of measuring the ground-state energy is directly -proportional to the overlap squared of the initial and ground states. Even beyond quantum chemistry, -good initial guesses are important for algorithms like quantum approximate optimization (QAOA) -and Grover search. - -Much like searching for a needle in a haystack, there are a lot of things you might try -to prepare a good guess for the ground state in the high-dimensional Hilbert space. In this -tutorial, we show how to use traditional computational chemistry techniques to -get a good initial state. Such an initial state will not be exactly -the ground state, but it will certainly be better than the standard guess of a computational -basis state :math:`|0\rangle^{\otimes N}` or the Hartree-Fock state. - -.. figure:: ../_static/demonstration_assets/initial_state_preparation/qchem_input_states.png - :align: center - :width: 65% - :target: javascript:void(0) - -Importing initial states ------------------------- -We can import initial states obtained from several post-Hartree-Fock quantum chemistry methods -to PennyLane. These methods are incredibly diverse in terms of their outputs, not always returning -an object that is easy to turn into a PennyLane state vector. - -We have already done this hard conversion work: all that you need to do is run these methods and -pass their outputs to PennyLane's :func:`~.pennylane.qchem.import_state` function. The currently -supported methods are configuration interaction with singles and doubles (CISD), coupled cluster -(CCSD), density-matrix renormalization group (DMRG) and semistochastic heat-bath configuration -interaction (SHCI). - -We now show how this works on the linear :math:`\text{H}_3^+` molecule as an example. - - -CISD states -~~~~~~~~~~~ -The first line of attack for initial state preparation is often a CISD calculation, performed with the `PySCF `_ -library. CISD is unsophisticated, but it is fast. It will not be of much help for strongly correlated molecules, -but it is better than Hartree-Fock. Here is the code example using the restricted Hartree-Fock -orbitals (:func:`~.pennylane.qchem.import_state` works for unrestricted orbitals, too). -""" - -from pyscf import gto, scf, ci -from pennylane.qchem import import_state -import numpy as np - -R = 1.2 -# create the H3+ molecule -mol = gto.M(atom=[["H", (0, 0, 0)], ["H", (0, 0, R)], ["H", (0, 0, 2 * R)]], charge=1) -# perfrom restricted Hartree-Fock and then CISD -myhf = scf.RHF(mol).run() -myci = ci.CISD(myhf).run() -wf_cisd = import_state(myci, tol=1e-1) -print(f"CISD-based state vector: \n{np.round(wf_cisd.real, 4)}") - -############################################################################## -# The final object, PennyLane's state vector ``wf_cisd``, is ready to be used as an -# initial state in a quantum circuit in PennyLane--we will showcase this below for VQE. -# -# Conversion for CISD to a state vector is straightforward: simply assign the PySCF-stored -# CI coefficients to appropriate entries in the state vector based on the determinant they correspond to. -# The second attribute passed to ``import_state()``, ``tol``, specifies the cutoff beyond -# which contributions to the wavefunctions are neglected. Internally, wavefunctions are -# stored in their Slater determinant representation. If their prefactor coefficient -# is below ``tol``, those determinants are dropped from the expression. - -############################################################################## -# CCSD states -# ~~~~~~~~~~~ -# The function :func:`~.pennylane.qchem.import_state` is general and works similarly for CCSD. It can -# automatically detect the input type and apply the appropriate conversion protocol. - -from pyscf import cc - -mycc = cc.CCSD(myhf).run() -wf_ccsd = import_state(mycc, tol=1e-1) -print(f"CCSD-based state vector: \n{np.round(wf_ccsd.real, 4)}") - -############################################################################## -# For CCSD conversion, at present the exponential form is expanded and terms are collected **to -# second order** to obtain the CI coefficients. -# -# DMRG states -# ~~~~~~~~~~~ -# For more complex or more correlated molecules, initial states from DMRG or -# SHCI will be better options. DMRG calculations involve using the library `Block2 `_, -# which can be installed with ``pip``: -# -# .. code-block:: bash -# -# pip install block2 -# -# The DMRG calculation is run on top of the molecular orbitals obtained by Hartree-Fock, -# stored in the ``myhf`` object, which we can reuse from before. -# -# .. code-block:: python -# -# from pyscf import mcscf -# from pyblock2.driver.core import DMRGDriver, SymmetryTypes -# from pyblock2._pyscf.ao2mo import integrals as itg -# -# # obtain molecular integrals and other parameters for DMRG -# mc = mcscf.CASCI(myhf, mol.nao, mol.nelectron) -# ncas, n_elec, spin, ecore, h1e, g2e, orb_sym = \ -# itg.get_rhf_integrals(myhf, mc.ncore, mc.ncas, g2e_symm=8) -# -# # initialize the DMRG solver, Hamiltonian (as matrix-product operator, MPO) and -# # state (as matrix-product state, MPS) -# driver = DMRGDriver(scratch="./dmrg_temp", symm_type=SymmetryTypes.SZ) -# driver.initialize_system(n_sites=ncas, n_elec=n_elec, spin=spin, orb_sym=orb_sym) -# mpo = driver.get_qc_mpo(h1e=h1e, g2e=g2e, ecore=ecore, iprint=0) -# ket = driver.get_random_mps(tag="GS") -# -# # execute DMRG by modifying the ket state in-place to minimize the energy -# driver.dmrg(mpo, ket, n_sweeps=30,bond_dims=[100,200],\ -# noises=[1e-3,1e-5],thrds=[1e-6,1e-7],tol=1e-6) -# -# # post-process the MPS to get an initial state -# dets, coeffs = driver.get_csf_coefficients(ket, iprint=0) -# dets = dets.tolist() -# wf_dmrg = import_state((dets, coeffs), tol=1e-1) -# print(f"DMRG-based state vector: \n {np.round(wf_dmrg, 4)}") -# -# .. code-block:: bash -# -# DMRG-based state vector -# [ 0. 0. 0. 0. 0. 0. 0. 0. 0. -# 0. 0. 0. -0.2243 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.9745 0. 0. 0. 0. 0. -# 0. 0. 0. 0. 0. 0. 0. 0. 0. -# 0. ] - -############################################################################## -# The crucial part is calling ``get_csf_coefficients()`` on the solution stored in -# MPS form in the ``ket``. This triggers an internal reconstruction calculation that -# converts the MPS to the sum of Slater determinants form, returning the output -# as a tuple ``(array([int]), array(float]))``. The first element expresses a given Slater -# determinant using Fock occupation vectors of length equal to the number of spatial -# orbitals. In Block2 notation, the entries can be ``0`` (orbital unoccupied), ``1`` (orbital occupied with spin-up -# electron), ``2`` (occupied with spin-down), and ``3`` (doubly occupied). The first -# element, ``array([int])``, must be converted to ``list`` -# for :func:`~.pennylane.qchem.import_state` to accept it. -# The second element stores the CI coefficients. -# -# In principle, this functionality can be used to generate any initial state, provided -# the user specifies a list of Slater determinants and their coefficients in this form. -# Let's take this opportunity to create the Hartree-Fock initial state, to compare the -# other states against it later on. - -hf_primer = ([[3, 0, 0]], np.array([1.0])) -wf_hf = import_state(hf_primer) - -############################################################################## -# SHCI states -# ~~~~~~~~~~~ -# -# The SHCI calculations utilize the library `Dice `_, and can be run -# using PySCF through the interface module `SHCI-SCF `_. -# For Dice, the execution process is similar to that of DMRG: -# -# .. code-block:: python -# -# from pyscf.shciscf import shci -# -# # prepare PySCF CASCI object, whose solver will be the SHCI method -# ncas, nelecas_a, nelecas_b = mol.nao, mol.nelectron // 2, mol.nelectron // 2 -# myshci = mcscf.CASCI(myhf, ncas, (nelecas_a, nelecas_b)) -# -# # set up essentials for the SHCI solver -# output_file = f"shci_output.out" -# myshci.fcisolver = shci.SHCI(myhf.mol) -# myshci.fcisolver.outputFile = output_file -# -# # execute SHCI through the PySCF interface -# e_tot, e_ci, ss, mo_coeff, mo_energies = myshci.kernel(verbose=5) -# -# # post-process the shci_output.out to extract the wave function -# # results and create the tuple of dets (list([str])) and coeffs (array([float])) -# # shci_data = (dets, coeffs) -# wf_shci = import_state(shci_data, tol=1e-1) -# print(f"SHCI-based state vector\n{wf_shci}") -# -# .. code-block:: bash -# -# SHCI-based state vector -# [ 0. 0. 0. 0. 0. 0. 0. 0. 0. -# 0. 0. 0. 0.2243 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.9745 0. 0. 0. 0. 0. -# 0. 0. 0. 0. 0. 0. 0. 0. 0. -# 0. ] - -############################################################################## -# The Dice output file prints determinants using symbols ``0`` (unoccupied orbital), -# ``a`` and ``b`` (orbital occupied with spin-up and spin-down electron, respectively), -# and ``2`` (doubly occupied orbital). These determinant outputs, and corresponding -# coefficients, should be extracted and arranged as ``(list([str]), array(float]))``, -# where each string combines all the determinant symbols ``0, a, b, 2`` for a single -# determinant with no spaces. For example, for the HF state we created in the DMRG section, -# the SHCI output should read ``([["200"]], np.array([1.]))`` - -############################################################################## -# Application: speed up VQE -# ------------------------- -# Let us now demonstrate how the choice of a better initial state shortens the runtime -# of VQE for obtaining the ground-state energy of a molecule. As a first step, create our -# linear :math:`\text{H}_3^+` molecule, a device, and a simple VQE circuit with single and double excitations: - -import pennylane as qml -from pennylane import qchem -from jax import numpy as jnp - -# generate the molecular Hamiltonian for H3+ -symbols = ["H", "H", "H"] -geometry = jnp.array([[0, 0, 0], [0, 0, R/0.529], [0, 0, 2*R/0.529]]) -molecule = qchem.Molecule(symbols, geometry, charge=1) - -H2mol, qubits = qchem.molecular_hamiltonian(molecule) -wires = list(range(qubits)) -dev = qml.device("default.qubit", wires=qubits) - -# create all possible excitations in H3+ -singles, doubles = qchem.excitations(2, qubits) -excitations = singles + doubles - -############################################################################## -# Now let's run VQE with the Hartree-Fock initial state. We first build the VQE circuit: - - -@qml.qnode(dev) -def circuit_VQE(theta, initial_state): - qml.StatePrep(initial_state, wires=wires) - for i, excitation in enumerate(excitations): - if len(excitation) == 4: - qml.DoubleExcitation(theta[i], wires=excitation) - else: - qml.SingleExcitation(theta[i], wires=excitation) - return qml.expval(H2mol) - - -def cost_fn(param): - return circuit_VQE(param, initial_state=wf_hf) - - -############################################################################## -# Next, we create the VQE optimizer, initialize the variational parameters and run the VQE optimization. -import optax -import jax -jax.config.update("jax_enable_x64", True) - -opt = optax.sgd(learning_rate=0.4) # sgd stands for StochasticGradientDescent -theta = jnp.array(jnp.zeros(len(excitations))) -delta_E, iteration = 10, 0 -results_hf = [] -opt_state = opt.init(theta) -prev_energy = cost_fn(theta) - -# run the VQE optimization loop until convergence threshold is reached -while abs(delta_E) > 1e-5: - gradient = jax.grad(cost_fn)(theta) - updates, opt_state = opt.update(gradient, opt_state) - theta = optax.apply_updates(theta, updates) - new_energy = cost_fn(theta) - delta_E = new_energy - prev_energy - prev_energy = new_energy - results_hf.append(new_energy) - if len(results_hf) % 5 == 0: - print(f"Step = {len(results_hf)}, Energy = {new_energy:.6f} Ha") -print(f"Starting with HF state took {len(results_hf)} iterations until convergence.") - -############################################################################## -# And compare with how things go when you run it with the CISD initial state: - - -def cost_fn_cisd(param): - return circuit_VQE(param, initial_state=wf_cisd) - - -theta = jnp.array(jnp.zeros(len(excitations))) -delta_E, iteration = 10, 0 -results_cisd = [] -opt_state = opt.init(theta) -prev_energy = cost_fn_cisd(theta) - -while abs(delta_E) > 1e-5: - gradient = jax.grad(cost_fn_cisd)(theta) - updates, opt_state = opt.update(gradient, opt_state) - theta = optax.apply_updates(theta, updates) - new_energy = cost_fn_cisd(theta) - delta_E = new_energy - prev_energy - prev_energy = new_energy - results_cisd.append(new_energy) - if len(results_cisd) % 5 == 0: - print(f"Step = {len(results_cisd)}, Energy = {new_energy:.6f} Ha") -print( - f"Starting with CISD state took {len(results_cisd)} iterations until convergence." -) - -############################################################################## -# Let's visualize the comparison between the two initial states, and see that indeed -# we get to the ground state much faster by starting with the CISD state. - -import matplotlib.pyplot as plt - -fig, ax = plt.subplots() -ax.plot(range(len(results_hf)), results_hf, color="r", marker="o", label="wf_hf") -ax.plot(range(len(results_cisd)), results_cisd, color="b", marker="o", label="wf_cisd") -ax.legend(fontsize=16) -ax.tick_params(axis="both", labelsize=16) -ax.set_xlabel("Iteration", fontsize=20) -ax.set_ylabel("Energy, Ha", fontsize=20) -plt.tight_layout() -plt.show() - -############################################################################## -# Indeed, the CISD state significantly shortens the VQE runtime. -# -# It is sometimes possible to foresee the extent of this speed-up of a particular initial state -# by computing its overlap with the ground state--a traditional metric of success for initial -# states in quantum algorithms. Because in our examples the states are regular arrays, computing an -# overlap between different states is as easy as computing a dot product - -print(np.dot(wf_cisd, wf_hf).real) -print(np.dot(wf_ccsd, wf_hf).real) -print(np.dot(wf_cisd, wf_ccsd).real) - -############################################################################## -# In this particular case of :math:`\text{H}_3^+,` even CISD gives the exact wavefunction, hence both overlaps -# with the HF state are identical. In more correlated molecules, overlaps will show that the more -# multireference methods DMRG and SHCI are farther away from the Hartree-Fock state, -# allowing them to perform better (you can check this by printing the overlaps with -# DMRG and SHCI in a more correlated molecule). If a ground state in such a case was known, -# the overlap to it could tell us directly the quality of the initial state. - -############################################################################## -# Conclusion -# ----------- -# This demo shows how to import initial states from outputs of traditional quantum chemistry methods -# for use in PennyLane. We showcased simple workflows for how to run -# a variety of state-of-the-art post-Hartree-Fock methods, from libraries such as -# `PySCF `_, -# `Block2 `_ and -# `Dice `_, to generate outputs that can then be -# converted to PennyLane's state vector format with a single line of code. With these -# initial states, we use the example of VQE to demonstrate how a better choice -# of initial state can lead to improved algorithmic performance. For the molecule -# used in our example, the CISD state was sufficient: however, in more correlated -# molecules, DMRG and SHCI initial states typically provide the best speed-ups. -# -# About the author -# ---------------- -# +r""" + +Initial state preparation for quantum chemistry +=============================================== + +A high-quality initial state can significantly reduce the runtime of many quantum algorithms. From +the variational quantum eigensolver (VQE) to quantum phase estimation (QPE) and even the recent +`intermediate-scale quantum (ISQ) `_ algorithms, obtaining the ground state of a chemical system requires +a good initial state. For instance, in the case of VQE, a good initial state directly translates into fewer +optimization steps. In QPE, the probability of measuring the ground-state energy is directly +proportional to the overlap squared of the initial and ground states. Even beyond quantum chemistry, +good initial guesses are important for algorithms like quantum approximate optimization (QAOA) +and Grover search. + +Much like searching for a needle in a haystack, there are a lot of things you might try +to prepare a good guess for the ground state in the high-dimensional Hilbert space. In this +tutorial, we show how to use traditional computational chemistry techniques to +get a good initial state. Such an initial state will not be exactly +the ground state, but it will certainly be better than the standard guess of a computational +basis state :math:`|0\rangle^{\otimes N}` or the Hartree-Fock state. + +.. figure:: ../_static/demonstration_assets/initial_state_preparation/qchem_input_states.png + :align: center + :width: 65% + :target: javascript:void(0) + +Importing initial states +------------------------ +We can import initial states obtained from several post-Hartree-Fock quantum chemistry methods +to PennyLane. These methods are incredibly diverse in terms of their outputs, not always returning +an object that is easy to turn into a PennyLane state vector. + +We have already done this hard conversion work: all that you need to do is run these methods and +pass their outputs to PennyLane's :func:`~.pennylane.qchem.import_state` function. The currently +supported methods are configuration interaction with singles and doubles (CISD), coupled cluster +(CCSD), density-matrix renormalization group (DMRG) and semistochastic heat-bath configuration +interaction (SHCI). + +We now show how this works on the linear :math:`\text{H}_3^+` molecule as an example. + + +CISD states +~~~~~~~~~~~ +The first line of attack for initial state preparation is often a CISD calculation, performed with the `PySCF `_ +library. CISD is unsophisticated, but it is fast. It will not be of much help for strongly correlated molecules, +but it is better than Hartree-Fock. Here is the code example using the restricted Hartree-Fock +orbitals (:func:`~.pennylane.qchem.import_state` works for unrestricted orbitals, too). +""" + +from pyscf import gto, scf, ci +from pennylane.qchem import import_state +import numpy as np + +R = 1.2 +# create the H3+ molecule +mol = gto.M(atom=[["H", (0, 0, 0)], ["H", (0, 0, R)], ["H", (0, 0, 2 * R)]], charge=1) +# perfrom restricted Hartree-Fock and then CISD +myhf = scf.RHF(mol).run() +myci = ci.CISD(myhf).run() +wf_cisd = import_state(myci, tol=1e-1) +print(f"CISD-based state vector: \n{np.round(wf_cisd.real, 4)}") + +############################################################################## +# The final object, PennyLane's state vector ``wf_cisd``, is ready to be used as an +# initial state in a quantum circuit in PennyLane--we will showcase this below for VQE. +# +# Conversion for CISD to a state vector is straightforward: simply assign the PySCF-stored +# CI coefficients to appropriate entries in the state vector based on the determinant they correspond to. +# The second attribute passed to ``import_state()``, ``tol``, specifies the cutoff beyond +# which contributions to the wavefunctions are neglected. Internally, wavefunctions are +# stored in their Slater determinant representation. If their prefactor coefficient +# is below ``tol``, those determinants are dropped from the expression. + +############################################################################## +# CCSD states +# ~~~~~~~~~~~ +# The function :func:`~.pennylane.qchem.import_state` is general and works similarly for CCSD. It can +# automatically detect the input type and apply the appropriate conversion protocol. + +from pyscf import cc + +mycc = cc.CCSD(myhf).run() +wf_ccsd = import_state(mycc, tol=1e-1) +print(f"CCSD-based state vector: \n{np.round(wf_ccsd.real, 4)}") + +############################################################################## +# For CCSD conversion, at present the exponential form is expanded and terms are collected **to +# second order** to obtain the CI coefficients. +# +# DMRG states +# ~~~~~~~~~~~ +# For more complex or more correlated molecules, initial states from DMRG or +# SHCI will be better options. DMRG calculations involve using the library `Block2 `_, +# which can be installed with ``pip``: +# +# .. code-block:: bash +# +# pip install block2 +# +# The DMRG calculation is run on top of the molecular orbitals obtained by Hartree-Fock, +# stored in the ``myhf`` object, which we can reuse from before. +# +# .. code-block:: python +# +# from pyscf import mcscf +# from pyblock2.driver.core import DMRGDriver, SymmetryTypes +# from pyblock2._pyscf.ao2mo import integrals as itg +# +# # obtain molecular integrals and other parameters for DMRG +# mc = mcscf.CASCI(myhf, mol.nao, mol.nelectron) +# ncas, n_elec, spin, ecore, h1e, g2e, orb_sym = \ +# itg.get_rhf_integrals(myhf, mc.ncore, mc.ncas, g2e_symm=8) +# +# # initialize the DMRG solver, Hamiltonian (as matrix-product operator, MPO) and +# # state (as matrix-product state, MPS) +# driver = DMRGDriver(scratch="./dmrg_temp", symm_type=SymmetryTypes.SZ) +# driver.initialize_system(n_sites=ncas, n_elec=n_elec, spin=spin, orb_sym=orb_sym) +# mpo = driver.get_qc_mpo(h1e=h1e, g2e=g2e, ecore=ecore, iprint=0) +# ket = driver.get_random_mps(tag="GS") +# +# # execute DMRG by modifying the ket state in-place to minimize the energy +# driver.dmrg(mpo, ket, n_sweeps=30,bond_dims=[100,200],\ +# noises=[1e-3,1e-5],thrds=[1e-6,1e-7],tol=1e-6) +# +# # post-process the MPS to get an initial state +# dets, coeffs = driver.get_csf_coefficients(ket, iprint=0) +# dets = dets.tolist() +# wf_dmrg = import_state((dets, coeffs), tol=1e-1) +# print(f"DMRG-based state vector: \n {np.round(wf_dmrg, 4)}") +# +# .. code-block:: bash +# +# DMRG-based state vector +# [ 0. 0. 0. 0. 0. 0. 0. 0. 0. +# 0. 0. 0. -0.2243 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.9745 0. 0. 0. 0. 0. +# 0. 0. 0. 0. 0. 0. 0. 0. 0. +# 0. ] + +############################################################################## +# The crucial part is calling ``get_csf_coefficients()`` on the solution stored in +# MPS form in the ``ket``. This triggers an internal reconstruction calculation that +# converts the MPS to the sum of Slater determinants form, returning the output +# as a tuple ``(array([int]), array(float]))``. The first element expresses a given Slater +# determinant using Fock occupation vectors of length equal to the number of spatial +# orbitals. In Block2 notation, the entries can be ``0`` (orbital unoccupied), ``1`` (orbital occupied with spin-up +# electron), ``2`` (occupied with spin-down), and ``3`` (doubly occupied). The first +# element, ``array([int])``, must be converted to ``list`` +# for :func:`~.pennylane.qchem.import_state` to accept it. +# The second element stores the CI coefficients. +# +# In principle, this functionality can be used to generate any initial state, provided +# the user specifies a list of Slater determinants and their coefficients in this form. +# Let's take this opportunity to create the Hartree-Fock initial state, to compare the +# other states against it later on. + +hf_primer = ([[3, 0, 0]], np.array([1.0])) +wf_hf = import_state(hf_primer) + +############################################################################## +# SHCI states +# ~~~~~~~~~~~ +# +# The SHCI calculations utilize the library `Dice `_, and can be run +# using PySCF through the interface module `SHCI-SCF `_. +# For Dice, the execution process is similar to that of DMRG: +# +# .. code-block:: python +# +# from pyscf.shciscf import shci +# +# # prepare PySCF CASCI object, whose solver will be the SHCI method +# ncas, nelecas_a, nelecas_b = mol.nao, mol.nelectron // 2, mol.nelectron // 2 +# myshci = mcscf.CASCI(myhf, ncas, (nelecas_a, nelecas_b)) +# +# # set up essentials for the SHCI solver +# output_file = f"shci_output.out" +# myshci.fcisolver = shci.SHCI(myhf.mol) +# myshci.fcisolver.outputFile = output_file +# +# # execute SHCI through the PySCF interface +# e_tot, e_ci, ss, mo_coeff, mo_energies = myshci.kernel(verbose=5) +# +# # post-process the shci_output.out to extract the wave function +# # results and create the tuple of dets (list([str])) and coeffs (array([float])) +# # shci_data = (dets, coeffs) +# wf_shci = import_state(shci_data, tol=1e-1) +# print(f"SHCI-based state vector\n{wf_shci}") +# +# .. code-block:: bash +# +# SHCI-based state vector +# [ 0. 0. 0. 0. 0. 0. 0. 0. 0. +# 0. 0. 0. 0.2243 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.9745 0. 0. 0. 0. 0. +# 0. 0. 0. 0. 0. 0. 0. 0. 0. +# 0. ] + +############################################################################## +# The Dice output file prints determinants using symbols ``0`` (unoccupied orbital), +# ``a`` and ``b`` (orbital occupied with spin-up and spin-down electron, respectively), +# and ``2`` (doubly occupied orbital). These determinant outputs, and corresponding +# coefficients, should be extracted and arranged as ``(list([str]), array(float]))``, +# where each string combines all the determinant symbols ``0, a, b, 2`` for a single +# determinant with no spaces. For example, for the HF state we created in the DMRG section, +# the SHCI output should read ``([["200"]], np.array([1.]))`` + +############################################################################## +# Application: speed up VQE +# ------------------------- +# Let us now demonstrate how the choice of a better initial state shortens the runtime +# of VQE for obtaining the ground-state energy of a molecule. As a first step, create our +# linear :math:`\text{H}_3^+` molecule, a device, and a simple VQE circuit with single and double excitations: + +import pennylane as qml +from pennylane import qchem +from jax import numpy as jnp + +# generate the molecular Hamiltonian for H3+ +symbols = ["H", "H", "H"] +geometry = jnp.array([[0, 0, 0], [0, 0, R/0.529], [0, 0, 2*R/0.529]]) +molecule = qchem.Molecule(symbols, geometry, charge=1) + +H2mol, qubits = qchem.molecular_hamiltonian(molecule) +wires = list(range(qubits)) +dev = qml.device("default.qubit", wires=qubits) + +# create all possible excitations in H3+ +singles, doubles = qchem.excitations(2, qubits) +excitations = singles + doubles + +############################################################################## +# Now let's run VQE with the Hartree-Fock initial state. We first build the VQE circuit: + + +@qml.qnode(dev) +def circuit_VQE(theta, initial_state): + qml.StatePrep(initial_state, wires=wires) + for i, excitation in enumerate(excitations): + if len(excitation) == 4: + qml.DoubleExcitation(theta[i], wires=excitation) + else: + qml.SingleExcitation(theta[i], wires=excitation) + return qml.expval(H2mol) + + +def cost_fn(param): + return circuit_VQE(param, initial_state=wf_hf) + + +############################################################################## +# Next, we create the VQE optimizer, initialize the variational parameters and run the VQE optimization. +import optax +import jax +jax.config.update("jax_enable_x64", True) + +opt = optax.sgd(learning_rate=0.4) # sgd stands for StochasticGradientDescent +theta = jnp.array(jnp.zeros(len(excitations))) +delta_E, iteration = 10, 0 +results_hf = [] +opt_state = opt.init(theta) +prev_energy = cost_fn(theta) + +# run the VQE optimization loop until convergence threshold is reached +while abs(delta_E) > 1e-5: + gradient = jax.grad(cost_fn)(theta) + updates, opt_state = opt.update(gradient, opt_state) + theta = optax.apply_updates(theta, updates) + new_energy = cost_fn(theta) + delta_E = new_energy - prev_energy + prev_energy = new_energy + results_hf.append(new_energy) + if len(results_hf) % 5 == 0: + print(f"Step = {len(results_hf)}, Energy = {new_energy:.6f} Ha") +print(f"Starting with HF state took {len(results_hf)} iterations until convergence.") + +############################################################################## +# And compare with how things go when you run it with the CISD initial state: + + +def cost_fn_cisd(param): + return circuit_VQE(param, initial_state=wf_cisd) + + +theta = jnp.array(jnp.zeros(len(excitations))) +delta_E, iteration = 10, 0 +results_cisd = [] +opt_state = opt.init(theta) +prev_energy = cost_fn_cisd(theta) + +while abs(delta_E) > 1e-5: + gradient = jax.grad(cost_fn_cisd)(theta) + updates, opt_state = opt.update(gradient, opt_state) + theta = optax.apply_updates(theta, updates) + new_energy = cost_fn_cisd(theta) + delta_E = new_energy - prev_energy + prev_energy = new_energy + results_cisd.append(new_energy) + if len(results_cisd) % 5 == 0: + print(f"Step = {len(results_cisd)}, Energy = {new_energy:.6f} Ha") +print( + f"Starting with CISD state took {len(results_cisd)} iterations until convergence." +) + +############################################################################## +# Let's visualize the comparison between the two initial states, and see that indeed +# we get to the ground state much faster by starting with the CISD state. + +import matplotlib.pyplot as plt + +fig, ax = plt.subplots() +ax.plot(range(len(results_hf)), results_hf, color="r", marker="o", label="wf_hf") +ax.plot(range(len(results_cisd)), results_cisd, color="b", marker="o", label="wf_cisd") +ax.legend(fontsize=16) +ax.tick_params(axis="both", labelsize=16) +ax.set_xlabel("Iteration", fontsize=20) +ax.set_ylabel("Energy, Ha", fontsize=20) +plt.tight_layout() +plt.show() + +############################################################################## +# Indeed, the CISD state significantly shortens the VQE runtime. +# +# It is sometimes possible to foresee the extent of this speed-up of a particular initial state +# by computing its overlap with the ground state--a traditional metric of success for initial +# states in quantum algorithms. Because in our examples the states are regular arrays, computing an +# overlap between different states is as easy as computing a dot product + +print(np.dot(wf_cisd, wf_hf).real) +print(np.dot(wf_ccsd, wf_hf).real) +print(np.dot(wf_cisd, wf_ccsd).real) + +############################################################################## +# In this particular case of :math:`\text{H}_3^+,` even CISD gives the exact wavefunction, hence both overlaps +# with the HF state are identical. In more correlated molecules, overlaps will show that the more +# multireference methods DMRG and SHCI are farther away from the Hartree-Fock state, +# allowing them to perform better (you can check this by printing the overlaps with +# DMRG and SHCI in a more correlated molecule). If a ground state in such a case was known, +# the overlap to it could tell us directly the quality of the initial state. + +############################################################################## +# Conclusion +# ----------- +# This demo shows how to import initial states from outputs of traditional quantum chemistry methods +# for use in PennyLane. We showcased simple workflows for how to run +# a variety of state-of-the-art post-Hartree-Fock methods, from libraries such as +# `PySCF `_, +# `Block2 `_ and +# `Dice `_, to generate outputs that can then be +# converted to PennyLane's state vector format with a single line of code. With these +# initial states, we use the example of VQE to demonstrate how a better choice +# of initial state can lead to improved algorithmic performance. For the molecule +# used in our example, the CISD state was sufficient: however, in more correlated +# molecules, DMRG and SHCI initial states typically provide the best speed-ups. +# +# About the author +# ---------------- +# diff --git a/demonstrations/tutorial_jax_transformations.py b/demonstrations/tutorial_jax_transformations.py index 9c13c37271..476e8fa3a4 100644 --- a/demonstrations/tutorial_jax_transformations.py +++ b/demonstrations/tutorial_jax_transformations.py @@ -1,311 +1,311 @@ -r""" -Using JAX with PennyLane -======================== - -.. meta:: - :property="og:description": Learn how to use JAX with PennyLane. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/jax.png - -.. related:: - - tutorial_qubit_rotation Basic tutorial: qubit rotation - tutorial_vqe A brief overview of VQE - tutorial_vqt Variational Quantum Thermalizer - -*Author: Chase Roberts — Posted: 12 April 2021. Last updated: 12 April 2021.* - -JAX is an incredibly powerful scientific computing library that has been gaining traction in -both the physics and deep learning communities. While JAX was originally designed for -classical machine learning (ML), many of its transformations are also useful -for quantum machine learning (QML), and can be used directly with PennyLane. -""" - -############################################################################## -# .. figure:: ../_static/demonstration_assets/jax_logo/jax.png -# :width: 50% -# :align: center -# -# In this tutorial, we'll go over a number of JAX transformations and show how you can -# use them to build and optimize quantum circuits. We'll show examples of how to -# do gradient descent with ``jax.grad``, run quantum circuits in parallel -# using ``jax.vmap``, compile and optimize simulations with ``jax.jit``, -# and control and seed the random nature of quantum computer simulations -# with ``jax.random``. By the end of this tutorial you should feel just as comfortable -# transforming quantum computing programs with JAX as you do transforming your -# neural networks. -# -# If this is your first time reading PennyLane code, we recommend going through -# the :doc:`basic tutorial ` -# first. It's all in vanilla NumPy, so you should be able to -# easily transfer what you learn to JAX when you come back. -# -# With that said, we begin by importing PennyLane, JAX, the JAX-provided version of NumPy and -# set up a two-qubit device for computations. We'll be using the ``default.qubit`` device -# for the first part of this tutorial. - -import jax -import jax.numpy as jnp -import pennylane as qml - -# Added to silence some warnings. -jax.config.update("jax_enable_x64", True) - -dev = qml.device("default.qubit", wires=2) - -############################################################################## -# Let's start with a simple example circuit that generates a two-qubit entangled state, -# then evaluates the expectation value of the Pauli-Z operator on the first wire. - - -@qml.qnode(dev, interface="jax") -def circuit(param): - # These two gates represent our QML model. - qml.RX(param, wires=0) - qml.CNOT(wires=[0, 1]) - - # The expval here will be the "cost function" we try to minimize. - # Usually, this would be defined by the problem we want to solve, - # but for this example we'll just use a single PauliZ. - return qml.expval(qml.PauliZ(0)) - - -############################################################################## -# We can now execute the circuit just like any other python function. -print(f"Result: {repr(circuit(0.123))}") - -############################################################################## -# Notice that the output of the circuit is a JAX ``DeviceArray``. -# In fact, when we use the ``default.qubit`` device, the entire computation -# is done in JAX, so we can use all of the JAX tools out of the box! -# -# Now let's move on to an example of a transformation. The code we wrote above is entirely -# differentiable, so let's calculate its gradient with ``jax.grad``. -print("\nGradient Descent") -print("---------------") - -# We use jax.grad here to transform our circuit method into one -# that calcuates the gradient of the output relative to the input. - -grad_circuit = jax.grad(circuit) -print(f"grad_circuit(jnp.pi / 2): {grad_circuit(jnp.pi / 2):0.3f}") - -# We can then use this grad_circuit function to optimize the parameter value -# via gradient descent. -param = 0.123 # Some initial value. - -print(f"Initial param: {param:0.3f}") -print(f"Initial cost: {circuit(param):0.3f}") - -for _ in range(100): # Run for 100 steps. - param -= grad_circuit(param) # Gradient-descent update. - -print(f"Tuned param: {param:0.3f}") -print(f"Tuned cost: {circuit(param):0.3f}") - -############################################################################# -# And that's QML in a nutshell! If you've done classical machine learning before, -# the above training loop should feel very familiar to you. The only difference is -# that we used a quantum computer (or rather, a simulation of one) as part of our -# model and cost calculation. In the end, almost all QML problems involve tuning some -# parameters and minimizing some cost function, just like classical ML. -# While classical ML focuses on learning classical systems like language or vision, -# QML is most useful for learning about quantum systems. For example, -# :doc:`finding chemical ground states ` -# or learning to :doc:`sample thermal energy states `. - - -############################################################################## -# Batching and Evolutionary Strategies -# ------------------------------------- -# -# .. figure:: ../_static/demonstration_assets/jax_logo/jaxvmap.png -# :width: 50% -# :align: center -# -# We just showed how we can use gradient methods to learn a parameter value, -# but on real quantum computing hardware, calculating gradients can be really expensive and noisy. -# Another approach is to use `evolutionary strategies `__ -# (ES) to learn these parameters. -# Here, we will be using the ``jax.vmap`` `transform `__ -# to make running batches of circuits much easier. ``vmap`` essentially transforms a single quantum computer into -# multiple running in parallel! - -print("\n\nBatching and Evolutionary Strategies") -print("------------------------------------") - -# Create a vectorized version of our original circuit. -vcircuit = jax.vmap(circuit) - -# Now, we call the ``vcircuit`` with multiple parameters at once and get back a -# batch of expectations. -# This examples runs 3 quantum circuits in parallel. -batch_params = jnp.array([1.02, 0.123, -0.571]) - -batched_results = vcircuit(batch_params) -print(f"Batched result: {batched_results}") - -############################################################################## -# Let's now set up our ES training loop. The idea is pretty simple. First, we -# calculate the expected values of each of our parameters. The cost values -# then determine the "weight" of that example. The lower the cost, the larger the weight. -# These batches are then used to generate a new set of parameters. - -# Needed to do randomness with JAX. -# For more info on how JAX handles randomness, see the documentation. -# https://jax.readthedocs.io/en/latest/jax.random.html -key = jax.random.PRNGKey(0) - -# Generate our first set of samples. -params = jax.random.normal(key, (100,)) -mean = jnp.average(params) -var = 1.0 -print(f"Initial value: {mean:0.3f}") -print(f"Initial cost: {circuit(mean):0.3f}") - -for _ in range(200): - # In this line, we run all 100 circuits in parallel. - costs = vcircuit(params) - - # Use exp(-x) here since the costs could be negative. - weights = jnp.exp(-costs) - mean = jnp.average(params, weights=weights) - - # We decrease the variance as we converge to a solution. - var = var * 0.97 - - # Split the PRNGKey to generate a new set of random samples. - key, split = jax.random.split(key) - params = jax.random.normal(split, (100,)) * var + mean - -print(f"Final value: {mean:0.3f}") -print(f"Final cost: {circuit(mean):0.3f}") - - -############################################################################# -# How to use jax.jit: Compiling Circuit Execution -# ----------------------------------------------- -# .. figure:: ../_static/demonstration_assets/jax_logo/jaxjit.png -# :width: 50% -# :align: center -# -# JAX is built on top of `XLA `__, a powerful -# numerics library that can optimize and cross compile computations to different hardware, -# including CPUs, GPUs, etc. JAX can compile its computation to XLA via the ``jax.jit`` -# `transform. `__ -# -# When compiling an XLA program, the compiler will do several rounds of optimization -# passes to enhance the performance of the computation. Because of this compilation overhead, -# you'll generally find the first time calling the function to be slow, but all subsequent -# calls are much, much faster. You'll likely want to do it if you're running -# the same circuit over and over but with different parameters, like you would find in almost -# all variational quantum algorithms. - - -print("\n\nJit Example") -print("-----------") - - -@qml.qnode(dev, interface="jax") -def circuit(param): - qml.RX(param, wires=0) - qml.CNOT(wires=[0, 1]) - return qml.expval(qml.PauliZ(0)) - - -# Compiling your circuit with JAX is very easy, just add jax.jit! -jit_circuit = jax.jit(circuit) - -import time - -# No jit. -start = time.time() -# JAX runs async, so .block_until_ready() blocks until the computation -# is actually finished. You'll only need to use this if you're doing benchmarking. -circuit(0.123).block_until_ready() -no_jit_time = time.time() - start - -# First call with jit. -start = time.time() -jit_circuit(0.123).block_until_ready() -first_time = time.time() - start - -# Second call with jit. -start = time.time() -jit_circuit(0.123).block_until_ready() -second_time = time.time() - start - - -print(f"No jit time: {no_jit_time:0.8f} seconds") -# Compilation overhead will make the first call slower than without jit... -print(f"First run time: {first_time:0.8f} seconds") -# ... but the second run time is >100x faster than the first! -print(f"Second run time: {second_time:0.8f} seconds") - - -# You can see that for the cost of some compilation overhead, we can -# greatly increase our performance of our simulation by orders of magnitude. - -############################################################################## -# Shots and Sampling with JAX -# ---------------------------- -# -# JAX was designed to enable experiments to be as repeatable as possible. Because of this, -# JAX requires us to seed all randomly generated values (as you saw in the above -# batching example). Sadly, the universe doesn't allow us to seed real quantum computers, -# so if we want our JAX to mimic a real device, we'll have to handle randomness ourselves. -# -# To learn more about how JAX handles randomness, visit their -# `documentation site. `__ -# -# .. note:: -# This example only applies if you are using ``jax.jit``. Otherwise, PennyLane -# automatically seeds and resets the random-number-generator for you on each call. -# -# To set the random number generating key, you'll have to pass the ``jax.random.PRNGKey`` -# when constructing the device. Because of this, if you want to use ``jax.jit`` with randomness, -# the device construction will have to happen within that jitted method. - -print("\n\nRandomness") -print("----------") - - -# Let's create our circuit with randomness and compile it with jax.jit. -@jax.jit -def circuit(key, param): - # Notice how the device construction now happens within the jitted method. - dev = qml.device("default.qubit", wires=2, shots=10, seed=key) - - # Now we can create our qnode within the circuit function. - @qml.qnode(dev, interface="jax", diff_method=None) - def my_circuit(): - qml.RX(param, wires=0) - qml.CNOT(wires=[0, 1]) - return qml.sample(qml.PauliZ(0)) - - return my_circuit() - - -key1 = jax.random.PRNGKey(0) -key2 = jax.random.PRNGKey(1) - -# Notice that the first two runs return exactly the same results, -print(f"key1: {circuit(key1, jnp.pi/2)}") -print(f"key1: {circuit(key1, jnp.pi/2)}") - -# The second run has different results. -print(f"key2: {circuit(key2, jnp.pi/2)}") - -################################################ -# Closing Remarks -# ---------------- -# By now, using JAX with PennyLane should feel very natural. They -# complement each other very nicely; JAX with its powerful transforms, and PennyLane -# with its easy access to quantum computers. We're still in early days of -# development, but we hope to continue to grow our ecosystem around JAX, -# and by extension, grow JAX into quantum computing and quantum machine learning. -# The future looks bright for this field, and we're excited to see what you build! -# -# -# About the author -# ---------------- -# +r""" +Using JAX with PennyLane +======================== + +.. meta:: + :property="og:description": Learn how to use JAX with PennyLane. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/jax.png + +.. related:: + + tutorial_qubit_rotation Basic tutorial: qubit rotation + tutorial_vqe A brief overview of VQE + tutorial_vqt Variational Quantum Thermalizer + +*Author: Chase Roberts — Posted: 12 April 2021. Last updated: 12 April 2021.* + +JAX is an incredibly powerful scientific computing library that has been gaining traction in +both the physics and deep learning communities. While JAX was originally designed for +classical machine learning (ML), many of its transformations are also useful +for quantum machine learning (QML), and can be used directly with PennyLane. +""" + +############################################################################## +# .. figure:: ../_static/demonstration_assets/jax_logo/jax.png +# :width: 50% +# :align: center +# +# In this tutorial, we'll go over a number of JAX transformations and show how you can +# use them to build and optimize quantum circuits. We'll show examples of how to +# do gradient descent with ``jax.grad``, run quantum circuits in parallel +# using ``jax.vmap``, compile and optimize simulations with ``jax.jit``, +# and control and seed the random nature of quantum computer simulations +# with ``jax.random``. By the end of this tutorial you should feel just as comfortable +# transforming quantum computing programs with JAX as you do transforming your +# neural networks. +# +# If this is your first time reading PennyLane code, we recommend going through +# the :doc:`basic tutorial ` +# first. It's all in vanilla NumPy, so you should be able to +# easily transfer what you learn to JAX when you come back. +# +# With that said, we begin by importing PennyLane, JAX, the JAX-provided version of NumPy and +# set up a two-qubit device for computations. We'll be using the ``default.qubit`` device +# for the first part of this tutorial. + +import jax +import jax.numpy as jnp +import pennylane as qml + +# Added to silence some warnings. +jax.config.update("jax_enable_x64", True) + +dev = qml.device("default.qubit", wires=2) + +############################################################################## +# Let's start with a simple example circuit that generates a two-qubit entangled state, +# then evaluates the expectation value of the Pauli-Z operator on the first wire. + + +@qml.qnode(dev, interface="jax") +def circuit(param): + # These two gates represent our QML model. + qml.RX(param, wires=0) + qml.CNOT(wires=[0, 1]) + + # The expval here will be the "cost function" we try to minimize. + # Usually, this would be defined by the problem we want to solve, + # but for this example we'll just use a single PauliZ. + return qml.expval(qml.PauliZ(0)) + + +############################################################################## +# We can now execute the circuit just like any other python function. +print(f"Result: {repr(circuit(0.123))}") + +############################################################################## +# Notice that the output of the circuit is a JAX ``DeviceArray``. +# In fact, when we use the ``default.qubit`` device, the entire computation +# is done in JAX, so we can use all of the JAX tools out of the box! +# +# Now let's move on to an example of a transformation. The code we wrote above is entirely +# differentiable, so let's calculate its gradient with ``jax.grad``. +print("\nGradient Descent") +print("---------------") + +# We use jax.grad here to transform our circuit method into one +# that calcuates the gradient of the output relative to the input. + +grad_circuit = jax.grad(circuit) +print(f"grad_circuit(jnp.pi / 2): {grad_circuit(jnp.pi / 2):0.3f}") + +# We can then use this grad_circuit function to optimize the parameter value +# via gradient descent. +param = 0.123 # Some initial value. + +print(f"Initial param: {param:0.3f}") +print(f"Initial cost: {circuit(param):0.3f}") + +for _ in range(100): # Run for 100 steps. + param -= grad_circuit(param) # Gradient-descent update. + +print(f"Tuned param: {param:0.3f}") +print(f"Tuned cost: {circuit(param):0.3f}") + +############################################################################# +# And that's QML in a nutshell! If you've done classical machine learning before, +# the above training loop should feel very familiar to you. The only difference is +# that we used a quantum computer (or rather, a simulation of one) as part of our +# model and cost calculation. In the end, almost all QML problems involve tuning some +# parameters and minimizing some cost function, just like classical ML. +# While classical ML focuses on learning classical systems like language or vision, +# QML is most useful for learning about quantum systems. For example, +# :doc:`finding chemical ground states ` +# or learning to :doc:`sample thermal energy states `. + + +############################################################################## +# Batching and Evolutionary Strategies +# ------------------------------------- +# +# .. figure:: ../_static/demonstration_assets/jax_logo/jaxvmap.png +# :width: 50% +# :align: center +# +# We just showed how we can use gradient methods to learn a parameter value, +# but on real quantum computing hardware, calculating gradients can be really expensive and noisy. +# Another approach is to use `evolutionary strategies `__ +# (ES) to learn these parameters. +# Here, we will be using the ``jax.vmap`` `transform `__ +# to make running batches of circuits much easier. ``vmap`` essentially transforms a single quantum computer into +# multiple running in parallel! + +print("\n\nBatching and Evolutionary Strategies") +print("------------------------------------") + +# Create a vectorized version of our original circuit. +vcircuit = jax.vmap(circuit) + +# Now, we call the ``vcircuit`` with multiple parameters at once and get back a +# batch of expectations. +# This examples runs 3 quantum circuits in parallel. +batch_params = jnp.array([1.02, 0.123, -0.571]) + +batched_results = vcircuit(batch_params) +print(f"Batched result: {batched_results}") + +############################################################################## +# Let's now set up our ES training loop. The idea is pretty simple. First, we +# calculate the expected values of each of our parameters. The cost values +# then determine the "weight" of that example. The lower the cost, the larger the weight. +# These batches are then used to generate a new set of parameters. + +# Needed to do randomness with JAX. +# For more info on how JAX handles randomness, see the documentation. +# https://jax.readthedocs.io/en/latest/jax.random.html +key = jax.random.PRNGKey(0) + +# Generate our first set of samples. +params = jax.random.normal(key, (100,)) +mean = jnp.average(params) +var = 1.0 +print(f"Initial value: {mean:0.3f}") +print(f"Initial cost: {circuit(mean):0.3f}") + +for _ in range(200): + # In this line, we run all 100 circuits in parallel. + costs = vcircuit(params) + + # Use exp(-x) here since the costs could be negative. + weights = jnp.exp(-costs) + mean = jnp.average(params, weights=weights) + + # We decrease the variance as we converge to a solution. + var = var * 0.97 + + # Split the PRNGKey to generate a new set of random samples. + key, split = jax.random.split(key) + params = jax.random.normal(split, (100,)) * var + mean + +print(f"Final value: {mean:0.3f}") +print(f"Final cost: {circuit(mean):0.3f}") + + +############################################################################# +# How to use jax.jit: Compiling Circuit Execution +# ----------------------------------------------- +# .. figure:: ../_static/demonstration_assets/jax_logo/jaxjit.png +# :width: 50% +# :align: center +# +# JAX is built on top of `XLA `__, a powerful +# numerics library that can optimize and cross compile computations to different hardware, +# including CPUs, GPUs, etc. JAX can compile its computation to XLA via the ``jax.jit`` +# `transform. `__ +# +# When compiling an XLA program, the compiler will do several rounds of optimization +# passes to enhance the performance of the computation. Because of this compilation overhead, +# you'll generally find the first time calling the function to be slow, but all subsequent +# calls are much, much faster. You'll likely want to do it if you're running +# the same circuit over and over but with different parameters, like you would find in almost +# all variational quantum algorithms. + + +print("\n\nJit Example") +print("-----------") + + +@qml.qnode(dev, interface="jax") +def circuit(param): + qml.RX(param, wires=0) + qml.CNOT(wires=[0, 1]) + return qml.expval(qml.PauliZ(0)) + + +# Compiling your circuit with JAX is very easy, just add jax.jit! +jit_circuit = jax.jit(circuit) + +import time + +# No jit. +start = time.time() +# JAX runs async, so .block_until_ready() blocks until the computation +# is actually finished. You'll only need to use this if you're doing benchmarking. +circuit(0.123).block_until_ready() +no_jit_time = time.time() - start + +# First call with jit. +start = time.time() +jit_circuit(0.123).block_until_ready() +first_time = time.time() - start + +# Second call with jit. +start = time.time() +jit_circuit(0.123).block_until_ready() +second_time = time.time() - start + + +print(f"No jit time: {no_jit_time:0.8f} seconds") +# Compilation overhead will make the first call slower than without jit... +print(f"First run time: {first_time:0.8f} seconds") +# ... but the second run time is >100x faster than the first! +print(f"Second run time: {second_time:0.8f} seconds") + + +# You can see that for the cost of some compilation overhead, we can +# greatly increase our performance of our simulation by orders of magnitude. + +############################################################################## +# Shots and Sampling with JAX +# ---------------------------- +# +# JAX was designed to enable experiments to be as repeatable as possible. Because of this, +# JAX requires us to seed all randomly generated values (as you saw in the above +# batching example). Sadly, the universe doesn't allow us to seed real quantum computers, +# so if we want our JAX to mimic a real device, we'll have to handle randomness ourselves. +# +# To learn more about how JAX handles randomness, visit their +# `documentation site. `__ +# +# .. note:: +# This example only applies if you are using ``jax.jit``. Otherwise, PennyLane +# automatically seeds and resets the random-number-generator for you on each call. +# +# To set the random number generating key, you'll have to pass the ``jax.random.PRNGKey`` +# when constructing the device. Because of this, if you want to use ``jax.jit`` with randomness, +# the device construction will have to happen within that jitted method. + +print("\n\nRandomness") +print("----------") + + +# Let's create our circuit with randomness and compile it with jax.jit. +@jax.jit +def circuit(key, param): + # Notice how the device construction now happens within the jitted method. + dev = qml.device("default.qubit", wires=2, shots=10, seed=key) + + # Now we can create our qnode within the circuit function. + @qml.qnode(dev, interface="jax", diff_method=None) + def my_circuit(): + qml.RX(param, wires=0) + qml.CNOT(wires=[0, 1]) + return qml.sample(qml.PauliZ(0)) + + return my_circuit() + + +key1 = jax.random.PRNGKey(0) +key2 = jax.random.PRNGKey(1) + +# Notice that the first two runs return exactly the same results, +print(f"key1: {circuit(key1, jnp.pi/2)}") +print(f"key1: {circuit(key1, jnp.pi/2)}") + +# The second run has different results. +print(f"key2: {circuit(key2, jnp.pi/2)}") + +################################################ +# Closing Remarks +# ---------------- +# By now, using JAX with PennyLane should feel very natural. They +# complement each other very nicely; JAX with its powerful transforms, and PennyLane +# with its easy access to quantum computers. We're still in early days of +# development, but we hope to continue to grow our ecosystem around JAX, +# and by extension, grow JAX into quantum computing and quantum machine learning. +# The future looks bright for this field, and we're excited to see what you build! +# +# +# About the author +# ---------------- +# diff --git a/demonstrations/tutorial_mapping.py b/demonstrations/tutorial_mapping.py index 8e88a64420..96a757a506 100644 --- a/demonstrations/tutorial_mapping.py +++ b/demonstrations/tutorial_mapping.py @@ -1,348 +1,348 @@ -r""" - -Mapping fermionic operators to qubit operators -============================================== - -Simulating quantum systems stands as one of the most anticipated applications of quantum -chemistry with the potential to transform our understanding of chemical and physical systems. These -simulations typically require mapping schemes that transform fermionic representations into qubit -representations. There are a variety of mapping schemes used in quantum computing but the -conventional ones are the Jordan-Wigner, Parity, and Bravyi-Kitaev transformations [#Tranter]_. In -this demo, you will learn about these mapping schemes and their implementation in PennyLane. You -will also learn how to use these mappings in the context of computing the ground state energy -of a molecular system. - -.. figure:: ../_static/demonstration_assets/mapping/long_image.png - :align: center - :width: 80% - :target: javascript:void(0) - -Jordan-Wigner Mapping ---------------------- -The state of a quantum system in the `second quantized `__ -formalism is typically represented in the occupation-number basis. For fermions, the occupation -number is either :math:`0` or :math:`1` as a result of the Pauli exclusion principle. The -occupation-number basis states can be represented by a vector that is constructed by -applying the fermionic creation operators, :math:`a^\dagger,` to a vacuum state: - -.. math:: - - a^\dagger | 0 \rangle = | 1 \rangle. - -Similarly, electrons can be removed from a state by applying the fermionic annihilation -operators :math:`a:` - -.. math:: - - a | 1 \rangle = | 0 \rangle. - -An intuitive way to represent a fermionic system in the qubit basis is to store the fermionic -occupation numbers in qubit states. This requires constructing qubit creation and annihilation -operators that can be applied to an initial state to provide the desired occupation number state. -These operators are defined as - -.. math:: - - Q^{\dagger}_j = \frac{1}{2}(X_j - iY_j), - -and - -.. math:: - - Q_j = \frac{1}{2}(X_j + iY_j), - -where :math:`X` and :math:`Y` are Pauli operators. However, an important property of fermionic -creation and annihilation operators is the anti-commutation relations between them, which is not -preserved by directly using the analogous qubit operators. - -.. math:: - - [a^{\dagger}_j, a^{\dagger}_k]_+ = 0, \:\:\:\:\:\:\: [a_j, a_k]_+=0, \:\:\:\:\:\:\: [a_j, a^{\dagger}_k]_+ = \delta_{jk} I, - -where :math:`I` is the identity operator. These relations are essential for capturing the Pauli -exclusion principle which requires the fermionic wave function to be antisymmetric. The -anti-commutation relations between fermionic operators can be incorporated by adding a sequence of -Pauli :math:`Z` operators to the qubit operators (can you verify that?). According to these, -the fermionic creation and annihilation operators can be represented in the basis of Pauli operators: - -.. math:: - - a_{j}^{\dagger} = \frac{1}{2}(X_j - iY_j) \otimes_{k`__, -:math:`a_{5}^{\dagger},` which creates an electron in the fifth orbital. One -way to do this in PennyLane is to use :func:`~.pennylane.fermi.from_string`. We -then map the operator using :func:`~.pennylane.fermi.jordan_wigner`. -""" - -import pennylane as qml -from pennylane.fermi import from_string, jordan_wigner - -qubits = 10 -fermi_op = from_string("5+") -pauli_jw = jordan_wigner(fermi_op, ps=True) -pauli_jw - -############################################################################### -# The long sequence of the :math:`Z` operations can significantly increase the -# resources needed to implement the operator on quantum hardware, as it may require using entangling -# operations across multiple qubits, which can be challenging to implement efficiently. One way to -# avoid having such long tails of :math:`Z` operations is to work in the parity basis where the -# fermionic state stores the parity instead of the occupation number. -# -# Parity Mapping -# -------------- -# In the Parity representation, the state of a fermionic system is represented with a binary -# vector that corresponds to the parity of the orbitals. Note that parity for a set of orbitals -# is defined as the sum (mod 2) of the occupation numbers. Let's look at an example using the -# PennyLane function :func:`~.pennylane.qchem.hf_state` to obtain the state of a system with -# :math:`10` orbitals and :math:`5` electrons in both the occupation-number and parity bases. - -orbitals = 10 -electrons = 5 -state_number = qml.qchem.hf_state(electrons, orbitals) -state_parity = qml.qchem.hf_state(electrons, orbitals, basis="parity") - -print("State in occupation number basis:\n", state_number) -print("State in parity basis:\n", state_parity) - -############################################################################## -# Note that Parity mapping solves the non-locality problem of the parity information by storing -# the parity of orbital :math:`j` in qubit :math:`j` while the occupation information for the -# orbital is stored non-locally. In the parity basis, we cannot represent the creation or -# annihilation of a particle in orbital -# :math:`j` by simply operating with qubit creation or annihilation operators. In fact, the state of -# the :math:`(j − 1)`-th qubit provides information about the occupation state of qubit :math:`j` -# and whether we need to act with a creation or annihilation operator. Similarly, the creation or -# annihilation of a particle in qubit :math:`j` changes the parity of all qubits following it. -# As a result, the operator that is equivalent to creation and annihilation operators in -# the parity basis is a two-qubit operator acting on qubits :math:`j` and :math:`j − 1,` and -# an update operator which updates the parity of all qubits with index larger than j: -# -# .. math:: -# -# a_{j}^{\dagger} = \frac{1}{2}(Z_{j-1} \otimes X_j - iY_j) \otimes_{k>j} X_{k} -# -# and -# -# .. math:: -# -# a_{j} = \frac{1}{2}(Z_{j-1} \otimes X_j + iY_j) \otimes_{k>j} X_{k} -# -# Let's now look at an example where we map our fermionic operator :math:`a_{5}^{\dagger}` in a -# :math:`10` qubit system with -# Parity mapping using :func:`~.pennylane.fermi.parity_transform` in PennyLane. - -qubits = 10 -pauli_pr = qml.parity_transform(fermi_op, qubits, ps=True) -pauli_pr - -############################################################################## -# It is evident from this example that the Parity mapping doesn't improve upon the -# the Jordan-Wigner mapping as the long :math:`Z` strings are now replaced by :math:`X` -# strings. However, a very important advantage of using parity mapping is the ability to taper two -# qubits by leveraging symmetries of molecular Hamiltonians. You can find -# more information about this in our -# `qubit tapering `__ demo. -# Let's look at an example. - -generators = [qml.prod(*[qml.Z(i) for i in range(qubits-1)]), qml.Z(qubits-1)] -paulixops = qml.paulix_ops(generators, qubits) -paulix_sector = [1, 1] -taper_op = qml.taper(pauli_pr, generators, paulixops, paulix_sector) -qml.simplify(taper_op) - -############################################################################### -# Note that the tapered operator doesn't include qubit :math:`8` and :math:`9.` -# -# Bravyi-Kitaev Mapping -# --------------------- -# The Bravyi-Kitaev mapping aims to improve the linear scaling of the Jordan-Wigner and Parity -# mappings, in the number of qubits, -# by storing both the occupation number and the parity non-locally. In this formalism, even-labelled -# qubits store the occupation number of orbitals and odd-labelled qubits store parity -# through partial sums of occupation numbers. The corresponding creation and annihilation operators -# are defined `here `__. -# Let's use the :func:`~.pennylane.fermi.bravyi_kitaev` function to map our :math:`a_{5}^{\dagger}` -# operator. - -pauli_bk = qml.bravyi_kitaev(fermi_op, qubits, ps=True) -pauli_bk - -############################################################################## -# It is clear that the local nature of the transformation in the Bravyi-Kitaev mapping helps to -# improve the number of qubits. This advantage becomes even more clear if you -# work with a larger qubit -# system. We now use the Bravyi-Kitaev mapping to construct a qubit Hamiltonian and -# compute its ground state energy with the `VQE `__ -# method. -# -# Energy Calculation -# ------------------ -# To perform a VQE calculation for a desired Hamiltonian, we need an initial state typically set to -# a Hartree-Fock state, and a set of excitation operators to build an ansatz that allows us to -# obtain the ground state and then compute the expectation value of the Hamiltonian. It is important -# to note that the initial state and the excitation operators should be consistent with the -# mapping scheme used for obtaining the qubit Hamiltonian. Let's now build these three components -# for :math:`H_2` and compute its ground state energy. For this example, we will use the -# Bravyi-Kitaev transformation but similar calculations can be run with the other mappings. -# -# Molecular Hamiltonian -# ^^^^^^^^^^^^^^^^^^^^^ -# First, let's build the molecular Hamiltonian. This requires defining the atomic symbols and -# coordinates. - -from pennylane import qchem -from jax import numpy as jnp -import jax - -jax.config.update("jax_enable_x64", True) - -symbols = ['H', 'H'] -geometry = jnp.array([[0.0, 0.0, -0.69434785], - [0.0, 0.0, 0.69434785]]) - -mol = qchem.Molecule(symbols, geometry) - -############################################################################## -# We then use the :func:`~pennylane.qchem.fermionic_hamiltonian` function to build -# the fermionic Hamiltonian for our molecule. - -h_fermi = qchem.fermionic_hamiltonian(mol)() - -############################################################################## -# We now use :func:`~pennylane.fermi.bravyi_kitaev` to transform the fermionic Hamiltonian to its -# qubit representation. - -electrons = 2 -qubits = len(h_fermi.wires) -h_pauli = qml.bravyi_kitaev(h_fermi, qubits, tol=1e-16) - -############################################################################## -# Initial state -# ^^^^^^^^^^^^^ -# We now need the initial state that has the correct number of electrons. We use the Hartree-Fock -# state which can be obtained in a user-defined basis by using :func:`~.pennylane.qchem.hf_state` in -# PennyLane. For that, we need to specify the number of electrons, the number of orbitals and the -# desired mapping. - -hf_state = qchem.hf_state(electrons, qubits, basis="bravyi_kitaev") - -############################################################################## -# Excitation operators -# ^^^^^^^^^^^^^^^^^^^^ -# We now build our quantum circuit with the :class:`~.pennylane.UCCSD` ansatz. This ansatz is -# constructed with a set of single and double excitation operators. In PennyLane, we have -# :class:`~.pennylane.SingleExcitation` and :class:`~.pennylane.DoubleExcitation` operators which -# are very efficient, but they are only compatible with the Jordan-Wigner mapping. Here we construct -# the excitation operators manually. We start from the fermionic single and double excitation -# operators defined as [#Yordanov]_ -# -# .. math:: -# -# T_i^k(\theta) = \theta(a_k^{\dagger}a_i - a_i^{\dagger}a_k) -# -# and -# -# .. math:: -# -# T_{ij}^{kl}(\theta) = \theta(a_k^{\dagger}a_l^{\dagger}a_i a_j - -# a_i^{\dagger}a_j^{\dagger}a_k a_l), -# -# where :math:`\theta` is an adjustable parameter. We can easily construct these fermionic -# excitation operators in PennyLane and then map them to the qubit basis with -# :func:`~pennylane.fermi.bravyi_kitaev`, similar to the way we transformed the fermionic -# Hamiltonian. - -from pennylane.fermi import from_string - -singles, doubles = qchem.excitations(electrons, qubits) - -singles_fermi = [] -for ex in singles: - singles_fermi.append(from_string(f"{ex[1]}+ {ex[0]}-") - - from_string(f"{ex[0]}+ {ex[1]}-")) - -doubles_fermi = [] -for ex in doubles: - doubles_fermi.append(from_string(f"{ex[3]}+ {ex[2]}+ {ex[1]}- {ex[0]}-") - - from_string(f"{ex[0]}+ {ex[1]}+ {ex[2]}- {ex[3]}-")) - -############################################################################## -# The fermionic operators are now mapped to qubit operators. - -singles_pauli = [] -for op in singles_fermi: - singles_pauli.append(qml.bravyi_kitaev(op, qubits, ps=True)) - -doubles_pauli = [] -for op in doubles_fermi: - doubles_pauli.append(qml.bravyi_kitaev(op, qubits, ps=True)) - -############################################################################## -# Note that we need to exponentiate these operators to be able to use them in the circuit -# [#Yordanov]_. We also use a set of pre-defined parameters to construct the excitation gates. - -params = jnp.array([0.22347661, 0.0, 0.0]) - -dev = qml.device("default.qubit", wires=qubits) - -@qml.qnode(dev) -def circuit(params): - qml.BasisState(hf_state, wires=range(qubits)) - - for i, excitation in enumerate(doubles_pauli): - qml.exp((excitation * params[i] / 2).operation()), range(qubits) - - for j, excitation in enumerate(singles_pauli): - qml.exp((excitation * params[i + j + 1] / 2).operation()), range(qubits) - - return qml.expval(h_pauli) - -print('Energy =', circuit(params)) - -############################################################################## -# Using the above circuit, we produce the ground state energy of :math:`H_2` molecule. -# -# Conclusion -# --------------- -# In this demo, we learned about various mapping schemes available in PennyLane and how -# they can be used to convert fermionic operators to qubits operators. We also learned -# the pros and cons associated with each scheme. The Jordan-Wigner mapping provides an intuitive -# approach while parity mapping allows tapering qubits in molecular systems. However, these two -# methods usually give qubit operators with a long chain of Pauli operators, which makes them -# challenging to implement on quantum hardware. The Bravyi-Kitaev mapping, on the other hand, -# emphasizes locality and resource efficiency, making it an attractive option for certain -# applications. Through this demonstration, we recognize the importance of choosing an appropriate -# mapping scheme tailored to the specific problem at hand and the available quantum -# resources. Lastly, we showed how a user can employ these different mappings in ground state energy -# calculations through an example. We would like to encourage the interested readers to run -# calculations for different molecular systems and observe how the scaling in the number of qubits -# is influenced by the selected mapping techniques. -# -# References -# ---------- -# -# .. [#Tranter] -# -# A. Tranter, S. Sofia, *et al.*, "The Bravyi–Kitaev Transformation: -# Properties and Applications". `International Journal of Quantum Chemistry 115.19 (2015). -# `__ -# -# .. [#Yordanov] -# -# Y. S. Yordanov, *et al.*, "Efficient quantum circuits for quantum computational chemistry". -# `Physical Review A 102.6 (2020). -# `__ -# -# About the author -# ---------------- +r""" + +Mapping fermionic operators to qubit operators +============================================== + +Simulating quantum systems stands as one of the most anticipated applications of quantum +chemistry with the potential to transform our understanding of chemical and physical systems. These +simulations typically require mapping schemes that transform fermionic representations into qubit +representations. There are a variety of mapping schemes used in quantum computing but the +conventional ones are the Jordan-Wigner, Parity, and Bravyi-Kitaev transformations [#Tranter]_. In +this demo, you will learn about these mapping schemes and their implementation in PennyLane. You +will also learn how to use these mappings in the context of computing the ground state energy +of a molecular system. + +.. figure:: ../_static/demonstration_assets/mapping/long_image.png + :align: center + :width: 80% + :target: javascript:void(0) + +Jordan-Wigner Mapping +--------------------- +The state of a quantum system in the `second quantized `__ +formalism is typically represented in the occupation-number basis. For fermions, the occupation +number is either :math:`0` or :math:`1` as a result of the Pauli exclusion principle. The +occupation-number basis states can be represented by a vector that is constructed by +applying the fermionic creation operators, :math:`a^\dagger,` to a vacuum state: + +.. math:: + + a^\dagger | 0 \rangle = | 1 \rangle. + +Similarly, electrons can be removed from a state by applying the fermionic annihilation +operators :math:`a:` + +.. math:: + + a | 1 \rangle = | 0 \rangle. + +An intuitive way to represent a fermionic system in the qubit basis is to store the fermionic +occupation numbers in qubit states. This requires constructing qubit creation and annihilation +operators that can be applied to an initial state to provide the desired occupation number state. +These operators are defined as + +.. math:: + + Q^{\dagger}_j = \frac{1}{2}(X_j - iY_j), + +and + +.. math:: + + Q_j = \frac{1}{2}(X_j + iY_j), + +where :math:`X` and :math:`Y` are Pauli operators. However, an important property of fermionic +creation and annihilation operators is the anti-commutation relations between them, which is not +preserved by directly using the analogous qubit operators. + +.. math:: + + [a^{\dagger}_j, a^{\dagger}_k]_+ = 0, \:\:\:\:\:\:\: [a_j, a_k]_+=0, \:\:\:\:\:\:\: [a_j, a^{\dagger}_k]_+ = \delta_{jk} I, + +where :math:`I` is the identity operator. These relations are essential for capturing the Pauli +exclusion principle which requires the fermionic wave function to be antisymmetric. The +anti-commutation relations between fermionic operators can be incorporated by adding a sequence of +Pauli :math:`Z` operators to the qubit operators (can you verify that?). According to these, +the fermionic creation and annihilation operators can be represented in the basis of Pauli operators: + +.. math:: + + a_{j}^{\dagger} = \frac{1}{2}(X_j - iY_j) \otimes_{k`__, +:math:`a_{5}^{\dagger},` which creates an electron in the fifth orbital. One +way to do this in PennyLane is to use :func:`~.pennylane.fermi.from_string`. We +then map the operator using :func:`~.pennylane.fermi.jordan_wigner`. +""" + +import pennylane as qml +from pennylane.fermi import from_string, jordan_wigner + +qubits = 10 +fermi_op = from_string("5+") +pauli_jw = jordan_wigner(fermi_op, ps=True) +pauli_jw + +############################################################################### +# The long sequence of the :math:`Z` operations can significantly increase the +# resources needed to implement the operator on quantum hardware, as it may require using entangling +# operations across multiple qubits, which can be challenging to implement efficiently. One way to +# avoid having such long tails of :math:`Z` operations is to work in the parity basis where the +# fermionic state stores the parity instead of the occupation number. +# +# Parity Mapping +# -------------- +# In the Parity representation, the state of a fermionic system is represented with a binary +# vector that corresponds to the parity of the orbitals. Note that parity for a set of orbitals +# is defined as the sum (mod 2) of the occupation numbers. Let's look at an example using the +# PennyLane function :func:`~.pennylane.qchem.hf_state` to obtain the state of a system with +# :math:`10` orbitals and :math:`5` electrons in both the occupation-number and parity bases. + +orbitals = 10 +electrons = 5 +state_number = qml.qchem.hf_state(electrons, orbitals) +state_parity = qml.qchem.hf_state(electrons, orbitals, basis="parity") + +print("State in occupation number basis:\n", state_number) +print("State in parity basis:\n", state_parity) + +############################################################################## +# Note that Parity mapping solves the non-locality problem of the parity information by storing +# the parity of orbital :math:`j` in qubit :math:`j` while the occupation information for the +# orbital is stored non-locally. In the parity basis, we cannot represent the creation or +# annihilation of a particle in orbital +# :math:`j` by simply operating with qubit creation or annihilation operators. In fact, the state of +# the :math:`(j − 1)`-th qubit provides information about the occupation state of qubit :math:`j` +# and whether we need to act with a creation or annihilation operator. Similarly, the creation or +# annihilation of a particle in qubit :math:`j` changes the parity of all qubits following it. +# As a result, the operator that is equivalent to creation and annihilation operators in +# the parity basis is a two-qubit operator acting on qubits :math:`j` and :math:`j − 1,` and +# an update operator which updates the parity of all qubits with index larger than j: +# +# .. math:: +# +# a_{j}^{\dagger} = \frac{1}{2}(Z_{j-1} \otimes X_j - iY_j) \otimes_{k>j} X_{k} +# +# and +# +# .. math:: +# +# a_{j} = \frac{1}{2}(Z_{j-1} \otimes X_j + iY_j) \otimes_{k>j} X_{k} +# +# Let's now look at an example where we map our fermionic operator :math:`a_{5}^{\dagger}` in a +# :math:`10` qubit system with +# Parity mapping using :func:`~.pennylane.fermi.parity_transform` in PennyLane. + +qubits = 10 +pauli_pr = qml.parity_transform(fermi_op, qubits, ps=True) +pauli_pr + +############################################################################## +# It is evident from this example that the Parity mapping doesn't improve upon the +# the Jordan-Wigner mapping as the long :math:`Z` strings are now replaced by :math:`X` +# strings. However, a very important advantage of using parity mapping is the ability to taper two +# qubits by leveraging symmetries of molecular Hamiltonians. You can find +# more information about this in our +# `qubit tapering `__ demo. +# Let's look at an example. + +generators = [qml.prod(*[qml.Z(i) for i in range(qubits-1)]), qml.Z(qubits-1)] +paulixops = qml.paulix_ops(generators, qubits) +paulix_sector = [1, 1] +taper_op = qml.taper(pauli_pr, generators, paulixops, paulix_sector) +qml.simplify(taper_op) + +############################################################################### +# Note that the tapered operator doesn't include qubit :math:`8` and :math:`9.` +# +# Bravyi-Kitaev Mapping +# --------------------- +# The Bravyi-Kitaev mapping aims to improve the linear scaling of the Jordan-Wigner and Parity +# mappings, in the number of qubits, +# by storing both the occupation number and the parity non-locally. In this formalism, even-labelled +# qubits store the occupation number of orbitals and odd-labelled qubits store parity +# through partial sums of occupation numbers. The corresponding creation and annihilation operators +# are defined `here `__. +# Let's use the :func:`~.pennylane.fermi.bravyi_kitaev` function to map our :math:`a_{5}^{\dagger}` +# operator. + +pauli_bk = qml.bravyi_kitaev(fermi_op, qubits, ps=True) +pauli_bk + +############################################################################## +# It is clear that the local nature of the transformation in the Bravyi-Kitaev mapping helps to +# improve the number of qubits. This advantage becomes even more clear if you +# work with a larger qubit +# system. We now use the Bravyi-Kitaev mapping to construct a qubit Hamiltonian and +# compute its ground state energy with the `VQE `__ +# method. +# +# Energy Calculation +# ------------------ +# To perform a VQE calculation for a desired Hamiltonian, we need an initial state typically set to +# a Hartree-Fock state, and a set of excitation operators to build an ansatz that allows us to +# obtain the ground state and then compute the expectation value of the Hamiltonian. It is important +# to note that the initial state and the excitation operators should be consistent with the +# mapping scheme used for obtaining the qubit Hamiltonian. Let's now build these three components +# for :math:`H_2` and compute its ground state energy. For this example, we will use the +# Bravyi-Kitaev transformation but similar calculations can be run with the other mappings. +# +# Molecular Hamiltonian +# ^^^^^^^^^^^^^^^^^^^^^ +# First, let's build the molecular Hamiltonian. This requires defining the atomic symbols and +# coordinates. + +from pennylane import qchem +from jax import numpy as jnp +import jax + +jax.config.update("jax_enable_x64", True) + +symbols = ['H', 'H'] +geometry = jnp.array([[0.0, 0.0, -0.69434785], + [0.0, 0.0, 0.69434785]]) + +mol = qchem.Molecule(symbols, geometry) + +############################################################################## +# We then use the :func:`~pennylane.qchem.fermionic_hamiltonian` function to build +# the fermionic Hamiltonian for our molecule. + +h_fermi = qchem.fermionic_hamiltonian(mol)() + +############################################################################## +# We now use :func:`~pennylane.fermi.bravyi_kitaev` to transform the fermionic Hamiltonian to its +# qubit representation. + +electrons = 2 +qubits = len(h_fermi.wires) +h_pauli = qml.bravyi_kitaev(h_fermi, qubits, tol=1e-16) + +############################################################################## +# Initial state +# ^^^^^^^^^^^^^ +# We now need the initial state that has the correct number of electrons. We use the Hartree-Fock +# state which can be obtained in a user-defined basis by using :func:`~.pennylane.qchem.hf_state` in +# PennyLane. For that, we need to specify the number of electrons, the number of orbitals and the +# desired mapping. + +hf_state = qchem.hf_state(electrons, qubits, basis="bravyi_kitaev") + +############################################################################## +# Excitation operators +# ^^^^^^^^^^^^^^^^^^^^ +# We now build our quantum circuit with the :class:`~.pennylane.UCCSD` ansatz. This ansatz is +# constructed with a set of single and double excitation operators. In PennyLane, we have +# :class:`~.pennylane.SingleExcitation` and :class:`~.pennylane.DoubleExcitation` operators which +# are very efficient, but they are only compatible with the Jordan-Wigner mapping. Here we construct +# the excitation operators manually. We start from the fermionic single and double excitation +# operators defined as [#Yordanov]_ +# +# .. math:: +# +# T_i^k(\theta) = \theta(a_k^{\dagger}a_i - a_i^{\dagger}a_k) +# +# and +# +# .. math:: +# +# T_{ij}^{kl}(\theta) = \theta(a_k^{\dagger}a_l^{\dagger}a_i a_j - +# a_i^{\dagger}a_j^{\dagger}a_k a_l), +# +# where :math:`\theta` is an adjustable parameter. We can easily construct these fermionic +# excitation operators in PennyLane and then map them to the qubit basis with +# :func:`~pennylane.fermi.bravyi_kitaev`, similar to the way we transformed the fermionic +# Hamiltonian. + +from pennylane.fermi import from_string + +singles, doubles = qchem.excitations(electrons, qubits) + +singles_fermi = [] +for ex in singles: + singles_fermi.append(from_string(f"{ex[1]}+ {ex[0]}-") + - from_string(f"{ex[0]}+ {ex[1]}-")) + +doubles_fermi = [] +for ex in doubles: + doubles_fermi.append(from_string(f"{ex[3]}+ {ex[2]}+ {ex[1]}- {ex[0]}-") + - from_string(f"{ex[0]}+ {ex[1]}+ {ex[2]}- {ex[3]}-")) + +############################################################################## +# The fermionic operators are now mapped to qubit operators. + +singles_pauli = [] +for op in singles_fermi: + singles_pauli.append(qml.bravyi_kitaev(op, qubits, ps=True)) + +doubles_pauli = [] +for op in doubles_fermi: + doubles_pauli.append(qml.bravyi_kitaev(op, qubits, ps=True)) + +############################################################################## +# Note that we need to exponentiate these operators to be able to use them in the circuit +# [#Yordanov]_. We also use a set of pre-defined parameters to construct the excitation gates. + +params = jnp.array([0.22347661, 0.0, 0.0]) + +dev = qml.device("default.qubit", wires=qubits) + +@qml.qnode(dev) +def circuit(params): + qml.BasisState(hf_state, wires=range(qubits)) + + for i, excitation in enumerate(doubles_pauli): + qml.exp((excitation * params[i] / 2).operation()), range(qubits) + + for j, excitation in enumerate(singles_pauli): + qml.exp((excitation * params[i + j + 1] / 2).operation()), range(qubits) + + return qml.expval(h_pauli) + +print('Energy =', circuit(params)) + +############################################################################## +# Using the above circuit, we produce the ground state energy of :math:`H_2` molecule. +# +# Conclusion +# --------------- +# In this demo, we learned about various mapping schemes available in PennyLane and how +# they can be used to convert fermionic operators to qubits operators. We also learned +# the pros and cons associated with each scheme. The Jordan-Wigner mapping provides an intuitive +# approach while parity mapping allows tapering qubits in molecular systems. However, these two +# methods usually give qubit operators with a long chain of Pauli operators, which makes them +# challenging to implement on quantum hardware. The Bravyi-Kitaev mapping, on the other hand, +# emphasizes locality and resource efficiency, making it an attractive option for certain +# applications. Through this demonstration, we recognize the importance of choosing an appropriate +# mapping scheme tailored to the specific problem at hand and the available quantum +# resources. Lastly, we showed how a user can employ these different mappings in ground state energy +# calculations through an example. We would like to encourage the interested readers to run +# calculations for different molecular systems and observe how the scaling in the number of qubits +# is influenced by the selected mapping techniques. +# +# References +# ---------- +# +# .. [#Tranter] +# +# A. Tranter, S. Sofia, *et al.*, "The Bravyi–Kitaev Transformation: +# Properties and Applications". `International Journal of Quantum Chemistry 115.19 (2015). +# `__ +# +# .. [#Yordanov] +# +# Y. S. Yordanov, *et al.*, "Efficient quantum circuits for quantum computational chemistry". +# `Physical Review A 102.6 (2020). +# `__ +# +# About the author +# ---------------- diff --git a/demonstrations/tutorial_measurement_optimize.py b/demonstrations/tutorial_measurement_optimize.py index f43645c3ba..198b7d2bfd 100644 --- a/demonstrations/tutorial_measurement_optimize.py +++ b/demonstrations/tutorial_measurement_optimize.py @@ -1,844 +1,844 @@ -r""" -Measurement optimization -======================== - -.. meta:: - :property="og:description": Optimize and reduce the number of measurements required to evaluate a variational algorithm cost function. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/grouping.png - -.. related:: - - tutorial_vqe A brief overview of VQE - tutorial_quantum_chemistry Building molecular Hamiltonians - tutorial_qaoa_intro Intro to QAOA - -*Author: Josh Izaac — Posted: 18 January 2021. Last updated: 29 August 2023.* - -The variational quantum eigensolver (VQE) is the OG variational quantum algorithm. Harnessing -near-term quantum hardware to solve for the electronic structure of molecules, VQE is *the* -algorithm that sparked the variational circuit craze of the last 5 years, and holds great -promise for showcasing a quantum advantage on near-term quantum hardware. It has also inspired -other quantum algorithms such as the :doc:`Quantum Approximate Optimization Algorithm (QAOA) -`. - -To scale VQE beyond the regime of classical computation, however, we need to solve for the -ground state of increasingly larger molecules. A consequence is that the number of -measurements we need to make on the quantum hardware also grows polynomially—a huge bottleneck, -especially when quantum hardware access is limited and expensive. - -To mitigate this 'measurement problem', a plethora of recent research dropped over the course of -2019 and 2020 [#yen2020]_ [#izmaylov2019]_ [#huggins2019]_ [#gokhale2020]_ [#verteletskyi2020]_ , -exploring potential strategies to minimize the number of measurements required. In fact, by grouping -commuting terms of the Hamiltonian, we can significantly reduce the number of -measurements needed—in some cases, reducing the number of measurements by up to 90%! - -.. figure:: /_static/demonstration_assets/measurement_optimize/grouping.png - :width: 90% - :align: center - -In this demonstration, we revisit the VQE algorithm, see first-hand how the required number of -measurements scales as molecule size increases, and finally use these measurement optimization -strategies to minimize the number of measurements we need to make. These techniques are valuable -beyond just VQE, allowing you to add measurement optimization to your toolkit of techniques to -perform variational algorithms more efficiently. - -Revisiting VQE --------------- - -The study of :doc:`variational quantum algorithms ` was spearheaded -by the introduction of the :doc:`variational quantum eigensolver ` (VQE) algorithm in -2014 [#peruzzo2014]_. While classical variational techniques have been known for decades to estimate -the ground state energy of a molecule, VQE allowed this variational technique to be applied using -quantum computers. Since then, the field of variational quantum algorithms has evolved -significantly, with larger and more complex models being proposed (such as -:doc:`quantum neural networks `, :doc:`QGANs `, and -:doc:`variational classifiers `). However, quantum chemistry -remains one of the flagship use-cases for variational quantum algorithms, and VQE the standard-bearer. - -Part of the appeal of VQE lies within its simplicity. A circuit ansatz :math:`U(\theta)` is chosen -(typically the Unitary Coupled-Cluster Singles and Doubles -(:func:`~pennylane.templates.subroutines.UCCSD`) ansatz), and the qubit representation of the -molecular Hamiltonian is computed: - -.. math:: H = \sum_i c_i h_i, - -where :math:`h_i` are the terms of the Hamiltonian written as a tensor product of Pauli operators or the identity -acting on wire :math:`n,` :math:`P_n \in \{I, \sigma_x, \sigma_y, \sigma_z\}:` - -.. math:: h_i = \bigotimes_{n=0}^{N-1} P_n. - -(The :math:`h_i` product of Pauli terms is often referred to as a 'Pauli word' in the literature.) The cost -function of the VQE is then simply the expectation value of this Hamiltonian on the state obtained -after running the variational quantum circuit: - -.. math:: \text{cost}(\theta) = \langle 0 | U(\theta)^\dagger H U(\theta) | 0 \rangle. - -By using a classical optimizer to *minimize* this quantity, we can estimate -the ground state energy of the Hamiltonian :math:`H:` - -.. math:: H U(\theta_{min}) |0\rangle = E_{min} U(\theta_{min}) |0\rangle. - -In practice, when we are using quantum hardware to compute these expectation values we expand out -the Hamiltonian as its summation, resulting in separate expectation values that need to be calculated for each term: - -.. math:: - - \text{cost}(\theta) = \langle 0 | U(\theta)^\dagger \left(\sum_i c_i h_i\right) U(\theta) | 0 \rangle - = \sum_i c_i \langle 0 | U(\theta)^\dagger h_i U(\theta) | 0 \rangle. - -.. note:: - - How do we compute the qubit representation of the molecular Hamiltonian? This is a more - complicated story that involves applying a self-consistent field method (such as Hartree-Fock), - and then performing a fermionic-to-qubit mapping such as the Jordan-Wigner or Bravyi-Kitaev - transformations. - - For more details on this process, check out the :doc:`/demos/tutorial_quantum_chemistry` - tutorial. - -The measurement problem ------------------------ - -For small molecules, the VQE algorithm scales and performs exceedingly well. For example, for the -Hydrogen molecule :math:`\text{H}_2,` the final Hamiltonian in its qubit representation -has 15 terms that need to be measured. Let's obtain the Hamiltonian from -`PennyLane's dataset library `__ -to verify the number of terms. In this tutorial, we use the :func:`~.pennylane.data.load` -function to download the dataset of the molecule. - -""" - -import functools -import warnings -import jax -from jax import numpy as jnp -import pennylane as qml - -jax.config.update("jax_enable_x64", True) - -dataset = qml.data.load("qchem", molname="H2", bondlength=0.7)[0] -H, num_qubits = dataset.hamiltonian, len(dataset.hamiltonian.wires) - -print("Required number of qubits:", num_qubits) -print(H) - -############################################################################### -# Here, we can see that the Hamiltonian involves 15 terms, so we expect to compute 15 expectation values -# on hardware. Let's generate the cost function to check this. - -# Create a 4 qubit simulator -dev = qml.device("default.qubit", shots=1000, seed=904932) - -# number of electrons -electrons = 2 - -# Define the Hartree-Fock initial state for our variational circuit -initial_state = qml.qchem.hf_state(electrons, num_qubits) - -# Construct the UCCSD ansatz -singles, doubles = qml.qchem.excitations(electrons, num_qubits) -s_wires, d_wires = qml.qchem.excitations_to_wires(singles, doubles) -ansatz = functools.partial( - qml.UCCSD, init_state=initial_state, s_wires=s_wires, d_wires=d_wires -) - -# generate the cost function -@qml.qnode(dev, interface="jax") -def cost_circuit(params): - ansatz(params, wires=range(num_qubits)) - return qml.expval(H) - -############################################################################## -# If we evaluate this cost function, we can see that it corresponds to 15 different -# executions under the hood—one per expectation value: - -from jax import random as random -key, scale = random.PRNGKey(0), jnp.pi -params = random.normal(key, shape=(len(singles) + len(doubles),)) * scale -with qml.Tracker(dev) as tracker: # track the number of executions - print("Cost function value:", cost_circuit(params)) - -print("Number of quantum evaluations:", tracker.totals['executions']) - -############################################################################## -# How about a larger molecule? Let's try the -# `water molecule `__: - -dataset = qml.data.load('qchem', molname="H2O")[0] -H, num_qubits = dataset.hamiltonian, len(dataset.hamiltonian.wires) - -print("Required number of qubits:", num_qubits) -print("Number of Hamiltonian terms/required measurements:", len(H.terms()[0])) - -print("\n", H) - - -############################################################################## -# Simply going from two atoms in :math:`\text{H}_2` to three in :math:`\text{H}_2 \text{O}` -# resulted in over triple the number of qubits required and 1086 measurements that must be made! -# -# We can see that as the size of our molecule increases, we run into a problem: larger molecules -# result in Hamiltonians that not only require a larger number of qubits :math:`N` in their -# representation, but the number of terms in the Hamiltonian scales like -# :math:`\mathcal{O}(N^4)!` 😱😱😱 -# -# We can mitigate this somewhat by choosing smaller `basis sets -# `__ to represent the electronic structure -# wavefunction, however this would be done at the cost of solution accuracy, and doesn't reduce the number of -# measurements significantly enough to allow us to scale to classically intractable problems. -# -# .. figure:: /_static/demonstration_assets/measurement_optimize/n4.png -# :width: 70% -# :align: center -# -# The number of qubit Hamiltonian terms required to represent various molecules in the specified -# basis sets (adapted from `Minimizing State Preparations for VQE by Gokhale et al. -# `__) - - -############################################################################## -# Simultaneously measuring observables -# ------------------------------------ -# -# One of the assumptions we made above was that every term in the Hamiltonian must be measured independently. -# However, this might not be the case. From the `Heisenberg uncertainty relationship -# `__ for two -# observables :math:`\hat{A}` and :math:`\hat{B},` we know that -# -# .. math:: \sigma_A^2 \sigma_B^2 \geq \frac{1}{2}\left|\left\langle [\hat{A}, \hat{B}] \right\rangle\right|, -# -# where :math:`\sigma^2_A` and :math:`\sigma^2_B` are the variances of measuring the expectation value of the -# associated observables, and -# -# .. math:: [\hat{A}, \hat{B}] = \hat{A}\hat{B}-\hat{B}\hat{A} -# -# is the commutator. Therefore, -# -# - If the two observables :math:`\hat{A}` and :math:`\hat{B}` do not commute (:math:`[\hat{A}, -# \hat{B}] \neq 0`), then :math:`\sigma_A^2 -# \sigma_B^2 > 0` and we cannot simultaneously measure the expectation values of the two -# observables. -# -# .. -# -# - If :math:`\hat{A}` and :math:`\hat{B}` **do** commute (:math:`[\hat{A}, -# \hat{B}] = 0`), then :math:`\sigma_A^2 -# \sigma_B^2 \geq 0` and there exists a measurement basis where we can **simultaneously measure** the -# expectation value of both observables on the same state. -# -# To explore why commutativity and simultaneous measurement are related, let's assume that there -# is a complete, orthonormal eigenbasis :math:`|\phi_n\rangle` that *simultaneously -# diagonalizes* both :math:`\hat{A}` and :math:`\hat{B}:` -# -# .. math:: -# -# ① ~~ \hat{A} |\phi_n\rangle &= \lambda_{A,n} |\phi_n\rangle,\\ -# ② ~~ \hat{B} |\phi_n\rangle &= \lambda_{B,n} |\phi_n\rangle. -# -# where :math:`\lambda_{A,n}` and :math:`\lambda_{B,n}` are the corresponding eigenvalues. -# If we pre-multiply the first equation by :math:`\hat{B},` and the second by :math:`\hat{A}` -# (both denoted in blue): -# -# .. math:: -# -# \color{blue}{\hat{B}}\hat{A} |\phi_n\rangle &= \lambda_{A,n} \color{blue}{\hat{B}} -# |\phi_n\rangle = \lambda_{A,n} \color{blue}{\lambda_{B,n}} |\phi_n\rangle,\\ -# \color{blue}{\hat{A}}\hat{B} |\phi_n\rangle &= \lambda_{B,n} \color{blue}{\hat{A}} -# |\phi_n\rangle = \lambda_{A,n} \color{blue}{\lambda_{B,n}} |\phi_n\rangle. -# -# We can see that assuming a simultaneous eigenbasis requires that -# :math:`\hat{A}\hat{B}|\phi_n\rangle = \hat{B}\hat{A}|\phi_n\rangle.` Or, rearranging, -# -# .. math:: (\hat{A}\hat{B} - \hat{B}\hat{A}) |\phi_n\rangle = [\hat{A}, \hat{B}]|\phi_n\rangle = 0. -# -# Our assumption that :math:`|\phi_n\rangle` simultaneously diagonalizes both :math:`\hat{A}` and -# :math:`\hat{B}` only holds true if the two observables commute. -# -# So far, this seems awfully theoretical. What does this mean in practice? -# -# In the realm of variational circuits, we typically want to compute expectation values of an -# observable on a given state :math:`|\psi\rangle.` If we have two commuting observables, we now know that -# they share a simultaneous eigenbasis: -# -# .. math:: -# -# \hat{A} &= \sum_n \lambda_{A, n} |\phi_n\rangle\langle \phi_n|,\\ -# \hat{B} &= \sum_n \lambda_{B, n} |\phi_n\rangle\langle \phi_n|. -# -# Substituting this into the expression for the expectation values: -# -# .. math:: -# -# \langle\hat{A}\rangle &= \langle \psi | \hat{A} | \psi \rangle = \langle \psi | \left( \sum_n -# \lambda_{A, n} |\phi_n\rangle\langle \phi_n| \right) | \psi \rangle = \sum_n \lambda_{A,n} -# |\langle \phi_n|\psi\rangle|^2,\\ -# \langle\hat{B}\rangle &= \langle \psi | \hat{B} | \psi \rangle = \langle \psi | \left( \sum_n -# \lambda_{B, n} |\phi_n\rangle\langle \phi_n| \right) | \psi \rangle = \sum_n \lambda_{B,n} -# |\langle \phi_n|\psi\rangle|^2. -# -# So, assuming we know the eigenvalues of the commuting observables in advance, if we perform a -# measurement in their shared eigenbasis (the :math:`|\phi_n\rangle`), we only need to perform a **single measurement** of the -# probabilities :math:`|\langle \phi_n|\psi\rangle|^2` in order to recover both expectation values! 😍 -# -# Fantastic! But, can we use this to reduce the number of measurements we need to perform in the VQE algorithm? -# To do so, we must find the answer to two questions: -# -# 1. How do we determine which terms of the cost Hamiltonian commute? -# -# 2. How do we rotate the circuit into the shared eigenbasis prior to measurement? -# -# The answers to these questions aren't necessarily easy nor straightforward. Thankfully, there are -# some recent techniques we can harness to address both. - -############################################################################## -# Qubit-wise commuting Pauli terms -# -------------------------------- -# -# Back when we summarized the VQE algorithm, we saw that each term of the Hamiltonian is generally represented -# as a tensor product of Pauli operators: -# -# .. math:: h_i = \bigotimes_{n=0}^{N-1} P_n. -# -# Luckily, this tensor product structure allows us to take a bit of a shortcut. Rather than consider -# **full commutativity**, we can consider a more strict condition known as **qubit-wise -# commutativity** (QWC). -# -# To start with, let's consider single-qubit Pauli operators and the identity. We know that the Pauli operators -# commute with themselves as well as the identity, but they do *not* commute with -# each other: -# -# .. math:: -# -# [\sigma_i, I] = 0, ~~~ [\sigma_i, \sigma_i] = 0, ~~~ [\sigma_i, \sigma_j] = c \sigma_k \delta_{ij}. -# -# Now consider two tensor products of Pauli terms, for example :math:`X\otimes Y \otimes I` and -# :math:`X\otimes I \otimes Z.` We say that these two terms are qubit-wise commuting, since, if -# we compare each subsystem in the tensor product, we see that every one commutes: -# -# .. math:: -# -# \begin{array}{ | *1c | *1c | *1c | *1c | *1c |} -# X &\otimes &Y &\otimes &I\\ -# X &\otimes &I &\otimes &Z -# \end{array} ~~~~ \Rightarrow ~~~~ [X, X] = 0, ~~~ [Y, I] = 0, ~~~ [I, Z] = 0. -# -# As a consequence, both terms must commute: -# -# .. math:: [X\otimes Y \otimes I, X\otimes I \otimes Z] = 0. -# -# .. important:: -# -# Qubit-wise commutativity is a **sufficient** but not **necessary** condition -# for full commutativity. For example, the two Pauli terms :math:`Y\otimes Y` and -# :math:`X\otimes X` are not qubit-wise commuting, but do commute (have a go verifying this!). -# -# Once we have identified a qubit-wise commuting pair of Pauli terms, it is also straightforward to -# find the gates to rotate the circuit into the shared eigenbasis. To do so, we simply rotate -# each qubit one-by-one depending on the Pauli operator we are measuring on that wire: -# -# .. raw:: html -# -# -#
-# -# .. rst-class:: docstable -# -# +------------------+-------------------------------+ -# | Observable | Rotation gate | -# +==================+===============================+ -# | :math:`X` | :math:`RY(-\pi/2) = H` | -# +------------------+-------------------------------+ -# | :math:`Y` | :math:`RX(\pi/2)=HS^{-1}=HSZ` | -# +------------------+-------------------------------+ -# | :math:`Z` | :math:`I` | -# +------------------+-------------------------------+ -# | :math:`I` | :math:`I` | -# +------------------+-------------------------------+ -# -# .. raw:: html -# -#
-# -# Therefore, in this particular example: -# -# * Wire 0: we are measuring both terms in the :math:`X` basis, apply the Hadamard gate -# * Wire 1: we are measuring both terms in the :math:`Y` basis, apply a :math:`RX(\pi/2)` gate -# * Wire 2: we are measuring both terms in the :math:`Z` basis (the computational basis), no gate needs to be applied. -# -# Let's use PennyLane to verify this. - - -obs = [ - qml.PauliX(0) @ qml.PauliY(1), - qml.PauliX(0) @ qml.PauliZ(2) -] - - -############################################################################## -# First, let's naively use two separate circuit evaluations to measure -# the two QWC terms. - - -dev = qml.device("default.qubit", wires=3) - -@qml.qnode(dev, interface="jax") -def circuit1(weights): - qml.StronglyEntanglingLayers(weights, wires=range(3)) - return qml.expval(obs[0]) - - -@qml.qnode(dev, interface="jax") -def circuit2(weights): - qml.StronglyEntanglingLayers(weights, wires=range(3)) - return qml.expval(obs[1]) - -param_shape = qml.templates.StronglyEntanglingLayers.shape(n_layers=3, n_wires=3) -key, scale = random.PRNGKey(192933), 0.1 -weights = scale * random.normal(key, shape=param_shape) - -print("Expectation value of XYI = ", circuit1(weights)) -print("Expectation value of XIZ = ", circuit2(weights)) - -############################################################################## -# Now, let's use our QWC approach to reduce this down to a *single* measurement -# of the probabilities in the shared eigenbasis of both QWC observables: - -@qml.qnode(dev, interface="jax") -def circuit_qwc(weights): - qml.StronglyEntanglingLayers(weights, wires=range(3)) - - # rotate wire 0 into the shared eigenbasis - qml.RY(-jnp.pi / 2, wires=0) - - # rotate wire 1 into the shared eigenbasis - qml.RX(jnp.pi / 2, wires=1) - - # wire 2 does not require a rotation - - # measure probabilities in the computational basis - return qml.probs(wires=range(3)) - - -rotated_probs = circuit_qwc(weights) -print(rotated_probs) - -############################################################################## -# We're not quite there yet; we have only calculated the probabilities of the variational circuit -# rotated into the shared eigenbasis—the :math:`|\langle \phi_n |\psi\rangle|^2.` To recover the -# *expectation values* of the two QWC observables from the probabilities, recall that we need one -# final piece of information: their eigenvalues :math:`\lambda_{A, n}` and :math:`\lambda_{B, n}.` -# -# We know that the single-qubit Pauli operators each have eigenvalues :math:`(1, -1),` while the identity -# operator has eigenvalues :math:`(1, 1).` We can make use of ``np.kron`` to quickly -# generate the eigenvalues of the full Pauli terms, making sure that the order -# of the eigenvalues in the Kronecker product corresponds to the tensor product. - -eigenvalues_XYI = jnp.kron(jnp.kron(jnp.array([1, -1]), jnp.array([1, -1])), jnp.array([1, 1])) -eigenvalues_XIZ = jnp.kron(jnp.kron(jnp.array([1, -1]), jnp.array([1, 1])), jnp.array([1, -1])) - -# Taking the linear combination of the eigenvalues and the probabilities -print("Expectation value of XYI = ", jnp.dot(eigenvalues_XYI, rotated_probs)) -print("Expectation value of XIZ = ", jnp.dot(eigenvalues_XIZ, rotated_probs)) - - -############################################################################## -# Compare this to the result when we used two circuit evaluations. We have successfully used a -# single circuit evaluation to recover both expectation values! -# -# Luckily, PennyLane automatically performs this QWC grouping under the hood. We simply -# return the two QWC Pauli terms from the QNode: - -@qml.qnode(dev, interface="jax") -def circuit(weights): - qml.StronglyEntanglingLayers(weights, wires=range(3)) - return [ - qml.expval(qml.PauliX(0) @ qml.PauliY(1)), - qml.expval(qml.PauliX(0) @ qml.PauliZ(2)) - ] - - -print(circuit(weights)) - - -############################################################################## -# Behind the scenes, PennyLane is making use of our built-in -# :mod:`qml.pauli ` module, which contains functions for diagonalizing QWC -# terms: - -rotations, new_obs = qml.pauli.diagonalize_qwc_pauli_words(obs) - -print(rotations) -print(new_obs) - - -############################################################################## -# Here, the first line corresponds to the basis rotations that were discussed above, written in -# terms of ``RX`` and ``RY`` rotations. Check out the :mod:`qml.pauli ` -# documentation for more details on its provided functionality and how it works. -# -# Given a Hamiltonian containing a large number of Pauli terms, -# there is a high likelihood of there being a significant number of terms that qubit-wise commute. Can -# we somehow partition the terms into **fewest** number of QWC groups to minimize the number of measurements -# we need to take? - -############################################################################## -# Grouping QWC terms -# ------------------ -# -# A nice example is provided in [#verteletskyi2020]_ showing how we might tackle this. Say we have -# the following Hamiltonian defined over four qubits: -# -# .. math:: H = Z_0 + Z_0 Z_1 + Z_0 Z_1 Z_2 + Z_0 Z_1 Z_2 Z_3 + X_2 X_3 + Y_0 X_2 X_3 + Y_0 Y_1 X_2 X_3, -# -# where we are using the shorthand :math:`P_0 P_2 = P\otimes I \otimes P \otimes I` for brevity. -# If we go through and work out which Pauli terms are qubit-wise commuting, we can represent -# this in a neat way using a graph: -# -# .. figure:: /_static/demonstration_assets/measurement_optimize/graph1.png -# :width: 70% -# :align: center -# -# In the above graph, every node represents an individual Pauli term of the Hamiltonian, with -# edges connecting terms that are qubit-wise commuting. Groups of qubit-wise commuting terms are -# represented as **complete subgraphs**. Straight away, we can make an observation: -# there is no unique solution for partitioning the Hamiltonian into groups of qubit-wise commuting -# terms! In fact, there are several solutions: -# -# .. figure:: /_static/demonstration_assets/measurement_optimize/graph2.png -# :width: 90% -# :align: center -# -# Of course, of the potential solutions above, there is one that is more optimal than the others --- -# on the bottom left, we have partitioned the graph into *two* complete subgraphs, as opposed to the -# other solutions that require three complete subgraphs. If we were to go with this solution, -# we would be able to measure the expectation value of the Hamiltonian using two circuit evaluations. -# -# This problem—finding the minimum number of complete subgraphs of a graph—is actually quite well -# known in graph theory, where it is referred to as the `minimum clique cover problem -# `__ (with 'clique' being another term for a complete subgraph). -# -# Unfortunately, that's where our good fortune ends—the minimum clique cover problem is known to -# be `NP-hard `__, meaning there is no known (classical) -# solution to finding the optimum/minimum clique cover in polynomial time. -# -# Thankfully, there is a silver lining: we know of polynomial-time algorithms for finding -# *approximate* solutions to the minimum clique cover problem. These heuristic approaches, while -# not guaranteed to find the optimum solution, scale quadratically with the number of nodes in the -# graph/terms in the Hamiltonian [#yen2020]_, so work reasonably well in practice. -# -# Many of these heuristic approaches have roots in another graph problem known as `graph -# colouring `__; the assignment of colours to -# the graph's vertices such that no adjacent vertices have the same colour. How is this related -# to the minimum clique cover problem, though? If we take our QWC graph above, and generate the -# `complement graph `__ by drawing edges -# between all *non*-adjacent nodes, -# -# .. figure:: /_static/demonstration_assets/measurement_optimize/graph3.png -# :width: 100% -# :align: center -# -# we see that solving the minimum clique cover problem on the QWC graph is equivalent to solving the -# graph colouring problem on the complement graph using the minimum possible number of colours. -# While there are various different heuristic algorithms, a common one is `greedy colouring -# `__; in fact, the open-source graph -# package `NetworkX even provides a function for greedy colouring -# `__, -# ``nx.greedy_color``. -# -# Let's give this a go, using NetworkX to solve the minimum clique problem for observable grouping. -# First, we'll need to generate the QWC graph (with each node corresponding to a Hamiltonian -# term, and edges indicating two terms that are QWC). - -import networkx as nx -from matplotlib import pyplot as plt - -terms = [ - qml.PauliZ(0), - qml.PauliZ(0) @ qml.PauliZ(1), - qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2), - qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3), - qml.PauliX(2) @ qml.PauliX(3), - qml.PauliY(0) @ qml.PauliX(2) @ qml.PauliX(3), - qml.PauliY(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliX(3) -] - -def format_pauli_word(term): - """Convenience function that nicely formats a PennyLane - tensor observable as a Pauli word""" - if isinstance(term, qml.ops.Prod): - return " ".join([format_pauli_word(t) for t in term]) - - return f"{term.name[-1]}{term.wires.tolist()[0]}" - -G = nx.Graph() - -with warnings.catch_warnings(): - # Muting irrelevant warnings - warnings.filterwarnings( - "ignore", - message="The behaviour of operator ", - category=UserWarning, - ) - - # add the terms to the graph - G.add_nodes_from(terms) - - # add QWC edges - G.add_edges_from([ - [terms[0], terms[1]], # Z0 <--> Z0 Z1 - [terms[0], terms[2]], # Z0 <--> Z0 Z1 Z2 - [terms[0], terms[3]], # Z0 <--> Z0 Z1 Z2 Z3 - [terms[1], terms[2]], # Z0 Z1 <--> Z0 Z1 Z2 - [terms[2], terms[3]], # Z0 Z1 Z2 <--> Z0 Z1 Z2 Z3 - [terms[1], terms[3]], # Z0 Z1 <--> Z0 Z1 Z2 Z3 - [terms[0], terms[4]], # Z0 <--> X2 X3 - [terms[1], terms[4]], # Z0 Z1 <--> X2 X3 - [terms[4], terms[5]], # X2 X3 <--> Y0 X2 X3 - [terms[4], terms[6]], # X2 X3 <--> Y0 Y1 X2 X3 - [terms[5], terms[6]], # Y0 X2 X3 <--> Y0 Y1 X2 X3 - ]) - - plt.margins(x=0.1) - coords = nx.spring_layout(G, seed=1) - nx.draw( - G, - coords, - labels={node: format_pauli_word(node) for node in terms}, - with_labels=True, - node_size=500, - font_size=8, - node_color="#9eded1", - edge_color="#c1c1c1", - ) - - ############################################################################## - # We can now generate the complement graph (compare this to our handdrawn - # version above!): - - C = nx.complement(G) - coords = nx.spring_layout(C, seed=1) - - nx.draw( - C, - coords, - labels={node: format_pauli_word(node) for node in terms}, - with_labels=True, - node_size=500, - font_size=8, - node_color="#9eded1", - edge_color="#c1c1c1" - ) - - ############################################################################## - # Now that we have the complement graph, we can perform a greedy coloring to - # determine the minimum number of QWC groups: - - groups = nx.coloring.greedy_color(C, strategy="largest_first") - - # plot the complement graph with the greedy colouring - nx.draw( - C, - coords, - labels={node: format_pauli_word(node) for node in terms}, - with_labels=True, - node_size=500, - font_size=8, - node_color=[("#9eded1", "#aad4f0")[groups[node]] for node in C], - edge_color="#c1c1c1" - ) - - -num_groups = len(set(groups.values())) -print("Minimum number of QWC groupings found:", num_groups) - - -for i in range(num_groups): - print(f"\nGroup {i}:") - - for term, group_id in groups.items(): - if group_id == i: - print(format_pauli_word(term)) - -############################################################################## -# Putting it all together -# ----------------------- -# -# So, we now have a strategy for minimizing the number of measurements we need to perform -# for our VQE problem: -# -# 1. Determine which terms of the Hamiltonian are qubit-wise commuting, and use -# this to construct a graph representing the QWC relationship. -# -# 2. Construct the complement QWC graph. -# -# 3. Use a graph colouring heuristic algorithm to determine a graph colouring for the complement graph -# with a minimum number of colours. Each coloured vertex set corresponds to a -# qubit-wise commuting group of Hamiltonian terms. -# -# 4. Generate and evaluate the circuit ansatz (with additional rotations) per -# QWC grouping, extracting probability distributions. -# -# 5. Finally, post-process the probability distributions with the observable eigenvalues -# to recover the Hamiltonian expectation value. -# -# Luckily, the PennyLane ``pauli`` module makes this relatively easy. Let's walk through -# the entire process using the provided grouping functions. -# -# Steps 1-3 (finding and grouping QWC terms in the Hamiltonian) can be done via the -# :func:`qml.pauli.group_observables ` function: - -obs_groupings = qml.pauli.group_observables(terms, grouping_type='qwc', method='rlf') - - -############################################################################## -# The ``grouping_type`` argument allows us to choose how the commuting terms -# are determined (more on that later!) whereas ``method`` determines the colouring -# heuristic (in this case, ``"rlf"`` refers to Recursive Largest First, a variant of largest first colouring heuristic). -# -# If we want to see what the required rotations and measurements are, we can use the -# :func:`qml.pauli.diagonalize_qwc_groupings ` -# function: - -rotations, measurements = qml.pauli.diagonalize_qwc_groupings(obs_groupings) - -############################################################################## -# However, this isn't strictly necessary—recall previously that the QNode -# has the capability to *automatically* measure qubit-wise commuting observables! - -dev = qml.device("lightning.qubit", wires=4) - -@qml.qnode(dev, interface="jax") -def circuit(weights, group=None, **kwargs): - qml.StronglyEntanglingLayers(weights, wires=range(4)) - return [qml.expval(o) for o in group] - -param_shape = qml.templates.StronglyEntanglingLayers.shape(n_layers=3, n_wires=4) -key = random.PRNGKey(1) -weights = random.normal(key, shape=param_shape) * 0.1 -result = [jnp.array(circuit(weights, group=g)) for g in obs_groupings] - -print("Term expectation values:") -for group, expvals in enumerate(result): - print(f"Group {group} expectation values:", expvals) - -# Since all the coefficients of the Hamiltonian are unity, -# we can simply sum the expectation values. -print(" = ", jnp.sum(jnp.hstack(result))) - - -############################################################################## -# Finally, we don't need to go through this process manually every time; if our cost function can be -# written in the form of an expectation value of a Hamiltonian (as is the case for most VQE and QAOA -# problems), we can use the option ``grouping_type="qwc"`` in :class:`~.pennylane.Hamiltonian` to -# automatically optimize the measurements. - -H = qml.Hamiltonian(coeffs=jnp.ones(len(terms)), observables=terms, grouping_type="qwc") -_, H_ops = H.terms() -@qml.qnode(dev, interface="jax") -def cost_fn(weights): - qml.StronglyEntanglingLayers(weights, wires=range(4)) - return qml.expval(H) -print(cost_fn(weights)) - -############################################################################## -# Beyond VQE -# ---------- -# -# Wait, hang on. We dove so deeply into measurement grouping and optimization, we forgot to check -# how this affects the number of measurements required to perform the VQE on :math:`\text{H}_2 \text{O}!` -# Let's use our new-found knowledge to see what happens. - -dataset = qml.data.load('qchem', molname="H2O")[0] -H, num_qubits = dataset.hamiltonian, len(dataset.hamiltonian.wires) -print("Number of Hamiltonian terms/required measurements:", len(H_ops)) - -# grouping -groups = qml.pauli.group_observables(H_ops, grouping_type='qwc', method='rlf') -print("Number of required measurements after optimization:", len(groups)) - -############################################################################## -# We went from 1086 required measurements/circuit evaluations to 320 (just over *one thousand* -# down to *three hundred* 😱😱😱). -# -# As impressive as this is, however, this is just the beginning of the optimization. -# -# While finding qubit-wise commutating terms is relatively straightforward, with a little -# extra computation we can push this number down even further. Recent work has explored -# the savings that can be made by considering *full* commutativity [#yen2020]_, unitary -# partitioning [#izmaylov2019]_, and Fermionic basis rotation grouping [#huggins2019]_. -# Work has also been performed to reduce the classical overhead associated with measurement -# optimization, allowing the classical measurement grouping to be performed in linear time -# [#gokhale2020]_. For example, recall that qubit-wise commutativity is only a subset of -# full commutativity; if we consider full commutativity instead, we can further reduce the -# number of groups required. -# -# Finally, it is worth pointing out that, as the field of variational quantum algorithms grows, this -# problem of measurement optimization no longer just applies to the VQE algorithm (the algorithm it -# was born from). Instead, there are a multitude of algorithms that could benefit from these -# measurement optimization techniques (QAOA being a prime example). -# -# So the next time you are working on a variational quantum algorithm and the number -# of measurements required begins to explode—stop, take a deep breath 😤, and consider grouping -# and optimizing your measurements. -# -# .. note:: -# -# Qubit-wise commuting group information for a wide variety of molecules has been -# pre-computed, and is available for download in -# in the `PennyLane Datasets library `__. - -############################################################################## -# References -# ---------- -# -# .. [#peruzzo2014] -# -# Alberto Peruzzo, Jarrod McClean *et al.*, "A variational eigenvalue solver on a photonic -# quantum processor". `Nature Communications 5, 4213 (2014). -# `__ -# -# .. [#yen2020] -# -# Tzu-Ching Yen, Vladyslav Verteletskyi, and Artur F. Izmaylov. "Measuring all compatible -# operators in one series of single-qubit measurements using unitary transformations." `Journal of -# Chemical Theory and Computation 16.4 (2020): 2400-2409. -# `__ -# -# .. [#izmaylov2019] -# -# Artur F. Izmaylov, *et al.* "Unitary partitioning approach to the measurement problem in the -# variational quantum eigensolver method." `Journal of Chemical Theory and Computation 16.1 (2019): -# 190-195. `__ -# -# .. [#huggins2019] -# -# William J. Huggins, *et al.* "Efficient and noise resilient measurements for quantum chemistry -# on near-term quantum computers." `arXiv preprint arXiv:1907.13117 (2019). -# `__ -# -# .. [#gokhale2020] -# -# Pranav Gokhale, *et al.* "Minimizing state preparations in variational quantum eigensolver by -# partitioning into commuting families." `arXiv preprint arXiv:1907.13623 (2019). -# `__ -# -# .. [#verteletskyi2020] -# -# Vladyslav Verteletskyi, Tzu-Ching Yen, and Artur F. Izmaylov. "Measurement optimization in the -# variational quantum eigensolver using a minimum clique cover." `The Journal of Chemical Physics -# 152.12 (2020): 124114. `__ -# -# -# About the author -# ---------------- -# +r""" +Measurement optimization +======================== + +.. meta:: + :property="og:description": Optimize and reduce the number of measurements required to evaluate a variational algorithm cost function. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/grouping.png + +.. related:: + + tutorial_vqe A brief overview of VQE + tutorial_quantum_chemistry Building molecular Hamiltonians + tutorial_qaoa_intro Intro to QAOA + +*Author: Josh Izaac — Posted: 18 January 2021. Last updated: 29 August 2023.* + +The variational quantum eigensolver (VQE) is the OG variational quantum algorithm. Harnessing +near-term quantum hardware to solve for the electronic structure of molecules, VQE is *the* +algorithm that sparked the variational circuit craze of the last 5 years, and holds great +promise for showcasing a quantum advantage on near-term quantum hardware. It has also inspired +other quantum algorithms such as the :doc:`Quantum Approximate Optimization Algorithm (QAOA) +`. + +To scale VQE beyond the regime of classical computation, however, we need to solve for the +ground state of increasingly larger molecules. A consequence is that the number of +measurements we need to make on the quantum hardware also grows polynomially—a huge bottleneck, +especially when quantum hardware access is limited and expensive. + +To mitigate this 'measurement problem', a plethora of recent research dropped over the course of +2019 and 2020 [#yen2020]_ [#izmaylov2019]_ [#huggins2019]_ [#gokhale2020]_ [#verteletskyi2020]_ , +exploring potential strategies to minimize the number of measurements required. In fact, by grouping +commuting terms of the Hamiltonian, we can significantly reduce the number of +measurements needed—in some cases, reducing the number of measurements by up to 90%! + +.. figure:: /_static/demonstration_assets/measurement_optimize/grouping.png + :width: 90% + :align: center + +In this demonstration, we revisit the VQE algorithm, see first-hand how the required number of +measurements scales as molecule size increases, and finally use these measurement optimization +strategies to minimize the number of measurements we need to make. These techniques are valuable +beyond just VQE, allowing you to add measurement optimization to your toolkit of techniques to +perform variational algorithms more efficiently. + +Revisiting VQE +-------------- + +The study of :doc:`variational quantum algorithms ` was spearheaded +by the introduction of the :doc:`variational quantum eigensolver ` (VQE) algorithm in +2014 [#peruzzo2014]_. While classical variational techniques have been known for decades to estimate +the ground state energy of a molecule, VQE allowed this variational technique to be applied using +quantum computers. Since then, the field of variational quantum algorithms has evolved +significantly, with larger and more complex models being proposed (such as +:doc:`quantum neural networks `, :doc:`QGANs `, and +:doc:`variational classifiers `). However, quantum chemistry +remains one of the flagship use-cases for variational quantum algorithms, and VQE the standard-bearer. + +Part of the appeal of VQE lies within its simplicity. A circuit ansatz :math:`U(\theta)` is chosen +(typically the Unitary Coupled-Cluster Singles and Doubles +(:func:`~pennylane.templates.subroutines.UCCSD`) ansatz), and the qubit representation of the +molecular Hamiltonian is computed: + +.. math:: H = \sum_i c_i h_i, + +where :math:`h_i` are the terms of the Hamiltonian written as a tensor product of Pauli operators or the identity +acting on wire :math:`n,` :math:`P_n \in \{I, \sigma_x, \sigma_y, \sigma_z\}:` + +.. math:: h_i = \bigotimes_{n=0}^{N-1} P_n. + +(The :math:`h_i` product of Pauli terms is often referred to as a 'Pauli word' in the literature.) The cost +function of the VQE is then simply the expectation value of this Hamiltonian on the state obtained +after running the variational quantum circuit: + +.. math:: \text{cost}(\theta) = \langle 0 | U(\theta)^\dagger H U(\theta) | 0 \rangle. + +By using a classical optimizer to *minimize* this quantity, we can estimate +the ground state energy of the Hamiltonian :math:`H:` + +.. math:: H U(\theta_{min}) |0\rangle = E_{min} U(\theta_{min}) |0\rangle. + +In practice, when we are using quantum hardware to compute these expectation values we expand out +the Hamiltonian as its summation, resulting in separate expectation values that need to be calculated for each term: + +.. math:: + + \text{cost}(\theta) = \langle 0 | U(\theta)^\dagger \left(\sum_i c_i h_i\right) U(\theta) | 0 \rangle + = \sum_i c_i \langle 0 | U(\theta)^\dagger h_i U(\theta) | 0 \rangle. + +.. note:: + + How do we compute the qubit representation of the molecular Hamiltonian? This is a more + complicated story that involves applying a self-consistent field method (such as Hartree-Fock), + and then performing a fermionic-to-qubit mapping such as the Jordan-Wigner or Bravyi-Kitaev + transformations. + + For more details on this process, check out the :doc:`/demos/tutorial_quantum_chemistry` + tutorial. + +The measurement problem +----------------------- + +For small molecules, the VQE algorithm scales and performs exceedingly well. For example, for the +Hydrogen molecule :math:`\text{H}_2,` the final Hamiltonian in its qubit representation +has 15 terms that need to be measured. Let's obtain the Hamiltonian from +`PennyLane's dataset library `__ +to verify the number of terms. In this tutorial, we use the :func:`~.pennylane.data.load` +function to download the dataset of the molecule. + +""" + +import functools +import warnings +import jax +from jax import numpy as jnp +import pennylane as qml + +jax.config.update("jax_enable_x64", True) + +dataset = qml.data.load("qchem", molname="H2", bondlength=0.7)[0] +H, num_qubits = dataset.hamiltonian, len(dataset.hamiltonian.wires) + +print("Required number of qubits:", num_qubits) +print(H) + +############################################################################### +# Here, we can see that the Hamiltonian involves 15 terms, so we expect to compute 15 expectation values +# on hardware. Let's generate the cost function to check this. + +# Create a 4 qubit simulator +dev = qml.device("default.qubit", shots=1000, seed=904932) + +# number of electrons +electrons = 2 + +# Define the Hartree-Fock initial state for our variational circuit +initial_state = qml.qchem.hf_state(electrons, num_qubits) + +# Construct the UCCSD ansatz +singles, doubles = qml.qchem.excitations(electrons, num_qubits) +s_wires, d_wires = qml.qchem.excitations_to_wires(singles, doubles) +ansatz = functools.partial( + qml.UCCSD, init_state=initial_state, s_wires=s_wires, d_wires=d_wires +) + +# generate the cost function +@qml.qnode(dev, interface="jax") +def cost_circuit(params): + ansatz(params, wires=range(num_qubits)) + return qml.expval(H) + +############################################################################## +# If we evaluate this cost function, we can see that it corresponds to 15 different +# executions under the hood—one per expectation value: + +from jax import random as random +key, scale = random.PRNGKey(0), jnp.pi +params = random.normal(key, shape=(len(singles) + len(doubles),)) * scale +with qml.Tracker(dev) as tracker: # track the number of executions + print("Cost function value:", cost_circuit(params)) + +print("Number of quantum evaluations:", tracker.totals['executions']) + +############################################################################## +# How about a larger molecule? Let's try the +# `water molecule `__: + +dataset = qml.data.load('qchem', molname="H2O")[0] +H, num_qubits = dataset.hamiltonian, len(dataset.hamiltonian.wires) + +print("Required number of qubits:", num_qubits) +print("Number of Hamiltonian terms/required measurements:", len(H.terms()[0])) + +print("\n", H) + + +############################################################################## +# Simply going from two atoms in :math:`\text{H}_2` to three in :math:`\text{H}_2 \text{O}` +# resulted in over triple the number of qubits required and 1086 measurements that must be made! +# +# We can see that as the size of our molecule increases, we run into a problem: larger molecules +# result in Hamiltonians that not only require a larger number of qubits :math:`N` in their +# representation, but the number of terms in the Hamiltonian scales like +# :math:`\mathcal{O}(N^4)!` 😱😱😱 +# +# We can mitigate this somewhat by choosing smaller `basis sets +# `__ to represent the electronic structure +# wavefunction, however this would be done at the cost of solution accuracy, and doesn't reduce the number of +# measurements significantly enough to allow us to scale to classically intractable problems. +# +# .. figure:: /_static/demonstration_assets/measurement_optimize/n4.png +# :width: 70% +# :align: center +# +# The number of qubit Hamiltonian terms required to represent various molecules in the specified +# basis sets (adapted from `Minimizing State Preparations for VQE by Gokhale et al. +# `__) + + +############################################################################## +# Simultaneously measuring observables +# ------------------------------------ +# +# One of the assumptions we made above was that every term in the Hamiltonian must be measured independently. +# However, this might not be the case. From the `Heisenberg uncertainty relationship +# `__ for two +# observables :math:`\hat{A}` and :math:`\hat{B},` we know that +# +# .. math:: \sigma_A^2 \sigma_B^2 \geq \frac{1}{2}\left|\left\langle [\hat{A}, \hat{B}] \right\rangle\right|, +# +# where :math:`\sigma^2_A` and :math:`\sigma^2_B` are the variances of measuring the expectation value of the +# associated observables, and +# +# .. math:: [\hat{A}, \hat{B}] = \hat{A}\hat{B}-\hat{B}\hat{A} +# +# is the commutator. Therefore, +# +# - If the two observables :math:`\hat{A}` and :math:`\hat{B}` do not commute (:math:`[\hat{A}, +# \hat{B}] \neq 0`), then :math:`\sigma_A^2 +# \sigma_B^2 > 0` and we cannot simultaneously measure the expectation values of the two +# observables. +# +# .. +# +# - If :math:`\hat{A}` and :math:`\hat{B}` **do** commute (:math:`[\hat{A}, +# \hat{B}] = 0`), then :math:`\sigma_A^2 +# \sigma_B^2 \geq 0` and there exists a measurement basis where we can **simultaneously measure** the +# expectation value of both observables on the same state. +# +# To explore why commutativity and simultaneous measurement are related, let's assume that there +# is a complete, orthonormal eigenbasis :math:`|\phi_n\rangle` that *simultaneously +# diagonalizes* both :math:`\hat{A}` and :math:`\hat{B}:` +# +# .. math:: +# +# ① ~~ \hat{A} |\phi_n\rangle &= \lambda_{A,n} |\phi_n\rangle,\\ +# ② ~~ \hat{B} |\phi_n\rangle &= \lambda_{B,n} |\phi_n\rangle. +# +# where :math:`\lambda_{A,n}` and :math:`\lambda_{B,n}` are the corresponding eigenvalues. +# If we pre-multiply the first equation by :math:`\hat{B},` and the second by :math:`\hat{A}` +# (both denoted in blue): +# +# .. math:: +# +# \color{blue}{\hat{B}}\hat{A} |\phi_n\rangle &= \lambda_{A,n} \color{blue}{\hat{B}} +# |\phi_n\rangle = \lambda_{A,n} \color{blue}{\lambda_{B,n}} |\phi_n\rangle,\\ +# \color{blue}{\hat{A}}\hat{B} |\phi_n\rangle &= \lambda_{B,n} \color{blue}{\hat{A}} +# |\phi_n\rangle = \lambda_{A,n} \color{blue}{\lambda_{B,n}} |\phi_n\rangle. +# +# We can see that assuming a simultaneous eigenbasis requires that +# :math:`\hat{A}\hat{B}|\phi_n\rangle = \hat{B}\hat{A}|\phi_n\rangle.` Or, rearranging, +# +# .. math:: (\hat{A}\hat{B} - \hat{B}\hat{A}) |\phi_n\rangle = [\hat{A}, \hat{B}]|\phi_n\rangle = 0. +# +# Our assumption that :math:`|\phi_n\rangle` simultaneously diagonalizes both :math:`\hat{A}` and +# :math:`\hat{B}` only holds true if the two observables commute. +# +# So far, this seems awfully theoretical. What does this mean in practice? +# +# In the realm of variational circuits, we typically want to compute expectation values of an +# observable on a given state :math:`|\psi\rangle.` If we have two commuting observables, we now know that +# they share a simultaneous eigenbasis: +# +# .. math:: +# +# \hat{A} &= \sum_n \lambda_{A, n} |\phi_n\rangle\langle \phi_n|,\\ +# \hat{B} &= \sum_n \lambda_{B, n} |\phi_n\rangle\langle \phi_n|. +# +# Substituting this into the expression for the expectation values: +# +# .. math:: +# +# \langle\hat{A}\rangle &= \langle \psi | \hat{A} | \psi \rangle = \langle \psi | \left( \sum_n +# \lambda_{A, n} |\phi_n\rangle\langle \phi_n| \right) | \psi \rangle = \sum_n \lambda_{A,n} +# |\langle \phi_n|\psi\rangle|^2,\\ +# \langle\hat{B}\rangle &= \langle \psi | \hat{B} | \psi \rangle = \langle \psi | \left( \sum_n +# \lambda_{B, n} |\phi_n\rangle\langle \phi_n| \right) | \psi \rangle = \sum_n \lambda_{B,n} +# |\langle \phi_n|\psi\rangle|^2. +# +# So, assuming we know the eigenvalues of the commuting observables in advance, if we perform a +# measurement in their shared eigenbasis (the :math:`|\phi_n\rangle`), we only need to perform a **single measurement** of the +# probabilities :math:`|\langle \phi_n|\psi\rangle|^2` in order to recover both expectation values! 😍 +# +# Fantastic! But, can we use this to reduce the number of measurements we need to perform in the VQE algorithm? +# To do so, we must find the answer to two questions: +# +# 1. How do we determine which terms of the cost Hamiltonian commute? +# +# 2. How do we rotate the circuit into the shared eigenbasis prior to measurement? +# +# The answers to these questions aren't necessarily easy nor straightforward. Thankfully, there are +# some recent techniques we can harness to address both. + +############################################################################## +# Qubit-wise commuting Pauli terms +# -------------------------------- +# +# Back when we summarized the VQE algorithm, we saw that each term of the Hamiltonian is generally represented +# as a tensor product of Pauli operators: +# +# .. math:: h_i = \bigotimes_{n=0}^{N-1} P_n. +# +# Luckily, this tensor product structure allows us to take a bit of a shortcut. Rather than consider +# **full commutativity**, we can consider a more strict condition known as **qubit-wise +# commutativity** (QWC). +# +# To start with, let's consider single-qubit Pauli operators and the identity. We know that the Pauli operators +# commute with themselves as well as the identity, but they do *not* commute with +# each other: +# +# .. math:: +# +# [\sigma_i, I] = 0, ~~~ [\sigma_i, \sigma_i] = 0, ~~~ [\sigma_i, \sigma_j] = c \sigma_k \delta_{ij}. +# +# Now consider two tensor products of Pauli terms, for example :math:`X\otimes Y \otimes I` and +# :math:`X\otimes I \otimes Z.` We say that these two terms are qubit-wise commuting, since, if +# we compare each subsystem in the tensor product, we see that every one commutes: +# +# .. math:: +# +# \begin{array}{ | *1c | *1c | *1c | *1c | *1c |} +# X &\otimes &Y &\otimes &I\\ +# X &\otimes &I &\otimes &Z +# \end{array} ~~~~ \Rightarrow ~~~~ [X, X] = 0, ~~~ [Y, I] = 0, ~~~ [I, Z] = 0. +# +# As a consequence, both terms must commute: +# +# .. math:: [X\otimes Y \otimes I, X\otimes I \otimes Z] = 0. +# +# .. important:: +# +# Qubit-wise commutativity is a **sufficient** but not **necessary** condition +# for full commutativity. For example, the two Pauli terms :math:`Y\otimes Y` and +# :math:`X\otimes X` are not qubit-wise commuting, but do commute (have a go verifying this!). +# +# Once we have identified a qubit-wise commuting pair of Pauli terms, it is also straightforward to +# find the gates to rotate the circuit into the shared eigenbasis. To do so, we simply rotate +# each qubit one-by-one depending on the Pauli operator we are measuring on that wire: +# +# .. raw:: html +# +# +#
+# +# .. rst-class:: docstable +# +# +------------------+-------------------------------+ +# | Observable | Rotation gate | +# +==================+===============================+ +# | :math:`X` | :math:`RY(-\pi/2) = H` | +# +------------------+-------------------------------+ +# | :math:`Y` | :math:`RX(\pi/2)=HS^{-1}=HSZ` | +# +------------------+-------------------------------+ +# | :math:`Z` | :math:`I` | +# +------------------+-------------------------------+ +# | :math:`I` | :math:`I` | +# +------------------+-------------------------------+ +# +# .. raw:: html +# +#
+# +# Therefore, in this particular example: +# +# * Wire 0: we are measuring both terms in the :math:`X` basis, apply the Hadamard gate +# * Wire 1: we are measuring both terms in the :math:`Y` basis, apply a :math:`RX(\pi/2)` gate +# * Wire 2: we are measuring both terms in the :math:`Z` basis (the computational basis), no gate needs to be applied. +# +# Let's use PennyLane to verify this. + + +obs = [ + qml.PauliX(0) @ qml.PauliY(1), + qml.PauliX(0) @ qml.PauliZ(2) +] + + +############################################################################## +# First, let's naively use two separate circuit evaluations to measure +# the two QWC terms. + + +dev = qml.device("default.qubit", wires=3) + +@qml.qnode(dev, interface="jax") +def circuit1(weights): + qml.StronglyEntanglingLayers(weights, wires=range(3)) + return qml.expval(obs[0]) + + +@qml.qnode(dev, interface="jax") +def circuit2(weights): + qml.StronglyEntanglingLayers(weights, wires=range(3)) + return qml.expval(obs[1]) + +param_shape = qml.templates.StronglyEntanglingLayers.shape(n_layers=3, n_wires=3) +key, scale = random.PRNGKey(192933), 0.1 +weights = scale * random.normal(key, shape=param_shape) + +print("Expectation value of XYI = ", circuit1(weights)) +print("Expectation value of XIZ = ", circuit2(weights)) + +############################################################################## +# Now, let's use our QWC approach to reduce this down to a *single* measurement +# of the probabilities in the shared eigenbasis of both QWC observables: + +@qml.qnode(dev, interface="jax") +def circuit_qwc(weights): + qml.StronglyEntanglingLayers(weights, wires=range(3)) + + # rotate wire 0 into the shared eigenbasis + qml.RY(-jnp.pi / 2, wires=0) + + # rotate wire 1 into the shared eigenbasis + qml.RX(jnp.pi / 2, wires=1) + + # wire 2 does not require a rotation + + # measure probabilities in the computational basis + return qml.probs(wires=range(3)) + + +rotated_probs = circuit_qwc(weights) +print(rotated_probs) + +############################################################################## +# We're not quite there yet; we have only calculated the probabilities of the variational circuit +# rotated into the shared eigenbasis—the :math:`|\langle \phi_n |\psi\rangle|^2.` To recover the +# *expectation values* of the two QWC observables from the probabilities, recall that we need one +# final piece of information: their eigenvalues :math:`\lambda_{A, n}` and :math:`\lambda_{B, n}.` +# +# We know that the single-qubit Pauli operators each have eigenvalues :math:`(1, -1),` while the identity +# operator has eigenvalues :math:`(1, 1).` We can make use of ``np.kron`` to quickly +# generate the eigenvalues of the full Pauli terms, making sure that the order +# of the eigenvalues in the Kronecker product corresponds to the tensor product. + +eigenvalues_XYI = jnp.kron(jnp.kron(jnp.array([1, -1]), jnp.array([1, -1])), jnp.array([1, 1])) +eigenvalues_XIZ = jnp.kron(jnp.kron(jnp.array([1, -1]), jnp.array([1, 1])), jnp.array([1, -1])) + +# Taking the linear combination of the eigenvalues and the probabilities +print("Expectation value of XYI = ", jnp.dot(eigenvalues_XYI, rotated_probs)) +print("Expectation value of XIZ = ", jnp.dot(eigenvalues_XIZ, rotated_probs)) + + +############################################################################## +# Compare this to the result when we used two circuit evaluations. We have successfully used a +# single circuit evaluation to recover both expectation values! +# +# Luckily, PennyLane automatically performs this QWC grouping under the hood. We simply +# return the two QWC Pauli terms from the QNode: + +@qml.qnode(dev, interface="jax") +def circuit(weights): + qml.StronglyEntanglingLayers(weights, wires=range(3)) + return [ + qml.expval(qml.PauliX(0) @ qml.PauliY(1)), + qml.expval(qml.PauliX(0) @ qml.PauliZ(2)) + ] + + +print(circuit(weights)) + + +############################################################################## +# Behind the scenes, PennyLane is making use of our built-in +# :mod:`qml.pauli ` module, which contains functions for diagonalizing QWC +# terms: + +rotations, new_obs = qml.pauli.diagonalize_qwc_pauli_words(obs) + +print(rotations) +print(new_obs) + + +############################################################################## +# Here, the first line corresponds to the basis rotations that were discussed above, written in +# terms of ``RX`` and ``RY`` rotations. Check out the :mod:`qml.pauli ` +# documentation for more details on its provided functionality and how it works. +# +# Given a Hamiltonian containing a large number of Pauli terms, +# there is a high likelihood of there being a significant number of terms that qubit-wise commute. Can +# we somehow partition the terms into **fewest** number of QWC groups to minimize the number of measurements +# we need to take? + +############################################################################## +# Grouping QWC terms +# ------------------ +# +# A nice example is provided in [#verteletskyi2020]_ showing how we might tackle this. Say we have +# the following Hamiltonian defined over four qubits: +# +# .. math:: H = Z_0 + Z_0 Z_1 + Z_0 Z_1 Z_2 + Z_0 Z_1 Z_2 Z_3 + X_2 X_3 + Y_0 X_2 X_3 + Y_0 Y_1 X_2 X_3, +# +# where we are using the shorthand :math:`P_0 P_2 = P\otimes I \otimes P \otimes I` for brevity. +# If we go through and work out which Pauli terms are qubit-wise commuting, we can represent +# this in a neat way using a graph: +# +# .. figure:: /_static/demonstration_assets/measurement_optimize/graph1.png +# :width: 70% +# :align: center +# +# In the above graph, every node represents an individual Pauli term of the Hamiltonian, with +# edges connecting terms that are qubit-wise commuting. Groups of qubit-wise commuting terms are +# represented as **complete subgraphs**. Straight away, we can make an observation: +# there is no unique solution for partitioning the Hamiltonian into groups of qubit-wise commuting +# terms! In fact, there are several solutions: +# +# .. figure:: /_static/demonstration_assets/measurement_optimize/graph2.png +# :width: 90% +# :align: center +# +# Of course, of the potential solutions above, there is one that is more optimal than the others --- +# on the bottom left, we have partitioned the graph into *two* complete subgraphs, as opposed to the +# other solutions that require three complete subgraphs. If we were to go with this solution, +# we would be able to measure the expectation value of the Hamiltonian using two circuit evaluations. +# +# This problem—finding the minimum number of complete subgraphs of a graph—is actually quite well +# known in graph theory, where it is referred to as the `minimum clique cover problem +# `__ (with 'clique' being another term for a complete subgraph). +# +# Unfortunately, that's where our good fortune ends—the minimum clique cover problem is known to +# be `NP-hard `__, meaning there is no known (classical) +# solution to finding the optimum/minimum clique cover in polynomial time. +# +# Thankfully, there is a silver lining: we know of polynomial-time algorithms for finding +# *approximate* solutions to the minimum clique cover problem. These heuristic approaches, while +# not guaranteed to find the optimum solution, scale quadratically with the number of nodes in the +# graph/terms in the Hamiltonian [#yen2020]_, so work reasonably well in practice. +# +# Many of these heuristic approaches have roots in another graph problem known as `graph +# colouring `__; the assignment of colours to +# the graph's vertices such that no adjacent vertices have the same colour. How is this related +# to the minimum clique cover problem, though? If we take our QWC graph above, and generate the +# `complement graph `__ by drawing edges +# between all *non*-adjacent nodes, +# +# .. figure:: /_static/demonstration_assets/measurement_optimize/graph3.png +# :width: 100% +# :align: center +# +# we see that solving the minimum clique cover problem on the QWC graph is equivalent to solving the +# graph colouring problem on the complement graph using the minimum possible number of colours. +# While there are various different heuristic algorithms, a common one is `greedy colouring +# `__; in fact, the open-source graph +# package `NetworkX even provides a function for greedy colouring +# `__, +# ``nx.greedy_color``. +# +# Let's give this a go, using NetworkX to solve the minimum clique problem for observable grouping. +# First, we'll need to generate the QWC graph (with each node corresponding to a Hamiltonian +# term, and edges indicating two terms that are QWC). + +import networkx as nx +from matplotlib import pyplot as plt + +terms = [ + qml.PauliZ(0), + qml.PauliZ(0) @ qml.PauliZ(1), + qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2), + qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3), + qml.PauliX(2) @ qml.PauliX(3), + qml.PauliY(0) @ qml.PauliX(2) @ qml.PauliX(3), + qml.PauliY(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliX(3) +] + +def format_pauli_word(term): + """Convenience function that nicely formats a PennyLane + tensor observable as a Pauli word""" + if isinstance(term, qml.ops.Prod): + return " ".join([format_pauli_word(t) for t in term]) + + return f"{term.name[-1]}{term.wires.tolist()[0]}" + +G = nx.Graph() + +with warnings.catch_warnings(): + # Muting irrelevant warnings + warnings.filterwarnings( + "ignore", + message="The behaviour of operator ", + category=UserWarning, + ) + + # add the terms to the graph + G.add_nodes_from(terms) + + # add QWC edges + G.add_edges_from([ + [terms[0], terms[1]], # Z0 <--> Z0 Z1 + [terms[0], terms[2]], # Z0 <--> Z0 Z1 Z2 + [terms[0], terms[3]], # Z0 <--> Z0 Z1 Z2 Z3 + [terms[1], terms[2]], # Z0 Z1 <--> Z0 Z1 Z2 + [terms[2], terms[3]], # Z0 Z1 Z2 <--> Z0 Z1 Z2 Z3 + [terms[1], terms[3]], # Z0 Z1 <--> Z0 Z1 Z2 Z3 + [terms[0], terms[4]], # Z0 <--> X2 X3 + [terms[1], terms[4]], # Z0 Z1 <--> X2 X3 + [terms[4], terms[5]], # X2 X3 <--> Y0 X2 X3 + [terms[4], terms[6]], # X2 X3 <--> Y0 Y1 X2 X3 + [terms[5], terms[6]], # Y0 X2 X3 <--> Y0 Y1 X2 X3 + ]) + + plt.margins(x=0.1) + coords = nx.spring_layout(G, seed=1) + nx.draw( + G, + coords, + labels={node: format_pauli_word(node) for node in terms}, + with_labels=True, + node_size=500, + font_size=8, + node_color="#9eded1", + edge_color="#c1c1c1", + ) + + ############################################################################## + # We can now generate the complement graph (compare this to our handdrawn + # version above!): + + C = nx.complement(G) + coords = nx.spring_layout(C, seed=1) + + nx.draw( + C, + coords, + labels={node: format_pauli_word(node) for node in terms}, + with_labels=True, + node_size=500, + font_size=8, + node_color="#9eded1", + edge_color="#c1c1c1" + ) + + ############################################################################## + # Now that we have the complement graph, we can perform a greedy coloring to + # determine the minimum number of QWC groups: + + groups = nx.coloring.greedy_color(C, strategy="largest_first") + + # plot the complement graph with the greedy colouring + nx.draw( + C, + coords, + labels={node: format_pauli_word(node) for node in terms}, + with_labels=True, + node_size=500, + font_size=8, + node_color=[("#9eded1", "#aad4f0")[groups[node]] for node in C], + edge_color="#c1c1c1" + ) + + +num_groups = len(set(groups.values())) +print("Minimum number of QWC groupings found:", num_groups) + + +for i in range(num_groups): + print(f"\nGroup {i}:") + + for term, group_id in groups.items(): + if group_id == i: + print(format_pauli_word(term)) + +############################################################################## +# Putting it all together +# ----------------------- +# +# So, we now have a strategy for minimizing the number of measurements we need to perform +# for our VQE problem: +# +# 1. Determine which terms of the Hamiltonian are qubit-wise commuting, and use +# this to construct a graph representing the QWC relationship. +# +# 2. Construct the complement QWC graph. +# +# 3. Use a graph colouring heuristic algorithm to determine a graph colouring for the complement graph +# with a minimum number of colours. Each coloured vertex set corresponds to a +# qubit-wise commuting group of Hamiltonian terms. +# +# 4. Generate and evaluate the circuit ansatz (with additional rotations) per +# QWC grouping, extracting probability distributions. +# +# 5. Finally, post-process the probability distributions with the observable eigenvalues +# to recover the Hamiltonian expectation value. +# +# Luckily, the PennyLane ``pauli`` module makes this relatively easy. Let's walk through +# the entire process using the provided grouping functions. +# +# Steps 1-3 (finding and grouping QWC terms in the Hamiltonian) can be done via the +# :func:`qml.pauli.group_observables ` function: + +obs_groupings = qml.pauli.group_observables(terms, grouping_type='qwc', method='rlf') + + +############################################################################## +# The ``grouping_type`` argument allows us to choose how the commuting terms +# are determined (more on that later!) whereas ``method`` determines the colouring +# heuristic (in this case, ``"rlf"`` refers to Recursive Largest First, a variant of largest first colouring heuristic). +# +# If we want to see what the required rotations and measurements are, we can use the +# :func:`qml.pauli.diagonalize_qwc_groupings ` +# function: + +rotations, measurements = qml.pauli.diagonalize_qwc_groupings(obs_groupings) + +############################################################################## +# However, this isn't strictly necessary—recall previously that the QNode +# has the capability to *automatically* measure qubit-wise commuting observables! + +dev = qml.device("lightning.qubit", wires=4) + +@qml.qnode(dev, interface="jax") +def circuit(weights, group=None, **kwargs): + qml.StronglyEntanglingLayers(weights, wires=range(4)) + return [qml.expval(o) for o in group] + +param_shape = qml.templates.StronglyEntanglingLayers.shape(n_layers=3, n_wires=4) +key = random.PRNGKey(1) +weights = random.normal(key, shape=param_shape) * 0.1 +result = [jnp.array(circuit(weights, group=g)) for g in obs_groupings] + +print("Term expectation values:") +for group, expvals in enumerate(result): + print(f"Group {group} expectation values:", expvals) + +# Since all the coefficients of the Hamiltonian are unity, +# we can simply sum the expectation values. +print(" = ", jnp.sum(jnp.hstack(result))) + + +############################################################################## +# Finally, we don't need to go through this process manually every time; if our cost function can be +# written in the form of an expectation value of a Hamiltonian (as is the case for most VQE and QAOA +# problems), we can use the option ``grouping_type="qwc"`` in :class:`~.pennylane.Hamiltonian` to +# automatically optimize the measurements. + +H = qml.Hamiltonian(coeffs=jnp.ones(len(terms)), observables=terms, grouping_type="qwc") +_, H_ops = H.terms() +@qml.qnode(dev, interface="jax") +def cost_fn(weights): + qml.StronglyEntanglingLayers(weights, wires=range(4)) + return qml.expval(H) +print(cost_fn(weights)) + +############################################################################## +# Beyond VQE +# ---------- +# +# Wait, hang on. We dove so deeply into measurement grouping and optimization, we forgot to check +# how this affects the number of measurements required to perform the VQE on :math:`\text{H}_2 \text{O}!` +# Let's use our new-found knowledge to see what happens. + +dataset = qml.data.load('qchem', molname="H2O")[0] +H, num_qubits = dataset.hamiltonian, len(dataset.hamiltonian.wires) +print("Number of Hamiltonian terms/required measurements:", len(H_ops)) + +# grouping +groups = qml.pauli.group_observables(H_ops, grouping_type='qwc', method='rlf') +print("Number of required measurements after optimization:", len(groups)) + +############################################################################## +# We went from 1086 required measurements/circuit evaluations to 320 (just over *one thousand* +# down to *three hundred* 😱😱😱). +# +# As impressive as this is, however, this is just the beginning of the optimization. +# +# While finding qubit-wise commutating terms is relatively straightforward, with a little +# extra computation we can push this number down even further. Recent work has explored +# the savings that can be made by considering *full* commutativity [#yen2020]_, unitary +# partitioning [#izmaylov2019]_, and Fermionic basis rotation grouping [#huggins2019]_. +# Work has also been performed to reduce the classical overhead associated with measurement +# optimization, allowing the classical measurement grouping to be performed in linear time +# [#gokhale2020]_. For example, recall that qubit-wise commutativity is only a subset of +# full commutativity; if we consider full commutativity instead, we can further reduce the +# number of groups required. +# +# Finally, it is worth pointing out that, as the field of variational quantum algorithms grows, this +# problem of measurement optimization no longer just applies to the VQE algorithm (the algorithm it +# was born from). Instead, there are a multitude of algorithms that could benefit from these +# measurement optimization techniques (QAOA being a prime example). +# +# So the next time you are working on a variational quantum algorithm and the number +# of measurements required begins to explode—stop, take a deep breath 😤, and consider grouping +# and optimizing your measurements. +# +# .. note:: +# +# Qubit-wise commuting group information for a wide variety of molecules has been +# pre-computed, and is available for download in +# in the `PennyLane Datasets library `__. + +############################################################################## +# References +# ---------- +# +# .. [#peruzzo2014] +# +# Alberto Peruzzo, Jarrod McClean *et al.*, "A variational eigenvalue solver on a photonic +# quantum processor". `Nature Communications 5, 4213 (2014). +# `__ +# +# .. [#yen2020] +# +# Tzu-Ching Yen, Vladyslav Verteletskyi, and Artur F. Izmaylov. "Measuring all compatible +# operators in one series of single-qubit measurements using unitary transformations." `Journal of +# Chemical Theory and Computation 16.4 (2020): 2400-2409. +# `__ +# +# .. [#izmaylov2019] +# +# Artur F. Izmaylov, *et al.* "Unitary partitioning approach to the measurement problem in the +# variational quantum eigensolver method." `Journal of Chemical Theory and Computation 16.1 (2019): +# 190-195. `__ +# +# .. [#huggins2019] +# +# William J. Huggins, *et al.* "Efficient and noise resilient measurements for quantum chemistry +# on near-term quantum computers." `arXiv preprint arXiv:1907.13117 (2019). +# `__ +# +# .. [#gokhale2020] +# +# Pranav Gokhale, *et al.* "Minimizing state preparations in variational quantum eigensolver by +# partitioning into commuting families." `arXiv preprint arXiv:1907.13623 (2019). +# `__ +# +# .. [#verteletskyi2020] +# +# Vladyslav Verteletskyi, Tzu-Ching Yen, and Artur F. Izmaylov. "Measurement optimization in the +# variational quantum eigensolver using a minimum clique cover." `The Journal of Chemical Physics +# 152.12 (2020): 124114. `__ +# +# +# About the author +# ---------------- +# diff --git a/demonstrations/tutorial_pasqal.py b/demonstrations/tutorial_pasqal.py index 8618d05f4b..b49898f392 100644 --- a/demonstrations/tutorial_pasqal.py +++ b/demonstrations/tutorial_pasqal.py @@ -1,362 +1,362 @@ -r""" -Quantum computation with neutral atoms -====================================== - -.. meta:: - :property="og:description": Neutral atom quantum devices allow you to place - qubits within interesting three-dimensional configurations. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/pasqal_thumbnail.png - -.. related:: - ahs_aquila Pulse programming on neutral atom hardware - -*Author: Nathan Killoran — Posted: 13 October 2020. Last updated: 21 January 2021.* - -Quantum computing architectures come in many flavours: superconducting qubits, ion traps, -photonics, silicon, and more. One very interesting physical substrate is *neutral atoms*. These -quantum devices have some basic similarities to ion traps. Ion-trap devices make use of atoms -that have an imbalance between protons (positively charged) and electrons (negatively charged). -Neutral atoms, on the other hand, have an equal number of protons and electrons. - -In neutral-atom systems, the individual atoms can be easily programmed into various two- or -three-dimensional configurations. Like ion-trap systems, individual qubits can be encoded into -the energy levels of the atoms. The qubits thus retain the three-dimensional geometry of their -host atoms. Qubits that are nearby in space can be programmed to interact with one another -via two-qubit gates. This opens up some tantalizing possibilities for exotic quantum-computing -circuit topologies. - -.. figure:: https://raw.githubusercontent.com/lhenriet/cirq-pasqal/fc4f9c7792a8737fde76d4c05828aa538be8452e/pasqal-tutorials/files/eiffel_tower.png - :align: center - :width: 50% - - .. - - Neutral atoms (green dots) arranged in various configurations. These atoms can be - used to encode qubits and carry out quantum computations. Image originally from [#barredo2017]_. - -The startup `Pasqal `_ is one of the companies working to bring -neutral-atom quantum computing devices to the world. To support this new class of devices, -Pasqal has contributed some new features to the quantum software library `Cirq `_. - -In this demo, we will use PennyLane, Cirq, and TensorFlow to show off the unique abilities of -neutral atom devices, leveraging them to make a variational quantum circuit which has a -very unique topology: *the Eiffel tower*. Specifically, we will build a simple toy -circuit whose qubits are arranged like the Eiffel tower. The girders between -the points on the tower will represent two-qubit gates, with the final output of our -variational circuit coming at the very peak of the tower. - -Let's get to it! - -.. note:: - - To run this demo locally, you will need to install `Cirq - `_, (version >= 0.9.1), and the - `PennyLane-cirq plugin `_ - (version >= 0.13). You will also need to download a copy of the data, which - is available `here - `_. - -""" - -############################################################################## -# Building the Eiffel tower -# ------------------------- -# -# Our first step will be to load and visualize the data for the Eiffel tower -# configuration, which was generously provided by the team at Pasqal. -# (If running locally, the line below should be updated with the local -# path where you have saved the downloaded data). - -import numpy as np -coords = np.loadtxt("../_static/demonstration_assets/pasqal/Eiffel_tower_data.dat") -xs = coords[:,0] -ys = coords[:,1] -zs = coords[:,2] - -import matplotlib.pyplot as plt -fig = plt.figure() -ax = fig.add_subplot(111, projection='3d') -ax.view_init(40, 15) -fig.subplots_adjust(left=0, right=1, bottom=0, top=1) -ax.set_xlim(-20, 20) -ax.set_ylim(-20, 20) -ax.set_zlim(-40, 10) -plt.axis('off') -ax.scatter(xs, ys, zs, c='g',alpha=0.3) -plt.show() - -############################################################################## -# This dataset contains 126 points. Each point represents a distinct -# neutral-atom qubit. Simulating this many qubits would be outside the -# reach of Cirq's built-in simulators, so for this demo, -# we will pare down to just 9 points, evenly spaced around the tower. -# These are highlighted in red below. -# - -fig = plt.figure() -ax = fig.add_subplot(111, projection='3d') -ax.view_init(40, 15) -fig.subplots_adjust(left=0, right=1, bottom=0, top=1) -ax.set_xlim(-20, 20) -ax.set_ylim(-20, 20) -ax.set_zlim(-40, 10) -plt.axis('off') -ax.scatter(xs, ys, zs, c='g', alpha=0.3) - -base_mask = [3, 7, 11, 15] -qubit_mask = [48, 51, 60, 63, 96, 97, 98, 99, 125] -input_coords = coords[base_mask] # we'll need this for a plot later -qubit_coords = coords[qubit_mask] - -subset_xs = qubit_coords[:, 0] -subset_ys = qubit_coords[:, 1] -subset_zs = qubit_coords[:, 2] -ax.scatter(subset_xs, subset_ys, subset_zs, c='r', alpha=1.0) -plt.show() - -############################################################################## -# Converting to Cirq qubits -# ------------------------- -# -# Our next step will be to convert these datapoints into objects that -# Cirq understands as qubits. For neutral-atom devices in Cirq, we can use the -# ``ThreeDQubit`` class, which carries information about the three-dimensional -# arrangement of the qubits. -# -# Now, neutral-atom devices come with some physical restrictions. -# Specifically, in a particular three-dimensional configuration, qubits that -# are too distant from one another can't easily interact. Instead, there is -# a notion of a *control radius;* any atoms which are within the system's -# control radius can interact with one another. Qubits separated by a -# distance larger than the control radius cannot interact. -# -# In order to allow our Eiffel tower qubits to interact with -# one another more easily, we will artificially scale some dimensions -# when placing the atoms. - -from cirq_pasqal import ThreeDQubit -xy_scale = 1.5 -z_scale = 0.75 -qubits = [ThreeDQubit(xy_scale * x, xy_scale * y, z_scale * z) - for x, y, z in qubit_coords] - -############################################################################## -# To simulate a neutral-atom quantum computation, we can use the -# ``"cirq.pasqal"`` device, available via the -# `PennyLane-Cirq plugin `_. -# We will need to provide this device with the ``ThreeDQubit`` object that we created -# above. We also need to instantiate the device with a fixed control radius. - -import pennylane as qml - -num_wires = len(qubits) -control_radius = 32.4 -dev = qml.device("cirq.pasqal", control_radius=control_radius, - qubits=qubits, wires=num_wires) - -############################################################################## -# Creating a quantum circuit -# -------------------------- -# -# We will now make a variational circuit out of the Eiffel tower configuration -# from above. Each of the 9 qubits we are using can be thought of -# as a single wire in a quantum circuit. We will cause these qubits to interact by applying -# a sequence of two-qubit gates. Specifically, the circuit consists of several -# stages: -# -# i. Input classical data is converted into quantum information at the first -# (lowest) vertical level of qubits. In this example, our classical data -# will be simple bit strings, which we can embed by using single-qubit -# bit flips (a simple -# `data-embedding `_ -# strategy). -# -# ii. For each corner of the tower, CNOTs are enacted between the first- -# and second-level qubits. -# -# iii. All qubits from the second level interact with a single "peak" qubit -# using a parametrized controlled-rotation operation. The free parameters -# of our variational circuit enter here. -# -# The output of our circuit is determined via a Pauli-Z measurement on -# the final "peak" qubit. -# -# That's a few things to keep track of, so let's show the circuit via a -# three-dimensional image: - -first_lvl_coords = qubit_coords[:4] -second_lvl_coords = qubit_coords[4:8] -peak_coords = qubit_coords[8] - -input_x, input_y, input_z = [input_coords[:, idx] - for idx in range(3)] -second_x, second_y, second_z = [first_lvl_coords[:, idx] - for idx in range(3)] -third_x, third_y, third_z = [second_lvl_coords[:, idx] - for idx in range(3)] -peak_x, peak_y, peak_z = peak_coords - -fig = plt.figure() -ax = fig.add_subplot(111, projection='3d') -ax.view_init(40, 15) -fig.subplots_adjust(left=0, right=1, bottom=0, top=1) -ax.set_xlim(-20, 20) -ax.set_ylim(-20, 20) -ax.set_zlim(-40, 10) -plt.axis('off') - -ax.scatter(xs, ys, zs, c='g', alpha=0.3) -ax.scatter(subset_xs, subset_ys, subset_zs, c='r', alpha=1.0); - -# Two-qubit gates between second and third levels -for corner in range(4): - ax.plot(xs=[second_x[corner], third_x[corner]], - ys=[second_y[corner], third_y[corner]], - zs=[second_z[corner], third_z[corner]], - c='k'); - -# Two-qubit gates between third level and peak -for corner in range(4): - ax.plot(xs=[third_x[corner], peak_x], - ys=[third_y[corner], peak_y], - zs=[third_z[corner], peak_z], - c='k'); - -# Additional lines to guide the eye -for corner in range(4): - ax.plot(xs=[input_x[corner], second_x[corner]], - ys=[input_y[corner], second_y[corner]], - zs=[input_z[corner], second_z[corner]], - c='grey', linestyle='--'); - ax.plot(xs=[second_x[corner], second_x[(corner + 1) % 4]], - ys=[second_y[corner], second_y[(corner + 1) % 4]], - zs=[second_z[corner], second_z[(corner + 1) % 4]], - c='grey', linestyle='--'); - ax.plot(xs=[third_x[corner], third_x[(corner + 1) % 4]], - ys=[third_y[corner], third_y[(corner + 1) % 4]], - zs=[third_z[corner], third_z[(corner + 1) % 4]], - c='grey', linestyle='--'); - -plt.show() - -############################################################################## -# In this figure, the red dots represent the specific qubits we will use in -# our circuit (the green dots are not used in this demo). -# -# The solid black lines indicate two-qubit gates between these qubits. -# The dashed grey lines are meant to guide the eye, but could also be -# used to make a more complex model by adding further two-qubit gates. -# -# Classical data is loaded in at the bottom qubits (the "tower legs") and -# the final measurement result is read out from the top "peak" qubit. -# The order of gate execution proceeds vertically from bottom to top, and -# clockwise at each level. -# -# The code below creates this particular quantum circuit configuration in -# PennyLane: - -peak_qubit = 8 - -def controlled_rotation(phi, wires): - qml.RY(phi, wires=wires[1]) - qml.CNOT(wires=wires) - qml.RY(-phi, wires=wires[1]) - qml.CNOT(wires=wires) - -@qml.qnode(dev, interface="tf") -def circuit(weights, data): - - # Input classical data loaded into qubits at second level - for idx in range(4): - if data[idx]: - qml.PauliX(wires=idx) - - # Interact qubits from second and third levels - for idx in range(4): - qml.CNOT(wires=[idx, idx + 4]) - - # Interact qubits from third level with peak using parameterized gates - for idx, wire in enumerate(range(4, 8)): - controlled_rotation(weights[idx], wires=[wire, peak_qubit]) - - return qml.expval(qml.PauliZ(wires=peak_qubit)) - - -############################################################################## -# Training the circuit -# -------------------- -# -# Let's now leverage this variational circuit to tackle a toy classification -# problem. -# For the purposes of this demo, we will consider a very simple classifier: -# -# * if the first input qubit is in the state :math:`\mid 0 \rangle,` the model -# should make the prediction "0", and -# -# * if the first input qubit is in the state :math:`\mid 1 \rangle,` the model -# should predict "1" (independent of the states of all other qubits). -# -# In other words, the idealized trained model should learn an -# identity transformation between the first qubit and the final one, while -# ignoring the states of all other qubits. -# -# With this goal in mind, we can create a basic cost function. This cost -# function randomly samples possible 4-bit input bitstrings, and compares -# the circuit's output with the value of the first bit. The other bits -# can be thought of as noise that we don't want our model to learn. - - -import tensorflow as tf -np.random.seed(143) -init_weights = np.pi * np.random.rand(4) - -weights = tf.Variable(init_weights, dtype=tf.float64) - -data = np.random.randint(0, 2, size=4) - -def cost(): - data = np.random.randint(0, 2, size=4) - label = data[0] - output = (-circuit(weights, data) + 1) / 2 - return tf.abs(output - label) ** 2 - -opt = tf.keras.optimizers.Adam(learning_rate=0.1) - -for step in range(100): - opt.minimize(cost, [weights]) - if step % 5 == 0: - print("Step {}: cost={}".format(step, cost())) - -print("Final cost value: {}".format(cost())) - -############################################################################## -# Success! The circuit has learned to transfer the state of the first qubit -# to the state of the last qubit, while ignoring the state of all other input -# qubits. -# -# The programmable three-dimensional configurations of neutral-atom quantum -# computers provide a special tool that is hard to replicate in other -# platforms. Could the physical -# arrangement of qubits, in particular the third dimension, be leveraged to -# make quantum algorithms more sparse or efficient? Could neutral-atom -# systems—with their unique programmability of the geometry—allow us to -# rapidly prototype and experiment with new circuit topologies? What -# possibilities could this open up for quantum computing, quantum chemistry, -# or quantum machine learning? -# - -############################################################################## -# References -# ---------- -# -# .. [#barredo2017] -# -# Daniel Barredo, Vincent Lienhard, Sylvain de Leseleuc, Thierry Lahaye, and Antoine Browaeys. -# "Synthetic three-dimensional atomic structures assembled atom by atom." -# `arXiv:1712.02727 -# `__, 2017. -# -# -# About the author -# ---------------- +r""" +Quantum computation with neutral atoms +====================================== + +.. meta:: + :property="og:description": Neutral atom quantum devices allow you to place + qubits within interesting three-dimensional configurations. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/pasqal_thumbnail.png + +.. related:: + ahs_aquila Pulse programming on neutral atom hardware + +*Author: Nathan Killoran — Posted: 13 October 2020. Last updated: 21 January 2021.* + +Quantum computing architectures come in many flavours: superconducting qubits, ion traps, +photonics, silicon, and more. One very interesting physical substrate is *neutral atoms*. These +quantum devices have some basic similarities to ion traps. Ion-trap devices make use of atoms +that have an imbalance between protons (positively charged) and electrons (negatively charged). +Neutral atoms, on the other hand, have an equal number of protons and electrons. + +In neutral-atom systems, the individual atoms can be easily programmed into various two- or +three-dimensional configurations. Like ion-trap systems, individual qubits can be encoded into +the energy levels of the atoms. The qubits thus retain the three-dimensional geometry of their +host atoms. Qubits that are nearby in space can be programmed to interact with one another +via two-qubit gates. This opens up some tantalizing possibilities for exotic quantum-computing +circuit topologies. + +.. figure:: https://raw.githubusercontent.com/lhenriet/cirq-pasqal/fc4f9c7792a8737fde76d4c05828aa538be8452e/pasqal-tutorials/files/eiffel_tower.png + :align: center + :width: 50% + + .. + + Neutral atoms (green dots) arranged in various configurations. These atoms can be + used to encode qubits and carry out quantum computations. Image originally from [#barredo2017]_. + +The startup `Pasqal `_ is one of the companies working to bring +neutral-atom quantum computing devices to the world. To support this new class of devices, +Pasqal has contributed some new features to the quantum software library `Cirq `_. + +In this demo, we will use PennyLane, Cirq, and TensorFlow to show off the unique abilities of +neutral atom devices, leveraging them to make a variational quantum circuit which has a +very unique topology: *the Eiffel tower*. Specifically, we will build a simple toy +circuit whose qubits are arranged like the Eiffel tower. The girders between +the points on the tower will represent two-qubit gates, with the final output of our +variational circuit coming at the very peak of the tower. + +Let's get to it! + +.. note:: + + To run this demo locally, you will need to install `Cirq + `_, (version >= 0.9.1), and the + `PennyLane-cirq plugin `_ + (version >= 0.13). You will also need to download a copy of the data, which + is available `here + `_. + +""" + +############################################################################## +# Building the Eiffel tower +# ------------------------- +# +# Our first step will be to load and visualize the data for the Eiffel tower +# configuration, which was generously provided by the team at Pasqal. +# (If running locally, the line below should be updated with the local +# path where you have saved the downloaded data). + +import numpy as np +coords = np.loadtxt("../_static/demonstration_assets/pasqal/Eiffel_tower_data.dat") +xs = coords[:,0] +ys = coords[:,1] +zs = coords[:,2] + +import matplotlib.pyplot as plt +fig = plt.figure() +ax = fig.add_subplot(111, projection='3d') +ax.view_init(40, 15) +fig.subplots_adjust(left=0, right=1, bottom=0, top=1) +ax.set_xlim(-20, 20) +ax.set_ylim(-20, 20) +ax.set_zlim(-40, 10) +plt.axis('off') +ax.scatter(xs, ys, zs, c='g',alpha=0.3) +plt.show() + +############################################################################## +# This dataset contains 126 points. Each point represents a distinct +# neutral-atom qubit. Simulating this many qubits would be outside the +# reach of Cirq's built-in simulators, so for this demo, +# we will pare down to just 9 points, evenly spaced around the tower. +# These are highlighted in red below. +# + +fig = plt.figure() +ax = fig.add_subplot(111, projection='3d') +ax.view_init(40, 15) +fig.subplots_adjust(left=0, right=1, bottom=0, top=1) +ax.set_xlim(-20, 20) +ax.set_ylim(-20, 20) +ax.set_zlim(-40, 10) +plt.axis('off') +ax.scatter(xs, ys, zs, c='g', alpha=0.3) + +base_mask = [3, 7, 11, 15] +qubit_mask = [48, 51, 60, 63, 96, 97, 98, 99, 125] +input_coords = coords[base_mask] # we'll need this for a plot later +qubit_coords = coords[qubit_mask] + +subset_xs = qubit_coords[:, 0] +subset_ys = qubit_coords[:, 1] +subset_zs = qubit_coords[:, 2] +ax.scatter(subset_xs, subset_ys, subset_zs, c='r', alpha=1.0) +plt.show() + +############################################################################## +# Converting to Cirq qubits +# ------------------------- +# +# Our next step will be to convert these datapoints into objects that +# Cirq understands as qubits. For neutral-atom devices in Cirq, we can use the +# ``ThreeDQubit`` class, which carries information about the three-dimensional +# arrangement of the qubits. +# +# Now, neutral-atom devices come with some physical restrictions. +# Specifically, in a particular three-dimensional configuration, qubits that +# are too distant from one another can't easily interact. Instead, there is +# a notion of a *control radius;* any atoms which are within the system's +# control radius can interact with one another. Qubits separated by a +# distance larger than the control radius cannot interact. +# +# In order to allow our Eiffel tower qubits to interact with +# one another more easily, we will artificially scale some dimensions +# when placing the atoms. + +from cirq_pasqal import ThreeDQubit +xy_scale = 1.5 +z_scale = 0.75 +qubits = [ThreeDQubit(xy_scale * x, xy_scale * y, z_scale * z) + for x, y, z in qubit_coords] + +############################################################################## +# To simulate a neutral-atom quantum computation, we can use the +# ``"cirq.pasqal"`` device, available via the +# `PennyLane-Cirq plugin `_. +# We will need to provide this device with the ``ThreeDQubit`` object that we created +# above. We also need to instantiate the device with a fixed control radius. + +import pennylane as qml + +num_wires = len(qubits) +control_radius = 32.4 +dev = qml.device("cirq.pasqal", control_radius=control_radius, + qubits=qubits, wires=num_wires) + +############################################################################## +# Creating a quantum circuit +# -------------------------- +# +# We will now make a variational circuit out of the Eiffel tower configuration +# from above. Each of the 9 qubits we are using can be thought of +# as a single wire in a quantum circuit. We will cause these qubits to interact by applying +# a sequence of two-qubit gates. Specifically, the circuit consists of several +# stages: +# +# i. Input classical data is converted into quantum information at the first +# (lowest) vertical level of qubits. In this example, our classical data +# will be simple bit strings, which we can embed by using single-qubit +# bit flips (a simple +# `data-embedding `_ +# strategy). +# +# ii. For each corner of the tower, CNOTs are enacted between the first- +# and second-level qubits. +# +# iii. All qubits from the second level interact with a single "peak" qubit +# using a parametrized controlled-rotation operation. The free parameters +# of our variational circuit enter here. +# +# The output of our circuit is determined via a Pauli-Z measurement on +# the final "peak" qubit. +# +# That's a few things to keep track of, so let's show the circuit via a +# three-dimensional image: + +first_lvl_coords = qubit_coords[:4] +second_lvl_coords = qubit_coords[4:8] +peak_coords = qubit_coords[8] + +input_x, input_y, input_z = [input_coords[:, idx] + for idx in range(3)] +second_x, second_y, second_z = [first_lvl_coords[:, idx] + for idx in range(3)] +third_x, third_y, third_z = [second_lvl_coords[:, idx] + for idx in range(3)] +peak_x, peak_y, peak_z = peak_coords + +fig = plt.figure() +ax = fig.add_subplot(111, projection='3d') +ax.view_init(40, 15) +fig.subplots_adjust(left=0, right=1, bottom=0, top=1) +ax.set_xlim(-20, 20) +ax.set_ylim(-20, 20) +ax.set_zlim(-40, 10) +plt.axis('off') + +ax.scatter(xs, ys, zs, c='g', alpha=0.3) +ax.scatter(subset_xs, subset_ys, subset_zs, c='r', alpha=1.0); + +# Two-qubit gates between second and third levels +for corner in range(4): + ax.plot(xs=[second_x[corner], third_x[corner]], + ys=[second_y[corner], third_y[corner]], + zs=[second_z[corner], third_z[corner]], + c='k'); + +# Two-qubit gates between third level and peak +for corner in range(4): + ax.plot(xs=[third_x[corner], peak_x], + ys=[third_y[corner], peak_y], + zs=[third_z[corner], peak_z], + c='k'); + +# Additional lines to guide the eye +for corner in range(4): + ax.plot(xs=[input_x[corner], second_x[corner]], + ys=[input_y[corner], second_y[corner]], + zs=[input_z[corner], second_z[corner]], + c='grey', linestyle='--'); + ax.plot(xs=[second_x[corner], second_x[(corner + 1) % 4]], + ys=[second_y[corner], second_y[(corner + 1) % 4]], + zs=[second_z[corner], second_z[(corner + 1) % 4]], + c='grey', linestyle='--'); + ax.plot(xs=[third_x[corner], third_x[(corner + 1) % 4]], + ys=[third_y[corner], third_y[(corner + 1) % 4]], + zs=[third_z[corner], third_z[(corner + 1) % 4]], + c='grey', linestyle='--'); + +plt.show() + +############################################################################## +# In this figure, the red dots represent the specific qubits we will use in +# our circuit (the green dots are not used in this demo). +# +# The solid black lines indicate two-qubit gates between these qubits. +# The dashed grey lines are meant to guide the eye, but could also be +# used to make a more complex model by adding further two-qubit gates. +# +# Classical data is loaded in at the bottom qubits (the "tower legs") and +# the final measurement result is read out from the top "peak" qubit. +# The order of gate execution proceeds vertically from bottom to top, and +# clockwise at each level. +# +# The code below creates this particular quantum circuit configuration in +# PennyLane: + +peak_qubit = 8 + +def controlled_rotation(phi, wires): + qml.RY(phi, wires=wires[1]) + qml.CNOT(wires=wires) + qml.RY(-phi, wires=wires[1]) + qml.CNOT(wires=wires) + +@qml.qnode(dev, interface="tf") +def circuit(weights, data): + + # Input classical data loaded into qubits at second level + for idx in range(4): + if data[idx]: + qml.PauliX(wires=idx) + + # Interact qubits from second and third levels + for idx in range(4): + qml.CNOT(wires=[idx, idx + 4]) + + # Interact qubits from third level with peak using parameterized gates + for idx, wire in enumerate(range(4, 8)): + controlled_rotation(weights[idx], wires=[wire, peak_qubit]) + + return qml.expval(qml.PauliZ(wires=peak_qubit)) + + +############################################################################## +# Training the circuit +# -------------------- +# +# Let's now leverage this variational circuit to tackle a toy classification +# problem. +# For the purposes of this demo, we will consider a very simple classifier: +# +# * if the first input qubit is in the state :math:`\mid 0 \rangle,` the model +# should make the prediction "0", and +# +# * if the first input qubit is in the state :math:`\mid 1 \rangle,` the model +# should predict "1" (independent of the states of all other qubits). +# +# In other words, the idealized trained model should learn an +# identity transformation between the first qubit and the final one, while +# ignoring the states of all other qubits. +# +# With this goal in mind, we can create a basic cost function. This cost +# function randomly samples possible 4-bit input bitstrings, and compares +# the circuit's output with the value of the first bit. The other bits +# can be thought of as noise that we don't want our model to learn. + + +import tensorflow as tf +np.random.seed(143) +init_weights = np.pi * np.random.rand(4) + +weights = tf.Variable(init_weights, dtype=tf.float64) + +data = np.random.randint(0, 2, size=4) + +def cost(): + data = np.random.randint(0, 2, size=4) + label = data[0] + output = (-circuit(weights, data) + 1) / 2 + return tf.abs(output - label) ** 2 + +opt = tf.keras.optimizers.Adam(learning_rate=0.1) + +for step in range(100): + opt.minimize(cost, [weights]) + if step % 5 == 0: + print("Step {}: cost={}".format(step, cost())) + +print("Final cost value: {}".format(cost())) + +############################################################################## +# Success! The circuit has learned to transfer the state of the first qubit +# to the state of the last qubit, while ignoring the state of all other input +# qubits. +# +# The programmable three-dimensional configurations of neutral-atom quantum +# computers provide a special tool that is hard to replicate in other +# platforms. Could the physical +# arrangement of qubits, in particular the third dimension, be leveraged to +# make quantum algorithms more sparse or efficient? Could neutral-atom +# systems—with their unique programmability of the geometry—allow us to +# rapidly prototype and experiment with new circuit topologies? What +# possibilities could this open up for quantum computing, quantum chemistry, +# or quantum machine learning? +# + +############################################################################## +# References +# ---------- +# +# .. [#barredo2017] +# +# Daniel Barredo, Vincent Lienhard, Sylvain de Leseleuc, Thierry Lahaye, and Antoine Browaeys. +# "Synthetic three-dimensional atomic structures assembled atom by atom." +# `arXiv:1712.02727 +# `__, 2017. +# +# +# About the author +# ---------------- # \ No newline at end of file diff --git a/demonstrations/tutorial_qchem_external.py b/demonstrations/tutorial_qchem_external.py index 36bc3094f3..ae25f561bf 100644 --- a/demonstrations/tutorial_qchem_external.py +++ b/demonstrations/tutorial_qchem_external.py @@ -1,230 +1,230 @@ -r""" - -Using PennyLane with PySCF and OpenFermion -========================================== - -.. meta:: - :property="og:description": Learn how to integrate external quantum chemistry libraries with PennyLane. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_external_libs.png - - -.. related:: - tutorial_quantum_chemistry Quantum chemistry with PennyLane - tutorial_vqe A brief overview of VQE - tutorial_givens_rotations Givens rotations for quantum chemistry - tutorial_adaptive_circuits Adaptive circuits for quantum chemistry - -*Author: Soran Jahangiri — Posted: 3 January 2023.* - -The quantum chemistry module in PennyLane, :mod:`qchem `, provides built-in -methods to compute molecular integrals, solve Hartree-Fock equations, and construct -`fully-differentiable `_ molecular -Hamiltonians. PennyLane also lets you take advantage of various -external resources and libraries to build upon existing tools. In this demo we will show you how -to integrate PennyLane with `PySCF `_ and -`OpenFermion `_ to compute molecular integrals, -construct molecular Hamiltonians, and import initial states. - -Building molecular Hamiltonians -------------------------------- -In PennyLane, Hamiltonians for quantum chemistry are built with the -:func:`~.pennylane.qchem.molecular_hamiltonian` function by specifying a backend for solving the -Hartree–Fock equations. The default backend is the differentiable Hartree–Fock solver of the -:mod:`qchem ` module. A molecular Hamiltonian can also be constructed with -non-differentiable backends that use the electronic structure package -`PySCF `_ or the -`OpenFermion-PySCF `_ plugin. These -backends can be selected by setting the keyword argument ``method='pyscf'`` or -``method='openfermion'`` in :func:`~.pennylane.qchem.molecular_hamiltonian`. This requires -``PySCF`` or ``OpenFermion-PySCF`` to be installed by the user depending on the desired backend: - -.. code-block:: bash - - pip install pyscf # for method='pyscf` - pip install openfermionpyscf # for method='openfermion` - -For example, the molecular Hamiltonian for a water molecule can be constructed with the ``pyscf`` -backend as: -""" - -import pennylane as qml -import numpy as np - -symbols = ["H", "O", "H"] -geometry = np.array([[-0.0399, -0.0038, 0.0000], - [ 1.5780, 0.8540, 0.0000], - [ 2.7909, -0.5159, 0.0000]]) -molecule = qml.qchem.Molecule(symbols, geometry) - -H, qubits = qml.qchem.molecular_hamiltonian(molecule, method="pyscf") -print(H) - -############################################################################## -# This generates a PennyLane :class:`~.pennylane.Hamiltonian` that can be used in a VQE workflow or -# converted to a -# `sparse matrix `_ -# in the computational basis. -# -# Additionally, if you have built your electronic Hamiltonian independently using -# `OpenFermion `_ tools, it can -# be readily converted to a PennyLane observable using the -# :func:`~.pennylane.import_operator` function. Here is an example: - -from openfermion.ops import QubitOperator - -H = 0.1 * QubitOperator('X0 X1') + 0.2 * QubitOperator('Z0') -H = qml.qchem.import_operator(H) - -print(f'Type: \n {type(H)} \n') -print(f'Hamiltonian: \n {H}') - -############################################################################## -# Computing molecular integrals -# ----------------------------- -# In order to build a -# `molecular Hamiltonian `_, we need -# one- and two-electron integrals in the molecular orbital basis. These integrals are used to -# construct a fermionic Hamiltonian which is then mapped onto the qubit basis. These molecular -# integrals can be computed with the -# :func:`~.pennylane.qchem.electron_integrals` function of PennyLane. Alternatively, the integrals -# can be computed with the `PySCF `_ package and used in PennyLane -# workflows such as building a -# `fermionic Hamiltonian `_ or -# quantum `resource estimation `_. -# Let's use water as an example. -# -# First, we define the PySCF molecule object and run a restricted Hartree-Fock -# calculation: - -from pyscf import gto, ao2mo, scf - -mol_pyscf = gto.M(atom = '''H -0.02111417 -0.00201087 0.; - O 0.83504162 0.45191733 0.; - H 1.47688065 -0.27300252 0.''') -rhf = scf.RHF(mol_pyscf) -energy = rhf.kernel() - -############################################################################## -# We obtain the molecular integrals ``one_ao`` and ``two_ao`` in the basis of atomic orbitals -# by following the example `here `_: - -one_ao = mol_pyscf.intor_symmetric('int1e_kin') + mol_pyscf.intor_symmetric('int1e_nuc') -two_ao = mol_pyscf.intor('int2e_sph') - -############################################################################## -# These integrals are then mapped to the basis of molecular orbitals: - -one_mo = np.einsum('pi,pq,qj->ij', rhf.mo_coeff, one_ao, rhf.mo_coeff) -two_mo = ao2mo.incore.full(two_ao, rhf.mo_coeff) - -############################################################################## -# Note that the two-electron integral tensor is represented in -# `chemists' notation `_. To use it -# in PennyLane, we need to convert it into the so-called -# *physicists' notation*: - -two_mo = np.swapaxes(two_mo, 1, 3) - -############################################################################## -# Let's now look at an example where these molecular integrals are used to build the fermionic -# Hamiltonian of water. To do that we also need to compute the nuclear energy contribution: - -core_constant = np.array([rhf.energy_nuc()]) - -############################################################################## -# We now use the integrals to construct a fermionic Hamiltonian with PennyLane's powerful tools -# for creating and manipulating -# `fermionic operators `_: - -H_fermionic = qml.qchem.fermionic_observable(core_constant, one_mo, two_mo) - -############################################################################## -# The Hamiltonian can be mapped to the qubit basis with the :func:`~.pennylane.jordan_wigner` -# function: - -H = qml.jordan_wigner(H_fermionic) - -############################################################################## -# Importing initial states -# ------------------------ -# Simulating molecules with quantum algorithms requires defining an initial state that should have -# non-zero overlap with the molecular ground state. A trivial choice for the initial state is the -# Hartree-Fock state which is obtained by putting the electrons in the lowest-energy molecular -# orbitals. For molecules with a complicated electronic structure, the Hartree-Fock state has -# only a small overlap with the ground state, which makes executing quantum algorithms -# inefficient. -# -# Initial states obtained from affordable post-Hartree-Fock calculations can be used to make the -# quantum workflow more performant. For instance, configuration interaction (CI) and coupled cluster -# (CC) calculations with single and double (SD) excitations can be performed using PySCF and the -# resulting wave function can be used as the initial state in the quantum algorithm. PennyLane -# provides the :func:`~.pennylane.qchem.import_state` function that takes a PySCF solver object, -# extracts the wave function and returns a state vector in the computational basis that can be used -# in a quantum circuit. Let’s look at an example. -# -# First, we run CCSD calculations for the hydrogen molecule to obtain the solver object. - -from pyscf import gto, scf, cc - -mol = gto.M(atom=[['H', (0, 0, 0)], ['H', (0, 0, 0.7)]]) -myhf = scf.RHF(mol).run() -mycc = cc.CCSD(myhf).run() - -############################################################################## -# Then, we use the :func:`~.pennylane.qchem.import_state` function to obtain the -# state vector. - -state = qml.qchem.import_state(mycc) -print(state) - -############################################################################## -# You can verify that this state is a superposition of the Hartree-Fock state and a doubly-excited -# state. -# -# Converting fermionic operators -# ------------------------------ -# Fermionic operators are commonly used to construct observables for molecules and spin systems. -# You can easily convert between fermionic operators created with PennyLane and OpenFermion by using -# the :func:`~.pennylane.from_openfermion` and :func:`~.pennylane.to_openfermion` functions. Let's -# look at some examples. First, we create a fermionic operator with OpenFermion and convert it to a -# PennyLane fermionic operator. - -from openfermion import FermionOperator -openfermion_op = 0.5 * FermionOperator('0^ 2') + FermionOperator('0 2^') -pennylane_op = qml.from_openfermion(openfermion_op) -print(pennylane_op) - -############################################################################## -# The resulting operator can be used in PennyLane like any other fermionic object. We now take this -# PennyLane fermionic operator and convert it back to an OpenFermion operator. - -openfermion_op = qml.to_openfermion(pennylane_op) -print(openfermion_op) - -############################################################################## -# The :func:`~.pennylane.from_openfermion` and :func:`~.pennylane.to_openfermion` functions support -# converting several operator types. You can look at the function documentations for more details -# and examples. - -############################################################################## -# Conclusions -# ----------- -# This tutorial demonstrates how to use PennyLane with external quantum chemistry libraries such as -# `PySCF `_ and -# `OpenFermion `_. -# -# To summarize: -# -# 1. We can construct molecular Hamiltonians in PennyLane by using a user-installed version of PySCF -# by passing the argument ``method=pyscf`` to the :func:`~.pennylane.qchem.molecular_hamiltonian` -# function. -# 2. We can directly use one- and two-electron integrals from PySCF, but we need to convert the -# tensor containing the two-electron integrals from chemists' notation to physicists' notation. -# 3. We can easily convert between OpenFermion operators and PennyLane operators using the -# :func:`~.pennylane.from_openfermion` and :func:`~.pennylane.to_openfermion` functions. -# 4. Finally, we can convert PySCF wave functions to PennyLane state vectors using the -# :func:`~.pennylane.qchem.import_state` function. -# -# About the author -# ---------------- -# +r""" + +Using PennyLane with PySCF and OpenFermion +========================================== + +.. meta:: + :property="og:description": Learn how to integrate external quantum chemistry libraries with PennyLane. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_external_libs.png + + +.. related:: + tutorial_quantum_chemistry Quantum chemistry with PennyLane + tutorial_vqe A brief overview of VQE + tutorial_givens_rotations Givens rotations for quantum chemistry + tutorial_adaptive_circuits Adaptive circuits for quantum chemistry + +*Author: Soran Jahangiri — Posted: 3 January 2023.* + +The quantum chemistry module in PennyLane, :mod:`qchem `, provides built-in +methods to compute molecular integrals, solve Hartree-Fock equations, and construct +`fully-differentiable `_ molecular +Hamiltonians. PennyLane also lets you take advantage of various +external resources and libraries to build upon existing tools. In this demo we will show you how +to integrate PennyLane with `PySCF `_ and +`OpenFermion `_ to compute molecular integrals, +construct molecular Hamiltonians, and import initial states. + +Building molecular Hamiltonians +------------------------------- +In PennyLane, Hamiltonians for quantum chemistry are built with the +:func:`~.pennylane.qchem.molecular_hamiltonian` function by specifying a backend for solving the +Hartree–Fock equations. The default backend is the differentiable Hartree–Fock solver of the +:mod:`qchem ` module. A molecular Hamiltonian can also be constructed with +non-differentiable backends that use the electronic structure package +`PySCF `_ or the +`OpenFermion-PySCF `_ plugin. These +backends can be selected by setting the keyword argument ``method='pyscf'`` or +``method='openfermion'`` in :func:`~.pennylane.qchem.molecular_hamiltonian`. This requires +``PySCF`` or ``OpenFermion-PySCF`` to be installed by the user depending on the desired backend: + +.. code-block:: bash + + pip install pyscf # for method='pyscf` + pip install openfermionpyscf # for method='openfermion` + +For example, the molecular Hamiltonian for a water molecule can be constructed with the ``pyscf`` +backend as: +""" + +import pennylane as qml +import numpy as np + +symbols = ["H", "O", "H"] +geometry = np.array([[-0.0399, -0.0038, 0.0000], + [ 1.5780, 0.8540, 0.0000], + [ 2.7909, -0.5159, 0.0000]]) +molecule = qml.qchem.Molecule(symbols, geometry) + +H, qubits = qml.qchem.molecular_hamiltonian(molecule, method="pyscf") +print(H) + +############################################################################## +# This generates a PennyLane :class:`~.pennylane.Hamiltonian` that can be used in a VQE workflow or +# converted to a +# `sparse matrix `_ +# in the computational basis. +# +# Additionally, if you have built your electronic Hamiltonian independently using +# `OpenFermion `_ tools, it can +# be readily converted to a PennyLane observable using the +# :func:`~.pennylane.import_operator` function. Here is an example: + +from openfermion.ops import QubitOperator + +H = 0.1 * QubitOperator('X0 X1') + 0.2 * QubitOperator('Z0') +H = qml.qchem.import_operator(H) + +print(f'Type: \n {type(H)} \n') +print(f'Hamiltonian: \n {H}') + +############################################################################## +# Computing molecular integrals +# ----------------------------- +# In order to build a +# `molecular Hamiltonian `_, we need +# one- and two-electron integrals in the molecular orbital basis. These integrals are used to +# construct a fermionic Hamiltonian which is then mapped onto the qubit basis. These molecular +# integrals can be computed with the +# :func:`~.pennylane.qchem.electron_integrals` function of PennyLane. Alternatively, the integrals +# can be computed with the `PySCF `_ package and used in PennyLane +# workflows such as building a +# `fermionic Hamiltonian `_ or +# quantum `resource estimation `_. +# Let's use water as an example. +# +# First, we define the PySCF molecule object and run a restricted Hartree-Fock +# calculation: + +from pyscf import gto, ao2mo, scf + +mol_pyscf = gto.M(atom = '''H -0.02111417 -0.00201087 0.; + O 0.83504162 0.45191733 0.; + H 1.47688065 -0.27300252 0.''') +rhf = scf.RHF(mol_pyscf) +energy = rhf.kernel() + +############################################################################## +# We obtain the molecular integrals ``one_ao`` and ``two_ao`` in the basis of atomic orbitals +# by following the example `here `_: + +one_ao = mol_pyscf.intor_symmetric('int1e_kin') + mol_pyscf.intor_symmetric('int1e_nuc') +two_ao = mol_pyscf.intor('int2e_sph') + +############################################################################## +# These integrals are then mapped to the basis of molecular orbitals: + +one_mo = np.einsum('pi,pq,qj->ij', rhf.mo_coeff, one_ao, rhf.mo_coeff) +two_mo = ao2mo.incore.full(two_ao, rhf.mo_coeff) + +############################################################################## +# Note that the two-electron integral tensor is represented in +# `chemists' notation `_. To use it +# in PennyLane, we need to convert it into the so-called +# *physicists' notation*: + +two_mo = np.swapaxes(two_mo, 1, 3) + +############################################################################## +# Let's now look at an example where these molecular integrals are used to build the fermionic +# Hamiltonian of water. To do that we also need to compute the nuclear energy contribution: + +core_constant = np.array([rhf.energy_nuc()]) + +############################################################################## +# We now use the integrals to construct a fermionic Hamiltonian with PennyLane's powerful tools +# for creating and manipulating +# `fermionic operators `_: + +H_fermionic = qml.qchem.fermionic_observable(core_constant, one_mo, two_mo) + +############################################################################## +# The Hamiltonian can be mapped to the qubit basis with the :func:`~.pennylane.jordan_wigner` +# function: + +H = qml.jordan_wigner(H_fermionic) + +############################################################################## +# Importing initial states +# ------------------------ +# Simulating molecules with quantum algorithms requires defining an initial state that should have +# non-zero overlap with the molecular ground state. A trivial choice for the initial state is the +# Hartree-Fock state which is obtained by putting the electrons in the lowest-energy molecular +# orbitals. For molecules with a complicated electronic structure, the Hartree-Fock state has +# only a small overlap with the ground state, which makes executing quantum algorithms +# inefficient. +# +# Initial states obtained from affordable post-Hartree-Fock calculations can be used to make the +# quantum workflow more performant. For instance, configuration interaction (CI) and coupled cluster +# (CC) calculations with single and double (SD) excitations can be performed using PySCF and the +# resulting wave function can be used as the initial state in the quantum algorithm. PennyLane +# provides the :func:`~.pennylane.qchem.import_state` function that takes a PySCF solver object, +# extracts the wave function and returns a state vector in the computational basis that can be used +# in a quantum circuit. Let’s look at an example. +# +# First, we run CCSD calculations for the hydrogen molecule to obtain the solver object. + +from pyscf import gto, scf, cc + +mol = gto.M(atom=[['H', (0, 0, 0)], ['H', (0, 0, 0.7)]]) +myhf = scf.RHF(mol).run() +mycc = cc.CCSD(myhf).run() + +############################################################################## +# Then, we use the :func:`~.pennylane.qchem.import_state` function to obtain the +# state vector. + +state = qml.qchem.import_state(mycc) +print(state) + +############################################################################## +# You can verify that this state is a superposition of the Hartree-Fock state and a doubly-excited +# state. +# +# Converting fermionic operators +# ------------------------------ +# Fermionic operators are commonly used to construct observables for molecules and spin systems. +# You can easily convert between fermionic operators created with PennyLane and OpenFermion by using +# the :func:`~.pennylane.from_openfermion` and :func:`~.pennylane.to_openfermion` functions. Let's +# look at some examples. First, we create a fermionic operator with OpenFermion and convert it to a +# PennyLane fermionic operator. + +from openfermion import FermionOperator +openfermion_op = 0.5 * FermionOperator('0^ 2') + FermionOperator('0 2^') +pennylane_op = qml.from_openfermion(openfermion_op) +print(pennylane_op) + +############################################################################## +# The resulting operator can be used in PennyLane like any other fermionic object. We now take this +# PennyLane fermionic operator and convert it back to an OpenFermion operator. + +openfermion_op = qml.to_openfermion(pennylane_op) +print(openfermion_op) + +############################################################################## +# The :func:`~.pennylane.from_openfermion` and :func:`~.pennylane.to_openfermion` functions support +# converting several operator types. You can look at the function documentations for more details +# and examples. + +############################################################################## +# Conclusions +# ----------- +# This tutorial demonstrates how to use PennyLane with external quantum chemistry libraries such as +# `PySCF `_ and +# `OpenFermion `_. +# +# To summarize: +# +# 1. We can construct molecular Hamiltonians in PennyLane by using a user-installed version of PySCF +# by passing the argument ``method=pyscf`` to the :func:`~.pennylane.qchem.molecular_hamiltonian` +# function. +# 2. We can directly use one- and two-electron integrals from PySCF, but we need to convert the +# tensor containing the two-electron integrals from chemists' notation to physicists' notation. +# 3. We can easily convert between OpenFermion operators and PennyLane operators using the +# :func:`~.pennylane.from_openfermion` and :func:`~.pennylane.to_openfermion` functions. +# 4. Finally, we can convert PySCF wave functions to PennyLane state vectors using the +# :func:`~.pennylane.qchem.import_state` function. +# +# About the author +# ---------------- +# diff --git a/demonstrations/tutorial_qft_arithmetics.py b/demonstrations/tutorial_qft_arithmetics.py index 92929830f5..3d67048454 100644 --- a/demonstrations/tutorial_qft_arithmetics.py +++ b/demonstrations/tutorial_qft_arithmetics.py @@ -1,433 +1,433 @@ -r""".. _qft_arithmetics: - -Basic arithmetic with the quantum Fourier transform (QFT) -======================================= - -.. meta:: - :property="og:description": Learn how to use the quantum Fourier transform (QFT) to do basic arithmetic - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qft_arithmetics_thumbnail.png - -.. related:: - tutorial_qubit_rotation Basis tutorial: qubit rotation - - - -*Author: Guillermo Alonso-Linaje — Posted: 07 November 2022.* - -Arithmetic is a fundamental branch of mathematics that consists of the study of the main operations with numbers such as -addition, multiplication, subtraction and division. Using arithmetic operations we can understand the world around us -and solve many of our daily tasks. - -Arithmetic is crucial for the implementation of any kind of algorithm in classical computer science, but also in quantum computing. For this reason, in this demo we are going to show -an approach to defining arithmetic operations on quantum computers. The simplest and most direct way to achieve this goal is to use the -quantum Fourier transform (QFT), which we will demonstrate on a basic level. - -In this demo we will not focus on understanding how the QFT is built, -as we can find a great explanation in the -`PennyLane Codebook `__. Instead, we will develop the -intuition for how it works and how we can best take advantage of it. - -Motivation ----------- - -The first question we have to ask ourselves is whether it makes sense to perform these basic operations on a quantum computer. Is the -goal purely academic or is it really something that is needed in certain algorithms? Why implement in a quantum computer -something that we can do with a calculator? - -When it comes to basic quantum computing algorithms like the Deustch–Jozsa or -Grover's algorithm, we might think that we have never needed to apply any arithmetic operations such as addition or multiplication. -However, the reality is different. When we learn about these algorithms from an academic point of view, -we work with a ready-made operator that we never have to worry about, the *oracle*. -Unfortunately, in real-world situations, we will have to build this seemingly magical operator by hand. -As an example, let's imagine that we want to use Grover's algorithm to search for `magic squares `__. -To define the oracle, which determines whether a solution is valid or not, we must perform sums over the rows and -columns to check that they all have the same value. Therefore, to create this oracle, -we will need to define a sum operator within the quantum computer. - -The second question we face is why we should work with the QFT at all. There are other procedures that could be used to perform these basic operations; for example, by -imitating the classical algorithm. But, as we can see in [#Draper2000]_, it has already been proven that the QFT needs fewer qubits to perform these operations, which is -nowadays of vital importance. - -We will organize the demo as follows. Initially, we will talk about the Fourier basis to give an intuitive idea of how -it works, after which we will address different basic arithmetic operations. Finally, we will move on to a practical example -in which we will factor numbers using Grover's algorithm. - - -QFT representation ------------------ - -To apply the QFT to basic arithmetic operations, our objective now is to learn how to add, -subtract and multiply numbers using quantum devices. As we are working with qubits, -—which, like bits, can take the -values :math:`0` or :math:`1`—we will represent the numbers in binary. For the -purposes of this tutorial, we will assume that we are working only with -integers. Therefore, if we have :math:`n` qubits, we will be able to -represent the numbers from :math:`0` to :math:`2^n-1.` - -The first thing we need to know is PennyLane's -standard for encoding numbers in a binary format. A binary number can be -represented as a string of 1s and 0s, which we can represent as the multi-qubit state - -.. math:: \vert m \rangle = \vert \overline{q_0q_1...q_{n-1}}\rangle, - -where the formula to obtain the equivalent decimal number :math:`m` will be: - -.. math:: m= \sum_{i = 0}^{n-1}2^{n-1-i}q_i. - -Note that :math:`\vert m \rangle` refers to the basic state -generated by the binary encoding of the number :math:`m.` -For instance, the natural number :math:`6` -is represented by the quantum state :math:`\vert 110\rangle,` since :math:`\vert 110 \rangle = 1 \cdot 2^2 + 1\cdot 2^1+0\cdot 2^0 = 6.` - -Let’s see how we would represent all the integers from :math:`0` to :math:`7` using the product state of three qubits, visualized by separate Bloch spheres for each qubit. - -.. figure:: /_static/demonstration_assets/qft_arithmetics/3_qubits_computational_basis.gif - :width: 90% - :align: center - - Representation of integers using a computational basis of three qubits. - -.. note:: - - The `Bloch sphere `_ - is a way of graphically representing the state of a qubit. - At the top of the sphere we place the state :math:`\vert 0 \rangle,` at the bottom - :math:`\vert 1 \rangle,` and in the rest of the - sphere we will place the possible states in superposition. It is a very useful - representation that helps better visualize and interpret quantum gates such as rotations. - -We can use -the :class:`qml.BasisEmbedding ` -template to obtain the binary representation in a simple way. -Let's see how we would code the number :math:`6.` -""" - -import pennylane as qml -import matplotlib.pyplot as plt - -dev = qml.device("default.qubit", wires=3) - -@qml.compile -@qml.qnode(dev) -def basis_embedding_circuit(m): - qml.BasisEmbedding(m, wires=range(3)) - return qml.state() - -m = 6 # number to be encoded - -qml.draw_mpl(basis_embedding_circuit, show_all_wires=True)(m) -plt.show() - -###################################################################### -# -# As we can see, the first qubit—the :math:`0`-th wire—is placed on top and the rest of the qubits are -# below it. However, this is not the only way we could represent numbers. -# We can also represent them in different bases, such as the so-called *Fourier base*. -# -# In this case, all the states of the basis will be represented via qubits in -# the XY-plane of the Bloch sphere, each rotated by a certain -# amount. -# -# -# How do we know how much we must rotate each qubit to represent a certain number? -# It is actually very easy! Suppose we are working with -# :math:`n` qubits and we want to represent the number :math:`m` in the -# Fourier basis. Then the :math:`j`-th qubit will have the phase: -# -# .. math:: \alpha_j = \frac{2m\pi}{2^{j}}. -# -# Now we can represent numbers in the Fourier basis using three qubits: -# -# .. figure:: /_static/demonstration_assets/qft_arithmetics/3_qubits_fourier_basis.gif -# :width: 90% -# :align: center -# -# Representation of integers using the Fourier basis with three qubits -# -# As we can see, the third qubit will rotate -# :math:`\frac{1}{8}` of a turn counterclockwise with each number. The next qubit -# rotates :math:`\frac{1}{4}` of a full turn and, finally, the first qubit rotates -# half a turn for each increase in number. -# -# Adding a number to a register -# ------------------------------ -# -# The fact that the states encoding the numbers are now in phase gives us great -# flexibility in carrying out our arithmetic operations. To see this in practice, -# let’s look at the situation in which want to create an operator Sum -# such that: -# -# .. math:: \text{Sum(k)}\vert m \rangle = \vert m + k \rangle. -# -# The procedure to implement this unitary operation is the following: -# -# #. We convert the state from the computational basis into the Fourier basis by applying the QFT to the :math:`\vert m \rangle` state via the :class:`~pennylane.QFT` operator. -# -# #. We rotate the :math:`j`-th qubit by the angle :math:`\frac{2k\pi}{2^{j}}` using the :math:`R_Z` gate, which leads to the new phases, :math:`\frac{2(m + k)\pi}{2^{j}}.` -# -# #. We apply the QFT inverse to return to the computational basis and obtain :math:`m+k.` -# -# -# Let's see how this process would look in PennyLane. -# - -import pennylane as qml -import numpy as np - -n_wires = 4 -dev = qml.device("default.qubit", wires=n_wires, shots=1) - -def add_k_fourier(k, wires): - for j in range(len(wires)): - qml.RZ(k * np.pi / (2**j), wires=wires[j]) - -@qml.qnode(dev) -def sum(m, k): - qml.BasisEmbedding(m, wires=range(n_wires)) # m encoding - - qml.QFT(wires=range(n_wires)) # step 1 - - add_k_fourier(k, range(n_wires)) # step 2 - - qml.adjoint(qml.QFT)(wires=range(n_wires)) # step 3 - - return qml.sample() - - -print(f"The ket representation of the sum of 3 and 4 is {sum(3,4)}") - -###################################################################### -# Perfect, we have obtained :math:`\vert 0111 \rangle,` which is equivalent to the number :math:`7` in binary! -# -# Note that this is a deterministic algorithm, which means that we have obtained the desired solution by executing a single shot. -# On the other hand, if the result of an operation is greater than the maximum -# value :math:`2^n-1,` we will start again from zero, that is to say, we -# will calculate the sum modulo :math:`2^n-1.` For instance, in our three-qubit example, suppose that -# we want to calculate :math:`6+3.` We see that we do not have -# enough memory space, as :math:`6+3 = 9 > 2^3-1.` The result we will get will -# be :math:`9 \pmod 8 = 1,` or :math:`\vert 001 \rangle` in binary. Make sure to use -# enough qubits to represent your solutions! -# Finally, it is important to point out that it is not necessary to know how the -# QFT is constructed in order to use it. By knowing the properties of the -# new basis, we can use it in a simple way. -# -# Adding two different registers -# ------------------------------ -# -# In this previous algorithm, we had to pass to the operator the value of :math:`k` that we wanted to add to the starting state. -# But at other times, instead of passing that value to the operator, we would like it to pull the information from another register. -# That is, we are looking for a new operator :math:`\text{Sum}_2` such that -# -# .. math:: \text{Sum}_2\vert m \rangle \vert k \rangle \vert 0 \rangle = \vert m \rangle \vert k \rangle \vert m+k \rangle. -# -# In this case, we can understand the third register (which is initially -# at :math:`0`) as a counter that will tally as many units as :math:`m` and -# :math:`k` combined. The binary decomposition will -# make this simple. If we have :math:`\vert m \rangle = \vert \overline{q_0q_1q_2} \rangle,` we will -# have to add :math:`1` to the counter if :math:`q_2 = 1` and nothing -# otherwise. In general, we should add :math:`2^{n-i-1}` units if the :math:`i`-th -# qubit is in state :math:`\vert 1 \rangle` and 0 otherwise. As we can see, this is the same idea that is also -# behind the concept of a controlled gate. Indeed, observe that we will, indeed, apply a corresponding -# phase if indeed the control qubit is in the state :math:`\vert 1\rangle.` -# Let us now code the :math:`\text{Sum}_2` operator. - -wires_m = [0, 1, 2] # qubits needed to encode m -wires_k = [3, 4, 5] # qubits needed to encode k -wires_solution = [6, 7, 8, 9] # qubits needed to encode the solution - -dev = qml.device("default.qubit", wires=wires_m + wires_k + wires_solution, shots=1) - -n_wires = len(dev.wires) # total number of qubits used - -def addition(wires_m, wires_k, wires_solution): - # prepare solution qubits to counting - qml.QFT(wires=wires_solution) - - # add m to the counter - for i in range(len(wires_m)): - qml.ctrl(add_k_fourier, control=wires_m[i])(2 **(len(wires_m) - i - 1), wires_solution) - - # add k to the counter - for i in range(len(wires_k)): - qml.ctrl(add_k_fourier, control=wires_k[i])(2 **(len(wires_k) - i - 1), wires_solution) - - # return to computational basis - qml.adjoint(qml.QFT)(wires=wires_solution) - -@qml.qnode(dev) -def sum2(m, k, wires_m, wires_k, wires_solution): - # m and k codification - qml.BasisEmbedding(m, wires=wires_m) - qml.BasisEmbedding(k, wires=wires_k) - - # apply the addition circuit - addition(wires_m, wires_k, wires_solution) - - return qml.sample(wires=wires_solution) - -print(f"The ket representation of the sum of 7 and 3 is " - f"{sum2(7, 3, wires_m, wires_k, wires_solution)}") - -qml.draw_mpl(sum2, show_all_wires=True)(7, 3, wires_m, wires_k, wires_solution) -plt.show() - -###################################################################### -# Great! We have just seen how to add a number to a counter. In the example above, -# we added :math:`3 + 7` to get :math:`10,` which in binary -# is :math:`\vert 1010 \rangle.` -# -# Multiplying qubits -# ------------------- -# -# Following the same idea, we will see how easily we can -# implement multiplication. For this purpose we'll take two arbitrary numbers :math:`m` and :math:`k` -# to carry out the operation. This time, we look for an operator Mul such that -# -# .. math:: \text{Mul}\vert m \rangle \vert k \rangle \vert 0 \rangle = \vert m \rangle \vert k \rangle \vert m\cdot k \rangle. -# -# To understand the multiplication process, let's work with the binary decomposition of -# :math:`k:=\sum_{i=0}^{n-1}2^{n-i-1}k_i` and -# :math:`m:=\sum_{j=0}^{l-1}2^{l-j-1}m_i.` In this case, the product would -# be: -# -# .. math:: k \cdot m = \sum_{i=0}^{n-1}\sum_{j = 0}^{l-1}m_ik_i (2^{n-i-1} \cdot 2^{l-j-1}). -# -# In other words, if :math:`k_i = 1` and :math:`m_i = 1,` we would add -# :math:`2^{n-i-1} \cdot 2^{l-j-1}` units to the counter, where :math:`n` and :math:`l` -# are the number of qubits with which we encode :math:`m` and :math:`k` respectively. -# Let's code to see how it works! - -wires_m = [0, 1, 2] # qubits needed to encode m -wires_k = [3, 4, 5] # qubits needed to encode k -wires_solution = [6, 7, 8, 9, 10] # qubits needed to encode the solution - -dev = qml.device("default.qubit", wires=wires_m + wires_k + wires_solution, shots=1) - -n_wires = len(dev.wires) - -def multiplication(wires_m, wires_k, wires_solution): - # prepare sol-qubits to counting - qml.QFT(wires=wires_solution) - - # add m to the counter - for i in range(len(wires_k)): - for j in range(len(wires_m)): - coeff = 2 ** (len(wires_m) + len(wires_k) - i - j - 2) - qml.ctrl(add_k_fourier, control=[wires_k[i], wires_m[j]])(coeff, wires_solution) - - # return to computational basis - qml.adjoint(qml.QFT)(wires=wires_solution) - -@qml.qnode(dev) -def mul(m, k): - # m and k codification - qml.BasisEmbedding(m, wires=wires_m) - qml.BasisEmbedding(k, wires=wires_k) - - # Apply multiplication - multiplication(wires_m, wires_k, wires_solution) - - return qml.sample(wires=wires_solution) - - -print(f"The ket representation of the multiplication of 3 and 7 is {mul(3,7)}") - -qml.draw_mpl(mul, show_all_wires=True)(3, 7) -plt.show() - - -###################################################################### -# Awesome! We have multiplied :math:`7 \cdot 3` and, as a result, we have -# :math:`\vert 10101 \rangle,` which is :math:`21` in binary. -# -# -# Factorization with Grover -# ------------------------- -# -# With this, we have already gained a large repertoire of interesting -# operations that we can do, but we can give the idea one more twist and -# apply what we have learned in an example. -# -# Let’s imagine now that we want just the opposite: to factor the -# number :math:`21` as a product of two terms. Is this something we could do -# using our previous reasoning? The answer is yes! We can make use of -# `Grover's algorithm `_ to -# amplify the states whose product is the number we -# are looking for. All we would need is to construct the oracle :math:`U,` i.e., an -# operator such that -# -# .. math:: U\vert m \rangle \vert k \rangle = \vert m \rangle \vert k \rangle \text{ if }m\cdot k \not = 21, -# -# .. math:: U\vert m \rangle \vert k \rangle = -\vert m \rangle \vert k \rangle \text{ if }m\cdot k = 21 -# -# The idea of the oracle is as simple as this: -# -# #. use auxiliary registers to store the product, -# #. check if the product state is :math:`\vert 10101 \rangle` and, in that case, change the sign, -# #. execute the inverse of the circuit to clear the auxiliary qubits. -# #. calculate the probabilities and see which states have been amplified. -# -# Let's go back to PennyLane to implement this idea. - -n = 21 # number we want to factor - -wires_m = [0, 1, 2] # qubits needed to encode m -wires_k = [3, 4, 5] # qubits needed to encode k -wires_solution = [6, 7, 8, 9, 10] # qubits needed to encode the solution - -dev = qml.device("default.qubit", wires=wires_m + wires_k + wires_solution) - -n_wires = len(dev.wires) - -@qml.qnode(dev) -def factorization(n, wires_m, wires_k, wires_solution): - # Superposition of the input - for wire in wires_m: - qml.Hadamard(wires=wire) - - for wire in wires_k: - qml.Hadamard(wires=wire) - - # Apply the multiplication - multiplication(wires_m, wires_k, wires_solution) - - # Change sign of n - qml.FlipSign(n, wires=wires_solution) - - # Uncompute multiplication - qml.adjoint(multiplication)(wires_m, wires_k, wires_solution) - - # Apply Grover operator - qml.GroverOperator(wires=wires_m + wires_k) - - return qml.probs(wires=wires_m) - - -plt.bar(range(2 ** len(wires_m)), factorization(n, wires_m, wires_k, wires_solution)) -plt.xlabel("Basic states") -plt.ylabel("Probability") -plt.show() - -###################################################################### -# By plotting the probabilities of obtaining each basic state we see that -# prime factors have been amplified! Factorization via Grover’s algorithm -# does not achieve exponential improvement that -# `Shor's algorithm `_ does, but we -# can see that this construction is simple and a great example to -# illustrate basic arithmetic! -# -# I hope we can now all see that oracles are not something magical and that there -# is a lot of work behind their construction! This will help us in the future to build -# more complicated operators, but until then, let’s keep on learning. 🚀 -# -# References -# ---------- -# -# .. [#Draper2000] -# -# Thomas G. Draper, "Addition on a Quantum Computer". `arXiv:quant-ph/0008033 `__. -# -# -# About the author -# ---------------- -# +r""".. _qft_arithmetics: + +Basic arithmetic with the quantum Fourier transform (QFT) +======================================= + +.. meta:: + :property="og:description": Learn how to use the quantum Fourier transform (QFT) to do basic arithmetic + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qft_arithmetics_thumbnail.png + +.. related:: + tutorial_qubit_rotation Basis tutorial: qubit rotation + + + +*Author: Guillermo Alonso-Linaje — Posted: 07 November 2022.* + +Arithmetic is a fundamental branch of mathematics that consists of the study of the main operations with numbers such as +addition, multiplication, subtraction and division. Using arithmetic operations we can understand the world around us +and solve many of our daily tasks. + +Arithmetic is crucial for the implementation of any kind of algorithm in classical computer science, but also in quantum computing. For this reason, in this demo we are going to show +an approach to defining arithmetic operations on quantum computers. The simplest and most direct way to achieve this goal is to use the +quantum Fourier transform (QFT), which we will demonstrate on a basic level. + +In this demo we will not focus on understanding how the QFT is built, +as we can find a great explanation in the +`PennyLane Codebook `__. Instead, we will develop the +intuition for how it works and how we can best take advantage of it. + +Motivation +---------- + +The first question we have to ask ourselves is whether it makes sense to perform these basic operations on a quantum computer. Is the +goal purely academic or is it really something that is needed in certain algorithms? Why implement in a quantum computer +something that we can do with a calculator? + +When it comes to basic quantum computing algorithms like the Deustch–Jozsa or +Grover's algorithm, we might think that we have never needed to apply any arithmetic operations such as addition or multiplication. +However, the reality is different. When we learn about these algorithms from an academic point of view, +we work with a ready-made operator that we never have to worry about, the *oracle*. +Unfortunately, in real-world situations, we will have to build this seemingly magical operator by hand. +As an example, let's imagine that we want to use Grover's algorithm to search for `magic squares `__. +To define the oracle, which determines whether a solution is valid or not, we must perform sums over the rows and +columns to check that they all have the same value. Therefore, to create this oracle, +we will need to define a sum operator within the quantum computer. + +The second question we face is why we should work with the QFT at all. There are other procedures that could be used to perform these basic operations; for example, by +imitating the classical algorithm. But, as we can see in [#Draper2000]_, it has already been proven that the QFT needs fewer qubits to perform these operations, which is +nowadays of vital importance. + +We will organize the demo as follows. Initially, we will talk about the Fourier basis to give an intuitive idea of how +it works, after which we will address different basic arithmetic operations. Finally, we will move on to a practical example +in which we will factor numbers using Grover's algorithm. + + +QFT representation +----------------- + +To apply the QFT to basic arithmetic operations, our objective now is to learn how to add, +subtract and multiply numbers using quantum devices. As we are working with qubits, +—which, like bits, can take the +values :math:`0` or :math:`1`—we will represent the numbers in binary. For the +purposes of this tutorial, we will assume that we are working only with +integers. Therefore, if we have :math:`n` qubits, we will be able to +represent the numbers from :math:`0` to :math:`2^n-1.` + +The first thing we need to know is PennyLane's +standard for encoding numbers in a binary format. A binary number can be +represented as a string of 1s and 0s, which we can represent as the multi-qubit state + +.. math:: \vert m \rangle = \vert \overline{q_0q_1...q_{n-1}}\rangle, + +where the formula to obtain the equivalent decimal number :math:`m` will be: + +.. math:: m= \sum_{i = 0}^{n-1}2^{n-1-i}q_i. + +Note that :math:`\vert m \rangle` refers to the basic state +generated by the binary encoding of the number :math:`m.` +For instance, the natural number :math:`6` +is represented by the quantum state :math:`\vert 110\rangle,` since :math:`\vert 110 \rangle = 1 \cdot 2^2 + 1\cdot 2^1+0\cdot 2^0 = 6.` + +Let’s see how we would represent all the integers from :math:`0` to :math:`7` using the product state of three qubits, visualized by separate Bloch spheres for each qubit. + +.. figure:: /_static/demonstration_assets/qft_arithmetics/3_qubits_computational_basis.gif + :width: 90% + :align: center + + Representation of integers using a computational basis of three qubits. + +.. note:: + + The `Bloch sphere `_ + is a way of graphically representing the state of a qubit. + At the top of the sphere we place the state :math:`\vert 0 \rangle,` at the bottom + :math:`\vert 1 \rangle,` and in the rest of the + sphere we will place the possible states in superposition. It is a very useful + representation that helps better visualize and interpret quantum gates such as rotations. + +We can use +the :class:`qml.BasisEmbedding ` +template to obtain the binary representation in a simple way. +Let's see how we would code the number :math:`6.` +""" + +import pennylane as qml +import matplotlib.pyplot as plt + +dev = qml.device("default.qubit", wires=3) + +@qml.compile +@qml.qnode(dev) +def basis_embedding_circuit(m): + qml.BasisEmbedding(m, wires=range(3)) + return qml.state() + +m = 6 # number to be encoded + +qml.draw_mpl(basis_embedding_circuit, show_all_wires=True)(m) +plt.show() + +###################################################################### +# +# As we can see, the first qubit—the :math:`0`-th wire—is placed on top and the rest of the qubits are +# below it. However, this is not the only way we could represent numbers. +# We can also represent them in different bases, such as the so-called *Fourier base*. +# +# In this case, all the states of the basis will be represented via qubits in +# the XY-plane of the Bloch sphere, each rotated by a certain +# amount. +# +# +# How do we know how much we must rotate each qubit to represent a certain number? +# It is actually very easy! Suppose we are working with +# :math:`n` qubits and we want to represent the number :math:`m` in the +# Fourier basis. Then the :math:`j`-th qubit will have the phase: +# +# .. math:: \alpha_j = \frac{2m\pi}{2^{j}}. +# +# Now we can represent numbers in the Fourier basis using three qubits: +# +# .. figure:: /_static/demonstration_assets/qft_arithmetics/3_qubits_fourier_basis.gif +# :width: 90% +# :align: center +# +# Representation of integers using the Fourier basis with three qubits +# +# As we can see, the third qubit will rotate +# :math:`\frac{1}{8}` of a turn counterclockwise with each number. The next qubit +# rotates :math:`\frac{1}{4}` of a full turn and, finally, the first qubit rotates +# half a turn for each increase in number. +# +# Adding a number to a register +# ------------------------------ +# +# The fact that the states encoding the numbers are now in phase gives us great +# flexibility in carrying out our arithmetic operations. To see this in practice, +# let’s look at the situation in which want to create an operator Sum +# such that: +# +# .. math:: \text{Sum(k)}\vert m \rangle = \vert m + k \rangle. +# +# The procedure to implement this unitary operation is the following: +# +# #. We convert the state from the computational basis into the Fourier basis by applying the QFT to the :math:`\vert m \rangle` state via the :class:`~pennylane.QFT` operator. +# +# #. We rotate the :math:`j`-th qubit by the angle :math:`\frac{2k\pi}{2^{j}}` using the :math:`R_Z` gate, which leads to the new phases, :math:`\frac{2(m + k)\pi}{2^{j}}.` +# +# #. We apply the QFT inverse to return to the computational basis and obtain :math:`m+k.` +# +# +# Let's see how this process would look in PennyLane. +# + +import pennylane as qml +import numpy as np + +n_wires = 4 +dev = qml.device("default.qubit", wires=n_wires, shots=1) + +def add_k_fourier(k, wires): + for j in range(len(wires)): + qml.RZ(k * np.pi / (2**j), wires=wires[j]) + +@qml.qnode(dev) +def sum(m, k): + qml.BasisEmbedding(m, wires=range(n_wires)) # m encoding + + qml.QFT(wires=range(n_wires)) # step 1 + + add_k_fourier(k, range(n_wires)) # step 2 + + qml.adjoint(qml.QFT)(wires=range(n_wires)) # step 3 + + return qml.sample() + + +print(f"The ket representation of the sum of 3 and 4 is {sum(3,4)}") + +###################################################################### +# Perfect, we have obtained :math:`\vert 0111 \rangle,` which is equivalent to the number :math:`7` in binary! +# +# Note that this is a deterministic algorithm, which means that we have obtained the desired solution by executing a single shot. +# On the other hand, if the result of an operation is greater than the maximum +# value :math:`2^n-1,` we will start again from zero, that is to say, we +# will calculate the sum modulo :math:`2^n-1.` For instance, in our three-qubit example, suppose that +# we want to calculate :math:`6+3.` We see that we do not have +# enough memory space, as :math:`6+3 = 9 > 2^3-1.` The result we will get will +# be :math:`9 \pmod 8 = 1,` or :math:`\vert 001 \rangle` in binary. Make sure to use +# enough qubits to represent your solutions! +# Finally, it is important to point out that it is not necessary to know how the +# QFT is constructed in order to use it. By knowing the properties of the +# new basis, we can use it in a simple way. +# +# Adding two different registers +# ------------------------------ +# +# In this previous algorithm, we had to pass to the operator the value of :math:`k` that we wanted to add to the starting state. +# But at other times, instead of passing that value to the operator, we would like it to pull the information from another register. +# That is, we are looking for a new operator :math:`\text{Sum}_2` such that +# +# .. math:: \text{Sum}_2\vert m \rangle \vert k \rangle \vert 0 \rangle = \vert m \rangle \vert k \rangle \vert m+k \rangle. +# +# In this case, we can understand the third register (which is initially +# at :math:`0`) as a counter that will tally as many units as :math:`m` and +# :math:`k` combined. The binary decomposition will +# make this simple. If we have :math:`\vert m \rangle = \vert \overline{q_0q_1q_2} \rangle,` we will +# have to add :math:`1` to the counter if :math:`q_2 = 1` and nothing +# otherwise. In general, we should add :math:`2^{n-i-1}` units if the :math:`i`-th +# qubit is in state :math:`\vert 1 \rangle` and 0 otherwise. As we can see, this is the same idea that is also +# behind the concept of a controlled gate. Indeed, observe that we will, indeed, apply a corresponding +# phase if indeed the control qubit is in the state :math:`\vert 1\rangle.` +# Let us now code the :math:`\text{Sum}_2` operator. + +wires_m = [0, 1, 2] # qubits needed to encode m +wires_k = [3, 4, 5] # qubits needed to encode k +wires_solution = [6, 7, 8, 9] # qubits needed to encode the solution + +dev = qml.device("default.qubit", wires=wires_m + wires_k + wires_solution, shots=1) + +n_wires = len(dev.wires) # total number of qubits used + +def addition(wires_m, wires_k, wires_solution): + # prepare solution qubits to counting + qml.QFT(wires=wires_solution) + + # add m to the counter + for i in range(len(wires_m)): + qml.ctrl(add_k_fourier, control=wires_m[i])(2 **(len(wires_m) - i - 1), wires_solution) + + # add k to the counter + for i in range(len(wires_k)): + qml.ctrl(add_k_fourier, control=wires_k[i])(2 **(len(wires_k) - i - 1), wires_solution) + + # return to computational basis + qml.adjoint(qml.QFT)(wires=wires_solution) + +@qml.qnode(dev) +def sum2(m, k, wires_m, wires_k, wires_solution): + # m and k codification + qml.BasisEmbedding(m, wires=wires_m) + qml.BasisEmbedding(k, wires=wires_k) + + # apply the addition circuit + addition(wires_m, wires_k, wires_solution) + + return qml.sample(wires=wires_solution) + +print(f"The ket representation of the sum of 7 and 3 is " + f"{sum2(7, 3, wires_m, wires_k, wires_solution)}") + +qml.draw_mpl(sum2, show_all_wires=True)(7, 3, wires_m, wires_k, wires_solution) +plt.show() + +###################################################################### +# Great! We have just seen how to add a number to a counter. In the example above, +# we added :math:`3 + 7` to get :math:`10,` which in binary +# is :math:`\vert 1010 \rangle.` +# +# Multiplying qubits +# ------------------- +# +# Following the same idea, we will see how easily we can +# implement multiplication. For this purpose we'll take two arbitrary numbers :math:`m` and :math:`k` +# to carry out the operation. This time, we look for an operator Mul such that +# +# .. math:: \text{Mul}\vert m \rangle \vert k \rangle \vert 0 \rangle = \vert m \rangle \vert k \rangle \vert m\cdot k \rangle. +# +# To understand the multiplication process, let's work with the binary decomposition of +# :math:`k:=\sum_{i=0}^{n-1}2^{n-i-1}k_i` and +# :math:`m:=\sum_{j=0}^{l-1}2^{l-j-1}m_i.` In this case, the product would +# be: +# +# .. math:: k \cdot m = \sum_{i=0}^{n-1}\sum_{j = 0}^{l-1}m_ik_i (2^{n-i-1} \cdot 2^{l-j-1}). +# +# In other words, if :math:`k_i = 1` and :math:`m_i = 1,` we would add +# :math:`2^{n-i-1} \cdot 2^{l-j-1}` units to the counter, where :math:`n` and :math:`l` +# are the number of qubits with which we encode :math:`m` and :math:`k` respectively. +# Let's code to see how it works! + +wires_m = [0, 1, 2] # qubits needed to encode m +wires_k = [3, 4, 5] # qubits needed to encode k +wires_solution = [6, 7, 8, 9, 10] # qubits needed to encode the solution + +dev = qml.device("default.qubit", wires=wires_m + wires_k + wires_solution, shots=1) + +n_wires = len(dev.wires) + +def multiplication(wires_m, wires_k, wires_solution): + # prepare sol-qubits to counting + qml.QFT(wires=wires_solution) + + # add m to the counter + for i in range(len(wires_k)): + for j in range(len(wires_m)): + coeff = 2 ** (len(wires_m) + len(wires_k) - i - j - 2) + qml.ctrl(add_k_fourier, control=[wires_k[i], wires_m[j]])(coeff, wires_solution) + + # return to computational basis + qml.adjoint(qml.QFT)(wires=wires_solution) + +@qml.qnode(dev) +def mul(m, k): + # m and k codification + qml.BasisEmbedding(m, wires=wires_m) + qml.BasisEmbedding(k, wires=wires_k) + + # Apply multiplication + multiplication(wires_m, wires_k, wires_solution) + + return qml.sample(wires=wires_solution) + + +print(f"The ket representation of the multiplication of 3 and 7 is {mul(3,7)}") + +qml.draw_mpl(mul, show_all_wires=True)(3, 7) +plt.show() + + +###################################################################### +# Awesome! We have multiplied :math:`7 \cdot 3` and, as a result, we have +# :math:`\vert 10101 \rangle,` which is :math:`21` in binary. +# +# +# Factorization with Grover +# ------------------------- +# +# With this, we have already gained a large repertoire of interesting +# operations that we can do, but we can give the idea one more twist and +# apply what we have learned in an example. +# +# Let’s imagine now that we want just the opposite: to factor the +# number :math:`21` as a product of two terms. Is this something we could do +# using our previous reasoning? The answer is yes! We can make use of +# `Grover's algorithm `_ to +# amplify the states whose product is the number we +# are looking for. All we would need is to construct the oracle :math:`U,` i.e., an +# operator such that +# +# .. math:: U\vert m \rangle \vert k \rangle = \vert m \rangle \vert k \rangle \text{ if }m\cdot k \not = 21, +# +# .. math:: U\vert m \rangle \vert k \rangle = -\vert m \rangle \vert k \rangle \text{ if }m\cdot k = 21 +# +# The idea of the oracle is as simple as this: +# +# #. use auxiliary registers to store the product, +# #. check if the product state is :math:`\vert 10101 \rangle` and, in that case, change the sign, +# #. execute the inverse of the circuit to clear the auxiliary qubits. +# #. calculate the probabilities and see which states have been amplified. +# +# Let's go back to PennyLane to implement this idea. + +n = 21 # number we want to factor + +wires_m = [0, 1, 2] # qubits needed to encode m +wires_k = [3, 4, 5] # qubits needed to encode k +wires_solution = [6, 7, 8, 9, 10] # qubits needed to encode the solution + +dev = qml.device("default.qubit", wires=wires_m + wires_k + wires_solution) + +n_wires = len(dev.wires) + +@qml.qnode(dev) +def factorization(n, wires_m, wires_k, wires_solution): + # Superposition of the input + for wire in wires_m: + qml.Hadamard(wires=wire) + + for wire in wires_k: + qml.Hadamard(wires=wire) + + # Apply the multiplication + multiplication(wires_m, wires_k, wires_solution) + + # Change sign of n + qml.FlipSign(n, wires=wires_solution) + + # Uncompute multiplication + qml.adjoint(multiplication)(wires_m, wires_k, wires_solution) + + # Apply Grover operator + qml.GroverOperator(wires=wires_m + wires_k) + + return qml.probs(wires=wires_m) + + +plt.bar(range(2 ** len(wires_m)), factorization(n, wires_m, wires_k, wires_solution)) +plt.xlabel("Basic states") +plt.ylabel("Probability") +plt.show() + +###################################################################### +# By plotting the probabilities of obtaining each basic state we see that +# prime factors have been amplified! Factorization via Grover’s algorithm +# does not achieve exponential improvement that +# `Shor's algorithm `_ does, but we +# can see that this construction is simple and a great example to +# illustrate basic arithmetic! +# +# I hope we can now all see that oracles are not something magical and that there +# is a lot of work behind their construction! This will help us in the future to build +# more complicated operators, but until then, let’s keep on learning. 🚀 +# +# References +# ---------- +# +# .. [#Draper2000] +# +# Thomas G. Draper, "Addition on a Quantum Computer". `arXiv:quant-ph/0008033 `__. +# +# +# About the author +# ---------------- +# diff --git a/demonstrations/tutorial_quantum_chemistry.py b/demonstrations/tutorial_quantum_chemistry.py index 6925c9fe1b..b015fb33b7 100644 --- a/demonstrations/tutorial_quantum_chemistry.py +++ b/demonstrations/tutorial_quantum_chemistry.py @@ -1,331 +1,331 @@ -r""" -Building molecular Hamiltonians -=============================== - - -.. meta:: - :property="og:description": Learn how to build electronic Hamiltonians of molecules. - - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/water_structure.png - -.. related:: - tutorial_vqe A brief overview of VQE - -*Author: Alain Delgado — Posted: 02 August 2020. Last updated: 29 August 2023.* - -.. note:: - - A wide variety of molecular data, including Hamiltonians, is - available on the `PennyLane Datasets service `__. - -The ultimate goal of computational quantum chemistry is to unravel the -quantum effects that determine the structure and properties of molecules. Reaching -this goal is challenging since the characteristic energies associated with -these effects, e.g., the electronic correlation energy, are typically a tiny fraction -of the total energy of the molecule. - -Accurate molecular properties can be computed from the wave function describing the -interacting electrons in a molecule. The **electronic** wave function -:math:`\Psi(r)` satisfies the `Schrödinger equation -`_ - -.. math:: - H_e \Psi(r) = E \Psi(r), - -where :math:`H_e` and :math:`E` denote the electronic Hamiltonian and the -total energy of the molecule, respectively. When solving the latter equation, -the nuclei of the molecule can be treated as point particles whose coordinates -are fixed [#BornOpp1927]_. In this approximation, both the total energy and -the electronic Hamiltonian depend parametrically on the nuclear coordinates. - - -In this tutorial, you will learn how to use PennyLane to build a -representation of the electronic Hamiltonian :math:`H_e` that can be used to perform -**quantum** simulations of molecules [#yudong2019]_. First, we show how to define -the structure of the molecule in terms of the symbols and the coordinates of -the atoms. Next, we describe how to solve the `Hartree-Fock -equations `_ for the target -molecule. Finally, we discuss some advanced features that can be used to simulate -more complicated systems. - -Let's get started! - -Defining the molecular structure --------------------------------- -In this example we construct the electronic Hamiltonian of the water molecule. - - -.. figure:: ../_static/demonstration_assets/quantum_chemistry/water_structure.png - :width: 30% - :align: center - -The structure of a molecule is defined by the symbols and the nuclear coordinates of -its constituent atoms. It can be specified using different `chemical file formats -`_. Within PennyLane, the molecular -structure is defined by providing a list with the atomic symbols and a one-dimensional -array with the nuclear coordinates in -`atomic units `_. -""" -import numpy as np - -symbols = ["H", "O", "H"] -coordinates = np.array([[-0.0399, -0.0038, 0.0], [1.5780, 0.8540, 0.0], [2.7909, -0.5159, 0.0]]) - -############################################################################## -# The :func:`~.pennylane.qchem.read_structure` function can also be used to read the -# molecular geometry from an external file. - - -from pennylane import qchem - -symbols, coordinates = qchem.read_structure("quantum_chemistry/h2o.xyz") - -############################################################################## -# The xyz format is supported. -# -# Solving the Hartree-Fock equations -# ---------------------------------- -# The molecule's electronic Hamiltonian is commonly represented using the -# `second-quantization `_ formalism, -# which we will explore in more detail in the -# next section. To that aim, a basis of **single-particle** states needs to be chosen. -# In quantum chemistry these states are the -# `molecular orbitals `_ -# which describe the wave function of a single electron in the molecule. -# -# Molecular orbitals are typically represented as a linear combination of **atomic orbitals**. -# The expansion coefficients in the atomic basis are calculated using the -# `Hartree-Fock (HF) method `_. -# In the HF approximation, each electron in the molecule is treated as an **independent** -# particle that moves under the influence of the Coulomb potential due to the nuclei, and a mean -# field generated by all other electrons [#pople1977]_. The optimized coefficients are precisely -# what we need to build the second-quantized Hamiltonian. -# -# PennyLane provides a differentiable Hartree-Fock solver and the functionality to construct a -# fully-differentiable molecular Hamiltonian. -# -# Building the Hamiltonian -# ------------------------ -# In the second quantization formalism, the electronic wave function of the molecule -# is represented in the occupation number basis. For :math:`M` *spin* molecular -# orbitals, the elements of this basis are labelled as -# :math:`\vert n_0, n_1, \dots, n_{M-1} \rangle,` where :math:`n_i = 0,1` -# indicates the occupation of each orbital. In this representation, the electronic -# Hamiltonian is given by -# -# .. math:: -# H = \sum_{p,q} h_{pq} c_p^\dagger c_q + -# \frac{1}{2} \sum_{p,q,r,s} h_{pqrs} c_p^\dagger c_q^\dagger c_r c_s, -# -# where :math:`c^\dagger` and :math:`c` are the electron creation -# and annihilation operators, respectively, and the coefficients -# :math:`h_{pq}` and :math:`h_{pqrs}` denote the one- and two-electron -# Coulomb integrals [#ref_integrals]_ evaluated using the Hartree-Fock -# orbitals. -# -# We can use the states of :math:`M` qubits to encode any element -# of the occupation number basis -# -# .. math:: -# \vert n_0, n_1, \dots, n_{M-1} \rangle \rightarrow \vert q_0 q_1 \cdots q_{M-1} \rangle. -# -# This implies that we need to map the fermionic operators onto operators -# that act on qubits. This can be done by using the -# `Jordan-Wigner `_ -# transformation [#seeley2012]_ which allows us to decompose the fermionic Hamiltonian -# into a linear combination of the tensor product of Pauli operators -# -# .. math:: -# H = \sum_j C_j \otimes_i \sigma_i^{(j)}, -# -# where :math:`C_j` is a scalar coefficient and :math:`\sigma_i` represents an -# element of the Pauli group :math:`\{ I, X, Y, Z \}.` -# -# In PennyLane we have the :func:`~.pennylane.qchem.molecular_hamiltonian` -# function which encapsulates all the steps explained above. It simplifies the process of building -# the electronic Hamiltonian to a single line of code. We just need to input -# the molecule, as shown below: - -molecule = qchem.Molecule(symbols, coordinates) -H, qubits = qchem.molecular_hamiltonian(molecule) -print("Number of qubits: {:}".format(qubits)) -print("Qubit Hamiltonian") -print(H) - -############################################################################## -# Advanced features -# ----------------- -# The :class:`~.pennylane.qchem.Molecule` object allows us to define additional -# keyword arguments to solve the Hartree-Fock equations of more complicated systems. -# The net charge of the molecule may be specified to simulate positively or negatively -# charged molecules. For a neutral system we choose - -charge = 0 - -############################################################################## -# We can also specify the -# `spin multiplicity `_. For the -# water molecule, which contains ten electrons, the `Slater determinant -# `_ resulting from occupying the five -# lowest-energy orbitals with two *paired* electrons per orbital has spin multiplicity one. -# Alternatively, if we define an occupation where the first four orbitals are doubly occupied -# and the next two are singly occupied by *unpaired* electrons, the HF state will have -# multiplicity three. -# -# | -# -# .. figure:: ../_static/demonstration_assets/quantum_chemistry/hf_references.png -# :width: 50% -# :align: center -# -# | -# -# For the neutral water molecule we have, - -multiplicity = 1 - -############################################################################## -# As mentioned above, molecular orbitals are represented as a linear combination -# of atomic orbitals which are typically modeled as `Gaussian-type orbitals -# `_. We can specify different types -# of `Gaussian atomic bases `_. In this example we -# choose a `minimal basis set -# `_. - -basis_set = "sto-3g" - -############################################################################## -# PennyLane also allows us to define an active space [#truhlar2018]_ to perform quantum -# simulations with a reduced number of qubits. This is done by classifying the molecular -# orbitals as core, active, and external orbitals: -# -# * Core orbitals are always occupied by two electrons. -# * Active orbitals can be occupied by zero, one, or two electrons. -# * The external orbitals are never occupied. -# -# Within this approximation, a certain number of **active electrons** are allowed to -# populate a finite set of **active orbitals**. -# -# .. figure:: ../_static/demonstration_assets/quantum_chemistry/sketch_active_space.png -# :width: 40% -# :align: center -# -# .. note:: -# The number of active **spin-orbitals** determines the **number of qubits** required -# to perform the quantum simulations. -# -# For the water molecule in a minimal basis set we have a total of ten electrons -# and seven molecular orbitals. In this example we define a symmetric active space with -# four electrons and four active orbitals using -# the :func:`~.pennylane.qchem.active_space` function: - -electrons = 10 -orbitals = 7 -core, active = qchem.active_space(electrons, orbitals, active_electrons=4, active_orbitals=4) - -############################################################################## -# Viewing the results: - -print("List of core orbitals: {:}".format(core)) -print("List of active orbitals: {:}".format(active)) -print("Number of qubits: {:}".format(2 * len(active))) - -############################################################################## -# Finally, we use the :func:`~.pennylane.qchem.molecular_hamiltonian` function to -# build the resulting Hamiltonian of the water molecule: - -molecule = qchem.Molecule( - symbols, - coordinates, - charge=charge, - mult=multiplicity, - basis_name=basis_set -) - -H, qubits = qchem.molecular_hamiltonian( - molecule, - active_electrons=4, - active_orbitals=4, -) - -print("Number of qubits required to perform quantum simulations: {:}".format(qubits)) -print("Hamiltonian of the water molecule") -print(H) - -############################################################################## -# In this case, since we have truncated the basis of molecular orbitals, the resulting -# observable is an approximation of the Hamiltonian generated in the -# section `Building the Hamiltonian `__. -# -# OpenFermion-PySCF backend -# ------------------------- -# The :func:`~.pennylane.qchem.molecular_hamiltonian` function can also be used to construct the -# molecular Hamiltonian with a non-differentiable backend that uses the -# `OpenFermion-PySCF `_ plugin interfaced with the -# electronic structure package `PySCF `_. This -# backend can be selected by setting ``method='pyscf'`` in -# :func:`~.pennylane.qchem.molecular_hamiltonian`: - -molecule = qchem.Molecule(symbols, coordinates) -H, qubits = qchem.molecular_hamiltonian(molecule, method="pyscf") -print(H) - -############################################################################## -# This backend requires the ``OpenFermion-PySCF`` plugin to be installed by the user with -# -# .. code-block:: bash -# -# pip install openfermionpyscf -# -# Additionally, if you have built your electronic Hamiltonian independently using -# `OpenFermion `_ tools, it can -# be readily converted to a PennyLane observable using the -# :func:`~.pennylane.qchem.import_operator` function. -# -# You have completed the tutorial! Now, select your favorite molecule and build its electronic -# Hamiltonian. -# To see how simple it is to implement the VQE algorithm to compute the ground-state energy of -# your molecule using PennyLane, take a look at the tutorial :doc:`tutorial_vqe`. -# -# References -# ---------- -# -# .. [#yudong2019] -# -# Yudong Cao, Jonathan Romero, *et al.*, "Quantum Chemistry in the Age of Quantum Computing". -# `Chem. Rev. 2019, 119, 19, 10856-10915. -# `_ -# -# .. [#BornOpp1927] -# -# M. Born, J.R. Oppenheimer, "Quantum Theory of the Molecules". -# `Annalen der Physik 84, 457-484 (1927) -# `_ -# -# .. [#pople1977] -# -# Rolf Seeger, John Pople. "Self‐consistent molecular orbital methods. XVIII. Constraints and -# stability in Hartree–Fock theory". `Journal of Chemical Physics 66, -# 3045 (1977). `_ -# -# .. [#ref_integrals] -# -# J.T. Fermann, E.F. Valeev, "Fundamentals of Molecular Integrals Evaluation". -# `arXiv:2007.12057 `_ -# -# .. [#seeley2012] -# -# Jacob T. Seeley, Martin J. Richard, Peter J. Love. "The Bravyi-Kitaev transformation for -# quantum computation of electronic structure". `Journal of Chemical Physics 137, 224109 (2012). -# `_ -# -# .. [#truhlar2018] -# -# J.J. Bao, S.S. Dong, L. Gagliardi, D.G. Truhlar. "Automatic Selection of an -# Active Space for Calculating Electronic Excitation Spectra by MS-CASPT2 or MC-PDFT". -# `Journal of Chemical Theory and Computation 14, 2017 (2018). -# `_ -# -# About the author -# ---------------- -# +r""" +Building molecular Hamiltonians +=============================== + + +.. meta:: + :property="og:description": Learn how to build electronic Hamiltonians of molecules. + + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/water_structure.png + +.. related:: + tutorial_vqe A brief overview of VQE + +*Author: Alain Delgado — Posted: 02 August 2020. Last updated: 29 August 2023.* + +.. note:: + + A wide variety of molecular data, including Hamiltonians, is + available on the `PennyLane Datasets service `__. + +The ultimate goal of computational quantum chemistry is to unravel the +quantum effects that determine the structure and properties of molecules. Reaching +this goal is challenging since the characteristic energies associated with +these effects, e.g., the electronic correlation energy, are typically a tiny fraction +of the total energy of the molecule. + +Accurate molecular properties can be computed from the wave function describing the +interacting electrons in a molecule. The **electronic** wave function +:math:`\Psi(r)` satisfies the `Schrödinger equation +`_ + +.. math:: + H_e \Psi(r) = E \Psi(r), + +where :math:`H_e` and :math:`E` denote the electronic Hamiltonian and the +total energy of the molecule, respectively. When solving the latter equation, +the nuclei of the molecule can be treated as point particles whose coordinates +are fixed [#BornOpp1927]_. In this approximation, both the total energy and +the electronic Hamiltonian depend parametrically on the nuclear coordinates. + + +In this tutorial, you will learn how to use PennyLane to build a +representation of the electronic Hamiltonian :math:`H_e` that can be used to perform +**quantum** simulations of molecules [#yudong2019]_. First, we show how to define +the structure of the molecule in terms of the symbols and the coordinates of +the atoms. Next, we describe how to solve the `Hartree-Fock +equations `_ for the target +molecule. Finally, we discuss some advanced features that can be used to simulate +more complicated systems. + +Let's get started! + +Defining the molecular structure +-------------------------------- +In this example we construct the electronic Hamiltonian of the water molecule. + + +.. figure:: ../_static/demonstration_assets/quantum_chemistry/water_structure.png + :width: 30% + :align: center + +The structure of a molecule is defined by the symbols and the nuclear coordinates of +its constituent atoms. It can be specified using different `chemical file formats +`_. Within PennyLane, the molecular +structure is defined by providing a list with the atomic symbols and a one-dimensional +array with the nuclear coordinates in +`atomic units `_. +""" +import numpy as np + +symbols = ["H", "O", "H"] +coordinates = np.array([[-0.0399, -0.0038, 0.0], [1.5780, 0.8540, 0.0], [2.7909, -0.5159, 0.0]]) + +############################################################################## +# The :func:`~.pennylane.qchem.read_structure` function can also be used to read the +# molecular geometry from an external file. + + +from pennylane import qchem + +symbols, coordinates = qchem.read_structure("quantum_chemistry/h2o.xyz") + +############################################################################## +# The xyz format is supported. +# +# Solving the Hartree-Fock equations +# ---------------------------------- +# The molecule's electronic Hamiltonian is commonly represented using the +# `second-quantization `_ formalism, +# which we will explore in more detail in the +# next section. To that aim, a basis of **single-particle** states needs to be chosen. +# In quantum chemistry these states are the +# `molecular orbitals `_ +# which describe the wave function of a single electron in the molecule. +# +# Molecular orbitals are typically represented as a linear combination of **atomic orbitals**. +# The expansion coefficients in the atomic basis are calculated using the +# `Hartree-Fock (HF) method `_. +# In the HF approximation, each electron in the molecule is treated as an **independent** +# particle that moves under the influence of the Coulomb potential due to the nuclei, and a mean +# field generated by all other electrons [#pople1977]_. The optimized coefficients are precisely +# what we need to build the second-quantized Hamiltonian. +# +# PennyLane provides a differentiable Hartree-Fock solver and the functionality to construct a +# fully-differentiable molecular Hamiltonian. +# +# Building the Hamiltonian +# ------------------------ +# In the second quantization formalism, the electronic wave function of the molecule +# is represented in the occupation number basis. For :math:`M` *spin* molecular +# orbitals, the elements of this basis are labelled as +# :math:`\vert n_0, n_1, \dots, n_{M-1} \rangle,` where :math:`n_i = 0,1` +# indicates the occupation of each orbital. In this representation, the electronic +# Hamiltonian is given by +# +# .. math:: +# H = \sum_{p,q} h_{pq} c_p^\dagger c_q + +# \frac{1}{2} \sum_{p,q,r,s} h_{pqrs} c_p^\dagger c_q^\dagger c_r c_s, +# +# where :math:`c^\dagger` and :math:`c` are the electron creation +# and annihilation operators, respectively, and the coefficients +# :math:`h_{pq}` and :math:`h_{pqrs}` denote the one- and two-electron +# Coulomb integrals [#ref_integrals]_ evaluated using the Hartree-Fock +# orbitals. +# +# We can use the states of :math:`M` qubits to encode any element +# of the occupation number basis +# +# .. math:: +# \vert n_0, n_1, \dots, n_{M-1} \rangle \rightarrow \vert q_0 q_1 \cdots q_{M-1} \rangle. +# +# This implies that we need to map the fermionic operators onto operators +# that act on qubits. This can be done by using the +# `Jordan-Wigner `_ +# transformation [#seeley2012]_ which allows us to decompose the fermionic Hamiltonian +# into a linear combination of the tensor product of Pauli operators +# +# .. math:: +# H = \sum_j C_j \otimes_i \sigma_i^{(j)}, +# +# where :math:`C_j` is a scalar coefficient and :math:`\sigma_i` represents an +# element of the Pauli group :math:`\{ I, X, Y, Z \}.` +# +# In PennyLane we have the :func:`~.pennylane.qchem.molecular_hamiltonian` +# function which encapsulates all the steps explained above. It simplifies the process of building +# the electronic Hamiltonian to a single line of code. We just need to input +# the molecule, as shown below: + +molecule = qchem.Molecule(symbols, coordinates) +H, qubits = qchem.molecular_hamiltonian(molecule) +print("Number of qubits: {:}".format(qubits)) +print("Qubit Hamiltonian") +print(H) + +############################################################################## +# Advanced features +# ----------------- +# The :class:`~.pennylane.qchem.Molecule` object allows us to define additional +# keyword arguments to solve the Hartree-Fock equations of more complicated systems. +# The net charge of the molecule may be specified to simulate positively or negatively +# charged molecules. For a neutral system we choose + +charge = 0 + +############################################################################## +# We can also specify the +# `spin multiplicity `_. For the +# water molecule, which contains ten electrons, the `Slater determinant +# `_ resulting from occupying the five +# lowest-energy orbitals with two *paired* electrons per orbital has spin multiplicity one. +# Alternatively, if we define an occupation where the first four orbitals are doubly occupied +# and the next two are singly occupied by *unpaired* electrons, the HF state will have +# multiplicity three. +# +# | +# +# .. figure:: ../_static/demonstration_assets/quantum_chemistry/hf_references.png +# :width: 50% +# :align: center +# +# | +# +# For the neutral water molecule we have, + +multiplicity = 1 + +############################################################################## +# As mentioned above, molecular orbitals are represented as a linear combination +# of atomic orbitals which are typically modeled as `Gaussian-type orbitals +# `_. We can specify different types +# of `Gaussian atomic bases `_. In this example we +# choose a `minimal basis set +# `_. + +basis_set = "sto-3g" + +############################################################################## +# PennyLane also allows us to define an active space [#truhlar2018]_ to perform quantum +# simulations with a reduced number of qubits. This is done by classifying the molecular +# orbitals as core, active, and external orbitals: +# +# * Core orbitals are always occupied by two electrons. +# * Active orbitals can be occupied by zero, one, or two electrons. +# * The external orbitals are never occupied. +# +# Within this approximation, a certain number of **active electrons** are allowed to +# populate a finite set of **active orbitals**. +# +# .. figure:: ../_static/demonstration_assets/quantum_chemistry/sketch_active_space.png +# :width: 40% +# :align: center +# +# .. note:: +# The number of active **spin-orbitals** determines the **number of qubits** required +# to perform the quantum simulations. +# +# For the water molecule in a minimal basis set we have a total of ten electrons +# and seven molecular orbitals. In this example we define a symmetric active space with +# four electrons and four active orbitals using +# the :func:`~.pennylane.qchem.active_space` function: + +electrons = 10 +orbitals = 7 +core, active = qchem.active_space(electrons, orbitals, active_electrons=4, active_orbitals=4) + +############################################################################## +# Viewing the results: + +print("List of core orbitals: {:}".format(core)) +print("List of active orbitals: {:}".format(active)) +print("Number of qubits: {:}".format(2 * len(active))) + +############################################################################## +# Finally, we use the :func:`~.pennylane.qchem.molecular_hamiltonian` function to +# build the resulting Hamiltonian of the water molecule: + +molecule = qchem.Molecule( + symbols, + coordinates, + charge=charge, + mult=multiplicity, + basis_name=basis_set +) + +H, qubits = qchem.molecular_hamiltonian( + molecule, + active_electrons=4, + active_orbitals=4, +) + +print("Number of qubits required to perform quantum simulations: {:}".format(qubits)) +print("Hamiltonian of the water molecule") +print(H) + +############################################################################## +# In this case, since we have truncated the basis of molecular orbitals, the resulting +# observable is an approximation of the Hamiltonian generated in the +# section `Building the Hamiltonian `__. +# +# OpenFermion-PySCF backend +# ------------------------- +# The :func:`~.pennylane.qchem.molecular_hamiltonian` function can also be used to construct the +# molecular Hamiltonian with a non-differentiable backend that uses the +# `OpenFermion-PySCF `_ plugin interfaced with the +# electronic structure package `PySCF `_. This +# backend can be selected by setting ``method='pyscf'`` in +# :func:`~.pennylane.qchem.molecular_hamiltonian`: + +molecule = qchem.Molecule(symbols, coordinates) +H, qubits = qchem.molecular_hamiltonian(molecule, method="pyscf") +print(H) + +############################################################################## +# This backend requires the ``OpenFermion-PySCF`` plugin to be installed by the user with +# +# .. code-block:: bash +# +# pip install openfermionpyscf +# +# Additionally, if you have built your electronic Hamiltonian independently using +# `OpenFermion `_ tools, it can +# be readily converted to a PennyLane observable using the +# :func:`~.pennylane.qchem.import_operator` function. +# +# You have completed the tutorial! Now, select your favorite molecule and build its electronic +# Hamiltonian. +# To see how simple it is to implement the VQE algorithm to compute the ground-state energy of +# your molecule using PennyLane, take a look at the tutorial :doc:`tutorial_vqe`. +# +# References +# ---------- +# +# .. [#yudong2019] +# +# Yudong Cao, Jonathan Romero, *et al.*, "Quantum Chemistry in the Age of Quantum Computing". +# `Chem. Rev. 2019, 119, 19, 10856-10915. +# `_ +# +# .. [#BornOpp1927] +# +# M. Born, J.R. Oppenheimer, "Quantum Theory of the Molecules". +# `Annalen der Physik 84, 457-484 (1927) +# `_ +# +# .. [#pople1977] +# +# Rolf Seeger, John Pople. "Self‐consistent molecular orbital methods. XVIII. Constraints and +# stability in Hartree–Fock theory". `Journal of Chemical Physics 66, +# 3045 (1977). `_ +# +# .. [#ref_integrals] +# +# J.T. Fermann, E.F. Valeev, "Fundamentals of Molecular Integrals Evaluation". +# `arXiv:2007.12057 `_ +# +# .. [#seeley2012] +# +# Jacob T. Seeley, Martin J. Richard, Peter J. Love. "The Bravyi-Kitaev transformation for +# quantum computation of electronic structure". `Journal of Chemical Physics 137, 224109 (2012). +# `_ +# +# .. [#truhlar2018] +# +# J.J. Bao, S.S. Dong, L. Gagliardi, D.G. Truhlar. "Automatic Selection of an +# Active Space for Calculating Electronic Excitation Spectra by MS-CASPT2 or MC-PDFT". +# `Journal of Chemical Theory and Computation 14, 2017 (2018). +# `_ +# +# About the author +# ---------------- +# diff --git a/demonstrations/tutorial_quantum_dropout.py b/demonstrations/tutorial_quantum_dropout.py index e0ea5e2ba2..cebca15758 100644 --- a/demonstrations/tutorial_quantum_dropout.py +++ b/demonstrations/tutorial_quantum_dropout.py @@ -1,702 +1,702 @@ -r"""Dropout for Quantum Neural Networks -=================================== -""" - -###################################################################### -# Are you struggling with overfitting while training Quantum Neural Networks (QNNs)? -# -# In this demo, we show how to exploit the quantum version of the dropout technique to avoid the problem of -# overfitting in overparametrized QNNs. What follows is based on the paper “A General -# Approach to Dropout in Quantum Neural Networks” by F. Scala, et al. [#dropout]_. -# -# .. figure:: ../_static/demonstration_assets/quantum_dropout/socialthumbnail_large_QuantumDropout_2024-03-07.png -# :align: center -# :width: 60% -# :target: javascript:void(0) -# -# -# What is overfitting and dropout? -# --------------------------------- -# -# Neural Networks (NNs) usually require highly flexible models with lots of trainable parameters in -# order to *learn* a certain underlying function (or data distribution). -# However, being able to learn with low in-sample error is not enough; *generalization* — the ability to provide -# good predictions on previously unseen data — is also desirable. -# -# Highly expressive models may suffer from **overfitting**, which means that -# they are trained too well on the training data, and as a result perform poorly on new, unseen -# data. This happens because the model has learned the noise in the training data, rather than the -# underlying pattern that is generalizable to new data. -# -# **Dropout** is a common technique for classical Deep Neural Networks (DNNs) preventing computational units -# from becoming too specialized and reducing the risk of overfitting [#Hinton2012]_, [#Srivastava2014]_. It consists of randomly removing -# neurons or connections *only during training* to block the flow of information. Once the -# model is trained, the DNN is employed in its original form. -# -# Why dropout for Quantum Neural Networks? -# ---------------------------------------- -# -# Recently, it has been shown that the use of overparametrized QNN models -# changes the optimization landscape by removing lots of local minima [#Kiani2020]_, [#Larocca2023]_. On the one hand, this increased number of -# parameters leads to faster and easier training, but on the other hand, it may drive -# the model to overfit the data. This is also strictly related to the `repeated encoding `__ of classical -# data to achieve nonlinearity in the computation. This is why, inspired from classical DNNs, one -# can think of applying some sort of dropout to QNNs. This would correspond to randomly dropping some -# (groups of) parameterized gates during training to achieve better generalization. -# -# Quantum dropout of rotations in a sine regression -# -------------------------------------------------- -# -# In this demo we will exploit quantum dropout to avoid overfitting during the regression of noisy -# data originally coming from the *sinusoidal* function. In particular, we will randomly “drop” -# rotations during the training phase. In practice, this will correspond to temporarily setting parameters to a value of 0. -# -# Let’s start by importing Pennylane and ``numpy`` and fixing the random seed for reproducibility: -# - -import numpy as np -import pennylane as qml - -seed = 12345 -np.random.seed(seed=seed) - -###################################################################### -# The circuit -# ~~~~~~~~~~~ -# -# Now we define the embedding of classical data and the variational ansatz that will then be combined -# to construct our QNN. Dropout will happen inside the variational ansatz. Obtaining dropout with standard -# Pennylane would be quite straightforward by means of some "if statements", but the training procedure -# will take ages. Here we will leverage JAX in order to speed up the training process with -# Just In Time (JIT) compilation. The drawback is that the definition of the variational ansatz becomes a -# little elaborated, since JAX has its own language for conditional statements. For this purpose we -# define two functions ``true_cond`` and ``false_cond`` to work with ``jax.lax.cond```, which is the JAX -# conditional statement. See this `demo `__ -# for additional insights on how to optimize QNNs with JAX. -# -# Practically speaking, rotation dropout will be performed by passing a list to the ansatz. -# The single qubit rotations are applied depending on the values stored in this list: -# if the value is negative the rotation is dropped (rotation dropout), otherwise it is applied. -# How to produce this list will be explained later in this demo (see the ``make_dropout`` function). - -import jax # require for Just In Time (JIT) compilation -import jax.numpy as jnp - -jax.config.update("jax_platform_name", "cpu") -jax.config.update("jax_enable_x64", True) - - -def embedding(x, wires): - # Encodes the datum multiple times in the register, - # employing also nonlinear functions - assert len(x) == 1 # check feature is 1-D - for i in wires: - qml.RY(jnp.arcsin(x), wires=i) - for i in wires: - qml.RZ(jnp.arccos(x ** 2), wires=i) - - -def true_cond(angle): - # necessary for using an if statement within jitted function - # exploiting jax.lax.cond - # if this function is assessed the rotation is dropped - return 0.0 - - -def false_cond(angle): - # necessary for using an if statement within jitted function - # exploiting jax.lax.cond - # if this function is assessed the rotation is kept - return angle - - -def var_ansatz( - theta, wires, rotations=[qml.RX, qml.RZ, qml.RX], entangler=qml.CNOT, keep_rotation=None -): - - """Single layer of the variational ansatz for our QNN. - We have a single qubit rotation per each qubit (wire) followed by - a linear chain of entangling gates (entangler). This structure is repeated per each rotation in `rotations` - (defining `inner_layers`). - The single qubit rotations are applied depending on the values stored in `keep_rotation`: - if the value is negative the rotation is dropped (rotation dropout), otherwise it is applied. - - Params: - - theta: variational angles that will undergo optimization - - wires: list of qubits (wires) - - rotations: list of rotation kind per each `inner_layer` - - entangler: entangling gate - - keep_rotation: list of lists. There is one list per each `inner_layer`. - In each list there are indexes of the rotations that we want to apply. - Some of these values may be substituted by -1 value - which means that the rotation gate wont be applied (dropout). - """ - - # the length of `rotations` defines the number of inner layers - N = len(wires) - assert len(theta) == 3 * N - wires = list(wires) - - counter = 0 - # keep_rotations contains a list per each inner_layer - for rots in keep_rotation: - # we cicle over the elements of the lists inside keep_rotation - for qb, keep_or_drop in enumerate(rots): - rot = rotations[counter] # each inner layer can have a different rotation - - angle = theta[counter * N + qb] - # conditional statement implementing dropout - # if `keep_or_drop` is negative the rotation is dropped - angle_drop = jax.lax.cond(keep_or_drop < 0, true_cond, false_cond, angle) - rot(angle_drop, wires=wires[qb]) - for qb in wires[:-1]: - entangler(wires=[wires[qb], wires[qb + 1]]) - counter += 1 - - -###################################################################### -# And then we define the hyperparameters of our QNN, namely the number of qubits, -# the number of sublayers in the variational ansatz (``inner_layers``) and the resulting -# number of parameters per layer: -# - -n_qubits = 5 -inner_layers = 3 -params_per_layer = n_qubits * inner_layers - -###################################################################### -# Now we actually build the QNN: -# - - -def create_circuit(n_qubits, layers): - device = qml.device("default.qubit", wires=n_qubits) - - @qml.qnode(device) - def circuit(x, theta, keep_rot): - # print(x) - # print(theta) - - for i in range(layers): - embedding(x, wires=range(n_qubits)) - - keep_rotation = keep_rot[i] - - var_ansatz( - theta[i * params_per_layer : (i + 1) * params_per_layer], - wires=range(n_qubits), - entangler=qml.CNOT, - keep_rotation=keep_rotation, - ) - - return qml.expval(qml.PauliZ(wires=0)) # we measure only the first qubit - - return circuit - - -###################################################################### -# Let’s have a look at a single layer of our QNN: -# -import matplotlib.pyplot as plt - - -plt.style.use("pennylane.drawer.plot") # set pennylane theme, which is nice to see - -# create the circuit with given number of qubits and layers -layers = 1 -circ = create_circuit(n_qubits, layers=layers) - -# for the moment let's keep all the rotations in all sublayers -keep_all_rot = [ - [list(range((n_qubits))) for j in range(1, inner_layers + 1)], -] -# we count the parameters -numbered_params = np.array(range(params_per_layer * layers), dtype=float) -# we encode a single coordinate -single_sample = np.array([0]) - -qml.draw_mpl(circ, decimals=2,)(single_sample, numbered_params, keep_all_rot) - -plt.show() - -###################################################################### -# We now build the model that we will employ for the regression task. -# Since we want to have an overparametrized QNN, we will add 10 layers and we exploit -# ``JAX`` to speed the training up: -# - -layers = 10 -qnn_tmp = create_circuit(n_qubits, layers) -qnn_tmp = jax.jit(qnn_tmp) -qnn_batched = jax.vmap( - qnn_tmp, (0, None, None) -) # we want to vmap on 0-axis of the first circuit param -# in this way we process in parallel all the inputs -# We jit for faster execution -qnn = jax.jit(qnn_batched) - - -###################################################################### -# Dropping rotations -# ~~~~~~~~~~~~~~~~~~ -# -# As anticipated, we need to set some random parameters to 0 at each optimization step. Given a layer -# dropout rate :math:`p_L` (this will be called ``layer_drop_rate``) and the gate dropout rate :math:`p_G` -# (this will be called ``rot_drop_rate``), the probability :math:`p` that a -# gate is dropped in a layer can be calculated with the conditioned probability law: -# -# .. math:: -# -# -# p=p(A\cap B)=p(A|B)p(B)=p_Gp_L -# -# where :math:`B` represents the selection of a specific layer and -# :math:`A` the selection of a specific gate within the chosen layer. -# -# In the following cell we define a function that produces the list of the indices of rotation gates that -# are kept. For gates which are dropped, the value ``-1`` is assigned to the corresponding index. The structure of the list -# is nested; we have one list per ``inner_layer`` inside one list per each layer, all contained in another list. -# This function will be called at each iteration. -# - - -def make_dropout(key): - drop_layers = [] - - for lay in range(layers): - # each layer has prob p_L=layer_drop_rate of being dropped - # according to that for every layer we sample - # if we have to appy dropout in it or not - out = jax.random.choice( - key, jnp.array(range(2)), p=jnp.array([1 - layer_drop_rate, layer_drop_rate]) - ) - key = jax.random.split(key)[0] # update the random key - - if out == 1: # if it has to be dropped - drop_layers.append(lay) - - keep_rot = [] - # we make list of indexes corresponding to the rotations gates - # that are kept in the computation during a single train step - for i in range(layers): - # each list is divded in layers and then in "inner layers" - # this is strictly related to the QNN architecture that we use - keep_rot_layer = [list(range((n_qubits))) for j in range(1, inner_layers + 1)] - - if i in drop_layers: # if dropout has to be applied in this layer - keep_rot_layer = [] # list of indexes for a single layer - inner_keep_r = [] # list of indexes for a single inner layer - for param in range(params_per_layer): - # each rotation within the layer has prob p=rot_drop_rate of being dropped - # according to that for every parameter (rotation) we sample - # if we have to drop it or not - out = jax.random.choice( - key, jnp.array(range(2)), p=jnp.array([1 - rot_drop_rate, rot_drop_rate]) - ) - key = jax.random.split(key)[0] # update the random key - - if out == 0: # if we have to keep it - inner_keep_r.append(param % n_qubits) # % is required because we work - # inner layer by inner layer - else: # if the rotation has to be dropped - inner_keep_r.append(-1) # we assign the value -1 - - if param % n_qubits == n_qubits - 1: # if it's the last qubit of the register - # append the inner layer list - keep_rot_layer.append(inner_keep_r) - # and reset it - inner_keep_r = [] - - keep_rot.append(keep_rot_layer) - - return jnp.array(keep_rot) - - -###################################################################### -# We can check the output of the ``make_dropout`` function: -# - -# setting the drop probability -layer_drop_rate, rot_drop_rate = 0.5, 0.3 # 15% probability of dropping a gate - -# JAX random key -key = jax.random.PRNGKey(12345) -# create the list of indexes, -# -1 implies we are dropping a gate -keep_rot = make_dropout(key) - -# let's just print the list for first layer -print(keep_rot[0]) - -###################################################################### -# Noisy sinusoidal function -# ~~~~~~~~~~~~~~~~~~~~~~~~~ -# -# To test the effectiveness of the dropout technique, we will use a prototypical dataset -# with which it is very easy to overfit: the sinusoidal function. We produce some -# points according to the :math:`\sin` function and then we add some white Gaussian noise -# (noise that follows a normal distribution) :math:`\epsilon.` The noise is essential to obtain overfitting; -# when our model is extremely expressive, it is capable of exactly fit each point and some parameters -# become hyper-specialized in recognizing the noisy features. This makes predictions on new unseen -# data difficult, since the overfitting model did not learn the true underlying data distribution. -# The dropout technique will help in avoiding co-adaptation and hyper-specialization, -# effectively reducing overfitting. -# - -from sklearn.model_selection import train_test_split - - -def make_sin_dataset(dataset_size=100, test_size=0.4, noise_value=0.4, plot=False): - """1D regression problem y=sin(x*\pi)""" - # x-axis - x_ax = np.linspace(-1, 1, dataset_size) - y = [[np.sin(x * np.pi)] for x in x_ax] - np.random.seed(123) - # noise vector - noise = np.array([np.random.normal(0, 0.5, 1) for i in y]) * noise_value - X = np.array(x_ax) - y = np.array(y + noise) # apply noise - - # split the dataset - X_train, X_test, y_train, y_test = train_test_split( - X, y, test_size=test_size, random_state=40, shuffle=True - ) - - X_train = X_train.reshape(-1, 1) - X_test = X_test.reshape(-1, 1) - - y_train = y_train.reshape(-1, 1) - y_test = y_test.reshape(-1, 1) - - return X_train, X_test, y_train, y_test - - -from matplotlib import ticker - -X, X_test, y, y_test = make_sin_dataset(dataset_size=20, test_size=0.25) - - -fig, ax = plt.subplots() -plt.plot(X, y, "o", label="Training") -plt.plot(X_test, y_test, "o", label="Test") - -plt.plot( - np.linspace(-1, 1, 100), - [np.sin(x * np.pi) for x in np.linspace(-1, 1, 100)], - linestyle="dotted", - label=r"$\sin(x)$", -) -plt.ylabel(r"$y = \sin(\pi\cdot x) + \epsilon$") -plt.xlabel(r"$x$") -ax.xaxis.set_major_locator(ticker.MultipleLocator(0.5)) -ax.yaxis.set_major_locator(ticker.MultipleLocator(0.5)) -plt.legend() - -plt.show() - -###################################################################### -# Since our circuit is only able to provide outputs in the range :math:`[-1,1],` we rescale all the -# noisy data within this range. To do this we leverage the `MinMaxScaler` from `sklearn`. -# It is common practice to fit the scaler only from training data and then apply it also to the -# test. The reason behind this is that in general one only has knowledge about the training dataset. -# (If the training dataset is not exhaustively representative of the underlying distribution, -# this preprocessing may lead to some outliers in the test set to be scaled out of the desired range.) -# - -from sklearn.preprocessing import MinMaxScaler - -scaler = MinMaxScaler(feature_range=(-1, 1)) -y = scaler.fit_transform(y) -y_test = scaler.transform(y_test) - -# reshaping for computation -y = y.reshape(-1,) -y_test = y_test.reshape(-1,) - -###################################################################### -# Optimization -# ~~~~~~~~~~~~ -# -# At this point we have to set the hyperparameters of the optimization, namely the number of epochs, the -# learning rate, and the optimizer: -# - -import optax # optimization using jax - -epochs = 700 -optimizer = optax.adam(learning_rate=0.01) - -###################################################################### -# We define the cost function as the Mean Square Error: -# - - -@jax.jit -def calculate_mse_cost(X, y, theta, keep_rot): - yp = qnn(X, theta, keep_rot) - # depending on your version of Pennylane you may require the following line - ##### - yp = jnp.array(yp).T - ##### - cost = jnp.mean((yp - y) ** 2) - - return cost - - -# Optimization update step -@jax.jit -def optimizer_update(opt_state, params, x, y, keep_rot): - loss, grads = jax.value_and_grad(lambda theta: calculate_mse_cost(x, y, theta, keep_rot))( - params - ) - updates, opt_state = optimizer.update(grads, opt_state) - - params = optax.apply_updates(params, updates) - return params, opt_state, loss - - -###################################################################### -# Training the model -# ------------------ -# -# And now we can try to train the model. We execute different runs of the training to understand the -# average behaviour of quantum dropout. To see the effect of dropout we can set different values of -# ``layer_drop_rate`` and ``rot_drop_rate``: -# - -n_run = 3 -drop_rates = [(0.0, 0.0), (0.3, 0.2), (0.7, 0.7)] - -train_history = {} -test_history = {} -opt_params = {} - - -for layer_drop_rate, rot_drop_rate in drop_rates: - # initialization of some lists to store data - costs_per_comb = [] - test_costs_per_comb = [] - opt_params_per_comb = [] - # we execute multiple runs in order to see the average behaviour - for tmp_seed in range(seed, seed + n_run): - key = jax.random.PRNGKey(tmp_seed) - assert len(X.shape) == 2 # X must be a matrix - assert len(y.shape) == 1 # y must be an array - assert X.shape[0] == y.shape[0] # compatibility check - - # parameters initialization with gaussian ditribution - initial_params = jax.random.normal(key, shape=(layers * params_per_layer,)) - # update the random key - key = jax.random.split(key)[0] - - params = jnp.copy(initial_params) - - # optimizer initialization - opt_state = optimizer.init(initial_params) - - # lists for saving single run training and test cost trend - costs = [] - test_costs = [] - - for epoch in range(epochs): - # generate the list for dropout - keep_rot = make_dropout(key) - # update the random key - key = jax.random.split(key)[0] - - # optimization step - params, opt_state, cost = optimizer_update(opt_state, params, X, y, keep_rot) - - ############## performance evaluation ############# - # inference is done with the original model - # with all the gates - keep_rot = jnp.array( - [ - [list(range((n_qubits))) for j in range(1, inner_layers + 1)] - for i in range(layers) - ] - ) - # inference on train set - cost = calculate_mse_cost(X, y, params, keep_rot) - - costs.append(cost) - - # inference on test set - test_cost = calculate_mse_cost(X_test, y_test, params, keep_rot) - test_costs.append(test_cost) - - # we print updates every 5 iterations - if epoch % 5 == 0: - print( - f"{layer_drop_rate:.1f}-{rot_drop_rate:.1f}", - f"run {tmp_seed-seed} - epoch {epoch}/{epochs}", - f"--- Train cost:{cost:.5f}", - f"--- Test cost:{test_cost:.5f}", - end="\r", - ) - - costs_per_comb.append(costs) - test_costs_per_comb.append(test_costs) - opt_params_per_comb.append(params) - print() - costs_per_comb = np.array(costs_per_comb) - test_costs_per_comb = np.array(test_costs_per_comb) - opt_params_per_comb = np.array(opt_params_per_comb) - - train_history[(layer_drop_rate, rot_drop_rate)] = costs_per_comb - test_history[(layer_drop_rate, rot_drop_rate)] = test_costs_per_comb - opt_params[(layer_drop_rate, rot_drop_rate)] = opt_params_per_comb - -###################################################################### -# Performance evaluation -# ---------------------- -# -# Let’s compare the difference in performance with a plot: -# - -fig, axs = plt.subplots(1, 2, figsize=(12, 4)) -plt.subplots_adjust(wspace=0.05) -axs[0].set_title("MSE train") -for k, v in train_history.items(): - train_losses = np.array(v) - mean_train_history = np.mean(train_losses, axis=0) - std_train_history = np.std(train_losses, axis=0,) - - mean_train_history = mean_train_history.reshape((epochs,)) - std_train_history = std_train_history.reshape((epochs,)) - - # shadow standard deviation - axs[0].fill_between( - range(epochs), - mean_train_history - std_train_history, - mean_train_history + std_train_history, - alpha=0.2, - ) - # average trend - axs[0].plot(range(epochs), mean_train_history, label=f"{k}") # Avg Loss - -axs[1].set_title("MSE test") -for k, v in test_history.items(): - test_losses = np.array(v) - mean_test_history = np.mean(test_losses, axis=0) - std_test_history = np.std(test_losses, axis=0,) - - mean_test_history = mean_test_history.reshape((epochs,)) - std_test_history = std_test_history.reshape((epochs,)) - - # shadow standard deviation - axs[1].fill_between( - range(epochs), - mean_test_history - std_test_history, - mean_test_history + std_test_history, - alpha=0.2, - ) - # averange trend - axs[1].plot(range(epochs), mean_test_history, label=f"{k}") # Avg Loss - -axs[0].legend(loc="upper center", bbox_to_anchor=(1.01, 1.25), ncol=4, fancybox=True, shadow=True) - -for ax in axs.flat: - ax.set_xlabel("Epochs") - ax.set_ylabel("MSE") - ax.set_yscale("log") - ax.set_ylim([1e-3, 0.6]) - ax.label_outer() - -plt.subplots_adjust(bottom=0.3) - -plt.show() - -###################################################################### -# On the left you can see that without dropout there is a deep minimization of the training loss, -# moderate values of dropout converge, whereas high drop probabilities impede any learning. On -# the right, we can see the difference in generalization during the optimization process. Standard -# training without dropout initially reaches a low value of generalization error, but as the -# model starts to learn the noise in the training data (overfitting), the generalization error grows -# back. Oppositely, moderate values of dropout enable generalization errors comparable to the respective -# training ones. As the learning is not successful for elevated drop probabilities, the generalization -# error is huge. It is interesting to notice that the “not-learning” error is very close to the final -# error of the QNN trained without dropout. -# -# Hence, one can conclude that low values of dropout greatly improve the generalization performance of -# the model and remove overfitting, even if the randomness of the technique inevitably makes the -# training a little noisy. On the other hand, high drop probabilities only hinder the training -# process. -# -# Validation -# ~~~~~~~~~~ -# -# To validate the technique we can also check how the model predicts in the whole :math:`[-1,1]` range -# with and without quantum dropout. -# - -X, X_test, y, y_test = make_sin_dataset(dataset_size=20, test_size=0.25) - -# spanning the whole range -x_ax = jnp.linspace(-1, 1, 100).reshape(100, 1) - -# selecting which run we want to plot -run = 1 - -fig, ax = plt.subplots() -styles = ["dashed", "-.", "solid", "-."] -for i, k in enumerate(train_history.keys()): - if k[0] == 0.3: - alpha = 1 - else: - alpha = 0.5 - # predicting and rescaling - yp = scaler.inverse_transform(qnn(x_ax, opt_params[k][run], keep_rot).reshape(-1, 1)) - plt.plot([[i] for i in np.linspace(-1, 1, 100)], yp, label=k, alpha=alpha, linestyle=styles[i]) - -plt.scatter(X, y, label="Training", zorder=10) -plt.scatter(X_test, y_test, label="Test", zorder=10) - -ylabel = r"$y = \sin(\pi\cdot x) + \epsilon$" -plt.xlabel("x", fontsize="medium") -plt.ylabel(ylabel, fontsize="medium") -plt.legend() -ax.xaxis.set_major_locator(ticker.MultipleLocator(0.5)) -ax.yaxis.set_major_locator(ticker.MultipleLocator(0.5)) - -plt.show() - -###################################################################### -# The model without dropout overfits the noisy data by trying to exactly predict each of them, -# whereas dropout actually mitigates overfitting and makes the approximation of the underlying sinusoidal -# function way smoother. -# -# Conclusion -# ---------------------- -# In this demo, we explained the basic idea behind quantum dropout and -# how to avoid overfitting by randomly "dropping" some rotation gates -# of a QNN during the training phase. We invite you to check out the paper [#dropout]_ -# for more dropout techniques and additional analysis. Try it yourself and develop new -# dropout strategies. -# -# -# References -# ---------- -# -# .. [#dropout] Scala, F., Ceschini, A., Panella, M., & Gerace, D. (2023). -# *A General Approach to Dropout in Quantum Neural Networks*. -# `Adv. Quantum Technol., 2300220 `__. -# -# .. [#Hinton2012] Hinton, G., Srivastava, N., Krizhevsky, A., Sutskever, I., & Salakhutdinov, R. (2012). -# *Improving neural networks by preventing co-adaptation of feature detectors*. -# `arXiv:1207.0580. `__. -# -# .. [#Srivastava2014] Srivastava, N., Hinton, G., Krizhevsky, A., Sutskever, I., & Salakhutdinov, R. (2014). -# *Dropout: A Simple Way to Prevent Neural Networks from Overfitting*. -# `Journal of Machine Learning Research, 15(56):1929−1958. `__. -# -# .. [#Kiani2020] Kiani,B. T., Lloyd, S., & Maity, R. (2020). -# *Learning Unitaries by Gradient Descent*. -# `arXiv: 2001.11897. `__. -# -# .. [#Larocca2023] Larocca, M., Ju, N., García-Martín, D., Coles, P. J., & Cerezo, M. (2023). -# *Theory of overparametrization in quantum neural networks*. -# `Nat. Comp. Science, 3, 542–551. `__. -# -# About the author -# ---------------- +r"""Dropout for Quantum Neural Networks +=================================== +""" + +###################################################################### +# Are you struggling with overfitting while training Quantum Neural Networks (QNNs)? +# +# In this demo, we show how to exploit the quantum version of the dropout technique to avoid the problem of +# overfitting in overparametrized QNNs. What follows is based on the paper “A General +# Approach to Dropout in Quantum Neural Networks” by F. Scala, et al. [#dropout]_. +# +# .. figure:: ../_static/demonstration_assets/quantum_dropout/socialthumbnail_large_QuantumDropout_2024-03-07.png +# :align: center +# :width: 60% +# :target: javascript:void(0) +# +# +# What is overfitting and dropout? +# --------------------------------- +# +# Neural Networks (NNs) usually require highly flexible models with lots of trainable parameters in +# order to *learn* a certain underlying function (or data distribution). +# However, being able to learn with low in-sample error is not enough; *generalization* — the ability to provide +# good predictions on previously unseen data — is also desirable. +# +# Highly expressive models may suffer from **overfitting**, which means that +# they are trained too well on the training data, and as a result perform poorly on new, unseen +# data. This happens because the model has learned the noise in the training data, rather than the +# underlying pattern that is generalizable to new data. +# +# **Dropout** is a common technique for classical Deep Neural Networks (DNNs) preventing computational units +# from becoming too specialized and reducing the risk of overfitting [#Hinton2012]_, [#Srivastava2014]_. It consists of randomly removing +# neurons or connections *only during training* to block the flow of information. Once the +# model is trained, the DNN is employed in its original form. +# +# Why dropout for Quantum Neural Networks? +# ---------------------------------------- +# +# Recently, it has been shown that the use of overparametrized QNN models +# changes the optimization landscape by removing lots of local minima [#Kiani2020]_, [#Larocca2023]_. On the one hand, this increased number of +# parameters leads to faster and easier training, but on the other hand, it may drive +# the model to overfit the data. This is also strictly related to the `repeated encoding `__ of classical +# data to achieve nonlinearity in the computation. This is why, inspired from classical DNNs, one +# can think of applying some sort of dropout to QNNs. This would correspond to randomly dropping some +# (groups of) parameterized gates during training to achieve better generalization. +# +# Quantum dropout of rotations in a sine regression +# -------------------------------------------------- +# +# In this demo we will exploit quantum dropout to avoid overfitting during the regression of noisy +# data originally coming from the *sinusoidal* function. In particular, we will randomly “drop” +# rotations during the training phase. In practice, this will correspond to temporarily setting parameters to a value of 0. +# +# Let’s start by importing Pennylane and ``numpy`` and fixing the random seed for reproducibility: +# + +import numpy as np +import pennylane as qml + +seed = 12345 +np.random.seed(seed=seed) + +###################################################################### +# The circuit +# ~~~~~~~~~~~ +# +# Now we define the embedding of classical data and the variational ansatz that will then be combined +# to construct our QNN. Dropout will happen inside the variational ansatz. Obtaining dropout with standard +# Pennylane would be quite straightforward by means of some "if statements", but the training procedure +# will take ages. Here we will leverage JAX in order to speed up the training process with +# Just In Time (JIT) compilation. The drawback is that the definition of the variational ansatz becomes a +# little elaborated, since JAX has its own language for conditional statements. For this purpose we +# define two functions ``true_cond`` and ``false_cond`` to work with ``jax.lax.cond```, which is the JAX +# conditional statement. See this `demo `__ +# for additional insights on how to optimize QNNs with JAX. +# +# Practically speaking, rotation dropout will be performed by passing a list to the ansatz. +# The single qubit rotations are applied depending on the values stored in this list: +# if the value is negative the rotation is dropped (rotation dropout), otherwise it is applied. +# How to produce this list will be explained later in this demo (see the ``make_dropout`` function). + +import jax # require for Just In Time (JIT) compilation +import jax.numpy as jnp + +jax.config.update("jax_platform_name", "cpu") +jax.config.update("jax_enable_x64", True) + + +def embedding(x, wires): + # Encodes the datum multiple times in the register, + # employing also nonlinear functions + assert len(x) == 1 # check feature is 1-D + for i in wires: + qml.RY(jnp.arcsin(x), wires=i) + for i in wires: + qml.RZ(jnp.arccos(x ** 2), wires=i) + + +def true_cond(angle): + # necessary for using an if statement within jitted function + # exploiting jax.lax.cond + # if this function is assessed the rotation is dropped + return 0.0 + + +def false_cond(angle): + # necessary for using an if statement within jitted function + # exploiting jax.lax.cond + # if this function is assessed the rotation is kept + return angle + + +def var_ansatz( + theta, wires, rotations=[qml.RX, qml.RZ, qml.RX], entangler=qml.CNOT, keep_rotation=None +): + + """Single layer of the variational ansatz for our QNN. + We have a single qubit rotation per each qubit (wire) followed by + a linear chain of entangling gates (entangler). This structure is repeated per each rotation in `rotations` + (defining `inner_layers`). + The single qubit rotations are applied depending on the values stored in `keep_rotation`: + if the value is negative the rotation is dropped (rotation dropout), otherwise it is applied. + + Params: + - theta: variational angles that will undergo optimization + - wires: list of qubits (wires) + - rotations: list of rotation kind per each `inner_layer` + - entangler: entangling gate + - keep_rotation: list of lists. There is one list per each `inner_layer`. + In each list there are indexes of the rotations that we want to apply. + Some of these values may be substituted by -1 value + which means that the rotation gate wont be applied (dropout). + """ + + # the length of `rotations` defines the number of inner layers + N = len(wires) + assert len(theta) == 3 * N + wires = list(wires) + + counter = 0 + # keep_rotations contains a list per each inner_layer + for rots in keep_rotation: + # we cicle over the elements of the lists inside keep_rotation + for qb, keep_or_drop in enumerate(rots): + rot = rotations[counter] # each inner layer can have a different rotation + + angle = theta[counter * N + qb] + # conditional statement implementing dropout + # if `keep_or_drop` is negative the rotation is dropped + angle_drop = jax.lax.cond(keep_or_drop < 0, true_cond, false_cond, angle) + rot(angle_drop, wires=wires[qb]) + for qb in wires[:-1]: + entangler(wires=[wires[qb], wires[qb + 1]]) + counter += 1 + + +###################################################################### +# And then we define the hyperparameters of our QNN, namely the number of qubits, +# the number of sublayers in the variational ansatz (``inner_layers``) and the resulting +# number of parameters per layer: +# + +n_qubits = 5 +inner_layers = 3 +params_per_layer = n_qubits * inner_layers + +###################################################################### +# Now we actually build the QNN: +# + + +def create_circuit(n_qubits, layers): + device = qml.device("default.qubit", wires=n_qubits) + + @qml.qnode(device) + def circuit(x, theta, keep_rot): + # print(x) + # print(theta) + + for i in range(layers): + embedding(x, wires=range(n_qubits)) + + keep_rotation = keep_rot[i] + + var_ansatz( + theta[i * params_per_layer : (i + 1) * params_per_layer], + wires=range(n_qubits), + entangler=qml.CNOT, + keep_rotation=keep_rotation, + ) + + return qml.expval(qml.PauliZ(wires=0)) # we measure only the first qubit + + return circuit + + +###################################################################### +# Let’s have a look at a single layer of our QNN: +# +import matplotlib.pyplot as plt + + +plt.style.use("pennylane.drawer.plot") # set pennylane theme, which is nice to see + +# create the circuit with given number of qubits and layers +layers = 1 +circ = create_circuit(n_qubits, layers=layers) + +# for the moment let's keep all the rotations in all sublayers +keep_all_rot = [ + [list(range((n_qubits))) for j in range(1, inner_layers + 1)], +] +# we count the parameters +numbered_params = np.array(range(params_per_layer * layers), dtype=float) +# we encode a single coordinate +single_sample = np.array([0]) + +qml.draw_mpl(circ, decimals=2,)(single_sample, numbered_params, keep_all_rot) + +plt.show() + +###################################################################### +# We now build the model that we will employ for the regression task. +# Since we want to have an overparametrized QNN, we will add 10 layers and we exploit +# ``JAX`` to speed the training up: +# + +layers = 10 +qnn_tmp = create_circuit(n_qubits, layers) +qnn_tmp = jax.jit(qnn_tmp) +qnn_batched = jax.vmap( + qnn_tmp, (0, None, None) +) # we want to vmap on 0-axis of the first circuit param +# in this way we process in parallel all the inputs +# We jit for faster execution +qnn = jax.jit(qnn_batched) + + +###################################################################### +# Dropping rotations +# ~~~~~~~~~~~~~~~~~~ +# +# As anticipated, we need to set some random parameters to 0 at each optimization step. Given a layer +# dropout rate :math:`p_L` (this will be called ``layer_drop_rate``) and the gate dropout rate :math:`p_G` +# (this will be called ``rot_drop_rate``), the probability :math:`p` that a +# gate is dropped in a layer can be calculated with the conditioned probability law: +# +# .. math:: +# +# +# p=p(A\cap B)=p(A|B)p(B)=p_Gp_L +# +# where :math:`B` represents the selection of a specific layer and +# :math:`A` the selection of a specific gate within the chosen layer. +# +# In the following cell we define a function that produces the list of the indices of rotation gates that +# are kept. For gates which are dropped, the value ``-1`` is assigned to the corresponding index. The structure of the list +# is nested; we have one list per ``inner_layer`` inside one list per each layer, all contained in another list. +# This function will be called at each iteration. +# + + +def make_dropout(key): + drop_layers = [] + + for lay in range(layers): + # each layer has prob p_L=layer_drop_rate of being dropped + # according to that for every layer we sample + # if we have to appy dropout in it or not + out = jax.random.choice( + key, jnp.array(range(2)), p=jnp.array([1 - layer_drop_rate, layer_drop_rate]) + ) + key = jax.random.split(key)[0] # update the random key + + if out == 1: # if it has to be dropped + drop_layers.append(lay) + + keep_rot = [] + # we make list of indexes corresponding to the rotations gates + # that are kept in the computation during a single train step + for i in range(layers): + # each list is divded in layers and then in "inner layers" + # this is strictly related to the QNN architecture that we use + keep_rot_layer = [list(range((n_qubits))) for j in range(1, inner_layers + 1)] + + if i in drop_layers: # if dropout has to be applied in this layer + keep_rot_layer = [] # list of indexes for a single layer + inner_keep_r = [] # list of indexes for a single inner layer + for param in range(params_per_layer): + # each rotation within the layer has prob p=rot_drop_rate of being dropped + # according to that for every parameter (rotation) we sample + # if we have to drop it or not + out = jax.random.choice( + key, jnp.array(range(2)), p=jnp.array([1 - rot_drop_rate, rot_drop_rate]) + ) + key = jax.random.split(key)[0] # update the random key + + if out == 0: # if we have to keep it + inner_keep_r.append(param % n_qubits) # % is required because we work + # inner layer by inner layer + else: # if the rotation has to be dropped + inner_keep_r.append(-1) # we assign the value -1 + + if param % n_qubits == n_qubits - 1: # if it's the last qubit of the register + # append the inner layer list + keep_rot_layer.append(inner_keep_r) + # and reset it + inner_keep_r = [] + + keep_rot.append(keep_rot_layer) + + return jnp.array(keep_rot) + + +###################################################################### +# We can check the output of the ``make_dropout`` function: +# + +# setting the drop probability +layer_drop_rate, rot_drop_rate = 0.5, 0.3 # 15% probability of dropping a gate + +# JAX random key +key = jax.random.PRNGKey(12345) +# create the list of indexes, +# -1 implies we are dropping a gate +keep_rot = make_dropout(key) + +# let's just print the list for first layer +print(keep_rot[0]) + +###################################################################### +# Noisy sinusoidal function +# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# To test the effectiveness of the dropout technique, we will use a prototypical dataset +# with which it is very easy to overfit: the sinusoidal function. We produce some +# points according to the :math:`\sin` function and then we add some white Gaussian noise +# (noise that follows a normal distribution) :math:`\epsilon.` The noise is essential to obtain overfitting; +# when our model is extremely expressive, it is capable of exactly fit each point and some parameters +# become hyper-specialized in recognizing the noisy features. This makes predictions on new unseen +# data difficult, since the overfitting model did not learn the true underlying data distribution. +# The dropout technique will help in avoiding co-adaptation and hyper-specialization, +# effectively reducing overfitting. +# + +from sklearn.model_selection import train_test_split + + +def make_sin_dataset(dataset_size=100, test_size=0.4, noise_value=0.4, plot=False): + """1D regression problem y=sin(x*\pi)""" + # x-axis + x_ax = np.linspace(-1, 1, dataset_size) + y = [[np.sin(x * np.pi)] for x in x_ax] + np.random.seed(123) + # noise vector + noise = np.array([np.random.normal(0, 0.5, 1) for i in y]) * noise_value + X = np.array(x_ax) + y = np.array(y + noise) # apply noise + + # split the dataset + X_train, X_test, y_train, y_test = train_test_split( + X, y, test_size=test_size, random_state=40, shuffle=True + ) + + X_train = X_train.reshape(-1, 1) + X_test = X_test.reshape(-1, 1) + + y_train = y_train.reshape(-1, 1) + y_test = y_test.reshape(-1, 1) + + return X_train, X_test, y_train, y_test + + +from matplotlib import ticker + +X, X_test, y, y_test = make_sin_dataset(dataset_size=20, test_size=0.25) + + +fig, ax = plt.subplots() +plt.plot(X, y, "o", label="Training") +plt.plot(X_test, y_test, "o", label="Test") + +plt.plot( + np.linspace(-1, 1, 100), + [np.sin(x * np.pi) for x in np.linspace(-1, 1, 100)], + linestyle="dotted", + label=r"$\sin(x)$", +) +plt.ylabel(r"$y = \sin(\pi\cdot x) + \epsilon$") +plt.xlabel(r"$x$") +ax.xaxis.set_major_locator(ticker.MultipleLocator(0.5)) +ax.yaxis.set_major_locator(ticker.MultipleLocator(0.5)) +plt.legend() + +plt.show() + +###################################################################### +# Since our circuit is only able to provide outputs in the range :math:`[-1,1],` we rescale all the +# noisy data within this range. To do this we leverage the `MinMaxScaler` from `sklearn`. +# It is common practice to fit the scaler only from training data and then apply it also to the +# test. The reason behind this is that in general one only has knowledge about the training dataset. +# (If the training dataset is not exhaustively representative of the underlying distribution, +# this preprocessing may lead to some outliers in the test set to be scaled out of the desired range.) +# + +from sklearn.preprocessing import MinMaxScaler + +scaler = MinMaxScaler(feature_range=(-1, 1)) +y = scaler.fit_transform(y) +y_test = scaler.transform(y_test) + +# reshaping for computation +y = y.reshape(-1,) +y_test = y_test.reshape(-1,) + +###################################################################### +# Optimization +# ~~~~~~~~~~~~ +# +# At this point we have to set the hyperparameters of the optimization, namely the number of epochs, the +# learning rate, and the optimizer: +# + +import optax # optimization using jax + +epochs = 700 +optimizer = optax.adam(learning_rate=0.01) + +###################################################################### +# We define the cost function as the Mean Square Error: +# + + +@jax.jit +def calculate_mse_cost(X, y, theta, keep_rot): + yp = qnn(X, theta, keep_rot) + # depending on your version of Pennylane you may require the following line + ##### + yp = jnp.array(yp).T + ##### + cost = jnp.mean((yp - y) ** 2) + + return cost + + +# Optimization update step +@jax.jit +def optimizer_update(opt_state, params, x, y, keep_rot): + loss, grads = jax.value_and_grad(lambda theta: calculate_mse_cost(x, y, theta, keep_rot))( + params + ) + updates, opt_state = optimizer.update(grads, opt_state) + + params = optax.apply_updates(params, updates) + return params, opt_state, loss + + +###################################################################### +# Training the model +# ------------------ +# +# And now we can try to train the model. We execute different runs of the training to understand the +# average behaviour of quantum dropout. To see the effect of dropout we can set different values of +# ``layer_drop_rate`` and ``rot_drop_rate``: +# + +n_run = 3 +drop_rates = [(0.0, 0.0), (0.3, 0.2), (0.7, 0.7)] + +train_history = {} +test_history = {} +opt_params = {} + + +for layer_drop_rate, rot_drop_rate in drop_rates: + # initialization of some lists to store data + costs_per_comb = [] + test_costs_per_comb = [] + opt_params_per_comb = [] + # we execute multiple runs in order to see the average behaviour + for tmp_seed in range(seed, seed + n_run): + key = jax.random.PRNGKey(tmp_seed) + assert len(X.shape) == 2 # X must be a matrix + assert len(y.shape) == 1 # y must be an array + assert X.shape[0] == y.shape[0] # compatibility check + + # parameters initialization with gaussian ditribution + initial_params = jax.random.normal(key, shape=(layers * params_per_layer,)) + # update the random key + key = jax.random.split(key)[0] + + params = jnp.copy(initial_params) + + # optimizer initialization + opt_state = optimizer.init(initial_params) + + # lists for saving single run training and test cost trend + costs = [] + test_costs = [] + + for epoch in range(epochs): + # generate the list for dropout + keep_rot = make_dropout(key) + # update the random key + key = jax.random.split(key)[0] + + # optimization step + params, opt_state, cost = optimizer_update(opt_state, params, X, y, keep_rot) + + ############## performance evaluation ############# + # inference is done with the original model + # with all the gates + keep_rot = jnp.array( + [ + [list(range((n_qubits))) for j in range(1, inner_layers + 1)] + for i in range(layers) + ] + ) + # inference on train set + cost = calculate_mse_cost(X, y, params, keep_rot) + + costs.append(cost) + + # inference on test set + test_cost = calculate_mse_cost(X_test, y_test, params, keep_rot) + test_costs.append(test_cost) + + # we print updates every 5 iterations + if epoch % 5 == 0: + print( + f"{layer_drop_rate:.1f}-{rot_drop_rate:.1f}", + f"run {tmp_seed-seed} - epoch {epoch}/{epochs}", + f"--- Train cost:{cost:.5f}", + f"--- Test cost:{test_cost:.5f}", + end="\r", + ) + + costs_per_comb.append(costs) + test_costs_per_comb.append(test_costs) + opt_params_per_comb.append(params) + print() + costs_per_comb = np.array(costs_per_comb) + test_costs_per_comb = np.array(test_costs_per_comb) + opt_params_per_comb = np.array(opt_params_per_comb) + + train_history[(layer_drop_rate, rot_drop_rate)] = costs_per_comb + test_history[(layer_drop_rate, rot_drop_rate)] = test_costs_per_comb + opt_params[(layer_drop_rate, rot_drop_rate)] = opt_params_per_comb + +###################################################################### +# Performance evaluation +# ---------------------- +# +# Let’s compare the difference in performance with a plot: +# + +fig, axs = plt.subplots(1, 2, figsize=(12, 4)) +plt.subplots_adjust(wspace=0.05) +axs[0].set_title("MSE train") +for k, v in train_history.items(): + train_losses = np.array(v) + mean_train_history = np.mean(train_losses, axis=0) + std_train_history = np.std(train_losses, axis=0,) + + mean_train_history = mean_train_history.reshape((epochs,)) + std_train_history = std_train_history.reshape((epochs,)) + + # shadow standard deviation + axs[0].fill_between( + range(epochs), + mean_train_history - std_train_history, + mean_train_history + std_train_history, + alpha=0.2, + ) + # average trend + axs[0].plot(range(epochs), mean_train_history, label=f"{k}") # Avg Loss + +axs[1].set_title("MSE test") +for k, v in test_history.items(): + test_losses = np.array(v) + mean_test_history = np.mean(test_losses, axis=0) + std_test_history = np.std(test_losses, axis=0,) + + mean_test_history = mean_test_history.reshape((epochs,)) + std_test_history = std_test_history.reshape((epochs,)) + + # shadow standard deviation + axs[1].fill_between( + range(epochs), + mean_test_history - std_test_history, + mean_test_history + std_test_history, + alpha=0.2, + ) + # averange trend + axs[1].plot(range(epochs), mean_test_history, label=f"{k}") # Avg Loss + +axs[0].legend(loc="upper center", bbox_to_anchor=(1.01, 1.25), ncol=4, fancybox=True, shadow=True) + +for ax in axs.flat: + ax.set_xlabel("Epochs") + ax.set_ylabel("MSE") + ax.set_yscale("log") + ax.set_ylim([1e-3, 0.6]) + ax.label_outer() + +plt.subplots_adjust(bottom=0.3) + +plt.show() + +###################################################################### +# On the left you can see that without dropout there is a deep minimization of the training loss, +# moderate values of dropout converge, whereas high drop probabilities impede any learning. On +# the right, we can see the difference in generalization during the optimization process. Standard +# training without dropout initially reaches a low value of generalization error, but as the +# model starts to learn the noise in the training data (overfitting), the generalization error grows +# back. Oppositely, moderate values of dropout enable generalization errors comparable to the respective +# training ones. As the learning is not successful for elevated drop probabilities, the generalization +# error is huge. It is interesting to notice that the “not-learning” error is very close to the final +# error of the QNN trained without dropout. +# +# Hence, one can conclude that low values of dropout greatly improve the generalization performance of +# the model and remove overfitting, even if the randomness of the technique inevitably makes the +# training a little noisy. On the other hand, high drop probabilities only hinder the training +# process. +# +# Validation +# ~~~~~~~~~~ +# +# To validate the technique we can also check how the model predicts in the whole :math:`[-1,1]` range +# with and without quantum dropout. +# + +X, X_test, y, y_test = make_sin_dataset(dataset_size=20, test_size=0.25) + +# spanning the whole range +x_ax = jnp.linspace(-1, 1, 100).reshape(100, 1) + +# selecting which run we want to plot +run = 1 + +fig, ax = plt.subplots() +styles = ["dashed", "-.", "solid", "-."] +for i, k in enumerate(train_history.keys()): + if k[0] == 0.3: + alpha = 1 + else: + alpha = 0.5 + # predicting and rescaling + yp = scaler.inverse_transform(qnn(x_ax, opt_params[k][run], keep_rot).reshape(-1, 1)) + plt.plot([[i] for i in np.linspace(-1, 1, 100)], yp, label=k, alpha=alpha, linestyle=styles[i]) + +plt.scatter(X, y, label="Training", zorder=10) +plt.scatter(X_test, y_test, label="Test", zorder=10) + +ylabel = r"$y = \sin(\pi\cdot x) + \epsilon$" +plt.xlabel("x", fontsize="medium") +plt.ylabel(ylabel, fontsize="medium") +plt.legend() +ax.xaxis.set_major_locator(ticker.MultipleLocator(0.5)) +ax.yaxis.set_major_locator(ticker.MultipleLocator(0.5)) + +plt.show() + +###################################################################### +# The model without dropout overfits the noisy data by trying to exactly predict each of them, +# whereas dropout actually mitigates overfitting and makes the approximation of the underlying sinusoidal +# function way smoother. +# +# Conclusion +# ---------------------- +# In this demo, we explained the basic idea behind quantum dropout and +# how to avoid overfitting by randomly "dropping" some rotation gates +# of a QNN during the training phase. We invite you to check out the paper [#dropout]_ +# for more dropout techniques and additional analysis. Try it yourself and develop new +# dropout strategies. +# +# +# References +# ---------- +# +# .. [#dropout] Scala, F., Ceschini, A., Panella, M., & Gerace, D. (2023). +# *A General Approach to Dropout in Quantum Neural Networks*. +# `Adv. Quantum Technol., 2300220 `__. +# +# .. [#Hinton2012] Hinton, G., Srivastava, N., Krizhevsky, A., Sutskever, I., & Salakhutdinov, R. (2012). +# *Improving neural networks by preventing co-adaptation of feature detectors*. +# `arXiv:1207.0580. `__. +# +# .. [#Srivastava2014] Srivastava, N., Hinton, G., Krizhevsky, A., Sutskever, I., & Salakhutdinov, R. (2014). +# *Dropout: A Simple Way to Prevent Neural Networks from Overfitting*. +# `Journal of Machine Learning Research, 15(56):1929−1958. `__. +# +# .. [#Kiani2020] Kiani,B. T., Lloyd, S., & Maity, R. (2020). +# *Learning Unitaries by Gradient Descent*. +# `arXiv: 2001.11897. `__. +# +# .. [#Larocca2023] Larocca, M., Ju, N., García-Martín, D., Coles, P. J., & Cerezo, M. (2023). +# *Theory of overparametrization in quantum neural networks*. +# `Nat. Comp. Science, 3, 542–551. `__. +# +# About the author +# ---------------- diff --git a/demonstrations/tutorial_quantum_natural_gradient.py b/demonstrations/tutorial_quantum_natural_gradient.py index d65bd9e2d5..f693b3d3aa 100644 --- a/demonstrations/tutorial_quantum_natural_gradient.py +++ b/demonstrations/tutorial_quantum_natural_gradient.py @@ -1,499 +1,499 @@ -r""" - -.. _quantum_natural_gradient: - -Quantum natural gradient -======================== - -.. meta:: - :property="og:description": The quantum natural gradient method can achieve faster convergence for quantum machine - learning problems by taking into account the intrinsic geometry of qubits. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qng_optimization.png - -.. related:: - - tutorial_backprop Quantum gradients with backpropagation - tutorial_vqe_qng Accelerating VQE with quantum natural gradient - -*Author: Josh Izaac — Posted: 11 October 2019. Last updated: 25 January 2021.* - -This example demonstrates the quantum natural gradient optimization technique -for variational quantum circuits, originally proposed in -`Stokes et al. (2019) `__. - -Background ----------- - -The most successful class of quantum algorithms for use on near-term noisy quantum hardware -is the so-called variational quantum algorithm. As laid out in the -`Concepts section `__, in variational quantum algorithms -a low-depth parametrized quantum circuit ansatz is chosen, and a problem-specific -observable measured. A classical optimization loop is then used to find -the set of quantum parameters that *minimize* a particular measurement expectation value -of the quantum device. Examples of such algorithms include the :doc:`variational quantum -eigensolver (VQE) `, the -`quantum approximate optimization algorithm (QAOA) `__, -and :ref:`quantum neural networks (QNN) `. - -Most recent demonstrations -of variational quantum algorithms have used gradient-free classical optimization -methods, such as the Nelder-Mead algorithm. However, the parameter-shift rule -(as implemented in PennyLane) allows the user to automatically compute -analytic gradients of quantum circuits. This opens up the possibility to train -quantum computing hardware using gradient descent—the same method used to train -deep learning models. -Though one caveat has surfaced with gradient descent — how do we choose the optimal -step size for our variational quantum algorithms, to ensure successful and -efficient optimization? - -The natural gradient -^^^^^^^^^^^^^^^^^^^^ - -In standard gradient descent, each optimization step is given by - -.. math:: \theta_{t+1} = \theta_t -\eta \nabla \mathcal{L}(\theta), - -where :math:`\mathcal{L}(\theta)` is the cost as a function of -the parameters :math:`\theta,` and :math:`\eta` is the learning rate -or step size. In essence, each optimization step calculates the -steepest descent direction around the local value of :math:`\theta_t` -in the parameter space, and updates :math:`\theta_t\rightarrow \theta_{t+1}` -by this vector. - -The problem with the above approach is that each optimization step -is strongly connected to a *Euclidean geometry* on the parameter space. -The parametrization is not unique, and different parametrizations can distort -distances within the optimization landscape. - -For example, consider the following cost function :math:`\mathcal{L},` parametrized -using two different coordinate systems, :math:`(\theta_0, \theta_1),` and -:math:`(\phi_0, \phi_1):` - -| - -.. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng7.png - :align: center - :width: 90% - :target: javascript:void(0) - -| - -Performing gradient descent in the :math:`(\theta_0, \theta_1)` parameter -space, we are updating each parameter by the same Euclidean distance, -and not taking into account the fact that the cost function might vary at a different -rate with respect to each parameter. - -Instead, if we perform a change of coordinate system (re-parametrization) -of the cost function, we might find a parameter space where variations in :math:`\mathcal{L}` -are similar across different parameters. This is the case with the new parametrization -:math:`(\phi_0, \phi_1);` the cost function is unchanged, -but we now have a nicer geometry in which to perform gradient descent, and a more -informative stepsize. This leads to faster convergence, and can help avoid optimization -becoming stuck in local minima. For a more in-depth explanation, -including why the parameter space might not be best represented by a Euclidean space, -see `Yamamoto (2019) `__. - -However, what if we avoid gradient descent in the parameter space altogether? -If we instead consider the optimization problem as a -probability distribution of possible output values given an input -(i.e., `maximum likelihood estimation `_), -a better approach is to perform the gradient descent in the *distribution space*, which is -dimensionless and invariant with respect to the parametrization. As a result, -each optimization step will always choose the optimum step-size for every -parameter, regardless of the parametrization. - -In classical neural networks, the above process is known as -*natural gradient descent*, and was first introduced by -`Amari (1998) `__. -The standard gradient descent is modified as follows: - -.. math:: \theta_{t+1} = \theta_t - \eta F^{-1}\nabla \mathcal{L}(\theta), - -where :math:`F` is the `Fisher information matrix `__. -The Fisher information matrix acts as a metric tensor, transforming the -steepest descent in the Euclidean parameter space to the steepest descent in the -distribution space. - -The quantum analog -^^^^^^^^^^^^^^^^^^ - -In a similar vein, it has been shown that the standard Euclidean geometry -is sub-optimal for optimization of quantum variational algorithms -`(Harrow and Napp, 2019) `__. -The space of quantum states instead possesses a unique invariant metric -tensor known as the Fubini-Study metric tensor :math:`g_{ij},` which can be used to -construct a quantum analog to natural gradient descent: - -.. math:: \theta_{t+1} = \theta_t - \eta g^{+}(\theta_t)\nabla \mathcal{L}(\theta), - -where :math:`g^{+}` refers to the pseudo-inverse. - -.. note:: - - It can be shown that the Fubini-Study metric tensor reduces - to the Fisher information matrix in the classical limit. - - Furthermore, in the limit where :math:`\eta\rightarrow 0,` - the dynamics of the system are equivalent to imaginary-time - evolution within the variational subspace, as proposed in - `McArdle et al. (2018) `__. - -""" - -############################################################################## -# Block-diagonal metric tensor -# ---------------------------- -# -# A block-diagonal approximation to the Fubini-Study metric tensor -# of a variational quantum circuit can be evaluated on quantum hardware. -# -# Consider a variational quantum circuit -# -# .. math:: -# -# U(\mathbf{\theta})|\psi_0\rangle = V_L(\theta_L) W_L V_{L-1}(\theta_{L-1}) W_{L-1} -# \cdots V_{\ell}(\theta_{\ell}) W_{\ell} \cdots V_{0}(\theta_{0}) W_{0} |\psi_0\rangle -# -# where -# -# * :math:`|\psi_0\rangle` is the initial state, -# * :math:`W_\ell` are layers of non-parametrized quantum gates, -# * :math:`V_\ell(\theta_\ell)` are layers of parametrized quantum gates -# with :math:`n_\ell` parameters :math:`\theta_\ell = \{\theta^{(\ell)}_0, \dots, \theta^{(\ell)}_n\}.` -# -# Further, assume all parametrized gates can be written in the form -# :math:`X(\theta^{(\ell)}_{i}) = e^{i\theta^{(\ell)}_{i} K^{(\ell)}_i},` -# where :math:`K^{(\ell)}_i` is the *generator* of the parametrized operation. -# -# For each parametric layer :math:`\ell` in the variational quantum circuit -# the :math:`n_\ell\times n_\ell` block-diagonal submatrix -# of the Fubini-Study tensor :math:`g_{ij}^{(\ell)}` is calculated by: -# -# .. math:: -# -# g_{ij}^{(\ell)} = \langle \psi_{\ell-1} | K_i K_j | \psi_{\ell-1} \rangle -# - \langle \psi_{\ell-1} | K_i | \psi_{\ell-1}\rangle -# \langle \psi_{\ell-1} |K_j | \psi_{\ell-1}\rangle -# -# where -# -# .. math:: -# -# | \psi_{\ell-1}\rangle = V_{\ell-1}(\theta_{\ell-1}) W_{\ell-1} \cdots V_{0}(\theta_{0}) W_{0} |\psi_0\rangle. -# -# (that is, :math:`|\psi_{\ell-1}\rangle` is the quantum state prior to the application -# of parameterized layer :math:`\ell`), and we have :math:`K_i \equiv K_i^{(\ell)}` for brevity. -# -# Let's consider a small variational quantum circuit example coded in PennyLane: - -import numpy as np -import pennylane as qml -from pennylane import numpy as pnp - -dev = qml.device("lightning.qubit", wires=3) - - -@qml.qnode(dev, interface="autograd", diff_method="parameter-shift") -def circuit(params): - # |psi_0>: state preparation - qml.RY(np.pi / 4, wires=0) - qml.RY(np.pi / 3, wires=1) - qml.RY(np.pi / 7, wires=2) - - # V0(theta0, theta1): Parametrized layer 0 - qml.RZ(params[0], wires=0) - qml.RZ(params[1], wires=1) - - # W1: non-parametrized gates - qml.CNOT(wires=[0, 1]) - qml.CNOT(wires=[1, 2]) - - # V_1(theta2, theta3): Parametrized layer 1 - qml.RY(params[2], wires=1) - qml.RX(params[3], wires=2) - - # W2: non-parametrized gates - qml.CNOT(wires=[0, 1]) - qml.CNOT(wires=[1, 2]) - - return qml.expval(qml.PauliY(0)) - -# Use pennylane.numpy for trainable parameters -params = pnp.array([0.432, -0.123, 0.543, 0.233]) - -############################################################################## -# The above circuit consists of 4 parameters, with two distinct parametrized -# layers of 2 parameters each. -# -# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng1.png -# :align: center -# :width: 90% -# :target: javascript:void(0) -# -# | -# -# (Note that in this example, the first non-parametrized layer :math:`W_0` -# is simply the identity.) Since there are two layers, each with two parameters, -# the block-diagonal approximation consists of two -# :math:`2\times 2` matrices, :math:`g^{(0)}` and :math:`g^{(1)}.` -# -# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng2.png -# :align: center -# :width: 30% -# :target: javascript:void(0) -# -# To compute the first block-diagonal :math:`g^{(0)},` we create subcircuits consisting -# of all gates prior to the layer, and observables corresponding to -# the *generators* of the gates in the layer: -# -# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng3.png -# :align: center -# :width: 30% -# :target: javascript:void(0) - -g0 = np.zeros([2, 2]) - - -def layer0_subcircuit(params): - """This function contains all gates that - precede parametrized layer 0""" - qml.RY(np.pi / 4, wires=0) - qml.RY(np.pi / 3, wires=1) - qml.RY(np.pi / 7, wires=2) - - -############################################################################## -# We then post-process the measurement results in order to determine :math:`g^{(0)},` -# as follows. -# -# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng4.png -# :align: center -# :width: 50% -# :target: javascript:void(0) -# -# We can see that the diagonal terms are simply given by the variance: - - -@qml.qnode(dev, interface="autograd") -def layer0_diag(params): - layer0_subcircuit(params) - return qml.var(qml.PauliZ(0)), qml.var(qml.PauliZ(1)) - - -# calculate the diagonal terms -varK0, varK1 = layer0_diag(params) -g0[0, 0] = varK0 / 4 -g0[1, 1] = varK1 / 4 - -############################################################################## -# The following two subcircuits are then used to calculate the -# off-diagonal covariance terms of :math:`g^{(0)}:` - - -@qml.qnode(dev, interface="autograd") -def layer0_off_diag_single(params): - layer0_subcircuit(params) - return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)) - - -@qml.qnode(dev, interface="autograd") -def layer0_off_diag_double(params): - layer0_subcircuit(params) - ZZ = np.kron(np.diag([1, -1]), np.diag([1, -1])) - return qml.expval(qml.Hermitian(ZZ, wires=[0, 1])) - - -# calculate the off-diagonal terms -exK0, exK1 = layer0_off_diag_single(params) -exK0K1 = layer0_off_diag_double(params) - -g0[0, 1] = (exK0K1 - exK0 * exK1) / 4 -g0[1, 0] = (exK0K1 - exK0 * exK1) / 4 - -############################################################################## -# Note that, by definition, the block-diagonal matrices must be real and -# symmetric. -# -# We can repeat the above process to compute :math:`g^{(1)}.` The subcircuit -# required is given by -# -# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng8.png -# :align: center -# :width: 50% -# :target: javascript:void(0) - -g1 = np.zeros([2, 2]) - - -def layer1_subcircuit(params): - """This function contains all gates that - precede parametrized layer 1""" - # |psi_0>: state preparation - qml.RY(np.pi / 4, wires=0) - qml.RY(np.pi / 3, wires=1) - qml.RY(np.pi / 7, wires=2) - - # V0(theta0, theta1): Parametrized layer 0 - qml.RZ(params[0], wires=0) - qml.RZ(params[1], wires=1) - - # W1: non-parametrized gates - qml.CNOT(wires=[0, 1]) - qml.CNOT(wires=[1, 2]) - - -############################################################################## -# Using this subcircuit, we can now generate the submatrix :math:`g^{(1)}.` -# -# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng5.png -# :align: center -# :width: 50% -# :target: javascript:void(0) - - -@qml.qnode(dev, interface="autograd") -def layer1_diag(params): - layer1_subcircuit(params) - return qml.var(qml.PauliY(1)), qml.var(qml.PauliX(2)) - - -############################################################################## -# As previously, the diagonal terms are simply given by the variance, - -varK0, varK1 = layer1_diag(params) -g1[0, 0] = varK0 / 4 -g1[1, 1] = varK1 / 4 - - -############################################################################## -# while the off-diagonal terms require covariance between the two -# observables to be computed. - - -@qml.qnode(dev, interface="autograd") -def layer1_off_diag_single(params): - layer1_subcircuit(params) - return qml.expval(qml.PauliY(1)), qml.expval(qml.PauliX(2)) - - -@qml.qnode(dev, interface="autograd") -def layer1_off_diag_double(params): - layer1_subcircuit(params) - X = np.array([[0, 1], [1, 0]]) - Y = np.array([[0, -1j], [1j, 0]]) - YX = np.kron(Y, X) - return qml.expval(qml.Hermitian(YX, wires=[1, 2])) - - -# calculate the off-diagonal terms -exK0, exK1 = layer1_off_diag_single(params) -exK0K1 = layer1_off_diag_double(params) - -g1[0, 1] = (exK0K1 - exK0 * exK1) / 4 -g1[1, 0] = g1[0, 1] - - -############################################################################## -# Putting this altogether, the block-diagonal approximation to the Fubini-Study -# metric tensor for this variational quantum circuit is -from scipy.linalg import block_diag - -g = block_diag(g0, g1) -print(np.round(g, 8)) - - -############################################################################## -# PennyLane contains a built-in function for computing the Fubini-Study metric -# tensor, :func:`~.pennylane.metric_tensor`, which -# we can use to verify this result: -print(np.round(qml.metric_tensor(circuit, approx="block-diag")(params), 8)) - -############################################################################## -# As opposed to our manual computation, which required 6 different quantum -# evaluations, the PennyLane Fubini-Study metric tensor implementation -# requires only 2 quantum evaluations, one per layer. This is done by -# automatically detecting the layer structure, and noting that every -# observable that must be measured commutes, allowing for simultaneous measurement. -# -# Therefore, by combining the quantum natural gradient optimizer with the analytic -# parameter-shift rule to optimize a variational circuit with :math:`d` parameters -# and :math:`L` parametrized layers, a total of :math:`2d+L` quantum evaluations -# are required per optimization step. -# -# Note that the :func:`~.pennylane.metric_tensor` function also supports computing the diagonal -# approximation to the metric tensor: -print(qml.metric_tensor(circuit, approx='diag')(params)) - -############################################################################## -# Furthermore, the returned metric tensor is **full differentiable**; include it -# in your cost function, and train or optimize its value! - -############################################################################## -# Quantum natural gradient optimization -# ------------------------------------- -# -# PennyLane provides an implementation of the quantum natural gradient -# optimizer, :class:`~.pennylane.QNGOptimizer`. Let's compare the optimization convergence -# of the QNG Optimizer and the :class:`~.pennylane.GradientDescentOptimizer` for the simple variational -# circuit above. - -steps = 200 -init_params = pnp.array([0.432, -0.123, 0.543, 0.233], requires_grad=True) - -############################################################################## -# Performing vanilla gradient descent: - -gd_cost = [] -opt = qml.GradientDescentOptimizer(0.01) - -theta = init_params -for _ in range(steps): - theta = opt.step(circuit, theta) - gd_cost.append(circuit(theta)) - -############################################################################## -# Performing quantum natural gradient descent: - -qng_cost = [] -opt = qml.QNGOptimizer(0.01) - -theta = init_params -for _ in range(steps): - theta = opt.step(circuit, theta) - qng_cost.append(circuit(theta)) - - -############################################################################## -# Plotting the cost vs optimization step for both optimization strategies: -from matplotlib import pyplot as plt - -plt.style.use("seaborn") -plt.plot(gd_cost, "b", label="Vanilla gradient descent") -plt.plot(qng_cost, "g", label="Quantum natural gradient descent") - -plt.ylabel("Cost function value") -plt.xlabel("Optimization steps") -plt.legend() -plt.show() - -############################################################################## -# References -# ---------- -# -# 1. Shun-Ichi Amari. "Natural gradient works efficiently in learning." -# `Neural computation 10.2, 251-276 `__, 1998. -# -# 2. James Stokes, Josh Izaac, Nathan Killoran, Giuseppe Carleo. -# "Quantum Natural Gradient." `arXiv:1909.02108 `__, 2019. -# -# 3. Aram Harrow and John Napp. "Low-depth gradient measurements can improve -# convergence in variational hybrid quantum-classical algorithms." -# `arXiv:1901.05374 `__, 2019. -# -# 4. Naoki Yamamoto. "On the natural gradient for variational quantum eigensolver." -# `arXiv:1909.05074 `__, 2019. -# -# -# About the author -# ---------------- +r""" + +.. _quantum_natural_gradient: + +Quantum natural gradient +======================== + +.. meta:: + :property="og:description": The quantum natural gradient method can achieve faster convergence for quantum machine + learning problems by taking into account the intrinsic geometry of qubits. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qng_optimization.png + +.. related:: + + tutorial_backprop Quantum gradients with backpropagation + tutorial_vqe_qng Accelerating VQE with quantum natural gradient + +*Author: Josh Izaac — Posted: 11 October 2019. Last updated: 25 January 2021.* + +This example demonstrates the quantum natural gradient optimization technique +for variational quantum circuits, originally proposed in +`Stokes et al. (2019) `__. + +Background +---------- + +The most successful class of quantum algorithms for use on near-term noisy quantum hardware +is the so-called variational quantum algorithm. As laid out in the +`Concepts section `__, in variational quantum algorithms +a low-depth parametrized quantum circuit ansatz is chosen, and a problem-specific +observable measured. A classical optimization loop is then used to find +the set of quantum parameters that *minimize* a particular measurement expectation value +of the quantum device. Examples of such algorithms include the :doc:`variational quantum +eigensolver (VQE) `, the +`quantum approximate optimization algorithm (QAOA) `__, +and :ref:`quantum neural networks (QNN) `. + +Most recent demonstrations +of variational quantum algorithms have used gradient-free classical optimization +methods, such as the Nelder-Mead algorithm. However, the parameter-shift rule +(as implemented in PennyLane) allows the user to automatically compute +analytic gradients of quantum circuits. This opens up the possibility to train +quantum computing hardware using gradient descent—the same method used to train +deep learning models. +Though one caveat has surfaced with gradient descent — how do we choose the optimal +step size for our variational quantum algorithms, to ensure successful and +efficient optimization? + +The natural gradient +^^^^^^^^^^^^^^^^^^^^ + +In standard gradient descent, each optimization step is given by + +.. math:: \theta_{t+1} = \theta_t -\eta \nabla \mathcal{L}(\theta), + +where :math:`\mathcal{L}(\theta)` is the cost as a function of +the parameters :math:`\theta,` and :math:`\eta` is the learning rate +or step size. In essence, each optimization step calculates the +steepest descent direction around the local value of :math:`\theta_t` +in the parameter space, and updates :math:`\theta_t\rightarrow \theta_{t+1}` +by this vector. + +The problem with the above approach is that each optimization step +is strongly connected to a *Euclidean geometry* on the parameter space. +The parametrization is not unique, and different parametrizations can distort +distances within the optimization landscape. + +For example, consider the following cost function :math:`\mathcal{L},` parametrized +using two different coordinate systems, :math:`(\theta_0, \theta_1),` and +:math:`(\phi_0, \phi_1):` + +| + +.. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng7.png + :align: center + :width: 90% + :target: javascript:void(0) + +| + +Performing gradient descent in the :math:`(\theta_0, \theta_1)` parameter +space, we are updating each parameter by the same Euclidean distance, +and not taking into account the fact that the cost function might vary at a different +rate with respect to each parameter. + +Instead, if we perform a change of coordinate system (re-parametrization) +of the cost function, we might find a parameter space where variations in :math:`\mathcal{L}` +are similar across different parameters. This is the case with the new parametrization +:math:`(\phi_0, \phi_1);` the cost function is unchanged, +but we now have a nicer geometry in which to perform gradient descent, and a more +informative stepsize. This leads to faster convergence, and can help avoid optimization +becoming stuck in local minima. For a more in-depth explanation, +including why the parameter space might not be best represented by a Euclidean space, +see `Yamamoto (2019) `__. + +However, what if we avoid gradient descent in the parameter space altogether? +If we instead consider the optimization problem as a +probability distribution of possible output values given an input +(i.e., `maximum likelihood estimation `_), +a better approach is to perform the gradient descent in the *distribution space*, which is +dimensionless and invariant with respect to the parametrization. As a result, +each optimization step will always choose the optimum step-size for every +parameter, regardless of the parametrization. + +In classical neural networks, the above process is known as +*natural gradient descent*, and was first introduced by +`Amari (1998) `__. +The standard gradient descent is modified as follows: + +.. math:: \theta_{t+1} = \theta_t - \eta F^{-1}\nabla \mathcal{L}(\theta), + +where :math:`F` is the `Fisher information matrix `__. +The Fisher information matrix acts as a metric tensor, transforming the +steepest descent in the Euclidean parameter space to the steepest descent in the +distribution space. + +The quantum analog +^^^^^^^^^^^^^^^^^^ + +In a similar vein, it has been shown that the standard Euclidean geometry +is sub-optimal for optimization of quantum variational algorithms +`(Harrow and Napp, 2019) `__. +The space of quantum states instead possesses a unique invariant metric +tensor known as the Fubini-Study metric tensor :math:`g_{ij},` which can be used to +construct a quantum analog to natural gradient descent: + +.. math:: \theta_{t+1} = \theta_t - \eta g^{+}(\theta_t)\nabla \mathcal{L}(\theta), + +where :math:`g^{+}` refers to the pseudo-inverse. + +.. note:: + + It can be shown that the Fubini-Study metric tensor reduces + to the Fisher information matrix in the classical limit. + + Furthermore, in the limit where :math:`\eta\rightarrow 0,` + the dynamics of the system are equivalent to imaginary-time + evolution within the variational subspace, as proposed in + `McArdle et al. (2018) `__. + +""" + +############################################################################## +# Block-diagonal metric tensor +# ---------------------------- +# +# A block-diagonal approximation to the Fubini-Study metric tensor +# of a variational quantum circuit can be evaluated on quantum hardware. +# +# Consider a variational quantum circuit +# +# .. math:: +# +# U(\mathbf{\theta})|\psi_0\rangle = V_L(\theta_L) W_L V_{L-1}(\theta_{L-1}) W_{L-1} +# \cdots V_{\ell}(\theta_{\ell}) W_{\ell} \cdots V_{0}(\theta_{0}) W_{0} |\psi_0\rangle +# +# where +# +# * :math:`|\psi_0\rangle` is the initial state, +# * :math:`W_\ell` are layers of non-parametrized quantum gates, +# * :math:`V_\ell(\theta_\ell)` are layers of parametrized quantum gates +# with :math:`n_\ell` parameters :math:`\theta_\ell = \{\theta^{(\ell)}_0, \dots, \theta^{(\ell)}_n\}.` +# +# Further, assume all parametrized gates can be written in the form +# :math:`X(\theta^{(\ell)}_{i}) = e^{i\theta^{(\ell)}_{i} K^{(\ell)}_i},` +# where :math:`K^{(\ell)}_i` is the *generator* of the parametrized operation. +# +# For each parametric layer :math:`\ell` in the variational quantum circuit +# the :math:`n_\ell\times n_\ell` block-diagonal submatrix +# of the Fubini-Study tensor :math:`g_{ij}^{(\ell)}` is calculated by: +# +# .. math:: +# +# g_{ij}^{(\ell)} = \langle \psi_{\ell-1} | K_i K_j | \psi_{\ell-1} \rangle +# - \langle \psi_{\ell-1} | K_i | \psi_{\ell-1}\rangle +# \langle \psi_{\ell-1} |K_j | \psi_{\ell-1}\rangle +# +# where +# +# .. math:: +# +# | \psi_{\ell-1}\rangle = V_{\ell-1}(\theta_{\ell-1}) W_{\ell-1} \cdots V_{0}(\theta_{0}) W_{0} |\psi_0\rangle. +# +# (that is, :math:`|\psi_{\ell-1}\rangle` is the quantum state prior to the application +# of parameterized layer :math:`\ell`), and we have :math:`K_i \equiv K_i^{(\ell)}` for brevity. +# +# Let's consider a small variational quantum circuit example coded in PennyLane: + +import numpy as np +import pennylane as qml +from pennylane import numpy as pnp + +dev = qml.device("lightning.qubit", wires=3) + + +@qml.qnode(dev, interface="autograd", diff_method="parameter-shift") +def circuit(params): + # |psi_0>: state preparation + qml.RY(np.pi / 4, wires=0) + qml.RY(np.pi / 3, wires=1) + qml.RY(np.pi / 7, wires=2) + + # V0(theta0, theta1): Parametrized layer 0 + qml.RZ(params[0], wires=0) + qml.RZ(params[1], wires=1) + + # W1: non-parametrized gates + qml.CNOT(wires=[0, 1]) + qml.CNOT(wires=[1, 2]) + + # V_1(theta2, theta3): Parametrized layer 1 + qml.RY(params[2], wires=1) + qml.RX(params[3], wires=2) + + # W2: non-parametrized gates + qml.CNOT(wires=[0, 1]) + qml.CNOT(wires=[1, 2]) + + return qml.expval(qml.PauliY(0)) + +# Use pennylane.numpy for trainable parameters +params = pnp.array([0.432, -0.123, 0.543, 0.233]) + +############################################################################## +# The above circuit consists of 4 parameters, with two distinct parametrized +# layers of 2 parameters each. +# +# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng1.png +# :align: center +# :width: 90% +# :target: javascript:void(0) +# +# | +# +# (Note that in this example, the first non-parametrized layer :math:`W_0` +# is simply the identity.) Since there are two layers, each with two parameters, +# the block-diagonal approximation consists of two +# :math:`2\times 2` matrices, :math:`g^{(0)}` and :math:`g^{(1)}.` +# +# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng2.png +# :align: center +# :width: 30% +# :target: javascript:void(0) +# +# To compute the first block-diagonal :math:`g^{(0)},` we create subcircuits consisting +# of all gates prior to the layer, and observables corresponding to +# the *generators* of the gates in the layer: +# +# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng3.png +# :align: center +# :width: 30% +# :target: javascript:void(0) + +g0 = np.zeros([2, 2]) + + +def layer0_subcircuit(params): + """This function contains all gates that + precede parametrized layer 0""" + qml.RY(np.pi / 4, wires=0) + qml.RY(np.pi / 3, wires=1) + qml.RY(np.pi / 7, wires=2) + + +############################################################################## +# We then post-process the measurement results in order to determine :math:`g^{(0)},` +# as follows. +# +# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng4.png +# :align: center +# :width: 50% +# :target: javascript:void(0) +# +# We can see that the diagonal terms are simply given by the variance: + + +@qml.qnode(dev, interface="autograd") +def layer0_diag(params): + layer0_subcircuit(params) + return qml.var(qml.PauliZ(0)), qml.var(qml.PauliZ(1)) + + +# calculate the diagonal terms +varK0, varK1 = layer0_diag(params) +g0[0, 0] = varK0 / 4 +g0[1, 1] = varK1 / 4 + +############################################################################## +# The following two subcircuits are then used to calculate the +# off-diagonal covariance terms of :math:`g^{(0)}:` + + +@qml.qnode(dev, interface="autograd") +def layer0_off_diag_single(params): + layer0_subcircuit(params) + return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)) + + +@qml.qnode(dev, interface="autograd") +def layer0_off_diag_double(params): + layer0_subcircuit(params) + ZZ = np.kron(np.diag([1, -1]), np.diag([1, -1])) + return qml.expval(qml.Hermitian(ZZ, wires=[0, 1])) + + +# calculate the off-diagonal terms +exK0, exK1 = layer0_off_diag_single(params) +exK0K1 = layer0_off_diag_double(params) + +g0[0, 1] = (exK0K1 - exK0 * exK1) / 4 +g0[1, 0] = (exK0K1 - exK0 * exK1) / 4 + +############################################################################## +# Note that, by definition, the block-diagonal matrices must be real and +# symmetric. +# +# We can repeat the above process to compute :math:`g^{(1)}.` The subcircuit +# required is given by +# +# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng8.png +# :align: center +# :width: 50% +# :target: javascript:void(0) + +g1 = np.zeros([2, 2]) + + +def layer1_subcircuit(params): + """This function contains all gates that + precede parametrized layer 1""" + # |psi_0>: state preparation + qml.RY(np.pi / 4, wires=0) + qml.RY(np.pi / 3, wires=1) + qml.RY(np.pi / 7, wires=2) + + # V0(theta0, theta1): Parametrized layer 0 + qml.RZ(params[0], wires=0) + qml.RZ(params[1], wires=1) + + # W1: non-parametrized gates + qml.CNOT(wires=[0, 1]) + qml.CNOT(wires=[1, 2]) + + +############################################################################## +# Using this subcircuit, we can now generate the submatrix :math:`g^{(1)}.` +# +# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng5.png +# :align: center +# :width: 50% +# :target: javascript:void(0) + + +@qml.qnode(dev, interface="autograd") +def layer1_diag(params): + layer1_subcircuit(params) + return qml.var(qml.PauliY(1)), qml.var(qml.PauliX(2)) + + +############################################################################## +# As previously, the diagonal terms are simply given by the variance, + +varK0, varK1 = layer1_diag(params) +g1[0, 0] = varK0 / 4 +g1[1, 1] = varK1 / 4 + + +############################################################################## +# while the off-diagonal terms require covariance between the two +# observables to be computed. + + +@qml.qnode(dev, interface="autograd") +def layer1_off_diag_single(params): + layer1_subcircuit(params) + return qml.expval(qml.PauliY(1)), qml.expval(qml.PauliX(2)) + + +@qml.qnode(dev, interface="autograd") +def layer1_off_diag_double(params): + layer1_subcircuit(params) + X = np.array([[0, 1], [1, 0]]) + Y = np.array([[0, -1j], [1j, 0]]) + YX = np.kron(Y, X) + return qml.expval(qml.Hermitian(YX, wires=[1, 2])) + + +# calculate the off-diagonal terms +exK0, exK1 = layer1_off_diag_single(params) +exK0K1 = layer1_off_diag_double(params) + +g1[0, 1] = (exK0K1 - exK0 * exK1) / 4 +g1[1, 0] = g1[0, 1] + + +############################################################################## +# Putting this altogether, the block-diagonal approximation to the Fubini-Study +# metric tensor for this variational quantum circuit is +from scipy.linalg import block_diag + +g = block_diag(g0, g1) +print(np.round(g, 8)) + + +############################################################################## +# PennyLane contains a built-in function for computing the Fubini-Study metric +# tensor, :func:`~.pennylane.metric_tensor`, which +# we can use to verify this result: +print(np.round(qml.metric_tensor(circuit, approx="block-diag")(params), 8)) + +############################################################################## +# As opposed to our manual computation, which required 6 different quantum +# evaluations, the PennyLane Fubini-Study metric tensor implementation +# requires only 2 quantum evaluations, one per layer. This is done by +# automatically detecting the layer structure, and noting that every +# observable that must be measured commutes, allowing for simultaneous measurement. +# +# Therefore, by combining the quantum natural gradient optimizer with the analytic +# parameter-shift rule to optimize a variational circuit with :math:`d` parameters +# and :math:`L` parametrized layers, a total of :math:`2d+L` quantum evaluations +# are required per optimization step. +# +# Note that the :func:`~.pennylane.metric_tensor` function also supports computing the diagonal +# approximation to the metric tensor: +print(qml.metric_tensor(circuit, approx='diag')(params)) + +############################################################################## +# Furthermore, the returned metric tensor is **full differentiable**; include it +# in your cost function, and train or optimize its value! + +############################################################################## +# Quantum natural gradient optimization +# ------------------------------------- +# +# PennyLane provides an implementation of the quantum natural gradient +# optimizer, :class:`~.pennylane.QNGOptimizer`. Let's compare the optimization convergence +# of the QNG Optimizer and the :class:`~.pennylane.GradientDescentOptimizer` for the simple variational +# circuit above. + +steps = 200 +init_params = pnp.array([0.432, -0.123, 0.543, 0.233], requires_grad=True) + +############################################################################## +# Performing vanilla gradient descent: + +gd_cost = [] +opt = qml.GradientDescentOptimizer(0.01) + +theta = init_params +for _ in range(steps): + theta = opt.step(circuit, theta) + gd_cost.append(circuit(theta)) + +############################################################################## +# Performing quantum natural gradient descent: + +qng_cost = [] +opt = qml.QNGOptimizer(0.01) + +theta = init_params +for _ in range(steps): + theta = opt.step(circuit, theta) + qng_cost.append(circuit(theta)) + + +############################################################################## +# Plotting the cost vs optimization step for both optimization strategies: +from matplotlib import pyplot as plt + +plt.style.use("seaborn") +plt.plot(gd_cost, "b", label="Vanilla gradient descent") +plt.plot(qng_cost, "g", label="Quantum natural gradient descent") + +plt.ylabel("Cost function value") +plt.xlabel("Optimization steps") +plt.legend() +plt.show() + +############################################################################## +# References +# ---------- +# +# 1. Shun-Ichi Amari. "Natural gradient works efficiently in learning." +# `Neural computation 10.2, 251-276 `__, 1998. +# +# 2. James Stokes, Josh Izaac, Nathan Killoran, Giuseppe Carleo. +# "Quantum Natural Gradient." `arXiv:1909.02108 `__, 2019. +# +# 3. Aram Harrow and John Napp. "Low-depth gradient measurements can improve +# convergence in variational hybrid quantum-classical algorithms." +# `arXiv:1901.05374 `__, 2019. +# +# 4. Naoki Yamamoto. "On the natural gradient for variational quantum eigensolver." +# `arXiv:1909.05074 `__, 2019. +# +# +# About the author +# ---------------- # \ No newline at end of file diff --git a/demonstrations/tutorial_quantum_phase_transitions.metadata.json b/demonstrations/tutorial_quantum_phase_transitions.metadata.json deleted file mode 100644 index 5dd1804b01..0000000000 --- a/demonstrations/tutorial_quantum_phase_transitions.metadata.json +++ /dev/null @@ -1,138 +0,0 @@ -{ - "title": "Seeing Quantum Phase Transitions with Quantum Computers", - "authors": [ - { - "username": "Damian_Pope" - }, - { - "username": "Tirth_Shah" - } - ], - "dateOfPublication": "", - "dateOfLastModification": "", - "categories": ["Optimization","Quantum Machine Learning"], - "tags": ["quantum phase transition", "Ising model"], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_3qubit_Ising_model_PyTorch.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_3qubit_Ising_model_PyTorch.png" - } - ], - "seoDescription": "Learn how to simulate and observe quantum phase transitions on a quantum computer", - "doi": "", - "references": [ - { - "id": "Vojta2002", - "type": "chapter-book", - "title": "Computational Statistical Physics", - "authors": "Thomas Vojta", - "year": "2002", - "doi": "", - "url": "https://link.springer.com/book/10.1007/978-3-662-04804-7" - }, - { - "id": "Mazumdar2019", - "type": "article-journal", - "title": "Cosmic phase transitions: their applications and experimental signatures", - "authors": ["Anupam Mazumdar","Graham White"], - "year": "2019", - "doi": "", - "url": "https://iopscience.iop.org/article/10.1088/1361-6633/ab1f55" - }, - { - "id": "Mueller2023", - "type": "article-journal", - "title": "Quantum Computation of Dynamical Quantum Phase Transitions and Entanglement Tomography in a Lattice Gauge Theory", - "authors": ["Niklas Mueller","Joseph A. Carolan","Andrew Connelly","Zohreh Davoudi","Eugene F. Dumitrescu","Kübra Yeter-Aydeniz"], - "year": "2023", - "doi": "", - "url": "https://journals.aps.org/prxquantum/abstract/10.1103/PRXQuantum.4.030323" - }, - { - "id": "Smith2019", - "type": "preprint", - "title": "Quantum Computation of Dynamical Quantum Phase Transitions and Entanglement Tomography in a Lattice Gauge Theory", - "authors": ["Adam Smith","Bernhard Jobst","Andrew G. Green","Frank Pollmann"], - "year": "2019", - "doi": "", - "url": "https://arxiv.org/abs/1910.05351" - }, - { - "id": "Haghshenas2024", - "type": "preprint", - "title": "Probing critical states of matter on a digital quantum computer", - "authors": ["Reza Haghshenas","Eli Chertkov","Matthew DeCross","Thomas M. Gatterman","Justin A. Gerber","Kevin Gilmore","Dan Gresh","Nathan Hewitt","Chandler V. Horst","Mitchell Matheny","Tanner Mengle","Brian Neyenhuis","David Hayes","Michael Foss-Feig"], - "year": "2024", - "doi": "", - "url": "https://arxiv.org/abs/2305.01650" - }, - { - "id": "Chertkov2022", - "type": "preprint", - "title": "Characterizing a non-equilibrium phase transition on a quantum computer", - "authors": ["Eli Chertkov","Zihan Cheng","Andrew C. Potter","Sarang Gopalakrishnan","Thomas M. Gatterman", "Justin A. Gerber","Kevin Gilmore","Dan Gresh","Alex Hall","Aaron Hankin","Mitchell Matheny","Tanner Mengle","David Hayes","Brian Neyenhuis","Russell Stutz","Michael Foss-Feig"], - "year": "2022", - "doi": "", - "url": "https://arxiv.org/abs/2305.01650" - }, - { - "id": "Thompson2023", - "type": "preprint", - "title": "Quantum Computation of Phase Transition in Interacting Scalar Quantum Field Theory", - "authors": ["Shane Thompson","George Siopsis"], - "year": "2023", - "doi": "", - "url": "https://arxiv.org/abs/2303.02425" - }, - { - "id": "Vodeb2025", - "type": "article-journal", - "title": "Stirring the false vacuum via interacting quantized bubbles on a 5,564-qubit quantum annealer", - "authors": ["Jaka Vodeb","Jean-Yves Desaules","Andrew Hallam","Andrea Rava","Gregor Humar","Dennis Willsch","Fengping Jin","Madita Willsch","Kristel Michielsen","Zlatko Papić"], - "year": "2024", - "doi": "", - "url": "https://www.nature.com/articles/s41567-024-02765-w" - }, - { - "id": "Kandala2017", - "type": "preprint", - "title": "Hardware-efficient Variational Quantum Eigensolver for Small Molecules and Quantum Magnets", - "authors": ["Abhinav Kandala","Antonio Mezzacapo","Kristan Temme","Maika Takita","Markus Brink","Jerry M. Chow","Jay M. Gambetta"], - "year": "2017", - "doi": "", - "url": "https://arxiv.org/abs/1704.05018" - }, - { - "id": "Blote2002", - "type": "article-journal", - "title": "Cluster Monte Carlo simulation of the transverse Ising model", - "authors": ["Henk W. J. Blote","Youjin Deng"], - "year": "2002", - "doi": "", - "url": "https://journals.aps.org/pre/abstract/10.1103/PhysRevE.66.066110" - }, - { - "id": "Hashizume2022", - "type": "article-journal", - "title": "Dynamical phase transitions in the two-dimensional transverse-field Ising model", - "authors": ["Tomohiro Hashizume","Ian P. McCulloch","Jad C. Halimeh"], - "year": "2022", - "doi": "", - "url": "https://journals.aps.org/prresearch/abstract/10.1103/PhysRevResearch.4.013250" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_isingmodel_PyTorch", - "weight": 1.0 - } - ], - "hardware": [] -} \ No newline at end of file diff --git a/demonstrations/tutorial_quantum_phase_transitions.py b/demonstrations/tutorial_quantum_phase_transitions.py deleted file mode 100644 index 3703eb3aa3..0000000000 --- a/demonstrations/tutorial_quantum_phase_transitions.py +++ /dev/null @@ -1,1156 +0,0 @@ -r""" -Seeing Phase Transitions with Quantum Computers -=============================================== - - -By Damian Pope and Tirth Shah - - - -Introduction ------------- - - -This tutorial introduces three quantum phase transitions related to condensed matter physics. It walks through how to simulate them on a quantum computer. The phase transitions it covers involve: - - - -.. raw:: html - - - -
    - - - -.. raw:: html - - - -
  • - - - -the 1D quantum Ising model - - - -.. raw:: html - - - -
  • - - - -.. raw:: html - - - -
  • - - - -the 2D quantum Ising model - - - -.. raw:: html - - - -
  • - - - -.. raw:: html - - - -
  • - - - -`dynamical quantum phase transitions`_ (i.e., phase transitions in time evolution) - - - -.. raw:: html - - - -
  • - - - -.. raw:: html - - - -
- - - - -A phase transition happens when there's an abrupt change in some property of a system. For example, when liquid water freezes and turns into ice. Phase transitions are important to many areas of physics including: - - - -.. raw:: html - - - -
    - - - -.. raw:: html - - - -
  • - - - -condensed matter physics, e.g., [#Vojta2002]_ - - - -.. raw:: html - - - -
  • - - - -.. raw:: html - - - -
  • - - - -cosmology, e.g., [#Mazumdar2019]_ - - - -.. raw:: html - - - -
  • - - - -.. raw:: html - - - -
  • - - - -high-energy physics, e.g., [#Mueller2023]_ - - - -.. raw:: html - - - -
  • - - - -.. raw:: html - - - -
- - - -They're important as they can: - - - -.. raw:: html - - - -
    - - - -.. raw:: html - - - -
  • - - - -help us find new quantum states of matter - - - -.. raw:: html - - - -
  • - - - -.. raw:: html - - - -
  • - - - -help to shed light on entanglement and long-range correlations in quantum systems - - - -.. raw:: html - - - -
  • - - - -.. raw:: html - - - -
  • - - - -help us understand the behaviour of many different quantum systems at the same time (due to the - -property of universality) - - - -.. raw:: html - - - -
  • - - - -.. raw:: html - - - -
- - - -Note: *Quantum* phase transitions are different from *classical* phase transitions. Classical phase - -transitions are caused by thermal fluctuations. Quantum phase transitions can occur at zero - -temperature and are caused by quantum fluctuations (i.e., Heisenberg's uncertainty principle). - -.. raw:: html - -
- -Phase transitions can be hard to study analytically. Due to discontinuities, mathematical models can break - -down. Phase transitions have been widely studied numerically with classical computers. However, in - -some cases, the amount of computational resources needed is prohibitive. But there's another way - -to study phase transitions: using a quantum computer. Potentially, they can compute aspects of phase - -transitions more efficiently than any conventional technique. - - - -To date, quantum computers have been used to study quantum phase transitions related to: - - - -.. raw:: html - - - -
    - - - -.. raw:: html - - - -
  • - - - -the early universe and high-energy particle colliders [#Mueller2023]_ - - - -.. raw:: html - - - -
  • - - - -.. raw:: html - - - -
  • - - - -a topological transition in an Ising-like model [#Smith2019]_ - - - -.. raw:: html - - - -
  • - - - -.. raw:: html - - - -
  • - - - -the transverse Ising model [#Haghshenas2024]_ - - - -.. raw:: html - - - -
  • - - - -.. raw:: html - - - -
  • - - - -noisy quantum systems [#Chertkov2022]_ - - - -.. raw:: html - - - -
  • - - - -.. raw:: html - - - -
  • - - - -scalar quantum field theory [#Thompson2023]_ - - - -.. raw:: html - - - -
  • - - - -.. raw:: html - - - -
  • - - - -the evolution of the universe [#Vodeb2025]_ - - - -.. raw:: html - - - -
  • - - - -.. raw:: html - - - -
- - - -Note: This tutorial focuses on the *quantum* Ising model. It complements existing content on this - -model: - -`3-qubit Ising model in PyTorch `_ - -`Transverse-field Ising model `_ - -`Ising Uprising Challenge `_ - -`How to Solve a QUBO problem `_ - -`Quadratic Unconstrained Binary Optimization (QUBO) `_ - -`Quantum Dataset How to build spin Hamiltonians `_ - - - -What is the Ising model? ------------------------- - - - -The simplest Ising model consists of :math:`N` qubits arranged along a line. - -""" - -############################################################################## -# .. figure:: ../_static/demonstration_assets/quantum_phase_transitions/Fig_1_Ising_chain.png -# :align: center -# :width: 50% - -###################################################################### -# Each qubit interacts with the qubits on either side of it. For example, the second qubit interacts with the first and third qubits. - -###################################################################### -# The system’s Hamiltonian is -# -# .. math:: -# \begin{equation} -# H = -J \,\, \Sigma_{i=1}^{N-1} \sigma_{z}^{(i)} \sigma^{(i+1)}_{z} -# \end{equation} -# -# where -# -# .. math:: -# \sigma_{z}^{(i)} = \left[ {\begin{array}{cc} -# 1 & 0 \\ -# 0 & -1 \\ -# \end{array} } \right] -# -# is the Pauli Z operator for the :math:`i^{th}` qubit and :math:`J` is the interaction strength -# between neighbouring qubits. -# -# The code below creates this Hamiltonian: -# -import pennylane as qml - -from pennylane import numpy as np - -N = 3 -J = 2 -wires = range(N) - -dev = qml.device("lightning.qubit", wires=N) - -coeffs = [-J] * (N - 1) - -obs = [] -for i in range(N - 1): - obs.append(qml.Z(i) @ qml.Z(i + 1)) -H = qml.Hamiltonian(coeffs, obs) - -print(f"H={H}") - -###################################################################### -# Why is the Ising Model Important? -# --------------------------------- -# At first glance, the Ising model looks like it's simple and unrealistic. However, it correctly -# models many properties of real-world magnets. Also, its simplicity allows us to actually solve it. -# You can think of the Ising model as a sandbox to play in and quickly learn about the essence of -# various complex real-world phenomena. -# -# The Ising model exhibits a wide range of interesting emergent properties, such as phase transitions. -# One calculation that gives us insight into their behaviour is finding the ground state of the Ising -# model and seeing how it changes as the interactions change. Often, we're looking to see if a phase -# transition happens. -# -# Let's look at an example. - - -###################################################################### -# Seeing Phase Transitions with Quantum Computers -# ----------------------------------------------- -# To do this, we'll use the well-known variational quantum eigensolver (VQE) algorithm to find the -# ground state. You can find an introduction to it `here `_. -# -# Let's start by finding the ground state of the Ising model for a fixed value of :math:`J`. -# We'll use the well-known Hardware Efficient Ansatz (HEA) [#Kandala2017]_ to do this. It's a -# general-purpose ansatz that efficiently represents a wide range of quantum states. It consists of: -# -# 1. Applying three single-qubit rotations to each qubit. Each one is parameterized by a different rotation angle. -# -# 2. Applying a CNOT gate to each neighbouring pair of qubits. -# -# 3. Applying three single-qubit rotations to each qubit. Again, each one is parameterized by a different rotation angle. - - - -import random - -random.seed(a=10) - -# params is an array that stores the parameter values of the statevector that we use in VQE. -# Generate some initial random angle values. -params = np.array([2 * np.pi * random.uniform(0, 1)] * (6 * N), requires_grad=True) - -# create an ansatz using the hardware efficiency ansatz (HEA) -def create_ansatz(params, N): - # STEP 1: perform single-qubit rotations on all the qubits - for i in range(N): - qml.RZ(phi=params[i], wires=i) - qml.RX(phi=params[N + i], wires=i) - qml.RZ(phi=params[2 * N + i], wires=i) - - # STEP 2: perform a CNOT gate on each pair of neighbouring qubits - for i in range(N - 1): - qml.CNOT(wires=[i, i + 1]) - - # STEP 3: perform single-qubit rotations on all the qubits - for i in range(N): - qml.RZ(phi=params[3 * N + i], wires=i) - qml.RX(phi=params[4 * N + i], wires=i) - qml.RZ(phi=params[5 * N + i], wires=i) - -@qml.qnode(dev) -def quantum_circuit(params): - # Create a quantum state using params - create_ansatz(params, N) - return qml.expval(H) - -max_iters = 200 -tolerance = 1e-04 - -# create an optimizer -opt = qml.GradientDescentOptimizer(stepsize=0.1) - -# energy is a list that stores all the estimates for the ground-state energy -energy = [] - -# execute the VQE optimization loop -for i in range(max_iters): - params, prev_energy = opt.step_and_cost(quantum_circuit, params) - energy.append(prev_energy) - - if i > 1: - if np.abs(energy[-2] - energy[-1]) < tolerance: - break - -# graph the energy as a function of the number of iterations -import matplotlib.pyplot as plt - -plt.plot(list(range(len(energy))), energy) -plt.xlabel("Iteration") -plt.ylabel("Energy") -plt.show() - -###################################################################### -# The graph above shows that the energy :math:`E` gradually decreases until it reaches :math:`E = - 4`. -# To check that this result makes sense, let's think about the Hamiltonian. Consider the first term, :math:`-2 * Z(0) @ Z(1)`. -# When the first and second qubits are in the computational basis state -# :math:`| 0 \rangle` , the product :math:`Z(0) @ Z(1)` is :math:`(+1)(+1) = +1`. Multiplying this by :math:`J = -2` -# gives an energy of -2. The second term :math:`-2 * Z(1) @ Z(2)` also gives :math:`E = -2`. Combining -# these results gives :math:`E = -2 -2 = -4`. When all the qubits are in the other basis state -# (:math:`| 1 \rangle`), we also get :math:`E = -4`. These two calculations agree with the numerical result from VQE. So far, so good. -# -# -# -# Let's now introduce an extra energy term that's proportional to the sum of all the Pauli X operators: -# -# .. math:: -# - h_{x}\Sigma_{i=1}^{N} \sigma_{x}^{(i)} -# -# If our qubits are actually spin-1/2 particles (e.g., electrons), :math:`h_{x}` is a horizontal -# magnetic field. Often, it's called a :math:`{\it transverse}` :math:`{\it field}`. -# - -############################################################################## -# .. figure:: ../_static/demonstration_assets/quantum_phase_transitions/Fig_2_transverse_Ising.png -# :align: center -# :width: 50% -# - -###################################################################### -# The system's Hamiltonian becomes -# -# .. math:: -# H = -J \,\, \Sigma_{i=1}^{N-1} \sigma_{z}^{(i)} \sigma^{(i+1)}_{z} - h_{x}\Sigma_{i=1}^{N} \sigma_{x}^{(i)} -# -# A quantum phase transitions happens when we change the ratio :math:`J/h_x`. Physically, this -# corresponds to changing the relative strengths of the coupling interaction and the horizontal -# magnetic field. When :math:`J` is much larger than :math:`h_{x}`, the ground state corresponds to -# all the spins (i.e., the qubits) being aligned vertically (parallel to the :math:`z` axis). -# - -############################################################################## -# .. figure:: ../_static/demonstration_assets/quantum_phase_transitions/Fig_3_ground_state_J_large.png -# :align: center -# :width: 75% -# - -###################################################################### -# But, when :math:`h_{x}` is much greater than :math:`J`, the ground state corresponds to -# all the spins being aligned along the :math:`x` axis parallel to the magnetic field: -# - -############################################################################## -# .. figure:: ../_static/demonstration_assets/quantum_phase_transitions/Fig_4_ground_state_h_large.png -# :align: center -# :width: 75% -# - -###################################################################### -# When :math:`J/h_{x} = 1`, the ground state suddenly switches from the first state (all -# vertical) to the second one (all horizontal). This is a quantum phase transition. The interplay -# between :math:`J` and :math:`h_{x}` is like a tug of war. The coupling constant :math:`J` tries to -# align all the qubits vertically, in the computational basis. The magnetic field :math:`h_{x}` tries -# to align them horizontally, in the Pauli X basis. Depending on value of :math:`J/h_{x}`, one of the two constants -# will dominate. -# -# To see the phase transition, let's introduce the total magnetization observable :math:`M` of all -# the qubits: -# -# .. math:: -# M =\frac{1}{N} \Sigma_{i} \sigma_{Z}^{(i)} -# -# It's just the sum of all the Pauli :math:`Z` operators, scaled by the number of qubits. For example, -# for the state :math:`| \psi \rangle = |0 \rangle |0\rangle`, -# :math:`M = \frac{1}{2} \left( 1 + 1 \right) = 1`. The total magnetization tracks the phase change as -# follows: -# -# - When :math:`h_{x} \gg J`, :math:`M = 0` as each qubit is in an equal superposition of :math:`|0 \rangle` and :math:`|1 \rangle`. -# -# - When :math:`J \gg h_{x}`, :math:`| M | = 1` as the qubits are either all in :math:`|0 \rangle` or all in :math:`|1 \rangle`. -# - -############################################################################## -# Let's now calculate :math:`M` for a range of :math:`J/h_{x}` values. -# -N = 5 -wires = range(N) - -# h_x is the strength of the transverse magnetic field -h_x = 1 - -# Vary the value of the coupling constant J in order to see a phase transition as we change J/h_x -J_list = [0.0, 0.25, 0.75, 0.9, 1.0, 1.1, 2.0, 5.0, 7.5] - -# This variable stores the values of the magnetization observable M for different values of J/h_x -magnetization_list = [] - -dev_2 = qml.device("lightning.qubit", wires=N) - -# This function prepares an estimate of the ground state & calculates its energy. -@qml.qnode(dev_2) -def quantum_circuit_2(params): - # Generate an estimate of the ground state - create_ansatz(params, N) - return qml.expval(H) - -# A function that returns the magnetization operator of N qubits. -def magnetization_op(N): - total_op = qml.PauliZ(0) - - if N > 1: - for i in range(1, N): - total_op = total_op + qml.PauliZ(i) - - return total_op / N - -#Prepare a parameterized state & return the value of the magnetization operator. -@qml.qnode(dev_2) -def calculate_magnetization(params): - create_ansatz(params, N) - return qml.expval(magnetization_op(N)) - -# Loop through all the different values of J -for i in range(len(J_list)): - - # Build the Hamiltonian - - # Add Pauli Z-Pauli Z interaction terms to the Hamiltonian - coeffs = [-J_list[i]] * (N - 1) - - obs = [] - for j in range(N - 1): - obs.append(qml.Z(j) @ qml.Z(j + 1)) - - # Add Pauli X terms to the Hamiltonian - for j in range(N): - obs.append(qml.X(j)) - coeffs.append(-h_x) - - H = qml.Hamiltonian(coeffs, obs) - - params = np.array([2 * np.pi * random.uniform(0, 1)] * (6 * N), requires_grad=True) - - max_iters = 200 - tolerance = 1e-04 - - # create an optimizer - opt = qml.MomentumOptimizer(stepsize=0.02, momentum=0.9) - - energy = [] - - # Run the VQE optimization loop - for j in range(max_iters): - params, prev_energy = opt.step_and_cost(quantum_circuit_2, params) - energy.append(prev_energy) - - if j > 1: - if np.abs(energy[-2] - energy[-1]) < tolerance: - break - - magnetization_list.append(calculate_magnetization(params)) - -############################################################################## -# Now that we've calculated :math:`M`, let's plot the results. -# - -# Plot |magnetization| versus J -plt.plot(J_list, np.abs(magnetization_list), marker="x") -plt.xlabel("J") -plt.ylabel("|Magnetization|") -plt.title("|Magnetization| vs. J for N=" + str(N)) -plt.show() - -###################################################################### -#Notice how the magnetization increases sharply around :math:`J/h_{x} = 1`. This suggests that a phase transition is happening. -#(It's also well known that a phase transition does happen at this value.) Why the graph doesn't have a sharp and discontinuous increase at -#exactly :math:`J/h_x=1`? There are two reasons: -# -#- Like all other numerical results, this result is just approximate. -#- The phase transition happens at :math:`J/h_x=1` in the asymptotic limit of large :math:`N`, i.e., as the number of qubits goes to infinity. You can see this by plotting how :math:`M` changes for three different values of :math:`N`, :math:`N = 4, 5, 6`. -# - -# magnetization values for N = 4 -magnetization_4 = [ - 0.01705303, - -0.05617393, - 0.34882499, - 0.38068118, - 0.74856645, - 0.90577316, - 0.9872206, -] -J_list_4 = [0.0, 0.25, 0.75, 0.9, 1.1, 2.0, 5.0] - -# magnetization values for N = 6 -magnetization_6 = [ - -0.11958867, - 0.00284093, - 0.01237123, - 0.00255386, - 0.81125517, - 0.92437233, - 0.99013448, -] -J_list_6 = J_list_4[:] - -# Plot |M| for multiple N values versus J -plt.plot(J_list_4, np.abs(magnetization_4), "xk-", label="N=4") -plt.plot(J_list[0:8], np.abs(magnetization_list[0:8]), "xb--", label="N=5") -plt.plot(J_list_6, np.abs(magnetization_6), "sg:", label="N=6") - -plt.xlabel("J") -plt.ylabel("|Magnetization|") -plt.title("|Magnetization| vs. J for N=4, 5, 6") -plt.legend(loc="lower right") -plt.show() - -###################################################################### -# Notice how the increase in :math:`|M|` gets steeper as we increase :math:`N`. You can -# think of this as showing that we're getting closer and closer to the asymptotic behaviour of a truly -# discontinuous phase transition. -# - -###################################################################### -# Two-dimensional Ising Model -# --------------------------- -# In the 2D quantum Ising model, the qubits are arranged in a 2D grid. -# - -###################################################################### -# .. figure:: ../_static/demonstration_assets/quantum_phase_transitions/Fig_5_2D_Ising_model.png -# :align: center -# :width: 25% - -############################################################################## -# Compared to the 1D model, it's richer, harder to solve mathematically, and harder to simulate on -# classical computers. It's also more realistic and is used by physicists to study -# low-dimensional quantum systems. In this section, we'll explore phase transitions -# in the 2D quantum Ising model. The Hamiltonian for the model is -# -# .. math:: -# H = -J \,\, \Sigma_{\langle i,j \rangle} \sigma_{z}^{(i)} \sigma^{(j)}_{z} - h_{x} \Sigma_{ i } \sigma_{x}^{(i)} -# -# The expression :math:`\langle i,j \rangle` includes every pair of neighbouring qubits in the lattice. The -# :math:`\Sigma_{i}` term sums over every qubit in the lattice. The code below creates the Hamiltonian -# using `PennyLane's spin module `_. -# - -N = 2 - -H = qml.spin.transverse_ising(lattice="square", n_cells=[N, N], h=1.0, boundary_condition=True) - -print(f"H={H}") - -###################################################################### -# Like we did for the 1D model, let's find the ground state using VQE. -# - -wires_2D = range(N**2) -dev_2D = qml.device("lightning.qubit", wires=wires_2D) - -random.seed(a=10) - -# generate random parameter values for the initial statevector -params = np.array([2 * np.pi * random.uniform(0, 1)] * (6 * N), requires_grad=True) - -@qml.qnode(dev_2D) -def quantum_circuit_2D(params): - create_ansatz(params, N) - return qml.expval(H) - -max_iters = 500 -tolerance = 3e-04 - -# create an optimizer -opt = qml.GradientDescentOptimizer(stepsize=0.015) - -energy = [] - -# execute the optimization loop -for i in range(max_iters): - params, prev_energy = opt.step_and_cost(quantum_circuit_2D, params) - energy.append(prev_energy) - - if i > 1: - if np.abs(energy[-2] - energy[-1]) < tolerance: - break - -# print out the results -plt.plot(list(range(len(energy))), energy) -plt.xlabel("Iteration") -plt.ylabel("Energy") -plt.show() - -############################################################################## -# The energy in the graph approaches -4.5, which makes sense. The Hamiltonian has four two-qubit interaction terms and the -# smallest that each one can be is -1. So, the ground-state energy must be less than -4. Let's vary the ratio :math:`J/h_{x}` again and calculate the -# magnetization :math:`M` each time. Finally, let's plot the results and see if there's a quantum -# phase transition. -# - -N = 3 -dev_2D_varying_J = qml.device("lightning.qubit", wires=N**2) - -# strength of transverse magnetic field -h_x = 1 - -# Vary J in order to see a phase transition in the magnetization as we change J/h_x -J_list = [0.035, 0.05, 0.1, 0.25, 0.375, 0.5, 0.75, 1.0, 5, 10] - -magnetization_list = [] - -# Prepare a parameterized state & calculate the value of the magnetization operator. -@qml.qnode(dev_2D_varying_J) -def calculate_magnetization_2D(params): - create_ansatz(params, N) - return qml.expval(magnetization_op(N)) - -@qml.qnode(dev_2D_varying_J) -def quantum_circuit_2D_varying_J(params): - create_ansatz(params, N) - return qml.expval(H) - -# Loop through all values of J -for i in range(len(J_list)): - H = qml.spin.transverse_ising( - lattice="square", coupling=J_list[i], n_cells=[N, N], boundary_condition=True - ) - #Set the initial values of the rotation angle parameters. - #The values below were chosen as, through trial and error, we discovered that they worked well. - params = np.zeros(6 * N, requires_grad=True) - for i in range(N): - params[i] = 0 - params[N + i] = np.pi / 2 - params[2 * N + i] = np.pi / 2 - - max_iters = 500 - - # create an optimizer - opt = qml.MomentumOptimizer(stepsize=0.03, momentum=0.9) - - energy = [] - - # execute the optimization loop - for j in range(max_iters): - params, prev_energy = opt.step_and_cost(quantum_circuit_2D_varying_J, params) - energy.append(prev_energy) - - if j > 1: - if np.abs(energy[-2] - energy[-1]) < tolerance: - break - - magnetization_list.append(calculate_magnetization_2D(params)) - -###################################################################### -# Let's plot the results. - -# Plot |magnetization| versus J -plt.plot(J_list, np.abs(magnetization_list), marker="x") -plt.xlabel("J") -plt.ylabel("|Magnetization|") -plt.title("|Magnetization| vs. J for N=" + str(N)) -plt.show() - -###################################################################### -# From the graph, it's unclear if there's a phase transition. Looking at it, multiple data points are -# bunched up on the left. To spread them out, let's change the scale by ignoring the last two -# points. -# - -plt.plot(J_list[0:8], np.abs(magnetization_list[0:8]), marker="x") -plt.xlabel("J") -plt.ylabel("|Magnetization|") -plt.title("|Magnetization| vs. J for N=" + str(N)) -plt.show() - -###################################################################### -# Like in the 1D case, the magnetization displays a rapid increase. This is consistent with a phase change -# but it's not conclusive as the increase is somewhat gradual. This is because :math:`N` is so small. -# Note that the result is consistent with where the phase change is known to occur [#Blote2002]_, [#Hashizume2022]_. -# - -############################################################################## -#.. _dynamical quantum phase transitions: -#Time Evolution & Dynamical Phase Transitions -#-------------------------------------------- -# -#Another important aspect of quantum systems is how they evolve over time. -#Sometimes, this evolution is hard to simulate on classical computers. So, researchers are -#interested in modelling it on quantum computers. Occasionally, -#some property of a quantum system changes abruptly. This is called a *dynamical quantum -#phase transition*: a phase transition that happens during the time evolution -#of a quantum system [#Heyl2013]_. -# -#To evolve the Ising model in time, we'll use the well-known Suzuki-Trotter product approximation. The code below does this. - -import math - -N = 5 -wires = range(N) - -# create the Hamiltonian for a 1D Ising model with transverse & longitudinal magnetic fields -# we do this to copy what was done in Reference 13: https://arxiv.org/abs/2008.04894 -obs = [] -for j in range(N - 1): - obs.append(qml.Z(j) @ qml.Z(j + 1)) - -# add Pauli X terms to Hamiltonian (transverse field) -for j in range(N): - obs.append(qml.X(j)) - -# add Pauli Z terms to Hamiltonian (longitudinal field) -for j in range(N): - obs.append(qml.Z(j)) - -dev = qml.device("lightning.qubit", wires=N) - -J = -0.1 - -# strength of transverse field interaction -h_x = 1 - -# strength of longitudinal field interaction -h_z = -0.15 - -J_coeffs = [-J] * (N - 1) - -X_coeffs = [h_x] * N - -Z_coeffs = [h_z] * N - -coeffs = J_coeffs + X_coeffs + Z_coeffs - -H = qml.Hamiltonian(coeffs, obs) - -# create the circuit that evolves the system in time -@qml.qnode(dev) -def time_evolution_circuit(H, T): - #Evolve the system via a sequence of short approximate Trotter time steps - #https://docs.pennylane.ai/en/stable/code/api/pennylane.TrotterProduct.html - qml.TrotterProduct(H, time=T, n=math.ceil(T / 0.1)+1, order=2) - - # return the final probabilities - return qml.probs(wires=range(N)) - - -############################################################################## -#To see if a dynamical phase transition happens, let's consider a observable called the :math:`\it{rate \; function}` -#:math:`\gamma`. It depends on the overlap between the quantum state that we start with and the final state at -#some time :math:`t`. More specifically, -# -# .. math:: -# \gamma = -\frac{1}{N} \log_{e} (|G|^{2}) -# -#where :math:`G = \langle \psi_{i} | \psi_{f}\rangle`, where :math:`| \psi_{i}\rangle` and :math:`| \psi_{f} \rangle` are the initial and final states -#respectively. As the system evolves, we'll keep calculating :math:`\gamma`. If it changes discontinuously, -#then a dynamical phase transition has happened. -# -# -#The function below calculates :math:`\gamma` at time :math:`T`. -def rate_function(H, T, N): - probability_list = time_evolution_circuit(H, T) - mag_G_squared = probability_list[0] - return -1 / N * np.log(mag_G_squared) - -###################################################################### -#Let's now calculate :math:`\gamma` at different times to see how it evolves. Finally, let's graph the value of :math:`\gamma` versus time to see if a dynamical phase transition happens. -# - -rate_function_list = [] - -# time step size for time evolution -deltaT = 0.05 - -num_time_steps = 50 - -for i in range(num_time_steps): - rate_function_list.append(rate_function(H, i * deltaT, N)) - -plt.plot(np.linspace(0, deltaT * (num_time_steps-1), num_time_steps), rate_function_list) -plt.xlabel("time") -plt.ylabel(r"Rate function, $\lambda$") -plt.title("Rate Function versus time") -plt.legend(["N=" + str(N)]) -plt.show() - -############################################################################## -# The sharp change in :math:`\Gamma` at :math:`t = 1.5` suggests that a dynamical phase transition has happened. This -# conclusion is supported by classical numerical simulations that show a phase -# transition at the same time [#Nicola2021]_. -# - -############################################################################## -#Summary -#------- -# In this demo, we have shown how you can use quantum computers to simulate quantum phase -# transitions in the 1D and 2D quantum Ising models. We've also shown how to use quantum computers to see dynamical quantum phase transitions. -# -#Acknowledgement -#--------------- -#Damian Pope would like to thank Associate Professor Matthew Johnson (Perimeter Institute for Theoretical Physics and -#York University) for insightful discussions on quantum phase transitions in cosmology and quantum computing. - -###################################################################### -#References -#------------ -# -# .. [#Vojta2002] -# T. Vojta, in K.H. Hoffmann and M. Schreiber (Eds): Computational Statistical Physics, Springer, Berlin (2002) -# -# .. [#Mazumdar2019] -# -# Anupam Mazumdar and Graham White. "Cosmic phase transitions: their applications and experimental signatures" Rep. Prog. Phys. 82, 076901, 2019 -# -# -# .. [#Mueller2023] -# -# Niklas Mueller et al. "Quantum Computation of Dynamical Quantum Phase Transitions and Entanglement Tomography in a Lattice Gauge Theory" PRX Quantum 4, 030323, 2023 -# -# -# .. [#Smith2019] -# -# Adam Smith, Bernhard Jobst, Andrew G. Green, and Frank Pollmann. "Crossing a topological phase transition with a quantum computer" `arXiv:1910.05351 [cond-mat.str-el] `__, 2019 -# -# -# .. [#Haghshenas2024] -# -# Reza Haghshenas et al. "Probing critical states of matter on a digital quantum computer" `arXiv:2305.01650 [quant-ph] `__, 2024 -# -# -# -# .. [#Chertkov2022] -# -# Eli Chertkov, et al., "Characterizing a non-equilibrium phase transition on a quantum computer", `arXiv:2209.12889 [quant-ph] `__, 2022 -# -# -# -# .. [#Thompson2023] -# -# Shane Thompson and George Siopsis. "Quantum Computation of Phase Transition in Interacting Scalar Quantum Field Theory" `arXiv:2303.02425 [quant-ph] `__, 2023 -# -# -# .. [#Vodeb2025] -# -# Jaka Vodeb et al., "Stirring the false vacuum via interacting quantized bubbles on a 5,564-qubit quantum annealer", Nature Physics, 21, 386, 2025 `https://www.nature.com/articles/s41567-024-02765-w `__ -# -# -# .. [#Kandala2017] -# -# Abhinav Kandala et al., "Hardware-efficient Variational Quantum Eigensolver for Small Molecules and Quantum Magnets", `arXiv:1704.05018 [quant-ph] `__ 2017 -# -# -# .. [#Blote2002] -# -# Henk W. J. Blöte and Youjin Deng. "Cluster Monte Carlo simulation of the transverse Ising model", Phys. Rev. E 66, 066110, 2002. (See Table II, row labelled 'square lattice'); -# -# -# .. [#Hashizume2022] -# -# Tomohiro Hashizume, Ian P. McCulloch, and Jad C. Halimeh. "Dynamical phase transitions in the two-dimensional transverse-field Ising model", Phys. Rev. Research 4, 013250, 2022 (See Figure 1 and Section II) -# -# .. [#Heyl2013] -# -# M. Heyl, A. Polkovnikov, S. Kehrein. "Dynamical Quantum Phase Transitions in the Transverse-Field Ising Model", Phys. Rev. Lett. 110 135704 (2013) -# -# .. [#Nicola2021] -# -# S. De Nicola , A. A. Michailidis , M. Serbyn. "Entanglement View of Dynamical Quantum Phase Transitions", Phys. Rev. Lett. 126 040602 (2021), Figure 1 (d) -# -############################################################################## -# About the author -# ---------------- -# - \ No newline at end of file diff --git a/demonstrations/tutorial_quantum_transfer_learning.py b/demonstrations/tutorial_quantum_transfer_learning.py index 8991710e41..a8e584f07c 100644 --- a/demonstrations/tutorial_quantum_transfer_learning.py +++ b/demonstrations/tutorial_quantum_transfer_learning.py @@ -1,612 +1,612 @@ -r""" -.. _quantum_transfer_learning: - -Quantum transfer learning -========================= - -.. meta:: - :property="og:description": Combine PyTorch and PennyLane to train a hybrid quantum-classical image - classifier using transfer learning. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/transfer_images.png - -*Author: Andrea Mari — Posted: 19 December 2019. Last updated: 28 January 2021.* - -In this tutorial we apply a machine learning method, known as *transfer learning*, to an -image classifier based on a hybrid classical-quantum network. - -This example follows the general structure of the PyTorch -`tutorial on transfer learning `_ -by Sasank Chilamkurthy, with the crucial difference of using a quantum circuit to perform the -final classification task. - -More details on this topic can be found in the research paper [1] (`Mari et al. (2019) `_). - - -Introduction ------------- - -Transfer learning is a well-established technique for training artificial neural networks (see e.g., Ref. [2]), -which is based on the general intuition that if a pre-trained network is good at solving a -given problem, then, with just a bit of additional training, it can be used to also solve a different -but related problem. - -As discussed in Ref. [1], this idea can be formalized in terms of two abstract networks :math:`A` -and :math:`B,` independently from their quantum or classical physical nature. - -| - - -.. figure:: ../_static/demonstration_assets/quantum_transfer_learning/transfer_learning_general.png - :scale: 45% - :alt: transfer_general - :align: center - -| - -As sketched in the above figure, one can give the following **general definition of the -transfer learning method**: - -1. Take a network :math:`A` that has been pre-trained on a dataset :math:`D_A` and for a given - task :math:`T_A.` - -2. Remove some of the final layers. In this way, the resulting truncated network :math:`A'` - can be used as a feature extractor. - -3. Connect a new trainable network :math:`B` at the end of the pre-trained network :math:`A'.` - -4. Keep the weights of :math:`A'` constant, and train the final block :math:`B` with a - new dataset :math:`D_B` and/or for a new task of interest :math:`T_B.` - -When dealing with hybrid systems, depending on the physical nature (classical or quantum) of the -networks :math:`A` and :math:`B,` one can have different implementations of transfer learning as - -summarized in following table: - -| - -.. rst-class:: docstable - -+-----------+-----------+-----------------------------------------------------+ -| Network A | Network B | Transfer learning scheme | -+===========+===========+=====================================================+ -| Classical | Classical | CC - Standard classical method. See e.g., Ref. [2]. | -+-----------+-----------+-----------------------------------------------------+ -| Classical | Quantum | CQ - **Hybrid model presented in this tutorial.** | -+-----------+-----------+-----------------------------------------------------+ -| Quantum | Classical | QC - Model studied in Ref. [1]. | -+-----------+-----------+-----------------------------------------------------+ -| Quantum | Quantum | QQ - Model studied in Ref. [1]. | -+-----------+-----------+-----------------------------------------------------+ - -Classical-to-quantum transfer learning --------------------------------------- - -We focus on the CQ transfer learning scheme discussed in the previous section and we give a specific example. - -1. As pre-trained network :math:`A` we use **ResNet18**, a deep residual neural network introduced by - Microsoft in Ref. [3], which is pre-trained on the *ImageNet* dataset. - -2. After removing its final layer we obtain :math:`A',` a pre-processing block which maps any - input high-resolution image into 512 abstract features. - -3. Such features are classified by a 4-qubit "dressed quantum circuit" :math:`B,` i.e., a - variational quantum circuit sandwiched between two classical layers. - -4. The hybrid model is trained, keeping :math:`A'` constant, on the *Hymenoptera* dataset - (a small subclass of ImageNet) containing images of *ants* and *bees*. - -A graphical representation of the full data processing pipeline is given in the figure below. - -.. figure:: ../_static/demonstration_assets/quantum_transfer_learning/transfer_learning_c2q.png - :scale: 55% - :alt: transfer_c2q - :align: center - -""" - -############################################################################## -# General setup -# ------------------------ -# -# .. note:: -# -# To use the PyTorch interface in PennyLane, you must first -# `install PyTorch `_. -# -# In addition to *PennyLane*, we will also need some standard *PyTorch* libraries and the -# plotting library *matplotlib*. - -# Some parts of this code are based on the Python script: -# https://github.com/pytorch/tutorials/blob/master/beginner_source/transfer_learning_tutorial.py -# License: BSD - -import time -import os -import copy - -# PyTorch -import torch -import torch.nn as nn -import torch.optim as optim -from torch.optim import lr_scheduler -import torchvision -from torchvision import datasets, transforms - -# Pennylane -import pennylane as qml -from pennylane import numpy as np - -torch.manual_seed(42) -np.random.seed(42) - -# Plotting -import matplotlib.pyplot as plt - -# OpenMP: number of parallel threads. -os.environ["OMP_NUM_THREADS"] = "1" - - -############################################################################## -# Setting of the main hyper-parameters of the model -# ------------------------------------------------------------ -# -# .. note:: -# To reproduce the results of Ref. [1], ``num_epochs`` should be set to ``30`` which may take a long time. -# We suggest to first try with ``num_epochs=1`` and, if everything runs smoothly, increase it to a larger value. - - -n_qubits = 4 # Number of qubits -step = 0.0004 # Learning rate -batch_size = 4 # Number of samples for each training step -num_epochs = 3 # Number of training epochs -q_depth = 6 # Depth of the quantum circuit (number of variational layers) -gamma_lr_scheduler = 0.1 # Learning rate reduction applied every 10 epochs. -q_delta = 0.01 # Initial spread of random quantum weights -start_time = time.time() # Start of the computation timer - -############################################################################## -# We initialize a PennyLane device with a ``default.qubit`` backend. - -dev = qml.device("default.qubit", wires=n_qubits) - -############################################################################## -# We configure PyTorch to use CUDA only if available. Otherwise the CPU is used. - -device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") - -############################################################################## -# Dataset loading -# ------------------------------------------------------------ -# -# .. note:: -# The dataset containing images of *ants* and *bees* can be downloaded -# `here `_ and -# should be extracted in the subfolder ``../_data/hymenoptera_data``. -# -# This is a very small dataset (roughly 250 images), too small for training from scratch a -# classical or quantum model, however it is enough when using *transfer learning* approach. -# -# The PyTorch packages ``torchvision`` and ``torch.utils.data`` are used for loading the dataset -# and performing standard preliminary image operations: resize, center, crop, normalize, *etc.* - -data_transforms = { - "train": transforms.Compose( - [ - # transforms.RandomResizedCrop(224), # uncomment for data augmentation - # transforms.RandomHorizontalFlip(), # uncomment for data augmentation - transforms.Resize(256), - transforms.CenterCrop(224), - transforms.ToTensor(), - # Normalize input channels using mean values and standard deviations of ImageNet. - transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), - ] - ), - "val": transforms.Compose( - [ - transforms.Resize(256), - transforms.CenterCrop(224), - transforms.ToTensor(), - transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), - ] - ), -} - -data_dir = "../_data/hymenoptera_data" -image_datasets = { - x if x == "train" else "validation": datasets.ImageFolder( - os.path.join(data_dir, x), data_transforms[x] - ) - for x in ["train", "val"] -} -dataset_sizes = {x: len(image_datasets[x]) for x in ["train", "validation"]} -class_names = image_datasets["train"].classes - -# Initialize dataloader -dataloaders = { - x: torch.utils.data.DataLoader(image_datasets[x], batch_size=batch_size, shuffle=True) - for x in ["train", "validation"] -} - -# function to plot images -def imshow(inp, title=None): - """Display image from tensor.""" - inp = inp.numpy().transpose((1, 2, 0)) - # Inverse of the initial normalization operation. - mean = np.array([0.485, 0.456, 0.406]) - std = np.array([0.229, 0.224, 0.225]) - inp = std * inp + mean - inp = np.clip(inp, 0, 1) - plt.imshow(inp) - if title is not None: - plt.title(title) - - -############################################################################## -# Let us show a batch of the test data, just to have an idea of the classification problem. - -# Get a batch of training data -inputs, classes = next(iter(dataloaders["validation"])) - -# Make a grid from batch -out = torchvision.utils.make_grid(inputs) - -imshow(out, title=[class_names[x] for x in classes]) - -dataloaders = { - x: torch.utils.data.DataLoader(image_datasets[x], batch_size=batch_size, shuffle=True) - for x in ["train", "validation"] -} - - -############################################################################## -# Variational quantum circuit -# ------------------------------------ -# We first define some quantum layers that will compose the quantum circuit. - - -def H_layer(nqubits): - """Layer of single-qubit Hadamard gates. - """ - for idx in range(nqubits): - qml.Hadamard(wires=idx) - - -def RY_layer(w): - """Layer of parametrized qubit rotations around the y axis. - """ - for idx, element in enumerate(w): - qml.RY(element, wires=idx) - - -def entangling_layer(nqubits): - """Layer of CNOTs followed by another shifted layer of CNOT. - """ - # In other words it should apply something like : - # CNOT CNOT CNOT CNOT... CNOT - # CNOT CNOT CNOT... CNOT - for i in range(0, nqubits - 1, 2): # Loop over even indices: i=0,2,...N-2 - qml.CNOT(wires=[i, i + 1]) - for i in range(1, nqubits - 1, 2): # Loop over odd indices: i=1,3,...N-3 - qml.CNOT(wires=[i, i + 1]) - - -############################################################################## -# Now we define the quantum circuit through the PennyLane `qnode` decorator . -# -# The structure is that of a typical variational quantum circuit: -# -# * **Embedding layer:** All qubits are first initialized in a balanced superposition -# of *up* and *down* states, then they are rotated according to the input parameters -# (local embedding). -# -# * **Variational layers:** A sequence of trainable rotation layers and constant -# entangling layers is applied. -# -# * **Measurement layer:** For each qubit, the local expectation value of the :math:`Z` -# operator is measured. This produces a classical output vector, suitable for -# additional post-processing. - - -@qml.qnode(dev) -def quantum_net(q_input_features, q_weights_flat): - """ - The variational quantum circuit. - """ - - # Reshape weights - q_weights = q_weights_flat.reshape(q_depth, n_qubits) - - # Start from state |+> , unbiased w.r.t. |0> and |1> - H_layer(n_qubits) - - # Embed features in the quantum node - RY_layer(q_input_features) - - # Sequence of trainable variational layers - for k in range(q_depth): - entangling_layer(n_qubits) - RY_layer(q_weights[k]) - - # Expectation values in the Z basis - exp_vals = [qml.expval(qml.PauliZ(position)) for position in range(n_qubits)] - return tuple(exp_vals) - - -############################################################################## -# Dressed quantum circuit -# ------------------------ -# -# We can now define a custom ``torch.nn.Module`` representing a *dressed* quantum circuit. -# -# This is a concatenation of: -# -# * A classical pre-processing layer (``nn.Linear``). -# * A classical activation function (``torch.tanh``). -# * A constant ``np.pi/2.0`` scaling. -# * The previously defined quantum circuit (``quantum_net``). -# * A classical post-processing layer (``nn.Linear``). -# -# The input of the module is a batch of vectors with 512 real parameters (features) and -# the output is a batch of vectors with two real outputs (associated with the two classes -# of images: *ants* and *bees*). - - -class DressedQuantumNet(nn.Module): - """ - Torch module implementing the *dressed* quantum net. - """ - - def __init__(self): - """ - Definition of the *dressed* layout. - """ - - super().__init__() - self.pre_net = nn.Linear(512, n_qubits) - self.q_params = nn.Parameter(q_delta * torch.randn(q_depth * n_qubits)) - self.post_net = nn.Linear(n_qubits, 2) - - def forward(self, input_features): - """ - Defining how tensors are supposed to move through the *dressed* quantum - net. - """ - - # obtain the input features for the quantum circuit - # by reducing the feature dimension from 512 to 4 - pre_out = self.pre_net(input_features) - q_in = torch.tanh(pre_out) * np.pi / 2.0 - - # Apply the quantum circuit to each element of the batch and append to q_out - q_out = torch.Tensor(0, n_qubits) - q_out = q_out.to(device) - for elem in q_in: - q_out_elem = torch.hstack(quantum_net(elem, self.q_params)).float().unsqueeze(0) - q_out = torch.cat((q_out, q_out_elem)) - - # return the two-dimensional prediction from the postprocessing layer - return self.post_net(q_out) - - -############################################################################## -# Hybrid classical-quantum model -# ------------------------------------ -# -# We are finally ready to build our full hybrid classical-quantum network. -# We follow the *transfer learning* approach: -# -# 1. First load the classical pre-trained network *ResNet18* from the ``torchvision.models`` zoo. -# 2. Freeze all the weights since they should not be trained. -# 3. Replace the last fully connected layer with our trainable dressed quantum circuit (``DressedQuantumNet``). -# -# .. note:: -# The *ResNet18* model is automatically downloaded by PyTorch and it may take several minutes (only the first time). -# - -weights = torchvision.models.ResNet18_Weights.IMAGENET1K_V1 -model_hybrid = torchvision.models.resnet18(weights=weights) - -for param in model_hybrid.parameters(): - param.requires_grad = False - - -# Notice that model_hybrid.fc is the last layer of ResNet18 -model_hybrid.fc = DressedQuantumNet() - -# Use CUDA or CPU according to the "device" object. -model_hybrid = model_hybrid.to(device) - -############################################################################## -# Training and results -# ------------------------ -# -# Before training the network we need to specify the *loss* function. -# -# We use, as usual in classification problem, the *cross-entropy* which is -# directly available within ``torch.nn``. - - -criterion = nn.CrossEntropyLoss() - -############################################################################## -# We also initialize the *Adam optimizer* which is called at each training step -# in order to update the weights of the model. - - -optimizer_hybrid = optim.Adam(model_hybrid.fc.parameters(), lr=step) - -############################################################################## -# We schedule to reduce the learning rate by a factor of ``gamma_lr_scheduler`` -# every 10 epochs. - - -exp_lr_scheduler = lr_scheduler.StepLR( - optimizer_hybrid, step_size=10, gamma=gamma_lr_scheduler -) - -############################################################################## -# What follows is a training function that will be called later. -# This function should return a trained model that can be used to make predictions -# (classifications). - - -def train_model(model, criterion, optimizer, scheduler, num_epochs): - since = time.time() - best_model_wts = copy.deepcopy(model.state_dict()) - best_acc = 0.0 - best_loss = 10000.0 # Large arbitrary number - best_acc_train = 0.0 - best_loss_train = 10000.0 # Large arbitrary number - print("Training started:") - - for epoch in range(num_epochs): - - # Each epoch has a training and validation phase - for phase in ["train", "validation"]: - if phase == "train": - # Set model to training mode - model.train() - else: - # Set model to evaluate mode - model.eval() - running_loss = 0.0 - running_corrects = 0 - - # Iterate over data. - n_batches = dataset_sizes[phase] // batch_size - it = 0 - for inputs, labels in dataloaders[phase]: - since_batch = time.time() - batch_size_ = len(inputs) - inputs = inputs.to(device) - labels = labels.to(device) - optimizer.zero_grad() - - # Track/compute gradient and make an optimization step only when training - with torch.set_grad_enabled(phase == "train"): - outputs = model(inputs) - _, preds = torch.max(outputs, 1) - loss = criterion(outputs, labels) - if phase == "train": - loss.backward() - optimizer.step() - - # Print iteration results - running_loss += loss.item() * batch_size_ - batch_corrects = torch.sum(preds == labels.data).item() - running_corrects += batch_corrects - print( - "Phase: {} Epoch: {}/{} Iter: {}/{} Batch time: {:.4f}".format( - phase, - epoch + 1, - num_epochs, - it + 1, - n_batches + 1, - time.time() - since_batch, - ), - end="\r", - flush=True, - ) - it += 1 - - # Print epoch results - epoch_loss = running_loss / dataset_sizes[phase] - epoch_acc = running_corrects / dataset_sizes[phase] - print( - "Phase: {} Epoch: {}/{} Loss: {:.4f} Acc: {:.4f} ".format( - "train" if phase == "train" else "validation ", - epoch + 1, - num_epochs, - epoch_loss, - epoch_acc, - ) - ) - - # Check if this is the best model wrt previous epochs - if phase == "validation" and epoch_acc > best_acc: - best_acc = epoch_acc - best_model_wts = copy.deepcopy(model.state_dict()) - if phase == "validation" and epoch_loss < best_loss: - best_loss = epoch_loss - if phase == "train" and epoch_acc > best_acc_train: - best_acc_train = epoch_acc - if phase == "train" and epoch_loss < best_loss_train: - best_loss_train = epoch_loss - - # Update learning rate - if phase == "train": - scheduler.step() - - # Print final results - model.load_state_dict(best_model_wts) - time_elapsed = time.time() - since - print( - "Training completed in {:.0f}m {:.0f}s".format(time_elapsed // 60, time_elapsed % 60) - ) - print("Best test loss: {:.4f} | Best test accuracy: {:.4f}".format(best_loss, best_acc)) - return model - - -############################################################################## -# We are ready to perform the actual training process. - -model_hybrid = train_model( - model_hybrid, criterion, optimizer_hybrid, exp_lr_scheduler, num_epochs=num_epochs -) - -############################################################################## -# Visualizing the model predictions -# ------------------------------------ - -############################################################################## -# We first define a visualization function for a batch of test data. - - -def visualize_model(model, num_images=6, fig_name="Predictions"): - images_so_far = 0 - _fig = plt.figure(fig_name) - model.eval() - with torch.no_grad(): - for _i, (inputs, labels) in enumerate(dataloaders["validation"]): - inputs = inputs.to(device) - labels = labels.to(device) - outputs = model(inputs) - _, preds = torch.max(outputs, 1) - for j in range(inputs.size()[0]): - images_so_far += 1 - ax = plt.subplot(num_images // 2, 2, images_so_far) - ax.axis("off") - ax.set_title("[{}]".format(class_names[preds[j]])) - imshow(inputs.cpu().data[j]) - if images_so_far == num_images: - return - - -############################################################################## -# Finally, we can run the previous function to see a batch of images -# with the corresponding predictions. -# -visualize_model(model_hybrid, num_images=batch_size) -plt.show() - -############################################################################## -# References -# ------------ -# -# [1] Andrea Mari, Thomas R. Bromley, Josh Izaac, Maria Schuld, and Nathan Killoran. -# *Transfer learning in hybrid classical-quantum neural networks*. arXiv:1912.08278 (2019). -# -# [2] Rajat Raina, Alexis Battle, Honglak Lee, Benjamin Packer, and Andrew Y Ng. -# *Self-taught learning: transfer learning from unlabeled data*. -# Proceedings of the 24th International Conference on Machine Learning*, 759–766 (2007). -# -# [3] Kaiming He, Xiangyu Zhang, Shaoqing ren and Jian Sun. *Deep residual learning for image recognition*. -# Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition, 770-778 (2016). -# -# [4] Ville Bergholm, Josh Izaac, Maria Schuld, Christian Gogolin, Carsten Blank, Keri McKiernan, and Nathan Killoran. -# *PennyLane: Automatic differentiation of hybrid quantum-classical computations*. arXiv:1811.04968 (2018). -# -# -# About the author -# ---------------- +r""" +.. _quantum_transfer_learning: + +Quantum transfer learning +========================= + +.. meta:: + :property="og:description": Combine PyTorch and PennyLane to train a hybrid quantum-classical image + classifier using transfer learning. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/transfer_images.png + +*Author: Andrea Mari — Posted: 19 December 2019. Last updated: 28 January 2021.* + +In this tutorial we apply a machine learning method, known as *transfer learning*, to an +image classifier based on a hybrid classical-quantum network. + +This example follows the general structure of the PyTorch +`tutorial on transfer learning `_ +by Sasank Chilamkurthy, with the crucial difference of using a quantum circuit to perform the +final classification task. + +More details on this topic can be found in the research paper [1] (`Mari et al. (2019) `_). + + +Introduction +------------ + +Transfer learning is a well-established technique for training artificial neural networks (see e.g., Ref. [2]), +which is based on the general intuition that if a pre-trained network is good at solving a +given problem, then, with just a bit of additional training, it can be used to also solve a different +but related problem. + +As discussed in Ref. [1], this idea can be formalized in terms of two abstract networks :math:`A` +and :math:`B,` independently from their quantum or classical physical nature. + +| + + +.. figure:: ../_static/demonstration_assets/quantum_transfer_learning/transfer_learning_general.png + :scale: 45% + :alt: transfer_general + :align: center + +| + +As sketched in the above figure, one can give the following **general definition of the +transfer learning method**: + +1. Take a network :math:`A` that has been pre-trained on a dataset :math:`D_A` and for a given + task :math:`T_A.` + +2. Remove some of the final layers. In this way, the resulting truncated network :math:`A'` + can be used as a feature extractor. + +3. Connect a new trainable network :math:`B` at the end of the pre-trained network :math:`A'.` + +4. Keep the weights of :math:`A'` constant, and train the final block :math:`B` with a + new dataset :math:`D_B` and/or for a new task of interest :math:`T_B.` + +When dealing with hybrid systems, depending on the physical nature (classical or quantum) of the +networks :math:`A` and :math:`B,` one can have different implementations of transfer learning as + +summarized in following table: + +| + +.. rst-class:: docstable + ++-----------+-----------+-----------------------------------------------------+ +| Network A | Network B | Transfer learning scheme | ++===========+===========+=====================================================+ +| Classical | Classical | CC - Standard classical method. See e.g., Ref. [2]. | ++-----------+-----------+-----------------------------------------------------+ +| Classical | Quantum | CQ - **Hybrid model presented in this tutorial.** | ++-----------+-----------+-----------------------------------------------------+ +| Quantum | Classical | QC - Model studied in Ref. [1]. | ++-----------+-----------+-----------------------------------------------------+ +| Quantum | Quantum | QQ - Model studied in Ref. [1]. | ++-----------+-----------+-----------------------------------------------------+ + +Classical-to-quantum transfer learning +-------------------------------------- + +We focus on the CQ transfer learning scheme discussed in the previous section and we give a specific example. + +1. As pre-trained network :math:`A` we use **ResNet18**, a deep residual neural network introduced by + Microsoft in Ref. [3], which is pre-trained on the *ImageNet* dataset. + +2. After removing its final layer we obtain :math:`A',` a pre-processing block which maps any + input high-resolution image into 512 abstract features. + +3. Such features are classified by a 4-qubit "dressed quantum circuit" :math:`B,` i.e., a + variational quantum circuit sandwiched between two classical layers. + +4. The hybrid model is trained, keeping :math:`A'` constant, on the *Hymenoptera* dataset + (a small subclass of ImageNet) containing images of *ants* and *bees*. + +A graphical representation of the full data processing pipeline is given in the figure below. + +.. figure:: ../_static/demonstration_assets/quantum_transfer_learning/transfer_learning_c2q.png + :scale: 55% + :alt: transfer_c2q + :align: center + +""" + +############################################################################## +# General setup +# ------------------------ +# +# .. note:: +# +# To use the PyTorch interface in PennyLane, you must first +# `install PyTorch `_. +# +# In addition to *PennyLane*, we will also need some standard *PyTorch* libraries and the +# plotting library *matplotlib*. + +# Some parts of this code are based on the Python script: +# https://github.com/pytorch/tutorials/blob/master/beginner_source/transfer_learning_tutorial.py +# License: BSD + +import time +import os +import copy + +# PyTorch +import torch +import torch.nn as nn +import torch.optim as optim +from torch.optim import lr_scheduler +import torchvision +from torchvision import datasets, transforms + +# Pennylane +import pennylane as qml +from pennylane import numpy as np + +torch.manual_seed(42) +np.random.seed(42) + +# Plotting +import matplotlib.pyplot as plt + +# OpenMP: number of parallel threads. +os.environ["OMP_NUM_THREADS"] = "1" + + +############################################################################## +# Setting of the main hyper-parameters of the model +# ------------------------------------------------------------ +# +# .. note:: +# To reproduce the results of Ref. [1], ``num_epochs`` should be set to ``30`` which may take a long time. +# We suggest to first try with ``num_epochs=1`` and, if everything runs smoothly, increase it to a larger value. + + +n_qubits = 4 # Number of qubits +step = 0.0004 # Learning rate +batch_size = 4 # Number of samples for each training step +num_epochs = 3 # Number of training epochs +q_depth = 6 # Depth of the quantum circuit (number of variational layers) +gamma_lr_scheduler = 0.1 # Learning rate reduction applied every 10 epochs. +q_delta = 0.01 # Initial spread of random quantum weights +start_time = time.time() # Start of the computation timer + +############################################################################## +# We initialize a PennyLane device with a ``default.qubit`` backend. + +dev = qml.device("default.qubit", wires=n_qubits) + +############################################################################## +# We configure PyTorch to use CUDA only if available. Otherwise the CPU is used. + +device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") + +############################################################################## +# Dataset loading +# ------------------------------------------------------------ +# +# .. note:: +# The dataset containing images of *ants* and *bees* can be downloaded +# `here `_ and +# should be extracted in the subfolder ``../_data/hymenoptera_data``. +# +# This is a very small dataset (roughly 250 images), too small for training from scratch a +# classical or quantum model, however it is enough when using *transfer learning* approach. +# +# The PyTorch packages ``torchvision`` and ``torch.utils.data`` are used for loading the dataset +# and performing standard preliminary image operations: resize, center, crop, normalize, *etc.* + +data_transforms = { + "train": transforms.Compose( + [ + # transforms.RandomResizedCrop(224), # uncomment for data augmentation + # transforms.RandomHorizontalFlip(), # uncomment for data augmentation + transforms.Resize(256), + transforms.CenterCrop(224), + transforms.ToTensor(), + # Normalize input channels using mean values and standard deviations of ImageNet. + transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), + ] + ), + "val": transforms.Compose( + [ + transforms.Resize(256), + transforms.CenterCrop(224), + transforms.ToTensor(), + transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), + ] + ), +} + +data_dir = "../_data/hymenoptera_data" +image_datasets = { + x if x == "train" else "validation": datasets.ImageFolder( + os.path.join(data_dir, x), data_transforms[x] + ) + for x in ["train", "val"] +} +dataset_sizes = {x: len(image_datasets[x]) for x in ["train", "validation"]} +class_names = image_datasets["train"].classes + +# Initialize dataloader +dataloaders = { + x: torch.utils.data.DataLoader(image_datasets[x], batch_size=batch_size, shuffle=True) + for x in ["train", "validation"] +} + +# function to plot images +def imshow(inp, title=None): + """Display image from tensor.""" + inp = inp.numpy().transpose((1, 2, 0)) + # Inverse of the initial normalization operation. + mean = np.array([0.485, 0.456, 0.406]) + std = np.array([0.229, 0.224, 0.225]) + inp = std * inp + mean + inp = np.clip(inp, 0, 1) + plt.imshow(inp) + if title is not None: + plt.title(title) + + +############################################################################## +# Let us show a batch of the test data, just to have an idea of the classification problem. + +# Get a batch of training data +inputs, classes = next(iter(dataloaders["validation"])) + +# Make a grid from batch +out = torchvision.utils.make_grid(inputs) + +imshow(out, title=[class_names[x] for x in classes]) + +dataloaders = { + x: torch.utils.data.DataLoader(image_datasets[x], batch_size=batch_size, shuffle=True) + for x in ["train", "validation"] +} + + +############################################################################## +# Variational quantum circuit +# ------------------------------------ +# We first define some quantum layers that will compose the quantum circuit. + + +def H_layer(nqubits): + """Layer of single-qubit Hadamard gates. + """ + for idx in range(nqubits): + qml.Hadamard(wires=idx) + + +def RY_layer(w): + """Layer of parametrized qubit rotations around the y axis. + """ + for idx, element in enumerate(w): + qml.RY(element, wires=idx) + + +def entangling_layer(nqubits): + """Layer of CNOTs followed by another shifted layer of CNOT. + """ + # In other words it should apply something like : + # CNOT CNOT CNOT CNOT... CNOT + # CNOT CNOT CNOT... CNOT + for i in range(0, nqubits - 1, 2): # Loop over even indices: i=0,2,...N-2 + qml.CNOT(wires=[i, i + 1]) + for i in range(1, nqubits - 1, 2): # Loop over odd indices: i=1,3,...N-3 + qml.CNOT(wires=[i, i + 1]) + + +############################################################################## +# Now we define the quantum circuit through the PennyLane `qnode` decorator . +# +# The structure is that of a typical variational quantum circuit: +# +# * **Embedding layer:** All qubits are first initialized in a balanced superposition +# of *up* and *down* states, then they are rotated according to the input parameters +# (local embedding). +# +# * **Variational layers:** A sequence of trainable rotation layers and constant +# entangling layers is applied. +# +# * **Measurement layer:** For each qubit, the local expectation value of the :math:`Z` +# operator is measured. This produces a classical output vector, suitable for +# additional post-processing. + + +@qml.qnode(dev) +def quantum_net(q_input_features, q_weights_flat): + """ + The variational quantum circuit. + """ + + # Reshape weights + q_weights = q_weights_flat.reshape(q_depth, n_qubits) + + # Start from state |+> , unbiased w.r.t. |0> and |1> + H_layer(n_qubits) + + # Embed features in the quantum node + RY_layer(q_input_features) + + # Sequence of trainable variational layers + for k in range(q_depth): + entangling_layer(n_qubits) + RY_layer(q_weights[k]) + + # Expectation values in the Z basis + exp_vals = [qml.expval(qml.PauliZ(position)) for position in range(n_qubits)] + return tuple(exp_vals) + + +############################################################################## +# Dressed quantum circuit +# ------------------------ +# +# We can now define a custom ``torch.nn.Module`` representing a *dressed* quantum circuit. +# +# This is a concatenation of: +# +# * A classical pre-processing layer (``nn.Linear``). +# * A classical activation function (``torch.tanh``). +# * A constant ``np.pi/2.0`` scaling. +# * The previously defined quantum circuit (``quantum_net``). +# * A classical post-processing layer (``nn.Linear``). +# +# The input of the module is a batch of vectors with 512 real parameters (features) and +# the output is a batch of vectors with two real outputs (associated with the two classes +# of images: *ants* and *bees*). + + +class DressedQuantumNet(nn.Module): + """ + Torch module implementing the *dressed* quantum net. + """ + + def __init__(self): + """ + Definition of the *dressed* layout. + """ + + super().__init__() + self.pre_net = nn.Linear(512, n_qubits) + self.q_params = nn.Parameter(q_delta * torch.randn(q_depth * n_qubits)) + self.post_net = nn.Linear(n_qubits, 2) + + def forward(self, input_features): + """ + Defining how tensors are supposed to move through the *dressed* quantum + net. + """ + + # obtain the input features for the quantum circuit + # by reducing the feature dimension from 512 to 4 + pre_out = self.pre_net(input_features) + q_in = torch.tanh(pre_out) * np.pi / 2.0 + + # Apply the quantum circuit to each element of the batch and append to q_out + q_out = torch.Tensor(0, n_qubits) + q_out = q_out.to(device) + for elem in q_in: + q_out_elem = torch.hstack(quantum_net(elem, self.q_params)).float().unsqueeze(0) + q_out = torch.cat((q_out, q_out_elem)) + + # return the two-dimensional prediction from the postprocessing layer + return self.post_net(q_out) + + +############################################################################## +# Hybrid classical-quantum model +# ------------------------------------ +# +# We are finally ready to build our full hybrid classical-quantum network. +# We follow the *transfer learning* approach: +# +# 1. First load the classical pre-trained network *ResNet18* from the ``torchvision.models`` zoo. +# 2. Freeze all the weights since they should not be trained. +# 3. Replace the last fully connected layer with our trainable dressed quantum circuit (``DressedQuantumNet``). +# +# .. note:: +# The *ResNet18* model is automatically downloaded by PyTorch and it may take several minutes (only the first time). +# + +weights = torchvision.models.ResNet18_Weights.IMAGENET1K_V1 +model_hybrid = torchvision.models.resnet18(weights=weights) + +for param in model_hybrid.parameters(): + param.requires_grad = False + + +# Notice that model_hybrid.fc is the last layer of ResNet18 +model_hybrid.fc = DressedQuantumNet() + +# Use CUDA or CPU according to the "device" object. +model_hybrid = model_hybrid.to(device) + +############################################################################## +# Training and results +# ------------------------ +# +# Before training the network we need to specify the *loss* function. +# +# We use, as usual in classification problem, the *cross-entropy* which is +# directly available within ``torch.nn``. + + +criterion = nn.CrossEntropyLoss() + +############################################################################## +# We also initialize the *Adam optimizer* which is called at each training step +# in order to update the weights of the model. + + +optimizer_hybrid = optim.Adam(model_hybrid.fc.parameters(), lr=step) + +############################################################################## +# We schedule to reduce the learning rate by a factor of ``gamma_lr_scheduler`` +# every 10 epochs. + + +exp_lr_scheduler = lr_scheduler.StepLR( + optimizer_hybrid, step_size=10, gamma=gamma_lr_scheduler +) + +############################################################################## +# What follows is a training function that will be called later. +# This function should return a trained model that can be used to make predictions +# (classifications). + + +def train_model(model, criterion, optimizer, scheduler, num_epochs): + since = time.time() + best_model_wts = copy.deepcopy(model.state_dict()) + best_acc = 0.0 + best_loss = 10000.0 # Large arbitrary number + best_acc_train = 0.0 + best_loss_train = 10000.0 # Large arbitrary number + print("Training started:") + + for epoch in range(num_epochs): + + # Each epoch has a training and validation phase + for phase in ["train", "validation"]: + if phase == "train": + # Set model to training mode + model.train() + else: + # Set model to evaluate mode + model.eval() + running_loss = 0.0 + running_corrects = 0 + + # Iterate over data. + n_batches = dataset_sizes[phase] // batch_size + it = 0 + for inputs, labels in dataloaders[phase]: + since_batch = time.time() + batch_size_ = len(inputs) + inputs = inputs.to(device) + labels = labels.to(device) + optimizer.zero_grad() + + # Track/compute gradient and make an optimization step only when training + with torch.set_grad_enabled(phase == "train"): + outputs = model(inputs) + _, preds = torch.max(outputs, 1) + loss = criterion(outputs, labels) + if phase == "train": + loss.backward() + optimizer.step() + + # Print iteration results + running_loss += loss.item() * batch_size_ + batch_corrects = torch.sum(preds == labels.data).item() + running_corrects += batch_corrects + print( + "Phase: {} Epoch: {}/{} Iter: {}/{} Batch time: {:.4f}".format( + phase, + epoch + 1, + num_epochs, + it + 1, + n_batches + 1, + time.time() - since_batch, + ), + end="\r", + flush=True, + ) + it += 1 + + # Print epoch results + epoch_loss = running_loss / dataset_sizes[phase] + epoch_acc = running_corrects / dataset_sizes[phase] + print( + "Phase: {} Epoch: {}/{} Loss: {:.4f} Acc: {:.4f} ".format( + "train" if phase == "train" else "validation ", + epoch + 1, + num_epochs, + epoch_loss, + epoch_acc, + ) + ) + + # Check if this is the best model wrt previous epochs + if phase == "validation" and epoch_acc > best_acc: + best_acc = epoch_acc + best_model_wts = copy.deepcopy(model.state_dict()) + if phase == "validation" and epoch_loss < best_loss: + best_loss = epoch_loss + if phase == "train" and epoch_acc > best_acc_train: + best_acc_train = epoch_acc + if phase == "train" and epoch_loss < best_loss_train: + best_loss_train = epoch_loss + + # Update learning rate + if phase == "train": + scheduler.step() + + # Print final results + model.load_state_dict(best_model_wts) + time_elapsed = time.time() - since + print( + "Training completed in {:.0f}m {:.0f}s".format(time_elapsed // 60, time_elapsed % 60) + ) + print("Best test loss: {:.4f} | Best test accuracy: {:.4f}".format(best_loss, best_acc)) + return model + + +############################################################################## +# We are ready to perform the actual training process. + +model_hybrid = train_model( + model_hybrid, criterion, optimizer_hybrid, exp_lr_scheduler, num_epochs=num_epochs +) + +############################################################################## +# Visualizing the model predictions +# ------------------------------------ + +############################################################################## +# We first define a visualization function for a batch of test data. + + +def visualize_model(model, num_images=6, fig_name="Predictions"): + images_so_far = 0 + _fig = plt.figure(fig_name) + model.eval() + with torch.no_grad(): + for _i, (inputs, labels) in enumerate(dataloaders["validation"]): + inputs = inputs.to(device) + labels = labels.to(device) + outputs = model(inputs) + _, preds = torch.max(outputs, 1) + for j in range(inputs.size()[0]): + images_so_far += 1 + ax = plt.subplot(num_images // 2, 2, images_so_far) + ax.axis("off") + ax.set_title("[{}]".format(class_names[preds[j]])) + imshow(inputs.cpu().data[j]) + if images_so_far == num_images: + return + + +############################################################################## +# Finally, we can run the previous function to see a batch of images +# with the corresponding predictions. +# +visualize_model(model_hybrid, num_images=batch_size) +plt.show() + +############################################################################## +# References +# ------------ +# +# [1] Andrea Mari, Thomas R. Bromley, Josh Izaac, Maria Schuld, and Nathan Killoran. +# *Transfer learning in hybrid classical-quantum neural networks*. arXiv:1912.08278 (2019). +# +# [2] Rajat Raina, Alexis Battle, Honglak Lee, Benjamin Packer, and Andrew Y Ng. +# *Self-taught learning: transfer learning from unlabeled data*. +# Proceedings of the 24th International Conference on Machine Learning*, 759–766 (2007). +# +# [3] Kaiming He, Xiangyu Zhang, Shaoqing ren and Jian Sun. *Deep residual learning for image recognition*. +# Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition, 770-778 (2016). +# +# [4] Ville Bergholm, Josh Izaac, Maria Schuld, Christian Gogolin, Carsten Blank, Keri McKiernan, and Nathan Killoran. +# *PennyLane: Automatic differentiation of hybrid quantum-classical computations*. arXiv:1811.04968 (2018). +# +# +# About the author +# ---------------- # \ No newline at end of file diff --git a/demonstrations/tutorial_qubit_tapering.py b/demonstrations/tutorial_qubit_tapering.py index a09519630b..5c55be064e 100644 --- a/demonstrations/tutorial_qubit_tapering.py +++ b/demonstrations/tutorial_qubit_tapering.py @@ -1,330 +1,330 @@ -r""" - -Qubit tapering -============== - -.. meta:: - :property="og:description": Learn how to taper off qubits - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qubit_tapering.png - -.. related:: - tutorial_quantum_chemistry Building molecular Hamiltonians - tutorial_vqe A brief overview of VQE - tutorial_givens_rotations Givens rotations for quantum chemistry - tutorial_adaptive_circuits Adaptive circuits for quantum chemistry - tutorial_differentiable_HF Differentiable Hartree-Fock - -*Authors: Utkarsh Azad and Soran Jahangiri. Posted: 16 May 2022. Last updated: 08 Nov 2022* - -The performance of variational quantum algorithms is considerably limited by the number of qubits -required to represent wave functions. In the context of quantum chemistry, this -limitation hinders the treatment of large molecules with algorithms such as the :doc:`variational quantum -eigensolver (VQE) `. Several approaches have been developed to reduce the qubit requirements for -quantum chemistry calculations. In this tutorial, we demonstrate the symmetry-based qubit -tapering approach which allows reducing the number of qubits required to perform molecular quantum -simulations based on the :math:`\mathbb{Z}_2` symmetries present in molecular Hamiltonians -[#bravyi2017]_ [#setia2019]_. - -A molecular Hamiltonian in the qubit basis can be expressed as a linear combination of Pauli words -as - -.. math:: H = \sum_{i=1}^r h_i P_i, - -where :math:`h_i` is a real coefficient and :math:`P_i` is a tensor product of Pauli and -identity operators acting on :math:`M` qubits - -.. math:: P_i \in \pm \left \{ I, X, Y, Z \right \} ^ {\bigotimes M}. - -The main idea in the symmetry-based qubit tapering approach is to find a unitary operator :math:`U` -that transforms :math:`H` to a new Hamiltonian :math:`H'` which has the same eigenvalues as -:math:`H` - -.. math:: H' = U^{\dagger} H U = \sum_{i=1}^r c_i \mu_i, - -such that each :math:`\mu_i` term in the new Hamiltonian always acts trivially, e.g., with an -identity or a Pauli operator, on a set of qubits. This allows tapering-off those qubits from the -Hamiltonian. - -For instance, consider the following Hamiltonian - -.. math:: H' = Z_0 X_1 - X_1 + Y_0 X_1, - -where all terms in the Hamiltonian act on the second qubit with the :math:`X` operator. It is -straightforward to show that each term in the Hamiltonian commutes with :math:`I_0 X_1` and the -ground-state eigenvector of :math:`H'` is also an eigenvector of :math:`I_0 X_1` with eigenvalues -:math:`\pm 1.` We can also rewrite the Hamiltonian as - -.. math:: H' = (Z_0 I_1 - I_0 I_1 + Y_0 I_1) I_0 X_1, - -which gives us - -.. math:: H'|\psi \rangle = \pm1 (Z_0 I_1 - I_0 I_1 + Y_0 I_1)|\psi \rangle, - -where :math:`|\psi \rangle` is an eigenvector of :math:`H'.` This means that the Hamiltonian -:math:`H` can be simplified as - -.. math:: H_{tapered} = \pm1 (Z_0 - I_0 + Y_0). - -The tapered Hamiltonian :math:`H_{tapered}` has the eigenvalues - -.. math:: [-2.41421, 0.41421], - -and - -.. math:: [2.41421, -0.41421], - -depending on the value of the :math:`\pm 1` prefactor. The eigenvalues of the original Hamiltonian -:math:`H` are - -.. math:: [2.41421, -2.41421, 0.41421, -0.41421], - -which are thus reproduced by the tapered Hamiltonian. - -More generally, we can construct the unitary :math:`U` such that each :math:`\mu_i` term acts with a -Pauli-X operator on a set of qubits -:math:`\left \{ j \right \}, j \in \left \{ l, ..., k \right \}` where :math:`j` is the qubit label. -This guarantees that each term of the transformed Hamiltonian commutes with each of the Pauli-X -operators applied to the :math:`j`-th qubit: - -.. math:: [H', X^j] = 0, - -and the eigenvectors of the transformed Hamiltonian :math:`H'` are also eigenvectors of each of the -:math:`X^{j}` operators. Then we can factor out all of the :math:`X^{j}` operators from the -transformed Hamiltonian and replace them with their eigenvalues :math:`\pm 1.` This gives us a -set of tapered Hamiltonians depending on which eigenvalue :math:`\pm 1` we chose for each of the -:math:`X^{j}` operators. For instance, in the case of two tapered qubits, we have four eigenvalue -sectors: :math:`[+1, +1]`, :math:`[-1, +1]`, :math:`[+1, -1],` :math:`[-1, -1].` In these tapered -Hamiltonians, the set of :math:`\left \{ j \right \}, j \in \left \{ l, ..., k \right \}` qubits -are eliminated. For tapered molecular Hamiltonians, it is possible to determine the optimal sector -of the eigenvalues that corresponds to the ground state. This is explained in more detail in the -following sections. - -The unitary operator :math:`U` can be constructed as a -`Clifford `__ operator [#bravyi2017]_ - -.. math:: U = \Pi_j \left [\frac{1}{\sqrt{2}} \left (X^{q(j)} + \tau_j \right) \right], - -where :math:`\tau` denotes the generators of the symmetry group of :math:`H` and -:math:`X^{q}` operators act on those qubits that will be ultimately tapered off from -the Hamiltonian. The symmetry group of the Hamiltonian is defined as an Abelian group of Pauli words that commute -with each term in the Hamiltonian (excluding :math:`−I`). The -`generators `__ of the symmetry group are -those elements of the group that can be combined, along with their inverses, to create any other -member of the group. - -Let's use the qubit tapering method and obtain the ground state energy of the `Helium hydride -cation `__ :math:`\textrm{HeH}^+.` - -Tapering the molecular Hamiltonian ----------------------------------- - -In PennyLane, a :doc:`molecular Hamiltonian ` can be created by specifying the atomic symbols and -coordinates. -""" -import pennylane as qml -from jax import numpy as jnp -import jax - -jax.config.update("jax_enable_x64", True) -symbols = ["He", "H"] -geometry = jnp.array([[0.00000000, 0.00000000, -0.87818361], - [0.00000000, 0.00000000, 0.87818362]]) - -molecule = qml.qchem.Molecule(symbols, geometry, charge=1) -H, qubits = qml.qchem.molecular_hamiltonian(molecule) -H - -############################################################################## -# This Hamiltonian contains 27 terms where each term acts on up to four qubits. -# -# We can now obtain the symmetry generators and the :math:`X^{j}` operators that are -# used to construct the unitary :math:`U` operator that transforms the :math:`\textrm{HeH}^+` -# Hamiltonian. In PennyLane, these are constructed by using the -# :func:`~.pennylane.symmetry_generators` and :func:`~.pennylane.paulix_ops` functions. - -generators = qml.symmetry_generators(H) -paulixops = qml.paulix_ops(generators, qubits) - -for idx, generator in enumerate(generators): - print(f"generator {idx+1}: {generator}, paulix_op: {paulixops[idx]}") - -############################################################################## -# Once the operator :math:`U` is applied, each of the Hamiltonian terms will act on the qubits -# :math:`q_2, q_3` either with the identity or with a Pauli-X operator. For each of these qubits, -# we can simply replace the Pauli-X operator with one of its eigenvalues :math:`+1` or :math:`-1.` -# This results in a total number of :math:`2^k` Hamiltonians, where :math:`k` is the number of -# tapered-off qubits and each Hamiltonian corresponds to one eigenvalue sector. The optimal sector -# corresponding to the ground-state energy of the molecule can be obtained by using the -# :func:`~.pennylane.qchem.optimal_sector` function. - - -n_electrons = 2 -paulix_sector = qml.qchem.optimal_sector(H, generators, n_electrons) -print(paulix_sector) - -############################################################################## -# The optimal eigenvalues are :math:`-1, -1` for qubits :math:`q_2, q_3,` respectively. We can now -# build the tapered Hamiltonian with the :func:`~.pennylane.taper` function which -# constructs the operator :math:`U,` applies it to the Hamiltonian and finally tapers off the -# qubits :math:`q_2, q_3` by replacing the Pauli-X operators acting on those qubits with the optimal -# eigenvalues. - -H_tapered = qml.taper(H, generators, paulixops, paulix_sector) -H_tapered_coeffs, H_tapered_ops = H_tapered.terms() -H_tapered = qml.Hamiltonian(jnp.real(jnp.array(H_tapered_coeffs)), H_tapered_ops) -print(H_tapered) - -############################################################################## -# The new Hamiltonian has only 9 non-zero terms acting on only 2 qubits! We can verify that the -# original and the tapered Hamiltonian both give the correct ground state energy of the -# :math:`\textrm{HeH}^+` cation, which is :math:`-2.862595242378` Ha computed with the full -# configuration interaction (FCI) method. In PennyLane, it's possible to build a sparse matrix -# representation of Hamiltonians. This allows us to directly diagonalize them to obtain exact values -# of the ground-state energies. - -H_sparse = qml.SparseHamiltonian(H.sparse_matrix(), wires=H.wires) -H_tapered_sparse = qml.SparseHamiltonian(H_tapered.sparse_matrix(), wires=H_tapered.wires) - -print("Eigenvalues of H:\n", qml.eigvals(H_sparse, k=16)) -print("\nEigenvalues of H_tapered:\n", qml.eigvals(H_tapered_sparse, k=4)) - -############################################################################## -# Note that a second-quantized Hamiltonian is independent of the number of electrons and its -# eigenspectrum contains the energies of the neutral and charged molecules. Therefore, the -# smallest eigenvalue returned by :func:`~.pennylane.eigvals` for a molecular Hamiltonian -# might correspond to the neutral or charged molecule. While in the case of :math:`\textrm{HeH}^+,` -# qubit tapering allows specifying the optimal sector of the eigenvectors corresponding only to the -# correct number of electrons, it is generally guaranteed that the optimal sector covers all -# eigenvectors with the correct number of electrons, but may contain additional eigenvectors of -# different charge. Therefore, the ground-state energy of the :math:`\textrm{HeH}^+` cation is -# the smallest eigenvalue of the tapered Hamiltonian. -# -# Tapering the reference state -# ---------------------------- -# The ground state Hartree-Fock energy of :math:`\textrm{HeH}^+` can be computed by directly -# applying the Hamiltonians to the Hartree-Fock state. For the tapered Hamiltonian, this requires -# transforming the Hartree-Fock state with the same symmetries obtained for the original -# Hamiltonian. This reduces the number of qubits in the Hartree-Fock state to match that of the -# tapered Hamiltonian. It can be done with the :func:`~.pennylane.qchem.taper_hf` function. - -state_tapered = qml.qchem.taper_hf(generators, paulixops, paulix_sector, - num_electrons=n_electrons, num_wires=len(H.wires)) -print(state_tapered) - -############################################################################## -# Recall that the original Hartree-Fock state for the :math:`\textrm{HeH}^+` cation is -# :math:`[1 1 0 0].` We can now generate the qubit representation of these states and compute the -# Hartree-Fock energies for each Hamiltonian. - -dev = qml.device("default.qubit", wires=H.wires) -@qml.qnode(dev, interface="jax") -def circuit(): - qml.BasisState(jnp.array([1, 1, 0, 0]), wires=H.wires) - return qml.state() - -qubit_state = circuit() -HF_energy = qubit_state.T @ H.sparse_matrix().toarray() @ qubit_state -print(f"HF energy: {jnp.real(HF_energy):.8f} Ha") - -dev = qml.device("lightning.qubit", wires=H_tapered.wires) -@qml.qnode(dev, interface="jax") -def circuit(): - qml.BasisState(jnp.array([1, 1]), wires=H_tapered.wires) - return qml.state() - -qubit_state = circuit() -HF_energy = qubit_state.T @ H_tapered.sparse_matrix().toarray() @ qubit_state -print(f"HF energy (tapered): {jnp.real(HF_energy):.8f} Ha") - -############################################################################## -# These values are identical to the reference Hartree-Fock energy :math:`-2.8543686493` Ha. -# -# VQE simulation -# -------------- -# Finally, we can use the tapered Hamiltonian and the tapered reference state to perform a VQE -# simulation and compute the ground-state energy of the :math:`\textrm{HeH}^+` cation. We build a -# tapered variational ansatz `[3] `__ -# that prepares an entangled state by evolving the tapered Hartree-Fock state using the tapered -# particle-conserving gates, i.e., the :func:`~.pennylane.SingleExcitation` and -# :func:`~.pennylane.DoubleExcitation` operations tapered using -# :func:`~.pennylane.qchem.taper_operation`. - -singles, doubles = qml.qchem.excitations(n_electrons, len(H.wires)) -tapered_doubles = [ - qml.taper_operation(qml.DoubleExcitation, generators, paulixops, paulix_sector, - wire_order=H.wires, op_wires=double) for double in doubles -] -tapered_singles = [ - qml.taper_operation(qml.SingleExcitation, generators, paulixops, paulix_sector, - wire_order=H.wires, op_wires=single) for single in singles -] - -dev = qml.device("lightning.qubit", wires=H_tapered.wires) - -@qml.qnode(dev, interface="jax") -def tapered_circuit(params): - qml.BasisState(state_tapered, wires=H_tapered.wires) - for idx, tapered_op in enumerate(tapered_doubles + tapered_singles): - tapered_op(params[idx]) - return qml.expval(H_tapered) - -############################################################################## -# We define an optimizer and the initial values of the circuit parameters and optimize the circuit -# parameters with respect to the ground state energy. - -import optax -import catalyst - -opt = optax.sgd(learning_rate=0.8) # sgd stands for StochasticGradientDescent -init_params = jnp.zeros(len(doubles) + len(singles)) - -def update_step(i, params, opt_state): - """Perform a single gradient update step""" - grads = catalyst.grad(tapered_circuit)(params) - updates, opt_state = opt.update(grads, opt_state) - params = optax.apply_updates(params, updates) - return (params, opt_state) - -loss_history = [] - -opt_state = opt.init(init_params) -params = init_params - -for i in range(1, 41): - params, opt_state = update_step(i, params, opt_state) - energy = tapered_circuit(params) - if not i % 5: - print(f"n: {i}, E: {energy:.8f} Ha, Params: {params}") - -############################################################################## -# The computed energy matches the FCI energy, :math:`-2.862595242378` Ha, while the number of qubits -# and the number of Hamiltonian terms are significantly reduced with respect to their original -# values. -# -# Conclusions -# ----------- -# Molecular Hamiltonians possess symmetries that can be leveraged to reduce the number of qubits -# required in quantum computing simulations. This tutorial introduces a PennyLane functionality that -# can be used for qubit tapering based on :math:`\mathbb{Z}_2` symmetries. The procedure includes -# obtaining tapered Hamiltonians and tapered reference states that can be used in variational -# quantum algorithms such as VQE. -# -# References -# ---------- -# -# .. [#bravyi2017] -# -# Sergey Bravyi, Jay M. Gambetta, Antonio Mezzacapo, Kristan Temme, "Tapering off qubits to -# simulate fermionic Hamiltonians". `arXiv:1701.08213 `__ -# -# .. [#setia2019] -# -# Kanav Setia, Richard Chen, Julia E. Rice, Antonio Mezzacapo, Marco Pistoia, James Whitfield, -# "Reducing qubit requirements for quantum simulation using molecular point group symmetries". -# `arXiv:1910.14644 `__ -# -# -# -# About the author -# ---------------- -# +r""" + +Qubit tapering +============== + +.. meta:: + :property="og:description": Learn how to taper off qubits + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qubit_tapering.png + +.. related:: + tutorial_quantum_chemistry Building molecular Hamiltonians + tutorial_vqe A brief overview of VQE + tutorial_givens_rotations Givens rotations for quantum chemistry + tutorial_adaptive_circuits Adaptive circuits for quantum chemistry + tutorial_differentiable_HF Differentiable Hartree-Fock + +*Authors: Utkarsh Azad and Soran Jahangiri. Posted: 16 May 2022. Last updated: 08 Nov 2022* + +The performance of variational quantum algorithms is considerably limited by the number of qubits +required to represent wave functions. In the context of quantum chemistry, this +limitation hinders the treatment of large molecules with algorithms such as the :doc:`variational quantum +eigensolver (VQE) `. Several approaches have been developed to reduce the qubit requirements for +quantum chemistry calculations. In this tutorial, we demonstrate the symmetry-based qubit +tapering approach which allows reducing the number of qubits required to perform molecular quantum +simulations based on the :math:`\mathbb{Z}_2` symmetries present in molecular Hamiltonians +[#bravyi2017]_ [#setia2019]_. + +A molecular Hamiltonian in the qubit basis can be expressed as a linear combination of Pauli words +as + +.. math:: H = \sum_{i=1}^r h_i P_i, + +where :math:`h_i` is a real coefficient and :math:`P_i` is a tensor product of Pauli and +identity operators acting on :math:`M` qubits + +.. math:: P_i \in \pm \left \{ I, X, Y, Z \right \} ^ {\bigotimes M}. + +The main idea in the symmetry-based qubit tapering approach is to find a unitary operator :math:`U` +that transforms :math:`H` to a new Hamiltonian :math:`H'` which has the same eigenvalues as +:math:`H` + +.. math:: H' = U^{\dagger} H U = \sum_{i=1}^r c_i \mu_i, + +such that each :math:`\mu_i` term in the new Hamiltonian always acts trivially, e.g., with an +identity or a Pauli operator, on a set of qubits. This allows tapering-off those qubits from the +Hamiltonian. + +For instance, consider the following Hamiltonian + +.. math:: H' = Z_0 X_1 - X_1 + Y_0 X_1, + +where all terms in the Hamiltonian act on the second qubit with the :math:`X` operator. It is +straightforward to show that each term in the Hamiltonian commutes with :math:`I_0 X_1` and the +ground-state eigenvector of :math:`H'` is also an eigenvector of :math:`I_0 X_1` with eigenvalues +:math:`\pm 1.` We can also rewrite the Hamiltonian as + +.. math:: H' = (Z_0 I_1 - I_0 I_1 + Y_0 I_1) I_0 X_1, + +which gives us + +.. math:: H'|\psi \rangle = \pm1 (Z_0 I_1 - I_0 I_1 + Y_0 I_1)|\psi \rangle, + +where :math:`|\psi \rangle` is an eigenvector of :math:`H'.` This means that the Hamiltonian +:math:`H` can be simplified as + +.. math:: H_{tapered} = \pm1 (Z_0 - I_0 + Y_0). + +The tapered Hamiltonian :math:`H_{tapered}` has the eigenvalues + +.. math:: [-2.41421, 0.41421], + +and + +.. math:: [2.41421, -0.41421], + +depending on the value of the :math:`\pm 1` prefactor. The eigenvalues of the original Hamiltonian +:math:`H` are + +.. math:: [2.41421, -2.41421, 0.41421, -0.41421], + +which are thus reproduced by the tapered Hamiltonian. + +More generally, we can construct the unitary :math:`U` such that each :math:`\mu_i` term acts with a +Pauli-X operator on a set of qubits +:math:`\left \{ j \right \}, j \in \left \{ l, ..., k \right \}` where :math:`j` is the qubit label. +This guarantees that each term of the transformed Hamiltonian commutes with each of the Pauli-X +operators applied to the :math:`j`-th qubit: + +.. math:: [H', X^j] = 0, + +and the eigenvectors of the transformed Hamiltonian :math:`H'` are also eigenvectors of each of the +:math:`X^{j}` operators. Then we can factor out all of the :math:`X^{j}` operators from the +transformed Hamiltonian and replace them with their eigenvalues :math:`\pm 1.` This gives us a +set of tapered Hamiltonians depending on which eigenvalue :math:`\pm 1` we chose for each of the +:math:`X^{j}` operators. For instance, in the case of two tapered qubits, we have four eigenvalue +sectors: :math:`[+1, +1]`, :math:`[-1, +1]`, :math:`[+1, -1],` :math:`[-1, -1].` In these tapered +Hamiltonians, the set of :math:`\left \{ j \right \}, j \in \left \{ l, ..., k \right \}` qubits +are eliminated. For tapered molecular Hamiltonians, it is possible to determine the optimal sector +of the eigenvalues that corresponds to the ground state. This is explained in more detail in the +following sections. + +The unitary operator :math:`U` can be constructed as a +`Clifford `__ operator [#bravyi2017]_ + +.. math:: U = \Pi_j \left [\frac{1}{\sqrt{2}} \left (X^{q(j)} + \tau_j \right) \right], + +where :math:`\tau` denotes the generators of the symmetry group of :math:`H` and +:math:`X^{q}` operators act on those qubits that will be ultimately tapered off from +the Hamiltonian. The symmetry group of the Hamiltonian is defined as an Abelian group of Pauli words that commute +with each term in the Hamiltonian (excluding :math:`−I`). The +`generators `__ of the symmetry group are +those elements of the group that can be combined, along with their inverses, to create any other +member of the group. + +Let's use the qubit tapering method and obtain the ground state energy of the `Helium hydride +cation `__ :math:`\textrm{HeH}^+.` + +Tapering the molecular Hamiltonian +---------------------------------- + +In PennyLane, a :doc:`molecular Hamiltonian ` can be created by specifying the atomic symbols and +coordinates. +""" +import pennylane as qml +from jax import numpy as jnp +import jax + +jax.config.update("jax_enable_x64", True) +symbols = ["He", "H"] +geometry = jnp.array([[0.00000000, 0.00000000, -0.87818361], + [0.00000000, 0.00000000, 0.87818362]]) + +molecule = qml.qchem.Molecule(symbols, geometry, charge=1) +H, qubits = qml.qchem.molecular_hamiltonian(molecule) +H + +############################################################################## +# This Hamiltonian contains 27 terms where each term acts on up to four qubits. +# +# We can now obtain the symmetry generators and the :math:`X^{j}` operators that are +# used to construct the unitary :math:`U` operator that transforms the :math:`\textrm{HeH}^+` +# Hamiltonian. In PennyLane, these are constructed by using the +# :func:`~.pennylane.symmetry_generators` and :func:`~.pennylane.paulix_ops` functions. + +generators = qml.symmetry_generators(H) +paulixops = qml.paulix_ops(generators, qubits) + +for idx, generator in enumerate(generators): + print(f"generator {idx+1}: {generator}, paulix_op: {paulixops[idx]}") + +############################################################################## +# Once the operator :math:`U` is applied, each of the Hamiltonian terms will act on the qubits +# :math:`q_2, q_3` either with the identity or with a Pauli-X operator. For each of these qubits, +# we can simply replace the Pauli-X operator with one of its eigenvalues :math:`+1` or :math:`-1.` +# This results in a total number of :math:`2^k` Hamiltonians, where :math:`k` is the number of +# tapered-off qubits and each Hamiltonian corresponds to one eigenvalue sector. The optimal sector +# corresponding to the ground-state energy of the molecule can be obtained by using the +# :func:`~.pennylane.qchem.optimal_sector` function. + + +n_electrons = 2 +paulix_sector = qml.qchem.optimal_sector(H, generators, n_electrons) +print(paulix_sector) + +############################################################################## +# The optimal eigenvalues are :math:`-1, -1` for qubits :math:`q_2, q_3,` respectively. We can now +# build the tapered Hamiltonian with the :func:`~.pennylane.taper` function which +# constructs the operator :math:`U,` applies it to the Hamiltonian and finally tapers off the +# qubits :math:`q_2, q_3` by replacing the Pauli-X operators acting on those qubits with the optimal +# eigenvalues. + +H_tapered = qml.taper(H, generators, paulixops, paulix_sector) +H_tapered_coeffs, H_tapered_ops = H_tapered.terms() +H_tapered = qml.Hamiltonian(jnp.real(jnp.array(H_tapered_coeffs)), H_tapered_ops) +print(H_tapered) + +############################################################################## +# The new Hamiltonian has only 9 non-zero terms acting on only 2 qubits! We can verify that the +# original and the tapered Hamiltonian both give the correct ground state energy of the +# :math:`\textrm{HeH}^+` cation, which is :math:`-2.862595242378` Ha computed with the full +# configuration interaction (FCI) method. In PennyLane, it's possible to build a sparse matrix +# representation of Hamiltonians. This allows us to directly diagonalize them to obtain exact values +# of the ground-state energies. + +H_sparse = qml.SparseHamiltonian(H.sparse_matrix(), wires=H.wires) +H_tapered_sparse = qml.SparseHamiltonian(H_tapered.sparse_matrix(), wires=H_tapered.wires) + +print("Eigenvalues of H:\n", qml.eigvals(H_sparse, k=16)) +print("\nEigenvalues of H_tapered:\n", qml.eigvals(H_tapered_sparse, k=4)) + +############################################################################## +# Note that a second-quantized Hamiltonian is independent of the number of electrons and its +# eigenspectrum contains the energies of the neutral and charged molecules. Therefore, the +# smallest eigenvalue returned by :func:`~.pennylane.eigvals` for a molecular Hamiltonian +# might correspond to the neutral or charged molecule. While in the case of :math:`\textrm{HeH}^+,` +# qubit tapering allows specifying the optimal sector of the eigenvectors corresponding only to the +# correct number of electrons, it is generally guaranteed that the optimal sector covers all +# eigenvectors with the correct number of electrons, but may contain additional eigenvectors of +# different charge. Therefore, the ground-state energy of the :math:`\textrm{HeH}^+` cation is +# the smallest eigenvalue of the tapered Hamiltonian. +# +# Tapering the reference state +# ---------------------------- +# The ground state Hartree-Fock energy of :math:`\textrm{HeH}^+` can be computed by directly +# applying the Hamiltonians to the Hartree-Fock state. For the tapered Hamiltonian, this requires +# transforming the Hartree-Fock state with the same symmetries obtained for the original +# Hamiltonian. This reduces the number of qubits in the Hartree-Fock state to match that of the +# tapered Hamiltonian. It can be done with the :func:`~.pennylane.qchem.taper_hf` function. + +state_tapered = qml.qchem.taper_hf(generators, paulixops, paulix_sector, + num_electrons=n_electrons, num_wires=len(H.wires)) +print(state_tapered) + +############################################################################## +# Recall that the original Hartree-Fock state for the :math:`\textrm{HeH}^+` cation is +# :math:`[1 1 0 0].` We can now generate the qubit representation of these states and compute the +# Hartree-Fock energies for each Hamiltonian. + +dev = qml.device("default.qubit", wires=H.wires) +@qml.qnode(dev, interface="jax") +def circuit(): + qml.BasisState(jnp.array([1, 1, 0, 0]), wires=H.wires) + return qml.state() + +qubit_state = circuit() +HF_energy = qubit_state.T @ H.sparse_matrix().toarray() @ qubit_state +print(f"HF energy: {jnp.real(HF_energy):.8f} Ha") + +dev = qml.device("lightning.qubit", wires=H_tapered.wires) +@qml.qnode(dev, interface="jax") +def circuit(): + qml.BasisState(jnp.array([1, 1]), wires=H_tapered.wires) + return qml.state() + +qubit_state = circuit() +HF_energy = qubit_state.T @ H_tapered.sparse_matrix().toarray() @ qubit_state +print(f"HF energy (tapered): {jnp.real(HF_energy):.8f} Ha") + +############################################################################## +# These values are identical to the reference Hartree-Fock energy :math:`-2.8543686493` Ha. +# +# VQE simulation +# -------------- +# Finally, we can use the tapered Hamiltonian and the tapered reference state to perform a VQE +# simulation and compute the ground-state energy of the :math:`\textrm{HeH}^+` cation. We build a +# tapered variational ansatz `[3] `__ +# that prepares an entangled state by evolving the tapered Hartree-Fock state using the tapered +# particle-conserving gates, i.e., the :func:`~.pennylane.SingleExcitation` and +# :func:`~.pennylane.DoubleExcitation` operations tapered using +# :func:`~.pennylane.qchem.taper_operation`. + +singles, doubles = qml.qchem.excitations(n_electrons, len(H.wires)) +tapered_doubles = [ + qml.taper_operation(qml.DoubleExcitation, generators, paulixops, paulix_sector, + wire_order=H.wires, op_wires=double) for double in doubles +] +tapered_singles = [ + qml.taper_operation(qml.SingleExcitation, generators, paulixops, paulix_sector, + wire_order=H.wires, op_wires=single) for single in singles +] + +dev = qml.device("lightning.qubit", wires=H_tapered.wires) + +@qml.qnode(dev, interface="jax") +def tapered_circuit(params): + qml.BasisState(state_tapered, wires=H_tapered.wires) + for idx, tapered_op in enumerate(tapered_doubles + tapered_singles): + tapered_op(params[idx]) + return qml.expval(H_tapered) + +############################################################################## +# We define an optimizer and the initial values of the circuit parameters and optimize the circuit +# parameters with respect to the ground state energy. + +import optax +import catalyst + +opt = optax.sgd(learning_rate=0.8) # sgd stands for StochasticGradientDescent +init_params = jnp.zeros(len(doubles) + len(singles)) + +def update_step(i, params, opt_state): + """Perform a single gradient update step""" + grads = catalyst.grad(tapered_circuit)(params) + updates, opt_state = opt.update(grads, opt_state) + params = optax.apply_updates(params, updates) + return (params, opt_state) + +loss_history = [] + +opt_state = opt.init(init_params) +params = init_params + +for i in range(1, 41): + params, opt_state = update_step(i, params, opt_state) + energy = tapered_circuit(params) + if not i % 5: + print(f"n: {i}, E: {energy:.8f} Ha, Params: {params}") + +############################################################################## +# The computed energy matches the FCI energy, :math:`-2.862595242378` Ha, while the number of qubits +# and the number of Hamiltonian terms are significantly reduced with respect to their original +# values. +# +# Conclusions +# ----------- +# Molecular Hamiltonians possess symmetries that can be leveraged to reduce the number of qubits +# required in quantum computing simulations. This tutorial introduces a PennyLane functionality that +# can be used for qubit tapering based on :math:`\mathbb{Z}_2` symmetries. The procedure includes +# obtaining tapered Hamiltonians and tapered reference states that can be used in variational +# quantum algorithms such as VQE. +# +# References +# ---------- +# +# .. [#bravyi2017] +# +# Sergey Bravyi, Jay M. Gambetta, Antonio Mezzacapo, Kristan Temme, "Tapering off qubits to +# simulate fermionic Hamiltonians". `arXiv:1701.08213 `__ +# +# .. [#setia2019] +# +# Kanav Setia, Richard Chen, Julia E. Rice, Antonio Mezzacapo, Marco Pistoia, James Whitfield, +# "Reducing qubit requirements for quantum simulation using molecular point group symmetries". +# `arXiv:1910.14644 `__ +# +# +# +# About the author +# ---------------- +# diff --git a/demonstrations/tutorial_qutrits_bernstein_vazirani.py b/demonstrations/tutorial_qutrits_bernstein_vazirani.py index 2664509a1f..e2da31eb6e 100644 --- a/demonstrations/tutorial_qutrits_bernstein_vazirani.py +++ b/demonstrations/tutorial_qutrits_bernstein_vazirani.py @@ -1,409 +1,409 @@ -r""" - -Qutrits and quantum algorithms -============================== - -.. meta:: - :property="og:description": Learn how to interpret the Bernstein-Vazirani algorithm with qutrits - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_qutrits_bernstein_vazirani.png - -.. related:: - - -*Author: Guillermo Alonso-Linaje — Posted: 9 May 2023. Last updated: 9 May 2023.* - -A qutrit is a basic quantum unit that can exist in a superposition of three possible quantum states, represented as :math:`|0\rangle`, :math:`|1\rangle`, and :math:`|2\rangle,` which functions as a generalization of the qubit. -There are many problems to which we can apply these units, among which we can highlight an improved decomposition of the Toffoli gate. -Using only qubits, it would take at least 6 CNOTs to decompose the gate, whereas with qutrits it would be enough to use 3 [#toffoli_qutrits]_. -This is one of the reasons why it is important to develop the intuition behind this basic unit of information, to see where qutrits can provide an advantage. The goal of this demo is to start working with qutrits from an algorithmic point of view. To do so, we will start with the Bernstein–Vazirani algorithm, which we will initially explore using qubits, and later using qutrits. - - - -Bernstein–Vazirani algorithm ------------------------------- - -The Bernstein–Vazirani algorithm is a quantum algorithm developed by Ethan Bernstein and Umesh Vazirani [#bv]_. -It was one of the first examples that demonstrated an exponential advantage of a quantum computer over a traditional one. So, in this first section we will understand the problem that they tackled. - - -Suppose there is some hidden bit string "a" that we are trying to learn, and that we have access to a function :math:`f(\vec{x})` that implements the following scalar product: - -.. math:: - f(\vec{x}) := \vec{a}\cdot\vec{x} \pmod 2, - -where :math:`\vec{a}=(a_0,a_1,...,a_{n-1})` and :math:`\vec{x}=(x_0,x_1,...,x_{n-1})` are bit strings of length :math:`n` with :math:`a_i, x_i \in \{0,1\}`. Our challenge will be to discover the hidden value of :math:`\vec{a}` by using the function :math:`f.` We don't know anything about :math:`\vec{a}` so the only thing we can do is to evaluate :math:`f` at different points :math:`\vec{x}` with the idea of gaining hidden information. - - -To give an example, let's imagine that we take :math:`\vec{x}=(1,0,1)` and get the value :math:`f(\vec{x}) = 0`. Although it may not seem obvious, knowing the structure that :math:`f` has, this gives us some information about :math:`\vec{a}.` In this case, :math:`a_0` and :math:`a_2` have the same value. This is because taking that value of :math:`\vec{x}`, the function will be equivalent to :math:`a_0 + a_2 \pmod 2,` which will only take the value 0 if they are equal. -I invite you to take your time to think of a possible strategy (at the classical level) in order to determine :math:`\vec{a}` with the minimum number of evaluations of the function :math:`f.` - -The optimal solution requires only :math:`n` calls to the function! Let's see how we can do this. -Knowing the form of :math:`\vec{a}` and :math:`\vec{x},` we can rewrite :math:`f` as: - -.. math:: - f(\vec{x})=\sum_{i=0}^{n-1}a_ix_i \pmod 2. - -The strategy will be to deduce one element of :math:`\vec{a}` with each call to the function. Imagine that we want to determine the value :math:`a_i.` We can simply choose :math:`\vec{x}` as a vector of all zeros except a one in the i-th position, since in this case: - - - -.. math:: - f(\vec{x})= 0\cdot a_0 + 0\cdot a_1 + ... + 1\cdot a_i + ... + 0\cdot a_{n-1} \pmod 2 \quad= a_i. - -It is trivial to see, therefore, that :math:`n` evaluations of :math:`f` are needed. The question is: can we do it more efficiently with a quantum computer? The answer is yes, and in fact, we only need to make one call to the function! - -The first step is to see how we can represent this statement in a circuit. In this case, we will assume that we have an oracle :math:`U_f` that encodes the function, as we can see in the picture below. - - -.. figure:: ../_static/demonstration_assets/qutrits_bernstein_vazirani/oracle_qutrit.jpg - :scale: 35% - :alt: Oracle definition. - :align: center - - Oracle representation of the function. - - -In general, :math:`U_f` sends the state :math:`|\vec{x} \rangle |y\rangle` to the state :math:`| \vec{x} \rangle |y + \vec{a} \cdot \vec{x} \pmod{2} \rangle.` - -Suppose, for example, that :math:`\vec{a}=[0,1,0]`. Then :math:`U_f|111\rangle |0\rangle = |111\rangle|1\rangle`, since we are evaluating :math:`f` at the point :math:`\vec{x} = [1,1,1]`. The scalar product between the two values is :math:`1,` so the last qubit of the output will take the value :math:`1.` - -The Bernstein–Vazirani algorithm makes use of this oracle according to the following circuit: - -.. figure:: ../_static/demonstration_assets/qutrits_bernstein_vazirani/bernstein_vazirani_algorithm.jpg - :scale: 35% - :alt: Bernstein-Vazirani's algorithm - :align: center - - Bernstein–Vazirani algorithm. - - -What we can see is that, by simply using Hadamard gates before and after the oracle, after a single run, the output of the circuit is exactly the hidden value of :math:`\vec{a}.` Let's do a little math to verify that this is so. - -First, the input to our circuit is :math:`|0001\rangle.` The second step is to apply Hadamard gates to this state, and for this we must use the following property: - -.. math:: - H^{\otimes n}|\vec{x}\rangle = \frac{1}{\sqrt{2^n}}\sum_{\vec{z} \in \{0,1\}^n}(-1)^{\vec{x}\cdot\vec{z}}|\vec{z}\rangle. - -Taking as input the value :math:`|0001\rangle,` we obtain the state - -.. math:: - |\phi_1\rangle=H^{\otimes 4}|0001\rangle = H^{\otimes 3}|000\rangle\otimes H|1\rangle = \frac{1}{\sqrt{2^3}}\left(\sum_{z \in \{0,1\}^3}|\vec{z}\rangle\right)\left(\frac{|0\rangle-|1\rangle}{\sqrt{2}}\right). - -As you can see, we have separated the first three qubits from the fourth for clarity. -If we now apply our operator :math:`U_f,` - -.. math:: - |\phi_2\rangle= U_f |\phi_1\rangle = \frac{1}{\sqrt{2^3}}\left(\sum_{\vec{z} \in \{0,1\}^3}|\vec{z}\rangle\frac{|\vec{a}\cdot\vec{z} \pmod 2\rangle-|1 + \vec{a}\cdot\vec{z} \pmod 2\rangle}{\sqrt{2}}\right). - -Depending on the value of :math:`f(\vec{x}),` the final part of the expression can take two values and it can be checked that - -.. math:: - |\phi_2\rangle = \frac{1}{\sqrt{2^3}}\left(\sum_{\vec{z} \in \{0,1\}^3}|\vec{z}\rangle(-1)^{\vec{a}\cdot\vec{z}}\frac{|0\rangle-|1\rangle}{\sqrt{2}}\right). - -This is because, if :math:`\vec{a}\cdot\vec{z}` takes the value :math:`0`, we will have the :math:`\frac{|0\rangle - |1\rangle}{\sqrt{2}}`, and if it takes the value :math:`1,` the result will be :math:`\frac{|1\rangle - |0\rangle}{\sqrt{2}} = - \frac{|0\rangle - |1\rangle}{\sqrt{2}}.` Therefore, by calculating :math:`(-1)^{\vec{a}\cdot\vec{z}}` we cover both cases. -After this, we can include the :math:`(-1)^{\vec{a}\cdot\vec{z}}` factor in the :math:`|\vec{z}\rangle` term and disregard the last qubit, since we are not going to use it again: - -.. math:: - |\phi_2\rangle =\frac{1}{\sqrt{2^3}}\sum_{\vec{z} \in \{0,1\}^3}(-1)^{\vec{a}\cdot\vec{z}}|\vec{z}\rangle. - -Note that you cannot always disregard a qubit. In cases where there is entanglement with other qubits that is not possible, but in this case the state is separable. - -Finally, we will reapply the property of the first step to calculate the result after using the Hadamard: - -.. math:: - |\phi_3\rangle = H^{\otimes 3}|\phi_2\rangle = \frac{1}{2^3}\sum_{\vec{z} \in \{0,1\}^3}(-1)^{\vec{a}\cdot\vec{z}}\left(\sum_{\vec{y} \in \{0,1\}^3}(-1)^{\vec{z}\cdot\vec{y}}|\vec{y}\rangle\right). - -Rearranging this expression, we obtain: - -.. math:: - |\phi_3\rangle = \frac{1}{2^3}\sum_{\vec{y} \in \{0,1\}^3}\left(\sum_{\vec{z} \in \{0,1\}^3}(-1)^{\vec{a}\cdot\vec{z}+\vec{y}\cdot\vec{z}}\right)|\vec{y}\rangle. - -Perfect! The only thing left to check is that, in fact, the previous state is exactly :math:`|\vec{a}\rangle`. It may seem complicated, but I invite you to demonstrate it by showing that :math:`\langle \vec{a}|\phi_3\rangle = 1.` Let's go to the code and check that it works. - -Algorithm coding with qubits ------------------------------- - -We will first code the classical solution. We will do this inside a quantum circuit to understand how to use the oracle, but we are really just programming the qubits as bits. - -""" - - -import pennylane as qml - -dev = qml.device("default.qubit", wires = 4, shots = 1) - -def Uf(): - # The oracle in charge of encoding a hidden "a" value. - qml.CNOT(wires=[1, 3]) - qml.CNOT(wires=[2 ,3]) - - -@qml.qnode(dev) -def circuit0(): - """Circuit used to derive a0""" - - - # Initialize x = [1,0,0] - qml.PauliX(wires = 0) - - # Apply our oracle - - Uf() - - # We measure the last qubit - return qml.sample(wires = 3) - -@qml.qnode(dev) -def circuit1(): - # Circuit used to derive a1 - - # Initialize x = [0,1,0] - qml.PauliX(wires = 1) - - # We apply our oracle - Uf() - - # We measure the last qubit - return qml.sample(wires = 3) - -@qml.qnode(dev) -def circuit2(): - # Circuit used to derive a2 - # Initialize x = [0,0,1] - qml.PauliX(wires = 2) - - # We apply our oracle - Uf() - - # We measure the last qubit - return qml.sample(wires = 3) - -# We run for x = [1,0,0] -a0 = circuit0() - -# We run for x = [0,1,0] -a1 = circuit1() - -# We run for x = [0,0,1] -a2 = circuit2() - -print(f"The value of 'a' is [{a0},{a1},{a2}]") - -############################################################################## -# -# In this case, with 3 queries (:math:`n=3`), we have discovered the value of :math:`\vec{a}.` Let's run the Bernstein–Vazirani subroutine (using qubits as qubits this time) to check that one call is enough: - - -@qml.qnode(dev) -def circuit(): - - # We initialize to |0001> - qml.PauliX(wires = 3) - - # We run the Hadamards - for i in range(4): - qml.Hadamard(wires = i) - - # We apply our function - Uf() - - # We run the Hadamards - for i in range(3): - qml.Hadamard(wires = i) - - # We measure the first 3 qubits - return qml.sample(wires = range(3)) - -a = circuit() - -print(f"The value of a is {a}") - - -############################################################################## -# Great! Everything works as expected, and we have successfully executed the Bernstein–Vazirani algorithm. -# It is important to note that, because of how we defined our device, we are only using a single shot to find this value! -# -# Generalization to qutrits -# ------------------------------ -# -# To make things more interesting, let's imagine a new scenario. We are given a function of the form :math:`f(\vec{x}) := \vec{a}\cdot\vec{x} \pmod 3` where, :math:`\vec{a}=(a_0,a_1,...,a_{n-1})` and :math:`\vec{x}=(x_0,x_1,...,x_{n-1})` are strings of length :math:`n` with :math:`a_i, x_i \in \{0,1,2\}`. How can we minimize the number of calls to the function to discover :math:`\vec{a}?` In this case, the classical procedure to detect the value of :math:`\vec{a}` is the same as in the case of qubits: we will evaluate the output of the inputs :math:`[1,0,0],` :math:`[0,1,0]` and :math:`[0,0,1].` -# -# But how can we work with these kinds of functions in a simple way? To do this we must use a qutrit and its operators. -# By using this new unit of information and unlocking the third orthogonal state, we will have states represented with a vector of dimension :math:`3^n` and the operators will be :math:`3^n \times 3^n` matrices where :math:`n` is the number of qutrits. -# Specifically, we will use the :class:`~.pennylane.TShift` gate, which is equivalent to the :class:`~.pennylane.PauliX` gate for qutrits. It has the following property: -# -# .. math:: -# \text{TShift}|0\rangle = |1\rangle -# -# .. math:: -# \text{TShift}|1\rangle = |2\rangle -# -# .. math:: -# \text{TShift}|2\rangle = |0\rangle -# -# This means we can use this gate to initialize each of the states. -# Another gate that we will use for the oracle definition is the :class:`~.pennylane.TAdd` gate, which is the generalization of the :class:`~.pennylane.Toffoli` gate for qutrits. -# These generalizations simply adjust the addition operation to be performed in modulo 3 instead of modulo 2. -# So, with these ingredients, we are ready to go to the code. - -dev = qml.device("default.qutrit", wires=4, shots=1) - -def Uf(): - # The oracle in charge of encoding a hidden "a" value. - qml.TAdd(wires = [1,3]) - qml.TAdd(wires = [1,3]) - qml.TAdd(wires = [2,3]) - -@qml.qnode(dev) -def circuit0(): - - # Initialize x = [1,0,0] - qml.TShift(wires = 0) - - # We apply our oracle - Uf() - - # We measure the last qutrit - return qml.sample(wires = 3) - -@qml.qnode(dev) -def circuit1(): - - # Initialize x = [0,1,0] - qml.TShift(wires = 1) - - # We apply our oracle - Uf() - - # We measure the last qutrit - return qml.sample(wires = 3) - -@qml.qnode(dev) -def circuit2(): - - # Initialize x = [0,0,1] - qml.TShift(wires = 2) - - # We apply our oracle - Uf() - - # We measure the last qutrit - return qml.sample(wires = 3) - -# Run to obtain the three trits of a -a0 = circuit0() -a1 = circuit1() -a2 = circuit2() - - -print(f"The value of a is [{a0},{a1},{a2}]") - -############################################################################## -# -# The question is, can we perform the same procedure as we have done before to find :math:`\vec{a}` using a single shot? The Hadamard gate also generalizes to qutrits (also denoted as :class:`~.pennylane.THadamard`), so we could try to simply substitute it and see what happens! -# -# -# The definition of the Hadamard gate in this space is: -# -# .. math:: -# \text{THadamard}=\frac{-i}{\sqrt{3}}\begin{pmatrix} -# 1 & 1 & 1\\ -# 1 & w & w^2\\ -# 1 & w^2 & w -# \end{pmatrix}, -# -# where :math:`w = e^{\frac{2 \pi i}{3}}.` -# Let's go to the code and see how to run this in PennyLane. - - -@qml.qnode(dev) -def circuit(): - - # We initialize to |0001> - qml.TShift(wires = 3) - - # We run the THadamard - for i in range(4): - qml.THadamard(wires = i) - -# We run the oracle - Uf() - -# We run the THadamard again - for i in range(3): - qml.THadamard(wires = i) - - # We measure the first 3 qutrits - return qml.sample(wires = range(3)) - -a = circuit() - -print(f"The value of a is {a}") - -############################################################################## -# -# Awesome! The Bernstein–Vazirani algorithm generalizes perfectly to qutrits! Let's do the mathematical calculations again to check that it does indeed make sense. -# -# As before, the input of our circuit is :math:`|0001\rangle.` -# We will then use the Hadamard definition applied to qutrits: -# -# .. math:: -# H^{\otimes n}|\vec{x}\rangle = \frac{1}{\sqrt{3^n}}\sum_{\vec{z} \in \{0,1,2\}^n}w^{\vec{x}\cdot\vec{z}}|\vec{z}\rangle. -# -# In this case, we are disregarding the global phase of :math:`-i` for simplicity. -# Applying this to the state :math:`|0001\rangle,` we obtain -# -# .. math:: -# |\phi_1\rangle=H^{\otimes 4}|0001\rangle = H^{\otimes 3}|000\rangle\otimes H|1\rangle = \frac{1}{\sqrt{3^3}}\left(\sum_{z \in \{0,1,2\}^3}|\vec{z}\rangle\frac{|0\rangle+w|1\rangle+w^2|2\rangle}{\sqrt{3}}\right). -# -# After that, we apply the operator :math:`U_f` to obtain -# -# .. math:: -# |\phi_2\rangle= U_f |\phi_1\rangle = \frac{1}{\sqrt{3^3}}\left(\sum_{\vec{z} \in \{0,1,2\}^3}|\vec{z}\rangle\frac{|0 + \vec{a}\cdot\vec{z} \pmod 3 \rangle+w|1+ \vec{a}\cdot\vec{z} \pmod 3 \rangle+w^2|2+ \vec{a}\cdot\vec{z} \pmod 3 \rangle}{\sqrt{3}}\right). -# -# Depending on the value of :math:`f(\vec{x}),` as before, we obtain three possible states: -# -# - If :math:`\vec{a}\cdot\vec{z} = 0,` we have :math:`\frac{1}{\sqrt{3}}\left(|0\rangle+w|1\rangle+w^2|2\rangle\right).` -# - If :math:`\vec{a}\cdot\vec{z} = 1,` we have :math:`\frac{w^2}{\sqrt{3}}\left(|0\rangle+|1\rangle+w|2\rangle\right).` -# - If :math:`\vec{a}\cdot\vec{z} = 2,` we have :math:`\frac{w}{\sqrt{3}}\left(|0\rangle+w^2|1\rangle+|2\rangle\right).` -# -# Based on this, we can group the three states as :math:`\frac{1}{\sqrt{3}}w^{-\vec{a}\cdot\vec{z}}\left(|0\rangle+w|1\rangle+w^2|2\rangle\right).` -# -# After this, we can enter the coefficient in the :math:`|\vec{z}\rangle` term and, as before, disregard the last qutrit, since we are not going to use it again: -# -# .. math:: -# |\phi_2\rangle =\frac{1}{\sqrt{3^3}}\sum_{\vec{z} \in \{0,1,2\}^3}w^{-\vec{a}\cdot\vec{z}}|\vec{z}\rangle. -# -# Finally, we reapply the THadamard: -# -# .. math:: -# |\phi_3\rangle := H^{\otimes 3}|\phi_2\rangle = \frac{1}{3^3}\sum_{\vec{z} \in \{0,1,2\}^3}w^{-\vec{a}\cdot\vec{z}}\left(\sum_{\vec{y} \in \{0,1,2\}^3}w^{\vec{z}\cdot\vec{y}}|\vec{y}\rangle\right). -# -# Rearranging this expression, we obtain: -# -# .. math:: -# |\phi_3\rangle = \frac{1}{3^3}\sum_{\vec{y} \in \{0,1,2\}^3}\left(\sum_{\vec{z} \in \{0,1,2\}^3}w^{-\vec{a}\cdot\vec{z}+\vec{y}\cdot\vec{z}}\right)|\vec{y}\rangle. -# -# -# In the same way as before, it can be easily checked that :math:`\langle \vec{a}|\phi_3\rangle = 1` and therefore, when measuring, one shot will be enough to obtain the value of :math:`\vec{a}!` -# -# Conclusion -# ---------- -# -# In this demo, we have practised the use of basic qutrit gates such as TShift or THadamard by applying the Bernstein–Vazirani algorithm. In this case, the generalization has been straightforward and we have found that it makes mathematical sense, but we cannot always substitute qubit gates for qutrit gates as we have seen in the demo. To give an easy example of this, we know the property that :math:`X = HZH`, but it turns out that this property does not generalize! The general property is actually :math:`X = H^{\dagger}ZH.` In the case of qubits it holds that :math:`H = H^{\dagger},` but in other dimensions it does not. I invite you to continue practising with other types of algorithms. For instance, will the `Deutsch–Jozsa algorithm `__ generalize well? Take a pen and paper and check it out! -# -# References -# ---------- -# -# .. [#bv] -# -# Ethan Bernstein, Umesh Vazirani, "Quantum Complexity Theory". `SIAM Journal on Computing Vol. 26 (1997). -# `__ -# -# .. [#toffoli_qutrits] -# Alexey Galda, Michael Cubeddu, Naoki Kanazawa, Prineha Narang, Nathan Earnest-Noble, `"Implementing a Ternary Decomposition of the Toffoli Gate on Fixed-FrequencyTransmon Qutrits". -# `__ -# About the author -# ---------------- -# - +r""" + +Qutrits and quantum algorithms +============================== + +.. meta:: + :property="og:description": Learn how to interpret the Bernstein-Vazirani algorithm with qutrits + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_qutrits_bernstein_vazirani.png + +.. related:: + + +*Author: Guillermo Alonso-Linaje — Posted: 9 May 2023. Last updated: 9 May 2023.* + +A qutrit is a basic quantum unit that can exist in a superposition of three possible quantum states, represented as :math:`|0\rangle`, :math:`|1\rangle`, and :math:`|2\rangle,` which functions as a generalization of the qubit. +There are many problems to which we can apply these units, among which we can highlight an improved decomposition of the Toffoli gate. +Using only qubits, it would take at least 6 CNOTs to decompose the gate, whereas with qutrits it would be enough to use 3 [#toffoli_qutrits]_. +This is one of the reasons why it is important to develop the intuition behind this basic unit of information, to see where qutrits can provide an advantage. The goal of this demo is to start working with qutrits from an algorithmic point of view. To do so, we will start with the Bernstein–Vazirani algorithm, which we will initially explore using qubits, and later using qutrits. + + + +Bernstein–Vazirani algorithm +------------------------------ + +The Bernstein–Vazirani algorithm is a quantum algorithm developed by Ethan Bernstein and Umesh Vazirani [#bv]_. +It was one of the first examples that demonstrated an exponential advantage of a quantum computer over a traditional one. So, in this first section we will understand the problem that they tackled. + + +Suppose there is some hidden bit string "a" that we are trying to learn, and that we have access to a function :math:`f(\vec{x})` that implements the following scalar product: + +.. math:: + f(\vec{x}) := \vec{a}\cdot\vec{x} \pmod 2, + +where :math:`\vec{a}=(a_0,a_1,...,a_{n-1})` and :math:`\vec{x}=(x_0,x_1,...,x_{n-1})` are bit strings of length :math:`n` with :math:`a_i, x_i \in \{0,1\}`. Our challenge will be to discover the hidden value of :math:`\vec{a}` by using the function :math:`f.` We don't know anything about :math:`\vec{a}` so the only thing we can do is to evaluate :math:`f` at different points :math:`\vec{x}` with the idea of gaining hidden information. + + +To give an example, let's imagine that we take :math:`\vec{x}=(1,0,1)` and get the value :math:`f(\vec{x}) = 0`. Although it may not seem obvious, knowing the structure that :math:`f` has, this gives us some information about :math:`\vec{a}.` In this case, :math:`a_0` and :math:`a_2` have the same value. This is because taking that value of :math:`\vec{x}`, the function will be equivalent to :math:`a_0 + a_2 \pmod 2,` which will only take the value 0 if they are equal. +I invite you to take your time to think of a possible strategy (at the classical level) in order to determine :math:`\vec{a}` with the minimum number of evaluations of the function :math:`f.` + +The optimal solution requires only :math:`n` calls to the function! Let's see how we can do this. +Knowing the form of :math:`\vec{a}` and :math:`\vec{x},` we can rewrite :math:`f` as: + +.. math:: + f(\vec{x})=\sum_{i=0}^{n-1}a_ix_i \pmod 2. + +The strategy will be to deduce one element of :math:`\vec{a}` with each call to the function. Imagine that we want to determine the value :math:`a_i.` We can simply choose :math:`\vec{x}` as a vector of all zeros except a one in the i-th position, since in this case: + + + +.. math:: + f(\vec{x})= 0\cdot a_0 + 0\cdot a_1 + ... + 1\cdot a_i + ... + 0\cdot a_{n-1} \pmod 2 \quad= a_i. + +It is trivial to see, therefore, that :math:`n` evaluations of :math:`f` are needed. The question is: can we do it more efficiently with a quantum computer? The answer is yes, and in fact, we only need to make one call to the function! + +The first step is to see how we can represent this statement in a circuit. In this case, we will assume that we have an oracle :math:`U_f` that encodes the function, as we can see in the picture below. + + +.. figure:: ../_static/demonstration_assets/qutrits_bernstein_vazirani/oracle_qutrit.jpg + :scale: 35% + :alt: Oracle definition. + :align: center + + Oracle representation of the function. + + +In general, :math:`U_f` sends the state :math:`|\vec{x} \rangle |y\rangle` to the state :math:`| \vec{x} \rangle |y + \vec{a} \cdot \vec{x} \pmod{2} \rangle.` + +Suppose, for example, that :math:`\vec{a}=[0,1,0]`. Then :math:`U_f|111\rangle |0\rangle = |111\rangle|1\rangle`, since we are evaluating :math:`f` at the point :math:`\vec{x} = [1,1,1]`. The scalar product between the two values is :math:`1,` so the last qubit of the output will take the value :math:`1.` + +The Bernstein–Vazirani algorithm makes use of this oracle according to the following circuit: + +.. figure:: ../_static/demonstration_assets/qutrits_bernstein_vazirani/bernstein_vazirani_algorithm.jpg + :scale: 35% + :alt: Bernstein-Vazirani's algorithm + :align: center + + Bernstein–Vazirani algorithm. + + +What we can see is that, by simply using Hadamard gates before and after the oracle, after a single run, the output of the circuit is exactly the hidden value of :math:`\vec{a}.` Let's do a little math to verify that this is so. + +First, the input to our circuit is :math:`|0001\rangle.` The second step is to apply Hadamard gates to this state, and for this we must use the following property: + +.. math:: + H^{\otimes n}|\vec{x}\rangle = \frac{1}{\sqrt{2^n}}\sum_{\vec{z} \in \{0,1\}^n}(-1)^{\vec{x}\cdot\vec{z}}|\vec{z}\rangle. + +Taking as input the value :math:`|0001\rangle,` we obtain the state + +.. math:: + |\phi_1\rangle=H^{\otimes 4}|0001\rangle = H^{\otimes 3}|000\rangle\otimes H|1\rangle = \frac{1}{\sqrt{2^3}}\left(\sum_{z \in \{0,1\}^3}|\vec{z}\rangle\right)\left(\frac{|0\rangle-|1\rangle}{\sqrt{2}}\right). + +As you can see, we have separated the first three qubits from the fourth for clarity. +If we now apply our operator :math:`U_f,` + +.. math:: + |\phi_2\rangle= U_f |\phi_1\rangle = \frac{1}{\sqrt{2^3}}\left(\sum_{\vec{z} \in \{0,1\}^3}|\vec{z}\rangle\frac{|\vec{a}\cdot\vec{z} \pmod 2\rangle-|1 + \vec{a}\cdot\vec{z} \pmod 2\rangle}{\sqrt{2}}\right). + +Depending on the value of :math:`f(\vec{x}),` the final part of the expression can take two values and it can be checked that + +.. math:: + |\phi_2\rangle = \frac{1}{\sqrt{2^3}}\left(\sum_{\vec{z} \in \{0,1\}^3}|\vec{z}\rangle(-1)^{\vec{a}\cdot\vec{z}}\frac{|0\rangle-|1\rangle}{\sqrt{2}}\right). + +This is because, if :math:`\vec{a}\cdot\vec{z}` takes the value :math:`0`, we will have the :math:`\frac{|0\rangle - |1\rangle}{\sqrt{2}}`, and if it takes the value :math:`1,` the result will be :math:`\frac{|1\rangle - |0\rangle}{\sqrt{2}} = - \frac{|0\rangle - |1\rangle}{\sqrt{2}}.` Therefore, by calculating :math:`(-1)^{\vec{a}\cdot\vec{z}}` we cover both cases. +After this, we can include the :math:`(-1)^{\vec{a}\cdot\vec{z}}` factor in the :math:`|\vec{z}\rangle` term and disregard the last qubit, since we are not going to use it again: + +.. math:: + |\phi_2\rangle =\frac{1}{\sqrt{2^3}}\sum_{\vec{z} \in \{0,1\}^3}(-1)^{\vec{a}\cdot\vec{z}}|\vec{z}\rangle. + +Note that you cannot always disregard a qubit. In cases where there is entanglement with other qubits that is not possible, but in this case the state is separable. + +Finally, we will reapply the property of the first step to calculate the result after using the Hadamard: + +.. math:: + |\phi_3\rangle = H^{\otimes 3}|\phi_2\rangle = \frac{1}{2^3}\sum_{\vec{z} \in \{0,1\}^3}(-1)^{\vec{a}\cdot\vec{z}}\left(\sum_{\vec{y} \in \{0,1\}^3}(-1)^{\vec{z}\cdot\vec{y}}|\vec{y}\rangle\right). + +Rearranging this expression, we obtain: + +.. math:: + |\phi_3\rangle = \frac{1}{2^3}\sum_{\vec{y} \in \{0,1\}^3}\left(\sum_{\vec{z} \in \{0,1\}^3}(-1)^{\vec{a}\cdot\vec{z}+\vec{y}\cdot\vec{z}}\right)|\vec{y}\rangle. + +Perfect! The only thing left to check is that, in fact, the previous state is exactly :math:`|\vec{a}\rangle`. It may seem complicated, but I invite you to demonstrate it by showing that :math:`\langle \vec{a}|\phi_3\rangle = 1.` Let's go to the code and check that it works. + +Algorithm coding with qubits +------------------------------ + +We will first code the classical solution. We will do this inside a quantum circuit to understand how to use the oracle, but we are really just programming the qubits as bits. + +""" + + +import pennylane as qml + +dev = qml.device("default.qubit", wires = 4, shots = 1) + +def Uf(): + # The oracle in charge of encoding a hidden "a" value. + qml.CNOT(wires=[1, 3]) + qml.CNOT(wires=[2 ,3]) + + +@qml.qnode(dev) +def circuit0(): + """Circuit used to derive a0""" + + + # Initialize x = [1,0,0] + qml.PauliX(wires = 0) + + # Apply our oracle + + Uf() + + # We measure the last qubit + return qml.sample(wires = 3) + +@qml.qnode(dev) +def circuit1(): + # Circuit used to derive a1 + + # Initialize x = [0,1,0] + qml.PauliX(wires = 1) + + # We apply our oracle + Uf() + + # We measure the last qubit + return qml.sample(wires = 3) + +@qml.qnode(dev) +def circuit2(): + # Circuit used to derive a2 + # Initialize x = [0,0,1] + qml.PauliX(wires = 2) + + # We apply our oracle + Uf() + + # We measure the last qubit + return qml.sample(wires = 3) + +# We run for x = [1,0,0] +a0 = circuit0() + +# We run for x = [0,1,0] +a1 = circuit1() + +# We run for x = [0,0,1] +a2 = circuit2() + +print(f"The value of 'a' is [{a0},{a1},{a2}]") + +############################################################################## +# +# In this case, with 3 queries (:math:`n=3`), we have discovered the value of :math:`\vec{a}.` Let's run the Bernstein–Vazirani subroutine (using qubits as qubits this time) to check that one call is enough: + + +@qml.qnode(dev) +def circuit(): + + # We initialize to |0001> + qml.PauliX(wires = 3) + + # We run the Hadamards + for i in range(4): + qml.Hadamard(wires = i) + + # We apply our function + Uf() + + # We run the Hadamards + for i in range(3): + qml.Hadamard(wires = i) + + # We measure the first 3 qubits + return qml.sample(wires = range(3)) + +a = circuit() + +print(f"The value of a is {a}") + + +############################################################################## +# Great! Everything works as expected, and we have successfully executed the Bernstein–Vazirani algorithm. +# It is important to note that, because of how we defined our device, we are only using a single shot to find this value! +# +# Generalization to qutrits +# ------------------------------ +# +# To make things more interesting, let's imagine a new scenario. We are given a function of the form :math:`f(\vec{x}) := \vec{a}\cdot\vec{x} \pmod 3` where, :math:`\vec{a}=(a_0,a_1,...,a_{n-1})` and :math:`\vec{x}=(x_0,x_1,...,x_{n-1})` are strings of length :math:`n` with :math:`a_i, x_i \in \{0,1,2\}`. How can we minimize the number of calls to the function to discover :math:`\vec{a}?` In this case, the classical procedure to detect the value of :math:`\vec{a}` is the same as in the case of qubits: we will evaluate the output of the inputs :math:`[1,0,0],` :math:`[0,1,0]` and :math:`[0,0,1].` +# +# But how can we work with these kinds of functions in a simple way? To do this we must use a qutrit and its operators. +# By using this new unit of information and unlocking the third orthogonal state, we will have states represented with a vector of dimension :math:`3^n` and the operators will be :math:`3^n \times 3^n` matrices where :math:`n` is the number of qutrits. +# Specifically, we will use the :class:`~.pennylane.TShift` gate, which is equivalent to the :class:`~.pennylane.PauliX` gate for qutrits. It has the following property: +# +# .. math:: +# \text{TShift}|0\rangle = |1\rangle +# +# .. math:: +# \text{TShift}|1\rangle = |2\rangle +# +# .. math:: +# \text{TShift}|2\rangle = |0\rangle +# +# This means we can use this gate to initialize each of the states. +# Another gate that we will use for the oracle definition is the :class:`~.pennylane.TAdd` gate, which is the generalization of the :class:`~.pennylane.Toffoli` gate for qutrits. +# These generalizations simply adjust the addition operation to be performed in modulo 3 instead of modulo 2. +# So, with these ingredients, we are ready to go to the code. + +dev = qml.device("default.qutrit", wires=4, shots=1) + +def Uf(): + # The oracle in charge of encoding a hidden "a" value. + qml.TAdd(wires = [1,3]) + qml.TAdd(wires = [1,3]) + qml.TAdd(wires = [2,3]) + +@qml.qnode(dev) +def circuit0(): + + # Initialize x = [1,0,0] + qml.TShift(wires = 0) + + # We apply our oracle + Uf() + + # We measure the last qutrit + return qml.sample(wires = 3) + +@qml.qnode(dev) +def circuit1(): + + # Initialize x = [0,1,0] + qml.TShift(wires = 1) + + # We apply our oracle + Uf() + + # We measure the last qutrit + return qml.sample(wires = 3) + +@qml.qnode(dev) +def circuit2(): + + # Initialize x = [0,0,1] + qml.TShift(wires = 2) + + # We apply our oracle + Uf() + + # We measure the last qutrit + return qml.sample(wires = 3) + +# Run to obtain the three trits of a +a0 = circuit0() +a1 = circuit1() +a2 = circuit2() + + +print(f"The value of a is [{a0},{a1},{a2}]") + +############################################################################## +# +# The question is, can we perform the same procedure as we have done before to find :math:`\vec{a}` using a single shot? The Hadamard gate also generalizes to qutrits (also denoted as :class:`~.pennylane.THadamard`), so we could try to simply substitute it and see what happens! +# +# +# The definition of the Hadamard gate in this space is: +# +# .. math:: +# \text{THadamard}=\frac{-i}{\sqrt{3}}\begin{pmatrix} +# 1 & 1 & 1\\ +# 1 & w & w^2\\ +# 1 & w^2 & w +# \end{pmatrix}, +# +# where :math:`w = e^{\frac{2 \pi i}{3}}.` +# Let's go to the code and see how to run this in PennyLane. + + +@qml.qnode(dev) +def circuit(): + + # We initialize to |0001> + qml.TShift(wires = 3) + + # We run the THadamard + for i in range(4): + qml.THadamard(wires = i) + +# We run the oracle + Uf() + +# We run the THadamard again + for i in range(3): + qml.THadamard(wires = i) + + # We measure the first 3 qutrits + return qml.sample(wires = range(3)) + +a = circuit() + +print(f"The value of a is {a}") + +############################################################################## +# +# Awesome! The Bernstein–Vazirani algorithm generalizes perfectly to qutrits! Let's do the mathematical calculations again to check that it does indeed make sense. +# +# As before, the input of our circuit is :math:`|0001\rangle.` +# We will then use the Hadamard definition applied to qutrits: +# +# .. math:: +# H^{\otimes n}|\vec{x}\rangle = \frac{1}{\sqrt{3^n}}\sum_{\vec{z} \in \{0,1,2\}^n}w^{\vec{x}\cdot\vec{z}}|\vec{z}\rangle. +# +# In this case, we are disregarding the global phase of :math:`-i` for simplicity. +# Applying this to the state :math:`|0001\rangle,` we obtain +# +# .. math:: +# |\phi_1\rangle=H^{\otimes 4}|0001\rangle = H^{\otimes 3}|000\rangle\otimes H|1\rangle = \frac{1}{\sqrt{3^3}}\left(\sum_{z \in \{0,1,2\}^3}|\vec{z}\rangle\frac{|0\rangle+w|1\rangle+w^2|2\rangle}{\sqrt{3}}\right). +# +# After that, we apply the operator :math:`U_f` to obtain +# +# .. math:: +# |\phi_2\rangle= U_f |\phi_1\rangle = \frac{1}{\sqrt{3^3}}\left(\sum_{\vec{z} \in \{0,1,2\}^3}|\vec{z}\rangle\frac{|0 + \vec{a}\cdot\vec{z} \pmod 3 \rangle+w|1+ \vec{a}\cdot\vec{z} \pmod 3 \rangle+w^2|2+ \vec{a}\cdot\vec{z} \pmod 3 \rangle}{\sqrt{3}}\right). +# +# Depending on the value of :math:`f(\vec{x}),` as before, we obtain three possible states: +# +# - If :math:`\vec{a}\cdot\vec{z} = 0,` we have :math:`\frac{1}{\sqrt{3}}\left(|0\rangle+w|1\rangle+w^2|2\rangle\right).` +# - If :math:`\vec{a}\cdot\vec{z} = 1,` we have :math:`\frac{w^2}{\sqrt{3}}\left(|0\rangle+|1\rangle+w|2\rangle\right).` +# - If :math:`\vec{a}\cdot\vec{z} = 2,` we have :math:`\frac{w}{\sqrt{3}}\left(|0\rangle+w^2|1\rangle+|2\rangle\right).` +# +# Based on this, we can group the three states as :math:`\frac{1}{\sqrt{3}}w^{-\vec{a}\cdot\vec{z}}\left(|0\rangle+w|1\rangle+w^2|2\rangle\right).` +# +# After this, we can enter the coefficient in the :math:`|\vec{z}\rangle` term and, as before, disregard the last qutrit, since we are not going to use it again: +# +# .. math:: +# |\phi_2\rangle =\frac{1}{\sqrt{3^3}}\sum_{\vec{z} \in \{0,1,2\}^3}w^{-\vec{a}\cdot\vec{z}}|\vec{z}\rangle. +# +# Finally, we reapply the THadamard: +# +# .. math:: +# |\phi_3\rangle := H^{\otimes 3}|\phi_2\rangle = \frac{1}{3^3}\sum_{\vec{z} \in \{0,1,2\}^3}w^{-\vec{a}\cdot\vec{z}}\left(\sum_{\vec{y} \in \{0,1,2\}^3}w^{\vec{z}\cdot\vec{y}}|\vec{y}\rangle\right). +# +# Rearranging this expression, we obtain: +# +# .. math:: +# |\phi_3\rangle = \frac{1}{3^3}\sum_{\vec{y} \in \{0,1,2\}^3}\left(\sum_{\vec{z} \in \{0,1,2\}^3}w^{-\vec{a}\cdot\vec{z}+\vec{y}\cdot\vec{z}}\right)|\vec{y}\rangle. +# +# +# In the same way as before, it can be easily checked that :math:`\langle \vec{a}|\phi_3\rangle = 1` and therefore, when measuring, one shot will be enough to obtain the value of :math:`\vec{a}!` +# +# Conclusion +# ---------- +# +# In this demo, we have practised the use of basic qutrit gates such as TShift or THadamard by applying the Bernstein–Vazirani algorithm. In this case, the generalization has been straightforward and we have found that it makes mathematical sense, but we cannot always substitute qubit gates for qutrit gates as we have seen in the demo. To give an easy example of this, we know the property that :math:`X = HZH`, but it turns out that this property does not generalize! The general property is actually :math:`X = H^{\dagger}ZH.` In the case of qubits it holds that :math:`H = H^{\dagger},` but in other dimensions it does not. I invite you to continue practising with other types of algorithms. For instance, will the `Deutsch–Jozsa algorithm `__ generalize well? Take a pen and paper and check it out! +# +# References +# ---------- +# +# .. [#bv] +# +# Ethan Bernstein, Umesh Vazirani, "Quantum Complexity Theory". `SIAM Journal on Computing Vol. 26 (1997). +# `__ +# +# .. [#toffoli_qutrits] +# Alexey Galda, Michael Cubeddu, Naoki Kanazawa, Prineha Narang, Nathan Earnest-Noble, `"Implementing a Ternary Decomposition of the Toffoli Gate on Fixed-FrequencyTransmon Qutrits". +# `__ +# About the author +# ---------------- +# + diff --git a/demonstrations/tutorial_resource_estimation.py b/demonstrations/tutorial_resource_estimation.py index 8deb382e9c..0c033e4dee 100644 --- a/demonstrations/tutorial_resource_estimation.py +++ b/demonstrations/tutorial_resource_estimation.py @@ -1,321 +1,321 @@ -r""" - -Resource estimation for quantum chemistry -========================================= - -.. meta:: - :property="og:description": Learn how to estimate the number of qubits and gates needed to - implement quantum algorithms - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/resource_estimation.jpeg - -.. related:: - tutorial_quantum_chemistry Quantum chemistry with PennyLane - tutorial_vqe A brief overview of VQE - - -*Author: Soran Jahangiri — Posted: 21 November 2022.* - -Quantum algorithms such as -`quantum phase estimation `_ -(QPE) and the `variational quantum eigensolver `_ (VQE) -are widely studied in quantum chemistry as potential avenues to tackle problems that are intractable -for conventional computers. However, we currently do not have quantum computers or simulators -capable of implementing large-scale -versions of these algorithms. This makes it difficult to properly explore their accuracy and -efficiency for problem sizes where the actual advantage of quantum algorithms can potentially occur. -Despite these difficulties, it is still possible to perform **resource estimation** -to assess what we need to implement such quantum algorithms. - -In this demo, we describe how to use PennyLane's resource estimation functionality to estimate the total number of logical qubits and gates required to -implement the QPE algorithm for simulating molecular Hamiltonians represented in first and second -quantization. We focus on `non-Clifford gates `_, which -are the most expensive to implement in a fault-tolerant setting. We also explain how to estimate the -total number of measurements needed to compute expectation values using algorithms such as VQE. - -Quantum Phase Estimation ------------------------- -The QPE algorithm can be used to compute the phase associated with an eigenstate of a unitary -operator. For the purpose of quantum simulation, the unitary operator :math:`U` can be chosen to -share eigenvectors with a molecular Hamiltonian :math:`H,` for example by setting -:math:`U = e^{-iH}.` Estimating the phase of such a unitary then permits recovering the -corresponding eigenvalue of the Hamiltonian. A conceptual QPE circuit diagram is shown below. - -.. figure:: /_static/demonstration_assets/resource_estimation/qpe.png - :width: 60% - :align: center - - Circuit representing the quantum phase estimation algorithm. - -For most cases of interest, this algorithm requires more qubits and longer circuit depths than what -can be implemented on existing hardware. The PennyLane functionality in the -:mod:`qml.resource ` module allows us to estimate the number of logical qubits -and the number of non-Clifford gates that are needed to implement the algorithm. We can estimate -these resources by simply defining system specifications and a target error for estimation. Let's -see how! - -QPE cost for simulating molecules -********************************* -We study the double low-rank Hamiltonian factorization algorithm of [#vonburg2021]_ and use its cost -equations as provided in APPENDIX C of [#lee2021]_. -This algorithm requires the one- and two-electron -`integrals `_ -as input. These integrals can be obtained in different ways and here we use PennyLane to compute -them. We first need to define the atomic symbols and coordinates for the given molecule. Let's use -the water molecule at its equilibrium geometry with the -`6-31g basis set `_ as an example. -""" -import pennylane as qml -import numpy as np -import jax - -jax.config.update("jax_enable_x64", True) - -symbols = ['O', 'H', 'H'] -geometry = np.array([[0.00000000, 0.00000000, 0.28377432], - [0.00000000, 1.45278171, -1.00662237], - [0.00000000, -1.45278171, -1.00662237]]) - -############################################################################## -# Then we construct a molecule object and compute the one- and two-electron -# integrals in the molecular orbital basis. - -mol = qml.qchem.Molecule(symbols, geometry, basis_name='6-31g') -core, one, two = qml.qchem.electron_integrals(mol)() - -############################################################################## -# We now create an instance of the :class:`~.pennylane.resource.DoubleFactorization` class - -algo = qml.resource.DoubleFactorization(one, two) - -############################################################################## -# and obtain the estimated number of non-Clifford gates and logical qubits. - -print(f'Estimated gates : {algo.gates:.2e} \nEstimated qubits: {algo.qubits}') - -############################################################################## -# This estimation is for a target error that is set to the chemical accuracy, 0.0016 -# :math:`\text{Ha},` by default. We can change the target error to a larger value which leads to a -# smaller number of non-Clifford gates and logical qubits. - -chemical_accuracy = 0.0016 -error = chemical_accuracy * 10 -algo = qml.resource.DoubleFactorization(one, two, error=error) -print(f'Estimated gates : {algo.gates:.2e} \nEstimated qubits: {algo.qubits}') - -############################################################################## -# We can also estimate the number of non-Clifford gates with respect to the threshold error values -# for discarding the negligible factors in the factorized Hamiltonian [#vonburg2021]_ and plot the -# estimated numbers. - -threshold = [10**-n for n in range(10)] -n_gates = [] -n_qubits = [] - -for tol in threshold: - algo_ = qml.resource.DoubleFactorization(one, two, tol_factor=tol, tol_eigval=tol) - n_gates.append(algo_.gates) - n_qubits.append(algo_.qubits) - -import matplotlib.pyplot as plt - -fig, ax = plt.subplots(figsize=(5, 3)) -ax.plot(threshold, n_gates, ':o', markerfacecolor='none', color='teal') - -ax.set_ylabel('n gates') -ax.set_xlabel('threshold') -ax.set_xscale('log') -fig.tight_layout() - -############################################################################## -# QPE cost for simulating periodic materials -# ****************************************** -# For periodic materials, we estimate the cost of implementing the QPE algorithm of [#zini2023]_ -# using Hamiltonians represented in first quantization and in a plane wave basis. We first need to -# define the number of plane waves, the number of electrons, and the lattice vectors that construct -# the unit cell of the periodic material. Let's use dilithium iron silicate -# :math:`\text{Li}_2\text{FeSiO}_4` as an example taken from [#delgado2022]_. For this material, the -# unit cell contains 156 electrons and has dimensions :math:`9.49 \times 10.20 \times 11.83` in -# `atomic units `_. We also use :math:`10^5` plane waves. - -planewaves = 100000 -electrons = 156 -vectors = np.array([[9.49, 0.00, 0.00], - [0.00, 10.20, 0.00], - [0.00, 0.00, 11.83]]) - -############################################################################## -# We now create an instance of the :class:`~.pennylane.resource.FirstQuantization` class -algo = qml.resource.FirstQuantization(planewaves, electrons, vectors=vectors) - -############################################################################## -# and obtain the estimated number of non-Clifford gates and logical qubits. -print(f'Estimated gates : {algo.gates:.2e} \nEstimated qubits: {algo.qubits}') - -############################################################################## -# We can also plot the estimated numbers as a function of the number of plane waves for different -# target errors - -error = [0.1, 0.01, 0.001] # in atomic units -planewaves = [10 ** n for n in range(1, 10)] -n_gates = [] -n_qubits = [] - -for er in error: - n_gates_ = [] - n_qubits_ = [] - - for pw in planewaves: - algo_ = qml.resource.FirstQuantization(pw, electrons, vectors=vectors, error=er) - n_gates_.append(algo_.gates) - n_qubits_.append(algo_.qubits) - n_gates.append(n_gates_) - n_qubits.append(n_qubits_) - -fig, ax = plt.subplots(2, 1) - -for i in range(len(n_gates)): - ax[0].plot(planewaves, n_gates[i], ':o', markerfacecolor='none', label=error[i]) -ax[1].plot(planewaves, n_qubits[i], ':o', markerfacecolor='none', label=error[-1]) - -ax[0].set_ylabel('n gates') -ax[1].set_ylabel('n qubits') - -for i in [0, 1]: - ax[i].set_xlabel('n planewaves') - ax[i].tick_params(axis='x') - ax[0].set_yscale('log') - ax[i].set_xscale('log') - ax[i].legend(title='error [Ha]') - -fig.tight_layout() - -############################################################################## -# The algorithm uses a decomposition of the Hamiltonian as a linear combination of unitaries, -# -# .. math:: H=\sum_{i} c_i U_i. -# -# The parameter :math:`\lambda=\sum_i c_i,` which can be interpreted as the 1-norm of the -# Hamiltonian, plays an important role in determining the cost of implementing the QPE -# algorithm [#delgado2022]_. In PennyLane, the 1-norm of the Hamiltonian can be obtained with - -print(f'1-norm of the Hamiltonian: {algo.lamb}') - -############################################################################## -# PennyLane allows you to get more detailed information about the cost of the algorithms as -# explained in the documentation of :class:`~.pennylane.resource.FirstQuantization` -# and :class:`~.pennylane.resource.DoubleFactorization` classes. -# -# Variational quantum eigensolver -# ------------------------------------------ -# In variational quantum algorithms such as VQE, the expectation value of an observable is -# typically computed by decomposing the observable into a linear combination of Pauli words, -# which are tensor products of Pauli and Identity operators. The expectation values are calculated -# through linearity by measuring the expectation value for each of these terms and combining the -# results. The number of qubits required for the measurement is trivially determined by -# the number of qubits the observable acts on. The number of gates required to implement the -# variational algorithm is determined by a circuit ansatz that is also known a priori. However, -# estimating the number of circuit evaluations, i.e. the number of shots, required to achieve a -# certain error in computing the expectation value is not as straightforward. Let's now use -# PennyLane to estimate the number of shots needed to compute the expectation value of the water -# Hamiltonian. -# -# First, we construct the molecular Hamiltonian. - -molecule = qml.qchem.Molecule(symbols, geometry) -H = qml.qchem.molecular_hamiltonian(molecule)[0] -H_coeffs, H_ops = H.terms() - -############################################################################## -# The number of measurements needed to compute :math:`\left \langle H \right \rangle` can be -# obtained with the :func:`~.pennylane.resource.estimate_shots` function, which requires the -# Hamiltonian coefficients as input. The number of measurements required to compute -# :math:`\left \langle H \right \rangle` with a target error set to the chemical accuracy, 0.0016 -# :math:`\text{Ha},` is obtained as follows. - -m = qml.resource.estimate_shots(H_coeffs) -print(f'Shots : {m:.2e}') - -############################################################################## -# This number corresponds to the measurement process where each term in the Hamiltonian is measured -# independently. The number can be reduced by using -# :func:`~.pennylane.pauli.group_observables()`, which partitions the Pauli words into -# groups of commuting terms that can be measured simultaneously. - -ops, coeffs = qml.pauli.group_observables(H_ops, H_coeffs) -coeffs = [np.array(c) for c in coeffs] # cast as numpy array - -m = qml.resource.estimate_shots(coeffs) -print(f'Shots : {m:.2e}') - -############################################################################## -# It is also interesting to illustrate how the number of shots depends on the target error. - -error = np.array([0.02, 0.015, 0.01, 0.005, 0.001]) -m = [qml.resource.estimate_shots(H_coeffs, error=er) for er in error] - -e_ = np.linspace(error[0], error[-1], num=50) -m_ = 1.4e4 / np.linspace(error[0], error[-1], num=50)**2 - -fig, ax = plt.subplots(figsize=(5, 3)) -ax.plot(error, m, 'o', markerfacecolor='none', color='teal', label='estimated') -ax.plot(e_, m_, ':', markerfacecolor='none', color='olive', label='$ 1.4e4 * 1/\epsilon^2 $') - -ax.set_ylabel('shots') -ax.set_xlabel('error [Ha]') -ax.set_yscale('log') -ax.tick_params(axis='x', labelrotation = 90) -ax.legend() -fig.tight_layout() - -############################################################################## -# We have added a line showing the dependency of the shots to the error, as -# :math:`\text{shots} = 1.4\text{e}4 \times 1/\epsilon^2,` for comparison. Can you draw any -# interesting information form the plot? -# -# Conclusions -# ----------- -# This tutorial shows how to use the resource estimation functionality in PennyLane to compute the -# total number of non-Clifford gates and logical qubits required to simulate a Hamiltonian with -# quantum phase estimation algorithms. The estimation can be performed for second-quantized -# molecular Hamiltonians obtained with a double low-rank factorization algorithm, -# and first-quantized Hamiltonians of periodic materials in the plane wave basis. We also discussed -# the estimation of the total number of shots required to obtain the expectation value of an -# observable using the variational quantum eigensolver algorithm. The functionality allows one to -# obtain interesting results about the cost of implementing important quantum algorithms. For -# instance, we estimated the costs with respect to factors such as the target error in obtaining -# energies and the number of basis functions used to simulate a system. Can you think of other -# interesting information that can be obtained using this PennyLane functionality? -# -# References -# ---------- -# -# .. [#vonburg2021] -# -# Vera von Burg, Guang Hao Low, Thomas Haner, Damian S. Steiger, *et al.*, -# "Quantum computing enhanced computational catalysis". -# `Phys. Rev. Research 3, 033055 (2021) -# `__ -# -# .. [#lee2021] -# -# Joonho Lee, Dominic W. Berry, Craig Gidney, William J. Huggins, *et al.*, -# "Even More Efficient Quantum Computations of Chemistry Through Tensor Hypercontraction". -# `PRX Quantum 2, 030305 (2021) -# `__ -# -# .. [#zini2023] -# -# Modjtaba Shokrian Zini, Alain Delgado, Roberto dos Reis, Pablo A. M. Casares, -# Jonathan E. Mueller, Arne-Christian Voigt, Juan Miguel Arrazola, -# "Quantum simulation of battery materials using ionic pseudopotentials". -# `Quantum 7, 1049 (2023) `__ -# -# .. [#delgado2022] -# -# Alain Delgado, Pablo A. M. Casares, Roberto dos Reis, Modjtaba Shokrian Zini, *et al.*, -# "Simulating key properties of lithium-ion batteries with a fault-tolerant quantum computer". -# `Phys. Rev. A 106, 032428 (2022) -# `__ -# About the author -# ---------------- -# +r""" + +Resource estimation for quantum chemistry +========================================= + +.. meta:: + :property="og:description": Learn how to estimate the number of qubits and gates needed to + implement quantum algorithms + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/resource_estimation.jpeg + +.. related:: + tutorial_quantum_chemistry Quantum chemistry with PennyLane + tutorial_vqe A brief overview of VQE + + +*Author: Soran Jahangiri — Posted: 21 November 2022.* + +Quantum algorithms such as +`quantum phase estimation `_ +(QPE) and the `variational quantum eigensolver `_ (VQE) +are widely studied in quantum chemistry as potential avenues to tackle problems that are intractable +for conventional computers. However, we currently do not have quantum computers or simulators +capable of implementing large-scale +versions of these algorithms. This makes it difficult to properly explore their accuracy and +efficiency for problem sizes where the actual advantage of quantum algorithms can potentially occur. +Despite these difficulties, it is still possible to perform **resource estimation** +to assess what we need to implement such quantum algorithms. + +In this demo, we describe how to use PennyLane's resource estimation functionality to estimate the total number of logical qubits and gates required to +implement the QPE algorithm for simulating molecular Hamiltonians represented in first and second +quantization. We focus on `non-Clifford gates `_, which +are the most expensive to implement in a fault-tolerant setting. We also explain how to estimate the +total number of measurements needed to compute expectation values using algorithms such as VQE. + +Quantum Phase Estimation +------------------------ +The QPE algorithm can be used to compute the phase associated with an eigenstate of a unitary +operator. For the purpose of quantum simulation, the unitary operator :math:`U` can be chosen to +share eigenvectors with a molecular Hamiltonian :math:`H,` for example by setting +:math:`U = e^{-iH}.` Estimating the phase of such a unitary then permits recovering the +corresponding eigenvalue of the Hamiltonian. A conceptual QPE circuit diagram is shown below. + +.. figure:: /_static/demonstration_assets/resource_estimation/qpe.png + :width: 60% + :align: center + + Circuit representing the quantum phase estimation algorithm. + +For most cases of interest, this algorithm requires more qubits and longer circuit depths than what +can be implemented on existing hardware. The PennyLane functionality in the +:mod:`qml.resource ` module allows us to estimate the number of logical qubits +and the number of non-Clifford gates that are needed to implement the algorithm. We can estimate +these resources by simply defining system specifications and a target error for estimation. Let's +see how! + +QPE cost for simulating molecules +********************************* +We study the double low-rank Hamiltonian factorization algorithm of [#vonburg2021]_ and use its cost +equations as provided in APPENDIX C of [#lee2021]_. +This algorithm requires the one- and two-electron +`integrals `_ +as input. These integrals can be obtained in different ways and here we use PennyLane to compute +them. We first need to define the atomic symbols and coordinates for the given molecule. Let's use +the water molecule at its equilibrium geometry with the +`6-31g basis set `_ as an example. +""" +import pennylane as qml +import numpy as np +import jax + +jax.config.update("jax_enable_x64", True) + +symbols = ['O', 'H', 'H'] +geometry = np.array([[0.00000000, 0.00000000, 0.28377432], + [0.00000000, 1.45278171, -1.00662237], + [0.00000000, -1.45278171, -1.00662237]]) + +############################################################################## +# Then we construct a molecule object and compute the one- and two-electron +# integrals in the molecular orbital basis. + +mol = qml.qchem.Molecule(symbols, geometry, basis_name='6-31g') +core, one, two = qml.qchem.electron_integrals(mol)() + +############################################################################## +# We now create an instance of the :class:`~.pennylane.resource.DoubleFactorization` class + +algo = qml.resource.DoubleFactorization(one, two) + +############################################################################## +# and obtain the estimated number of non-Clifford gates and logical qubits. + +print(f'Estimated gates : {algo.gates:.2e} \nEstimated qubits: {algo.qubits}') + +############################################################################## +# This estimation is for a target error that is set to the chemical accuracy, 0.0016 +# :math:`\text{Ha},` by default. We can change the target error to a larger value which leads to a +# smaller number of non-Clifford gates and logical qubits. + +chemical_accuracy = 0.0016 +error = chemical_accuracy * 10 +algo = qml.resource.DoubleFactorization(one, two, error=error) +print(f'Estimated gates : {algo.gates:.2e} \nEstimated qubits: {algo.qubits}') + +############################################################################## +# We can also estimate the number of non-Clifford gates with respect to the threshold error values +# for discarding the negligible factors in the factorized Hamiltonian [#vonburg2021]_ and plot the +# estimated numbers. + +threshold = [10**-n for n in range(10)] +n_gates = [] +n_qubits = [] + +for tol in threshold: + algo_ = qml.resource.DoubleFactorization(one, two, tol_factor=tol, tol_eigval=tol) + n_gates.append(algo_.gates) + n_qubits.append(algo_.qubits) + +import matplotlib.pyplot as plt + +fig, ax = plt.subplots(figsize=(5, 3)) +ax.plot(threshold, n_gates, ':o', markerfacecolor='none', color='teal') + +ax.set_ylabel('n gates') +ax.set_xlabel('threshold') +ax.set_xscale('log') +fig.tight_layout() + +############################################################################## +# QPE cost for simulating periodic materials +# ****************************************** +# For periodic materials, we estimate the cost of implementing the QPE algorithm of [#zini2023]_ +# using Hamiltonians represented in first quantization and in a plane wave basis. We first need to +# define the number of plane waves, the number of electrons, and the lattice vectors that construct +# the unit cell of the periodic material. Let's use dilithium iron silicate +# :math:`\text{Li}_2\text{FeSiO}_4` as an example taken from [#delgado2022]_. For this material, the +# unit cell contains 156 electrons and has dimensions :math:`9.49 \times 10.20 \times 11.83` in +# `atomic units `_. We also use :math:`10^5` plane waves. + +planewaves = 100000 +electrons = 156 +vectors = np.array([[9.49, 0.00, 0.00], + [0.00, 10.20, 0.00], + [0.00, 0.00, 11.83]]) + +############################################################################## +# We now create an instance of the :class:`~.pennylane.resource.FirstQuantization` class +algo = qml.resource.FirstQuantization(planewaves, electrons, vectors=vectors) + +############################################################################## +# and obtain the estimated number of non-Clifford gates and logical qubits. +print(f'Estimated gates : {algo.gates:.2e} \nEstimated qubits: {algo.qubits}') + +############################################################################## +# We can also plot the estimated numbers as a function of the number of plane waves for different +# target errors + +error = [0.1, 0.01, 0.001] # in atomic units +planewaves = [10 ** n for n in range(1, 10)] +n_gates = [] +n_qubits = [] + +for er in error: + n_gates_ = [] + n_qubits_ = [] + + for pw in planewaves: + algo_ = qml.resource.FirstQuantization(pw, electrons, vectors=vectors, error=er) + n_gates_.append(algo_.gates) + n_qubits_.append(algo_.qubits) + n_gates.append(n_gates_) + n_qubits.append(n_qubits_) + +fig, ax = plt.subplots(2, 1) + +for i in range(len(n_gates)): + ax[0].plot(planewaves, n_gates[i], ':o', markerfacecolor='none', label=error[i]) +ax[1].plot(planewaves, n_qubits[i], ':o', markerfacecolor='none', label=error[-1]) + +ax[0].set_ylabel('n gates') +ax[1].set_ylabel('n qubits') + +for i in [0, 1]: + ax[i].set_xlabel('n planewaves') + ax[i].tick_params(axis='x') + ax[0].set_yscale('log') + ax[i].set_xscale('log') + ax[i].legend(title='error [Ha]') + +fig.tight_layout() + +############################################################################## +# The algorithm uses a decomposition of the Hamiltonian as a linear combination of unitaries, +# +# .. math:: H=\sum_{i} c_i U_i. +# +# The parameter :math:`\lambda=\sum_i c_i,` which can be interpreted as the 1-norm of the +# Hamiltonian, plays an important role in determining the cost of implementing the QPE +# algorithm [#delgado2022]_. In PennyLane, the 1-norm of the Hamiltonian can be obtained with + +print(f'1-norm of the Hamiltonian: {algo.lamb}') + +############################################################################## +# PennyLane allows you to get more detailed information about the cost of the algorithms as +# explained in the documentation of :class:`~.pennylane.resource.FirstQuantization` +# and :class:`~.pennylane.resource.DoubleFactorization` classes. +# +# Variational quantum eigensolver +# ------------------------------------------ +# In variational quantum algorithms such as VQE, the expectation value of an observable is +# typically computed by decomposing the observable into a linear combination of Pauli words, +# which are tensor products of Pauli and Identity operators. The expectation values are calculated +# through linearity by measuring the expectation value for each of these terms and combining the +# results. The number of qubits required for the measurement is trivially determined by +# the number of qubits the observable acts on. The number of gates required to implement the +# variational algorithm is determined by a circuit ansatz that is also known a priori. However, +# estimating the number of circuit evaluations, i.e. the number of shots, required to achieve a +# certain error in computing the expectation value is not as straightforward. Let's now use +# PennyLane to estimate the number of shots needed to compute the expectation value of the water +# Hamiltonian. +# +# First, we construct the molecular Hamiltonian. + +molecule = qml.qchem.Molecule(symbols, geometry) +H = qml.qchem.molecular_hamiltonian(molecule)[0] +H_coeffs, H_ops = H.terms() + +############################################################################## +# The number of measurements needed to compute :math:`\left \langle H \right \rangle` can be +# obtained with the :func:`~.pennylane.resource.estimate_shots` function, which requires the +# Hamiltonian coefficients as input. The number of measurements required to compute +# :math:`\left \langle H \right \rangle` with a target error set to the chemical accuracy, 0.0016 +# :math:`\text{Ha},` is obtained as follows. + +m = qml.resource.estimate_shots(H_coeffs) +print(f'Shots : {m:.2e}') + +############################################################################## +# This number corresponds to the measurement process where each term in the Hamiltonian is measured +# independently. The number can be reduced by using +# :func:`~.pennylane.pauli.group_observables()`, which partitions the Pauli words into +# groups of commuting terms that can be measured simultaneously. + +ops, coeffs = qml.pauli.group_observables(H_ops, H_coeffs) +coeffs = [np.array(c) for c in coeffs] # cast as numpy array + +m = qml.resource.estimate_shots(coeffs) +print(f'Shots : {m:.2e}') + +############################################################################## +# It is also interesting to illustrate how the number of shots depends on the target error. + +error = np.array([0.02, 0.015, 0.01, 0.005, 0.001]) +m = [qml.resource.estimate_shots(H_coeffs, error=er) for er in error] + +e_ = np.linspace(error[0], error[-1], num=50) +m_ = 1.4e4 / np.linspace(error[0], error[-1], num=50)**2 + +fig, ax = plt.subplots(figsize=(5, 3)) +ax.plot(error, m, 'o', markerfacecolor='none', color='teal', label='estimated') +ax.plot(e_, m_, ':', markerfacecolor='none', color='olive', label='$ 1.4e4 * 1/\epsilon^2 $') + +ax.set_ylabel('shots') +ax.set_xlabel('error [Ha]') +ax.set_yscale('log') +ax.tick_params(axis='x', labelrotation = 90) +ax.legend() +fig.tight_layout() + +############################################################################## +# We have added a line showing the dependency of the shots to the error, as +# :math:`\text{shots} = 1.4\text{e}4 \times 1/\epsilon^2,` for comparison. Can you draw any +# interesting information form the plot? +# +# Conclusions +# ----------- +# This tutorial shows how to use the resource estimation functionality in PennyLane to compute the +# total number of non-Clifford gates and logical qubits required to simulate a Hamiltonian with +# quantum phase estimation algorithms. The estimation can be performed for second-quantized +# molecular Hamiltonians obtained with a double low-rank factorization algorithm, +# and first-quantized Hamiltonians of periodic materials in the plane wave basis. We also discussed +# the estimation of the total number of shots required to obtain the expectation value of an +# observable using the variational quantum eigensolver algorithm. The functionality allows one to +# obtain interesting results about the cost of implementing important quantum algorithms. For +# instance, we estimated the costs with respect to factors such as the target error in obtaining +# energies and the number of basis functions used to simulate a system. Can you think of other +# interesting information that can be obtained using this PennyLane functionality? +# +# References +# ---------- +# +# .. [#vonburg2021] +# +# Vera von Burg, Guang Hao Low, Thomas Haner, Damian S. Steiger, *et al.*, +# "Quantum computing enhanced computational catalysis". +# `Phys. Rev. Research 3, 033055 (2021) +# `__ +# +# .. [#lee2021] +# +# Joonho Lee, Dominic W. Berry, Craig Gidney, William J. Huggins, *et al.*, +# "Even More Efficient Quantum Computations of Chemistry Through Tensor Hypercontraction". +# `PRX Quantum 2, 030305 (2021) +# `__ +# +# .. [#zini2023] +# +# Modjtaba Shokrian Zini, Alain Delgado, Roberto dos Reis, Pablo A. M. Casares, +# Jonathan E. Mueller, Arne-Christian Voigt, Juan Miguel Arrazola, +# "Quantum simulation of battery materials using ionic pseudopotentials". +# `Quantum 7, 1049 (2023) `__ +# +# .. [#delgado2022] +# +# Alain Delgado, Pablo A. M. Casares, Roberto dos Reis, Modjtaba Shokrian Zini, *et al.*, +# "Simulating key properties of lithium-ion batteries with a fault-tolerant quantum computer". +# `Phys. Rev. A 106, 032428 (2022) +# `__ +# About the author +# ---------------- +# diff --git a/demonstrations/tutorial_rosalin.py b/demonstrations/tutorial_rosalin.py index 08b548c590..aed37d6224 100644 --- a/demonstrations/tutorial_rosalin.py +++ b/demonstrations/tutorial_rosalin.py @@ -1,666 +1,666 @@ -r""" -Frugal shot optimization with Rosalin -===================================== - -.. meta:: - :property="og:description": The Rosalin optimizer uses a measurement-frugal optimization strategy to minimize the - number of times a quantum computer is accessed. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/sphx_glr_tutorial_rosalin_002.png - -.. related:: - - tutorial_vqe A brief overview of VQE - tutorial_quantum_natural_gradient Quantum natural gradient - tutorial_doubly_stochastic Doubly stochastic gradient descent - tutorial_rotoselect Quantum circuit structure learning - -*Author: Josh Izaac — Posted: 19 May 2020. Last updated: 30 January 2023.* - -In this tutorial we investigate and implement the Rosalin (Random Operator Sampling for -Adaptive Learning with Individual Number of shots) from -Arrasmith et al. [#arrasmith2020]_. In this paper, a strategy -is introduced for reducing the number of shots required when optimizing variational quantum -algorithms, by both: - -* Frugally adapting the number of shots used per parameter update, and -* Performing a weighted sampling of operators from the cost Hamiltonian. - -.. note:: - - The Rosalin optimizer is available in PennyLane via the - :class:`~.pennylane.ShotAdaptiveOptimizer`. - -Background ----------- - -While a large number of papers in variational quantum algorithms focus on the -choice of circuit ansatz, cost function, gradient computation, or initialization method, -the optimization strategy—an important component affecting both convergence time and -quantum resource dependence—is not as frequently considered. Instead, common -'out-of-the-box' classical optimization techniques, such as gradient-free -methods (COBLYA, Nelder-Mead), gradient-descent, and Hessian-free methods (L-BFGS) tend to be used. - -However, for variational algorithms such as :doc:`VQE `, which involve evaluating -a large number of non-commuting operators in the cost function, decreasing the number of -quantum evaluations required for convergence, while still minimizing statistical noise, can -be a delicate balance. - -Recent work has highlighted that 'quantum-aware' optimization techniques -can lead to marked improvements when training variational quantum algorithms: - -* :doc:`/demos/tutorial_quantum_natural_gradient` descent by Stokes et al. [#stokes2019]_, which - takes into account the quantum geometry during the gradient-descent update step. - -* The work of Sweke et al. [#sweke2019]_, which shows - that quantum gradient descent with a finite number of shots is equivalent to - `stochastic gradient descent `_, - and has guaranteed convergence. Furthermore, combining a finite number of shots with - weighted sampling of the cost function terms leads to :doc:`/demos/tutorial_doubly_stochastic`. - -* The iCANS (individual Coupled Adaptive Number of Shots) optimization technique by - Jonas Kuebler et al. [#kubler2020]_ adapts the number - of shots measurements during training, by maximizing the expected gain per shot. - -In this latest result by Arrasmith et al. [#arrasmith2020]_, the -idea of doubly stochastic gradient descent has been used to extend the iCANS optimizer, -resulting in faster convergence. - -Over the course of this tutorial, we will explore their results; beginning first with a -demonstration of *weighted random sampling* of the cost Hamiltonian operators, before -combining this with the shot-frugal iCANS optimizer to perform doubly stochastic -Rosalin optimization. - -Weighted random sampling ------------------------- - -Consider a Hamiltonian :math:`H` expanded as a weighted sum of operators :math:`h_i` that can -be directly measured: - -.. math:: H = \sum_{i=1}^N c_i h_i. - -Due to the linearity of expectation values, the expectation value of this Hamiltonian -can be expressed as the weighted sum of each individual term: - -.. math:: \langle H\rangle = \sum_{i=1}^N c_i \langle h_i\rangle. - -In the :doc:`doubly stochastic gradient descent demonstration `, -we estimated this expectation value by **uniformly sampling** a subset of the terms -at each optimization step, and evaluating each term by using the same finite number of shots -:math:`N.` - -However, what happens if we use a weighted approach to determine how to distribute -our samples across the terms of the Hamiltonian? In **weighted random sampling** (WRS), -the number of shots used to determine the expectation value :math:`\langle h_i\rangle` -is a discrete random variable distributed according to a -`multinomial distribution `__, - -.. math:: S \sim \text{Multinomial}(p_i), - -with event probabilities - -.. math:: p_i = \frac{|c_i|}{\sum_i |c_i|}. - -That is, the number of shots assigned to the measurement of the expectation value of the -:math:`i\text{th}` term of the Hamiltonian is drawn from a probability distribution -*proportional to the magnitude of its coefficient* :math:`c_i.` - -To see this strategy in action, consider the Hamiltonian - -.. math:: H = 2I\otimes X + 4 I\otimes Z - X\otimes X + 5Y\otimes Y + 2 Z\otimes X. - -We can solve for the ground state energy using the variational quantum eigensolver (VQE) algorithm. - -First, let's import NumPy and PennyLane, and define our Hamiltonian. -""" -import pennylane as qml -from pennylane import numpy as np - -# set the random seed -np.random.seed(4) - -coeffs = [2, 4, -1, 5, 2] - -obs = [ - qml.PauliX(1), - qml.PauliZ(1), - qml.PauliX(0) @ qml.PauliX(1), - qml.PauliY(0) @ qml.PauliY(1), - qml.PauliZ(0) @ qml.PauliZ(1) -] - - -############################################################################## -# We can now create our quantum device (let's use the ``default.qubit`` simulator). - -num_layers = 2 -num_wires = 2 - -# create a device that estimates expectation values using a finite number of shots -non_analytic_dev = qml.device("default.qubit", wires=num_wires, shots=100, seed=432423) - -# create a device that calculates exact expectation values -analytic_dev = qml.device("default.qubit", wires=num_wires, shots=None) - -############################################################################## -# Now, let's set the total number of shots, and determine the probability -# for sampling each Hamiltonian term. - -total_shots = 8000 -prob_shots = np.abs(coeffs) / np.sum(np.abs(coeffs)) -print(prob_shots) - -############################################################################## -# We can now use SciPy to create our multinomial distributed random variable -# :math:`S,` using the number of trials (total shot number) and probability values: - -from scipy.stats import multinomial - -si = multinomial(n=total_shots, p=prob_shots) - -############################################################################## -# Sampling from this distribution will provide the number of shots used to -# sample each term in the Hamiltonian: - -samples = si.rvs()[0] -print(samples) -print(sum(samples)) - -############################################################################## -# As expected, if we sum the sampled shots per term, we recover the total number of shots. -# -# Let's now create our cost function. Recall that the cost function must do the -# following: -# -# 1. It must sample from the multinomial distribution we created above, -# to determine the number of shots :math:`s_i` to use to estimate the expectation -# value of the ith Hamiltonian term. -# -# 2. It then must estimate the expectation value :math:`\langle h_i\rangle` -# by creating the required QNode. For our ansatz, we'll use the -# :class:`~.pennylane.templates.layers.StronglyEntanglingLayers`. -# -# 3. And, last but not least, estimate the expectation value -# :math:`\langle H\rangle = \sum_i c_i\langle h_i\rangle.` -# - -from pennylane.templates.layers import StronglyEntanglingLayers - - -@qml.qnode(non_analytic_dev, diff_method="parameter-shift", interface="autograd") -def qnode(weights, observable): - StronglyEntanglingLayers(weights, wires=non_analytic_dev.wires) - return qml.expval(observable) - -def cost(params): - # sample from the multinomial distribution - shots_per_term = si.rvs()[0] - - result = 0 - - for o, c, s in zip(obs, coeffs, shots_per_term): - # evaluate the QNode corresponding to - # the Hamiltonian term, and add it on to our running sum - result += c * qnode(params, o, shots=int(s)) - - return result - - -############################################################################## -# Evaluating our cost function with some initial parameters, we can test out -# that our cost function evaluates correctly. - -param_shape = StronglyEntanglingLayers.shape(n_layers=num_layers, n_wires=num_wires) -init_params = np.random.uniform(low=0.0, high=2*np.pi, size=param_shape, requires_grad=True) -print(cost(init_params)) - - -############################################################################## -# Performing the optimization, with the number of shots randomly -# determined at each optimization step: - -opt = qml.AdamOptimizer(0.05) -params = init_params - -cost_wrs = [] -shots_wrs = [] - -for i in range(100): - params, _cost = opt.step_and_cost(cost, params) - cost_wrs.append(_cost) - shots_wrs.append(total_shots*i) - print("Step {}: cost = {} shots used = {}".format(i, cost_wrs[-1], shots_wrs[-1])) - -############################################################################## -# Let's compare this against an optimization not using weighted random sampling. -# Here, we will split the 8000 total shots evenly across all Hamiltonian terms, -# also known as *uniform deterministic sampling*. - -@qml.qnode(non_analytic_dev, diff_method="parameter-shift", interface="autograd") -def qnode(weights, obs): - StronglyEntanglingLayers(weights, wires=non_analytic_dev.wires) - return qml.expval(obs) - -def cost(params): - shots_per_term = int(total_shots / len(coeffs)) - - result = 0 - - for o, c in zip(obs, coeffs): - - # evaluate the QNode corresponding to - # the Hamiltonian term, and add it on to our running sum - result += c * qnode(params, o, shots=shots_per_term) - - return result - -opt = qml.AdamOptimizer(0.05) -params = init_params - -cost_adam = [] -shots_adam = [] - -for i in range(100): - params, _cost = opt.step_and_cost(cost, params) - cost_adam.append(_cost) - shots_adam.append(total_shots*i) - print("Step {}: cost = {} shots used = {}".format(i, cost_adam[-1], shots_adam[-1])) - -############################################################################## -# Comparing these two techniques: - -from matplotlib import pyplot as plt - -plt.style.use("seaborn-v0_8") -plt.plot(shots_wrs, cost_wrs, "b", label="Adam WRS") -plt.plot(shots_adam, cost_adam, "g", label="Adam") - -plt.ylabel("Cost function value") -plt.xlabel("Number of shots") -plt.legend() -plt.show() - -############################################################################## -# We can see that weighted random sampling performs just as well as the uniform -# deterministic sampling. However, weighted random sampling begins to show a -# non-negligible improvement over deterministic sampling for large Hamiltonians -# with highly non-uniform coefficients. For example, see Fig (3) and (4) of -# Arrasmith et al. [#arrasmith2020]_, comparing weighted random sampling VQE optimization -# for both :math:`\text{H}_2` and :math:`\text{LiH}` molecules. -# -# .. note:: -# -# While not covered here, another approach that could be taken is -# *weighted deterministic sampling*. Here, the number of shots is distributed -# across terms as per -# -# .. math:: s_i = \left\lfloor N \frac{|c_i|}{\sum_i |c_i|}\right\rfloor, -# -# where :math:`N` is the total number of shots. -# - -############################################################################## -# Rosalin: Frugal shot optimization -# --------------------------------- -# -# We can see above that both methods optimize fairly well; weighted random -# sampling converges just as well as evenly distributing the shots across -# all Hamiltonian terms. However, deterministic shot distribution approaches -# will always have a minimum shot value required per expectation value, as below -# this threshold they become biased estimators. This is not the case with random -# sampling; as we saw in the -# :doc:`doubly stochastic gradient descent demonstration `, -# the introduction of randomness allows for as little -# as a single shot per expectation term, while still remaining an unbiased estimator. -# -# Using this insight, Arrasmith et al. [#arrasmith2020]_ modified the iCANS frugal -# shot-optimization technique [#kubler2020]_ to include weighted random sampling, making it -# 'doubly stochastic'. -# -# iCANS optimizer -# ~~~~~~~~~~~~~~~ -# -# Two variants of the iCANS optimizer were introduced in Kübler et al., iCANS1 and iCANS2. -# The iCANS1 optimizer, on which Rosalin is based, frugally distributes a shot budget -# across the partial derivatives of each parameter, which are computed using the -# :doc:`parameter-shift rule `. It works roughly as follows: -# -# 1. The initial step of the optimizer is performed with some specified minimum -# number of shots, :math:`s_{min},` for all partial derivatives. -# -# 2. The parameter-shift rule is then used to estimate the gradient :math:`g_i` -# for each parameter :math:`\theta_i,` parameters, as well as the *variances* -# :math:`v_i` of the estimated gradients. -# -# 3. Gradient descent is performed for each parameter :math:`\theta_i,` using -# the pre-defined learning rate :math:`\alpha` and the gradient information :math:`g_i:` -# -# .. math:: \theta_i = \theta_i - \alpha g_i. -# -# 4. The improvement in the cost function per shot, for a specific parameter value, -# is then calculated via -# -# .. math:: -# -# \gamma_i = \frac{1}{s_i} \left[ \left(\alpha - \frac{1}{2} L\alpha^2\right) -# g_i^2 - \frac{L\alpha^2}{2s_i}v_i \right], -# -# where: -# -# * :math:`L \leq \sum_i|c_i|` is the bound on the `Lipschitz constant -# `__ of the variational quantum algorithm objective function, -# -# * :math:`c_i` are the coefficients of the Hamiltonian, and -# -# * :math:`\alpha` is the learning rate, and *must* be bound such that :math:`\alpha < 2/L` -# for the above expression to hold. -# -# 5. Finally, the new values of :math:`s_i` (shots for partial derivative of parameter -# :math:`\theta_i`) is given by: -# -# .. math:: -# -# s_i = \frac{2L\alpha}{2-L\alpha}\left(\frac{v_i}{g_i^2}\right)\propto -# \frac{v_i}{g_i^2}. -# -# In addition to the above, to counteract the presence of noise in the system, a -# running average of :math:`g_i` and :math:`s_i` (:math:`\chi_i` and :math:`\xi_i` respectively) -# are used when computing :math:`\gamma_i` and :math:`s_i.` -# -# .. note:: -# -# In classical machine learning, the Lipschitz constant of the cost function is generally -# unknown. However, for a variational quantum algorithm with cost of the form -# :math:`f(x) = \langle \psi(x) | \hat{H} |\psi(x)\rangle,` -# an upper bound on the Lipschitz constant is given by :math:`L < \sum_i|c_i|,` -# where :math:`c_i` are the coefficients of :math:`\hat{H}` when decomposed -# into a linear combination of Pauli-operator tensor products. -# -# Rosalin implementation -# ~~~~~~~~~~~~~~~~~~~~~~ -# -# Let's now modify iCANS above to incorporate weighted random sampling of Hamiltonian -# terms — the Rosalin frugal shot optimizer. -# -# Rosalin takes several hyper-parameters: -# -# * ``min_shots``: the minimum number of shots used to estimate the expectations -# of each term in the Hamiltonian. Note that this must be larger than 2 for the variance -# of the gradients to be computed. -# -# * ``mu``: The running average constant :math:`\mu\in[0, 1].` Used to control how quickly the -# number of shots recommended for each gradient component changes. -# -# * ``b``: Regularization bias. The bias should be kept small, but non-zero. -# -# * ``lr``: The learning rate. Recall from above that the learning rate *must* be such -# that :math:`\alpha < 2/L = 2/\sum_i|c_i|.` -# -# Since the Rosalin optimizer has a state that must be preserved between optimization steps, -# let's use a class to create our optimizer. -# - -class Rosalin: - - def __init__(self, obs, coeffs, min_shots, mu=0.99, b=1e-6, lr=0.07): - self.obs = obs - self.coeffs = coeffs - - self.lipschitz = np.sum(np.abs(coeffs)) - - if lr > 2 / self.lipschitz: - raise ValueError("The learning rate must be less than ", 2 / self.lipschitz) - - # hyperparameters - self.min_shots = min_shots - self.mu = mu # running average constant - self.b = b # regularization bias - self.lr = lr # learning rate - - # keep track of the total number of shots used - self.shots_used = 0 - # total number of iterations - self.k = 0 - # Number of shots per parameter - self.s = np.zeros_like(params, dtype=np.float64) + min_shots - - # Running average of the parameter gradients - self.chi = None - # Running average of the variance of the parameter gradients - self.xi = None - - def estimate_hamiltonian(self, params, shots): - """Returns an array containing length ``shots`` single-shot estimates - of the Hamiltonian. The shots are distributed randomly over - the terms in the Hamiltonian, as per a Multinomial distribution. - - Since we are performing single-shot estimates, the QNodes must be - set to 'sample' mode. - """ - # note that convergence depends on seed for random number generation - rosalin_device = qml.device("default.qubit", wires=num_wires, shots=100, seed=93754352) - - # determine the shot probability per term - prob_shots = np.abs(coeffs) / np.sum(np.abs(coeffs)) - - # construct the multinomial distribution, and sample - # from it to determine how many shots to apply per term - si = multinomial(n=shots, p=prob_shots) - shots_per_term = si.rvs()[0] - - results = [] - - @qml.qnode(rosalin_device, diff_method="parameter-shift", interface="autograd") - def qnode(weights, observable): - StronglyEntanglingLayers(weights, wires=rosalin_device.wires) - return qml.sample(observable) - - for o, c, p, s in zip(self.obs, self.coeffs, prob_shots, shots_per_term): - - # if the number of shots is 0, do nothing - if s == 0: - continue - - # evaluate the QNode corresponding to - # the Hamiltonian term - res = qnode(params, o, shots=int(s)) - - if s == 1: - res = np.array([res]) - - # Note that, unlike above, we divide each term by the - # probability per shot. This is because we are sampling one at a time. - results.append(c * res / p) - - return np.concatenate(results) - - def evaluate_grad_var(self, i, params, shots): - """Evaluate the gradient, as well as the variance in the gradient, - for the ith parameter in params, using the parameter-shift rule. - """ - shift = np.zeros_like(params) - shift[i] = np.pi / 2 - - shift_forward = self.estimate_hamiltonian(params + shift, shots) - shift_backward = self.estimate_hamiltonian(params - shift, shots) - - g = np.mean(shift_forward - shift_backward) / 2 - s = np.var((shift_forward - shift_backward) / 2, ddof=1) - - return g, s - - def step(self, params): - """Perform a single step of the Rosalin optimizer.""" - # keep track of the number of shots run - self.shots_used += int(2 * np.sum(self.s)) - - # compute the gradient, as well as the variance in the gradient, - # using the number of shots determined by the array s. - grad = [] - S = [] - - p_ind = list(np.ndindex(*params.shape)) - - for l in p_ind: - # loop through each parameter, performing - # the parameter-shift rule - g_, s_ = self.evaluate_grad_var(l, params, self.s[l]) - grad.append(g_) - S.append(s_) - - grad = np.reshape(np.stack(grad), params.shape) - S = np.reshape(np.stack(S), params.shape) - - # gradient descent update - params = params - self.lr * grad - - if self.xi is None: - self.chi = np.zeros_like(params, dtype=np.float64) - self.xi = np.zeros_like(params, dtype=np.float64) - - # running average of the gradient variance - self.xi = self.mu * self.xi + (1 - self.mu) * S - xi = self.xi / (1 - self.mu ** (self.k + 1)) - - # running average of the gradient - self.chi = self.mu * self.chi + (1 - self.mu) * grad - chi = self.chi / (1 - self.mu ** (self.k + 1)) - - # determine the new optimum shots distribution for the next - # iteration of the optimizer - s = np.ceil( - (2 * self.lipschitz * self.lr * xi) - / ((2 - self.lipschitz * self.lr) * (chi ** 2 + self.b * (self.mu ** self.k))) - ) - - # apply an upper and lower bound on the new shot distributions, - # to avoid the number of shots reducing below min(2, min_shots), - # or growing too significantly. - gamma = ( - (self.lr - self.lipschitz * self.lr ** 2 / 2) * chi ** 2 - - xi * self.lipschitz * self.lr ** 2 / (2 * s) - ) / s - - argmax_gamma = np.unravel_index(np.argmax(gamma), gamma.shape) - smax = s[argmax_gamma] - self.s = np.clip(s, min(2, self.min_shots), max(2, smax)) - - self.k += 1 - return params - - -############################################################################## -# Rosalin optimization -# ~~~~~~~~~~~~~~~~~~~~ -# -# We are now ready to use our Rosalin optimizer to optimize the initial VQE problem. But first let's -# also create a separate cost function using an 'exact' quantum device, so that we can keep track of the -# *exact* cost function value at each iteration. - -@qml.qnode(analytic_dev, interface="autograd") -def cost_analytic(weights): - StronglyEntanglingLayers(weights, wires=analytic_dev.wires) - return qml.expval(qml.Hamiltonian(coeffs, obs)) - -############################################################################## -# Creating the optimizer and beginning the optimization: - - -opt = Rosalin(obs, coeffs, min_shots=10) -params = init_params - -cost_rosalin = [cost_analytic(params)] -shots_rosalin = [0] - -for i in range(60): - params = opt.step(params) - cost_rosalin.append(cost_analytic(params)) - shots_rosalin.append(opt.shots_used) - print(f"Step {i}: cost = {cost_rosalin[-1]}, shots_used = {shots_rosalin[-1]}") - - -############################################################################## -# Let's compare this to a standard Adam optimization. Using 100 shots per quantum -# evaluation, for each update step there are 2 quantum evaluations per parameter. - -adam_shots_per_eval = 100 -adam_shots_per_step = 2 * adam_shots_per_eval * len(params.flatten()) -print(adam_shots_per_step) - -############################################################################## -# Thus, Adam is using 2400 shots per update step. - -params = init_params -opt = qml.AdamOptimizer(0.07) - -adam_dev = qml.device('default.qubit', shots=adam_shots_per_eval, seed=595905) - -@qml.qnode(adam_dev, diff_method="parameter-shift", interface="autograd") -def cost(weights): - StronglyEntanglingLayers(weights, wires=non_analytic_dev.wires) - return qml.expval(qml.Hamiltonian(coeffs, obs)) - -cost_adam = [cost_analytic(params)] -shots_adam = [0] - -for i in range(100): - params = opt.step(cost, params) - cost_adam.append(cost_analytic(params)) - shots_adam.append(adam_shots_per_step * (i + 1)) - print("Step {}: cost = {} shots_used = {}".format(i, cost_adam[-1], shots_adam[-1])) - -############################################################################## -# Plotting both experiments: - -plt.style.use("seaborn-v0_8") -plt.plot(shots_rosalin, cost_rosalin, "b", label="Rosalin") -plt.plot(shots_adam, cost_adam, "g", label="Adam") - -plt.ylabel("Cost function value") -plt.xlabel("Number of shots") -plt.legend() -plt.xlim(0, 300000) -plt.show() - -############################################################################## -# The Rosalin optimizer performs significantly better than the Adam optimizer, -# approaching the ground state energy of the Hamiltonian with strikingly -# fewer shots. -# -# While beyond the scope of this demonstration, the Rosalin optimizer can be -# modified in various other ways; for instance, by incorporating *weighted hybrid -# sampling* (which distributes some shots deterministically, with the remainder -# done randomly), or by adapting the variant iCANS2 optimizer. Download -# this demonstration from the sidebar 👉 and give it a go! ⚛️ - - -############################################################################## -# References -# ---------- -# -# .. [#arrasmith2020] -# -# Andrew Arrasmith, Lukasz Cincio, Rolando D. Somma, and Patrick J. Coles. "Operator Sampling -# for Shot-frugal Optimization in Variational Algorithms." `arXiv:2004.06252 -# `__ (2020). -# -# .. [#stokes2019] -# -# James Stokes, Josh Izaac, Nathan Killoran, and Giuseppe Carleo. "Quantum Natural Gradient." -# `arXiv:1909.02108 `__ (2019). -# -# .. [#sweke2019] -# -# Ryan Sweke, Frederik Wilde, Johannes Jakob Meyer, Maria Schuld, Paul K. Fährmann, Barthélémy -# Meynard-Piganeau, and Jens Eisert. "Stochastic gradient descent for hybrid quantum-classical -# optimization." `arXiv:1910.01155 `__ (2019). -# -# .. [#kubler2020] -# -# Jonas M. Kübler, Andrew Arrasmith, Lukasz Cincio, and Patrick J. Coles. "An Adaptive Optimizer -# for Measurement-Frugal Variational Algorithms." `Quantum 4, 263 -# `__ (2020). -# -# -# About the author -# ---------------- +r""" +Frugal shot optimization with Rosalin +===================================== + +.. meta:: + :property="og:description": The Rosalin optimizer uses a measurement-frugal optimization strategy to minimize the + number of times a quantum computer is accessed. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/sphx_glr_tutorial_rosalin_002.png + +.. related:: + + tutorial_vqe A brief overview of VQE + tutorial_quantum_natural_gradient Quantum natural gradient + tutorial_doubly_stochastic Doubly stochastic gradient descent + tutorial_rotoselect Quantum circuit structure learning + +*Author: Josh Izaac — Posted: 19 May 2020. Last updated: 30 January 2023.* + +In this tutorial we investigate and implement the Rosalin (Random Operator Sampling for +Adaptive Learning with Individual Number of shots) from +Arrasmith et al. [#arrasmith2020]_. In this paper, a strategy +is introduced for reducing the number of shots required when optimizing variational quantum +algorithms, by both: + +* Frugally adapting the number of shots used per parameter update, and +* Performing a weighted sampling of operators from the cost Hamiltonian. + +.. note:: + + The Rosalin optimizer is available in PennyLane via the + :class:`~.pennylane.ShotAdaptiveOptimizer`. + +Background +---------- + +While a large number of papers in variational quantum algorithms focus on the +choice of circuit ansatz, cost function, gradient computation, or initialization method, +the optimization strategy—an important component affecting both convergence time and +quantum resource dependence—is not as frequently considered. Instead, common +'out-of-the-box' classical optimization techniques, such as gradient-free +methods (COBLYA, Nelder-Mead), gradient-descent, and Hessian-free methods (L-BFGS) tend to be used. + +However, for variational algorithms such as :doc:`VQE `, which involve evaluating +a large number of non-commuting operators in the cost function, decreasing the number of +quantum evaluations required for convergence, while still minimizing statistical noise, can +be a delicate balance. + +Recent work has highlighted that 'quantum-aware' optimization techniques +can lead to marked improvements when training variational quantum algorithms: + +* :doc:`/demos/tutorial_quantum_natural_gradient` descent by Stokes et al. [#stokes2019]_, which + takes into account the quantum geometry during the gradient-descent update step. + +* The work of Sweke et al. [#sweke2019]_, which shows + that quantum gradient descent with a finite number of shots is equivalent to + `stochastic gradient descent `_, + and has guaranteed convergence. Furthermore, combining a finite number of shots with + weighted sampling of the cost function terms leads to :doc:`/demos/tutorial_doubly_stochastic`. + +* The iCANS (individual Coupled Adaptive Number of Shots) optimization technique by + Jonas Kuebler et al. [#kubler2020]_ adapts the number + of shots measurements during training, by maximizing the expected gain per shot. + +In this latest result by Arrasmith et al. [#arrasmith2020]_, the +idea of doubly stochastic gradient descent has been used to extend the iCANS optimizer, +resulting in faster convergence. + +Over the course of this tutorial, we will explore their results; beginning first with a +demonstration of *weighted random sampling* of the cost Hamiltonian operators, before +combining this with the shot-frugal iCANS optimizer to perform doubly stochastic +Rosalin optimization. + +Weighted random sampling +------------------------ + +Consider a Hamiltonian :math:`H` expanded as a weighted sum of operators :math:`h_i` that can +be directly measured: + +.. math:: H = \sum_{i=1}^N c_i h_i. + +Due to the linearity of expectation values, the expectation value of this Hamiltonian +can be expressed as the weighted sum of each individual term: + +.. math:: \langle H\rangle = \sum_{i=1}^N c_i \langle h_i\rangle. + +In the :doc:`doubly stochastic gradient descent demonstration `, +we estimated this expectation value by **uniformly sampling** a subset of the terms +at each optimization step, and evaluating each term by using the same finite number of shots +:math:`N.` + +However, what happens if we use a weighted approach to determine how to distribute +our samples across the terms of the Hamiltonian? In **weighted random sampling** (WRS), +the number of shots used to determine the expectation value :math:`\langle h_i\rangle` +is a discrete random variable distributed according to a +`multinomial distribution `__, + +.. math:: S \sim \text{Multinomial}(p_i), + +with event probabilities + +.. math:: p_i = \frac{|c_i|}{\sum_i |c_i|}. + +That is, the number of shots assigned to the measurement of the expectation value of the +:math:`i\text{th}` term of the Hamiltonian is drawn from a probability distribution +*proportional to the magnitude of its coefficient* :math:`c_i.` + +To see this strategy in action, consider the Hamiltonian + +.. math:: H = 2I\otimes X + 4 I\otimes Z - X\otimes X + 5Y\otimes Y + 2 Z\otimes X. + +We can solve for the ground state energy using the variational quantum eigensolver (VQE) algorithm. + +First, let's import NumPy and PennyLane, and define our Hamiltonian. +""" +import pennylane as qml +from pennylane import numpy as np + +# set the random seed +np.random.seed(4) + +coeffs = [2, 4, -1, 5, 2] + +obs = [ + qml.PauliX(1), + qml.PauliZ(1), + qml.PauliX(0) @ qml.PauliX(1), + qml.PauliY(0) @ qml.PauliY(1), + qml.PauliZ(0) @ qml.PauliZ(1) +] + + +############################################################################## +# We can now create our quantum device (let's use the ``default.qubit`` simulator). + +num_layers = 2 +num_wires = 2 + +# create a device that estimates expectation values using a finite number of shots +non_analytic_dev = qml.device("default.qubit", wires=num_wires, shots=100, seed=432423) + +# create a device that calculates exact expectation values +analytic_dev = qml.device("default.qubit", wires=num_wires, shots=None) + +############################################################################## +# Now, let's set the total number of shots, and determine the probability +# for sampling each Hamiltonian term. + +total_shots = 8000 +prob_shots = np.abs(coeffs) / np.sum(np.abs(coeffs)) +print(prob_shots) + +############################################################################## +# We can now use SciPy to create our multinomial distributed random variable +# :math:`S,` using the number of trials (total shot number) and probability values: + +from scipy.stats import multinomial + +si = multinomial(n=total_shots, p=prob_shots) + +############################################################################## +# Sampling from this distribution will provide the number of shots used to +# sample each term in the Hamiltonian: + +samples = si.rvs()[0] +print(samples) +print(sum(samples)) + +############################################################################## +# As expected, if we sum the sampled shots per term, we recover the total number of shots. +# +# Let's now create our cost function. Recall that the cost function must do the +# following: +# +# 1. It must sample from the multinomial distribution we created above, +# to determine the number of shots :math:`s_i` to use to estimate the expectation +# value of the ith Hamiltonian term. +# +# 2. It then must estimate the expectation value :math:`\langle h_i\rangle` +# by creating the required QNode. For our ansatz, we'll use the +# :class:`~.pennylane.templates.layers.StronglyEntanglingLayers`. +# +# 3. And, last but not least, estimate the expectation value +# :math:`\langle H\rangle = \sum_i c_i\langle h_i\rangle.` +# + +from pennylane.templates.layers import StronglyEntanglingLayers + + +@qml.qnode(non_analytic_dev, diff_method="parameter-shift", interface="autograd") +def qnode(weights, observable): + StronglyEntanglingLayers(weights, wires=non_analytic_dev.wires) + return qml.expval(observable) + +def cost(params): + # sample from the multinomial distribution + shots_per_term = si.rvs()[0] + + result = 0 + + for o, c, s in zip(obs, coeffs, shots_per_term): + # evaluate the QNode corresponding to + # the Hamiltonian term, and add it on to our running sum + result += c * qnode(params, o, shots=int(s)) + + return result + + +############################################################################## +# Evaluating our cost function with some initial parameters, we can test out +# that our cost function evaluates correctly. + +param_shape = StronglyEntanglingLayers.shape(n_layers=num_layers, n_wires=num_wires) +init_params = np.random.uniform(low=0.0, high=2*np.pi, size=param_shape, requires_grad=True) +print(cost(init_params)) + + +############################################################################## +# Performing the optimization, with the number of shots randomly +# determined at each optimization step: + +opt = qml.AdamOptimizer(0.05) +params = init_params + +cost_wrs = [] +shots_wrs = [] + +for i in range(100): + params, _cost = opt.step_and_cost(cost, params) + cost_wrs.append(_cost) + shots_wrs.append(total_shots*i) + print("Step {}: cost = {} shots used = {}".format(i, cost_wrs[-1], shots_wrs[-1])) + +############################################################################## +# Let's compare this against an optimization not using weighted random sampling. +# Here, we will split the 8000 total shots evenly across all Hamiltonian terms, +# also known as *uniform deterministic sampling*. + +@qml.qnode(non_analytic_dev, diff_method="parameter-shift", interface="autograd") +def qnode(weights, obs): + StronglyEntanglingLayers(weights, wires=non_analytic_dev.wires) + return qml.expval(obs) + +def cost(params): + shots_per_term = int(total_shots / len(coeffs)) + + result = 0 + + for o, c in zip(obs, coeffs): + + # evaluate the QNode corresponding to + # the Hamiltonian term, and add it on to our running sum + result += c * qnode(params, o, shots=shots_per_term) + + return result + +opt = qml.AdamOptimizer(0.05) +params = init_params + +cost_adam = [] +shots_adam = [] + +for i in range(100): + params, _cost = opt.step_and_cost(cost, params) + cost_adam.append(_cost) + shots_adam.append(total_shots*i) + print("Step {}: cost = {} shots used = {}".format(i, cost_adam[-1], shots_adam[-1])) + +############################################################################## +# Comparing these two techniques: + +from matplotlib import pyplot as plt + +plt.style.use("seaborn-v0_8") +plt.plot(shots_wrs, cost_wrs, "b", label="Adam WRS") +plt.plot(shots_adam, cost_adam, "g", label="Adam") + +plt.ylabel("Cost function value") +plt.xlabel("Number of shots") +plt.legend() +plt.show() + +############################################################################## +# We can see that weighted random sampling performs just as well as the uniform +# deterministic sampling. However, weighted random sampling begins to show a +# non-negligible improvement over deterministic sampling for large Hamiltonians +# with highly non-uniform coefficients. For example, see Fig (3) and (4) of +# Arrasmith et al. [#arrasmith2020]_, comparing weighted random sampling VQE optimization +# for both :math:`\text{H}_2` and :math:`\text{LiH}` molecules. +# +# .. note:: +# +# While not covered here, another approach that could be taken is +# *weighted deterministic sampling*. Here, the number of shots is distributed +# across terms as per +# +# .. math:: s_i = \left\lfloor N \frac{|c_i|}{\sum_i |c_i|}\right\rfloor, +# +# where :math:`N` is the total number of shots. +# + +############################################################################## +# Rosalin: Frugal shot optimization +# --------------------------------- +# +# We can see above that both methods optimize fairly well; weighted random +# sampling converges just as well as evenly distributing the shots across +# all Hamiltonian terms. However, deterministic shot distribution approaches +# will always have a minimum shot value required per expectation value, as below +# this threshold they become biased estimators. This is not the case with random +# sampling; as we saw in the +# :doc:`doubly stochastic gradient descent demonstration `, +# the introduction of randomness allows for as little +# as a single shot per expectation term, while still remaining an unbiased estimator. +# +# Using this insight, Arrasmith et al. [#arrasmith2020]_ modified the iCANS frugal +# shot-optimization technique [#kubler2020]_ to include weighted random sampling, making it +# 'doubly stochastic'. +# +# iCANS optimizer +# ~~~~~~~~~~~~~~~ +# +# Two variants of the iCANS optimizer were introduced in Kübler et al., iCANS1 and iCANS2. +# The iCANS1 optimizer, on which Rosalin is based, frugally distributes a shot budget +# across the partial derivatives of each parameter, which are computed using the +# :doc:`parameter-shift rule `. It works roughly as follows: +# +# 1. The initial step of the optimizer is performed with some specified minimum +# number of shots, :math:`s_{min},` for all partial derivatives. +# +# 2. The parameter-shift rule is then used to estimate the gradient :math:`g_i` +# for each parameter :math:`\theta_i,` parameters, as well as the *variances* +# :math:`v_i` of the estimated gradients. +# +# 3. Gradient descent is performed for each parameter :math:`\theta_i,` using +# the pre-defined learning rate :math:`\alpha` and the gradient information :math:`g_i:` +# +# .. math:: \theta_i = \theta_i - \alpha g_i. +# +# 4. The improvement in the cost function per shot, for a specific parameter value, +# is then calculated via +# +# .. math:: +# +# \gamma_i = \frac{1}{s_i} \left[ \left(\alpha - \frac{1}{2} L\alpha^2\right) +# g_i^2 - \frac{L\alpha^2}{2s_i}v_i \right], +# +# where: +# +# * :math:`L \leq \sum_i|c_i|` is the bound on the `Lipschitz constant +# `__ of the variational quantum algorithm objective function, +# +# * :math:`c_i` are the coefficients of the Hamiltonian, and +# +# * :math:`\alpha` is the learning rate, and *must* be bound such that :math:`\alpha < 2/L` +# for the above expression to hold. +# +# 5. Finally, the new values of :math:`s_i` (shots for partial derivative of parameter +# :math:`\theta_i`) is given by: +# +# .. math:: +# +# s_i = \frac{2L\alpha}{2-L\alpha}\left(\frac{v_i}{g_i^2}\right)\propto +# \frac{v_i}{g_i^2}. +# +# In addition to the above, to counteract the presence of noise in the system, a +# running average of :math:`g_i` and :math:`s_i` (:math:`\chi_i` and :math:`\xi_i` respectively) +# are used when computing :math:`\gamma_i` and :math:`s_i.` +# +# .. note:: +# +# In classical machine learning, the Lipschitz constant of the cost function is generally +# unknown. However, for a variational quantum algorithm with cost of the form +# :math:`f(x) = \langle \psi(x) | \hat{H} |\psi(x)\rangle,` +# an upper bound on the Lipschitz constant is given by :math:`L < \sum_i|c_i|,` +# where :math:`c_i` are the coefficients of :math:`\hat{H}` when decomposed +# into a linear combination of Pauli-operator tensor products. +# +# Rosalin implementation +# ~~~~~~~~~~~~~~~~~~~~~~ +# +# Let's now modify iCANS above to incorporate weighted random sampling of Hamiltonian +# terms — the Rosalin frugal shot optimizer. +# +# Rosalin takes several hyper-parameters: +# +# * ``min_shots``: the minimum number of shots used to estimate the expectations +# of each term in the Hamiltonian. Note that this must be larger than 2 for the variance +# of the gradients to be computed. +# +# * ``mu``: The running average constant :math:`\mu\in[0, 1].` Used to control how quickly the +# number of shots recommended for each gradient component changes. +# +# * ``b``: Regularization bias. The bias should be kept small, but non-zero. +# +# * ``lr``: The learning rate. Recall from above that the learning rate *must* be such +# that :math:`\alpha < 2/L = 2/\sum_i|c_i|.` +# +# Since the Rosalin optimizer has a state that must be preserved between optimization steps, +# let's use a class to create our optimizer. +# + +class Rosalin: + + def __init__(self, obs, coeffs, min_shots, mu=0.99, b=1e-6, lr=0.07): + self.obs = obs + self.coeffs = coeffs + + self.lipschitz = np.sum(np.abs(coeffs)) + + if lr > 2 / self.lipschitz: + raise ValueError("The learning rate must be less than ", 2 / self.lipschitz) + + # hyperparameters + self.min_shots = min_shots + self.mu = mu # running average constant + self.b = b # regularization bias + self.lr = lr # learning rate + + # keep track of the total number of shots used + self.shots_used = 0 + # total number of iterations + self.k = 0 + # Number of shots per parameter + self.s = np.zeros_like(params, dtype=np.float64) + min_shots + + # Running average of the parameter gradients + self.chi = None + # Running average of the variance of the parameter gradients + self.xi = None + + def estimate_hamiltonian(self, params, shots): + """Returns an array containing length ``shots`` single-shot estimates + of the Hamiltonian. The shots are distributed randomly over + the terms in the Hamiltonian, as per a Multinomial distribution. + + Since we are performing single-shot estimates, the QNodes must be + set to 'sample' mode. + """ + # note that convergence depends on seed for random number generation + rosalin_device = qml.device("default.qubit", wires=num_wires, shots=100, seed=93754352) + + # determine the shot probability per term + prob_shots = np.abs(coeffs) / np.sum(np.abs(coeffs)) + + # construct the multinomial distribution, and sample + # from it to determine how many shots to apply per term + si = multinomial(n=shots, p=prob_shots) + shots_per_term = si.rvs()[0] + + results = [] + + @qml.qnode(rosalin_device, diff_method="parameter-shift", interface="autograd") + def qnode(weights, observable): + StronglyEntanglingLayers(weights, wires=rosalin_device.wires) + return qml.sample(observable) + + for o, c, p, s in zip(self.obs, self.coeffs, prob_shots, shots_per_term): + + # if the number of shots is 0, do nothing + if s == 0: + continue + + # evaluate the QNode corresponding to + # the Hamiltonian term + res = qnode(params, o, shots=int(s)) + + if s == 1: + res = np.array([res]) + + # Note that, unlike above, we divide each term by the + # probability per shot. This is because we are sampling one at a time. + results.append(c * res / p) + + return np.concatenate(results) + + def evaluate_grad_var(self, i, params, shots): + """Evaluate the gradient, as well as the variance in the gradient, + for the ith parameter in params, using the parameter-shift rule. + """ + shift = np.zeros_like(params) + shift[i] = np.pi / 2 + + shift_forward = self.estimate_hamiltonian(params + shift, shots) + shift_backward = self.estimate_hamiltonian(params - shift, shots) + + g = np.mean(shift_forward - shift_backward) / 2 + s = np.var((shift_forward - shift_backward) / 2, ddof=1) + + return g, s + + def step(self, params): + """Perform a single step of the Rosalin optimizer.""" + # keep track of the number of shots run + self.shots_used += int(2 * np.sum(self.s)) + + # compute the gradient, as well as the variance in the gradient, + # using the number of shots determined by the array s. + grad = [] + S = [] + + p_ind = list(np.ndindex(*params.shape)) + + for l in p_ind: + # loop through each parameter, performing + # the parameter-shift rule + g_, s_ = self.evaluate_grad_var(l, params, self.s[l]) + grad.append(g_) + S.append(s_) + + grad = np.reshape(np.stack(grad), params.shape) + S = np.reshape(np.stack(S), params.shape) + + # gradient descent update + params = params - self.lr * grad + + if self.xi is None: + self.chi = np.zeros_like(params, dtype=np.float64) + self.xi = np.zeros_like(params, dtype=np.float64) + + # running average of the gradient variance + self.xi = self.mu * self.xi + (1 - self.mu) * S + xi = self.xi / (1 - self.mu ** (self.k + 1)) + + # running average of the gradient + self.chi = self.mu * self.chi + (1 - self.mu) * grad + chi = self.chi / (1 - self.mu ** (self.k + 1)) + + # determine the new optimum shots distribution for the next + # iteration of the optimizer + s = np.ceil( + (2 * self.lipschitz * self.lr * xi) + / ((2 - self.lipschitz * self.lr) * (chi ** 2 + self.b * (self.mu ** self.k))) + ) + + # apply an upper and lower bound on the new shot distributions, + # to avoid the number of shots reducing below min(2, min_shots), + # or growing too significantly. + gamma = ( + (self.lr - self.lipschitz * self.lr ** 2 / 2) * chi ** 2 + - xi * self.lipschitz * self.lr ** 2 / (2 * s) + ) / s + + argmax_gamma = np.unravel_index(np.argmax(gamma), gamma.shape) + smax = s[argmax_gamma] + self.s = np.clip(s, min(2, self.min_shots), max(2, smax)) + + self.k += 1 + return params + + +############################################################################## +# Rosalin optimization +# ~~~~~~~~~~~~~~~~~~~~ +# +# We are now ready to use our Rosalin optimizer to optimize the initial VQE problem. But first let's +# also create a separate cost function using an 'exact' quantum device, so that we can keep track of the +# *exact* cost function value at each iteration. + +@qml.qnode(analytic_dev, interface="autograd") +def cost_analytic(weights): + StronglyEntanglingLayers(weights, wires=analytic_dev.wires) + return qml.expval(qml.Hamiltonian(coeffs, obs)) + +############################################################################## +# Creating the optimizer and beginning the optimization: + + +opt = Rosalin(obs, coeffs, min_shots=10) +params = init_params + +cost_rosalin = [cost_analytic(params)] +shots_rosalin = [0] + +for i in range(60): + params = opt.step(params) + cost_rosalin.append(cost_analytic(params)) + shots_rosalin.append(opt.shots_used) + print(f"Step {i}: cost = {cost_rosalin[-1]}, shots_used = {shots_rosalin[-1]}") + + +############################################################################## +# Let's compare this to a standard Adam optimization. Using 100 shots per quantum +# evaluation, for each update step there are 2 quantum evaluations per parameter. + +adam_shots_per_eval = 100 +adam_shots_per_step = 2 * adam_shots_per_eval * len(params.flatten()) +print(adam_shots_per_step) + +############################################################################## +# Thus, Adam is using 2400 shots per update step. + +params = init_params +opt = qml.AdamOptimizer(0.07) + +adam_dev = qml.device('default.qubit', shots=adam_shots_per_eval, seed=595905) + +@qml.qnode(adam_dev, diff_method="parameter-shift", interface="autograd") +def cost(weights): + StronglyEntanglingLayers(weights, wires=non_analytic_dev.wires) + return qml.expval(qml.Hamiltonian(coeffs, obs)) + +cost_adam = [cost_analytic(params)] +shots_adam = [0] + +for i in range(100): + params = opt.step(cost, params) + cost_adam.append(cost_analytic(params)) + shots_adam.append(adam_shots_per_step * (i + 1)) + print("Step {}: cost = {} shots_used = {}".format(i, cost_adam[-1], shots_adam[-1])) + +############################################################################## +# Plotting both experiments: + +plt.style.use("seaborn-v0_8") +plt.plot(shots_rosalin, cost_rosalin, "b", label="Rosalin") +plt.plot(shots_adam, cost_adam, "g", label="Adam") + +plt.ylabel("Cost function value") +plt.xlabel("Number of shots") +plt.legend() +plt.xlim(0, 300000) +plt.show() + +############################################################################## +# The Rosalin optimizer performs significantly better than the Adam optimizer, +# approaching the ground state energy of the Hamiltonian with strikingly +# fewer shots. +# +# While beyond the scope of this demonstration, the Rosalin optimizer can be +# modified in various other ways; for instance, by incorporating *weighted hybrid +# sampling* (which distributes some shots deterministically, with the remainder +# done randomly), or by adapting the variant iCANS2 optimizer. Download +# this demonstration from the sidebar 👉 and give it a go! ⚛️ + + +############################################################################## +# References +# ---------- +# +# .. [#arrasmith2020] +# +# Andrew Arrasmith, Lukasz Cincio, Rolando D. Somma, and Patrick J. Coles. "Operator Sampling +# for Shot-frugal Optimization in Variational Algorithms." `arXiv:2004.06252 +# `__ (2020). +# +# .. [#stokes2019] +# +# James Stokes, Josh Izaac, Nathan Killoran, and Giuseppe Carleo. "Quantum Natural Gradient." +# `arXiv:1909.02108 `__ (2019). +# +# .. [#sweke2019] +# +# Ryan Sweke, Frederik Wilde, Johannes Jakob Meyer, Maria Schuld, Paul K. Fährmann, Barthélémy +# Meynard-Piganeau, and Jens Eisert. "Stochastic gradient descent for hybrid quantum-classical +# optimization." `arXiv:1910.01155 `__ (2019). +# +# .. [#kubler2020] +# +# Jonas M. Kübler, Andrew Arrasmith, Lukasz Cincio, and Patrick J. Coles. "An Adaptive Optimizer +# for Measurement-Frugal Variational Algorithms." `Quantum 4, 263 +# `__ (2020). +# +# +# About the author +# ---------------- # \ No newline at end of file diff --git a/demonstrations/vqe_parallel.py b/demonstrations/vqe_parallel.py index 91c47d7bf1..7c298007e8 100644 --- a/demonstrations/vqe_parallel.py +++ b/demonstrations/vqe_parallel.py @@ -1,393 +1,393 @@ - -# coding=utf-8 -r""" -VQE with parallel QPUs with Rigetti -======================================== - -.. meta:: - :property="og:description": Using parallel QPUs to - speed up the calculation of the potential energy surface of molecular Hamiltonian. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/vqe_diagram.png - -.. related:: - - tutorial_vqe A brief overview of VQE - -*Author: Tom Bromley — Posted: 14 February 2020. Last updated: 29 August 2023.* - -.. warning:: - This demo requires Python <=3.10 and uses the PennyLane-Rigetti plugin, which is only compatible with PennyLane v0.40 or below. To run this demo with newer versions of PennyLane, you will need to use `a different simulator device `__. - -This tutorial showcases how using asynchronously-evaluated parallel QPUs can speed up the -calculation of the potential energy surface of molecular hydrogen (:math:`H_2`). - -Using a VQE setup, we task two devices from the -`PennyLane-Rigetti `__ plugin with evaluating -separate terms in the qubit Hamiltonian of :math:`H_2.` As these devices are allowed to operate -asynchronously, i.e., at the same time and without having to wait for each other, -the calculation can be performed in roughly half the time. - -We begin by importing the prerequisite libraries: -""" - -import time -import dask - -import matplotlib.pyplot as plt -from pennylane import numpy as np -import pennylane as qml -from pennylane import qchem - -############################################################################## -# -# This tutorial requires the ``pennylane-rigetti`` and ``dask`` -# packages, which are installed separately using: -# -# .. code-block:: bash -# -# pip install pennylane-rigetti -# pip install "dask[delayed]" -# -# Finding the qubit Hamiltonians of :math:`H_{2}` -# ----------------------------------------------- -# -# The objective of this tutorial is to evaluate the potential energy surface of molecular -# hydrogen. This is achieved by finding the ground state energy of :math:`H_{2}` as we increase -# the bond length between the hydrogen atoms. -# -# Each inter-atomic distance results in a different qubit Hamiltonian. Further -# details on the mapping from the electronic Hamiltonian of a molecule to a -# qubit Hamiltonian can be found in the -# :doc:`tutorial_quantum_chemistry` and :doc:`tutorial_vqe` -# tutorials. -# -# We begin by downloading a selection of datasets of :math:`H_2` molecule for -# various bond lengths using the -# `PennyLane Datasets library `__: - -bonds = [0.5, 0.58, 0.7, 0.9, 1.1, 1.3, 1.5, 1.7, 1.9, 2.1] -datasets = qml.data.load("qchem", molname="H2", bondlength=bonds, basis="STO-3G") - -############################################################################## -# We can now extract the qubit Hamiltonians from these datasets for each bond length: - -hamiltonians = [d.hamiltonian for d in datasets] - -############################################################################## -# Each Hamiltonian can be written as a linear combination of fifteen tensor products of Pauli -# matrices. Let's take a look more closely at one of the Hamiltonians: - -h = hamiltonians[0] -_, h_ops = h.terms() - -print("Number of terms: {}\n".format(len(h_ops))) -for op in h_ops: - print("Measurement {} on wires {}".format(str(op), op.wires)) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# Number of terms: 15 -# -# Measurement I(0) on wires Wires([0]) -# Measurement Z(0) on wires Wires([0]) -# Measurement Z(1) on wires Wires([1]) -# Measurement Z(0) @ Z(1) on wires Wires([0, 1]) -# Measurement Y(0) @ X(1) @ X(2) @ Y(3) on wires Wires([0, 1, 2, 3]) -# Measurement Y(0) @ Y(1) @ X(2) @ X(3) on wires Wires([0, 1, 2, 3]) -# Measurement X(0) @ X(1) @ Y(2) @ Y(3) on wires Wires([0, 1, 2, 3]) -# Measurement X(0) @ Y(1) @ Y(2) @ X(3) on wires Wires([0, 1, 2, 3]) -# Measurement Z(2) on wires Wires([2]) -# Measurement Z(0) @ Z(2) on wires Wires([0, 2]) -# Measurement Z(3) on wires Wires([3]) -# Measurement Z(0) @ Z(3) on wires Wires([0, 3]) -# Measurement Z(1) @ Z(2) on wires Wires([1, 2]) -# Measurement Z(1) @ Z(3) on wires Wires([1, 3]) -# Measurement Z(2) @ Z(3) on wires Wires([2, 3]) - -############################################################################## -# Defining the energy function -# ---------------------------- -# -# The fifteen Pauli terms comprising each Hamiltonian can conventionally be evaluated in a -# sequential manner: we evaluate one expectation value at a time before moving on to the next. -# However, this task is highly suited to parallelization. With access to multiple QPUs, -# we can split up evaluating the terms between the QPUs and gain an increase in processing speed. -# -# -# .. note:: -# Some of the Pauli terms commute, and so they can be evaluated in practice with fewer than -# fifteen quantum circuit runs. Nevertheless, these quantum circuit runs can still be -# parallelized to multiple QPUs. -# -# Let's suppose we have access to two quantum devices. In this tutorial we consider two -# simulators from Rigetti: ``4q-qvm`` and ``9q-square-qvm``, but we could also run on hardware -# devices from Rigetti or other providers. -# -# We can evaluate the expectation value of each Hamiltonian with eight terms run on -# one device and seven terms run on the other, as summarized by the diagram below: -# -# .. figure:: /_static/demonstration_assets/vqe_parallel/vqe_diagram.png -# :width: 65% -# :align: center -# -# To do this, start by instantiating a device for each term: - -dev1 = [qml.device("rigetti.qvm", device="4q-qvm") for _ in range(8)] -dev2 = [qml.device("rigetti.qvm", device="9q-square-qvm") for _ in range(7)] -devs = dev1 + dev2 - -############################################################################## -# .. note:: -# -# For the purposes of this demonstration, we are simulating the QPUs using the -# ``rigetti.qvm`` simulator. To run this demonstration on hardware, simply -# swap ``rigetti.qvm`` for ``rigetti.qpu`` and specify the hardware device to run on. -# -# Please refer to the `Rigetti website `__ for an up-to-date -# list on available QPUs. -# -# .. warning:: -# Rigetti's QVM and Quil Compiler services must be running for this tutorial to execute. They -# can be installed by consulting the `Rigetti documentation -# `__ or, for users with Docker, by running: -# -# .. code-block:: bash -# -# docker run -d -p 5555:5555 rigetti/quilc -R -p 5555 -# docker run -d -p 5000:5000 rigetti/qvm -S -p 5000 -# -# We must also define a circuit to prepare the ground state, which is a superposition of the -# Hartree-Fock (:math:`|1100\rangle`) and doubly-excited (:math:`|0011\rangle`) configurations. -# The simple circuit below is able to prepare states of the form :math:`\alpha |1100\rangle + -# \beta |0011\rangle` and hence encode the ground state wave function of the hydrogen molecule. The -# circuit has a single free parameter, which controls a Y-rotation on the third qubit. - - -def circuit(param, H): - qml.BasisState(np.array([1, 1, 0, 0], requires_grad=False), wires=[0, 1, 2, 3]) - qml.RY(param, wires=2) - qml.CNOT(wires=[2, 3]) - qml.CNOT(wires=[2, 0]) - qml.CNOT(wires=[3, 1]) - return qml.expval(H) - - -############################################################################## -# The ground state for each inter-atomic distance is characterized by a different Y-rotation angle. -# The values of these Y-rotations can be found by minimizing the ground state energy as outlined in -# :doc:`tutorial_vqe`. In this tutorial, we load pre-optimized rotations and focus on -# comparing the speed of evaluating the potential energy surface with sequential and parallel -# evaluation. These parameters can be downloaded by clicking :download:`here -# <../_static/demonstration_assets/vqe_parallel/RY_params.npy>`. - -params = np.load("vqe_parallel/RY_params.npy") - -############################################################################## -# Calculating the potential energy surface -# ---------------------------------------- -# The most vanilla execution of these 10 energy surfaces is using the standard PennyLane functionalities by executing the QNodes. -# Internally, this creates a measurement for each term in the Hamiltonian that are then sequentially computed. - -print("Evaluating the potential energy surface sequentially") -t0 = time.time() - -energies_seq = [] -for i, (h, param) in enumerate(zip(hamiltonians, params)): - print( - f"{i+1} / {len(bonds)}: Sequential execution; Running for inter-atomic distance {bonds[i]} Å" - ) - energies_seq.append(qml.QNode(circuit, devs[0])(param, h)) - -dt_seq = time.time() - t0 - -print(f"Evaluation time: {dt_seq:.2f} s") - -############################################################################## -# We can parallelize the individual evaluations using ``dask`` in the following way: We take the 15 terms of the Hamiltonian and -# distribute them to the 15 devices in ``devs``. This evaluation is delayed using ``dask.delayed`` and later computed -# in parallel using ``dask.compute``, which asynchronously executes the delayed objects in ``results``. - - -def compute_energy_parallel(H, devs, param): - H_coeffs, H_ops = H.terms() - assert len(H_ops) == len(devs) - results = [] - - for i in range(len(H_ops)): - qnode = qml.QNode(circuit, devs[i]) - results.append(dask.delayed(qnode)(param, H_ops[i])) - - results = dask.compute(*results, scheduler="threads") - result = sum(c * r for c, r in zip(H_coeffs, results)) - return result - - -############################################################################## -# We can now compute all 10 samples from the energy surface sequentially, where each execution is making use of -# parallel device execution. Curiously, in this example the overhead from doing so outweighs the speed-up -# and the execution is slower than standard execution using ``qml.expval``. For different circuits and -# different Hamiltonians, however, parallelization may provide significant speed-ups. - -print("Evaluating the potential energy surface in parallel") -t0 = time.time() - -energies_par = [] -for i, (h, param) in enumerate(zip(hamiltonians, params)): - print( - f"{i+1} / {len(bonds)}: Parallel execution; Running for inter-atomic distance {bonds[i]} Å" - ) - energies_par.append(compute_energy_parallel(h, devs, param)) - -dt_par = time.time() - t0 - -print(f"Evaluation time: {dt_par:.2f} s") - - -############################################################################## -# We can improve this procedure further by optimizing the measurement. Currently, we are measuring each term of the Hamiltonian -# in a separate measurement. This is not necessary as there are sub-groups of commuting terms in the Hamiltonian that can be measured -# simultaneously. We can utilize the grouping function :func:`~.pennylane.pauli.group_observables` to generate few measurements that -# are executed in parallel: - - -def compute_energy_parallel_optimized(H, devs, param): - H_coeffs, H_ops = H.terms() - assert len(H_ops) == len(devs) - results = [] - - obs_groupings, coeffs_groupings = qml.pauli.group_observables( - H_ops, H_coeffs, "qwc" - ) - - for i, (obs, coeffs) in enumerate(zip(obs_groupings, coeffs_groupings)): - H_part = qml.Hamiltonian(coeffs, obs) - qnode = qml.QNode(circuit, devs[i]) - results.append(dask.delayed(qnode)(param, H_part)) - - result = qml.math.sum(dask.compute(*results, scheduler="threads")) - return result - -print( - "Evaluating the potential energy surface in parallel with measurement optimization" -) -t0 = time.time() - -energies_par_opt = [] -for i, (h, param) in enumerate(zip(hamiltonians, params)): - print( - f"{i+1} / {len(bonds)}: Parallel execution and measurement optimization; Running for inter-atomic distance {bonds[i]} Å" - ) - energies_par_opt.append(compute_energy_parallel_optimized(h, devs, param)) - -dt_par_opt = time.time() - t0 - -print(f"Evaluation time: {dt_par_opt:.2f} s") - - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# Evaluating the potential energy surface sequentially -# 1 / 10: Sequential execution; Running for inter-atomic distance 0.3 Å -# 2 / 10: Sequential execution; Running for inter-atomic distance 0.5 Å -# 3 / 10: Sequential execution; Running for inter-atomic distance 0.7 Å -# 4 / 10: Sequential execution; Running for inter-atomic distance 0.9 Å -# 5 / 10: Sequential execution; Running for inter-atomic distance 1.1 Å -# 6 / 10: Sequential execution; Running for inter-atomic distance 1.3 Å -# 7 / 10: Sequential execution; Running for inter-atomic distance 1.5 Å -# 8 / 10: Sequential execution; Running for inter-atomic distance 1.7 Å -# 9 / 10: Sequential execution; Running for inter-atomic distance 1.9 Å -# 10 / 10: Sequential execution; Running for inter-atomic distance 2.1 Å -# Evaluation time: 39.33 s -# -# Evaluating the potential energy surface in parallel -# 1 / 10: Parallel execution; Running for inter-atomic distance 0.3 Å -# 2 / 10: Parallel execution; Running for inter-atomic distance 0.5 Å -# 3 / 10: Parallel execution; Running for inter-atomic distance 0.7 Å -# 4 / 10: Parallel execution; Running for inter-atomic distance 0.9 Å -# 5 / 10: Parallel execution; Running for inter-atomic distance 1.1 Å -# 6 / 10: Parallel execution; Running for inter-atomic distance 1.3 Å -# 7 / 10: Parallel execution; Running for inter-atomic distance 1.5 Å -# 8 / 10: Parallel execution; Running for inter-atomic distance 1.7 Å -# 9 / 10: Parallel execution; Running for inter-atomic distance 1.9 Å -# 10 / 10: Parallel execution; Running for inter-atomic distance 2.1 Å -# Evaluation time: 73.42 s -# -# Evaluating the potential energy surface in parallel with measurement optimization -# 1 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.3 Å -# 2 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.5 Å -# 3 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.7 Å -# 4 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.9 Å -# 5 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.1 Å -# 6 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.3 Å -# 7 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.5 Å -# 8 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.7 Å -# 9 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.9 Å -# 10 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 2.1 Å -# Evaluation time: 26.51 s - - -############################################################################## -# We have seen how Hamiltonian measurements can be parallelized and optimized at the same time. - -print("Speed up: {0:.2f}".format(dt_seq / dt_par_opt)) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# Speed up: 1.48 - -############################################################################## -# To conclude the tutorial, let's plot the calculated -# potential energy surfaces: - -np.savez( - "vqe_parallel", - energies_seq=energies_seq, - energies_par=energies_par, - energies_par_opt=energies_par_opt, -) - -plt.plot( - bonds, energies_seq, linewidth=2.2, marker="d", color="green", label="sequential" -) -plt.plot(bonds, energies_par, linewidth=2.2, marker="o", color="red", label="parallel") -plt.plot( - bonds, - energies_par_opt, - linewidth=2.2, - marker="d", - color="blue", - label="paralell and optimized", -) -plt.legend(fontsize=12) -plt.title("Potential energy surface for molecular hydrogen", fontsize=12) -plt.xlabel("Atomic separation (Å)", fontsize=16) -plt.ylabel("Ground state energy (Ha)", fontsize=16) -plt.grid(True) - -############################################################################## -# .. figure:: /_static/demonstration_assets/vqe_parallel/vqe_parallel_001.png -# :width: 80% -# :align: center -# - -############################################################################## -# These surfaces overlap, with any variation due to the limited number of shots used to evaluate the -# expectation values in the ``rigetti.qvm`` device (we are using the default value of -# ``shots=1024``). - -############################################################################## -# About the author -# ---------------- -# + +# coding=utf-8 +r""" +VQE with parallel QPUs with Rigetti +======================================== + +.. meta:: + :property="og:description": Using parallel QPUs to + speed up the calculation of the potential energy surface of molecular Hamiltonian. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/vqe_diagram.png + +.. related:: + + tutorial_vqe A brief overview of VQE + +*Author: Tom Bromley — Posted: 14 February 2020. Last updated: 29 August 2023.* + +.. warning:: + This demo requires Python <=3.10 and uses the PennyLane-Rigetti plugin, which is only compatible with PennyLane v0.40 or below. To run this demo with newer versions of PennyLane, you will need to use `a different simulator device `__. + +This tutorial showcases how using asynchronously-evaluated parallel QPUs can speed up the +calculation of the potential energy surface of molecular hydrogen (:math:`H_2`). + +Using a VQE setup, we task two devices from the +`PennyLane-Rigetti `__ plugin with evaluating +separate terms in the qubit Hamiltonian of :math:`H_2.` As these devices are allowed to operate +asynchronously, i.e., at the same time and without having to wait for each other, +the calculation can be performed in roughly half the time. + +We begin by importing the prerequisite libraries: +""" + +import time +import dask + +import matplotlib.pyplot as plt +from pennylane import numpy as np +import pennylane as qml +from pennylane import qchem + +############################################################################## +# +# This tutorial requires the ``pennylane-rigetti`` and ``dask`` +# packages, which are installed separately using: +# +# .. code-block:: bash +# +# pip install pennylane-rigetti +# pip install "dask[delayed]" +# +# Finding the qubit Hamiltonians of :math:`H_{2}` +# ----------------------------------------------- +# +# The objective of this tutorial is to evaluate the potential energy surface of molecular +# hydrogen. This is achieved by finding the ground state energy of :math:`H_{2}` as we increase +# the bond length between the hydrogen atoms. +# +# Each inter-atomic distance results in a different qubit Hamiltonian. Further +# details on the mapping from the electronic Hamiltonian of a molecule to a +# qubit Hamiltonian can be found in the +# :doc:`tutorial_quantum_chemistry` and :doc:`tutorial_vqe` +# tutorials. +# +# We begin by downloading a selection of datasets of :math:`H_2` molecule for +# various bond lengths using the +# `PennyLane Datasets library `__: + +bonds = [0.5, 0.58, 0.7, 0.9, 1.1, 1.3, 1.5, 1.7, 1.9, 2.1] +datasets = qml.data.load("qchem", molname="H2", bondlength=bonds, basis="STO-3G") + +############################################################################## +# We can now extract the qubit Hamiltonians from these datasets for each bond length: + +hamiltonians = [d.hamiltonian for d in datasets] + +############################################################################## +# Each Hamiltonian can be written as a linear combination of fifteen tensor products of Pauli +# matrices. Let's take a look more closely at one of the Hamiltonians: + +h = hamiltonians[0] +_, h_ops = h.terms() + +print("Number of terms: {}\n".format(len(h_ops))) +for op in h_ops: + print("Measurement {} on wires {}".format(str(op), op.wires)) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Number of terms: 15 +# +# Measurement I(0) on wires Wires([0]) +# Measurement Z(0) on wires Wires([0]) +# Measurement Z(1) on wires Wires([1]) +# Measurement Z(0) @ Z(1) on wires Wires([0, 1]) +# Measurement Y(0) @ X(1) @ X(2) @ Y(3) on wires Wires([0, 1, 2, 3]) +# Measurement Y(0) @ Y(1) @ X(2) @ X(3) on wires Wires([0, 1, 2, 3]) +# Measurement X(0) @ X(1) @ Y(2) @ Y(3) on wires Wires([0, 1, 2, 3]) +# Measurement X(0) @ Y(1) @ Y(2) @ X(3) on wires Wires([0, 1, 2, 3]) +# Measurement Z(2) on wires Wires([2]) +# Measurement Z(0) @ Z(2) on wires Wires([0, 2]) +# Measurement Z(3) on wires Wires([3]) +# Measurement Z(0) @ Z(3) on wires Wires([0, 3]) +# Measurement Z(1) @ Z(2) on wires Wires([1, 2]) +# Measurement Z(1) @ Z(3) on wires Wires([1, 3]) +# Measurement Z(2) @ Z(3) on wires Wires([2, 3]) + +############################################################################## +# Defining the energy function +# ---------------------------- +# +# The fifteen Pauli terms comprising each Hamiltonian can conventionally be evaluated in a +# sequential manner: we evaluate one expectation value at a time before moving on to the next. +# However, this task is highly suited to parallelization. With access to multiple QPUs, +# we can split up evaluating the terms between the QPUs and gain an increase in processing speed. +# +# +# .. note:: +# Some of the Pauli terms commute, and so they can be evaluated in practice with fewer than +# fifteen quantum circuit runs. Nevertheless, these quantum circuit runs can still be +# parallelized to multiple QPUs. +# +# Let's suppose we have access to two quantum devices. In this tutorial we consider two +# simulators from Rigetti: ``4q-qvm`` and ``9q-square-qvm``, but we could also run on hardware +# devices from Rigetti or other providers. +# +# We can evaluate the expectation value of each Hamiltonian with eight terms run on +# one device and seven terms run on the other, as summarized by the diagram below: +# +# .. figure:: /_static/demonstration_assets/vqe_parallel/vqe_diagram.png +# :width: 65% +# :align: center +# +# To do this, start by instantiating a device for each term: + +dev1 = [qml.device("rigetti.qvm", device="4q-qvm") for _ in range(8)] +dev2 = [qml.device("rigetti.qvm", device="9q-square-qvm") for _ in range(7)] +devs = dev1 + dev2 + +############################################################################## +# .. note:: +# +# For the purposes of this demonstration, we are simulating the QPUs using the +# ``rigetti.qvm`` simulator. To run this demonstration on hardware, simply +# swap ``rigetti.qvm`` for ``rigetti.qpu`` and specify the hardware device to run on. +# +# Please refer to the `Rigetti website `__ for an up-to-date +# list on available QPUs. +# +# .. warning:: +# Rigetti's QVM and Quil Compiler services must be running for this tutorial to execute. They +# can be installed by consulting the `Rigetti documentation +# `__ or, for users with Docker, by running: +# +# .. code-block:: bash +# +# docker run -d -p 5555:5555 rigetti/quilc -R -p 5555 +# docker run -d -p 5000:5000 rigetti/qvm -S -p 5000 +# +# We must also define a circuit to prepare the ground state, which is a superposition of the +# Hartree-Fock (:math:`|1100\rangle`) and doubly-excited (:math:`|0011\rangle`) configurations. +# The simple circuit below is able to prepare states of the form :math:`\alpha |1100\rangle + +# \beta |0011\rangle` and hence encode the ground state wave function of the hydrogen molecule. The +# circuit has a single free parameter, which controls a Y-rotation on the third qubit. + + +def circuit(param, H): + qml.BasisState(np.array([1, 1, 0, 0], requires_grad=False), wires=[0, 1, 2, 3]) + qml.RY(param, wires=2) + qml.CNOT(wires=[2, 3]) + qml.CNOT(wires=[2, 0]) + qml.CNOT(wires=[3, 1]) + return qml.expval(H) + + +############################################################################## +# The ground state for each inter-atomic distance is characterized by a different Y-rotation angle. +# The values of these Y-rotations can be found by minimizing the ground state energy as outlined in +# :doc:`tutorial_vqe`. In this tutorial, we load pre-optimized rotations and focus on +# comparing the speed of evaluating the potential energy surface with sequential and parallel +# evaluation. These parameters can be downloaded by clicking :download:`here +# <../_static/demonstration_assets/vqe_parallel/RY_params.npy>`. + +params = np.load("vqe_parallel/RY_params.npy") + +############################################################################## +# Calculating the potential energy surface +# ---------------------------------------- +# The most vanilla execution of these 10 energy surfaces is using the standard PennyLane functionalities by executing the QNodes. +# Internally, this creates a measurement for each term in the Hamiltonian that are then sequentially computed. + +print("Evaluating the potential energy surface sequentially") +t0 = time.time() + +energies_seq = [] +for i, (h, param) in enumerate(zip(hamiltonians, params)): + print( + f"{i+1} / {len(bonds)}: Sequential execution; Running for inter-atomic distance {bonds[i]} Å" + ) + energies_seq.append(qml.QNode(circuit, devs[0])(param, h)) + +dt_seq = time.time() - t0 + +print(f"Evaluation time: {dt_seq:.2f} s") + +############################################################################## +# We can parallelize the individual evaluations using ``dask`` in the following way: We take the 15 terms of the Hamiltonian and +# distribute them to the 15 devices in ``devs``. This evaluation is delayed using ``dask.delayed`` and later computed +# in parallel using ``dask.compute``, which asynchronously executes the delayed objects in ``results``. + + +def compute_energy_parallel(H, devs, param): + H_coeffs, H_ops = H.terms() + assert len(H_ops) == len(devs) + results = [] + + for i in range(len(H_ops)): + qnode = qml.QNode(circuit, devs[i]) + results.append(dask.delayed(qnode)(param, H_ops[i])) + + results = dask.compute(*results, scheduler="threads") + result = sum(c * r for c, r in zip(H_coeffs, results)) + return result + + +############################################################################## +# We can now compute all 10 samples from the energy surface sequentially, where each execution is making use of +# parallel device execution. Curiously, in this example the overhead from doing so outweighs the speed-up +# and the execution is slower than standard execution using ``qml.expval``. For different circuits and +# different Hamiltonians, however, parallelization may provide significant speed-ups. + +print("Evaluating the potential energy surface in parallel") +t0 = time.time() + +energies_par = [] +for i, (h, param) in enumerate(zip(hamiltonians, params)): + print( + f"{i+1} / {len(bonds)}: Parallel execution; Running for inter-atomic distance {bonds[i]} Å" + ) + energies_par.append(compute_energy_parallel(h, devs, param)) + +dt_par = time.time() - t0 + +print(f"Evaluation time: {dt_par:.2f} s") + + +############################################################################## +# We can improve this procedure further by optimizing the measurement. Currently, we are measuring each term of the Hamiltonian +# in a separate measurement. This is not necessary as there are sub-groups of commuting terms in the Hamiltonian that can be measured +# simultaneously. We can utilize the grouping function :func:`~.pennylane.pauli.group_observables` to generate few measurements that +# are executed in parallel: + + +def compute_energy_parallel_optimized(H, devs, param): + H_coeffs, H_ops = H.terms() + assert len(H_ops) == len(devs) + results = [] + + obs_groupings, coeffs_groupings = qml.pauli.group_observables( + H_ops, H_coeffs, "qwc" + ) + + for i, (obs, coeffs) in enumerate(zip(obs_groupings, coeffs_groupings)): + H_part = qml.Hamiltonian(coeffs, obs) + qnode = qml.QNode(circuit, devs[i]) + results.append(dask.delayed(qnode)(param, H_part)) + + result = qml.math.sum(dask.compute(*results, scheduler="threads")) + return result + +print( + "Evaluating the potential energy surface in parallel with measurement optimization" +) +t0 = time.time() + +energies_par_opt = [] +for i, (h, param) in enumerate(zip(hamiltonians, params)): + print( + f"{i+1} / {len(bonds)}: Parallel execution and measurement optimization; Running for inter-atomic distance {bonds[i]} Å" + ) + energies_par_opt.append(compute_energy_parallel_optimized(h, devs, param)) + +dt_par_opt = time.time() - t0 + +print(f"Evaluation time: {dt_par_opt:.2f} s") + + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Evaluating the potential energy surface sequentially +# 1 / 10: Sequential execution; Running for inter-atomic distance 0.3 Å +# 2 / 10: Sequential execution; Running for inter-atomic distance 0.5 Å +# 3 / 10: Sequential execution; Running for inter-atomic distance 0.7 Å +# 4 / 10: Sequential execution; Running for inter-atomic distance 0.9 Å +# 5 / 10: Sequential execution; Running for inter-atomic distance 1.1 Å +# 6 / 10: Sequential execution; Running for inter-atomic distance 1.3 Å +# 7 / 10: Sequential execution; Running for inter-atomic distance 1.5 Å +# 8 / 10: Sequential execution; Running for inter-atomic distance 1.7 Å +# 9 / 10: Sequential execution; Running for inter-atomic distance 1.9 Å +# 10 / 10: Sequential execution; Running for inter-atomic distance 2.1 Å +# Evaluation time: 39.33 s +# +# Evaluating the potential energy surface in parallel +# 1 / 10: Parallel execution; Running for inter-atomic distance 0.3 Å +# 2 / 10: Parallel execution; Running for inter-atomic distance 0.5 Å +# 3 / 10: Parallel execution; Running for inter-atomic distance 0.7 Å +# 4 / 10: Parallel execution; Running for inter-atomic distance 0.9 Å +# 5 / 10: Parallel execution; Running for inter-atomic distance 1.1 Å +# 6 / 10: Parallel execution; Running for inter-atomic distance 1.3 Å +# 7 / 10: Parallel execution; Running for inter-atomic distance 1.5 Å +# 8 / 10: Parallel execution; Running for inter-atomic distance 1.7 Å +# 9 / 10: Parallel execution; Running for inter-atomic distance 1.9 Å +# 10 / 10: Parallel execution; Running for inter-atomic distance 2.1 Å +# Evaluation time: 73.42 s +# +# Evaluating the potential energy surface in parallel with measurement optimization +# 1 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.3 Å +# 2 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.5 Å +# 3 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.7 Å +# 4 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.9 Å +# 5 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.1 Å +# 6 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.3 Å +# 7 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.5 Å +# 8 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.7 Å +# 9 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.9 Å +# 10 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 2.1 Å +# Evaluation time: 26.51 s + + +############################################################################## +# We have seen how Hamiltonian measurements can be parallelized and optimized at the same time. + +print("Speed up: {0:.2f}".format(dt_seq / dt_par_opt)) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Speed up: 1.48 + +############################################################################## +# To conclude the tutorial, let's plot the calculated +# potential energy surfaces: + +np.savez( + "vqe_parallel", + energies_seq=energies_seq, + energies_par=energies_par, + energies_par_opt=energies_par_opt, +) + +plt.plot( + bonds, energies_seq, linewidth=2.2, marker="d", color="green", label="sequential" +) +plt.plot(bonds, energies_par, linewidth=2.2, marker="o", color="red", label="parallel") +plt.plot( + bonds, + energies_par_opt, + linewidth=2.2, + marker="d", + color="blue", + label="paralell and optimized", +) +plt.legend(fontsize=12) +plt.title("Potential energy surface for molecular hydrogen", fontsize=12) +plt.xlabel("Atomic separation (Å)", fontsize=16) +plt.ylabel("Ground state energy (Ha)", fontsize=16) +plt.grid(True) + +############################################################################## +# .. figure:: /_static/demonstration_assets/vqe_parallel/vqe_parallel_001.png +# :width: 80% +# :align: center +# + +############################################################################## +# These surfaces overlap, with any variation due to the limited number of shots used to evaluate the +# expectation values in the ``rigetti.qvm`` device (we are using the default value of +# ``shots=1024``). + +############################################################################## +# About the author +# ---------------- +# diff --git a/demonstrations_v2/ensemble_multi_qpu/demo.py b/demonstrations_v2/ensemble_multi_qpu/demo.py index 8b68a43741..eaf0bc3ea3 100644 --- a/demonstrations_v2/ensemble_multi_qpu/demo.py +++ b/demonstrations_v2/ensemble_multi_qpu/demo.py @@ -1,581 +1,581 @@ -r""" -Ensemble classification with Rigetti and Qiskit devices -======================================================= - -.. meta:: - :property="og:description": We demonstrate how two QPUs can be - combined in parallel to help solve a machine learning classification problem, - using PyTorch and PennyLane. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/ensemble_diagram.png - -.. related - - tutorial_variational_classifier Variational classifier - -*Author: Tom Bromley — Posted: 14 February 2020. Last updated: 13 December 2021.* - -This tutorial outlines how two QPUs can be combined in parallel to help solve a machine learning -classification problem. - -.. warning:: - This demo does not work with the latest version of Qiskit or the Pennylane-Qiskit plugin and - is compatible with Python versions 3.10 and lower. It is compatible with ``qiskit==0.46`` and - ``pennylane-qiskit==0.35.1``. Older versions of Qiskit and the Pennylane-Qiskit plugin should - not be installed in environments with an existing installation of Qiskit 1.0 or above. - -We use the ``rigetti.qvm`` device to simulate one QPU and the ``qiskit.aer`` device to -simulate another. Each QPU makes an independent prediction, and an ensemble model is -formed by choosing the prediction of the most confident QPU. The iris dataset is used in this -tutorial, consisting of three classes of iris flower. Using a pre-trained model and the PyTorch -interface, we'll see that ensembling allows the QPUs to specialize towards -different classes. - -Let's begin by importing the prerequisite libraries: -""" - -from collections import Counter - -import dask -import matplotlib.pyplot as plt -import numpy as np -import pennylane as qml -import sklearn.datasets -import sklearn.decomposition -import torch -from matplotlib.lines import Line2D -from matplotlib.patches import Patch - -############################################################################## -# This tutorial requires the ``pennylane-rigetti`` and ``pennylane-qiskit`` packages, which can be -# installed by following the instructions `here `__. We also -# make use of the `PyTorch interface `_, which can be installed from `here -# `__. -# -# .. warning:: -# Rigetti's QVM and Quil Compiler services must be running for this tutorial to execute. They -# can be installed by consulting the `Rigetti documentation -# `__ or, for users with Docker, by running: -# -# .. code-block:: bash -# -# docker run -d -p 5555:5555 rigetti/quilc -R -p 5555 -# docker run -d -p 5000:5000 rigetti/qvm -S -p 5000 -# -# Load data -# --------- -# -# The next step is to load the iris dataset. - -n_features = 2 -n_classes = 3 -n_samples = 150 - -data = sklearn.datasets.load_iris() -x = data["data"] -y = data["target"] - -############################################################################## -# We shuffle the data and then embed the four features into a two-dimensional space for ease of -# plotting later on. The first two principal components of the data are used. - -np.random.seed(1967) - -data_order = np.random.permutation(np.arange(n_samples)) -x, y = x[data_order], y[data_order] - -pca = sklearn.decomposition.PCA(n_components=n_features) -pca.fit(x) -x = pca.transform(x) - -############################################################################## -# We will be encoding these two features into quantum circuits using :class:`~.pennylane.RX` -# rotations, and hence renormalize our features to be between :math:`[-\pi, \pi].` - - -x_min = np.min(x, axis=0) -x_max = np.max(x, axis=0) - -x = 2 * np.pi * (x - x_min) / (x_max - x_min) - np.pi - -############################################################################## -# The data is split between a training and a test set. This tutorial uses a model that is -# pre-trained on the training set. - - -split = 125 - -x_train = x[:split] -x_test = x[split:] -y_train = y[:split] -y_test = y[split:] - -############################################################################## -# Finally, let's take a quick look at our data: - - -colours = ["#ec6f86", "#4573e7", "#ad61ed"] - - -def plot_points(x_train, y_train, x_test, y_test): - c_train = [] - c_test = [] - - for y in y_train: - c_train.append(colours[y]) - - for y in y_test: - c_test.append(colours[y]) - - plt.scatter(x_train[:, 0], x_train[:, 1], c=c_train) - plt.scatter(x_test[:, 0], x_test[:, 1], c=c_test, marker="x") - - plt.xlabel("Feature 1", fontsize=16) - plt.ylabel("Feature 2", fontsize=16) - - ax = plt.gca() - ax.set_aspect(1) - - c_transparent = "#00000000" - - custom_lines = [ - Patch(facecolor=colours[0], edgecolor=c_transparent, label="Class 0"), - Patch(facecolor=colours[1], edgecolor=c_transparent, label="Class 1"), - Patch(facecolor=colours[2], edgecolor=c_transparent, label="Class 2"), - Line2D([0], [0], marker="o", color=c_transparent, label="Train", - markerfacecolor="black", markersize=10), - Line2D([0], [0], marker="x", color=c_transparent, label="Test", - markerfacecolor="black", markersize=10), - ] - - ax.legend(handles=custom_lines, bbox_to_anchor=(1.0, 0.75)) - - -plot_points(x_train, y_train, x_test, y_test) -plt.show() - -############################################################################## -# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_001.png -# :width: 80% -# :align: center -# - -############################################################################## -# This plot shows us that class 0 points can be nicely separated, but that there is an overlap -# between points from classes 1 and 2. -# -# Define model -# ------------ -# -# Our model is summarized in the figure below. We use two 4-qubit devices: ``4q-qvm`` -# from the pennyLane-rigetti plugin and ``qiskit.aer`` from the PennyLane-Qiskit plugin. -# -# Data is input using :class:`~.pennylane.RX` rotations and then a different circuit is enacted -# for each device with a unique set of trainable parameters. The output of both circuits is a -# :class:`~.pennylane.PauliZ` measurement on three of the qubits. This is then fed through a -# softmax function, resulting in two 3-dimensional probability vectors corresponding to the 3 -# classes. -# -# Finally, the ensemble model chooses the QPU which is most confident about its prediction -# (i.e., the class with the highest overall probability over all QPUs) and uses that to make a -# prediction. -# -# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_diagram.png -# :width: 80% -# :align: center -# -# Quantum nodes -# ^^^^^^^^^^^^^ -# -# We begin by defining the two quantum devices and the circuits to be run on them. - -n_wires = 4 - -dev0 = qml.device("rigetti.qvm", device="4q-qvm") -dev1 = qml.device("qiskit.aer", wires=4) -devs = [dev0, dev1] - -############################################################################## -# .. note:: -# If you have access to Rigetti hardware, you can swap out ``rigetti.qvm`` for ``rigetti.qpu`` -# and specify the hardware device to run on. Users with access to the IBM hardware can -# swap ``qiskit.aer`` for ``qiskit.remote`` and specify their chosen backend (see `here -# `__). -# -# -# The circuits for both QPUs are shown in the figure below: -# -# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/diagram_circuits.png -# :width: 80% -# :align: center - - -def circuit0(params, x=None): - for i in range(n_wires): - qml.RX(x[i % n_features], wires=i) - qml.Rot(*params[1, 0, i], wires=i) - - qml.CZ(wires=[1, 0]) - qml.CZ(wires=[1, 2]) - qml.CZ(wires=[3, 0]) - - for i in range(n_wires): - qml.Rot(*params[1, 1, i], wires=i) - return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)), qml.expval(qml.PauliZ(2)) - - -def circuit1(params, x=None): - for i in range(n_wires): - qml.RX(x[i % n_features], wires=i) - qml.Rot(*params[0, 0, i], wires=i) - - qml.CZ(wires=[0, 1]) - qml.CZ(wires=[1, 2]) - qml.CZ(wires=[1, 3]) - - for i in range(n_wires): - qml.Rot(*params[0, 1, i], wires=i) - return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)), qml.expval(qml.PauliZ(2)) - - -############################################################################## -# We finally combine the two devices into a :class:`~.pennylane.QNode` list: - - -qnodes = [ - qml.QNode(circuit0, dev0), - qml.QNode(circuit1, dev1), -] - -############################################################################## -# Postprocessing into a prediction -# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# -# The ``predict_point`` function below allows us to find the ensemble prediction, as well as keeping -# track of the individual predictions from each QPU. -# -# We include a ``parallel`` keyword argument for evaluating the :class:`~.pennylane.QNode` list -# in a parallel asynchronous manner. This feature requires the ``dask`` library, which can be -# installed using ``pip install "dask[delayed]"``. When ``parallel=True``, we are able to make -# predictions faster because we do not need to wait for one QPU to output before running on the -# other. - -def decision(softmax): - return int(torch.argmax(softmax)) - - -def predict_point(params, x_point=None, parallel=True): - if parallel: - results = tuple(dask.delayed(q)(params, x=torch.from_numpy(x_point)) for q in qnodes) - results = torch.tensor(dask.compute(*results, scheduler="threads")) - else: - results = tuple(q(params, x=x_point) for q in qnodes) - results = torch.tensor(results) - softmax = torch.nn.functional.softmax(results, dim=1) - choice = torch.where(softmax == torch.max(softmax))[0][0] - chosen_softmax = softmax[choice] - return decision(chosen_softmax), decision(softmax[0]), decision(softmax[1]), int(choice) - - -############################################################################## -# Next, let's define a function to make a predictions over multiple data points. - - -def predict(params, x=None, parallel=True): - predictions_ensemble = [] - predictions_0 = [] - predictions_1 = [] - choices = [] - - for i, x_point in enumerate(x): - if i % 10 == 0 and i > 0: - print("Completed up to iteration {}".format(i)) - results = predict_point(params, x_point=x_point, parallel=parallel) - predictions_ensemble.append(results[0]) - predictions_0.append(results[1]) - predictions_1.append(results[2]) - choices.append(results[3]) - - return predictions_ensemble, predictions_0, predictions_1, choices - - -############################################################################## -# Make predictions -# ---------------- -# -# To test our model, we first load a pre-trained set of parameters which can also be downloaded -# by clicking :download:`here <../_static/demonstration_assets/ensemble_multi_qpu/params.npy>`. - - -params = np.load("../_static/demonstration_assets/ensemble_multi_qpu/params.npy") - -############################################################################## -# We can then make predictions for the training and test datasets. - - -print("Predicting on training dataset") -p_train, p_train_0, p_train_1, choices_train = predict(params, x=x_train) -print("Predicting on test dataset") -p_test, p_test_0, p_test_1, choices_test = predict(params, x=x_test) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# Predicting on training dataset -# Completed up to iteration 10 -# Completed up to iteration 20 -# Completed up to iteration 30 -# Completed up to iteration 40 -# Completed up to iteration 50 -# Completed up to iteration 60 -# Completed up to iteration 70 -# Completed up to iteration 80 -# Completed up to iteration 90 -# Completed up to iteration 100 -# Completed up to iteration 110 -# Completed up to iteration 120 -# Predicting on test dataset -# Completed up to iteration 10 -# Completed up to iteration 20 - -############################################################################## -# Analyze performance -# ------------------- -# -# The last thing to do is test how well the model performs. We begin by looking at the accuracy. -# -# Accuracy -# ^^^^^^^^ - - -def accuracy(predictions, actuals): - count = 0 - - for i in range(len(predictions)): - if predictions[i] == actuals[i]: - count += 1 - - accuracy = count / (len(predictions)) - return accuracy - -############################################################################## - -print("Training accuracy (ensemble): {}".format(accuracy(p_train, y_train))) -print("Training accuracy (QPU0): {}".format(accuracy(p_train_0, y_train))) -print("Training accuracy (QPU1): {}".format(accuracy(p_train_1, y_train))) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# Training accuracy (ensemble): 0.824 -# Training accuracy (QPU0): 0.648 -# Training accuracy (QPU1): 0.296 - -############################################################################## - -print("Test accuracy (ensemble): {}".format(accuracy(p_test, y_test))) -print("Test accuracy (QPU0): {}".format(accuracy(p_test_0, y_test))) -print("Test accuracy (QPU1): {}".format(accuracy(p_test_1, y_test))) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# Test accuracy (ensemble): 0.72 -# Test accuracy (QPU0): 0.56 -# Test accuracy (QPU1): 0.24 - -############################################################################## -# These numbers tell us a few things: -# -# - On both training and test datasets, the ensemble model outperforms the predictions from each -# QPU. This provides a nice example of how QPUs can be used in parallel to gain a performance -# advantage. -# -# - The accuracy of QPU0 is much higher than the accuracy of QPU1. This does not mean that one -# device is intrinsically better than the other. In fact, another set of parameters can lead to -# QPU1 becoming more accurate. We will see in the next section that the difference in accuracy -# is due to specialization of each QPU, which leads to overall better performance of the -# ensemble model. -# -# - The test accuracy is lower than the training accuracy. Here our focus is on analyzing the -# performance of the ensemble model, rather than minimizing the generalization error. -# -# Choice of QPU -# ^^^^^^^^^^^^^ -# -# Is there a link between the class of a datapoint and the QPU chosen to make the prediction in -# the ensemble model? Let's investigate. - - -# Combine choices_train and choices_test to simplify analysis -choices = np.append(choices_train, choices_test) -print("Choices: {}".format(choices)) -print("Choices counts: {}".format(Counter(choices))) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# Choices: [0 0 1 1 0 0 1 0 0 0 1 0 0 0 0 0 0 1 1 0 0 1 0 1 1 0 0 0 1 0 0 1 0 1 0 0 0 -# 0 1 1 0 0 0 0 0 0 0 1 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 1 1 0 0 0 0 1 0 0 0 -# 0 0 0 0 0 1 1 1 1 0 0 0 1 0 1 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 -# 1 0 0 0 1 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 1 1 0 1 0 0 -# 0 0] -# Choices counts: Counter({0: 110, 1: 40}) - -############################################################################## -# The following lines keep track of choices and corresponding predictions in the ensemble model. - - -predictions = np.append(p_train, p_test) -choice_vs_prediction = np.array([(choices[i], predictions[i]) for i in range(n_samples)]) - -############################################################################## -# We can hence find the predictions each QPU was responsible for. - - -choices_vs_prediction_0 = choice_vs_prediction[choice_vs_prediction[:, 0] == 0] -choices_vs_prediction_1 = choice_vs_prediction[choice_vs_prediction[:, 0] == 1] -predictions_0 = choices_vs_prediction_0[:, 1] -predictions_1 = choices_vs_prediction_1[:, 1] - - -expl = "When QPU{} was chosen by the ensemble, it made the following distribution of " \ - "predictions:\n{}" -print(expl.format("0", Counter(predictions_0))) -print("\n" + expl.format("1", Counter(predictions_1))) -print("\nDistribution of classes in iris dataset: {}".format(Counter(y))) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# When QPU0 was chosen by the ensemble, it made the following distribution of predictions: -# Counter({0: 55, 2: 55}) -# -# When QPU1 was chosen by the ensemble, it made the following distribution of predictions: -# Counter({1: 37, 0: 3}) -# -# Distribution of classes in iris dataset: Counter({0: 50, 2: 50, 1: 50}) - -############################################################################## -# These results show us that QPU0 specializes to making predictions on classes 0 and 2, -# while QPU1 specializes to class 1. -# -# Visualization -# ^^^^^^^^^^^^^ -# -# We conclude by visualizing the correct and incorrect predictions on the dataset. The following -# function plots correctly predicted points in green and incorrectly predicted points in red. - - -colours_prediction = {"correct": "#83b5b9", "incorrect": "#f98d91"} -markers = ["o", "v", "d"] - - -def plot_points_prediction(x, y, p, title): - c = {0: [], 1: [], 2: []} - x_ = {0: [], 1: [], 2: []} - - for i in range(n_samples): - x_[y[i]].append(x[i]) - if p[i] == y[i]: - c[y[i]].append(colours_prediction["correct"]) - else: - c[y[i]].append(colours_prediction["incorrect"]) - - for i in range(n_classes): - x_class = np.array(x_[i]) - plt.scatter(x_class[:, 0], x_class[:, 1], c=c[i], marker=markers[i]) - - plt.xlabel("Feature 1", fontsize=16) - plt.ylabel("Feature 2", fontsize=16) - plt.title("Predictions from {} model".format(title)) - - ax = plt.gca() - ax.set_aspect(1) - - c_transparent = "#00000000" - - custom_lines = [ - Patch( - facecolor=colours_prediction["correct"], - edgecolor=c_transparent, label="Correct" - ), - Patch( - facecolor=colours_prediction["incorrect"], - edgecolor=c_transparent, label="Incorrect" - ), - Line2D([0], [0], marker=markers[0], color=c_transparent, label="Class 0", - markerfacecolor="black", markersize=10), - Line2D([0], [0], marker=markers[1], color=c_transparent, label="Class 1", - markerfacecolor="black", markersize=10), - Line2D([0], [0], marker=markers[2], color=c_transparent, label="Class 2", - markerfacecolor="black", markersize=10), - ] - - ax.legend(handles=custom_lines, bbox_to_anchor=(1.0, 0.75)) - - -############################################################################## -# We can again compare the ensemble model with the individual models from each QPU. - - -plot_points_prediction(x, y, predictions, "ensemble") # ensemble -plt.show() - -############################################################################## -# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_002.png -# :width: 80% -# :align: center -# - -############################################################################## - -plot_points_prediction(x, y, np.append(p_train_0, p_test_0), "QPU0") # QPU 0 -plt.show() - -############################################################################## -# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_003.png -# :width: 80% -# :align: center -# - -############################################################################## - -plot_points_prediction(x, y, np.append(p_train_1, p_test_1), "QPU1") # QPU 1 -plt.show() - -############################################################################## -# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_004.png -# :width: 80% -# :align: center -# - -############################################################################## -# These plots reinforce the specialization of the two QPUs. QPU1 concentrates on doing a good job -# at predicting class 1, while QPU0 is focused on classes 0 and 2. By combining together, -# the resultant ensemble performs better. -# -# This tutorial shows how QPUs can work in parallel to realize a performance advantage. Check out -# our :doc:`vqe_parallel` tutorial to see how multiple QPUs can be -# evaluated asynchronously to speed up calculating the potential energy surface of molecular -# hydrogen! - -############################################################################## -# About the author -# ---------------- -# +r""" +Ensemble classification with Rigetti and Qiskit devices +======================================================= + +.. meta:: + :property="og:description": We demonstrate how two QPUs can be + combined in parallel to help solve a machine learning classification problem, + using PyTorch and PennyLane. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/ensemble_diagram.png + +.. related + + tutorial_variational_classifier Variational classifier + +*Author: Tom Bromley — Posted: 14 February 2020. Last updated: 13 December 2021.* + +This tutorial outlines how two QPUs can be combined in parallel to help solve a machine learning +classification problem. + +.. warning:: + This demo does not work with the latest version of Qiskit or the Pennylane-Qiskit plugin and + is compatible with Python versions 3.10 and lower. It is compatible with ``qiskit==0.46`` and + ``pennylane-qiskit==0.35.1``. Older versions of Qiskit and the Pennylane-Qiskit plugin should + not be installed in environments with an existing installation of Qiskit 1.0 or above. + +We use the ``rigetti.qvm`` device to simulate one QPU and the ``qiskit.aer`` device to +simulate another. Each QPU makes an independent prediction, and an ensemble model is +formed by choosing the prediction of the most confident QPU. The iris dataset is used in this +tutorial, consisting of three classes of iris flower. Using a pre-trained model and the PyTorch +interface, we'll see that ensembling allows the QPUs to specialize towards +different classes. + +Let's begin by importing the prerequisite libraries: +""" + +from collections import Counter + +import dask +import matplotlib.pyplot as plt +import numpy as np +import pennylane as qml +import sklearn.datasets +import sklearn.decomposition +import torch +from matplotlib.lines import Line2D +from matplotlib.patches import Patch + +############################################################################## +# This tutorial requires the ``pennylane-rigetti`` and ``pennylane-qiskit`` packages, which can be +# installed by following the instructions `here `__. We also +# make use of the `PyTorch interface `_, which can be installed from `here +# `__. +# +# .. warning:: +# Rigetti's QVM and Quil Compiler services must be running for this tutorial to execute. They +# can be installed by consulting the `Rigetti documentation +# `__ or, for users with Docker, by running: +# +# .. code-block:: bash +# +# docker run -d -p 5555:5555 rigetti/quilc -R -p 5555 +# docker run -d -p 5000:5000 rigetti/qvm -S -p 5000 +# +# Load data +# --------- +# +# The next step is to load the iris dataset. + +n_features = 2 +n_classes = 3 +n_samples = 150 + +data = sklearn.datasets.load_iris() +x = data["data"] +y = data["target"] + +############################################################################## +# We shuffle the data and then embed the four features into a two-dimensional space for ease of +# plotting later on. The first two principal components of the data are used. + +np.random.seed(1967) + +data_order = np.random.permutation(np.arange(n_samples)) +x, y = x[data_order], y[data_order] + +pca = sklearn.decomposition.PCA(n_components=n_features) +pca.fit(x) +x = pca.transform(x) + +############################################################################## +# We will be encoding these two features into quantum circuits using :class:`~.pennylane.RX` +# rotations, and hence renormalize our features to be between :math:`[-\pi, \pi].` + + +x_min = np.min(x, axis=0) +x_max = np.max(x, axis=0) + +x = 2 * np.pi * (x - x_min) / (x_max - x_min) - np.pi + +############################################################################## +# The data is split between a training and a test set. This tutorial uses a model that is +# pre-trained on the training set. + + +split = 125 + +x_train = x[:split] +x_test = x[split:] +y_train = y[:split] +y_test = y[split:] + +############################################################################## +# Finally, let's take a quick look at our data: + + +colours = ["#ec6f86", "#4573e7", "#ad61ed"] + + +def plot_points(x_train, y_train, x_test, y_test): + c_train = [] + c_test = [] + + for y in y_train: + c_train.append(colours[y]) + + for y in y_test: + c_test.append(colours[y]) + + plt.scatter(x_train[:, 0], x_train[:, 1], c=c_train) + plt.scatter(x_test[:, 0], x_test[:, 1], c=c_test, marker="x") + + plt.xlabel("Feature 1", fontsize=16) + plt.ylabel("Feature 2", fontsize=16) + + ax = plt.gca() + ax.set_aspect(1) + + c_transparent = "#00000000" + + custom_lines = [ + Patch(facecolor=colours[0], edgecolor=c_transparent, label="Class 0"), + Patch(facecolor=colours[1], edgecolor=c_transparent, label="Class 1"), + Patch(facecolor=colours[2], edgecolor=c_transparent, label="Class 2"), + Line2D([0], [0], marker="o", color=c_transparent, label="Train", + markerfacecolor="black", markersize=10), + Line2D([0], [0], marker="x", color=c_transparent, label="Test", + markerfacecolor="black", markersize=10), + ] + + ax.legend(handles=custom_lines, bbox_to_anchor=(1.0, 0.75)) + + +plot_points(x_train, y_train, x_test, y_test) +plt.show() + +############################################################################## +# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_001.png +# :width: 80% +# :align: center +# + +############################################################################## +# This plot shows us that class 0 points can be nicely separated, but that there is an overlap +# between points from classes 1 and 2. +# +# Define model +# ------------ +# +# Our model is summarized in the figure below. We use two 4-qubit devices: ``4q-qvm`` +# from the pennyLane-rigetti plugin and ``qiskit.aer`` from the PennyLane-Qiskit plugin. +# +# Data is input using :class:`~.pennylane.RX` rotations and then a different circuit is enacted +# for each device with a unique set of trainable parameters. The output of both circuits is a +# :class:`~.pennylane.PauliZ` measurement on three of the qubits. This is then fed through a +# softmax function, resulting in two 3-dimensional probability vectors corresponding to the 3 +# classes. +# +# Finally, the ensemble model chooses the QPU which is most confident about its prediction +# (i.e., the class with the highest overall probability over all QPUs) and uses that to make a +# prediction. +# +# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_diagram.png +# :width: 80% +# :align: center +# +# Quantum nodes +# ^^^^^^^^^^^^^ +# +# We begin by defining the two quantum devices and the circuits to be run on them. + +n_wires = 4 + +dev0 = qml.device("rigetti.qvm", device="4q-qvm") +dev1 = qml.device("qiskit.aer", wires=4) +devs = [dev0, dev1] + +############################################################################## +# .. note:: +# If you have access to Rigetti hardware, you can swap out ``rigetti.qvm`` for ``rigetti.qpu`` +# and specify the hardware device to run on. Users with access to the IBM hardware can +# swap ``qiskit.aer`` for ``qiskit.remote`` and specify their chosen backend (see `here +# `__). +# +# +# The circuits for both QPUs are shown in the figure below: +# +# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/diagram_circuits.png +# :width: 80% +# :align: center + + +def circuit0(params, x=None): + for i in range(n_wires): + qml.RX(x[i % n_features], wires=i) + qml.Rot(*params[1, 0, i], wires=i) + + qml.CZ(wires=[1, 0]) + qml.CZ(wires=[1, 2]) + qml.CZ(wires=[3, 0]) + + for i in range(n_wires): + qml.Rot(*params[1, 1, i], wires=i) + return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)), qml.expval(qml.PauliZ(2)) + + +def circuit1(params, x=None): + for i in range(n_wires): + qml.RX(x[i % n_features], wires=i) + qml.Rot(*params[0, 0, i], wires=i) + + qml.CZ(wires=[0, 1]) + qml.CZ(wires=[1, 2]) + qml.CZ(wires=[1, 3]) + + for i in range(n_wires): + qml.Rot(*params[0, 1, i], wires=i) + return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)), qml.expval(qml.PauliZ(2)) + + +############################################################################## +# We finally combine the two devices into a :class:`~.pennylane.QNode` list: + + +qnodes = [ + qml.QNode(circuit0, dev0), + qml.QNode(circuit1, dev1), +] + +############################################################################## +# Postprocessing into a prediction +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# +# The ``predict_point`` function below allows us to find the ensemble prediction, as well as keeping +# track of the individual predictions from each QPU. +# +# We include a ``parallel`` keyword argument for evaluating the :class:`~.pennylane.QNode` list +# in a parallel asynchronous manner. This feature requires the ``dask`` library, which can be +# installed using ``pip install "dask[delayed]"``. When ``parallel=True``, we are able to make +# predictions faster because we do not need to wait for one QPU to output before running on the +# other. + +def decision(softmax): + return int(torch.argmax(softmax)) + + +def predict_point(params, x_point=None, parallel=True): + if parallel: + results = tuple(dask.delayed(q)(params, x=torch.from_numpy(x_point)) for q in qnodes) + results = torch.tensor(dask.compute(*results, scheduler="threads")) + else: + results = tuple(q(params, x=x_point) for q in qnodes) + results = torch.tensor(results) + softmax = torch.nn.functional.softmax(results, dim=1) + choice = torch.where(softmax == torch.max(softmax))[0][0] + chosen_softmax = softmax[choice] + return decision(chosen_softmax), decision(softmax[0]), decision(softmax[1]), int(choice) + + +############################################################################## +# Next, let's define a function to make a predictions over multiple data points. + + +def predict(params, x=None, parallel=True): + predictions_ensemble = [] + predictions_0 = [] + predictions_1 = [] + choices = [] + + for i, x_point in enumerate(x): + if i % 10 == 0 and i > 0: + print("Completed up to iteration {}".format(i)) + results = predict_point(params, x_point=x_point, parallel=parallel) + predictions_ensemble.append(results[0]) + predictions_0.append(results[1]) + predictions_1.append(results[2]) + choices.append(results[3]) + + return predictions_ensemble, predictions_0, predictions_1, choices + + +############################################################################## +# Make predictions +# ---------------- +# +# To test our model, we first load a pre-trained set of parameters which can also be downloaded +# by clicking :download:`here <../_static/demonstration_assets/ensemble_multi_qpu/params.npy>`. + + +params = np.load("../_static/demonstration_assets/ensemble_multi_qpu/params.npy") + +############################################################################## +# We can then make predictions for the training and test datasets. + + +print("Predicting on training dataset") +p_train, p_train_0, p_train_1, choices_train = predict(params, x=x_train) +print("Predicting on test dataset") +p_test, p_test_0, p_test_1, choices_test = predict(params, x=x_test) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Predicting on training dataset +# Completed up to iteration 10 +# Completed up to iteration 20 +# Completed up to iteration 30 +# Completed up to iteration 40 +# Completed up to iteration 50 +# Completed up to iteration 60 +# Completed up to iteration 70 +# Completed up to iteration 80 +# Completed up to iteration 90 +# Completed up to iteration 100 +# Completed up to iteration 110 +# Completed up to iteration 120 +# Predicting on test dataset +# Completed up to iteration 10 +# Completed up to iteration 20 + +############################################################################## +# Analyze performance +# ------------------- +# +# The last thing to do is test how well the model performs. We begin by looking at the accuracy. +# +# Accuracy +# ^^^^^^^^ + + +def accuracy(predictions, actuals): + count = 0 + + for i in range(len(predictions)): + if predictions[i] == actuals[i]: + count += 1 + + accuracy = count / (len(predictions)) + return accuracy + +############################################################################## + +print("Training accuracy (ensemble): {}".format(accuracy(p_train, y_train))) +print("Training accuracy (QPU0): {}".format(accuracy(p_train_0, y_train))) +print("Training accuracy (QPU1): {}".format(accuracy(p_train_1, y_train))) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Training accuracy (ensemble): 0.824 +# Training accuracy (QPU0): 0.648 +# Training accuracy (QPU1): 0.296 + +############################################################################## + +print("Test accuracy (ensemble): {}".format(accuracy(p_test, y_test))) +print("Test accuracy (QPU0): {}".format(accuracy(p_test_0, y_test))) +print("Test accuracy (QPU1): {}".format(accuracy(p_test_1, y_test))) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Test accuracy (ensemble): 0.72 +# Test accuracy (QPU0): 0.56 +# Test accuracy (QPU1): 0.24 + +############################################################################## +# These numbers tell us a few things: +# +# - On both training and test datasets, the ensemble model outperforms the predictions from each +# QPU. This provides a nice example of how QPUs can be used in parallel to gain a performance +# advantage. +# +# - The accuracy of QPU0 is much higher than the accuracy of QPU1. This does not mean that one +# device is intrinsically better than the other. In fact, another set of parameters can lead to +# QPU1 becoming more accurate. We will see in the next section that the difference in accuracy +# is due to specialization of each QPU, which leads to overall better performance of the +# ensemble model. +# +# - The test accuracy is lower than the training accuracy. Here our focus is on analyzing the +# performance of the ensemble model, rather than minimizing the generalization error. +# +# Choice of QPU +# ^^^^^^^^^^^^^ +# +# Is there a link between the class of a datapoint and the QPU chosen to make the prediction in +# the ensemble model? Let's investigate. + + +# Combine choices_train and choices_test to simplify analysis +choices = np.append(choices_train, choices_test) +print("Choices: {}".format(choices)) +print("Choices counts: {}".format(Counter(choices))) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Choices: [0 0 1 1 0 0 1 0 0 0 1 0 0 0 0 0 0 1 1 0 0 1 0 1 1 0 0 0 1 0 0 1 0 1 0 0 0 +# 0 1 1 0 0 0 0 0 0 0 1 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 1 1 0 0 0 0 1 0 0 0 +# 0 0 0 0 0 1 1 1 1 0 0 0 1 0 1 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 +# 1 0 0 0 1 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 1 1 0 1 0 0 +# 0 0] +# Choices counts: Counter({0: 110, 1: 40}) + +############################################################################## +# The following lines keep track of choices and corresponding predictions in the ensemble model. + + +predictions = np.append(p_train, p_test) +choice_vs_prediction = np.array([(choices[i], predictions[i]) for i in range(n_samples)]) + +############################################################################## +# We can hence find the predictions each QPU was responsible for. + + +choices_vs_prediction_0 = choice_vs_prediction[choice_vs_prediction[:, 0] == 0] +choices_vs_prediction_1 = choice_vs_prediction[choice_vs_prediction[:, 0] == 1] +predictions_0 = choices_vs_prediction_0[:, 1] +predictions_1 = choices_vs_prediction_1[:, 1] + + +expl = "When QPU{} was chosen by the ensemble, it made the following distribution of " \ + "predictions:\n{}" +print(expl.format("0", Counter(predictions_0))) +print("\n" + expl.format("1", Counter(predictions_1))) +print("\nDistribution of classes in iris dataset: {}".format(Counter(y))) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# When QPU0 was chosen by the ensemble, it made the following distribution of predictions: +# Counter({0: 55, 2: 55}) +# +# When QPU1 was chosen by the ensemble, it made the following distribution of predictions: +# Counter({1: 37, 0: 3}) +# +# Distribution of classes in iris dataset: Counter({0: 50, 2: 50, 1: 50}) + +############################################################################## +# These results show us that QPU0 specializes to making predictions on classes 0 and 2, +# while QPU1 specializes to class 1. +# +# Visualization +# ^^^^^^^^^^^^^ +# +# We conclude by visualizing the correct and incorrect predictions on the dataset. The following +# function plots correctly predicted points in green and incorrectly predicted points in red. + + +colours_prediction = {"correct": "#83b5b9", "incorrect": "#f98d91"} +markers = ["o", "v", "d"] + + +def plot_points_prediction(x, y, p, title): + c = {0: [], 1: [], 2: []} + x_ = {0: [], 1: [], 2: []} + + for i in range(n_samples): + x_[y[i]].append(x[i]) + if p[i] == y[i]: + c[y[i]].append(colours_prediction["correct"]) + else: + c[y[i]].append(colours_prediction["incorrect"]) + + for i in range(n_classes): + x_class = np.array(x_[i]) + plt.scatter(x_class[:, 0], x_class[:, 1], c=c[i], marker=markers[i]) + + plt.xlabel("Feature 1", fontsize=16) + plt.ylabel("Feature 2", fontsize=16) + plt.title("Predictions from {} model".format(title)) + + ax = plt.gca() + ax.set_aspect(1) + + c_transparent = "#00000000" + + custom_lines = [ + Patch( + facecolor=colours_prediction["correct"], + edgecolor=c_transparent, label="Correct" + ), + Patch( + facecolor=colours_prediction["incorrect"], + edgecolor=c_transparent, label="Incorrect" + ), + Line2D([0], [0], marker=markers[0], color=c_transparent, label="Class 0", + markerfacecolor="black", markersize=10), + Line2D([0], [0], marker=markers[1], color=c_transparent, label="Class 1", + markerfacecolor="black", markersize=10), + Line2D([0], [0], marker=markers[2], color=c_transparent, label="Class 2", + markerfacecolor="black", markersize=10), + ] + + ax.legend(handles=custom_lines, bbox_to_anchor=(1.0, 0.75)) + + +############################################################################## +# We can again compare the ensemble model with the individual models from each QPU. + + +plot_points_prediction(x, y, predictions, "ensemble") # ensemble +plt.show() + +############################################################################## +# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_002.png +# :width: 80% +# :align: center +# + +############################################################################## + +plot_points_prediction(x, y, np.append(p_train_0, p_test_0), "QPU0") # QPU 0 +plt.show() + +############################################################################## +# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_003.png +# :width: 80% +# :align: center +# + +############################################################################## + +plot_points_prediction(x, y, np.append(p_train_1, p_test_1), "QPU1") # QPU 1 +plt.show() + +############################################################################## +# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_004.png +# :width: 80% +# :align: center +# + +############################################################################## +# These plots reinforce the specialization of the two QPUs. QPU1 concentrates on doing a good job +# at predicting class 1, while QPU0 is focused on classes 0 and 2. By combining together, +# the resultant ensemble performs better. +# +# This tutorial shows how QPUs can work in parallel to realize a performance advantage. Check out +# our :doc:`vqe_parallel` tutorial to see how multiple QPUs can be +# evaluated asynchronously to speed up calculating the potential energy surface of molecular +# hydrogen! + +############################################################################## +# About the author +# ---------------- +# diff --git a/demonstrations_v2/gbs/demo.py b/demonstrations_v2/gbs/demo.py index f2e1984e33..044c2f6c56 100644 --- a/demonstrations_v2/gbs/demo.py +++ b/demonstrations_v2/gbs/demo.py @@ -1,488 +1,488 @@ -r""" -.. role:: html(raw) - :format: html - -Quantum advantage with Gaussian Boson Sampling -============================================== - -.. meta:: - :property="og:description": Using light to perform tasks beyond the reach of classical computers. - - :property="og:image": https://pennylane.ai/qml/_static/demo_thumbnails/opengraph_demo_thumbnails/gbs_expt2.png - -.. related:: - - tutorial_gaussian_transformation Gaussian transformation - qsim_beyond_classical Beyond classical computing with qsim - qonn Optimizing a quantum optical neural network - tutorial_photonics Photonic quantum computers - -*Authors: Josh Izaac and Nathan Killoran — Posted: 04 December 2020. Last updated: 04 December 2020.* - -.. warning:: - This demo is only compatible with PennyLane version ``0.29`` or below. - -On the journey to large-scale fault-tolerant quantum computers, one of the first major -milestones is to demonstrate a quantum device carrying out tasks that are beyond the reach of -any classical algorithm. The launch of Xanadu's Borealis device marked an important milestone -within the quantum computing community, wherein our very own quantum computational advantage -experiment using quantum photonics was demonstrated in our `Nature paper `__. -Among other quantum advantage achievements are the Google Quantum team's experiment as can be seen in their paper -`Quantum supremacy using a programmable superconducting processor `__ [#Arute2019]_, -and the experiment from the team led by Chao-Yang Lu and Jian-Wei as can be seen in their paper -`Quantum computational advantage using photons `__ -[#Zhong2020]_. - -While Google's experiment performed the task of :doc:`random circuit sampling ` -using a superconducting processor, both Chao-Yang Lu and Jian-Wei's team and Xanadu leveraged the -quantum properties of light to tackle a task called -`Gaussian Boson Sampling `__ (GBS). - -This tutorial will walk you through the basic elements of GBS, motivate why it is -classically challenging, and show you how to explore GBS using PennyLane and the photonic -quantum devices accessible via the -`PennyLane-Strawberry Fields plugin `__. If you are -interested in possible applications of GBS, or want to access programmable GBS hardware -via the cloud, check out the -`Strawberry Fields website `__ for more details. - -| - -.. image:: /_static/demonstration_assets/gbs/gbs_expt2.png - :align: center - :width: 80% - :target: javascript:void(0); - -.. figure:: /_static/demonstration_assets/gbs/gbs_expt1.png - :align: center - :width: 80% - :target: javascript:void(0); - - *Illustration of the experimental setup used by Zhong et al. in Quantum computational advantage - using photons* [#Zhong2020]_. - -The origins of GBS ------------------- - -Let's first explain the name. `Boson `__ refers to bosonic -matter, which, along with fermions, makes up one of the two elementary classes of particles. -The most prevalent bosonic system in our everyday lives is light, which is made of particles -called photons. Another famous example, though much harder to find, is the Higgs boson. -The distinguishing characteristic of bosons is that they follow "Bose-Einstein statistics", -which very loosely means that the particles like to bunch together (contrast this to fermionic -matter like electrons, which must follow the Pauli Exclusion Principle and keep apart). - -This property can be observed in simple interference experiments such as the -`Hong-Ou Mandel setup `__. -If two single photons are interfered on a balanced beamsplitter, they will both emerge at -the same output port—there is zero probability that they will emerge at separate outputs. -This is a simple but notable quantum property of light; if electrons were brought -together in a similar experiement, they would always appear at separate output ports. - -Gaussian Boson Sampling [#hamilton2017]_ is, in fact, a member of a larger family of -"Boson Sampling" algorithms, -stemming back to the initial proposal of Aaronson and Arkhipov [#aaronson2013]_ in 2013. -Boson Sampling is quantum interferometry writ large. Aaronson and Arkhipov's original proposal -was to inject many single photons into distinct input ports of a large interferometer, then -measure which output ports they appear at. The natural interference properties of bosons -means that photons will appear at the output ports in very unique and specific ways. Boson -Sampling was not proposed with any kind of practical real-world use-case in mind. Like -the random circuit sampling, it's just a quantum system being its best self. With sufficient -size and quality, it is strongly believed to be hard for a classical computer to simulate this efficiently. - -Finally, the "Gaussian" in GBS refers to the fact that we modify the original Boson Sampling -proposal slightly: instead of injecting single photons—which are hard to jointly create in the -size and quality needed to demonstrate Boson Sampling conclusively—we instead use states of -light that are experimentally less demanding (though still challenging!). -These states of light are called Gaussian states, -because they bear strong connections to the -`Gaussian (or Normal) distribution `__ -from statistics. In practice, we use a particular Gaussian state called a -`squeezed state `__ for the inputs, -since these are arguably the most non-classical of Gaussian states. - - -.. note:: While computationally hard to simulate, Boson Sampling devices, on their own, - are not capable of universal quantum computing. However, in combination with other - components, GBS is a key building block for a - universal device [#Bourassa2020]_. - - -Coding a GBS algorithm ----------------------- - -The researchers in [#Zhong2020]_ experimentally demonstrate a GBS device by preparing 50 -squeezed states and injecting them into a 100-mode interferometer. In this demo, -in order to keep things classically simulable, we will stick to a much simpler setting -consisting of 4 squeezed states injected into a 4-mode interferometer. At a high level, -an interferometer on :math:`N` modes can be represented using an :math:`N\times N` unitary -matrix :math:`U.` When decomposed into a quantum optical circuit, the interferometer will -be made up of beamsplitters and phase shifters. - -.. image:: /_static/demonstration_assets/gbs/gbs_circuit2.png - :align: center - :width: 90% - :target: javascript:void(0); - -.. raw:: html - -
- -Simulating this circuit using PennyLane is easy; we can simply read off the gates from left -to right, and convert it into a QNode. -""" - -import numpy as np - -# set the random seed -np.random.seed(42) - -# import PennyLane -import pennylane as qml - -############################################################################## -# We must define the unitary matrix we would like to embed in the circuit. -# We will use SciPy to generate a Haar-random unitary: - -from scipy.stats import unitary_group - -# define the linear interferometer -U = unitary_group.rvs(4) -print(U) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# [[ 0.23648826-0.48221431j 0.06829648+0.04447898j 0.51150074-0.09529866j -# 0.55205719-0.35974699j] -# [-0.11148167+0.69780321j -0.24943828+0.08410701j 0.46705929-0.43192981j -# 0.16220654-0.01817602j] -# [-0.22351926-0.25918352j 0.24364996-0.05375623j -0.09259829-0.53810588j -# 0.27267708+0.66941977j] -# [ 0.11519953-0.28596729j -0.90164923-0.22099186j -0.09627758-0.13105595j -# -0.0200152 +0.12766128j]] -# -# We can now use this to construct the circuit, choosing a compatible -# device. For the simulation, we can use the Strawberry Fields -# Gaussian backend. This backend is perfectly suited for simulation of GBS, -# as the initial states are Gaussian, and all gates transform Gaussian states to other -# Gaussian states. - -n_wires = 4 -cutoff = 10 - -dev = qml.device("strawberryfields.gaussian", wires=n_wires, cutoff_dim=cutoff) - - -@qml.qnode(dev) -def gbs_circuit(): - # prepare the input squeezed states - for i in range(n_wires): - qml.Squeezing(1.0, 0.0, wires=i) - - # linear interferometer - qml.InterferometerUnitary(U, wires=range(n_wires)) - return qml.probs(wires=range(n_wires)) - - -############################################################################## -# A couple of things to note in this particular example: -# -# 1. To prepare the input single mode squeezed vacuum state :math:`|re^{i\phi}\rangle,` -# where :math:`r = 1` and :math:`\phi=0,` we -# apply a squeezing gate (:class:`~pennylane.Squeezing`) to each of the wires (initially in -# the vacuum state). -# -# 2. Next we apply the linear interferometer to all four wires using -# :class:`~pennylane.Interferometer` and the unitary matrix ``U``. This operator -# decomposes the unitary matrix representing the linear interferometer into single-mode -# rotation gates (:class:`~pennylane.PhaseShift`) and two-mode beamsplitters -# (:class:`~pennylane.Beamsplitter`). After applying the interferometer, we will denote the -# output state by :math:`|\psi'\rangle.` -# -# 3. GBS takes place physically in an infinite-dimensional Hilbert space, -# which is not practical for simulation. We need to set an upper limit on the maximum -# number of photons we can detect. This is the -# ``cutoff`` value we defined above; we will only be considering detection events -# containing 0 to 9 photons per mode. -# -# We can now execute the QNode, and extract the resulting probability distribution: - -probs = gbs_circuit().reshape([cutoff] * n_wires) -print(probs.shape) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# (10, 10, 10, 10) -# - -############################################################################## -# For example, element ``[1,2,0,1]`` represents the probability of -# detecting 1 photon on wire -# ``0`` and wire ``3``, and 2 photons at wire ``1``, i.e., the value -# -# .. math:: \text{prob}(1,2,0,1) = \left|\langle{1,2,0,1} \mid \psi'\rangle \right|^2. -# -# Let's extract and view the probabilities of measuring various Fock states. - -# Fock states to measure at output -measure_states = [(0, 0, 0, 0), (1, 1, 0, 0), (0, 1, 0, 1), (1, 1, 1, 1), (2, 0, 0, 0)] - -# extract the probabilities of calculating several -# different Fock states at the output, and print them out -for i in measure_states: - print(f"|{''.join(str(j) for j in i)}>: {probs[i]}") - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# |0000>: 0.17637844761413496 -# |1100>: 0.03473293649420282 -# |0101>: 0.011870900427255589 -# |1111>: 0.005957399165336106 -# |2000>: 0.02957384308320549 -# - -############################################################################## -# The GBS Distribution -# -------------------- -# -# Hamilton et al. [#hamilton2017]_ showed that the probability of -# measuring a final state containing only 0 or 1 photons per mode is given by -# -# .. math:: -# -# \left|\langle{n_1,n_2,\dots,n_N}\middle|{\psi'}\rangle\right|^2 = -# \frac{\left|\text{Haf}[(U(\bigoplus_i\mathrm{tanh}(r_i))U^T)]_{st}\right|^2}{\prod_{i=1}^N \cosh(r_i)} -# -# i.e., the sampled single-photon probability distribution is proportional to the **hafnian** of a -# submatrix of :math:`U(\bigoplus_i\mathrm{tanh}(r_i))U^T.` -# -# .. note:: -# -# The hafnian of a matrix is defined by -# -# .. math:: \text{Haf}(A) = \sum_{\sigma \in \text{PMP}_{2N}}\prod_{i=1}^N A_{\sigma(2i-1)\sigma(2i)}, -# -# where :math:`\text{PMP}_{2N}` is the set of all perfect matching permutations of :math:`2N` elements. In graph theory, the -# hafnian calculates the number of perfect `matchings -# `_ in a graph with -# adjacency matrix :math:`A.` -# -# Compare this to the permanent, which calculates the number of perfect matchings on a *bipartite* -# graph. Notably, the permanent appears in vanilla Boson Sampling in a similar way -# that the hafnian appears in GBS. -# The hafnian turns out to be a generalization of the permanent, with the relationship -# -# .. math:: -# -# \text{Per(A)} = \text{Haf}\left(\left[\begin{matrix} -# 0&A\\ A^T&0 -# \end{matrix}\right]\right). -# -# As any algorithm that could calculate (or even approximate) the hafnian could also calculate the -# permanent—a `#P-hard problem `__---it follows that -# calculating or approximating the hafnian must also be a classically hard problem. This lies behind -# the classical hardness of GBS. -# -# In this demo, we will use the same squeezing parameter, :math:`z=r,` for -# all input states; this allows us to simplify this equation. To start with, the hafnian expression -# simply becomes :math:`\text{Haf}[(UU^T\mathrm{tanh}(r))]_{st},` removing the need for the direct sum. -# -# Thus, we have -# -# .. math:: -# -# \left|\left\langle{n_1,n_2,\dots,n_N}\middle|{\psi'}\right\rangle\right|^2 = -# \frac{\left|\text{Haf}[(UU^T\tanh(r))]_{st}\right|^2}{n_1!n_2!\cdots n_N!\cosh^N(r)}. -# -# Now that we have the theoretical formulas, as well as the probabilities from our simulated GBS -# QNode, we can compare the two and see whether they agree. -# -# In order to calculate the probability of different GBS events classically, we need a -# method for calculating the hafnian. -# For this, we will use `The Walrus -# `_ library (which is installed as a dependency of the -# PennyLane-SF plugin): - -from thewalrus import hafnian as haf - -############################################################################## -# Now, for the right-hand side numerator, we first calculate the submatrix -# :math:`A = [(UU^T\mathrm{tanh}(r))]_{st}:` - -A = np.dot(U, U.T) * np.tanh(1) - -############################################################################## -# In GBS, we determine the submatrix by taking the -# rows and columns corresponding to the measured Fock state. For example, to calculate the submatrix -# in the case of the output measurement :math:`\left|{1,1,0,0}\right\rangle,` -# we have - -print(A[:, [0, 1]][[0, 1]]) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# [[ 0.19343159-0.54582922j 0.43418269-0.09169615j] -# [ 0.43418269-0.09169615j -0.27554025-0.46222197j]] -# - -############################################################################## -# i.e., we consider only the rows and columns where a photon was detected, which gives us -# the submatrix corresponding to indices :math:`0` and :math:`1.` - -############################################################################## -# Comparing to simulation -# ----------------------- -# -# Now that we have a method for calculating the hafnian, let's compare the output to that provided by -# the PennyLane QNode. -# -# **Measuring** :math:`|0,0,0,0\rangle` **at the output** -# -# This corresponds to the hafnian of an *empty* matrix, which is simply 1: - -print(1 / np.cosh(1) ** 4) -print(probs[0, 0, 0, 0]) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# 0.1763784476141347 -# 0.17637844761413496 -# - -############################################################################## -# **Measuring** :math:`|1,1,0,0\rangle` **at the output** - -A = (np.dot(U, U.T) * np.tanh(1))[:, [0, 1]][[0, 1]] -print(np.abs(haf(A)) ** 2 / np.cosh(1) ** 4) -print(probs[1, 1, 0, 0]) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# 0.03473293649420271 -# 0.03473293649420282 -# - -############################################################################## -# **Measuring** :math:`|0,1,0,1\rangle` **at the output** - -A = (np.dot(U, U.T) * np.tanh(1))[:, [1, 3]][[1, 3]] -print(np.abs(haf(A)) ** 2 / np.cosh(1) ** 4) -print(probs[0, 1, 0, 1]) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# 0.011870900427255558 -# 0.011870900427255589 -# - -############################################################################## -# **Measuring** :math:`|1,1,1,1\rangle` **at the output** -# -# This corresponds to the hafnian of the full matrix :math:`A=UU^T\mathrm{tanh}(r):` - -A = np.dot(U, U.T) * np.tanh(1) -print(np.abs(haf(A)) ** 2 / np.cosh(1) ** 4) -print(probs[1, 1, 1, 1]) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# 0.005957399165336081 -# 0.005957399165336106 -# - -############################################################################## -# **Measuring** :math:`|2,0,0,0\rangle` **at the output** -# -# Since we have two photons in mode ``q[0]``, we take two copies of the -# first row and first column, making sure to divide by :math:`2!:` - -A = (np.dot(U, U.T) * np.tanh(1))[:, [0, 0]][[0, 0]] -print(np.abs(haf(A)) ** 2 / (2 * np.cosh(1) ** 4)) -print(probs[2, 0, 0, 0]) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# 0.029573843083205383 -# 0.02957384308320549 -# -# The PennyLane simulation results agree (with almost negligible numerical error) to the -# expected result from the Gaussian boson sampling equation! -# -# This demo provides an entry-level walkthrough to the ideas behind GBS, -# providing you with the basic code needed for exploring the ideas behind -# the photonic quantum advantage paper. Try changing the number of modes, -# the number of injected squeezed states, or the cutoff dimension, and -# see how each of these affect the classical computation time. If you're -# interested in learning more about GBS, or about photonic quantum -# computing in general, the -# `Strawberry Fields website `__ is a great resource. -# -# References -# ---------- -# -# .. [#Arute2019] -# -# Arute, F., Arya, K., Babbush, R., et al. "Quantum supremacy using a programmable -# superconducting processor" -# `Nature 574, 505-510 (2019) `__. -# -# .. [#Zhong2020] -# -# Zhong, H.-S., Wang, H., Deng, Y.-H., et al. (2020). Quantum computational advantage using photons. Science, 10.1126/science.abe8770. -# -# .. [#hamilton2017] -# -# Craig S. Hamilton, Regina Kruse, Linda Sansoni, Sonja Barkhofen, Christine Silberhorn, -# and Igor Jex. Gaussian boson sampling. Physical Review Letters, 119:170501, Oct 2017. -# arXiv:1612.01199, doi:10.1103/PhysRevLett.119.170501. -# -# .. [#aaronson2013] -# -# Scott Aaronson and Alex Arkhipov. The computational complexity of linear optics. Theory of -# Computing, 9(1):143–252, 2013. doi:10.4086/toc.2013.v009a004. -# -# .. [#Bourassa2020] -# -# Bourassa, J. E., Alexander, R. N., Vasmer, et al. (2020). Blueprint for a scalable -# photonic fault-tolerant quantum computer. arXiv preprint arXiv:2010.02905. -# -# -# About the author -# ---------------- -# +r""" +.. role:: html(raw) + :format: html + +Quantum advantage with Gaussian Boson Sampling +============================================== + +.. meta:: + :property="og:description": Using light to perform tasks beyond the reach of classical computers. + + :property="og:image": https://pennylane.ai/qml/_static/demo_thumbnails/opengraph_demo_thumbnails/gbs_expt2.png + +.. related:: + + tutorial_gaussian_transformation Gaussian transformation + qsim_beyond_classical Beyond classical computing with qsim + qonn Optimizing a quantum optical neural network + tutorial_photonics Photonic quantum computers + +*Authors: Josh Izaac and Nathan Killoran — Posted: 04 December 2020. Last updated: 04 December 2020.* + +.. warning:: + This demo is only compatible with PennyLane version ``0.29`` or below. + +On the journey to large-scale fault-tolerant quantum computers, one of the first major +milestones is to demonstrate a quantum device carrying out tasks that are beyond the reach of +any classical algorithm. The launch of Xanadu's Borealis device marked an important milestone +within the quantum computing community, wherein our very own quantum computational advantage +experiment using quantum photonics was demonstrated in our `Nature paper `__. +Among other quantum advantage achievements are the Google Quantum team's experiment as can be seen in their paper +`Quantum supremacy using a programmable superconducting processor `__ [#Arute2019]_, +and the experiment from the team led by Chao-Yang Lu and Jian-Wei as can be seen in their paper +`Quantum computational advantage using photons `__ +[#Zhong2020]_. + +While Google's experiment performed the task of :doc:`random circuit sampling ` +using a superconducting processor, both Chao-Yang Lu and Jian-Wei's team and Xanadu leveraged the +quantum properties of light to tackle a task called +`Gaussian Boson Sampling `__ (GBS). + +This tutorial will walk you through the basic elements of GBS, motivate why it is +classically challenging, and show you how to explore GBS using PennyLane and the photonic +quantum devices accessible via the +`PennyLane-Strawberry Fields plugin `__. If you are +interested in possible applications of GBS, or want to access programmable GBS hardware +via the cloud, check out the +`Strawberry Fields website `__ for more details. + +| + +.. image:: /_static/demonstration_assets/gbs/gbs_expt2.png + :align: center + :width: 80% + :target: javascript:void(0); + +.. figure:: /_static/demonstration_assets/gbs/gbs_expt1.png + :align: center + :width: 80% + :target: javascript:void(0); + + *Illustration of the experimental setup used by Zhong et al. in Quantum computational advantage + using photons* [#Zhong2020]_. + +The origins of GBS +------------------ + +Let's first explain the name. `Boson `__ refers to bosonic +matter, which, along with fermions, makes up one of the two elementary classes of particles. +The most prevalent bosonic system in our everyday lives is light, which is made of particles +called photons. Another famous example, though much harder to find, is the Higgs boson. +The distinguishing characteristic of bosons is that they follow "Bose-Einstein statistics", +which very loosely means that the particles like to bunch together (contrast this to fermionic +matter like electrons, which must follow the Pauli Exclusion Principle and keep apart). + +This property can be observed in simple interference experiments such as the +`Hong-Ou Mandel setup `__. +If two single photons are interfered on a balanced beamsplitter, they will both emerge at +the same output port—there is zero probability that they will emerge at separate outputs. +This is a simple but notable quantum property of light; if electrons were brought +together in a similar experiement, they would always appear at separate output ports. + +Gaussian Boson Sampling [#hamilton2017]_ is, in fact, a member of a larger family of +"Boson Sampling" algorithms, +stemming back to the initial proposal of Aaronson and Arkhipov [#aaronson2013]_ in 2013. +Boson Sampling is quantum interferometry writ large. Aaronson and Arkhipov's original proposal +was to inject many single photons into distinct input ports of a large interferometer, then +measure which output ports they appear at. The natural interference properties of bosons +means that photons will appear at the output ports in very unique and specific ways. Boson +Sampling was not proposed with any kind of practical real-world use-case in mind. Like +the random circuit sampling, it's just a quantum system being its best self. With sufficient +size and quality, it is strongly believed to be hard for a classical computer to simulate this efficiently. + +Finally, the "Gaussian" in GBS refers to the fact that we modify the original Boson Sampling +proposal slightly: instead of injecting single photons—which are hard to jointly create in the +size and quality needed to demonstrate Boson Sampling conclusively—we instead use states of +light that are experimentally less demanding (though still challenging!). +These states of light are called Gaussian states, +because they bear strong connections to the +`Gaussian (or Normal) distribution `__ +from statistics. In practice, we use a particular Gaussian state called a +`squeezed state `__ for the inputs, +since these are arguably the most non-classical of Gaussian states. + + +.. note:: While computationally hard to simulate, Boson Sampling devices, on their own, + are not capable of universal quantum computing. However, in combination with other + components, GBS is a key building block for a + universal device [#Bourassa2020]_. + + +Coding a GBS algorithm +---------------------- + +The researchers in [#Zhong2020]_ experimentally demonstrate a GBS device by preparing 50 +squeezed states and injecting them into a 100-mode interferometer. In this demo, +in order to keep things classically simulable, we will stick to a much simpler setting +consisting of 4 squeezed states injected into a 4-mode interferometer. At a high level, +an interferometer on :math:`N` modes can be represented using an :math:`N\times N` unitary +matrix :math:`U.` When decomposed into a quantum optical circuit, the interferometer will +be made up of beamsplitters and phase shifters. + +.. image:: /_static/demonstration_assets/gbs/gbs_circuit2.png + :align: center + :width: 90% + :target: javascript:void(0); + +.. raw:: html + +
+ +Simulating this circuit using PennyLane is easy; we can simply read off the gates from left +to right, and convert it into a QNode. +""" + +import numpy as np + +# set the random seed +np.random.seed(42) + +# import PennyLane +import pennylane as qml + +############################################################################## +# We must define the unitary matrix we would like to embed in the circuit. +# We will use SciPy to generate a Haar-random unitary: + +from scipy.stats import unitary_group + +# define the linear interferometer +U = unitary_group.rvs(4) +print(U) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# [[ 0.23648826-0.48221431j 0.06829648+0.04447898j 0.51150074-0.09529866j +# 0.55205719-0.35974699j] +# [-0.11148167+0.69780321j -0.24943828+0.08410701j 0.46705929-0.43192981j +# 0.16220654-0.01817602j] +# [-0.22351926-0.25918352j 0.24364996-0.05375623j -0.09259829-0.53810588j +# 0.27267708+0.66941977j] +# [ 0.11519953-0.28596729j -0.90164923-0.22099186j -0.09627758-0.13105595j +# -0.0200152 +0.12766128j]] +# +# We can now use this to construct the circuit, choosing a compatible +# device. For the simulation, we can use the Strawberry Fields +# Gaussian backend. This backend is perfectly suited for simulation of GBS, +# as the initial states are Gaussian, and all gates transform Gaussian states to other +# Gaussian states. + +n_wires = 4 +cutoff = 10 + +dev = qml.device("strawberryfields.gaussian", wires=n_wires, cutoff_dim=cutoff) + + +@qml.qnode(dev) +def gbs_circuit(): + # prepare the input squeezed states + for i in range(n_wires): + qml.Squeezing(1.0, 0.0, wires=i) + + # linear interferometer + qml.InterferometerUnitary(U, wires=range(n_wires)) + return qml.probs(wires=range(n_wires)) + + +############################################################################## +# A couple of things to note in this particular example: +# +# 1. To prepare the input single mode squeezed vacuum state :math:`|re^{i\phi}\rangle,` +# where :math:`r = 1` and :math:`\phi=0,` we +# apply a squeezing gate (:class:`~pennylane.Squeezing`) to each of the wires (initially in +# the vacuum state). +# +# 2. Next we apply the linear interferometer to all four wires using +# :class:`~pennylane.Interferometer` and the unitary matrix ``U``. This operator +# decomposes the unitary matrix representing the linear interferometer into single-mode +# rotation gates (:class:`~pennylane.PhaseShift`) and two-mode beamsplitters +# (:class:`~pennylane.Beamsplitter`). After applying the interferometer, we will denote the +# output state by :math:`|\psi'\rangle.` +# +# 3. GBS takes place physically in an infinite-dimensional Hilbert space, +# which is not practical for simulation. We need to set an upper limit on the maximum +# number of photons we can detect. This is the +# ``cutoff`` value we defined above; we will only be considering detection events +# containing 0 to 9 photons per mode. +# +# We can now execute the QNode, and extract the resulting probability distribution: + +probs = gbs_circuit().reshape([cutoff] * n_wires) +print(probs.shape) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# (10, 10, 10, 10) +# + +############################################################################## +# For example, element ``[1,2,0,1]`` represents the probability of +# detecting 1 photon on wire +# ``0`` and wire ``3``, and 2 photons at wire ``1``, i.e., the value +# +# .. math:: \text{prob}(1,2,0,1) = \left|\langle{1,2,0,1} \mid \psi'\rangle \right|^2. +# +# Let's extract and view the probabilities of measuring various Fock states. + +# Fock states to measure at output +measure_states = [(0, 0, 0, 0), (1, 1, 0, 0), (0, 1, 0, 1), (1, 1, 1, 1), (2, 0, 0, 0)] + +# extract the probabilities of calculating several +# different Fock states at the output, and print them out +for i in measure_states: + print(f"|{''.join(str(j) for j in i)}>: {probs[i]}") + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# |0000>: 0.17637844761413496 +# |1100>: 0.03473293649420282 +# |0101>: 0.011870900427255589 +# |1111>: 0.005957399165336106 +# |2000>: 0.02957384308320549 +# + +############################################################################## +# The GBS Distribution +# -------------------- +# +# Hamilton et al. [#hamilton2017]_ showed that the probability of +# measuring a final state containing only 0 or 1 photons per mode is given by +# +# .. math:: +# +# \left|\langle{n_1,n_2,\dots,n_N}\middle|{\psi'}\rangle\right|^2 = +# \frac{\left|\text{Haf}[(U(\bigoplus_i\mathrm{tanh}(r_i))U^T)]_{st}\right|^2}{\prod_{i=1}^N \cosh(r_i)} +# +# i.e., the sampled single-photon probability distribution is proportional to the **hafnian** of a +# submatrix of :math:`U(\bigoplus_i\mathrm{tanh}(r_i))U^T.` +# +# .. note:: +# +# The hafnian of a matrix is defined by +# +# .. math:: \text{Haf}(A) = \sum_{\sigma \in \text{PMP}_{2N}}\prod_{i=1}^N A_{\sigma(2i-1)\sigma(2i)}, +# +# where :math:`\text{PMP}_{2N}` is the set of all perfect matching permutations of :math:`2N` elements. In graph theory, the +# hafnian calculates the number of perfect `matchings +# `_ in a graph with +# adjacency matrix :math:`A.` +# +# Compare this to the permanent, which calculates the number of perfect matchings on a *bipartite* +# graph. Notably, the permanent appears in vanilla Boson Sampling in a similar way +# that the hafnian appears in GBS. +# The hafnian turns out to be a generalization of the permanent, with the relationship +# +# .. math:: +# +# \text{Per(A)} = \text{Haf}\left(\left[\begin{matrix} +# 0&A\\ A^T&0 +# \end{matrix}\right]\right). +# +# As any algorithm that could calculate (or even approximate) the hafnian could also calculate the +# permanent—a `#P-hard problem `__---it follows that +# calculating or approximating the hafnian must also be a classically hard problem. This lies behind +# the classical hardness of GBS. +# +# In this demo, we will use the same squeezing parameter, :math:`z=r,` for +# all input states; this allows us to simplify this equation. To start with, the hafnian expression +# simply becomes :math:`\text{Haf}[(UU^T\mathrm{tanh}(r))]_{st},` removing the need for the direct sum. +# +# Thus, we have +# +# .. math:: +# +# \left|\left\langle{n_1,n_2,\dots,n_N}\middle|{\psi'}\right\rangle\right|^2 = +# \frac{\left|\text{Haf}[(UU^T\tanh(r))]_{st}\right|^2}{n_1!n_2!\cdots n_N!\cosh^N(r)}. +# +# Now that we have the theoretical formulas, as well as the probabilities from our simulated GBS +# QNode, we can compare the two and see whether they agree. +# +# In order to calculate the probability of different GBS events classically, we need a +# method for calculating the hafnian. +# For this, we will use `The Walrus +# `_ library (which is installed as a dependency of the +# PennyLane-SF plugin): + +from thewalrus import hafnian as haf + +############################################################################## +# Now, for the right-hand side numerator, we first calculate the submatrix +# :math:`A = [(UU^T\mathrm{tanh}(r))]_{st}:` + +A = np.dot(U, U.T) * np.tanh(1) + +############################################################################## +# In GBS, we determine the submatrix by taking the +# rows and columns corresponding to the measured Fock state. For example, to calculate the submatrix +# in the case of the output measurement :math:`\left|{1,1,0,0}\right\rangle,` +# we have + +print(A[:, [0, 1]][[0, 1]]) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# [[ 0.19343159-0.54582922j 0.43418269-0.09169615j] +# [ 0.43418269-0.09169615j -0.27554025-0.46222197j]] +# + +############################################################################## +# i.e., we consider only the rows and columns where a photon was detected, which gives us +# the submatrix corresponding to indices :math:`0` and :math:`1.` + +############################################################################## +# Comparing to simulation +# ----------------------- +# +# Now that we have a method for calculating the hafnian, let's compare the output to that provided by +# the PennyLane QNode. +# +# **Measuring** :math:`|0,0,0,0\rangle` **at the output** +# +# This corresponds to the hafnian of an *empty* matrix, which is simply 1: + +print(1 / np.cosh(1) ** 4) +print(probs[0, 0, 0, 0]) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# 0.1763784476141347 +# 0.17637844761413496 +# + +############################################################################## +# **Measuring** :math:`|1,1,0,0\rangle` **at the output** + +A = (np.dot(U, U.T) * np.tanh(1))[:, [0, 1]][[0, 1]] +print(np.abs(haf(A)) ** 2 / np.cosh(1) ** 4) +print(probs[1, 1, 0, 0]) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# 0.03473293649420271 +# 0.03473293649420282 +# + +############################################################################## +# **Measuring** :math:`|0,1,0,1\rangle` **at the output** + +A = (np.dot(U, U.T) * np.tanh(1))[:, [1, 3]][[1, 3]] +print(np.abs(haf(A)) ** 2 / np.cosh(1) ** 4) +print(probs[0, 1, 0, 1]) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# 0.011870900427255558 +# 0.011870900427255589 +# + +############################################################################## +# **Measuring** :math:`|1,1,1,1\rangle` **at the output** +# +# This corresponds to the hafnian of the full matrix :math:`A=UU^T\mathrm{tanh}(r):` + +A = np.dot(U, U.T) * np.tanh(1) +print(np.abs(haf(A)) ** 2 / np.cosh(1) ** 4) +print(probs[1, 1, 1, 1]) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# 0.005957399165336081 +# 0.005957399165336106 +# + +############################################################################## +# **Measuring** :math:`|2,0,0,0\rangle` **at the output** +# +# Since we have two photons in mode ``q[0]``, we take two copies of the +# first row and first column, making sure to divide by :math:`2!:` + +A = (np.dot(U, U.T) * np.tanh(1))[:, [0, 0]][[0, 0]] +print(np.abs(haf(A)) ** 2 / (2 * np.cosh(1) ** 4)) +print(probs[2, 0, 0, 0]) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# 0.029573843083205383 +# 0.02957384308320549 +# +# The PennyLane simulation results agree (with almost negligible numerical error) to the +# expected result from the Gaussian boson sampling equation! +# +# This demo provides an entry-level walkthrough to the ideas behind GBS, +# providing you with the basic code needed for exploring the ideas behind +# the photonic quantum advantage paper. Try changing the number of modes, +# the number of injected squeezed states, or the cutoff dimension, and +# see how each of these affect the classical computation time. If you're +# interested in learning more about GBS, or about photonic quantum +# computing in general, the +# `Strawberry Fields website `__ is a great resource. +# +# References +# ---------- +# +# .. [#Arute2019] +# +# Arute, F., Arya, K., Babbush, R., et al. "Quantum supremacy using a programmable +# superconducting processor" +# `Nature 574, 505-510 (2019) `__. +# +# .. [#Zhong2020] +# +# Zhong, H.-S., Wang, H., Deng, Y.-H., et al. (2020). Quantum computational advantage using photons. Science, 10.1126/science.abe8770. +# +# .. [#hamilton2017] +# +# Craig S. Hamilton, Regina Kruse, Linda Sansoni, Sonja Barkhofen, Christine Silberhorn, +# and Igor Jex. Gaussian boson sampling. Physical Review Letters, 119:170501, Oct 2017. +# arXiv:1612.01199, doi:10.1103/PhysRevLett.119.170501. +# +# .. [#aaronson2013] +# +# Scott Aaronson and Alex Arkhipov. The computational complexity of linear optics. Theory of +# Computing, 9(1):143–252, 2013. doi:10.4086/toc.2013.v009a004. +# +# .. [#Bourassa2020] +# +# Bourassa, J. E., Alexander, R. N., Vasmer, et al. (2020). Blueprint for a scalable +# photonic fault-tolerant quantum computer. arXiv preprint arXiv:2010.02905. +# +# +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_adaptive_circuits/demo.py b/demonstrations_v2/tutorial_adaptive_circuits/demo.py index 3e29579ccd..79d3041fb6 100644 --- a/demonstrations_v2/tutorial_adaptive_circuits/demo.py +++ b/demonstrations_v2/tutorial_adaptive_circuits/demo.py @@ -1,426 +1,426 @@ -r""" - -Adaptive circuits for quantum chemistry -======================================= - -.. meta:: - :property="og:description": Learn how to build quantum chemistry circuits adaptively - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_adaptive_circuits.png - -.. related:: - tutorial_quantum_chemistry Building molecular Hamiltonians - tutorial_vqe A brief overview of VQE - tutorial_givens_rotations Givens rotations for quantum chemistry - - -*Author: Soran Jahangiri — Posted: 13 September 2021. Last updated: 10 April 2023* - -The key component of variational quantum algorithms for quantum chemistry is the circuit used to -prepare electronic ground states of a molecule. The variational quantum eigensolver (VQE) -[#peruzzo2014]_, [#yudong2019]_ is the method of choice for performing such quantum chemistry -simulations on quantum devices with few qubits. For a given molecule, the appropriate circuit can -be generated by using a pre-selected wavefunction ansatz, for example, the unitary coupled cluster -with single and double excitations (UCCSD) [#romero2017]_. In this approach, we include all -possible single and double excitations of electrons from the occupied spin-orbitals of a reference -state to the unoccupied spin-orbitals [#givenstutorial]_. This makes the construction of the ansatz -straightforward for any given molecule. However, using a pre-constructed ansatz has the disadvantage -of reducing performance in favour of generality: the approach may work well in many cases, but it -will not be optimized for a specific problem. - -In practical applications, including all possible excitations usually increases the cost of the -simulations without improving the accuracy of the results. This motivates implementing a strategy -that allows for approximation of the contribution of the excitations and selects only those -excitations that are found to be important for the given molecule. This can be done by using -adaptive methods to construct a circuit for each given problem [#grimsley2019]_. Using adaptive -circuits helps improve performance at the cost of reducing generality. - -.. figure:: /_static/demonstration_assets/adaptive_circuits/main.png - :width: 75% - :align: center - - Examples of selecting specific gates to generate adaptive circuits. - -In this tutorial, you will learn how to **adaptively** build customized quantum chemistry circuits -to perform ADAPT-VQE [#grimsley2019]_ simulations. This includes a recipe to adaptively select gates -that have a significant contribution to the desired state, while neglecting those that have a small -contribution. You will also learn how to use PennyLane to leverage the sparsity of a molecular -Hamiltonian to make the computation of the expectation values even more efficient. Let's get -started! - -Adaptive circuits ------------------ - -The main idea behind building adaptive circuits is to compute the gradients with respect to all -possible excitation gates and then select gates based on the magnitude of the computed gradients. - -There are different ways to make use of the gradient information and here we discuss one of -these strategies and apply it to compute the ground state energy of LiH. This method requires constructing the -Hamiltonian and determining all possible excitations, which we can do with functionality built into PennyLane. -But we first need to define the molecular parameters, including atomic symbols and coordinates. -Note that the atomic coordinates are in `Bohr `_. -""" - -import pennylane as qml -import jax -import numpy as np -import time - -from pennylane import qchem -from jax import numpy as jnp - -jax.config.update("jax_enable_x64", True) - -symbols = ["Li", "H"] -geometry = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 2.969280527]]) -molecule = qchem.Molecule(symbols, geometry) - -############################################################################## -# We now compute the molecular Hamiltonian in the -# `STO-3G `_ basis and obtain the electronic -# excitations. We restrict ourselves to single and double excitations, but higher-level ones such -# as triple and quadruple excitations can be considered as well. Each of these electronic excitations -# is represented by a gate that excites electrons from the occupied orbitals of a reference state to -# the unoccupied ones. This allows us to prepare a state that is a superposition of the reference -# state and all of the excited states. - -H, qubits = qchem.molecular_hamiltonian( - molecule, - active_electrons=2, - active_orbitals=5 -) - -active_electrons = 2 - -singles, doubles = qchem.excitations(active_electrons, qubits) - -print(f"Total number of excitations = {len(singles) + len(doubles)}") - -############################################################################## -# Note that we have a total of 24 excitations which can be represented by the same number of -# excitation gates [#givenstutorial]_. Let's now use an :class:`~.pennylane.AdaptiveOptimizer` -# implemented in PennyLane to construct an adaptive circuit. -# -# Adaptive Optimizer -# ~~~~~~~~~~~~~~~~~~ -# The adaptive optimizer -# grows an input quantum circuit by adding and optimizing gates selected from a user-defined -# collection of operators. The algorithm first appends all of the gates provided in the initial -# operator pool and computes the circuit gradients with respect to the gate parameters. It retains -# the gate which has the largest gradient and then optimizes its parameter. -# The process of growing the circuit can be repeated until the computed gradients converge to zero. -# Let's use :class:`~.pennylane.AdaptiveOptimizer` to perform an ADAPT-VQE [#grimsley2019]_ -# simulation and build an adaptive circuit for LiH. -# -# We first create the operator pool which contains all single and double excitations. - -singles_excitations = [qml.SingleExcitation(0.0, x) for x in singles] -doubles_excitations = [qml.DoubleExcitation(0.0, x) for x in doubles] -operator_pool = doubles_excitations + singles_excitations - -############################################################################## -# We now define an initial circuit that prepares a Hartree-Fock state and returns the expectation -# value of the Hamiltonian. We also need to define a device. - -hf_state = qchem.hf_state(active_electrons, qubits) -dev = qml.device("default.qubit", wires=qubits) -@qml.qnode(dev) -def circuit(): - [qml.PauliX(i) for i in np.nonzero(hf_state)[0]] - return qml.expval(H) - -############################################################################## -# We instantiate the optimizer and use it to build the circuit adaptively. - -opt = qml.optimize.AdaptiveOptimizer() -for i in range(len(operator_pool)): - circuit, energy, gradient = opt.step_and_cost(circuit, operator_pool) - if i % 3 == 0: - print("n = {:}, E = {:.8f} H, Largest Gradient = {:.3f}".format(i, energy, gradient)) - print(qml.draw(circuit, decimals=None)()) - print() - if gradient < 3e-3: - break - -############################################################################## -# The resulting energy matches the exact energy of the ground electronic state of LiH, which is -# -7.8825378193 Ha, within chemical accuracy. Note that some of the gates appear more than once in -# the circuit. By default, :class:`~.pennylane.AdaptiveOptimizer` does not eliminate the selected -# gates from the pool. We can set ``drain_pool=True`` to prevent repetition of the gates by -# removing the selected gate from the operator pool. - -@qml.qnode(dev) -def circuit(): - [qml.PauliX(i) for i in np.nonzero(hf_state)[0]] - return qml.expval(H) - -opt = qml.optimize.AdaptiveOptimizer() -for i in range(len(operator_pool)): - circuit, energy, gradient = opt.step_and_cost(circuit, operator_pool, drain_pool=True) - if i % 2 == 0: - print("n = {:}, E = {:.8f} H, Largest Gradient = {:.3f}".format(i, energy, gradient)) - print(qml.draw(circuit, decimals=None)()) - print() - if gradient < 3e-3: - break - -############################################################################## -# Manual construction -# ~~~~~~~~~~~~~~~~~~~ -# We can also build adaptive circuits manually by adding groups of gates one at a time. We follow -# these steps: -# -# 1. Compute gradients for all double excitations. -# 2. Select the double excitations with gradients larger than a pre-defined threshold. -# 3. Perform VQE to obtain the optimized parameters for the selected double excitations. -# 4. Repeat steps 1 and 2 for the single excitations. -# 5. Perform the final VQE optimization with all the selected excitations. -# -# We create a circuit that applies a selected group of gates to the reference Hartree-Fock state. - - -# Re-define H using Jax Arrays -molecule = qchem.Molecule(symbols, jnp.array(geometry)) -H, qubits = qchem.molecular_hamiltonian( - molecule, - active_electrons=2, - active_orbitals=5 -) - -def circuit_1(params, excitations): - qml.BasisState(jnp.array(hf_state), wires=range(qubits)) - - for i, excitation in enumerate(excitations): - if len(excitation) == 4: - qml.DoubleExcitation(params[i], wires=excitation) - else: - qml.SingleExcitation(params[i], wires=excitation) - return qml.expval(H) - -############################################################################## -# We now construct our first group of gates by including all the double excitations and compute the -# gradient for each one. We also need to define a cost -# function. We initialize the parameter values to zero such that the gradients are computed -# with respect to the Hartree-Fock state. - - -dev = qml.device("lightning.qubit", wires=qubits) -cost_fn = qml.QNode(circuit_1, dev, interface="jax") - -circuit_gradient = jax.grad(cost_fn, argnums=0) - -params = [0.0] * len(doubles) -grads = circuit_gradient(params, excitations=doubles) - -for i in range(len(doubles)): - print(f"Excitation : {doubles[i]}, Gradient: {grads[i]}") - -############################################################################## -# The computed gradients have different values, reflecting the contribution of each gate -# in the final state prepared by the circuit. Many of the gradient values are zero and we select -# those gates that have a gradient above a pre-defined threshold, which we set to :math:`10^{-5}.` - -doubles_select = [doubles[i] for i in range(len(doubles)) if abs(grads[i]) > 1.0e-5] -doubles_select - -############################################################################## -# There are only 6 double excitation gates, out of the original 16, that have gradients above the -# threshold. We add the selected gates to the circuit and optimize it to determine -# the updated parameters for the selected gates. We also need to define an optimizer. Note that the -# optimization is not very costly as we only have six gates in our circuit. - -import optax - -params_doubles = jnp.zeros(len(doubles_select)) - -opt = optax.sgd(learning_rate=0.5) # sgd stands for StochasticGradientDescent -opt_state = opt.init(params_doubles) - -for n in range(10): - gradient = jax.grad(cost_fn, argnums=0)(params_doubles, excitations=doubles_select) - updates, opt_state = opt.update(gradient, opt_state) - params_doubles = optax.apply_updates(params_doubles, updates) - -############################################################################## -# Now, we keep the selected gates in the circuit and compute the gradients with respect to all of -# the single excitation gates, selecting those that have a non-negligible gradient. To do that, we -# need to slightly modify our circuit such that parameters of the double excitation gates are kept -# fixed while the gradients are computed for the single excitation gates. - - -def circuit_2(params, excitations, gates_select, params_select): - qml.BasisState(hf_state, wires=range(qubits)) - - for i, gate in enumerate(gates_select): - if len(gate) == 4: - qml.DoubleExcitation(params_select[i], wires=gate) - elif len(gate) == 2: - qml.SingleExcitation(params_select[i], wires=gate) - - for i, gate in enumerate(excitations): - if len(gate) == 4: - qml.DoubleExcitation(params[i], wires=gate) - elif len(gate) == 2: - qml.SingleExcitation(params[i], wires=gate) - return qml.expval(H) - - -############################################################################## -# We now compute the gradients for the single excitation gates. - -cost_fn = qml.QNode(circuit_2, dev, interface="jax") -circuit_gradient = jax.grad(cost_fn, argnums=0) -params = [0.0] * len(singles) - -grads = circuit_gradient( - params, - excitations=singles, - gates_select=doubles_select, - params_select=params_doubles -) - -for i in range(len(singles)): - print(f"Excitation : {singles[i]}, Gradient: {grads[i]}") - -############################################################################## -# Similar to the double excitation gates, we select those single excitations that have a gradient -# larger than a predefined threshold. - -singles_select = [singles[i] for i in range(len(singles)) if abs(grads[i]) > 1.0e-5] -singles_select - -############################################################################## -# We now have all of the gates we need to build our circuit. The selected single and double -# excitation gates are highlighted in the figure below. -# -# .. figure:: /_static/demonstration_assets/adaptive_circuits/adapted_circuit.png -# :width: 90% -# :align: center -# -# We perform a final circuit optimization to get the ground-state energy. The resulting energy -# should match the exact energy of the ground electronic state of LiH which is -7.8825378193 Ha. - -cost_fn = qml.QNode(circuit_1, dev, interface="jax") - -params = jnp.zeros(len(doubles_select + singles_select)) - -gates_select = doubles_select + singles_select -opt_state = opt.init(params) - -for n in range(10): - t1 = time.time() - gradient = jax.grad(cost_fn, argnums=0)(params, excitations=doubles_select) - updates, opt_state = opt.update(gradient, opt_state) - params = optax.apply_updates(params, updates) - energy = cost_fn(params, doubles_select) - t2 = time.time() - print("n = {:}, E = {:.8f} H, t = {:.2f} s".format(n, energy, t2 - t1)) - -############################################################################## -# Success! We obtained the ground state energy of LiH, within chemical accuracy, by having -# only 10 gates in our circuit. This is less than half of the total number of single and double -# excitations of LiH (24). - -############################################################################## -# Sparse Hamiltonians -# ------------------- -# -# Molecular Hamiltonians and quantum states are sparse. For instance, let’s look at the Hamiltonian -# we built for LiH. We can compute its matrix representation in the computational basis using the -# Hamiltonian function :meth:`~.pennylane.Hamiltonian.sparse_matrix`. This function -# returns the matrix in the SciPy `sparse coordinate `_ format. - -H_sparse = H.sparse_matrix() -H_sparse - -############################################################################## -# The matrix has :math:`1024^2=1,048,576` entries, but only :math:`11264` of them are non-zero. -# -# .. figure:: /_static/demonstration_assets/adaptive_circuits/h_sparse.png -# :width: 65% -# :align: center -# -# Matrix representation of the LiH Hamiltonian in the computational basis. -# -# Leveraging this sparsity can significantly reduce the -# simulation times. We use the implemented functionality in PennyLane for computing the expectation -# value of the sparse Hamiltonian observable. This can reduce the cost of simulations by -# orders of magnitude depending on the size of the molecule. We use the selected gates obtained in -# the previous steps and perform the final optimization step with the sparse method. Note that the -# sparse method currently only works with the parameter-shift differentiation method. - -excitations = doubles_select + singles_select - -params = jnp.zeros(len(excitations)) - -@qml.qnode(dev, diff_method="parameter-shift", interface="jax") -def circuit(params): - qml.BasisState(hf_state, wires=range(qubits)) - - for i, excitation in enumerate(excitations): - if len(excitation) == 4: - qml.DoubleExcitation(params[i], wires=excitation) - elif len(excitation) == 2: - qml.SingleExcitation(params[i], wires=excitation) - - return qml.expval(qml.SparseHamiltonian(H_sparse, wires=range(qubits))) - - -for n in range(10): - t1 = time.time() - gradient = jax.grad(cost_fn, argnums=0)(params, excitations=doubles_select) - updates, opt_state = opt.update(gradient, opt_state) - params = optax.apply_updates(params, updates) - energy = cost_fn(params, doubles_select) - t2 = time.time() - print("n = {:}, E = {:.8f} H, t = {:.2f} s".format(n, energy, t2 - t1)) - -############################################################################## -# Using the sparse method reproduces the ground state energy while the optimization time is -# much shorter. The average iteration time for the sparse method is about 18 times smaller than that -# of the original non-sparse approach. The performance of the sparse optimization will be even -# better for larger molecules. -# -# Conclusions -# ----------- -# We have learned that building quantum chemistry circuits adaptively and using the -# functionality for sparse objects makes molecular simulations significantly more efficient. We -# learned how to use an adaptive optimizer implemented in PennyLane, that selects the gates one at -# time, to perform ADAPT-VQE [#grimsley2019]_ simulations. We also followed an adaptive strategy -# that selects a group of gates based on information about the gradients. -# -# References -# ---------- -# -# .. [#peruzzo2014] -# -# A. Peruzzo, J. McClean *et al.*, "A variational eigenvalue solver on a photonic -# quantum processor". `Nat. Commun. 5, 4213 (2014). -# `__ -# -# .. [#yudong2019] -# -# Y. Cao, J. Romero, *et al.*, "Quantum Chemistry in the Age of Quantum Computing". -# `Chem. Rev. 2019, 119, 19, 10856-10915. -# `__ -# -# .. [#romero2017] -# -# J. Romero, R. Babbush, *et al.*, "Strategies for quantum computing molecular -# energies using the unitary coupled cluster ansatz". `arXiv:1701.02691 -# `_ -# -# .. [#givenstutorial] -# -# :doc:`tutorial_givens_rotations` -# -# .. [#grimsley2019] -# -# H. R. Grimsley, S. E. Economou, E. Barnes, N. J. Mayhall, "An adaptive -# variational algorithm for exact molecular simulations on a quantum computer". -# `Nat. Commun. 2019, 10, 3007. -# `__ -# -# -# About the author -# ---------------- -# +r""" + +Adaptive circuits for quantum chemistry +======================================= + +.. meta:: + :property="og:description": Learn how to build quantum chemistry circuits adaptively + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_adaptive_circuits.png + +.. related:: + tutorial_quantum_chemistry Building molecular Hamiltonians + tutorial_vqe A brief overview of VQE + tutorial_givens_rotations Givens rotations for quantum chemistry + + +*Author: Soran Jahangiri — Posted: 13 September 2021. Last updated: 10 April 2023* + +The key component of variational quantum algorithms for quantum chemistry is the circuit used to +prepare electronic ground states of a molecule. The variational quantum eigensolver (VQE) +[#peruzzo2014]_, [#yudong2019]_ is the method of choice for performing such quantum chemistry +simulations on quantum devices with few qubits. For a given molecule, the appropriate circuit can +be generated by using a pre-selected wavefunction ansatz, for example, the unitary coupled cluster +with single and double excitations (UCCSD) [#romero2017]_. In this approach, we include all +possible single and double excitations of electrons from the occupied spin-orbitals of a reference +state to the unoccupied spin-orbitals [#givenstutorial]_. This makes the construction of the ansatz +straightforward for any given molecule. However, using a pre-constructed ansatz has the disadvantage +of reducing performance in favour of generality: the approach may work well in many cases, but it +will not be optimized for a specific problem. + +In practical applications, including all possible excitations usually increases the cost of the +simulations without improving the accuracy of the results. This motivates implementing a strategy +that allows for approximation of the contribution of the excitations and selects only those +excitations that are found to be important for the given molecule. This can be done by using +adaptive methods to construct a circuit for each given problem [#grimsley2019]_. Using adaptive +circuits helps improve performance at the cost of reducing generality. + +.. figure:: /_static/demonstration_assets/adaptive_circuits/main.png + :width: 75% + :align: center + + Examples of selecting specific gates to generate adaptive circuits. + +In this tutorial, you will learn how to **adaptively** build customized quantum chemistry circuits +to perform ADAPT-VQE [#grimsley2019]_ simulations. This includes a recipe to adaptively select gates +that have a significant contribution to the desired state, while neglecting those that have a small +contribution. You will also learn how to use PennyLane to leverage the sparsity of a molecular +Hamiltonian to make the computation of the expectation values even more efficient. Let's get +started! + +Adaptive circuits +----------------- + +The main idea behind building adaptive circuits is to compute the gradients with respect to all +possible excitation gates and then select gates based on the magnitude of the computed gradients. + +There are different ways to make use of the gradient information and here we discuss one of +these strategies and apply it to compute the ground state energy of LiH. This method requires constructing the +Hamiltonian and determining all possible excitations, which we can do with functionality built into PennyLane. +But we first need to define the molecular parameters, including atomic symbols and coordinates. +Note that the atomic coordinates are in `Bohr `_. +""" + +import pennylane as qml +import jax +import numpy as np +import time + +from pennylane import qchem +from jax import numpy as jnp + +jax.config.update("jax_enable_x64", True) + +symbols = ["Li", "H"] +geometry = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 2.969280527]]) +molecule = qchem.Molecule(symbols, geometry) + +############################################################################## +# We now compute the molecular Hamiltonian in the +# `STO-3G `_ basis and obtain the electronic +# excitations. We restrict ourselves to single and double excitations, but higher-level ones such +# as triple and quadruple excitations can be considered as well. Each of these electronic excitations +# is represented by a gate that excites electrons from the occupied orbitals of a reference state to +# the unoccupied ones. This allows us to prepare a state that is a superposition of the reference +# state and all of the excited states. + +H, qubits = qchem.molecular_hamiltonian( + molecule, + active_electrons=2, + active_orbitals=5 +) + +active_electrons = 2 + +singles, doubles = qchem.excitations(active_electrons, qubits) + +print(f"Total number of excitations = {len(singles) + len(doubles)}") + +############################################################################## +# Note that we have a total of 24 excitations which can be represented by the same number of +# excitation gates [#givenstutorial]_. Let's now use an :class:`~.pennylane.AdaptiveOptimizer` +# implemented in PennyLane to construct an adaptive circuit. +# +# Adaptive Optimizer +# ~~~~~~~~~~~~~~~~~~ +# The adaptive optimizer +# grows an input quantum circuit by adding and optimizing gates selected from a user-defined +# collection of operators. The algorithm first appends all of the gates provided in the initial +# operator pool and computes the circuit gradients with respect to the gate parameters. It retains +# the gate which has the largest gradient and then optimizes its parameter. +# The process of growing the circuit can be repeated until the computed gradients converge to zero. +# Let's use :class:`~.pennylane.AdaptiveOptimizer` to perform an ADAPT-VQE [#grimsley2019]_ +# simulation and build an adaptive circuit for LiH. +# +# We first create the operator pool which contains all single and double excitations. + +singles_excitations = [qml.SingleExcitation(0.0, x) for x in singles] +doubles_excitations = [qml.DoubleExcitation(0.0, x) for x in doubles] +operator_pool = doubles_excitations + singles_excitations + +############################################################################## +# We now define an initial circuit that prepares a Hartree-Fock state and returns the expectation +# value of the Hamiltonian. We also need to define a device. + +hf_state = qchem.hf_state(active_electrons, qubits) +dev = qml.device("default.qubit", wires=qubits) +@qml.qnode(dev) +def circuit(): + [qml.PauliX(i) for i in np.nonzero(hf_state)[0]] + return qml.expval(H) + +############################################################################## +# We instantiate the optimizer and use it to build the circuit adaptively. + +opt = qml.optimize.AdaptiveOptimizer() +for i in range(len(operator_pool)): + circuit, energy, gradient = opt.step_and_cost(circuit, operator_pool) + if i % 3 == 0: + print("n = {:}, E = {:.8f} H, Largest Gradient = {:.3f}".format(i, energy, gradient)) + print(qml.draw(circuit, decimals=None)()) + print() + if gradient < 3e-3: + break + +############################################################################## +# The resulting energy matches the exact energy of the ground electronic state of LiH, which is +# -7.8825378193 Ha, within chemical accuracy. Note that some of the gates appear more than once in +# the circuit. By default, :class:`~.pennylane.AdaptiveOptimizer` does not eliminate the selected +# gates from the pool. We can set ``drain_pool=True`` to prevent repetition of the gates by +# removing the selected gate from the operator pool. + +@qml.qnode(dev) +def circuit(): + [qml.PauliX(i) for i in np.nonzero(hf_state)[0]] + return qml.expval(H) + +opt = qml.optimize.AdaptiveOptimizer() +for i in range(len(operator_pool)): + circuit, energy, gradient = opt.step_and_cost(circuit, operator_pool, drain_pool=True) + if i % 2 == 0: + print("n = {:}, E = {:.8f} H, Largest Gradient = {:.3f}".format(i, energy, gradient)) + print(qml.draw(circuit, decimals=None)()) + print() + if gradient < 3e-3: + break + +############################################################################## +# Manual construction +# ~~~~~~~~~~~~~~~~~~~ +# We can also build adaptive circuits manually by adding groups of gates one at a time. We follow +# these steps: +# +# 1. Compute gradients for all double excitations. +# 2. Select the double excitations with gradients larger than a pre-defined threshold. +# 3. Perform VQE to obtain the optimized parameters for the selected double excitations. +# 4. Repeat steps 1 and 2 for the single excitations. +# 5. Perform the final VQE optimization with all the selected excitations. +# +# We create a circuit that applies a selected group of gates to the reference Hartree-Fock state. + + +# Re-define H using Jax Arrays +molecule = qchem.Molecule(symbols, jnp.array(geometry)) +H, qubits = qchem.molecular_hamiltonian( + molecule, + active_electrons=2, + active_orbitals=5 +) + +def circuit_1(params, excitations): + qml.BasisState(jnp.array(hf_state), wires=range(qubits)) + + for i, excitation in enumerate(excitations): + if len(excitation) == 4: + qml.DoubleExcitation(params[i], wires=excitation) + else: + qml.SingleExcitation(params[i], wires=excitation) + return qml.expval(H) + +############################################################################## +# We now construct our first group of gates by including all the double excitations and compute the +# gradient for each one. We also need to define a cost +# function. We initialize the parameter values to zero such that the gradients are computed +# with respect to the Hartree-Fock state. + + +dev = qml.device("lightning.qubit", wires=qubits) +cost_fn = qml.QNode(circuit_1, dev, interface="jax") + +circuit_gradient = jax.grad(cost_fn, argnums=0) + +params = [0.0] * len(doubles) +grads = circuit_gradient(params, excitations=doubles) + +for i in range(len(doubles)): + print(f"Excitation : {doubles[i]}, Gradient: {grads[i]}") + +############################################################################## +# The computed gradients have different values, reflecting the contribution of each gate +# in the final state prepared by the circuit. Many of the gradient values are zero and we select +# those gates that have a gradient above a pre-defined threshold, which we set to :math:`10^{-5}.` + +doubles_select = [doubles[i] for i in range(len(doubles)) if abs(grads[i]) > 1.0e-5] +doubles_select + +############################################################################## +# There are only 6 double excitation gates, out of the original 16, that have gradients above the +# threshold. We add the selected gates to the circuit and optimize it to determine +# the updated parameters for the selected gates. We also need to define an optimizer. Note that the +# optimization is not very costly as we only have six gates in our circuit. + +import optax + +params_doubles = jnp.zeros(len(doubles_select)) + +opt = optax.sgd(learning_rate=0.5) # sgd stands for StochasticGradientDescent +opt_state = opt.init(params_doubles) + +for n in range(10): + gradient = jax.grad(cost_fn, argnums=0)(params_doubles, excitations=doubles_select) + updates, opt_state = opt.update(gradient, opt_state) + params_doubles = optax.apply_updates(params_doubles, updates) + +############################################################################## +# Now, we keep the selected gates in the circuit and compute the gradients with respect to all of +# the single excitation gates, selecting those that have a non-negligible gradient. To do that, we +# need to slightly modify our circuit such that parameters of the double excitation gates are kept +# fixed while the gradients are computed for the single excitation gates. + + +def circuit_2(params, excitations, gates_select, params_select): + qml.BasisState(hf_state, wires=range(qubits)) + + for i, gate in enumerate(gates_select): + if len(gate) == 4: + qml.DoubleExcitation(params_select[i], wires=gate) + elif len(gate) == 2: + qml.SingleExcitation(params_select[i], wires=gate) + + for i, gate in enumerate(excitations): + if len(gate) == 4: + qml.DoubleExcitation(params[i], wires=gate) + elif len(gate) == 2: + qml.SingleExcitation(params[i], wires=gate) + return qml.expval(H) + + +############################################################################## +# We now compute the gradients for the single excitation gates. + +cost_fn = qml.QNode(circuit_2, dev, interface="jax") +circuit_gradient = jax.grad(cost_fn, argnums=0) +params = [0.0] * len(singles) + +grads = circuit_gradient( + params, + excitations=singles, + gates_select=doubles_select, + params_select=params_doubles +) + +for i in range(len(singles)): + print(f"Excitation : {singles[i]}, Gradient: {grads[i]}") + +############################################################################## +# Similar to the double excitation gates, we select those single excitations that have a gradient +# larger than a predefined threshold. + +singles_select = [singles[i] for i in range(len(singles)) if abs(grads[i]) > 1.0e-5] +singles_select + +############################################################################## +# We now have all of the gates we need to build our circuit. The selected single and double +# excitation gates are highlighted in the figure below. +# +# .. figure:: /_static/demonstration_assets/adaptive_circuits/adapted_circuit.png +# :width: 90% +# :align: center +# +# We perform a final circuit optimization to get the ground-state energy. The resulting energy +# should match the exact energy of the ground electronic state of LiH which is -7.8825378193 Ha. + +cost_fn = qml.QNode(circuit_1, dev, interface="jax") + +params = jnp.zeros(len(doubles_select + singles_select)) + +gates_select = doubles_select + singles_select +opt_state = opt.init(params) + +for n in range(10): + t1 = time.time() + gradient = jax.grad(cost_fn, argnums=0)(params, excitations=doubles_select) + updates, opt_state = opt.update(gradient, opt_state) + params = optax.apply_updates(params, updates) + energy = cost_fn(params, doubles_select) + t2 = time.time() + print("n = {:}, E = {:.8f} H, t = {:.2f} s".format(n, energy, t2 - t1)) + +############################################################################## +# Success! We obtained the ground state energy of LiH, within chemical accuracy, by having +# only 10 gates in our circuit. This is less than half of the total number of single and double +# excitations of LiH (24). + +############################################################################## +# Sparse Hamiltonians +# ------------------- +# +# Molecular Hamiltonians and quantum states are sparse. For instance, let’s look at the Hamiltonian +# we built for LiH. We can compute its matrix representation in the computational basis using the +# Hamiltonian function :meth:`~.pennylane.Hamiltonian.sparse_matrix`. This function +# returns the matrix in the SciPy `sparse coordinate `_ format. + +H_sparse = H.sparse_matrix() +H_sparse + +############################################################################## +# The matrix has :math:`1024^2=1,048,576` entries, but only :math:`11264` of them are non-zero. +# +# .. figure:: /_static/demonstration_assets/adaptive_circuits/h_sparse.png +# :width: 65% +# :align: center +# +# Matrix representation of the LiH Hamiltonian in the computational basis. +# +# Leveraging this sparsity can significantly reduce the +# simulation times. We use the implemented functionality in PennyLane for computing the expectation +# value of the sparse Hamiltonian observable. This can reduce the cost of simulations by +# orders of magnitude depending on the size of the molecule. We use the selected gates obtained in +# the previous steps and perform the final optimization step with the sparse method. Note that the +# sparse method currently only works with the parameter-shift differentiation method. + +excitations = doubles_select + singles_select + +params = jnp.zeros(len(excitations)) + +@qml.qnode(dev, diff_method="parameter-shift", interface="jax") +def circuit(params): + qml.BasisState(hf_state, wires=range(qubits)) + + for i, excitation in enumerate(excitations): + if len(excitation) == 4: + qml.DoubleExcitation(params[i], wires=excitation) + elif len(excitation) == 2: + qml.SingleExcitation(params[i], wires=excitation) + + return qml.expval(qml.SparseHamiltonian(H_sparse, wires=range(qubits))) + + +for n in range(10): + t1 = time.time() + gradient = jax.grad(cost_fn, argnums=0)(params, excitations=doubles_select) + updates, opt_state = opt.update(gradient, opt_state) + params = optax.apply_updates(params, updates) + energy = cost_fn(params, doubles_select) + t2 = time.time() + print("n = {:}, E = {:.8f} H, t = {:.2f} s".format(n, energy, t2 - t1)) + +############################################################################## +# Using the sparse method reproduces the ground state energy while the optimization time is +# much shorter. The average iteration time for the sparse method is about 18 times smaller than that +# of the original non-sparse approach. The performance of the sparse optimization will be even +# better for larger molecules. +# +# Conclusions +# ----------- +# We have learned that building quantum chemistry circuits adaptively and using the +# functionality for sparse objects makes molecular simulations significantly more efficient. We +# learned how to use an adaptive optimizer implemented in PennyLane, that selects the gates one at +# time, to perform ADAPT-VQE [#grimsley2019]_ simulations. We also followed an adaptive strategy +# that selects a group of gates based on information about the gradients. +# +# References +# ---------- +# +# .. [#peruzzo2014] +# +# A. Peruzzo, J. McClean *et al.*, "A variational eigenvalue solver on a photonic +# quantum processor". `Nat. Commun. 5, 4213 (2014). +# `__ +# +# .. [#yudong2019] +# +# Y. Cao, J. Romero, *et al.*, "Quantum Chemistry in the Age of Quantum Computing". +# `Chem. Rev. 2019, 119, 19, 10856-10915. +# `__ +# +# .. [#romero2017] +# +# J. Romero, R. Babbush, *et al.*, "Strategies for quantum computing molecular +# energies using the unitary coupled cluster ansatz". `arXiv:1701.02691 +# `_ +# +# .. [#givenstutorial] +# +# :doc:`tutorial_givens_rotations` +# +# .. [#grimsley2019] +# +# H. R. Grimsley, S. E. Economou, E. Barnes, N. J. Mayhall, "An adaptive +# variational algorithm for exact molecular simulations on a quantum computer". +# `Nat. Commun. 2019, 10, 3007. +# `__ +# +# +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_adversarial_attacks_QML/metadata.json b/demonstrations_v2/tutorial_adversarial_attacks_QML/metadata.json index ba6ee618b5..79f6a3580b 100644 --- a/demonstrations_v2/tutorial_adversarial_attacks_QML/metadata.json +++ b/demonstrations_v2/tutorial_adversarial_attacks_QML/metadata.json @@ -1,79 +1,79 @@ -{ - "title": "Adversarial attacks and robustness for quantum machine learning", - "authors": [ - { - "username": "mxw" - }, - { - "username": "kil" - - } - ], - "dateOfPublication": "2024-09-16T00:00:00+00:00", - "dateOfLastModification": "2024-11-08T00:00:00+00:00", - "categories": ["Quantum Machine Learning", "Quantum Computing"], - "tags": ["Quantum machine learning", "QML", "Adversarial attacks", "Quantum security"], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_adversarial_attacks_qml.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_adversarial_attacks_qml.png" - } - ], - "seoDescription": "Learn how to construct adversarial attacks on classification networks and how to make quantum machine learning (QML) models more robust.", - "doi": "", - "references": [ - { - "id": "Wendlinger2024", - "type": "preprint", - "title": "A Comparative Analysis of Adversarial Robustness for Quantum and Classical Machine Learning Models", - "authors": "Maximilian Wendlinger, Kilian Tscharke, Pascal Debus", - "year": "2024", - "doi": "10.48550/arXiv.2404.16154", - "url": "https://arxiv.org/abs/2404.16154" - }, - { - "id": "Goodfellow2014", - "type": "preprint", - "title": "Explaining and harnessing adversarial examples", - "authors": "Ian J. Goodfellow, Jonathon Shlens, Christian Szegedy", - "year": "2014", - "doi": "10.48550/arXiv.1412.6572", - "url": "https://arxiv.org/abs/1412.6572" - - }, - { - "id": "Liu2020", - "type": "preprint", - "title": "A rigorous and robust quantum speed-up in supervised machine learning", - "authors": "Yunchao Liu, Srinivasan Arunachalam, Kristan Temme", - "year": "2020", - "doi": "10.48550/arXiv.2010.02174", - "url": "https://arxiv.org/abs/2010.02174" - - }, - { - "id": "Lu2019", - "type": "preprint", - "title": "Quantum Adversarial Machine Learning", - "authors": "Sirui Lu, Lu-Ming Duan, Dong-Ling Deng", - "year": "2019", - "doi": "10.48550/arXiv.2001.00030", - "url": "https://arxiv.org/abs/2001.00030" - - } - ], - "basedOnPapers": ["10.48550/arXiv.2404.16154"], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_variational_classifier", - "weight": 1.0 - } - ], - "hardware": [] -} +{ + "title": "Adversarial attacks and robustness for quantum machine learning", + "authors": [ + { + "username": "mxw" + }, + { + "username": "kil" + + } + ], + "dateOfPublication": "2024-09-16T00:00:00+00:00", + "dateOfLastModification": "2024-11-08T00:00:00+00:00", + "categories": ["Quantum Machine Learning", "Quantum Computing"], + "tags": ["Quantum machine learning", "QML", "Adversarial attacks", "Quantum security"], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_adversarial_attacks_qml.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_adversarial_attacks_qml.png" + } + ], + "seoDescription": "Learn how to construct adversarial attacks on classification networks and how to make quantum machine learning (QML) models more robust.", + "doi": "", + "references": [ + { + "id": "Wendlinger2024", + "type": "preprint", + "title": "A Comparative Analysis of Adversarial Robustness for Quantum and Classical Machine Learning Models", + "authors": "Maximilian Wendlinger, Kilian Tscharke, Pascal Debus", + "year": "2024", + "doi": "10.48550/arXiv.2404.16154", + "url": "https://arxiv.org/abs/2404.16154" + }, + { + "id": "Goodfellow2014", + "type": "preprint", + "title": "Explaining and harnessing adversarial examples", + "authors": "Ian J. Goodfellow, Jonathon Shlens, Christian Szegedy", + "year": "2014", + "doi": "10.48550/arXiv.1412.6572", + "url": "https://arxiv.org/abs/1412.6572" + + }, + { + "id": "Liu2020", + "type": "preprint", + "title": "A rigorous and robust quantum speed-up in supervised machine learning", + "authors": "Yunchao Liu, Srinivasan Arunachalam, Kristan Temme", + "year": "2020", + "doi": "10.48550/arXiv.2010.02174", + "url": "https://arxiv.org/abs/2010.02174" + + }, + { + "id": "Lu2019", + "type": "preprint", + "title": "Quantum Adversarial Machine Learning", + "authors": "Sirui Lu, Lu-Ming Duan, Dong-Ling Deng", + "year": "2019", + "doi": "10.48550/arXiv.2001.00030", + "url": "https://arxiv.org/abs/2001.00030" + + } + ], + "basedOnPapers": ["10.48550/arXiv.2404.16154"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_variational_classifier", + "weight": 1.0 + } + ], + "hardware": [] +} diff --git a/demonstrations_v2/tutorial_backprop/demo.py b/demonstrations_v2/tutorial_backprop/demo.py index 9db4a40766..c55bbf280b 100644 --- a/demonstrations_v2/tutorial_backprop/demo.py +++ b/demonstrations_v2/tutorial_backprop/demo.py @@ -1,456 +1,456 @@ -r""" -Quantum gradients with backpropagation -====================================== - -.. meta:: - :property="og:description": Using backpropagation can speed up training of quantum circuits compared to the parameter-shift rule—if you are using a simulator. - - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/sphx_glr_tutorial_backprop_002.png - -.. related:: - - tutorial_quantum_natural_gradient Quantum natural gradient - -*Author: Josh Izaac — Posted: 11 August 2020. Last updated: 31 January 2021.* - -In PennyLane, any quantum device, whether a hardware device or a simulator, can be -trained using the :doc:`parameter-shift rule ` to compute quantum -gradients. Indeed, the parameter-shift rule is ideally suited to hardware devices, as it does -not require any knowledge about the internal workings of the device; it is sufficient to treat -the device as a 'black box', and to query it with different input values in order to determine the gradient. - -When working with simulators, however, we *do* have access to the internal (classical) -computations being performed. This allows us to take advantage of other methods of computing the -gradient, such as backpropagation, which may be advantageous in certain regimes. In this tutorial, -we will compare and contrast the parameter-shift rule against backpropagation, using -the PennyLane :class:`default.qubit ` -device. - -The parameter-shift rule ------------------------- - -The parameter-shift rule states that, given a variational quantum circuit :math:`U(\boldsymbol -\theta)` composed of parametrized Pauli rotations, and some measured observable :math:`\hat{B},` the -derivative of the expectation value - -.. math:: - - \langle \hat{B} \rangle (\boldsymbol\theta) = - \langle 0 \vert U(\boldsymbol\theta)^\dagger \hat{B} U(\boldsymbol\theta) \vert 0\rangle - -with respect to the input circuit parameters :math:`\boldsymbol{\theta}` is given by - -.. math:: - - \nabla_{\theta_i}\langle \hat{B} \rangle(\boldsymbol\theta) - = \frac{1}{2} - \left[ - \langle \hat{B} \rangle\left(\boldsymbol\theta + \frac{\pi}{2}\hat{\mathbf{e}}_i\right) - - \langle \hat{B} \rangle\left(\boldsymbol\theta - \frac{\pi}{2}\hat{\mathbf{e}}_i\right) - \right]. - -Thus, the gradient of the expectation value can be calculated by evaluating the same variational -quantum circuit, but with shifted parameter values (hence the name, parameter-shift rule!). - -Let's have a go implementing the parameter-shift rule manually in PennyLane. -""" -import pennylane as qml -from jax import numpy as jnp -from matplotlib import pyplot as plt -import jax - -jax.config.update("jax_platform_name", "cpu") -jax.config.update('jax_enable_x64', True) - -# set the random seed -key = jax.random.PRNGKey(42) - - -# create a device to execute the circuit on -dev = qml.device("default.qubit", wires=3) - - -def CNOT_ring(wires): - """Apply CNOTs in a ring pattern""" - n_wires = len(wires) - - for w in wires: - qml.CNOT([w % n_wires, (w + 1) % n_wires]) - - -@qml.qnode(dev, diff_method="parameter-shift") -def circuit(params): - qml.RX(params[0], wires=0) - qml.RY(params[1], wires=1) - qml.RZ(params[2], wires=2) - - CNOT_ring(wires=[0, 1, 2]) - - qml.RX(params[3], wires=0) - qml.RY(params[4], wires=1) - qml.RZ(params[5], wires=2) - - CNOT_ring(wires=[0, 1, 2]) - return qml.expval(qml.PauliY(0) @ qml.PauliZ(2)) - - -############################################################################## -# Let's test the variational circuit evaluation with some parameter input: - -# initial parameters -params = jax.random.normal(key, [6]) - - -print("Parameters:", params) -print("Expectation value:", circuit(params)) - -############################################################################## -# We can also draw the executed quantum circuit: - -fig, ax = qml.draw_mpl(circuit, decimals=2)(params) -plt.show() - - -############################################################################## -# Now that we have defined our variational circuit QNode, we can construct -# a function that computes the gradient of the :math:`i\text{th}` parameter -# using the parameter-shift rule. - -def parameter_shift_term(qnode, params, i): - shifted = params.copy() - shifted = shifted.at[i].add(jnp.pi/2) - forward = qnode(shifted) # forward evaluation - - shifted = shifted.at[i].add(-jnp.pi) - backward = qnode(shifted) # backward evaluation - - return 0.5 * (forward - backward) - -# gradient with respect to the first parameter -print(parameter_shift_term(circuit, params, 0)) - -############################################################################## -# In order to compute the gradient with respect to *all* parameters, we need -# to loop over the index ``i``: - -def parameter_shift(qnode, params): - gradients = jnp.zeros([len(params)]) - - for i in range(len(params)): - gradients = gradients.at[i].set(parameter_shift_term(qnode, params, i)) - - return gradients - -print(parameter_shift(circuit, params)) - -############################################################################## -# We can compare this to PennyLane's *built-in* quantum gradient support by using -# the ``jax.grad`` function. Remember, when we defined the -# QNode, we specified that we wanted it to be differentiable using the parameter-shift -# method (``diff_method="parameter-shift"``). - -grad_function = jax.grad(circuit) -print(grad_function(params)[0]) - -############################################################################## -# Alternatively, we can directly compute quantum gradients of QNodes using -# PennyLane's built in :mod:`qml.gradients ` module: - -print(jnp.stack(qml.gradients.param_shift(circuit)(params))) - -############################################################################## -# If you count the number of quantum evaluations, you will notice that we had to evaluate the circuit -# ``2*len(params)`` number of times in order to compute the quantum gradient with respect to all -# parameters. While reasonably fast for a small number of parameters, as the number of parameters in -# our quantum circuit grows, so does both -# -# 1. the circuit depth (and thus the time taken to evaluate each expectation value or 'forward' pass), and -# -# 2. the number of parameter-shift evaluations required. -# -# Both of these factors increase the time taken to compute the gradient with -# respect to all parameters. -# -# Benchmarking -# ~~~~~~~~~~~~ -# -# Let's consider an example with a significantly larger number of parameters. -# We'll make use of the :class:`~pennylane.StronglyEntanglingLayers` template -# to make a more complicated QNode. - -dev = qml.device("default.qubit", wires=4) - -@qml.qnode(dev, diff_method="parameter-shift") -def circuit(params): - qml.StronglyEntanglingLayers(params, wires=[0, 1, 2, 3]) - return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3)) - -# initialize circuit parameters -param_shape = qml.StronglyEntanglingLayers.shape(n_wires=4, n_layers=15) -params = jax.random.normal(key, param_shape) * 0.1 - -print(params.size) -print(circuit(params)) - -############################################################################## -# This circuit has 180 parameters. Let's see how long it takes to perform a forward -# pass of the circuit. - -import timeit - -reps = 3 -num = 10 -times = timeit.repeat("circuit(params)", globals=globals(), number=num, repeat=reps) -forward_time = min(times) / num - -print(f"Forward pass (best of {reps}): {forward_time} sec per loop") - - -############################################################################## -# We can now estimate the time taken to compute the full gradient vector, -# and see how this compares. - -# create the gradient function -grad_fn = jax.grad(circuit) - -times = timeit.repeat("grad_fn(params)", globals=globals(), number=num, repeat=reps) -backward_time = min(times) / num - -print(f"Gradient computation (best of {reps}): {backward_time} sec per loop") - - -############################################################################## -# Based on the parameter-shift rule, we expect that the amount of time to compute the quantum -# gradients should be approximately :math:`2p\Delta t_{f}` where :math:`p` is the number of -# parameters and :math:`\Delta t_{f}` if the time taken for the forward pass. Let's verify this: - -print(2 * forward_time * params.size) - - -############################################################################## -# Backpropagation -# --------------- -# -# An alternative to the parameter-shift rule for computing gradients is -# `reverse-mode autodifferentiation `__. -# Unlike the parameter-shift method, which requires :math:`2p` circuit evaluations for -# :math:`p` parameters, reverse-mode requires only a *single* forward pass of the -# differentiable function to compute -# the gradient of all variables, at the expense of increased memory usage. -# During the forward pass, the results of all intermediate subexpressions are stored; -# the computation is then traversed *in reverse*, with the gradient computed by repeatedly -# applying the chain rule. -# In most classical machine learning settings (where we are training scalar loss functions -# consisting of a large number of parameters), -# reverse-mode autodifferentiation is the -# preferred method of autodifferentiation—the reduction in computational time enables larger and -# more complex models to be successfully trained. The backpropagation algorithm is a particular -# special-case of reverse-mode autodifferentiation, which has helped lead to the machine learning -# explosion we see today. -# -# In quantum machine learning, however, the inability to store and utilize the results of -# *intermediate* quantum operations on hardware remains a barrier to using backprop; -# while reverse-mode -# autodifferentiation works fine for small quantum simulations, only the -# parameter-shift rule can be used to compute gradients on quantum hardware directly. Nevertheless, -# when training quantum models via classical simulation, it's useful to explore the regimes where -# reverse-mode differentiation may be a better choice than the parameter-shift rule. -# -# Benchmarking -# ~~~~~~~~~~~~ -# -# When creating a QNode, :doc:`PennyLane supports various methods of differentiation -# `, including ``"parameter-shift"`` (which we used previously), -# ``"finite-diff"``, ``"reversible"``, and ``"backprop"``. While ``"parameter-shift"`` works with all devices -# (simulator or hardware), ``"backprop"`` will only work for specific simulator devices that are -# designed to support backpropagation. -# -# One such device is :class:`default.qubit `. It -# has backends written using TensorFlow, JAX, and Autograd, so when used with the -# TensorFlow, JAX, and Autograd interfaces respectively, supports backpropagation. -# In this demo, we will use the JAX interface. - -dev = qml.device("default.qubit", wires=4) - -############################################################################## -# When defining the QNode, we specify ``diff_method="backprop"`` to ensure that -# we are using backpropagation mode. Note that this is the *default differentiation -# mode* for the ``default.qubit`` device. - - -@qml.qnode(dev, diff_method="backprop") -def circuit(params): - qml.StronglyEntanglingLayers(params, wires=[0, 1, 2, 3]) - return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3)) - -# initialize circuit parameters -param_shape = qml.StronglyEntanglingLayers.shape(n_wires=4, n_layers=15) -params = jax.random.normal(key, param_shape) * 0.1 - -print(circuit(params)) - -############################################################################## -# Let's see how long it takes to perform a forward pass of the circuit. - -import timeit - -reps = 3 -num = 10 -times = timeit.repeat("circuit(params)", globals=globals(), number=num, repeat=reps) -forward_time = min(times) / num -print(f"Forward pass (best of {reps}): {forward_time} sec per loop") - - -############################################################################## -# Comparing this to the forward pass from ``default.qubit``, we note that there is some potential -# overhead from using backpropagation. We can now estimate the time required to perform a -# gradient computation via backpropagation: - -times = timeit.repeat("jax.grad(circuit)(params)", globals=globals(), number=num, repeat=reps) -backward_time = min(times) / num -print(f"Backward pass (best of {reps}): {backward_time} sec per loop") - -############################################################################## -# Unlike with the parameter-shift rule, the time taken to perform the backwards pass appears -# of the order of a single forward pass! This can significantly speed up training of simulated -# circuits with many parameters. -# -# Time comparison -# --------------- -# -# Let's compare the two differentiation approaches as the number of trainable parameters -# in the variational circuit increases, by timing both the forward pass and the gradient -# computation as the number of layers is allowed to increase. - -dev = qml.device("default.qubit", wires=4) - -def circuit(params): - qml.StronglyEntanglingLayers(params, wires=[0, 1, 2, 3]) - return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3)) - -############################################################################## -# We'll continue to use the same ansatz as before, but to reduce the time taken -# to collect the data, we'll reduce the number and repetitions of timings per data -# point. Below, we loop over a variational circuit depth ranging from 0 (no gates/ -# trainable parameters) to 20. Each layer will contain :math:`3N` parameters, where -# :math:`N` is the number of wires (in this case, we have :math:`N=4`). - -reps = 2 -num = 3 - -forward_shift = [] -gradient_shift = [] -forward_backprop = [] -gradient_backprop = [] - -qnode_shift = jax.jit(qml.QNode(circuit, dev, diff_method="parameter-shift")) -qnode_backprop = jax.jit(qml.QNode(circuit, dev, diff_method="backprop")) - -grad_qnode_shift = jax.jit(jax.grad(qml.QNode(circuit, dev, diff_method="parameter-shift"))) -grad_qnode_backprop = jax.jit(jax.grad(qml.QNode(circuit, dev, diff_method="backprop"))) - -for depth in range(0, 21): - param_shape = qml.StronglyEntanglingLayers.shape(n_wires=4, n_layers=depth) - params = jax.random.normal(key, param_shape) * 0.1 - - num_params = params.size - - # forward pass timing - # =================== - - - # parameter-shift - t = timeit.repeat("qnode_shift(params)", globals=globals(), number=num, repeat=reps) - forward_shift.append([num_params, min(t) / num]) - - # backprop - t = timeit.repeat("qnode_backprop(params)", globals=globals(), number=num, repeat=reps) - forward_backprop.append([num_params, min(t) / num]) - - if num_params == 0: - continue - - # Gradient timing - # =============== - - # parameter-shift - t = timeit.repeat("grad_qnode_shift(params)", globals=globals(), number=num, repeat=reps) - gradient_shift.append([num_params, min(t) / num]) - - # backprop - t = timeit.repeat("grad_qnode_backprop(params)", globals=globals(), number=num, repeat=reps) - gradient_backprop.append([num_params, min(t) / num]) - -gradient_shift = jnp.array(gradient_shift).T -gradient_backprop = jnp.array(gradient_backprop).T -forward_shift = jnp.array(forward_shift).T -forward_backprop = jnp.array(forward_backprop).T - -############################################################################## -# We now import matplotlib, and plot the results. - -plt.style.use("bmh") - -fig, ax = plt.subplots(1, 1, figsize=(6, 4)) - -ax.plot(*gradient_shift, '.-', label="Parameter-shift") -ax.plot(*gradient_backprop, '.-', label="Backprop") -ax.set_ylabel("Time (s)") -ax.set_xlabel("Number of parameters") -ax.legend() - -plt.show() - -############################################################################## -# .. raw:: html -# -#
-# -# We can see that the computational time for the parameter-shift rule increases with -# increasing number of parameters, as expected, whereas the computational time -# for backpropagation appears much more constant, with perhaps a minute linear increase -# with :math:`p.` Note that the plots are not perfectly linear, with some 'bumpiness' or -# noisiness. This is likely due to low-level operating system jitter, and -# other environmental fluctuations—increasing the number of repeats can help smooth -# out the plot. -# -# For a better comparison, we can scale the time required for computing the quantum -# gradients against the time taken for the corresponding forward pass: - -gradient_shift = gradient_shift.at[1].divide(forward_shift[1, 1:]) -gradient_backprop = gradient_backprop.at[1].divide(forward_backprop[1, 1:]) - -fig, ax = plt.subplots(1, 1, figsize=(6, 4)) - -ax.plot(*gradient_shift, '.-', label="Parameter-shift") -ax.plot(*gradient_backprop, '.-', label="Backprop") - -# perform a least squares regression to determine the linear best fit/gradient -# for the normalized time vs. number of parameters -x = gradient_shift[0] -m_shift, c_shift = jnp.polyfit(*gradient_shift, deg=1) -m_back, c_back = jnp.polyfit(*gradient_backprop, deg=1) - -ax.plot(x, m_shift * x + c_shift, '--', label=f"{m_shift:.2f}p{c_shift:+.2f}") -ax.plot(x, m_back * x + c_back, '--', label=f"{m_back:.2f}p{c_back:+.2f}") - -ax.set_ylabel("Normalized time") -ax.set_xlabel("Number of parameters") -ax.set_xscale("log") -ax.set_yscale("log") -ax.legend() - -plt.show() - -############################################################################## -# .. raw:: html -# -#
-# -# We can now see clearly that there is constant overhead for backpropagation with -# ``default.qubit``, but the parameter-shift rule scales as :math:`\sim 2p.` -# -# -# About the author -# ---------------- -# +r""" +Quantum gradients with backpropagation +====================================== + +.. meta:: + :property="og:description": Using backpropagation can speed up training of quantum circuits compared to the parameter-shift rule—if you are using a simulator. + + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/sphx_glr_tutorial_backprop_002.png + +.. related:: + + tutorial_quantum_natural_gradient Quantum natural gradient + +*Author: Josh Izaac — Posted: 11 August 2020. Last updated: 31 January 2021.* + +In PennyLane, any quantum device, whether a hardware device or a simulator, can be +trained using the :doc:`parameter-shift rule ` to compute quantum +gradients. Indeed, the parameter-shift rule is ideally suited to hardware devices, as it does +not require any knowledge about the internal workings of the device; it is sufficient to treat +the device as a 'black box', and to query it with different input values in order to determine the gradient. + +When working with simulators, however, we *do* have access to the internal (classical) +computations being performed. This allows us to take advantage of other methods of computing the +gradient, such as backpropagation, which may be advantageous in certain regimes. In this tutorial, +we will compare and contrast the parameter-shift rule against backpropagation, using +the PennyLane :class:`default.qubit ` +device. + +The parameter-shift rule +------------------------ + +The parameter-shift rule states that, given a variational quantum circuit :math:`U(\boldsymbol +\theta)` composed of parametrized Pauli rotations, and some measured observable :math:`\hat{B},` the +derivative of the expectation value + +.. math:: + + \langle \hat{B} \rangle (\boldsymbol\theta) = + \langle 0 \vert U(\boldsymbol\theta)^\dagger \hat{B} U(\boldsymbol\theta) \vert 0\rangle + +with respect to the input circuit parameters :math:`\boldsymbol{\theta}` is given by + +.. math:: + + \nabla_{\theta_i}\langle \hat{B} \rangle(\boldsymbol\theta) + = \frac{1}{2} + \left[ + \langle \hat{B} \rangle\left(\boldsymbol\theta + \frac{\pi}{2}\hat{\mathbf{e}}_i\right) + - \langle \hat{B} \rangle\left(\boldsymbol\theta - \frac{\pi}{2}\hat{\mathbf{e}}_i\right) + \right]. + +Thus, the gradient of the expectation value can be calculated by evaluating the same variational +quantum circuit, but with shifted parameter values (hence the name, parameter-shift rule!). + +Let's have a go implementing the parameter-shift rule manually in PennyLane. +""" +import pennylane as qml +from jax import numpy as jnp +from matplotlib import pyplot as plt +import jax + +jax.config.update("jax_platform_name", "cpu") +jax.config.update('jax_enable_x64', True) + +# set the random seed +key = jax.random.PRNGKey(42) + + +# create a device to execute the circuit on +dev = qml.device("default.qubit", wires=3) + + +def CNOT_ring(wires): + """Apply CNOTs in a ring pattern""" + n_wires = len(wires) + + for w in wires: + qml.CNOT([w % n_wires, (w + 1) % n_wires]) + + +@qml.qnode(dev, diff_method="parameter-shift") +def circuit(params): + qml.RX(params[0], wires=0) + qml.RY(params[1], wires=1) + qml.RZ(params[2], wires=2) + + CNOT_ring(wires=[0, 1, 2]) + + qml.RX(params[3], wires=0) + qml.RY(params[4], wires=1) + qml.RZ(params[5], wires=2) + + CNOT_ring(wires=[0, 1, 2]) + return qml.expval(qml.PauliY(0) @ qml.PauliZ(2)) + + +############################################################################## +# Let's test the variational circuit evaluation with some parameter input: + +# initial parameters +params = jax.random.normal(key, [6]) + + +print("Parameters:", params) +print("Expectation value:", circuit(params)) + +############################################################################## +# We can also draw the executed quantum circuit: + +fig, ax = qml.draw_mpl(circuit, decimals=2)(params) +plt.show() + + +############################################################################## +# Now that we have defined our variational circuit QNode, we can construct +# a function that computes the gradient of the :math:`i\text{th}` parameter +# using the parameter-shift rule. + +def parameter_shift_term(qnode, params, i): + shifted = params.copy() + shifted = shifted.at[i].add(jnp.pi/2) + forward = qnode(shifted) # forward evaluation + + shifted = shifted.at[i].add(-jnp.pi) + backward = qnode(shifted) # backward evaluation + + return 0.5 * (forward - backward) + +# gradient with respect to the first parameter +print(parameter_shift_term(circuit, params, 0)) + +############################################################################## +# In order to compute the gradient with respect to *all* parameters, we need +# to loop over the index ``i``: + +def parameter_shift(qnode, params): + gradients = jnp.zeros([len(params)]) + + for i in range(len(params)): + gradients = gradients.at[i].set(parameter_shift_term(qnode, params, i)) + + return gradients + +print(parameter_shift(circuit, params)) + +############################################################################## +# We can compare this to PennyLane's *built-in* quantum gradient support by using +# the ``jax.grad`` function. Remember, when we defined the +# QNode, we specified that we wanted it to be differentiable using the parameter-shift +# method (``diff_method="parameter-shift"``). + +grad_function = jax.grad(circuit) +print(grad_function(params)[0]) + +############################################################################## +# Alternatively, we can directly compute quantum gradients of QNodes using +# PennyLane's built in :mod:`qml.gradients ` module: + +print(jnp.stack(qml.gradients.param_shift(circuit)(params))) + +############################################################################## +# If you count the number of quantum evaluations, you will notice that we had to evaluate the circuit +# ``2*len(params)`` number of times in order to compute the quantum gradient with respect to all +# parameters. While reasonably fast for a small number of parameters, as the number of parameters in +# our quantum circuit grows, so does both +# +# 1. the circuit depth (and thus the time taken to evaluate each expectation value or 'forward' pass), and +# +# 2. the number of parameter-shift evaluations required. +# +# Both of these factors increase the time taken to compute the gradient with +# respect to all parameters. +# +# Benchmarking +# ~~~~~~~~~~~~ +# +# Let's consider an example with a significantly larger number of parameters. +# We'll make use of the :class:`~pennylane.StronglyEntanglingLayers` template +# to make a more complicated QNode. + +dev = qml.device("default.qubit", wires=4) + +@qml.qnode(dev, diff_method="parameter-shift") +def circuit(params): + qml.StronglyEntanglingLayers(params, wires=[0, 1, 2, 3]) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3)) + +# initialize circuit parameters +param_shape = qml.StronglyEntanglingLayers.shape(n_wires=4, n_layers=15) +params = jax.random.normal(key, param_shape) * 0.1 + +print(params.size) +print(circuit(params)) + +############################################################################## +# This circuit has 180 parameters. Let's see how long it takes to perform a forward +# pass of the circuit. + +import timeit + +reps = 3 +num = 10 +times = timeit.repeat("circuit(params)", globals=globals(), number=num, repeat=reps) +forward_time = min(times) / num + +print(f"Forward pass (best of {reps}): {forward_time} sec per loop") + + +############################################################################## +# We can now estimate the time taken to compute the full gradient vector, +# and see how this compares. + +# create the gradient function +grad_fn = jax.grad(circuit) + +times = timeit.repeat("grad_fn(params)", globals=globals(), number=num, repeat=reps) +backward_time = min(times) / num + +print(f"Gradient computation (best of {reps}): {backward_time} sec per loop") + + +############################################################################## +# Based on the parameter-shift rule, we expect that the amount of time to compute the quantum +# gradients should be approximately :math:`2p\Delta t_{f}` where :math:`p` is the number of +# parameters and :math:`\Delta t_{f}` if the time taken for the forward pass. Let's verify this: + +print(2 * forward_time * params.size) + + +############################################################################## +# Backpropagation +# --------------- +# +# An alternative to the parameter-shift rule for computing gradients is +# `reverse-mode autodifferentiation `__. +# Unlike the parameter-shift method, which requires :math:`2p` circuit evaluations for +# :math:`p` parameters, reverse-mode requires only a *single* forward pass of the +# differentiable function to compute +# the gradient of all variables, at the expense of increased memory usage. +# During the forward pass, the results of all intermediate subexpressions are stored; +# the computation is then traversed *in reverse*, with the gradient computed by repeatedly +# applying the chain rule. +# In most classical machine learning settings (where we are training scalar loss functions +# consisting of a large number of parameters), +# reverse-mode autodifferentiation is the +# preferred method of autodifferentiation—the reduction in computational time enables larger and +# more complex models to be successfully trained. The backpropagation algorithm is a particular +# special-case of reverse-mode autodifferentiation, which has helped lead to the machine learning +# explosion we see today. +# +# In quantum machine learning, however, the inability to store and utilize the results of +# *intermediate* quantum operations on hardware remains a barrier to using backprop; +# while reverse-mode +# autodifferentiation works fine for small quantum simulations, only the +# parameter-shift rule can be used to compute gradients on quantum hardware directly. Nevertheless, +# when training quantum models via classical simulation, it's useful to explore the regimes where +# reverse-mode differentiation may be a better choice than the parameter-shift rule. +# +# Benchmarking +# ~~~~~~~~~~~~ +# +# When creating a QNode, :doc:`PennyLane supports various methods of differentiation +# `, including ``"parameter-shift"`` (which we used previously), +# ``"finite-diff"``, ``"reversible"``, and ``"backprop"``. While ``"parameter-shift"`` works with all devices +# (simulator or hardware), ``"backprop"`` will only work for specific simulator devices that are +# designed to support backpropagation. +# +# One such device is :class:`default.qubit `. It +# has backends written using TensorFlow, JAX, and Autograd, so when used with the +# TensorFlow, JAX, and Autograd interfaces respectively, supports backpropagation. +# In this demo, we will use the JAX interface. + +dev = qml.device("default.qubit", wires=4) + +############################################################################## +# When defining the QNode, we specify ``diff_method="backprop"`` to ensure that +# we are using backpropagation mode. Note that this is the *default differentiation +# mode* for the ``default.qubit`` device. + + +@qml.qnode(dev, diff_method="backprop") +def circuit(params): + qml.StronglyEntanglingLayers(params, wires=[0, 1, 2, 3]) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3)) + +# initialize circuit parameters +param_shape = qml.StronglyEntanglingLayers.shape(n_wires=4, n_layers=15) +params = jax.random.normal(key, param_shape) * 0.1 + +print(circuit(params)) + +############################################################################## +# Let's see how long it takes to perform a forward pass of the circuit. + +import timeit + +reps = 3 +num = 10 +times = timeit.repeat("circuit(params)", globals=globals(), number=num, repeat=reps) +forward_time = min(times) / num +print(f"Forward pass (best of {reps}): {forward_time} sec per loop") + + +############################################################################## +# Comparing this to the forward pass from ``default.qubit``, we note that there is some potential +# overhead from using backpropagation. We can now estimate the time required to perform a +# gradient computation via backpropagation: + +times = timeit.repeat("jax.grad(circuit)(params)", globals=globals(), number=num, repeat=reps) +backward_time = min(times) / num +print(f"Backward pass (best of {reps}): {backward_time} sec per loop") + +############################################################################## +# Unlike with the parameter-shift rule, the time taken to perform the backwards pass appears +# of the order of a single forward pass! This can significantly speed up training of simulated +# circuits with many parameters. +# +# Time comparison +# --------------- +# +# Let's compare the two differentiation approaches as the number of trainable parameters +# in the variational circuit increases, by timing both the forward pass and the gradient +# computation as the number of layers is allowed to increase. + +dev = qml.device("default.qubit", wires=4) + +def circuit(params): + qml.StronglyEntanglingLayers(params, wires=[0, 1, 2, 3]) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3)) + +############################################################################## +# We'll continue to use the same ansatz as before, but to reduce the time taken +# to collect the data, we'll reduce the number and repetitions of timings per data +# point. Below, we loop over a variational circuit depth ranging from 0 (no gates/ +# trainable parameters) to 20. Each layer will contain :math:`3N` parameters, where +# :math:`N` is the number of wires (in this case, we have :math:`N=4`). + +reps = 2 +num = 3 + +forward_shift = [] +gradient_shift = [] +forward_backprop = [] +gradient_backprop = [] + +qnode_shift = jax.jit(qml.QNode(circuit, dev, diff_method="parameter-shift")) +qnode_backprop = jax.jit(qml.QNode(circuit, dev, diff_method="backprop")) + +grad_qnode_shift = jax.jit(jax.grad(qml.QNode(circuit, dev, diff_method="parameter-shift"))) +grad_qnode_backprop = jax.jit(jax.grad(qml.QNode(circuit, dev, diff_method="backprop"))) + +for depth in range(0, 21): + param_shape = qml.StronglyEntanglingLayers.shape(n_wires=4, n_layers=depth) + params = jax.random.normal(key, param_shape) * 0.1 + + num_params = params.size + + # forward pass timing + # =================== + + + # parameter-shift + t = timeit.repeat("qnode_shift(params)", globals=globals(), number=num, repeat=reps) + forward_shift.append([num_params, min(t) / num]) + + # backprop + t = timeit.repeat("qnode_backprop(params)", globals=globals(), number=num, repeat=reps) + forward_backprop.append([num_params, min(t) / num]) + + if num_params == 0: + continue + + # Gradient timing + # =============== + + # parameter-shift + t = timeit.repeat("grad_qnode_shift(params)", globals=globals(), number=num, repeat=reps) + gradient_shift.append([num_params, min(t) / num]) + + # backprop + t = timeit.repeat("grad_qnode_backprop(params)", globals=globals(), number=num, repeat=reps) + gradient_backprop.append([num_params, min(t) / num]) + +gradient_shift = jnp.array(gradient_shift).T +gradient_backprop = jnp.array(gradient_backprop).T +forward_shift = jnp.array(forward_shift).T +forward_backprop = jnp.array(forward_backprop).T + +############################################################################## +# We now import matplotlib, and plot the results. + +plt.style.use("bmh") + +fig, ax = plt.subplots(1, 1, figsize=(6, 4)) + +ax.plot(*gradient_shift, '.-', label="Parameter-shift") +ax.plot(*gradient_backprop, '.-', label="Backprop") +ax.set_ylabel("Time (s)") +ax.set_xlabel("Number of parameters") +ax.legend() + +plt.show() + +############################################################################## +# .. raw:: html +# +#
+# +# We can see that the computational time for the parameter-shift rule increases with +# increasing number of parameters, as expected, whereas the computational time +# for backpropagation appears much more constant, with perhaps a minute linear increase +# with :math:`p.` Note that the plots are not perfectly linear, with some 'bumpiness' or +# noisiness. This is likely due to low-level operating system jitter, and +# other environmental fluctuations—increasing the number of repeats can help smooth +# out the plot. +# +# For a better comparison, we can scale the time required for computing the quantum +# gradients against the time taken for the corresponding forward pass: + +gradient_shift = gradient_shift.at[1].divide(forward_shift[1, 1:]) +gradient_backprop = gradient_backprop.at[1].divide(forward_backprop[1, 1:]) + +fig, ax = plt.subplots(1, 1, figsize=(6, 4)) + +ax.plot(*gradient_shift, '.-', label="Parameter-shift") +ax.plot(*gradient_backprop, '.-', label="Backprop") + +# perform a least squares regression to determine the linear best fit/gradient +# for the normalized time vs. number of parameters +x = gradient_shift[0] +m_shift, c_shift = jnp.polyfit(*gradient_shift, deg=1) +m_back, c_back = jnp.polyfit(*gradient_backprop, deg=1) + +ax.plot(x, m_shift * x + c_shift, '--', label=f"{m_shift:.2f}p{c_shift:+.2f}") +ax.plot(x, m_back * x + c_back, '--', label=f"{m_back:.2f}p{c_back:+.2f}") + +ax.set_ylabel("Normalized time") +ax.set_xlabel("Number of parameters") +ax.set_xscale("log") +ax.set_yscale("log") +ax.legend() + +plt.show() + +############################################################################## +# .. raw:: html +# +#
+# +# We can now see clearly that there is constant overhead for backpropagation with +# ``default.qubit``, but the parameter-shift rule scales as :math:`\sim 2p.` +# +# +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_barren_gadgets/barren_gadgets/barren_gadgets.py b/demonstrations_v2/tutorial_barren_gadgets/barren_gadgets/barren_gadgets.py index 09d3c89df7..0e2929dc41 100644 --- a/demonstrations_v2/tutorial_barren_gadgets/barren_gadgets/barren_gadgets.py +++ b/demonstrations_v2/tutorial_barren_gadgets/barren_gadgets/barren_gadgets.py @@ -1,130 +1,130 @@ -import pennylane as qml -from pennylane import numpy as np - -def non_identity_obs(obs): - return [o for o in obs if not isinstance(o, qml.Identity)] - -class PerturbativeGadgets: - """ Class to generate the gadget Hamiltonian corresponding to a given - computational hamiltonian according to the gadget construction derived - by Faehrmann & Cichy - - Args: - perturbation_factor (float) : parameter controlling the magnitude of the - perturbation (aa pre-factor to \lambda_max) - """ - def __init__(self, perturbation_factor=1): - self.perturbation_factor = perturbation_factor - - def gadgetize(self, Hamiltonian, target_locality=3): - """Generation of the perturbative gadget equivalent of the given - Hamiltonian according to the proceedure in Cichy, Fährmann et al. - Args: - Hamiltonian (qml.Hamiltonian) : target Hamiltonian to decompose - into more local terms - target_locality (int > 2) : desired locality of the resulting - gadget Hamiltonian - Returns: - Hgad (qml.Hamiltonian) : gadget Hamiltonian - """ - # checking for unaccounted for situations - self.run_checks(Hamiltonian, target_locality) - computational_qubits, computational_locality, computational_terms = self.get_params(Hamiltonian) - Hamiltonian_coeffs, Hamiltonian_ops = Hamiltonian.terms() - - # total qubit count, updated progressively when adding ancillaries - total_qubits = computational_qubits - #TODO: check proper convergence guarantee - gap = 1 - perturbation_norm = np.sum(np.abs(Hamiltonian_coeffs)) \ - + computational_terms * (computational_locality - 1) - lambda_max = gap / (4 * perturbation_norm) - l = self.perturbation_factor * lambda_max - sign_correction = (-1)**(computational_locality % 2 + 1) - # creating the gadget Hamiltonian - coeffs_anc = [] - coeffs_pert = [] - obs_anc = [] - obs_pert = [] - ancillary_register_size = int(computational_locality / (target_locality - 2)) - for str_count, string in enumerate(Hamiltonian_ops): - previous_total = total_qubits - total_qubits += ancillary_register_size - # Generating the ancillary part - for anc_q in range(previous_total, total_qubits): - coeffs_anc += [0.5, -0.5] - obs_anc += [qml.Identity(anc_q), qml.PauliZ(anc_q)] - # Generating the perturbative part - for anc_q in range(ancillary_register_size): - term = qml.PauliX(previous_total+anc_q) @ qml.PauliX(previous_total+(anc_q+1)%ancillary_register_size) - term = qml.prod(term, *non_identity_obs(string.operands)[ - (target_locality-2)*anc_q:(target_locality-2)*(anc_q+1)]) - obs_pert.append(term) - coeffs_pert += [l * sign_correction * Hamiltonian_coeffs[str_count]] \ - + [l] * (ancillary_register_size - 1) - coeffs = coeffs_anc + coeffs_pert - obs = obs_anc + obs_pert - Hgad = qml.Hamiltonian(coeffs, obs) - return Hgad - - def get_params(self, Hamiltonian): - """ retrieving the parameters n, k and r from the given Hamiltonian - Args: - Hamiltonian (qml.Hamiltonian) : Hamiltonian from which to get the - relevant parameters - Returns: - computational_qubits (int) : total number of qubits acted upon by - the Hamiltonian - computational_locality (int) : maximum number of qubits acted upon - by a single term of the Hamiltonian - computational_terms (int) : number of terms in the sum - composing the Hamiltonian - """ - _, Hamiltonian_ops = Hamiltonian.terms() - # checking how many qubits the Hamiltonian acts on - computational_qubits = len(Hamiltonian.wires) - # getting the number of terms in the Hamiltonian - computational_terms = len(Hamiltonian_ops) - # getting the locality, assuming all terms have the same - computational_locality = max([len(non_identity_obs(Hamiltonian_ops[s])) - for s in range(computational_terms)]) - return computational_qubits, computational_locality, computational_terms - - def run_checks(self, Hamiltonian, target_locality): - """ method to check a few conditions for the correct application of - the methods - Args: - Hamiltonian (qml.Hamiltonian) : Hamiltonian of interest - target_locality (int > 2) : desired locality of the resulting - gadget Hamiltonian - Returns: - None - """ - _, Hamiltonian_ops = Hamiltonian.terms() - computational_qubits, computational_locality, _ = self.get_params(Hamiltonian) - computational_qubits = len(Hamiltonian.wires) - if computational_qubits != Hamiltonian.wires[-1] + 1: - raise Exception('The studied computational Hamiltonian is not acting on ' + - 'the first {} qubits. '.format(computational_qubits) + - 'Decomposition not implemented for this case') - # Check for same string lengths - localities=[] - for string in Hamiltonian_ops: - localities.append(len(non_identity_obs(string))) - if len(np.unique(localities)) > 1: - raise Exception('The given Hamiltonian has terms with different locality.' + - ' Gadgetization not implemented for this case') - # validity of the target locality given the computational locality - if target_locality < 3: - raise Exception('The target locality can not be smaller than 3') - ancillary_register_size = computational_locality / (target_locality - 2) - if int(ancillary_register_size) != ancillary_register_size: - raise Exception('The locality of the Hamiltonian and the target' + - ' locality are not compatible. The gadgetization' + - ' with "unfull" ancillary registers is not' + - ' supported yet. Please choose such that the' + - ' computational locality is divisible by the' + - ' target locality - 2') - - - +import pennylane as qml +from pennylane import numpy as np + +def non_identity_obs(obs): + return [o for o in obs if not isinstance(o, qml.Identity)] + +class PerturbativeGadgets: + """ Class to generate the gadget Hamiltonian corresponding to a given + computational hamiltonian according to the gadget construction derived + by Faehrmann & Cichy + + Args: + perturbation_factor (float) : parameter controlling the magnitude of the + perturbation (aa pre-factor to \lambda_max) + """ + def __init__(self, perturbation_factor=1): + self.perturbation_factor = perturbation_factor + + def gadgetize(self, Hamiltonian, target_locality=3): + """Generation of the perturbative gadget equivalent of the given + Hamiltonian according to the proceedure in Cichy, Fährmann et al. + Args: + Hamiltonian (qml.Hamiltonian) : target Hamiltonian to decompose + into more local terms + target_locality (int > 2) : desired locality of the resulting + gadget Hamiltonian + Returns: + Hgad (qml.Hamiltonian) : gadget Hamiltonian + """ + # checking for unaccounted for situations + self.run_checks(Hamiltonian, target_locality) + computational_qubits, computational_locality, computational_terms = self.get_params(Hamiltonian) + Hamiltonian_coeffs, Hamiltonian_ops = Hamiltonian.terms() + + # total qubit count, updated progressively when adding ancillaries + total_qubits = computational_qubits + #TODO: check proper convergence guarantee + gap = 1 + perturbation_norm = np.sum(np.abs(Hamiltonian_coeffs)) \ + + computational_terms * (computational_locality - 1) + lambda_max = gap / (4 * perturbation_norm) + l = self.perturbation_factor * lambda_max + sign_correction = (-1)**(computational_locality % 2 + 1) + # creating the gadget Hamiltonian + coeffs_anc = [] + coeffs_pert = [] + obs_anc = [] + obs_pert = [] + ancillary_register_size = int(computational_locality / (target_locality - 2)) + for str_count, string in enumerate(Hamiltonian_ops): + previous_total = total_qubits + total_qubits += ancillary_register_size + # Generating the ancillary part + for anc_q in range(previous_total, total_qubits): + coeffs_anc += [0.5, -0.5] + obs_anc += [qml.Identity(anc_q), qml.PauliZ(anc_q)] + # Generating the perturbative part + for anc_q in range(ancillary_register_size): + term = qml.PauliX(previous_total+anc_q) @ qml.PauliX(previous_total+(anc_q+1)%ancillary_register_size) + term = qml.prod(term, *non_identity_obs(string.operands)[ + (target_locality-2)*anc_q:(target_locality-2)*(anc_q+1)]) + obs_pert.append(term) + coeffs_pert += [l * sign_correction * Hamiltonian_coeffs[str_count]] \ + + [l] * (ancillary_register_size - 1) + coeffs = coeffs_anc + coeffs_pert + obs = obs_anc + obs_pert + Hgad = qml.Hamiltonian(coeffs, obs) + return Hgad + + def get_params(self, Hamiltonian): + """ retrieving the parameters n, k and r from the given Hamiltonian + Args: + Hamiltonian (qml.Hamiltonian) : Hamiltonian from which to get the + relevant parameters + Returns: + computational_qubits (int) : total number of qubits acted upon by + the Hamiltonian + computational_locality (int) : maximum number of qubits acted upon + by a single term of the Hamiltonian + computational_terms (int) : number of terms in the sum + composing the Hamiltonian + """ + _, Hamiltonian_ops = Hamiltonian.terms() + # checking how many qubits the Hamiltonian acts on + computational_qubits = len(Hamiltonian.wires) + # getting the number of terms in the Hamiltonian + computational_terms = len(Hamiltonian_ops) + # getting the locality, assuming all terms have the same + computational_locality = max([len(non_identity_obs(Hamiltonian_ops[s])) + for s in range(computational_terms)]) + return computational_qubits, computational_locality, computational_terms + + def run_checks(self, Hamiltonian, target_locality): + """ method to check a few conditions for the correct application of + the methods + Args: + Hamiltonian (qml.Hamiltonian) : Hamiltonian of interest + target_locality (int > 2) : desired locality of the resulting + gadget Hamiltonian + Returns: + None + """ + _, Hamiltonian_ops = Hamiltonian.terms() + computational_qubits, computational_locality, _ = self.get_params(Hamiltonian) + computational_qubits = len(Hamiltonian.wires) + if computational_qubits != Hamiltonian.wires[-1] + 1: + raise Exception('The studied computational Hamiltonian is not acting on ' + + 'the first {} qubits. '.format(computational_qubits) + + 'Decomposition not implemented for this case') + # Check for same string lengths + localities=[] + for string in Hamiltonian_ops: + localities.append(len(non_identity_obs(string))) + if len(np.unique(localities)) > 1: + raise Exception('The given Hamiltonian has terms with different locality.' + + ' Gadgetization not implemented for this case') + # validity of the target locality given the computational locality + if target_locality < 3: + raise Exception('The target locality can not be smaller than 3') + ancillary_register_size = computational_locality / (target_locality - 2) + if int(ancillary_register_size) != ancillary_register_size: + raise Exception('The locality of the Hamiltonian and the target' + + ' locality are not compatible. The gadgetization' + + ' with "unfull" ancillary registers is not' + + ' supported yet. Please choose such that the' + + ' computational locality is divisible by the' + + ' target locality - 2') + + + diff --git a/demonstrations_v2/tutorial_barren_gadgets/barren_gadgets/layered_ansatz.py b/demonstrations_v2/tutorial_barren_gadgets/barren_gadgets/layered_ansatz.py index 22f324835e..7a26d9e059 100644 --- a/demonstrations_v2/tutorial_barren_gadgets/barren_gadgets/layered_ansatz.py +++ b/demonstrations_v2/tutorial_barren_gadgets/barren_gadgets/layered_ansatz.py @@ -1,54 +1,54 @@ -import pennylane as qml -from pennylane import numpy as np - -""" Based on the SimplifiedTwoDesign template from pennylane -https://docs.pennylane.ai/en/latest/code/api/pennylane.SimplifiedTwoDesign.html -as proposed in `Cerezo et al. (2021) `_. -but changin the Y-rotations for a random choice of {X, Y, Z}-rotations. -""" - -def build_ansatz(initial_layer_weights, weights, wires, gate_sequence=None): - - n_layers = qml.math.shape(weights)[0] - op_list = [] - - # initial rotations - for i in range(len(wires)): - op_list.append(qml.RY(initial_layer_weights[i], wires=wires[i])) - - # generating the rotation sequence - if gate_sequence is None: - gate_sequence = generate_random_gate_sequence(qml.math.shape(weights)) - - # repeated layers - for layer in range(n_layers): - - # even layer of entanglers - even_wires = [wires[i : i + 2] for i in range(0, len(wires) - 1, 2)] - for i, wire_pair in enumerate(even_wires): - op_list.append(qml.CZ(wires=wire_pair)) - op_list.append(gate_sequence[layer, i, 0].item()(weights[layer, i, 0], wires=wire_pair[0])) - op_list.append(gate_sequence[layer, i, 1].item()(weights[layer, i, 1], wires=wire_pair[1])) - # op_list.append(qml.RX(weights[layer, i, 0], wires=wire_pair[0])) - # op_list.append(qml.RX(weights[layer, i, 1], wires=wire_pair[1])) - - # odd layer of entanglers - odd_wires = [wires[i : i + 2] for i in range(1, len(wires) - 1, 2)] - for i, wire_pair in enumerate(odd_wires): - op_list.append(qml.CZ(wires=wire_pair)) - op_list.append(gate_sequence[layer, len(wires) // 2 + i, 0].item()(weights[layer, len(wires) // 2 + i, 0], wires=wire_pair[0])) - op_list.append(gate_sequence[layer, len(wires) // 2 + i, 1].item()(weights[layer, len(wires) // 2 + i, 1], wires=wire_pair[1])) - # op_list.append(qml.RX(weights[layer, len(wires) // 2 + i, 0], wires=wire_pair[0])) - # op_list.append(qml.RX(weights[layer, len(wires) // 2 + i, 1], wires=wire_pair[1])) - - return op_list - -def generate_random_gate_sequence(shape): - gate_set = [qml.RX, qml.RY, qml.RZ] - return np.random.choice(gate_set, size=shape) - -def get_parameter_shape(n_layers, n_wires): - if n_wires == 1: - return [(n_wires,), (n_layers,)] - return [(n_wires,), (n_layers, n_wires - 1, 2)] - +import pennylane as qml +from pennylane import numpy as np + +""" Based on the SimplifiedTwoDesign template from pennylane +https://docs.pennylane.ai/en/latest/code/api/pennylane.SimplifiedTwoDesign.html +as proposed in `Cerezo et al. (2021) `_. +but changin the Y-rotations for a random choice of {X, Y, Z}-rotations. +""" + +def build_ansatz(initial_layer_weights, weights, wires, gate_sequence=None): + + n_layers = qml.math.shape(weights)[0] + op_list = [] + + # initial rotations + for i in range(len(wires)): + op_list.append(qml.RY(initial_layer_weights[i], wires=wires[i])) + + # generating the rotation sequence + if gate_sequence is None: + gate_sequence = generate_random_gate_sequence(qml.math.shape(weights)) + + # repeated layers + for layer in range(n_layers): + + # even layer of entanglers + even_wires = [wires[i : i + 2] for i in range(0, len(wires) - 1, 2)] + for i, wire_pair in enumerate(even_wires): + op_list.append(qml.CZ(wires=wire_pair)) + op_list.append(gate_sequence[layer, i, 0].item()(weights[layer, i, 0], wires=wire_pair[0])) + op_list.append(gate_sequence[layer, i, 1].item()(weights[layer, i, 1], wires=wire_pair[1])) + # op_list.append(qml.RX(weights[layer, i, 0], wires=wire_pair[0])) + # op_list.append(qml.RX(weights[layer, i, 1], wires=wire_pair[1])) + + # odd layer of entanglers + odd_wires = [wires[i : i + 2] for i in range(1, len(wires) - 1, 2)] + for i, wire_pair in enumerate(odd_wires): + op_list.append(qml.CZ(wires=wire_pair)) + op_list.append(gate_sequence[layer, len(wires) // 2 + i, 0].item()(weights[layer, len(wires) // 2 + i, 0], wires=wire_pair[0])) + op_list.append(gate_sequence[layer, len(wires) // 2 + i, 1].item()(weights[layer, len(wires) // 2 + i, 1], wires=wire_pair[1])) + # op_list.append(qml.RX(weights[layer, len(wires) // 2 + i, 0], wires=wire_pair[0])) + # op_list.append(qml.RX(weights[layer, len(wires) // 2 + i, 1], wires=wire_pair[1])) + + return op_list + +def generate_random_gate_sequence(shape): + gate_set = [qml.RX, qml.RY, qml.RZ] + return np.random.choice(gate_set, size=shape) + +def get_parameter_shape(n_layers, n_wires): + if n_wires == 1: + return [(n_wires,), (n_layers,)] + return [(n_wires,), (n_layers, n_wires - 1, 2)] + diff --git a/demonstrations_v2/tutorial_barren_gadgets/demo.py b/demonstrations_v2/tutorial_barren_gadgets/demo.py index f0d19253c1..0eacb1fff7 100644 --- a/demonstrations_v2/tutorial_barren_gadgets/demo.py +++ b/demonstrations_v2/tutorial_barren_gadgets/demo.py @@ -1,390 +1,390 @@ -r""" -Perturbative Gadgets for Variational Quantum Algorithms -========================================== - -.. meta:: - :property="og:description": Use perturbative gadgets to avoid cost-function-dependent barren plateaus - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_barren_gadgets.png - - -.. related:: - tutorial_barren_plateaus Barren plateaus in quantum neural networks¶ - tutorial_local_cost_functions Alleviating barren plateaus with local cost functions - -*Author: Simon Cichy — Posted: 09 December 2022. Last updated: 09 December 2022.* - -Variational quantum algorithms are seen as one of the most primising candidates -for useful applications of quantum computers in the near term, but there are -still a few hurdles to overcome when it comes to practical implementation. -One of them, is the trainability. -In other words, one needs to ensure that the cost function is not flat. -In this tutorial, we will explore the application of perturbative gadgets in -variational quantum algorithms to outgo the issue of cost-function-dependent -barren plateaus, as proposed in Ref. [#cichy2022]_ - -Some context ------------- - -Barren plateaus refer to the phenomenon where the gradients of the cost function -decay exponentially with the size of the problem. Essentially, the cost -landscape becomes flat, with exception of some small regions, e.g., around -the minimum. -That is a problem because increasing the precision of the cost -function requires more measurements from the quantum device due to shot noise, -and an exponential number of measurements would render the algorithm impractical. -If you are not familiar yet with the concept of barren plateaus, I recommend you -first check out the demonstrations on :doc:`barren plateaus ` -and :doc:`avoiding barren plateaus with local cost functions `. - -As presented in the second aforementioned demo, barren plateaus are more severe when using global -cost functions compared to local ones. -A global cost function requires the simultaneous measurement of all -qubits at once. In contrast, a local one is constructed from terms that only -act on a small subset of qubits. - -We want to explore this topic further and learn about one possible mitigation -strategy. -Thinking about Variational Quantum Eigensolver (VQE) applications, let us consider cost functions that are -expectation values of Hamiltonians such as - -.. math:: C(\theta) = \operatorname{Tr} \left[ H V(\theta) |00\ldots 0\rangle \! \langle 00\ldots 0| V(\theta)^\dagger\right]. - -Here :math:`|00\ldots 0\rangle` is our initial state, -:math:`V(\theta)` is the circuit ansatz and :math:`H` the Hamiltonian -whose expectation value we need to minimize. -In some cases, it is easy to find a local cost function which can substitute a global one with the same ground state. -Take, for instance, the following Hamiltonians that induce global and local cost functions, respectively. - - -.. math:: H_G = \mathbb{I} - |00\ldots 0\rangle \! \langle 00\ldots 0| \quad \textrm{ and } \quad H_L = \mathbb{I} - \frac{1}{n} \sum_j |0\rangle \! \langle 0|_j. - -Those are two different Hamiltonians (not just different formulations of the -same one), but they share the same ground state: - - -.. math:: |\psi_{\textrm{min}} \rangle = |00\ldots 0\rangle. - -Therefore, one can work with either Hamiltonian to perform the VQE routine. -However, it is not always so simple. -What if we want to find the minimum eigenenergy of -:math:`H = X \otimes X \otimes Y \otimes Z + Z \otimes Y \otimes X \otimes X` ? -It is not always trivial to construct a local cost -function that has the same minimum as the cost function of interest. -This is where perturbative gadgets come into play! - - -The definitions ---------------- -Perturbative gadgets are a common tool in adiabatic quantum computing. -Their goal is to find a Hamiltonian with local interactions that mimics -another Hamiltonian with more complex couplings. - -Ideally, they would want to implement the target Hamiltonian with complex couplings, but since it's hard to implement more than few-body interactions on hardware, they cannot do so. Perturbative gadgets work by increasing the dimension of the Hilbert space (i.e., the number -of qubits) and "encoding" the target Hamiltonian in the low-energy -subspace of a so-called "gadget" Hamiltonian. - -Let us now construct such a gadget Hamiltonian tailored for VQE applications. -First, we start from a target Hamiltonian that is a linear combination of -Pauli words acting on :math:`k` qubits each: - -.. math:: H^\text{target} = \sum_i c_i h_i, - -where :math:`h_i = \sigma_{i,1} \otimes \sigma_{i,2} \otimes \ldots \otimes \sigma_{i,k},` -:math:`\sigma_{i,j} \in \{ X, Y, Z \},` and :math:`c_i \in \mathbb{R}.` -Now we construct the gadget Hamiltonian. -For each term :math:`h_i,` we will need :math:`k` additional qubits, which we -call auxiliary qubits, and to add two terms to the Hamiltonian: -an "unperturbed" part :math:`H^\text{aux}_i` and a perturbation :math:`V_i` -of strength :math:`\lambda.` -The unperturbed part penalizes each of the newly added qubits for not being in -the :math:`|0\rangle` state - -.. math:: H^\text{aux}_i = \sum_{j=1}^k |1\rangle \! \langle 1|_{i,j} = \sum_{j=1}^k \frac{1}{2}(\mathbb{I} - Z_{i,j}). - -On the other hand, the perturbation part implements one of the operators in the Pauli word -:math:`\sigma_{i,j}` on the corresponding qubit of the target register and a -pair of Pauli :math:`X` gates on two of the auxiliary qubits: - -.. math:: V_i = \sum_{j=1}^k c_{i,j} \sigma_{i,j} \otimes X_{i,j} \otimes X_{i,(j+1) \mathrm{mod }k}. - -In the end, - -.. math:: H^\text{gad} = \sum_{i} \left( H^\text{aux}_i + \lambda V_i \right). - - - -To grasp this idea better, this is what would result from working with a Hamiltonian -acting on a total of :math:`8` qubits and having :math:`3` terms, each of them being a -:math:`4`-body interaction. - -.. figure:: ../_static/demonstration_assets/barren_gadgets/gadget-terms-tutorial.png - :align: center - :width: 90% - -For each of the terms :math:`h_1`, :math:`h_2,` and :math:`h_3` we add :math:`4` auxiliary qubits. -In the end, our gadget Hamiltonian acts on :math:`8+3\cdot 4 = 20` qubits. - -The penalization (red) acts only on the auxiliary registers, penalizing each -qubit individually, while the perturbations couple the target with the auxiliary qubits. - -As shown in Ref. [#cichy2022]_, this construction results in a spectrum that, for low energies, is similar -to that of the original Hamiltonian. -This means that by minimizing the gadget Hamiltonian and reaching its global -minimum, the resulting state will be close to the global minimum of -:math:`H^\text{target}.` - -Since it is a local cost function, it is better behaved with respect to -barren plateaus than the global cost function, making it more trainable. -As a result, one can mitigate the onset of cost-function-dependent barren -plateaus by substituting the global cost function with the resulting gadget -and using that for training instead. That is what we will do in the rest of this tutorial. -""" - -############################################################################## -# First, a few imports. PennyLane and NumPy of course, and a few -# functions specific to our tutorial. -# The ``PerturbativeGadget`` class allows the user to generate the gadget Hamiltonian -# from a user-given target Hamiltonian in an automated way. -# For those who want to check its inner workings, -# you can find the code here: -# :download:`barren_gadgets.py `. -# The functions ``get_parameter_shape``, ``generate_random_gate_sequence``, and -# ``build_ansatz`` (for the details: -# :download:`layered_ansatz.py <../_static/demonstration_assets/barren_gadgets/layered_ansatz.py>` -# ) are there to build the parameterized quantum circuit we use in this demo. -# The first computes the shape of the array of trainable parameters that the -# circuit will need. The second generates a random sequence of Pauli rotations -# from :math:`\{R_X, R_Y, R_Z\}` with the right dimension. -# Finally, ``build_ansatz`` puts the pieces together. - -import pennylane as qml -from pennylane import numpy as np -from barren_gadgets.barren_gadgets import PerturbativeGadgets -from barren_gadgets.layered_ansatz import ( - generate_random_gate_sequence, - get_parameter_shape, - build_ansatz, -) - -np.random.seed(3) - -############################################################################## -# Now, let's take the example given above: -# -# .. math:: H = X \otimes X \otimes Y \otimes Z + Z \otimes Y \otimes X \otimes X. -# -# First, we construct our target Hamiltonian in PennyLane. -# For this, we use the -# :class:`~pennylane.Hamiltonian` class. - - -H_target = qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliY(2) @ qml.PauliZ(3) \ - + qml.PauliZ(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliX(3) - -############################################################################## -# Now we can check that we constructed what we wanted. - -print(H_target) - -############################################################################## -# We indeed have a Hamiltonian composed of two terms with the expected Pauli -# words. -# Next, we can construct the corresponding gadget Hamiltonian. -# Using the class ``PerturbativeGadgets``, we can automatically -# generate the gadget Hamiltonian from the target Hamiltonian. -# The object ``gadgetizer`` will contain all the information about the settings of -# the gadgetization procedure (there are quite a few knobs one can tweak, -# but we'll skip that for now). -# Then, the method ``gadgetize`` takes a -# :class:`~pennylane.Hamiltonian` -# object and generates the -# corresponding gadget Hamiltonian. - -gadgetizer = PerturbativeGadgets() -H_gadget = gadgetizer.gadgetize(H_target) -H_gadget - -############################################################################## -# So, let's see what we got. -# We started with 4 target qubits (labelled ``0`` to ``3``) and two 4-body terms. -# Thus we get 4 additional qubits twice (``4`` to ``11``). -# The first 16 elements of our Hamiltonian correspond to the unperturbed part. -# The last 8 are the perturbation. They are a little scrambled, but one can -# recognize the 8 Paulis from the target Hamiltonian on the qubits ``0`` to -# ``3`` and the cyclic pairwise :math:`X` structure on the auxiliaries. -# Indeed, they are :math:`(X_4X_5, X_5X_6, X_6X_7, X_7X_4)` and -# :math:`(X_8X_9, X_9X_{10}, X_{10}X_{11}, X_{11}X_8).` - -############################################################################## -# Training with the gadget Hamiltonian -# ----------------------------------- -# Now that we have a little intuition on how the gadget Hamiltonian construction -# works, we will use it to train. -# Classical simulations of qubit systems are expensive, so we will simplify further -# to a target Hamiltonian with a single term, and show that using the -# gadget Hamiltonian for training allows us to minimize the target Hamiltonian. -# So, let us construct the two Hamiltonians of interest. - - -H_target = 1 * qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliY(2) @ qml.PauliZ(3) -gadgetizer = PerturbativeGadgets(perturbation_factor=10) -H_gadget = gadgetizer.gadgetize(H_target) - -############################################################################## -# Then we need to set up our variational quantum algorithm. -# That is, we choose a circuit ansatz with randomly initialized weights, -# the cost function, the optimizer with its step size, the number of -# optimization steps, and the device to run the circuit on. -# For an ansatz, we will use a variation of the -# `qml.SimplifiedTwoDesign `_, -# which was proposed in previous -# works on cost-function-dependent barren plateaus [#cerezo2021]_. -# I will skip the details of the construction, since it is not our focus here, -# and just show what it looks like. -# Here is the circuit for a small example - -shapes = get_parameter_shape(n_layers=3, n_wires=5) -init_weights = [np.pi / 4] * shapes[0][0] -weights = np.random.uniform(0, np.pi, size=shapes[1]) - - -@qml.qnode(qml.device("default.qubit", wires=range(5))) -def display_circuit(weights): - build_ansatz(initial_layer_weights=init_weights, weights=weights, wires=range(5)) - return qml.expval(qml.PauliZ(wires=0)) - -import matplotlib.pyplot as plt -qml.draw_mpl(display_circuit)(weights) -plt.show() - -############################################################################## -# Now we build the circuit for our actual experiment. - - -# Total number of qubits: target + auxiliary -num_qubits = 4 + 1 * 4 - -# Other parameters of the ansatz: weights and gate sequence -shapes = get_parameter_shape(n_layers=num_qubits, n_wires=num_qubits) -init_weights = [np.pi / 4] * shapes[0][0] -weights = np.random.uniform(0, np.pi, size=shapes[1]) -random_gate_sequence = generate_random_gate_sequence(qml.math.shape(weights)) - -############################################################################## -# For the classical optimization, we will use the standard gradient descent -# algorithm and perform 500 iterations. For the quantum part, we will simulate -# our circuit using the -# `default.qubit `_ -# simulator. - -opt = qml.GradientDescentOptimizer(stepsize=0.1) -max_iter = 500 -dev = qml.device("default.qubit", wires=range(num_qubits)) - -############################################################################## -# Finally, we will use two cost functions and create a -# `QNode `_ for each. -# The first cost function, the training cost, is the loss function of the optimization. -# For the training, we use the gadget Hamiltonian. To ensure -# that our gadget optimization is proceeding as intended, -# we also define another cost function based on the target Hamiltonian. -# We will evaluate its value at each iteration for monitoring purposes, but it -# will not be used in the optimization. - - - -@qml.qnode(dev) -def training_cost(weights): - build_ansatz( - initial_layer_weights=init_weights, - weights=weights, - wires=range(num_qubits), - gate_sequence=random_gate_sequence, - ) - return qml.expval(H_gadget) - - -@qml.qnode(dev) -def monitoring_cost(weights): - build_ansatz( - initial_layer_weights=init_weights, - weights=weights, - wires=range(num_qubits), - gate_sequence=random_gate_sequence, - ) - return qml.expval(H_target) - - -############################################################################## -# The idea is that if we reach the global minimum for the gadget Hamiltonian, we -# should also be close to the global minimum of the target Hamiltonian, which is -# what we are ultimately interested in. -# To see the results and plot them, we will save the cost values -# at each iteration. - -costs_lists = {} -costs_lists["training"] = [training_cost(weights)] -costs_lists["monitoring"] = [monitoring_cost(weights)] - -############################################################################## -# Now everything is set up, let's run the optimization and see how it goes. -# Be careful, this will take a while. - -for it in range(max_iter): - weights = opt.step(training_cost, weights) - costs_lists["training"].append(training_cost(weights)) - costs_lists["monitoring"].append(monitoring_cost(weights)) - - -plt.style.use("seaborn") - -plt.figure() -plt.plot(costs_lists["training"]) -plt.plot(costs_lists["monitoring"]) -plt.legend(["training", "monitoring"]) -plt.xlabel("Number of iterations") -plt.ylabel("Cost values") -plt.show() - -############################################################################## -# -# Since our example target Hamiltonian is a single Pauli string, we know -# without needing any training that it has only :math:`\pm 1` eigenvalues. -# It is a very simple example, but we see that the training of our circuit using -# the gadget Hamiltonian as a cost function did indeed allow us to reach the -# global minimum of the target cost function. -# -# Now that you have an idea of how you can use perturbative gadgets in -# variational quantum algorithms, you can try applying them to more complex -# problems! However, be aware of the exponential scaling of classical -# simulations of quantum systems; adding linearly many auxiliary qubits -# quickly becomes hard to simulate. -# For those interested in the theory behind it or more formal statements of -# "how close" the results using the gadget are from the targeted ones, -# check out the original paper [#cichy2022]_. -# There you will also find further discussions on the advantages and limits of -# this proposal, -# as well as a more general recipe to design other gadget -# constructions with similar properties. -# Also, the complete code with explanations on how to reproduce the -# figures from the paper can be found in -# `this repository `_. -# -# References -# ---------- -# -# .. [#cichy2022] -# -# Cichy, S., Faehrmann, P.K., Khatri, S., Eisert, J. -# "A perturbative gadget for delaying the onset of barren plateaus in variational quantum algorithms." `arXiv:2210.03099 -# `__, 2022. -# -# .. [#cerezo2021] -# -# Cerezo, M., Sone, A., Volkoff, T. et al. -# "Cost function dependent barren plateaus in shallow parametrized quantum circuits." `Nat Commun 12, 1791 -# `__, 2021. -# -# About the author -# ---------------- +r""" +Perturbative Gadgets for Variational Quantum Algorithms +========================================== + +.. meta:: + :property="og:description": Use perturbative gadgets to avoid cost-function-dependent barren plateaus + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_barren_gadgets.png + + +.. related:: + tutorial_barren_plateaus Barren plateaus in quantum neural networks¶ + tutorial_local_cost_functions Alleviating barren plateaus with local cost functions + +*Author: Simon Cichy — Posted: 09 December 2022. Last updated: 09 December 2022.* + +Variational quantum algorithms are seen as one of the most primising candidates +for useful applications of quantum computers in the near term, but there are +still a few hurdles to overcome when it comes to practical implementation. +One of them, is the trainability. +In other words, one needs to ensure that the cost function is not flat. +In this tutorial, we will explore the application of perturbative gadgets in +variational quantum algorithms to outgo the issue of cost-function-dependent +barren plateaus, as proposed in Ref. [#cichy2022]_ + +Some context +------------ + +Barren plateaus refer to the phenomenon where the gradients of the cost function +decay exponentially with the size of the problem. Essentially, the cost +landscape becomes flat, with exception of some small regions, e.g., around +the minimum. +That is a problem because increasing the precision of the cost +function requires more measurements from the quantum device due to shot noise, +and an exponential number of measurements would render the algorithm impractical. +If you are not familiar yet with the concept of barren plateaus, I recommend you +first check out the demonstrations on :doc:`barren plateaus ` +and :doc:`avoiding barren plateaus with local cost functions `. + +As presented in the second aforementioned demo, barren plateaus are more severe when using global +cost functions compared to local ones. +A global cost function requires the simultaneous measurement of all +qubits at once. In contrast, a local one is constructed from terms that only +act on a small subset of qubits. + +We want to explore this topic further and learn about one possible mitigation +strategy. +Thinking about Variational Quantum Eigensolver (VQE) applications, let us consider cost functions that are +expectation values of Hamiltonians such as + +.. math:: C(\theta) = \operatorname{Tr} \left[ H V(\theta) |00\ldots 0\rangle \! \langle 00\ldots 0| V(\theta)^\dagger\right]. + +Here :math:`|00\ldots 0\rangle` is our initial state, +:math:`V(\theta)` is the circuit ansatz and :math:`H` the Hamiltonian +whose expectation value we need to minimize. +In some cases, it is easy to find a local cost function which can substitute a global one with the same ground state. +Take, for instance, the following Hamiltonians that induce global and local cost functions, respectively. + + +.. math:: H_G = \mathbb{I} - |00\ldots 0\rangle \! \langle 00\ldots 0| \quad \textrm{ and } \quad H_L = \mathbb{I} - \frac{1}{n} \sum_j |0\rangle \! \langle 0|_j. + +Those are two different Hamiltonians (not just different formulations of the +same one), but they share the same ground state: + + +.. math:: |\psi_{\textrm{min}} \rangle = |00\ldots 0\rangle. + +Therefore, one can work with either Hamiltonian to perform the VQE routine. +However, it is not always so simple. +What if we want to find the minimum eigenenergy of +:math:`H = X \otimes X \otimes Y \otimes Z + Z \otimes Y \otimes X \otimes X` ? +It is not always trivial to construct a local cost +function that has the same minimum as the cost function of interest. +This is where perturbative gadgets come into play! + + +The definitions +--------------- +Perturbative gadgets are a common tool in adiabatic quantum computing. +Their goal is to find a Hamiltonian with local interactions that mimics +another Hamiltonian with more complex couplings. + +Ideally, they would want to implement the target Hamiltonian with complex couplings, but since it's hard to implement more than few-body interactions on hardware, they cannot do so. Perturbative gadgets work by increasing the dimension of the Hilbert space (i.e., the number +of qubits) and "encoding" the target Hamiltonian in the low-energy +subspace of a so-called "gadget" Hamiltonian. + +Let us now construct such a gadget Hamiltonian tailored for VQE applications. +First, we start from a target Hamiltonian that is a linear combination of +Pauli words acting on :math:`k` qubits each: + +.. math:: H^\text{target} = \sum_i c_i h_i, + +where :math:`h_i = \sigma_{i,1} \otimes \sigma_{i,2} \otimes \ldots \otimes \sigma_{i,k},` +:math:`\sigma_{i,j} \in \{ X, Y, Z \},` and :math:`c_i \in \mathbb{R}.` +Now we construct the gadget Hamiltonian. +For each term :math:`h_i,` we will need :math:`k` additional qubits, which we +call auxiliary qubits, and to add two terms to the Hamiltonian: +an "unperturbed" part :math:`H^\text{aux}_i` and a perturbation :math:`V_i` +of strength :math:`\lambda.` +The unperturbed part penalizes each of the newly added qubits for not being in +the :math:`|0\rangle` state + +.. math:: H^\text{aux}_i = \sum_{j=1}^k |1\rangle \! \langle 1|_{i,j} = \sum_{j=1}^k \frac{1}{2}(\mathbb{I} - Z_{i,j}). + +On the other hand, the perturbation part implements one of the operators in the Pauli word +:math:`\sigma_{i,j}` on the corresponding qubit of the target register and a +pair of Pauli :math:`X` gates on two of the auxiliary qubits: + +.. math:: V_i = \sum_{j=1}^k c_{i,j} \sigma_{i,j} \otimes X_{i,j} \otimes X_{i,(j+1) \mathrm{mod }k}. + +In the end, + +.. math:: H^\text{gad} = \sum_{i} \left( H^\text{aux}_i + \lambda V_i \right). + + + +To grasp this idea better, this is what would result from working with a Hamiltonian +acting on a total of :math:`8` qubits and having :math:`3` terms, each of them being a +:math:`4`-body interaction. + +.. figure:: ../_static/demonstration_assets/barren_gadgets/gadget-terms-tutorial.png + :align: center + :width: 90% + +For each of the terms :math:`h_1`, :math:`h_2,` and :math:`h_3` we add :math:`4` auxiliary qubits. +In the end, our gadget Hamiltonian acts on :math:`8+3\cdot 4 = 20` qubits. + +The penalization (red) acts only on the auxiliary registers, penalizing each +qubit individually, while the perturbations couple the target with the auxiliary qubits. + +As shown in Ref. [#cichy2022]_, this construction results in a spectrum that, for low energies, is similar +to that of the original Hamiltonian. +This means that by minimizing the gadget Hamiltonian and reaching its global +minimum, the resulting state will be close to the global minimum of +:math:`H^\text{target}.` + +Since it is a local cost function, it is better behaved with respect to +barren plateaus than the global cost function, making it more trainable. +As a result, one can mitigate the onset of cost-function-dependent barren +plateaus by substituting the global cost function with the resulting gadget +and using that for training instead. That is what we will do in the rest of this tutorial. +""" + +############################################################################## +# First, a few imports. PennyLane and NumPy of course, and a few +# functions specific to our tutorial. +# The ``PerturbativeGadget`` class allows the user to generate the gadget Hamiltonian +# from a user-given target Hamiltonian in an automated way. +# For those who want to check its inner workings, +# you can find the code here: +# :download:`barren_gadgets.py `. +# The functions ``get_parameter_shape``, ``generate_random_gate_sequence``, and +# ``build_ansatz`` (for the details: +# :download:`layered_ansatz.py <../_static/demonstration_assets/barren_gadgets/layered_ansatz.py>` +# ) are there to build the parameterized quantum circuit we use in this demo. +# The first computes the shape of the array of trainable parameters that the +# circuit will need. The second generates a random sequence of Pauli rotations +# from :math:`\{R_X, R_Y, R_Z\}` with the right dimension. +# Finally, ``build_ansatz`` puts the pieces together. + +import pennylane as qml +from pennylane import numpy as np +from barren_gadgets.barren_gadgets import PerturbativeGadgets +from barren_gadgets.layered_ansatz import ( + generate_random_gate_sequence, + get_parameter_shape, + build_ansatz, +) + +np.random.seed(3) + +############################################################################## +# Now, let's take the example given above: +# +# .. math:: H = X \otimes X \otimes Y \otimes Z + Z \otimes Y \otimes X \otimes X. +# +# First, we construct our target Hamiltonian in PennyLane. +# For this, we use the +# :class:`~pennylane.Hamiltonian` class. + + +H_target = qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliY(2) @ qml.PauliZ(3) \ + + qml.PauliZ(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliX(3) + +############################################################################## +# Now we can check that we constructed what we wanted. + +print(H_target) + +############################################################################## +# We indeed have a Hamiltonian composed of two terms with the expected Pauli +# words. +# Next, we can construct the corresponding gadget Hamiltonian. +# Using the class ``PerturbativeGadgets``, we can automatically +# generate the gadget Hamiltonian from the target Hamiltonian. +# The object ``gadgetizer`` will contain all the information about the settings of +# the gadgetization procedure (there are quite a few knobs one can tweak, +# but we'll skip that for now). +# Then, the method ``gadgetize`` takes a +# :class:`~pennylane.Hamiltonian` +# object and generates the +# corresponding gadget Hamiltonian. + +gadgetizer = PerturbativeGadgets() +H_gadget = gadgetizer.gadgetize(H_target) +H_gadget + +############################################################################## +# So, let's see what we got. +# We started with 4 target qubits (labelled ``0`` to ``3``) and two 4-body terms. +# Thus we get 4 additional qubits twice (``4`` to ``11``). +# The first 16 elements of our Hamiltonian correspond to the unperturbed part. +# The last 8 are the perturbation. They are a little scrambled, but one can +# recognize the 8 Paulis from the target Hamiltonian on the qubits ``0`` to +# ``3`` and the cyclic pairwise :math:`X` structure on the auxiliaries. +# Indeed, they are :math:`(X_4X_5, X_5X_6, X_6X_7, X_7X_4)` and +# :math:`(X_8X_9, X_9X_{10}, X_{10}X_{11}, X_{11}X_8).` + +############################################################################## +# Training with the gadget Hamiltonian +# ----------------------------------- +# Now that we have a little intuition on how the gadget Hamiltonian construction +# works, we will use it to train. +# Classical simulations of qubit systems are expensive, so we will simplify further +# to a target Hamiltonian with a single term, and show that using the +# gadget Hamiltonian for training allows us to minimize the target Hamiltonian. +# So, let us construct the two Hamiltonians of interest. + + +H_target = 1 * qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliY(2) @ qml.PauliZ(3) +gadgetizer = PerturbativeGadgets(perturbation_factor=10) +H_gadget = gadgetizer.gadgetize(H_target) + +############################################################################## +# Then we need to set up our variational quantum algorithm. +# That is, we choose a circuit ansatz with randomly initialized weights, +# the cost function, the optimizer with its step size, the number of +# optimization steps, and the device to run the circuit on. +# For an ansatz, we will use a variation of the +# `qml.SimplifiedTwoDesign `_, +# which was proposed in previous +# works on cost-function-dependent barren plateaus [#cerezo2021]_. +# I will skip the details of the construction, since it is not our focus here, +# and just show what it looks like. +# Here is the circuit for a small example + +shapes = get_parameter_shape(n_layers=3, n_wires=5) +init_weights = [np.pi / 4] * shapes[0][0] +weights = np.random.uniform(0, np.pi, size=shapes[1]) + + +@qml.qnode(qml.device("default.qubit", wires=range(5))) +def display_circuit(weights): + build_ansatz(initial_layer_weights=init_weights, weights=weights, wires=range(5)) + return qml.expval(qml.PauliZ(wires=0)) + +import matplotlib.pyplot as plt +qml.draw_mpl(display_circuit)(weights) +plt.show() + +############################################################################## +# Now we build the circuit for our actual experiment. + + +# Total number of qubits: target + auxiliary +num_qubits = 4 + 1 * 4 + +# Other parameters of the ansatz: weights and gate sequence +shapes = get_parameter_shape(n_layers=num_qubits, n_wires=num_qubits) +init_weights = [np.pi / 4] * shapes[0][0] +weights = np.random.uniform(0, np.pi, size=shapes[1]) +random_gate_sequence = generate_random_gate_sequence(qml.math.shape(weights)) + +############################################################################## +# For the classical optimization, we will use the standard gradient descent +# algorithm and perform 500 iterations. For the quantum part, we will simulate +# our circuit using the +# `default.qubit `_ +# simulator. + +opt = qml.GradientDescentOptimizer(stepsize=0.1) +max_iter = 500 +dev = qml.device("default.qubit", wires=range(num_qubits)) + +############################################################################## +# Finally, we will use two cost functions and create a +# `QNode `_ for each. +# The first cost function, the training cost, is the loss function of the optimization. +# For the training, we use the gadget Hamiltonian. To ensure +# that our gadget optimization is proceeding as intended, +# we also define another cost function based on the target Hamiltonian. +# We will evaluate its value at each iteration for monitoring purposes, but it +# will not be used in the optimization. + + + +@qml.qnode(dev) +def training_cost(weights): + build_ansatz( + initial_layer_weights=init_weights, + weights=weights, + wires=range(num_qubits), + gate_sequence=random_gate_sequence, + ) + return qml.expval(H_gadget) + + +@qml.qnode(dev) +def monitoring_cost(weights): + build_ansatz( + initial_layer_weights=init_weights, + weights=weights, + wires=range(num_qubits), + gate_sequence=random_gate_sequence, + ) + return qml.expval(H_target) + + +############################################################################## +# The idea is that if we reach the global minimum for the gadget Hamiltonian, we +# should also be close to the global minimum of the target Hamiltonian, which is +# what we are ultimately interested in. +# To see the results and plot them, we will save the cost values +# at each iteration. + +costs_lists = {} +costs_lists["training"] = [training_cost(weights)] +costs_lists["monitoring"] = [monitoring_cost(weights)] + +############################################################################## +# Now everything is set up, let's run the optimization and see how it goes. +# Be careful, this will take a while. + +for it in range(max_iter): + weights = opt.step(training_cost, weights) + costs_lists["training"].append(training_cost(weights)) + costs_lists["monitoring"].append(monitoring_cost(weights)) + + +plt.style.use("seaborn") + +plt.figure() +plt.plot(costs_lists["training"]) +plt.plot(costs_lists["monitoring"]) +plt.legend(["training", "monitoring"]) +plt.xlabel("Number of iterations") +plt.ylabel("Cost values") +plt.show() + +############################################################################## +# +# Since our example target Hamiltonian is a single Pauli string, we know +# without needing any training that it has only :math:`\pm 1` eigenvalues. +# It is a very simple example, but we see that the training of our circuit using +# the gadget Hamiltonian as a cost function did indeed allow us to reach the +# global minimum of the target cost function. +# +# Now that you have an idea of how you can use perturbative gadgets in +# variational quantum algorithms, you can try applying them to more complex +# problems! However, be aware of the exponential scaling of classical +# simulations of quantum systems; adding linearly many auxiliary qubits +# quickly becomes hard to simulate. +# For those interested in the theory behind it or more formal statements of +# "how close" the results using the gadget are from the targeted ones, +# check out the original paper [#cichy2022]_. +# There you will also find further discussions on the advantages and limits of +# this proposal, +# as well as a more general recipe to design other gadget +# constructions with similar properties. +# Also, the complete code with explanations on how to reproduce the +# figures from the paper can be found in +# `this repository `_. +# +# References +# ---------- +# +# .. [#cichy2022] +# +# Cichy, S., Faehrmann, P.K., Khatri, S., Eisert, J. +# "A perturbative gadget for delaying the onset of barren plateaus in variational quantum algorithms." `arXiv:2210.03099 +# `__, 2022. +# +# .. [#cerezo2021] +# +# Cerezo, M., Sone, A., Volkoff, T. et al. +# "Cost function dependent barren plateaus in shallow parametrized quantum circuits." `Nat Commun 12, 1791 +# `__, 2021. +# +# About the author +# ---------------- # \ No newline at end of file diff --git a/demonstrations_v2/tutorial_differentiable_HF/demo.py b/demonstrations_v2/tutorial_differentiable_HF/demo.py index d1820613d1..e06dc3fa4d 100644 --- a/demonstrations_v2/tutorial_differentiable_HF/demo.py +++ b/demonstrations_v2/tutorial_differentiable_HF/demo.py @@ -1,393 +1,393 @@ -r""" - -Differentiable Hartree-Fock -=========================== - -.. meta:: - :property="og:description": Learn how to use the differentiable Hartree-Fock solver - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/differentiable_HF.png - -.. related:: - tutorial_quantum_chemistry Building molecular Hamiltonians - tutorial_vqe A brief overview of VQE - tutorial_givens_rotations Givens rotations for quantum chemistry - tutorial_adaptive_circuits Adaptive circuits for quantum chemistry - - -*Author: Soran Jahangiri — Posted: 09 May 2022. Last updated: 09 May 2022.* - -In this tutorial, you will learn how to use PennyLane's differentiable Hartree-Fock solver -[#arrazola2021]_. The quantum chemistry module in PennyLane, :mod:`qml.qchem `, -provides built-in methods for constructing -atomic and molecular orbitals, building Fock matrices and solving the self-consistent field -equations to obtain optimized orbitals which can be used to construct fully-differentiable -molecular Hamiltonians. PennyLane allows users to natively compute derivatives of all these objects -with respect to the underlying parameters using the methods of -`automatic differentiation `_. We -introduce a workflow to jointly optimize circuit parameters, nuclear coordinates and basis set -parameters in a variational quantum eigensolver algorithm. You will also learn how to visualize the -atomic and molecular orbitals which can be used to create an animation like this: - -.. figure:: /_static/demonstration_assets/differentiable_HF/h2.gif - :width: 60% - :align: center - - The bonding molecular orbital of hydrogen visualized during a full geometry, circuit and - basis set optimization. - -Let's get started! - -Differentiable Hamiltonians ---------------------------- - -Variational quantum algorithms aim to calculate the energy of a molecule by constructing a -parameterized quantum circuit and finding a set of parameters that minimize the expectation value of -the electronic `molecular Hamiltonian `_. The -optimization can be carried out by computing the gradients of the expectation value with respect to -these parameters and iteratively updating them until convergence is achieved. In principle, the -optimization process is not limited to the circuit parameters and can be extended to include the -parameters of the Hamiltonian that can be optimized concurrently with the circuit parameters. The -aim is now to obtain the set of parameters that minimize the following expectation value - -.. math:: \left \langle \Psi(\theta) | H(\beta) | \Psi(\theta) \right \rangle, - -where :math:`\theta` and :math:`\beta` represent the circuit and Hamiltonian parameters, -respectively. - -Computing the gradient of a molecular Hamiltonian is challenging because the dependency of the -Hamiltonian on the molecular parameters is typically not very straightforward. This makes symbolic -differentiation methods, which obtain derivatives of an input function by direct mathematical -manipulation, of limited scope. Furthermore, numerical differentiation methods based on -`finite differences `_ are not always -reliable due to their intrinsic instability, especially when the number of -differentiable parameters is large. These limitations can be alleviated by using automatic -differentiation methods which can be used to compute exact gradients of a function, implemented with -computer code, using resources comparable to those required to evaluate the function itself. - -Efficient optimization of the molecular Hamiltonian parameters in a variational quantum algorithm -is essential for tackling problems such as -`geometry optimization `_ and vibrational -frequency -calculations. These problems require computing the first- and second-order derivatives of the -molecular energy with respect to nuclear coordinates which can be efficiently obtained if the -variational workflow is automatically differentiable. Another important example is the simultaneous -optimization of the parameters of the basis set used to construct the atomic orbitals which can in -principle increase the accuracy of the computed energy without increasing the number of qubits in a -quantum simulation. The joint optimization of the circuit and Hamiltonian parameters can also be -used when the chemical problem involves optimizing the parameters of external potentials. - -The Hartree-Fock method ------------------------ - -The main goal of the Hartree-Fock method is to obtain molecular orbitals that minimize the -energy of a system where electrons are treated as independent particles that experience a mean field -generated by the other electrons. These optimized molecular orbitals are then used to -construct one- and two-body electron integrals in the basis of molecular orbitals, :math:`\phi,` - -.. math:: h_{pq} =\int dx \,\phi_p^*(x)\left(-\frac{\nabla^2}{2}-\sum_{i=1}^N\frac{Z_i}{|r-R_i|}\right)\phi_q(x),\\\\ -.. math:: h_{pqrs} = \int dx_1 dx_2\, \frac{\phi_p^*(x_1)\phi_q^*(x_2)\phi_r(x_2)\phi_s(x_1)}{|r_1-r_2|}. - -These integrals are used to generate a differentiable -`second-quantized `_ molecular Hamiltonian as - - -.. math:: H=\sum_{pq} h_{pq}a_p^\dagger a_q +\frac{1}{2}\sum_{pqrs}h_{pqrs}a_p^\dagger a_q^\dagger a_r a_s, - -where :math:`a^\dagger` and :math:`a` are the fermionic creation and annihilation operators, -respectively. This Hamiltonian is then transformed to the qubit basis. Let's see how this can be -done in PennyLane. - -To get started, we need to define the atomic symbols and the nuclear coordinates of the molecule. -For the hydrogen molecule we have -""" - -import pennylane as qml -import numpy as np -import jax -import jax.numpy as jnp -import matplotlib.pyplot as plt -np.set_printoptions(precision=5) -jax.config.update("jax_enable_x64", True) - -symbols = ["H", "H"] -# optimized geometry at the Hartree-Fock level -geometry = jnp.array([[-0.672943567415407, 0.0, 0.0], - [ 0.672943567415407, 0.0, 0.0]]) - -############################################################################## -# We can now compute the Hartree-Fock energy and its gradient with respect to the -# nuclear coordinates. To do that, we create a molecule object that stores all the molecular -# parameters needed to perform a Hartree-Fock calculation. - -mol = qml.qchem.Molecule(symbols, geometry) - -############################################################################## -# The Hartree-Fock energy can now be computed with the -# :func:`~.pennylane.qchem.hf_energy` function which is a function transform - -qml.qchem.hf_energy(mol)() - -############################################################################## -# We now compute the gradient of the energy with respect to the nuclear coordinates - -jax.grad(qml.qchem.hf_energy(mol), argnums=0)(geometry, mol.coeff, mol.alpha) - -############################################################################## -# The obtained gradients are equal or very close to zero because the geometry we used here has been -# previously optimized at the Hartree-Fock level. You can use a different geometry and verify that -# the newly computed gradients are not all zero. -# -# We can also compute the values and gradients of several other quantities that are obtained during -# the Hartree-Fock procedure. These include all integrals over basis functions, matrices formed from -# these integrals and the one- and two-body integrals over molecular orbitals [#arrazola2021]_. -# Let's look at a few examples. -# -# We first compute the overlap integral between the two S-type atomic orbitals of the hydrogen -# atoms. Here we are using the `STO-3G `_ -# basis set in which each of the atomic orbitals is represented by one basis function composed of -# three primitive Gaussian functions. These basis functions can be accessed from the molecule -# object as - -S1 = mol.basis_set[0] -S2 = mol.basis_set[1] - -############################################################################## -# We can check the parameters of the basis functions as - -for param in S1.params: - print(param) - -############################################################################## -# This returns the exponents, contraction coefficients and the centres of the three Gaussian -# functions of the STO-3G basis set. These parameters can be also obtained individually by using -# ``S1.alpha``, ``S1.coeff`` and ``S1.r``, respectively. You can verify that both of the ``S1`` an -# ``S2`` atomic orbitals have the same exponents and contraction coefficients but are centred on -# different hydrogen atoms. You can also verify that the orbitals are S-type by printing the angular -# momentum quantum numbers with - -S1.l - -############################################################################## -# This gives us a tuple of three integers, representing the exponents of the :math:`x,` :math:`y` -# and :math:`z` components in the Gaussian functions [#arrazola2021]_. -# -# We can now compute the overlap integral, -# -# .. math:: -# -# S_{\mu \nu} = \int \chi_\mu^* (r) \chi_\nu (r) dr -# -# between the atomic orbitals :math:`\chi,` by passing the orbitals and the initial values of their -# centres to the :func:`~.pennylane.qchem.overlap_integral` function. The centres of the orbitals -# are those of the hydrogen atoms by default and are therefore treated as differentiable parameters -# by PennyLane. - -qml.qchem.overlap_integral(S1, S2)() - -############################################################################## -# You can verify that the overlap integral between two identical atomic orbitals is equal to one. -# We can now compute the gradient of the overlap integral with respect to the orbital centres - -jax.grad(qml.qchem.overlap_integral(S1, S2))(geometry, mol.coeff, mol.alpha) - -############################################################################## -# Can you explain why some of the computed gradients are zero? -# -# Let's now plot the atomic orbitals and their overlap. We can do it by using -# the :py:meth:`~.pennylane.qchem.Molecule.atomic_orbital` function, which evaluates the -# atomic orbital at a given coordinate. For instance, the value of the S orbital on the first -# hydrogen atom can be computed at the origin as - -V1 = mol.atomic_orbital(0) -V1(0.0, 0.0, 0.0) - -############################################################################## -# We can evaluate this orbital at different points along the :math:`x` axis and plot it. - -x = np.linspace(-5, 5, 1000) -plt.plot(x, V1(x, 0.0, 0.0), color='teal') -plt.xlabel('X [Bohr]') -plt.show() - -############################################################################## -# We can also plot the second S orbital and visualize the overlap between them - -V2 = mol.atomic_orbital(1) -plt.plot(x, V1(x, 0.0, 0.0), color='teal') -plt.plot(x, V2(x, 0.0, 0.0), color='teal') -plt.fill_between( - x, np.minimum(V1(x, 0.0, 0.0), V2(x, 0.0, 0.0)), color = 'red', alpha = 0.5, hatch = '||') -plt.xlabel('X [Bohr]') -plt.show() - -############################################################################## -# By looking at the orbitals, can you guess at what distance the value of the overlap becomes -# negligible? Can you verify your guess by computing the overlap at that distance? -# -# Similarly, we can plot the molecular orbitals of the hydrogen molecule obtained from the -# Hartree-Fock calculations. We plot the cross-section of the bonding orbital on the :math:`x-y` -# plane. - -n = 30 # number of grid points along each axis -qml.qchem.hf_energy(mol)() -mol.mo_coefficients = mol.mo_coefficients.T -mo = mol.molecular_orbital(0) -x, y = np.meshgrid(np.linspace(-2, 2, n), - np.linspace(-2, 2, n)) -val = np.vectorize(mo)(x, y, 0) -val = np.array([val[i][j] for i in range(n) for j in range(n)]).reshape(n, n) - -fig, ax = plt.subplots() -co = ax.contour(x, y, val, 10, cmap='summer_r', zorder=0) -ax.clabel(co, inline=2, fontsize=10) -plt.scatter(mol.coordinates[:,0], mol.coordinates[:,1], s = 80, color='black') -ax.set_xlabel('X [Bohr]') -ax.set_ylabel('Y [Bohr]') -plt.show() - -############################################################################## -# VQE simulations -# --------------- -# -# By performing the Hartree-Fock calculations, we obtain a set of one- and two-body integrals -# over molecular orbitals that can be used to construct the molecular Hamiltonian with the -# :func:`~.pennylane.qchem.molecular_hamiltonian` function. - -hamiltonian, qubits = qml.qchem.molecular_hamiltonian(mol) -print(hamiltonian) - -############################################################################## -# The Hamiltonian contains 15 terms and, importantly, the coefficients of the Hamiltonian are all -# differentiable. We can construct a circuit and perform a VQE simulation in which both of the -# circuit parameters and the nuclear coordinates are optimized simultaneously by using the computed -# gradients. We will have two sets of differentiable parameters: the first set is the -# rotation angles of the excitation gates which are applied to the reference Hartree-Fock state -# to construct the exact ground state. The second set contains the nuclear coordinates of the -# hydrogen atoms. - -dev = qml.device("default.qubit", wires=4) -def energy(): - @qml.qnode(dev, interface="jax") - def circuit(*args): - qml.BasisState(np.array([1, 1, 0, 0]), wires=range(4)) - qml.DoubleExcitation(*args[0], wires=[0, 1, 2, 3]) - mol = qml.qchem.Molecule(symbols, geometry, alpha=args[3], coeff=args[2]) - H = qml.qchem.molecular_hamiltonian(mol, args=args[1:])[0] - return qml.expval(H) - return circuit - -############################################################################## -# Note that we only use the :class:`~.pennylane.DoubleExcitation` gate as the -# :class:`~.pennylane.SingleExcitation` ones can be neglected in this particular example -# [#szabo1996]_. We now compute the gradients of the energy with respect to the circuit parameter -# and the nuclear coordinates and update the parameters iteratively. Note that the nuclear -# coordinate gradients are simply the forces on the atomic nuclei. - -# initial value of the circuit parameter -circuit_param = jnp.array([0.0]) - -geometry = jnp.array([[0.0, 0.02, -0.672943567415407], - [0.1, 0.0, 0.672943567415407]]) - -for n in range(36): - mol = qml.qchem.Molecule(symbols, geometry) - args = [circuit_param, geometry, mol.coeff, mol.alpha] - # gradient for circuit parameters - g_param = jax.grad(energy(), argnums = 0)(*args) - circuit_param = circuit_param - 0.25 * g_param[0] - - # gradient for nuclear coordinates - forces = jax.grad(energy(), argnums = 1)(*args) - geometry = geometry - 0.5 * forces - - if n % 5 == 0: - print(f'n: {n}, E: {energy()(*args):.8f}, Force-max: {abs(forces).max():.8f}') - -############################################################################## -# After 35 steps of optimization, the forces on the atomic nuclei and the gradient of the -# circuit parameter are both approaching zero, and the energy of the molecule is that of the -# optimized geometry at the -# `full-CI `_ level: -# :math:`-1.1373060483` Ha. You can print the optimized geometry and verify that the final bond -# length of hydrogen is identical to the one computed with full-CI which is :math:`1.389` -# `Bohr `_. -# -# We are now ready to perform a full optimization where the circuit parameters, the nuclear -# coordinates and the basis set parameters are all differentiable parameters that can be optimized -# simultaneously. - -symbols = ["H", "H"] -# initial values of the nuclear coordinates -geometry = jnp.array([[0.0, 0.0, -0.672943567415407], - [0.0, 0.0, 0.672943567415407]]) - -# initial values of the basis set contraction coefficients -coeff = jnp.array([[0.1543289673, 0.5353281423, 0.4446345422], - [0.1543289673, 0.5353281423, 0.4446345422]]) - -# initial values of the basis set exponents -alpha = jnp.array([[3.42525091, 0.62391373, 0.1688554], - [3.42525091, 0.62391373, 0.1688554]]) - -# initial value of the circuit parameter -circuit_param = jnp.array([0.0]) - -mol = qml.qchem.Molecule(symbols, geometry, coeff=coeff, alpha=alpha) -args = [circuit_param, geometry, coeff, alpha] - -for n in range(36): - args = [circuit_param, geometry, coeff, alpha] - mol = qml.qchem.Molecule(symbols, geometry, alpha=alpha, coeff=coeff) - - # gradient for circuit parameters - g_param = jax.grad(energy(), argnums=[0, 1, 2, 3])(*args)[0] - circuit_param = circuit_param - 0.25 * g_param[0] - - # gradient for nuclear coordinates - value, gradients = jax.value_and_grad(energy(), argnums=[1, 2, 3])(*args) - geometry = geometry - 0.5 * gradients[0] - alpha = alpha - 0.25 * gradients[2] - coeff = coeff - 0.25 * gradients[1] - - if n % 5 == 0: - print(f'n: {n}, E: {value:.8f}, Force-max: {abs(gradients[0]).max():.8f}') - -############################################################################## -# You can also print the gradients of the circuit and basis set parameters and confirm that they are -# approaching zero. The computed energy of :math:`-1.14040160` Ha is -# lower than the full-CI energy, :math:`-1.1373060483` Ha (obtained with the STO-3G basis set for -# the hydrogen molecule) because we have optimized the basis set parameters in our example. This -# means that we can reach a lower energy for hydrogen without increasing the basis set size, which -# would otherwise lead to a larger number of qubits. -# -# Conclusions -# ----------- -# This tutorial introduces an important feature of PennyLane that allows performing -# fully-differentiable Hartree-Fock and subsequently VQE simulations. This feature provides two -# major benefits: i) All gradient computations needed for parameter optimization can be carried out -# with the elegant methods of automatic differentiation which facilitates simultaneous optimizations -# of circuit and Hamiltonian parameters in applications such as VQE molecular geometry -# optimizations. ii) By optimizing the molecular parameters such as the exponent and contraction -# coefficients of Gaussian functions of the basis set, one can reach a lower energy without -# increasing the number of basis functions. Can you think of other interesting molecular parameters -# that can be optimized along with the nuclear coordinates and basis set parameters that we -# optimized in this tutorial? -# -# References -# ---------- -# -# .. [#arrazola2021] -# -# Juan Miguel Arrazola, Soran Jahangiri, Alain Delgado, Jack Ceroni *et al.*, "Differentiable -# quantum computational chemistry with PennyLane". `arXiv:2111.09967 -# `__ -# -# .. [#szabo1996] -# -# Attila Szabo, Neil S. Ostlund, "Modern Quantum Chemistry: Introduction to Advanced Electronic -# Structure Theory". Dover Publications, 1996. -# -# -# About the author -# ---------------- -# +r""" + +Differentiable Hartree-Fock +=========================== + +.. meta:: + :property="og:description": Learn how to use the differentiable Hartree-Fock solver + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/differentiable_HF.png + +.. related:: + tutorial_quantum_chemistry Building molecular Hamiltonians + tutorial_vqe A brief overview of VQE + tutorial_givens_rotations Givens rotations for quantum chemistry + tutorial_adaptive_circuits Adaptive circuits for quantum chemistry + + +*Author: Soran Jahangiri — Posted: 09 May 2022. Last updated: 09 May 2022.* + +In this tutorial, you will learn how to use PennyLane's differentiable Hartree-Fock solver +[#arrazola2021]_. The quantum chemistry module in PennyLane, :mod:`qml.qchem `, +provides built-in methods for constructing +atomic and molecular orbitals, building Fock matrices and solving the self-consistent field +equations to obtain optimized orbitals which can be used to construct fully-differentiable +molecular Hamiltonians. PennyLane allows users to natively compute derivatives of all these objects +with respect to the underlying parameters using the methods of +`automatic differentiation `_. We +introduce a workflow to jointly optimize circuit parameters, nuclear coordinates and basis set +parameters in a variational quantum eigensolver algorithm. You will also learn how to visualize the +atomic and molecular orbitals which can be used to create an animation like this: + +.. figure:: /_static/demonstration_assets/differentiable_HF/h2.gif + :width: 60% + :align: center + + The bonding molecular orbital of hydrogen visualized during a full geometry, circuit and + basis set optimization. + +Let's get started! + +Differentiable Hamiltonians +--------------------------- + +Variational quantum algorithms aim to calculate the energy of a molecule by constructing a +parameterized quantum circuit and finding a set of parameters that minimize the expectation value of +the electronic `molecular Hamiltonian `_. The +optimization can be carried out by computing the gradients of the expectation value with respect to +these parameters and iteratively updating them until convergence is achieved. In principle, the +optimization process is not limited to the circuit parameters and can be extended to include the +parameters of the Hamiltonian that can be optimized concurrently with the circuit parameters. The +aim is now to obtain the set of parameters that minimize the following expectation value + +.. math:: \left \langle \Psi(\theta) | H(\beta) | \Psi(\theta) \right \rangle, + +where :math:`\theta` and :math:`\beta` represent the circuit and Hamiltonian parameters, +respectively. + +Computing the gradient of a molecular Hamiltonian is challenging because the dependency of the +Hamiltonian on the molecular parameters is typically not very straightforward. This makes symbolic +differentiation methods, which obtain derivatives of an input function by direct mathematical +manipulation, of limited scope. Furthermore, numerical differentiation methods based on +`finite differences `_ are not always +reliable due to their intrinsic instability, especially when the number of +differentiable parameters is large. These limitations can be alleviated by using automatic +differentiation methods which can be used to compute exact gradients of a function, implemented with +computer code, using resources comparable to those required to evaluate the function itself. + +Efficient optimization of the molecular Hamiltonian parameters in a variational quantum algorithm +is essential for tackling problems such as +`geometry optimization `_ and vibrational +frequency +calculations. These problems require computing the first- and second-order derivatives of the +molecular energy with respect to nuclear coordinates which can be efficiently obtained if the +variational workflow is automatically differentiable. Another important example is the simultaneous +optimization of the parameters of the basis set used to construct the atomic orbitals which can in +principle increase the accuracy of the computed energy without increasing the number of qubits in a +quantum simulation. The joint optimization of the circuit and Hamiltonian parameters can also be +used when the chemical problem involves optimizing the parameters of external potentials. + +The Hartree-Fock method +----------------------- + +The main goal of the Hartree-Fock method is to obtain molecular orbitals that minimize the +energy of a system where electrons are treated as independent particles that experience a mean field +generated by the other electrons. These optimized molecular orbitals are then used to +construct one- and two-body electron integrals in the basis of molecular orbitals, :math:`\phi,` + +.. math:: h_{pq} =\int dx \,\phi_p^*(x)\left(-\frac{\nabla^2}{2}-\sum_{i=1}^N\frac{Z_i}{|r-R_i|}\right)\phi_q(x),\\\\ +.. math:: h_{pqrs} = \int dx_1 dx_2\, \frac{\phi_p^*(x_1)\phi_q^*(x_2)\phi_r(x_2)\phi_s(x_1)}{|r_1-r_2|}. + +These integrals are used to generate a differentiable +`second-quantized `_ molecular Hamiltonian as + + +.. math:: H=\sum_{pq} h_{pq}a_p^\dagger a_q +\frac{1}{2}\sum_{pqrs}h_{pqrs}a_p^\dagger a_q^\dagger a_r a_s, + +where :math:`a^\dagger` and :math:`a` are the fermionic creation and annihilation operators, +respectively. This Hamiltonian is then transformed to the qubit basis. Let's see how this can be +done in PennyLane. + +To get started, we need to define the atomic symbols and the nuclear coordinates of the molecule. +For the hydrogen molecule we have +""" + +import pennylane as qml +import numpy as np +import jax +import jax.numpy as jnp +import matplotlib.pyplot as plt +np.set_printoptions(precision=5) +jax.config.update("jax_enable_x64", True) + +symbols = ["H", "H"] +# optimized geometry at the Hartree-Fock level +geometry = jnp.array([[-0.672943567415407, 0.0, 0.0], + [ 0.672943567415407, 0.0, 0.0]]) + +############################################################################## +# We can now compute the Hartree-Fock energy and its gradient with respect to the +# nuclear coordinates. To do that, we create a molecule object that stores all the molecular +# parameters needed to perform a Hartree-Fock calculation. + +mol = qml.qchem.Molecule(symbols, geometry) + +############################################################################## +# The Hartree-Fock energy can now be computed with the +# :func:`~.pennylane.qchem.hf_energy` function which is a function transform + +qml.qchem.hf_energy(mol)() + +############################################################################## +# We now compute the gradient of the energy with respect to the nuclear coordinates + +jax.grad(qml.qchem.hf_energy(mol), argnums=0)(geometry, mol.coeff, mol.alpha) + +############################################################################## +# The obtained gradients are equal or very close to zero because the geometry we used here has been +# previously optimized at the Hartree-Fock level. You can use a different geometry and verify that +# the newly computed gradients are not all zero. +# +# We can also compute the values and gradients of several other quantities that are obtained during +# the Hartree-Fock procedure. These include all integrals over basis functions, matrices formed from +# these integrals and the one- and two-body integrals over molecular orbitals [#arrazola2021]_. +# Let's look at a few examples. +# +# We first compute the overlap integral between the two S-type atomic orbitals of the hydrogen +# atoms. Here we are using the `STO-3G `_ +# basis set in which each of the atomic orbitals is represented by one basis function composed of +# three primitive Gaussian functions. These basis functions can be accessed from the molecule +# object as + +S1 = mol.basis_set[0] +S2 = mol.basis_set[1] + +############################################################################## +# We can check the parameters of the basis functions as + +for param in S1.params: + print(param) + +############################################################################## +# This returns the exponents, contraction coefficients and the centres of the three Gaussian +# functions of the STO-3G basis set. These parameters can be also obtained individually by using +# ``S1.alpha``, ``S1.coeff`` and ``S1.r``, respectively. You can verify that both of the ``S1`` an +# ``S2`` atomic orbitals have the same exponents and contraction coefficients but are centred on +# different hydrogen atoms. You can also verify that the orbitals are S-type by printing the angular +# momentum quantum numbers with + +S1.l + +############################################################################## +# This gives us a tuple of three integers, representing the exponents of the :math:`x,` :math:`y` +# and :math:`z` components in the Gaussian functions [#arrazola2021]_. +# +# We can now compute the overlap integral, +# +# .. math:: +# +# S_{\mu \nu} = \int \chi_\mu^* (r) \chi_\nu (r) dr +# +# between the atomic orbitals :math:`\chi,` by passing the orbitals and the initial values of their +# centres to the :func:`~.pennylane.qchem.overlap_integral` function. The centres of the orbitals +# are those of the hydrogen atoms by default and are therefore treated as differentiable parameters +# by PennyLane. + +qml.qchem.overlap_integral(S1, S2)() + +############################################################################## +# You can verify that the overlap integral between two identical atomic orbitals is equal to one. +# We can now compute the gradient of the overlap integral with respect to the orbital centres + +jax.grad(qml.qchem.overlap_integral(S1, S2))(geometry, mol.coeff, mol.alpha) + +############################################################################## +# Can you explain why some of the computed gradients are zero? +# +# Let's now plot the atomic orbitals and their overlap. We can do it by using +# the :py:meth:`~.pennylane.qchem.Molecule.atomic_orbital` function, which evaluates the +# atomic orbital at a given coordinate. For instance, the value of the S orbital on the first +# hydrogen atom can be computed at the origin as + +V1 = mol.atomic_orbital(0) +V1(0.0, 0.0, 0.0) + +############################################################################## +# We can evaluate this orbital at different points along the :math:`x` axis and plot it. + +x = np.linspace(-5, 5, 1000) +plt.plot(x, V1(x, 0.0, 0.0), color='teal') +plt.xlabel('X [Bohr]') +plt.show() + +############################################################################## +# We can also plot the second S orbital and visualize the overlap between them + +V2 = mol.atomic_orbital(1) +plt.plot(x, V1(x, 0.0, 0.0), color='teal') +plt.plot(x, V2(x, 0.0, 0.0), color='teal') +plt.fill_between( + x, np.minimum(V1(x, 0.0, 0.0), V2(x, 0.0, 0.0)), color = 'red', alpha = 0.5, hatch = '||') +plt.xlabel('X [Bohr]') +plt.show() + +############################################################################## +# By looking at the orbitals, can you guess at what distance the value of the overlap becomes +# negligible? Can you verify your guess by computing the overlap at that distance? +# +# Similarly, we can plot the molecular orbitals of the hydrogen molecule obtained from the +# Hartree-Fock calculations. We plot the cross-section of the bonding orbital on the :math:`x-y` +# plane. + +n = 30 # number of grid points along each axis +qml.qchem.hf_energy(mol)() +mol.mo_coefficients = mol.mo_coefficients.T +mo = mol.molecular_orbital(0) +x, y = np.meshgrid(np.linspace(-2, 2, n), + np.linspace(-2, 2, n)) +val = np.vectorize(mo)(x, y, 0) +val = np.array([val[i][j] for i in range(n) for j in range(n)]).reshape(n, n) + +fig, ax = plt.subplots() +co = ax.contour(x, y, val, 10, cmap='summer_r', zorder=0) +ax.clabel(co, inline=2, fontsize=10) +plt.scatter(mol.coordinates[:,0], mol.coordinates[:,1], s = 80, color='black') +ax.set_xlabel('X [Bohr]') +ax.set_ylabel('Y [Bohr]') +plt.show() + +############################################################################## +# VQE simulations +# --------------- +# +# By performing the Hartree-Fock calculations, we obtain a set of one- and two-body integrals +# over molecular orbitals that can be used to construct the molecular Hamiltonian with the +# :func:`~.pennylane.qchem.molecular_hamiltonian` function. + +hamiltonian, qubits = qml.qchem.molecular_hamiltonian(mol) +print(hamiltonian) + +############################################################################## +# The Hamiltonian contains 15 terms and, importantly, the coefficients of the Hamiltonian are all +# differentiable. We can construct a circuit and perform a VQE simulation in which both of the +# circuit parameters and the nuclear coordinates are optimized simultaneously by using the computed +# gradients. We will have two sets of differentiable parameters: the first set is the +# rotation angles of the excitation gates which are applied to the reference Hartree-Fock state +# to construct the exact ground state. The second set contains the nuclear coordinates of the +# hydrogen atoms. + +dev = qml.device("default.qubit", wires=4) +def energy(): + @qml.qnode(dev, interface="jax") + def circuit(*args): + qml.BasisState(np.array([1, 1, 0, 0]), wires=range(4)) + qml.DoubleExcitation(*args[0], wires=[0, 1, 2, 3]) + mol = qml.qchem.Molecule(symbols, geometry, alpha=args[3], coeff=args[2]) + H = qml.qchem.molecular_hamiltonian(mol, args=args[1:])[0] + return qml.expval(H) + return circuit + +############################################################################## +# Note that we only use the :class:`~.pennylane.DoubleExcitation` gate as the +# :class:`~.pennylane.SingleExcitation` ones can be neglected in this particular example +# [#szabo1996]_. We now compute the gradients of the energy with respect to the circuit parameter +# and the nuclear coordinates and update the parameters iteratively. Note that the nuclear +# coordinate gradients are simply the forces on the atomic nuclei. + +# initial value of the circuit parameter +circuit_param = jnp.array([0.0]) + +geometry = jnp.array([[0.0, 0.02, -0.672943567415407], + [0.1, 0.0, 0.672943567415407]]) + +for n in range(36): + mol = qml.qchem.Molecule(symbols, geometry) + args = [circuit_param, geometry, mol.coeff, mol.alpha] + # gradient for circuit parameters + g_param = jax.grad(energy(), argnums = 0)(*args) + circuit_param = circuit_param - 0.25 * g_param[0] + + # gradient for nuclear coordinates + forces = jax.grad(energy(), argnums = 1)(*args) + geometry = geometry - 0.5 * forces + + if n % 5 == 0: + print(f'n: {n}, E: {energy()(*args):.8f}, Force-max: {abs(forces).max():.8f}') + +############################################################################## +# After 35 steps of optimization, the forces on the atomic nuclei and the gradient of the +# circuit parameter are both approaching zero, and the energy of the molecule is that of the +# optimized geometry at the +# `full-CI `_ level: +# :math:`-1.1373060483` Ha. You can print the optimized geometry and verify that the final bond +# length of hydrogen is identical to the one computed with full-CI which is :math:`1.389` +# `Bohr `_. +# +# We are now ready to perform a full optimization where the circuit parameters, the nuclear +# coordinates and the basis set parameters are all differentiable parameters that can be optimized +# simultaneously. + +symbols = ["H", "H"] +# initial values of the nuclear coordinates +geometry = jnp.array([[0.0, 0.0, -0.672943567415407], + [0.0, 0.0, 0.672943567415407]]) + +# initial values of the basis set contraction coefficients +coeff = jnp.array([[0.1543289673, 0.5353281423, 0.4446345422], + [0.1543289673, 0.5353281423, 0.4446345422]]) + +# initial values of the basis set exponents +alpha = jnp.array([[3.42525091, 0.62391373, 0.1688554], + [3.42525091, 0.62391373, 0.1688554]]) + +# initial value of the circuit parameter +circuit_param = jnp.array([0.0]) + +mol = qml.qchem.Molecule(symbols, geometry, coeff=coeff, alpha=alpha) +args = [circuit_param, geometry, coeff, alpha] + +for n in range(36): + args = [circuit_param, geometry, coeff, alpha] + mol = qml.qchem.Molecule(symbols, geometry, alpha=alpha, coeff=coeff) + + # gradient for circuit parameters + g_param = jax.grad(energy(), argnums=[0, 1, 2, 3])(*args)[0] + circuit_param = circuit_param - 0.25 * g_param[0] + + # gradient for nuclear coordinates + value, gradients = jax.value_and_grad(energy(), argnums=[1, 2, 3])(*args) + geometry = geometry - 0.5 * gradients[0] + alpha = alpha - 0.25 * gradients[2] + coeff = coeff - 0.25 * gradients[1] + + if n % 5 == 0: + print(f'n: {n}, E: {value:.8f}, Force-max: {abs(gradients[0]).max():.8f}') + +############################################################################## +# You can also print the gradients of the circuit and basis set parameters and confirm that they are +# approaching zero. The computed energy of :math:`-1.14040160` Ha is +# lower than the full-CI energy, :math:`-1.1373060483` Ha (obtained with the STO-3G basis set for +# the hydrogen molecule) because we have optimized the basis set parameters in our example. This +# means that we can reach a lower energy for hydrogen without increasing the basis set size, which +# would otherwise lead to a larger number of qubits. +# +# Conclusions +# ----------- +# This tutorial introduces an important feature of PennyLane that allows performing +# fully-differentiable Hartree-Fock and subsequently VQE simulations. This feature provides two +# major benefits: i) All gradient computations needed for parameter optimization can be carried out +# with the elegant methods of automatic differentiation which facilitates simultaneous optimizations +# of circuit and Hamiltonian parameters in applications such as VQE molecular geometry +# optimizations. ii) By optimizing the molecular parameters such as the exponent and contraction +# coefficients of Gaussian functions of the basis set, one can reach a lower energy without +# increasing the number of basis functions. Can you think of other interesting molecular parameters +# that can be optimized along with the nuclear coordinates and basis set parameters that we +# optimized in this tutorial? +# +# References +# ---------- +# +# .. [#arrazola2021] +# +# Juan Miguel Arrazola, Soran Jahangiri, Alain Delgado, Jack Ceroni *et al.*, "Differentiable +# quantum computational chemistry with PennyLane". `arXiv:2111.09967 +# `__ +# +# .. [#szabo1996] +# +# Attila Szabo, Neil S. Ostlund, "Modern Quantum Chemistry: Introduction to Advanced Electronic +# Structure Theory". Dover Publications, 1996. +# +# +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_doubly_stochastic/demo.py b/demonstrations_v2/tutorial_doubly_stochastic/demo.py index 58a18ea688..b75b365834 100644 --- a/demonstrations_v2/tutorial_doubly_stochastic/demo.py +++ b/demonstrations_v2/tutorial_doubly_stochastic/demo.py @@ -1,407 +1,407 @@ -r""" -Doubly stochastic gradient descent -================================== - -.. meta:: - :property="og:description": Minimize a Hamiltonian via an adaptive shot optimization - strategy with doubly stochastic gradient descent. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/single_shot.png - -.. related:: - - tutorial_backprop Quantum gradients with backpropagation - tutorial_quantum_natural_gradient Quantum natural gradient - tutorial_rosalin Frugal shot optimization with Rosalin - -*Author: Josh Izaac — Posted: 16 October 2019. Last updated: 20 January 2021.* - -In this tutorial we investigate and implement the doubly stochastic gradient descent -paper from `Ryan Sweke et al. (2019) `__. In this paper, -it is shown that quantum gradient descent, where a finite number of measurement samples -(or *shots*) are used to estimate the gradient, is a form of stochastic gradient descent. -Furthermore, if the optimization involves a linear combination of expectation values -(such as VQE), sampling from the terms in this linear combination can further reduce required -resources, allowing for "doubly stochastic gradient descent". - -Note that based on very similar observations, `Jonas Kuebler et al. (2019) `_ -recently proposed an optimizer (which they call the *individual Coupled Adaptive -Number of Shots (iCANS)* optimizer) that adapts the shot number of -measurements during training. - -Background ----------- - -In classical machine learning, `stochastic gradient descent -`_ is a common optimization strategy -where the standard gradient descent parameter update rule, - -.. math:: \theta^{(t+1)} = \theta^{(t)} - \eta \nabla \mathcal{L}(\theta^{(t)}), - -is modified such that - -.. math:: \theta^{(t+1)} = \theta^{(t)} - \eta g^{(t)}(\theta^{(t)}) - -where :math:`\eta` is the step-size, and :math:`\{g^{(t)}(\theta)\}` is a sequence of random -variables such that - -.. math:: \mathbb{E}[g^{(t)}(\theta)] = \nabla\mathcal{L}(\theta). - -In general, stochastic gradient descent is preferred over standard gradient -descent for several reasons: - -1. Samples of the gradient estimator :math:`g^{(t)}(\theta)` can typically - be computed much more efficiently than :math:`\mathcal{L}(\theta),` - -2. Stochasticity can help to avoid local minima and saddle points, - -3. Numerical evidence shows that convergence properties are superior to regular gradient descent. - -In variational quantum algorithms, a parametrized quantum circuit :math:`U(\theta)` -is optimized by a classical optimization loop in order to minimize a function of the expectation -values. For example, consider the expectation values - -.. math:: \langle A_i \rangle = \langle 0 | U(\theta)^\dagger A_i U(\theta) | 0\rangle - -for a set of observables :math:`\{A_i\},` and loss function - -.. math:: \mathcal{L}(\theta, \langle A_1 \rangle, \dots, \langle A_M \rangle). - -While the expectation values can be calculated analytically in classical simulations, -on quantum hardware we are limited to *sampling* from the expectation values; as the -number of samples (or shots) increase, we converge on the analytic expectation value, but can -never recover the exact expression. Furthermore, the parameter-shift rule -(`Schuld et al., 2018 `__) allows for analytic -quantum gradients to be computed from a linear combination of the variational circuits' -expectation values. - -Putting these two results together, `Sweke et al. (2019) `__ -show that samples of the expectation value fed into the parameter-shift rule provide -unbiased estimators of the quantum gradient—resulting in a form of stochastic gradient descent -(referred to as QSGD). Moreover, they show that convergence of the stochastic gradient -descent is guaranteed in sufficiently simplified settings, even in the case where the number -of shots is 1! - -.. note:: - - It is worth noting that the smaller the number of shots used, the larger the - variance in the estimated expectation value. As a result, it may take - more optimization steps for convergence than using a larger number of shots, - or an exact value. - - At the same time, a reduced number of shots may significantly reduce the - wall time of each optimization step, leading to a reduction in the overall - optimization time. - -""" - -############################################################################## -# Let's consider a simple example in PennyLane, comparing analytic gradient -# descent (with exact expectation values) to stochastic gradient descent -# using a finite number of shots. -# -# A single-shot stochastic gradient descent -# ----------------------------------------- -# -# Consider the Hamiltonian -# -# .. math:: -# -# H = \begin{bmatrix} -# 8 & 4 & 0 & -6\\ -# 4 & 0 & 4 & 0\\ -# 0 & 4 & 8 & 0\\ -# -6 & 0 & 0 & 0 -# \end{bmatrix}. -# -# We can solve for the ground state energy using -# the variational quantum eigensolver (VQE) algorithm. -# -# Let's use the ``lightning.qubit`` simulator for both the analytic gradient, -# as well as the estimated gradient using number of shots :math:`N\in\{1, 100\}.` - -import pennylane as qml -import numpy as np -from pennylane import numpy as pnp - -np.random.seed(3) - -from pennylane import expval -from pennylane.templates.layers import StronglyEntanglingLayers - -num_layers = 2 -num_wires = 2 -eta = 0.01 -steps = 200 - -dev_analytic = qml.device("lightning.qubit", wires=num_wires, shots=None) -dev_stochastic = qml.device("lightning.qubit", wires=num_wires, shots=1000) - -############################################################################## -# We can use ``qml.Hermitian`` to directly specify that we want to measure -# the expectation value of the matrix :math:`H:` - -H = np.array([[8, 4, 0, -6], [4, 0, 4, 0], [0, 4, 8, 0], [-6, 0, 0, 0]]) - - -def circuit(params): - StronglyEntanglingLayers(weights=params, wires=[0, 1]) - return expval(qml.Hermitian(H, wires=[0, 1])) - - -############################################################################## -# Now, we create three QNodes, each corresponding to a device above, -# and optimize them using gradient descent via the parameter-shift rule. - -qnode_analytic = qml.QNode(circuit, dev_analytic, interface="autograd", diff_method="parameter-shift") -qnode_stochastic = qml.QNode(circuit, dev_stochastic, interface="autograd", diff_method="parameter-shift") - -param_shape = StronglyEntanglingLayers.shape(n_layers=num_layers, n_wires=num_wires) -init_params = pnp.random.uniform(low=0, high=2 * np.pi, size=param_shape, requires_grad=True) - -# Optimizing using exact gradient descent - -cost_GD = [] -params_GD = init_params -opt = qml.GradientDescentOptimizer(eta) - -for _ in range(steps): - cost_GD.append(qnode_analytic(params_GD)) - params_GD = opt.step(qnode_analytic, params_GD) - -# Optimizing using stochastic gradient descent with shots=1 - -cost_SGD1 = [] -params_SGD1 = init_params -opt = qml.GradientDescentOptimizer(eta) - -for _ in range(steps): - cost_SGD1.append(qnode_stochastic(params_SGD1, shots=1)) - params_SGD1 = opt.step(qnode_stochastic, params_SGD1, shots=1) - -# Optimizing using stochastic gradient descent with shots=100 - -cost_SGD100 = [] -params_SGD100 = init_params -opt = qml.GradientDescentOptimizer(eta) - -for _ in range(steps): - cost_SGD100.append(qnode_stochastic(params_SGD100, shots=100)) - params_SGD100 = opt.step(qnode_stochastic, params_SGD100, shots=100) - -############################################################################## -# Note that in the latter two cases we are sampling from an unbiased -# estimator of the cost function, not the analytic cost function. -# -# To track optimization convergence, approaches could include: -# -# * Evaluating the cost function with a larger number of samples at specified -# intervals, -# -# * Keeping track of the *moving average* of the low-shot cost evaluations. -# -# We can now plot the cost against optimization step for the three cases above. - -from matplotlib import pyplot as plt - -plt.style.use("seaborn") -plt.plot(cost_GD[:100], label="Vanilla gradient descent") -plt.plot(cost_SGD100[:100], "--", label="QSGD (100 shots)") -plt.plot(cost_SGD1[:100], ".", label="QSGD (1 shot)") - -# analytic ground state -min_energy = min(np.linalg.eigvalsh(H)) -plt.hlines(min_energy, 0, 100, linestyles=":", label="Ground-state energy") - -plt.ylabel("Cost function value") -plt.xlabel("Optimization steps") -plt.legend() -plt.show() - -############################################################################## -# Using the trained parameters from each optimization strategy, we can -# evaluate the analytic quantum device: - -print("Vanilla gradient descent min energy = ", qnode_analytic(params_GD)) -print( - "Stochastic gradient descent (shots=100) min energy = ", - qnode_analytic(params_SGD100), -) -print( - "Stochastic gradient descent (shots=1) min energy = ", qnode_analytic(params_SGD1) -) - - -############################################################################## -# Amazingly, we see that even the ``shots=1`` optimization converged -# to a reasonably close approximation of the ground-state energy! - -############################################################################## -# Doubly stochastic gradient descent for VQE -# ------------------------------------------ -# -# As noted in `Sweke et al. (2019) `__, -# variational quantum algorithms often include terms consisting of linear combinations -# of expectation values. This is true of the parameter-shift rule (where the -# gradient of each parameter is determined by shifting the parameter by macroscopic -# amounts and taking the difference), as well as VQE, where the Hamiltonian -# is usually decomposed into a sum of Pauli expectation values. -# -# Consider the Hamiltonian from the previous section. As this Hamiltonian is a -# Hermitian observable, we can always express it as a sum of Pauli matrices using -# the relation -# -# .. math:: -# -# H = \sum_{i,j=0,1,2,3} a_{i,j} (\sigma_i\otimes \sigma_j), -# -# where -# -# .. math:: -# -# a_{i,j} = \frac{1}{4}\text{tr}[(\sigma_i\otimes \sigma_j )H], ~~ \sigma = \{I, X, Y, Z\}. -# -# Applying this, we can see that -# -# .. math:: -# -# H = 4 + 2I\otimes X + 4I \otimes Z - X\otimes X + 5 Y\otimes Y + 2Z\otimes X. -# -# To perform "doubly stochastic" gradient descent, we simply apply the stochastic -# gradient descent approach from above, but in addition also uniformly sample -# a subset of the terms for the Hamiltonian expectation at each optimization step. -# This inserts another element of stochasticity into the system—all the while -# convergence continues to be guaranteed! -# -# Let's create a QNode that randomly samples a single term from the above -# Hamiltonian as the observable to be measured. - -I = np.identity(2) -X = np.array([[0, 1], [1, 0]]) -Y = np.array([[0, -1j], [1j, 0]]) -Z = np.array([[1, 0], [0, -1]]) - -terms = np.array( - [ - 2 * np.kron(I, X), - 4 * np.kron(I, Z), - -np.kron(X, X), - 5 * np.kron(Y, Y), - 2 * np.kron(Z, X), - ] -) - - -@qml.qnode(dev_stochastic, interface="autograd") -def circuit(params, n=None): - StronglyEntanglingLayers(weights=params, wires=[0, 1]) - idx = np.random.choice(np.arange(5), size=n, replace=False) - A = np.sum(terms[idx], axis=0) - return expval(qml.Hermitian(A, wires=[0, 1])) - - -def loss(params, shots=None): - return 4 + (5 / 1) * circuit(params, shots=shots, n=1) - - -############################################################################## -# Optimizing the circuit using gradient descent via the parameter-shift rule: - -cost = [] -params = init_params -opt = qml.GradientDescentOptimizer(0.005) - -for _ in range(250): - cost.append(loss(params, shots=100)) - params = opt.step(loss, params, shots=100) - - -############################################################################## -# During doubly stochastic gradient descent, we are sampling from terms of the -# analytic cost function, so it is not entirely instructive to plot the cost -# versus optimization step—partial sums of the terms in the Hamiltonian -# may have minimum energy below the ground state energy of the total Hamiltonian. -# Nevertheless, we can keep track of the cost value moving average during doubly -# stochastic gradient descent as an indicator of convergence. - - -def moving_average(data, n=3): - ret = np.cumsum(data, dtype=np.float64) - ret[n:] = ret[n:] - ret[:-n] - return ret[n - 1:] / n - - -average = np.vstack([np.arange(25, 200), moving_average(cost, n=50)[:-26]]) - -plt.plot(cost_GD, label="Vanilla gradient descent") -plt.plot(cost, ".", label="Doubly QSGD") -plt.plot(average[0], average[1], "--", label="Doubly QSGD (moving average)") -plt.hlines(min_energy, 0, 200, linestyles=":", label="Ground state energy") - -plt.ylabel("Cost function value") -plt.xlabel("Optimization steps") -plt.xlim(-2, 200) -plt.legend() -plt.show() - -############################################################################## -# Finally, verifying that the doubly stochastic gradient descent optimization -# correctly provides the ground state energy when evaluated for a larger -# number of shots: - -print("Doubly stochastic gradient descent min energy = ", qnode_analytic(params)) - -############################################################################## -# While stochastic gradient descent requires more optimization steps to achieve -# convergence, it is worth noting that it requires significantly fewer quantum -# device evaluations, and thus may as a result take less time overall. - -############################################################################## -# Adaptive stochasticity -# ---------------------- -# -# To improve on the convergence, we may even consider a crude "adaptive" modification -# of the doubly stochastic gradient descent optimization performed above. In this -# approach, we successively increase the number of terms we are sampling from as -# the optimization proceeds, as well as increasing the number of shots. - -cost = [] -params = init_params -opt = qml.GradientDescentOptimizer(0.005) - -for i in range(250): - n = min(i // 25 + 1, 5) - - def loss(params, shots=None): - return 4 + (5 / n) * circuit(params, shots=shots, n=n) - - cost.append(loss(params, shots=int(1 + (n - 1) ** 2))) - params = opt.step(loss, params, shots=int(1 + (n - 1) ** 2)) - -average = np.vstack([np.arange(25, 200), moving_average(cost, n=50)[:-26]]) - -plt.plot(cost_GD, label="Vanilla gradient descent") -plt.plot(cost, ".", label="Adaptive QSGD") -plt.plot(average[0], average[1], "--", label="Adaptive QSGD (moving average)") -plt.hlines(min_energy, 0, 250, linestyles=":", label="Ground state energy") - -plt.ylabel("Cost function value") -plt.xlabel("Optimization steps") -plt.xlim(-2, 200) -plt.legend() -plt.show() - -print("Adaptive QSGD min energy = ", qnode_analytic(params)) - -############################################################################## -# References -# ---------- -# -# 1. Ryan Sweke, Frederik Wilde, Johannes Jakob Meyer, Maria Schuld, Paul K. Fährmann, -# Barthélémy Meynard-Piganeau, Jens Eisert. "Stochastic gradient descent for -# hybrid quantum-classical optimization." `arXiv:1910.01155 -# `__, 2019. -# -# -# About the author -# ---------------- +r""" +Doubly stochastic gradient descent +================================== + +.. meta:: + :property="og:description": Minimize a Hamiltonian via an adaptive shot optimization + strategy with doubly stochastic gradient descent. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/single_shot.png + +.. related:: + + tutorial_backprop Quantum gradients with backpropagation + tutorial_quantum_natural_gradient Quantum natural gradient + tutorial_rosalin Frugal shot optimization with Rosalin + +*Author: Josh Izaac — Posted: 16 October 2019. Last updated: 20 January 2021.* + +In this tutorial we investigate and implement the doubly stochastic gradient descent +paper from `Ryan Sweke et al. (2019) `__. In this paper, +it is shown that quantum gradient descent, where a finite number of measurement samples +(or *shots*) are used to estimate the gradient, is a form of stochastic gradient descent. +Furthermore, if the optimization involves a linear combination of expectation values +(such as VQE), sampling from the terms in this linear combination can further reduce required +resources, allowing for "doubly stochastic gradient descent". + +Note that based on very similar observations, `Jonas Kuebler et al. (2019) `_ +recently proposed an optimizer (which they call the *individual Coupled Adaptive +Number of Shots (iCANS)* optimizer) that adapts the shot number of +measurements during training. + +Background +---------- + +In classical machine learning, `stochastic gradient descent +`_ is a common optimization strategy +where the standard gradient descent parameter update rule, + +.. math:: \theta^{(t+1)} = \theta^{(t)} - \eta \nabla \mathcal{L}(\theta^{(t)}), + +is modified such that + +.. math:: \theta^{(t+1)} = \theta^{(t)} - \eta g^{(t)}(\theta^{(t)}) + +where :math:`\eta` is the step-size, and :math:`\{g^{(t)}(\theta)\}` is a sequence of random +variables such that + +.. math:: \mathbb{E}[g^{(t)}(\theta)] = \nabla\mathcal{L}(\theta). + +In general, stochastic gradient descent is preferred over standard gradient +descent for several reasons: + +1. Samples of the gradient estimator :math:`g^{(t)}(\theta)` can typically + be computed much more efficiently than :math:`\mathcal{L}(\theta),` + +2. Stochasticity can help to avoid local minima and saddle points, + +3. Numerical evidence shows that convergence properties are superior to regular gradient descent. + +In variational quantum algorithms, a parametrized quantum circuit :math:`U(\theta)` +is optimized by a classical optimization loop in order to minimize a function of the expectation +values. For example, consider the expectation values + +.. math:: \langle A_i \rangle = \langle 0 | U(\theta)^\dagger A_i U(\theta) | 0\rangle + +for a set of observables :math:`\{A_i\},` and loss function + +.. math:: \mathcal{L}(\theta, \langle A_1 \rangle, \dots, \langle A_M \rangle). + +While the expectation values can be calculated analytically in classical simulations, +on quantum hardware we are limited to *sampling* from the expectation values; as the +number of samples (or shots) increase, we converge on the analytic expectation value, but can +never recover the exact expression. Furthermore, the parameter-shift rule +(`Schuld et al., 2018 `__) allows for analytic +quantum gradients to be computed from a linear combination of the variational circuits' +expectation values. + +Putting these two results together, `Sweke et al. (2019) `__ +show that samples of the expectation value fed into the parameter-shift rule provide +unbiased estimators of the quantum gradient—resulting in a form of stochastic gradient descent +(referred to as QSGD). Moreover, they show that convergence of the stochastic gradient +descent is guaranteed in sufficiently simplified settings, even in the case where the number +of shots is 1! + +.. note:: + + It is worth noting that the smaller the number of shots used, the larger the + variance in the estimated expectation value. As a result, it may take + more optimization steps for convergence than using a larger number of shots, + or an exact value. + + At the same time, a reduced number of shots may significantly reduce the + wall time of each optimization step, leading to a reduction in the overall + optimization time. + +""" + +############################################################################## +# Let's consider a simple example in PennyLane, comparing analytic gradient +# descent (with exact expectation values) to stochastic gradient descent +# using a finite number of shots. +# +# A single-shot stochastic gradient descent +# ----------------------------------------- +# +# Consider the Hamiltonian +# +# .. math:: +# +# H = \begin{bmatrix} +# 8 & 4 & 0 & -6\\ +# 4 & 0 & 4 & 0\\ +# 0 & 4 & 8 & 0\\ +# -6 & 0 & 0 & 0 +# \end{bmatrix}. +# +# We can solve for the ground state energy using +# the variational quantum eigensolver (VQE) algorithm. +# +# Let's use the ``lightning.qubit`` simulator for both the analytic gradient, +# as well as the estimated gradient using number of shots :math:`N\in\{1, 100\}.` + +import pennylane as qml +import numpy as np +from pennylane import numpy as pnp + +np.random.seed(3) + +from pennylane import expval +from pennylane.templates.layers import StronglyEntanglingLayers + +num_layers = 2 +num_wires = 2 +eta = 0.01 +steps = 200 + +dev_analytic = qml.device("lightning.qubit", wires=num_wires, shots=None) +dev_stochastic = qml.device("lightning.qubit", wires=num_wires, shots=1000) + +############################################################################## +# We can use ``qml.Hermitian`` to directly specify that we want to measure +# the expectation value of the matrix :math:`H:` + +H = np.array([[8, 4, 0, -6], [4, 0, 4, 0], [0, 4, 8, 0], [-6, 0, 0, 0]]) + + +def circuit(params): + StronglyEntanglingLayers(weights=params, wires=[0, 1]) + return expval(qml.Hermitian(H, wires=[0, 1])) + + +############################################################################## +# Now, we create three QNodes, each corresponding to a device above, +# and optimize them using gradient descent via the parameter-shift rule. + +qnode_analytic = qml.QNode(circuit, dev_analytic, interface="autograd", diff_method="parameter-shift") +qnode_stochastic = qml.QNode(circuit, dev_stochastic, interface="autograd", diff_method="parameter-shift") + +param_shape = StronglyEntanglingLayers.shape(n_layers=num_layers, n_wires=num_wires) +init_params = pnp.random.uniform(low=0, high=2 * np.pi, size=param_shape, requires_grad=True) + +# Optimizing using exact gradient descent + +cost_GD = [] +params_GD = init_params +opt = qml.GradientDescentOptimizer(eta) + +for _ in range(steps): + cost_GD.append(qnode_analytic(params_GD)) + params_GD = opt.step(qnode_analytic, params_GD) + +# Optimizing using stochastic gradient descent with shots=1 + +cost_SGD1 = [] +params_SGD1 = init_params +opt = qml.GradientDescentOptimizer(eta) + +for _ in range(steps): + cost_SGD1.append(qnode_stochastic(params_SGD1, shots=1)) + params_SGD1 = opt.step(qnode_stochastic, params_SGD1, shots=1) + +# Optimizing using stochastic gradient descent with shots=100 + +cost_SGD100 = [] +params_SGD100 = init_params +opt = qml.GradientDescentOptimizer(eta) + +for _ in range(steps): + cost_SGD100.append(qnode_stochastic(params_SGD100, shots=100)) + params_SGD100 = opt.step(qnode_stochastic, params_SGD100, shots=100) + +############################################################################## +# Note that in the latter two cases we are sampling from an unbiased +# estimator of the cost function, not the analytic cost function. +# +# To track optimization convergence, approaches could include: +# +# * Evaluating the cost function with a larger number of samples at specified +# intervals, +# +# * Keeping track of the *moving average* of the low-shot cost evaluations. +# +# We can now plot the cost against optimization step for the three cases above. + +from matplotlib import pyplot as plt + +plt.style.use("seaborn") +plt.plot(cost_GD[:100], label="Vanilla gradient descent") +plt.plot(cost_SGD100[:100], "--", label="QSGD (100 shots)") +plt.plot(cost_SGD1[:100], ".", label="QSGD (1 shot)") + +# analytic ground state +min_energy = min(np.linalg.eigvalsh(H)) +plt.hlines(min_energy, 0, 100, linestyles=":", label="Ground-state energy") + +plt.ylabel("Cost function value") +plt.xlabel("Optimization steps") +plt.legend() +plt.show() + +############################################################################## +# Using the trained parameters from each optimization strategy, we can +# evaluate the analytic quantum device: + +print("Vanilla gradient descent min energy = ", qnode_analytic(params_GD)) +print( + "Stochastic gradient descent (shots=100) min energy = ", + qnode_analytic(params_SGD100), +) +print( + "Stochastic gradient descent (shots=1) min energy = ", qnode_analytic(params_SGD1) +) + + +############################################################################## +# Amazingly, we see that even the ``shots=1`` optimization converged +# to a reasonably close approximation of the ground-state energy! + +############################################################################## +# Doubly stochastic gradient descent for VQE +# ------------------------------------------ +# +# As noted in `Sweke et al. (2019) `__, +# variational quantum algorithms often include terms consisting of linear combinations +# of expectation values. This is true of the parameter-shift rule (where the +# gradient of each parameter is determined by shifting the parameter by macroscopic +# amounts and taking the difference), as well as VQE, where the Hamiltonian +# is usually decomposed into a sum of Pauli expectation values. +# +# Consider the Hamiltonian from the previous section. As this Hamiltonian is a +# Hermitian observable, we can always express it as a sum of Pauli matrices using +# the relation +# +# .. math:: +# +# H = \sum_{i,j=0,1,2,3} a_{i,j} (\sigma_i\otimes \sigma_j), +# +# where +# +# .. math:: +# +# a_{i,j} = \frac{1}{4}\text{tr}[(\sigma_i\otimes \sigma_j )H], ~~ \sigma = \{I, X, Y, Z\}. +# +# Applying this, we can see that +# +# .. math:: +# +# H = 4 + 2I\otimes X + 4I \otimes Z - X\otimes X + 5 Y\otimes Y + 2Z\otimes X. +# +# To perform "doubly stochastic" gradient descent, we simply apply the stochastic +# gradient descent approach from above, but in addition also uniformly sample +# a subset of the terms for the Hamiltonian expectation at each optimization step. +# This inserts another element of stochasticity into the system—all the while +# convergence continues to be guaranteed! +# +# Let's create a QNode that randomly samples a single term from the above +# Hamiltonian as the observable to be measured. + +I = np.identity(2) +X = np.array([[0, 1], [1, 0]]) +Y = np.array([[0, -1j], [1j, 0]]) +Z = np.array([[1, 0], [0, -1]]) + +terms = np.array( + [ + 2 * np.kron(I, X), + 4 * np.kron(I, Z), + -np.kron(X, X), + 5 * np.kron(Y, Y), + 2 * np.kron(Z, X), + ] +) + + +@qml.qnode(dev_stochastic, interface="autograd") +def circuit(params, n=None): + StronglyEntanglingLayers(weights=params, wires=[0, 1]) + idx = np.random.choice(np.arange(5), size=n, replace=False) + A = np.sum(terms[idx], axis=0) + return expval(qml.Hermitian(A, wires=[0, 1])) + + +def loss(params, shots=None): + return 4 + (5 / 1) * circuit(params, shots=shots, n=1) + + +############################################################################## +# Optimizing the circuit using gradient descent via the parameter-shift rule: + +cost = [] +params = init_params +opt = qml.GradientDescentOptimizer(0.005) + +for _ in range(250): + cost.append(loss(params, shots=100)) + params = opt.step(loss, params, shots=100) + + +############################################################################## +# During doubly stochastic gradient descent, we are sampling from terms of the +# analytic cost function, so it is not entirely instructive to plot the cost +# versus optimization step—partial sums of the terms in the Hamiltonian +# may have minimum energy below the ground state energy of the total Hamiltonian. +# Nevertheless, we can keep track of the cost value moving average during doubly +# stochastic gradient descent as an indicator of convergence. + + +def moving_average(data, n=3): + ret = np.cumsum(data, dtype=np.float64) + ret[n:] = ret[n:] - ret[:-n] + return ret[n - 1:] / n + + +average = np.vstack([np.arange(25, 200), moving_average(cost, n=50)[:-26]]) + +plt.plot(cost_GD, label="Vanilla gradient descent") +plt.plot(cost, ".", label="Doubly QSGD") +plt.plot(average[0], average[1], "--", label="Doubly QSGD (moving average)") +plt.hlines(min_energy, 0, 200, linestyles=":", label="Ground state energy") + +plt.ylabel("Cost function value") +plt.xlabel("Optimization steps") +plt.xlim(-2, 200) +plt.legend() +plt.show() + +############################################################################## +# Finally, verifying that the doubly stochastic gradient descent optimization +# correctly provides the ground state energy when evaluated for a larger +# number of shots: + +print("Doubly stochastic gradient descent min energy = ", qnode_analytic(params)) + +############################################################################## +# While stochastic gradient descent requires more optimization steps to achieve +# convergence, it is worth noting that it requires significantly fewer quantum +# device evaluations, and thus may as a result take less time overall. + +############################################################################## +# Adaptive stochasticity +# ---------------------- +# +# To improve on the convergence, we may even consider a crude "adaptive" modification +# of the doubly stochastic gradient descent optimization performed above. In this +# approach, we successively increase the number of terms we are sampling from as +# the optimization proceeds, as well as increasing the number of shots. + +cost = [] +params = init_params +opt = qml.GradientDescentOptimizer(0.005) + +for i in range(250): + n = min(i // 25 + 1, 5) + + def loss(params, shots=None): + return 4 + (5 / n) * circuit(params, shots=shots, n=n) + + cost.append(loss(params, shots=int(1 + (n - 1) ** 2))) + params = opt.step(loss, params, shots=int(1 + (n - 1) ** 2)) + +average = np.vstack([np.arange(25, 200), moving_average(cost, n=50)[:-26]]) + +plt.plot(cost_GD, label="Vanilla gradient descent") +plt.plot(cost, ".", label="Adaptive QSGD") +plt.plot(average[0], average[1], "--", label="Adaptive QSGD (moving average)") +plt.hlines(min_energy, 0, 250, linestyles=":", label="Ground state energy") + +plt.ylabel("Cost function value") +plt.xlabel("Optimization steps") +plt.xlim(-2, 200) +plt.legend() +plt.show() + +print("Adaptive QSGD min energy = ", qnode_analytic(params)) + +############################################################################## +# References +# ---------- +# +# 1. Ryan Sweke, Frederik Wilde, Johannes Jakob Meyer, Maria Schuld, Paul K. Fährmann, +# Barthélémy Meynard-Piganeau, Jens Eisert. "Stochastic gradient descent for +# hybrid quantum-classical optimization." `arXiv:1910.01155 +# `__, 2019. +# +# +# About the author +# ---------------- # \ No newline at end of file diff --git a/demonstrations_v2/tutorial_fermionic_operators/demo.py b/demonstrations_v2/tutorial_fermionic_operators/demo.py index 673c5a705a..d06f98aafb 100644 --- a/demonstrations_v2/tutorial_fermionic_operators/demo.py +++ b/demonstrations_v2/tutorial_fermionic_operators/demo.py @@ -1,231 +1,231 @@ -r""" - -Fermionic operators -=================== - -.. meta:: - :property="og:description": Learn how to work with fermionic operators - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_fermionic_operators.png - -.. related:: - tutorial_quantum_chemistry Building molecular Hamiltonians - tutorial_vqe A brief overview of VQE - -*Author: Soran Jahangiri — Posted: 27 June 2023. Last updated: 27 June 2023.* - -Fermionic creation and annihilation operators are commonly used to construct -`Hamiltonians `_ and other observables of molecules and spin -systems [#surjan]_. In this demo, you will learn how to use PennyLane to create fermionic operators -and map them to a qubit representation for use in quantum algorithms. - -Constructing fermionic operators --------------------------------- -The fermionic `creation and annihilation `_ -operators can be constructed in PennyLane similarly to Pauli operators by using -:class:`~.pennylane.FermiC` and :class:`~.pennylane.FermiA` for creation and annihilation operators, -respectively. -""" - -from pennylane import FermiC, FermiA - -a0_dag = FermiC(0) -a1 = FermiA(1) - -############################################################################## -# We used the compact notations ``a0_dag`` to denote a creation operator applied to -# the :math:`0\text{th}` orbital and ``a1`` to denote an annihilation operator applied to the -# :math:`1\text{st}` orbital. Once created, these operators can be multiplied or added to each other -# to create new operators. A product of fermionic operators will be called a *Fermi word* and a -# linear combination of Fermi words will be called a *Fermi sentence*. - -fermi_word = a0_dag * a1 -fermi_sentence = 1.3 * a0_dag * a1 + 2.4 * a1 -print(fermi_sentence) - -############################################################################## -# In this simple example, we first created the operator :math:`a^{\dagger}_0 a_1` and then created -# the linear combination :math:`1.3 a^{\dagger}_0 a_1 + 2.4 a_1.` We can also perform -# arithmetic operations between Fermi words and Fermi sentences. - -fermi_sentence = fermi_sentence * fermi_word + 2.3 * fermi_word -print(fermi_sentence) - -############################################################################## -# Beyond multiplication, summation, and subtraction, we can exponentiate fermionic operators in -# PennyLane to an integer power. For instance, we can create a more complicated operator -# -# .. math:: -# -# 1.2 \times a_0^{\dagger} + 0.5 \times a_1 - 2.3 \times \left ( a_0^{\dagger} a_1 \right )^2, -# -# in the same way that you would write down the operator on a piece of paper: - -fermi_sentence = 1.2 * a0_dag + 0.5 * a1 - 2.3 * (a0_dag * a1) ** 2 -print(fermi_sentence) - -############################################################################## -# This Fermi sentence can be mapped to the qubit basis using the -# `Jordan-Wigner `_ -# transformation to get a linear combination of Pauli operators. - -from pennylane import jordan_wigner - -pauli_sentence = jordan_wigner(fermi_sentence) -pauli_sentence - -############################################################################## -# Fermionic Hamiltonians -# ---------------------- -# Now that we have nice tools to create and manipulate fermionic operators, we can build some -# interesting fermionic Hamiltonians. -# -# A toy model -# ^^^^^^^^^^^ -# Our first example is a toy Hamiltonian inspired by the -# `Hückel method `_, which is a method for -# describing molecules with alternating single and double bonds. Our toy model is a simplified -# version of the Hückel Hamiltonian and assumes only two orbitals and a single electron. -# -# .. math:: -# -# H = \alpha \left (a^{\dagger}_0 a_0 + a^{\dagger}_1 a_1 \right ) + -# \beta \left (a^{\dagger}_0 a_1 + a^{\dagger}_1 a_0 \right ). -# -# This Hamiltonian can be constructed with pre-defined values :math:`\alpha = 0.01` and -# :math:`\beta = -0.02.` - -h1 = 0.01 * (FermiC(0) * FermiA(0) + FermiC(1) * FermiA(1)) -h2 = -0.02 * (FermiC(0) * FermiA(1) + FermiC(1) * FermiA(0)) -h = h1 + h2 -print(h) - -############################################################################## -# The fermionic Hamiltonian can be converted to the qubit Hamiltonian with: - -h = jordan_wigner(h) - -############################################################################## -# The matrix representation of the qubit Hamiltonian in the computational basis can be diagonalized -# to get its eigenpairs. - -import numpy as np - -val, vec = np.linalg.eigh(h.sparse_matrix().toarray()) -print(f"eigenvalues:\n{val}") -print() -print(f"eigenvectors:\n{np.real(vec.T)}") - -############################################################################## -# -# Hydrogen molecule -# ^^^^^^^^^^^^^^^^^ -# The `second quantized `_ molecular electronic -# Hamiltonian is usually constructed as -# -# .. math:: -# H = \sum_{\alpha \in \{\uparrow, \downarrow \} } \sum_{pq} c_{pq} a_{p,\alpha}^{\dagger} -# a_{q, \alpha} + \frac{1}{2} \sum_{\alpha, \beta \in \{\uparrow, \downarrow \} } \sum_{pqrs} -# c_{pqrs} a_{p, \alpha}^{\dagger} a_{q, \beta}^{\dagger} a_{r, \beta} a_{s, \alpha}, -# -# where :math:`\alpha` and :math:`\beta` denote the electron spin and :math:`p, q, r, s` are the -# orbital indices. The coefficients :math:`c` are integrals over -# molecular orbitals that are obtained from -# `Hartree-Fock `_ -# calculations. These integrals can be computed with PennyLane using the -# :func:`~.pennylane.qchem.electron_integrals` function. We can build the molecular Hamiltonian for -# the hydrogen molecule as an example. We first define the atom types and the atomic coordinates. - -import pennylane as qml -from jax import numpy as jnp - -symbols = ["H", "H"] -geometry = jnp.array([[-0.67294, 0.0, 0.0], [0.67294, 0.0, 0.0]]) - -############################################################################## -# Then we compute the one- and two-electron integrals, which are the coefficients :math:`c` in the -# second quantized molecular Hamiltonian defined above. We also obtain the core constant, which is -# later used to calculate the contribution of the nuclear energy to the Hamiltonian. - -mol = qml.qchem.Molecule(symbols, geometry) -core, one, two = qml.qchem.electron_integrals(mol)() - -############################################################################## -# These integrals are computed over molecular orbitals. Each molecular orbital contains a pair of -# electrons with different spins. We have assumed that the spatial distribution of these electron -# pairs is the same to simplify the calculation of the integrals. However, to properly account for -# all electrons, we need to duplicate the integrals for electrons with the same spin. For example, -# the :math:`pq` integral, which is the integral over the orbital :math:`p` and the orbital -# :math:`q,` can be used -# for both spin-up and spin-down electrons. Then, if we have a :math:`2 \times 2` matrix of such -# integrals, it will become a :math:`4 \times 4` matrix. The code block below simply extends the -# integrals by duplicating terms to account for both spin-up and spin-down electrons. - -for i in range(4): - if i < 2: - one = one.repeat(2, axis=i) - two = two.repeat(2, axis=i) - -############################################################################## -# We can now construct the fermionic Hamiltonian for the hydrogen molecule. The one-body terms, -# which are the first part in the Hamiltonian above, can be added first. We will use -# `itertools `_ to efficiently -# create all the combinations we need. Some of these combinations are not allowed because of spin -# restrictions and we need to exclude them. You can find more details about -# constructing a molecular Hamiltonian in reference [#surjan]_. - -import itertools - -n = one.shape[0] - -h = 0.0 - -for p, q in itertools.product(range(n), repeat=2): - if p % 2 == q % 2: # to account for spin-forbidden terms - h += one[p, q] * FermiC(p) * FermiA(q) - -############################################################################## -# The two-body terms can be added with: - -for p, q, r, s in itertools.product(range(n), repeat=4): - if p % 2 == s % 2 and q % 2 == r % 2: # to account for spin-forbidden terms - h += two[p, q, r, s] / 2 * FermiC(p) * FermiC(q) * FermiA(r) * FermiA(s) - -############################################################################## -# We then simplify the Hamiltonian to remove terms with negligible coefficients and then map it to -# the qubit basis. - -h.simplify() -h = jordan_wigner(h) - -############################################################################## -# We also need to include the contribution of the nuclear energy. - -h += np.sum(core * qml.Identity(0)) - -############################################################################## -# This gives us the qubit Hamiltonian which can be used as an input for quantum algorithms. We can -# also compute the ground-state energy by diagonalizing the matrix representation of the -# Hamiltonian in the computational basis. - -np.linalg.eigh(h.sparse_matrix().toarray())[0].min() - -############################################################################## -# Summary -# ------- -# This demo explains how to create and manipulate fermionic operators in PennyLane, which is as -# easy as writing the operators on paper. PennyLane supports several arithmetic operations between -# fermionic operators and provides tools for mapping them to the qubit basis. This makes it easy and -# intuitive to construct complicated fermionic Hamiltonians such as -# `molecular Hamiltonians `_. -# -# References -# ---------- -# -# .. [#surjan] -# -# Peter R. Surjan, "Second Quantized Approach to Quantum Chemistry". Springer-Verlag, 1989. -# -# -# About the author -# ---------------- -# +r""" + +Fermionic operators +=================== + +.. meta:: + :property="og:description": Learn how to work with fermionic operators + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_fermionic_operators.png + +.. related:: + tutorial_quantum_chemistry Building molecular Hamiltonians + tutorial_vqe A brief overview of VQE + +*Author: Soran Jahangiri — Posted: 27 June 2023. Last updated: 27 June 2023.* + +Fermionic creation and annihilation operators are commonly used to construct +`Hamiltonians `_ and other observables of molecules and spin +systems [#surjan]_. In this demo, you will learn how to use PennyLane to create fermionic operators +and map them to a qubit representation for use in quantum algorithms. + +Constructing fermionic operators +-------------------------------- +The fermionic `creation and annihilation `_ +operators can be constructed in PennyLane similarly to Pauli operators by using +:class:`~.pennylane.FermiC` and :class:`~.pennylane.FermiA` for creation and annihilation operators, +respectively. +""" + +from pennylane import FermiC, FermiA + +a0_dag = FermiC(0) +a1 = FermiA(1) + +############################################################################## +# We used the compact notations ``a0_dag`` to denote a creation operator applied to +# the :math:`0\text{th}` orbital and ``a1`` to denote an annihilation operator applied to the +# :math:`1\text{st}` orbital. Once created, these operators can be multiplied or added to each other +# to create new operators. A product of fermionic operators will be called a *Fermi word* and a +# linear combination of Fermi words will be called a *Fermi sentence*. + +fermi_word = a0_dag * a1 +fermi_sentence = 1.3 * a0_dag * a1 + 2.4 * a1 +print(fermi_sentence) + +############################################################################## +# In this simple example, we first created the operator :math:`a^{\dagger}_0 a_1` and then created +# the linear combination :math:`1.3 a^{\dagger}_0 a_1 + 2.4 a_1.` We can also perform +# arithmetic operations between Fermi words and Fermi sentences. + +fermi_sentence = fermi_sentence * fermi_word + 2.3 * fermi_word +print(fermi_sentence) + +############################################################################## +# Beyond multiplication, summation, and subtraction, we can exponentiate fermionic operators in +# PennyLane to an integer power. For instance, we can create a more complicated operator +# +# .. math:: +# +# 1.2 \times a_0^{\dagger} + 0.5 \times a_1 - 2.3 \times \left ( a_0^{\dagger} a_1 \right )^2, +# +# in the same way that you would write down the operator on a piece of paper: + +fermi_sentence = 1.2 * a0_dag + 0.5 * a1 - 2.3 * (a0_dag * a1) ** 2 +print(fermi_sentence) + +############################################################################## +# This Fermi sentence can be mapped to the qubit basis using the +# `Jordan-Wigner `_ +# transformation to get a linear combination of Pauli operators. + +from pennylane import jordan_wigner + +pauli_sentence = jordan_wigner(fermi_sentence) +pauli_sentence + +############################################################################## +# Fermionic Hamiltonians +# ---------------------- +# Now that we have nice tools to create and manipulate fermionic operators, we can build some +# interesting fermionic Hamiltonians. +# +# A toy model +# ^^^^^^^^^^^ +# Our first example is a toy Hamiltonian inspired by the +# `Hückel method `_, which is a method for +# describing molecules with alternating single and double bonds. Our toy model is a simplified +# version of the Hückel Hamiltonian and assumes only two orbitals and a single electron. +# +# .. math:: +# +# H = \alpha \left (a^{\dagger}_0 a_0 + a^{\dagger}_1 a_1 \right ) + +# \beta \left (a^{\dagger}_0 a_1 + a^{\dagger}_1 a_0 \right ). +# +# This Hamiltonian can be constructed with pre-defined values :math:`\alpha = 0.01` and +# :math:`\beta = -0.02.` + +h1 = 0.01 * (FermiC(0) * FermiA(0) + FermiC(1) * FermiA(1)) +h2 = -0.02 * (FermiC(0) * FermiA(1) + FermiC(1) * FermiA(0)) +h = h1 + h2 +print(h) + +############################################################################## +# The fermionic Hamiltonian can be converted to the qubit Hamiltonian with: + +h = jordan_wigner(h) + +############################################################################## +# The matrix representation of the qubit Hamiltonian in the computational basis can be diagonalized +# to get its eigenpairs. + +import numpy as np + +val, vec = np.linalg.eigh(h.sparse_matrix().toarray()) +print(f"eigenvalues:\n{val}") +print() +print(f"eigenvectors:\n{np.real(vec.T)}") + +############################################################################## +# +# Hydrogen molecule +# ^^^^^^^^^^^^^^^^^ +# The `second quantized `_ molecular electronic +# Hamiltonian is usually constructed as +# +# .. math:: +# H = \sum_{\alpha \in \{\uparrow, \downarrow \} } \sum_{pq} c_{pq} a_{p,\alpha}^{\dagger} +# a_{q, \alpha} + \frac{1}{2} \sum_{\alpha, \beta \in \{\uparrow, \downarrow \} } \sum_{pqrs} +# c_{pqrs} a_{p, \alpha}^{\dagger} a_{q, \beta}^{\dagger} a_{r, \beta} a_{s, \alpha}, +# +# where :math:`\alpha` and :math:`\beta` denote the electron spin and :math:`p, q, r, s` are the +# orbital indices. The coefficients :math:`c` are integrals over +# molecular orbitals that are obtained from +# `Hartree-Fock `_ +# calculations. These integrals can be computed with PennyLane using the +# :func:`~.pennylane.qchem.electron_integrals` function. We can build the molecular Hamiltonian for +# the hydrogen molecule as an example. We first define the atom types and the atomic coordinates. + +import pennylane as qml +from jax import numpy as jnp + +symbols = ["H", "H"] +geometry = jnp.array([[-0.67294, 0.0, 0.0], [0.67294, 0.0, 0.0]]) + +############################################################################## +# Then we compute the one- and two-electron integrals, which are the coefficients :math:`c` in the +# second quantized molecular Hamiltonian defined above. We also obtain the core constant, which is +# later used to calculate the contribution of the nuclear energy to the Hamiltonian. + +mol = qml.qchem.Molecule(symbols, geometry) +core, one, two = qml.qchem.electron_integrals(mol)() + +############################################################################## +# These integrals are computed over molecular orbitals. Each molecular orbital contains a pair of +# electrons with different spins. We have assumed that the spatial distribution of these electron +# pairs is the same to simplify the calculation of the integrals. However, to properly account for +# all electrons, we need to duplicate the integrals for electrons with the same spin. For example, +# the :math:`pq` integral, which is the integral over the orbital :math:`p` and the orbital +# :math:`q,` can be used +# for both spin-up and spin-down electrons. Then, if we have a :math:`2 \times 2` matrix of such +# integrals, it will become a :math:`4 \times 4` matrix. The code block below simply extends the +# integrals by duplicating terms to account for both spin-up and spin-down electrons. + +for i in range(4): + if i < 2: + one = one.repeat(2, axis=i) + two = two.repeat(2, axis=i) + +############################################################################## +# We can now construct the fermionic Hamiltonian for the hydrogen molecule. The one-body terms, +# which are the first part in the Hamiltonian above, can be added first. We will use +# `itertools `_ to efficiently +# create all the combinations we need. Some of these combinations are not allowed because of spin +# restrictions and we need to exclude them. You can find more details about +# constructing a molecular Hamiltonian in reference [#surjan]_. + +import itertools + +n = one.shape[0] + +h = 0.0 + +for p, q in itertools.product(range(n), repeat=2): + if p % 2 == q % 2: # to account for spin-forbidden terms + h += one[p, q] * FermiC(p) * FermiA(q) + +############################################################################## +# The two-body terms can be added with: + +for p, q, r, s in itertools.product(range(n), repeat=4): + if p % 2 == s % 2 and q % 2 == r % 2: # to account for spin-forbidden terms + h += two[p, q, r, s] / 2 * FermiC(p) * FermiC(q) * FermiA(r) * FermiA(s) + +############################################################################## +# We then simplify the Hamiltonian to remove terms with negligible coefficients and then map it to +# the qubit basis. + +h.simplify() +h = jordan_wigner(h) + +############################################################################## +# We also need to include the contribution of the nuclear energy. + +h += np.sum(core * qml.Identity(0)) + +############################################################################## +# This gives us the qubit Hamiltonian which can be used as an input for quantum algorithms. We can +# also compute the ground-state energy by diagonalizing the matrix representation of the +# Hamiltonian in the computational basis. + +np.linalg.eigh(h.sparse_matrix().toarray())[0].min() + +############################################################################## +# Summary +# ------- +# This demo explains how to create and manipulate fermionic operators in PennyLane, which is as +# easy as writing the operators on paper. PennyLane supports several arithmetic operations between +# fermionic operators and provides tools for mapping them to the qubit basis. This makes it easy and +# intuitive to construct complicated fermionic Hamiltonians such as +# `molecular Hamiltonians `_. +# +# References +# ---------- +# +# .. [#surjan] +# +# Peter R. Surjan, "Second Quantized Approach to Quantum Chemistry". Springer-Verlag, 1989. +# +# +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_givens_rotations/demo.py b/demonstrations_v2/tutorial_givens_rotations/demo.py index 9413006ffb..1c312bf402 100644 --- a/demonstrations_v2/tutorial_givens_rotations/demo.py +++ b/demonstrations_v2/tutorial_givens_rotations/demo.py @@ -1,506 +1,506 @@ -r""" - -Givens rotations for quantum chemistry -====================================== - -.. meta:: - :property="og:description": Discover the building blocks of quantum circuits for - quantum chemistry - - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/Givens_rotations.png - -.. related:: - tutorial_quantum_chemistry Building molecular Hamiltonians - tutorial_vqe A brief overview of VQE - - -*Author: Juan Miguel Arrazola — Posted: 30 June 2021. Last updated: 30 June 2021.* - -In the book `"Sophie's world" `_, the young -protagonist receives a white envelope containing a letter -with an intriguing question: "Why is Lego the most ingenious toy in the world?" At first baffled by -this curious message, she decides to reflect on the question. As told by the book's narrator, -she arrives at a conclusion: - -*The best thing about them was that with Lego she could construct any kind of object. And then -she could separate the blocks and construct something new. What more could one ask of a toy? -Sophie decided that Lego really could be called the most ingenious toy in the world.* - - - -.. figure:: ../_static/demonstration_assets/givens_rotations/lego-circuit.svg - :align: center - :width: 50% - - -In this tutorial, you will learn about the building blocks of quantum circuits for quantum -chemistry: Givens rotations. These are operations that can be used to construct any kind of -particle-conserving circuit. We discuss single and double excitation gates, which are particular -types of Givens rotations that play an important role in quantum chemistry. Notably, -controlled single excitation gates are universal for particle-conserving unitaries. You will also -learn how to use these gates to build arbitrary states of a fixed number of particles. - -Particle-conserving unitaries ------------------------------ - -Understanding the electronic structure of molecules is arguably the central problem in quantum chemistry. -Quantum computers tackle this problem by using systems of qubits to -represent the quantum states of the electrons. One method is to consider a -collection of `molecular orbitals `_, which -capture the three-dimensional region of space occupied by the electrons. Each orbital can be -occupied by at most two electrons, each with a different spin orientation. In this case we refer to -*spin orbitals* that can be occupied by a single electron. - -The state of electrons in a molecule can then be described by specifying how the -orbitals are occupied. The `Jordan-Wigner representation -`_ provides a -convenient way to do this: we associate a qubit with each spin orbital and -use its states to represent occupied :math:`|1\rangle` or unoccupied -:math:`|0\rangle` spin orbitals. - -An :math:`n`-qubit state with `Hamming weight `_ -:math:`k`, i.e., with :math:`k` qubits in state :math:`|1\rangle,` represents a state of -:math:`k` electrons in :math:`n` spin orbitals. For example :math:`|1010\rangle` is a state of -two electrons in two spin orbitals. More generally, superpositions over all basis states with a -fixed number of particles are valid states of the electrons in a molecule. These are states such as - -.. math:: - - |\psi\rangle = c_1|1100\rangle + c_2|1010\rangle + c_3|1001\rangle + c_4|0110\rangle + - c_5|0101\rangle + c_6|0011\rangle, - -for some coefficients :math:`c_i.` - -.. figure:: ../_static/demonstration_assets/givens_rotations/orbitals_states.png - :align: center - :width: 50% - - States of a system with six spin orbitals and three electrons. Orbitals are for illustration; - they correspond to carbon dioxide, which has more electrons and orbitals. - -Because the number of electrons in a molecule is -fixed, any transformation must conserve the number of particles. We refer to these as -**particle-conserving unitaries**. When designing quantum circuits and algorithms for quantum -chemistry, it is desirable to employ only particle-conserving gates that guarantee that the -states of the system remain valid. This raises the questions: what are the simplest -particle-conserving unitaries? Like Legos, how can they be used to construct any quantum circuit for -quantum chemistry applications? - -Givens rotations ----------------- - -Consider single-qubit gates. In their most general form, they perform the transformation - -.. math:: - - U|0\rangle &= a |0\rangle + b |1\rangle,\\ - U|1\rangle &= c |1\rangle + d |0\rangle, - -where :math:`|a|^2+|b|^2=|c|^2+|d|^2=1` and :math:`ab^* + cd^*=0.` This gate is -particle-conserving only if :math:`b=d=0,` which means that the only single-qubit gates that -preserve particle number are diagonal gates of the form - -.. math:: - - U = \begin{pmatrix} - e^{i\theta} & 0\\ - 0 & e^{i\phi} - \end{pmatrix}. - -On their own, these gates are not very interesting. They can only be used to change the -relative phases of states in a superposition; they cannot be used to create and control such -superpositions. So let's take a look at two-qubit gates. - -Basis states of two qubits can be categorized depending on -their number of particles. - -We have: - -- :math:`|00\rangle` with zero particles, -- :math:`|01\rangle,|10\rangle` with one particle, and -- :math:`|11\rangle` with two particles. - -We can now consider transformations that couple the states :math:`|01\rangle,|10\rangle.` These -are gates of the form - -.. math:: - - U|01\rangle &= a |01\rangle + b |10\rangle\\ - U|10\rangle &= c |10\rangle + d |01\rangle. - -This should be familiar: the unitary has the same form as a single-qubit gate, except that the -states :math:`|01\rangle, |10\rangle` respectively take the place of :math:`|0\rangle, -|1\rangle`. This correspondence has a name: the `dual-rail qubit -`_, where a two-level system is constructed -by specifying in which of two possible systems a single particle is located. The -difference compared to single-qubit gates is that any values of the parameters :math:`a, -b,c,d` give rise to a valid particle-conserving unitary. Take for instance the two-qubit gate - -.. math:: - - G(\theta)=\begin{pmatrix} - 1 & 0 & 0 & 0\\ - 0 & \cos (\theta/2) & -\sin (\theta/2) & 0\\ - 0 & \sin(\theta/2) & \cos(\theta/2) & 0\\ - 0 & 0 & 0 & 1 - \end{pmatrix}. - - -This is an example of a `Givens rotation `_: a -rotation in a two-dimensional subspace of a larger Hilbert space. In this case, we are performing a -Givens rotation in a two-dimensional subspace of the four-dimensional space of two-qubit states. -This gate allows us to create superpositions by exchanging the particle -between the two qubits. Such transformations can be interpreted as a **single excitation**, -where we view the exchange from :math:`|10\rangle` to :math:`|01\rangle` as exciting the electron -from the first to the second qubit. - -.. figure:: ../_static/demonstration_assets/givens_rotations/Givens_rotations_1.png - :align: center - :width: 35% - - A Givens rotation can be used to couple states that differ by a single excitation. - -This gate is implemented in PennyLane as the :class:`~.pennylane.SingleExcitation` operation. -We can use it to prepare an equal superposition of three-qubit states with a single particle -:math:`\frac{1}{\sqrt{3}}(|001\rangle + |010\rangle + |100\rangle).` We apply two single -excitation gates with parameters :math:`\theta, \phi` to the initial state :math:`|100\rangle.` -The excitation gates act on qubits 0,1 and 0,2 respectively. This yields the state - -.. math:: - - |\psi\rangle = \cos(\theta/2)\cos(\phi/2)|100\rangle - \sin(\theta/2)|010\rangle - - \cos(\theta/2)\sin(\phi/2)|001\rangle. - -Since the amplitude of :math:`|010\rangle` must be :math:`1/\sqrt{3},` we conclude that -:math:`-\sin(\theta/2)=1/\sqrt{3}.` This in turn implies that :math:`\cos(\theta/2)=\sqrt{2/3}` and -therefore :math:`-\sin(\phi/2)=1/\sqrt{2}.` Thus, to prepare an equal superposition state we -choose the angles of rotation to be - -.. math:: - - \theta &= - 2 \arcsin(1/\sqrt{3}),\\ - \phi &= - 2 \arcsin(1/\sqrt{2}). - -This can be implemented in PennyLane as follows: -""" - -import pennylane as qml -from jax import numpy as jnp -import jax - -jax.config.update("jax_enable_x64", True) -dev = qml.device('lightning.qubit', wires=3) - -@qml.qnode(dev, interface="jax") -def circuit(x, y): - # prepares the reference state |100> - qml.BasisState(jnp.array([1, 0, 0]), wires=[0, 1, 2]) - # applies the single excitations - qml.SingleExcitation(x, wires=[0, 1]) - qml.SingleExcitation(y, wires=[0, 2]) - return qml.state() - -x = -2 * jnp.arcsin(jnp.sqrt(1/3)) -y = -2 * jnp.arcsin(jnp.sqrt(1/2)) -print(circuit(x, y)) - -############################################################################## -# The components of the output state are ordered according to their binary -# representation, so entry 1 is :math:`|001\rangle`, entry 2 is :math:`|010\rangle,` and entry 4 is -# :math:`|100\rangle,` meaning we indeed prepared the desired state. We can check this by -# reshaping the output state - -tensor_state = circuit(x, y).reshape(2, 2, 2) -print("Amplitude of state |001> = ", tensor_state[0, 0, 1]) -print("Amplitude of state |010> = ", tensor_state[0, 1, 0]) -print("Amplitude of state |100> = ", tensor_state[1, 0, 0]) - -############################################################################## -# We can also study **double excitations** involving the transfer of two particles. For example, -# consider a Givens rotation in the subspace spanned by the states -# :math:`|1100\rangle` and :math:`|0011\rangle.` These -# states differ by a double excitation since we can map :math:`|1100\rangle` to -# :math:`|0011\rangle` by exciting the particles from the first two qubits to the last two. -# Mathematically, this gate can be represented by a unitary :math:`G^{(2)}(\theta)` that performs -# the mapping -# -# .. math:: -# -# G^{(2)}|0011\rangle &= \cos (\theta/2)|0011\rangle + \sin (\theta/2)|1100\rangle\\ -# G^{(2)}|1100\rangle &= \cos (\theta/2)|1100\rangle - \sin (\theta/2)|0011\rangle, -# -# while leaving all other basis states unchanged. This gate is implemented in PennyLane as the -# :class:`~.pennylane.DoubleExcitation` operation. -# -# -# .. figure:: ../_static/demonstration_assets/givens_rotations/Givens_rotations_2.png -# :align: center -# :width: 35% -# -# A Givens rotation can also be used to couple states that differ by a double excitation. -# -# -# In the context of quantum chemistry, it is common to consider excitations on a fixed reference -# state and include only the excitations that preserve the spin orientation of the electron. -# For a system with :math:`n` qubits and :math:`k` particles, this reference state is typically -# chosen as the state with the first :math:`k` qubits in state :math:`|1\rangle,` and the remainder -# in state :math:`|0\rangle.` -# PennyLane allows you to obtain all such excitations using the function -# :func:`~.pennylane.qchem.excitations`. Let's employ it to build a circuit that includes -# all single and double excitations acting on a reference state of three particles in six qubits. -# We apply a random rotation for each gate: - -nr_particles = 3 -nr_qubits = 6 - -singles, doubles = qml.qchem.excitations(3, 6) -print(f"Single excitations = {singles}") -print(f"Double excitations = {doubles}") - -############################################################################## -# Now we continue to build the circuit: - -from jax import random - -dev2 = qml.device('lightning.qubit', wires=6) - -@qml.qnode(dev2, interface="jax") -def circuit2(x, y): - # prepares reference state - qml.BasisState(jnp.array([1, 1, 1, 0, 0, 0]), wires=[0, 1, 2, 3, 4, 5]) - # apply all single excitations - for i, s in enumerate(singles): - qml.SingleExcitation(x[i], wires=s) - # apply all double excitations - for j, d in enumerate(doubles): - qml.DoubleExcitation(y[j], wires=d) - return qml.state() - -# random angles of rotation -key = random.PRNGKey(0) -key_x, key_y = random.split(key) -x = random.normal(key_x, shape=(len(singles),)) -y = random.normal(key_y, shape=(len(singles),)) - -output = circuit2(x, y) - -############################################################################## -# We can check which basis states appear in the resulting superposition to confirm that they -# involve only states with three particles. - -# constructs binary representation of states with non-zero amplitude -import numpy as np - -states = [np.binary_repr(i, width=6) for i in range(len(output)) if output[i] != 0] -print(states) - -############################################################################## -# Besides these Givens rotations, there are other versions that have been -# reported in the literature and used to construct circuits for quantum chemistry. For instance, -# Ref. [#anselmetti]_ considers a different sign convention for single-excitation gates, -# -# .. math:: -# G(\theta)=\begin{pmatrix} -# 1 & 0 & 0 & 0\\ -# 0 & \cos (\theta/2) & \sin (\theta/2) & 0\\ -# 0 & -\sin(\theta/2) & \cos(\theta/2) & 0\\ -# 0 & 0 & 0 & 1 -# \end{pmatrix}, -# -# and Ref. [#barkoutsos]_ introduces the particle-conserving gates listed below, which are all -# Givens rotations -# -# .. math:: -# U_1(\theta, \phi) &= \begin{pmatrix} -# 1 & 0 & 0 & 0\\ -# 0 & \cos (\theta) & e^{i\phi}\sin (\theta) & 0\\ -# 0 & e^{-i\phi}\sin(\theta) & -\cos(\theta) & 0\\ -# 0 & 0 & 0 & 1 -# \end{pmatrix},\\ -# -# U_2(\theta) &= \begin{pmatrix} -# 1 & 0 & 0 & 0\\ -# 0 & \cos (2\theta) & -i\sin (2\theta) & 0\\ -# 0 & -i\sin(2\theta) & \cos(2\theta) & 0\\ -# 0 & 0 & 0 & 1 -# \end{pmatrix}. -# -# Givens rotations are a powerful abstraction for understanding -# quantum circuits for quantum chemistry. Instead of thinking of single-qubit gates and CNOTs as the -# building-blocks of quantum circuits, we can be more clever and select two-dimensional subspaces -# spanned by states with an equal number of particles, and use Givens rotations in that subspace -# to construct the circuits. 🧠 -# -# Controlled excitation gates -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# -# Single-qubit gates and CNOT gates are universal for quantum -# computing: they can be used to implement any conceivable quantum computation. If Givens -# rotations are analogous to single-qubit gates, then **controlled** Givens rotations are -# analogous to two-qubit gates. In universality constructions, the ability to control operations -# based on the states of other qubits is essential, so for this reason it's natural to study -# controlled Givens rotations. The simplest of these are controlled single-excitation gates, -# which are three-qubit gates that perform the mapping -# -# .. math:: -# CG(\theta) |101\rangle &= \cos (\theta/2)|101\rangle + \sin (\theta/2)|110\rangle,\\ -# CG(\theta) |110\rangle &= \cos (\theta/2)|110\rangle - \sin (\theta/2)|101\rangle, -# -# while leaving all other basis states unchanged. This gate only excites a particle -# from the second to third qubit, and vice versa, if the first (control) qubit is in state -# :math:`|1\rangle.` This is a useful property: as the name suggests, it provides us with better -# control over the transformations we want to apply. Suppose we aim to prepare the state -# -# .. math:: -# |\psi\rangle = \frac{1}{2}(|110000\rangle + |001100\rangle + |000011\rangle + |100100\rangle). -# -# Some inspection is enough to see that the states :math:`|001100\rangle` and :math:`|000011\rangle` -# differ by a double excitation from the reference state :math:`|110000\rangle.` Meanwhile, the state -# :math:`|100100\rangle` differs by a single excitation. It is thus tempting to think that applying -# two double-excitation gates and a single-excitation gate can be used to prepare the target state. -# It won't work! Applying the single-excitation gate on qubits 1 and 3 will also lead to an -# undesired contribution for the state :math:`|011000\rangle` through a coupling with -# :math:`|001100\rangle.` Let's check that this is the case: - -dev = qml.device('default.qubit', wires=6) - -@qml.qnode(dev, interface="jax") -def circuit3(x, y, z): - qml.BasisState(jnp.array([1, 1, 0, 0, 0, 0]), wires=[i for i in range(6)]) - qml.DoubleExcitation(x, wires=[0, 1, 2, 3]) - qml.DoubleExcitation(y, wires=[0, 1, 4, 5]) - qml.SingleExcitation(z, wires=[1, 3]) - return qml.state() - -x = -2 * jnp.arcsin(jnp.sqrt(1/4)) -y = -2 * jnp.arcsin(jnp.sqrt(1/3)) -z = -2 * jnp.arcsin(jnp.sqrt(1/2)) - -output = circuit3(x, y, z) -states = [np.binary_repr(i, width=6) for i in range(len(output)) if output[i] != 0] -print(states) - -############################################################################## -# Indeed, we have a non-zero coefficient for :math:`|011000\rangle.` To address this problem, -# we can instead apply the single-excitation gate controlled on the -# state of the first qubit. This ensures that there is no coupling with the state :math:`| -# 001100\rangle` since here the first qubit is in state :math:`|0\rangle.` Let's implement the circuit -# above, this time controlling on the state of the first qubit and verify that we can prepare the -# desired state. To perform the control, we use the :func:`~.pennylane.ctrl` transform: - -@qml.qnode(dev, interface="jax") -def circuit4(x, y, z): - qml.BasisState(jnp.array([1, 1, 0, 0, 0, 0]), wires=[i for i in range(6)]) - qml.DoubleExcitation(x, wires=[0, 1, 2, 3]) - qml.DoubleExcitation(y, wires=[0, 1, 4, 5]) - # single excitation controlled on qubit 0 - qml.ctrl(qml.SingleExcitation, control=0)(z, wires=[1, 3]) - return qml.state() - -output = circuit4(x, y, z) -states = [np.binary_repr(i, width=6) for i in range(len(output)) if output[i] != 0] -print(states) - -############################################################################## -# It was proven in Ref. [#arrazola]_ that controlled single-excitation gates are universal for -# particle-conserving unitaries. With enough ingenuity, you can use these operations to construct -# any kind of circuit for quantum chemistry applications. What more could you ask from a gate? -# -# State preparation -# ----------------- -# -# We can bring all these pieces together and implement a circuit capable of preparing -# four-qubit states of two particles with real coefficients. The main idea is that we can -# perform the construction one basis state at a time by applying a suitable excitation gate, -# which may need to be controlled. -# -# -# .. figure:: ../_static/demonstration_assets/givens_rotations/circuit.png -# :align: center -# :width: 70% -# -# A circuit for preparing four-qubit states with two particles. -# -# -# Starting from the reference state :math:`|1100\rangle,` we create a superposition -# with the state :math:`|1010\rangle` by applying a single-excitation gate on qubits 1 and 2. -# Similarly, we create a superposition with the state :math:`|1001\rangle` with a single -# excitation between qubits 1 and 3. This leaves us with a state of the form -# -# .. math:: -# |\psi\rangle = a |1100\rangle + b |1010\rangle + c |1001\rangle. -# -# We can now perform two single excitations from qubit 0 to qubits 2 and 3. These will have -# to be controlled on the state of qubit 1. Finally, applying a double-excitation gate on all qubits -# can create a superposition of the form -# -# .. math:: -# -# |\psi\rangle = c_1|1100\rangle + c_2|1010\rangle + c_3|1001\rangle + c_4|0110\rangle + -# c_5|0101\rangle + c_6|0011\rangle, -# -# which is our intended outcome. Let's use this approach to create an equal superposition over -# all two-particle states on four qubits. We follow the same strategy as before, setting the angle -# of the :math:`k`-th Givens rotation as :math:`-2 \arcsin(1/\sqrt{n-k}),` where :math:`n` is the -# number of basis states in the superposition. - -dev = qml.device('default.qubit', wires=4) - -@qml.qnode(dev, interface="jax") -def state_preparation(params): - qml.BasisState(jnp.array([1, 1, 0, 0]), wires=[0, 1, 2, 3]) - qml.SingleExcitation(params[0], wires=[1, 2]) - qml.SingleExcitation(params[1], wires=[1, 3]) - # single excitations controlled on qubit 1 - qml.ctrl(qml.SingleExcitation, control=1)(params[2], wires=[0, 2]) - qml.ctrl(qml.SingleExcitation, control=1)(params[3], wires=[0, 3]) - qml.DoubleExcitation(params[4], wires=[0, 1, 2, 3]) - return qml.state() - -n = 6 -params = jnp.array([-2 * jnp.arcsin(1/jnp.sqrt(n-i)) for i in range(n-1)]) - -output = state_preparation(params) -# sets very small coefficients to zero -output = jnp.where(jnp.abs(output) < 1e-10, 0.0, output) -states = [np.binary_repr(i, width=4) for i in range(len(output)) if output[i] != 0] -print("Basis states = ", states) -print("Output state =", output) - -############################################################################## -# Success! This is the equal superposition state we wanted to prepare. 🚀 -# -# When it comes to quantum circuits for quantum chemistry, a wide variety of -# architectures have been proposed. Researchers in the field are faced with the apparent -# choice of making a selection among these circuits to conduct their computations and benchmark new -# algorithms. Like a kid in a toy store, it is challenging to pick just one. -# -# Ultimately, the aim of this tutorial is to provide you with the conceptual and software tools -# to implement any of these proposed circuits, and **also to design your own**. It's not only fun -# to play with toys; it's also fun to build them. -# -# -# References -# ---------- -# -# .. [#anselmetti] -# -# G-L. R. Anselmetti, D. Wierichs, C. Gogolin, Christian, and R. M. Parrish, -# "Local, Expressive, Quantum-Number-Preserving VQE Ansatze -# for Fermionic Systems", arXiv:2104.05695, (2021). -# -# .. [#barkoutsos] -# -# P. KL. Barkoutsos, Panagiotis, et al., "Quantum algorithms for electronic structure -# calculations: Particle-hole Hamiltonian and optimized wave-function expansions", Physical -# Review A 98(2), 022322, (2018). -# -# .. [#arrazola] -# -# J. M. Arrazola, O. Di Matteo, N. Quesada, S. Jahangiri, A. Delgado, N. Killoran, "Universal -# quantum circuits for quantum chemistry", arXiv:2106.13839, (2021) -# -# -# About the author -# ---------------- -# +r""" + +Givens rotations for quantum chemistry +====================================== + +.. meta:: + :property="og:description": Discover the building blocks of quantum circuits for + quantum chemistry + + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/Givens_rotations.png + +.. related:: + tutorial_quantum_chemistry Building molecular Hamiltonians + tutorial_vqe A brief overview of VQE + + +*Author: Juan Miguel Arrazola — Posted: 30 June 2021. Last updated: 30 June 2021.* + +In the book `"Sophie's world" `_, the young +protagonist receives a white envelope containing a letter +with an intriguing question: "Why is Lego the most ingenious toy in the world?" At first baffled by +this curious message, she decides to reflect on the question. As told by the book's narrator, +she arrives at a conclusion: + +*The best thing about them was that with Lego she could construct any kind of object. And then +she could separate the blocks and construct something new. What more could one ask of a toy? +Sophie decided that Lego really could be called the most ingenious toy in the world.* + + + +.. figure:: ../_static/demonstration_assets/givens_rotations/lego-circuit.svg + :align: center + :width: 50% + + +In this tutorial, you will learn about the building blocks of quantum circuits for quantum +chemistry: Givens rotations. These are operations that can be used to construct any kind of +particle-conserving circuit. We discuss single and double excitation gates, which are particular +types of Givens rotations that play an important role in quantum chemistry. Notably, +controlled single excitation gates are universal for particle-conserving unitaries. You will also +learn how to use these gates to build arbitrary states of a fixed number of particles. + +Particle-conserving unitaries +----------------------------- + +Understanding the electronic structure of molecules is arguably the central problem in quantum chemistry. +Quantum computers tackle this problem by using systems of qubits to +represent the quantum states of the electrons. One method is to consider a +collection of `molecular orbitals `_, which +capture the three-dimensional region of space occupied by the electrons. Each orbital can be +occupied by at most two electrons, each with a different spin orientation. In this case we refer to +*spin orbitals* that can be occupied by a single electron. + +The state of electrons in a molecule can then be described by specifying how the +orbitals are occupied. The `Jordan-Wigner representation +`_ provides a +convenient way to do this: we associate a qubit with each spin orbital and +use its states to represent occupied :math:`|1\rangle` or unoccupied +:math:`|0\rangle` spin orbitals. + +An :math:`n`-qubit state with `Hamming weight `_ +:math:`k`, i.e., with :math:`k` qubits in state :math:`|1\rangle,` represents a state of +:math:`k` electrons in :math:`n` spin orbitals. For example :math:`|1010\rangle` is a state of +two electrons in two spin orbitals. More generally, superpositions over all basis states with a +fixed number of particles are valid states of the electrons in a molecule. These are states such as + +.. math:: + + |\psi\rangle = c_1|1100\rangle + c_2|1010\rangle + c_3|1001\rangle + c_4|0110\rangle + + c_5|0101\rangle + c_6|0011\rangle, + +for some coefficients :math:`c_i.` + +.. figure:: ../_static/demonstration_assets/givens_rotations/orbitals_states.png + :align: center + :width: 50% + + States of a system with six spin orbitals and three electrons. Orbitals are for illustration; + they correspond to carbon dioxide, which has more electrons and orbitals. + +Because the number of electrons in a molecule is +fixed, any transformation must conserve the number of particles. We refer to these as +**particle-conserving unitaries**. When designing quantum circuits and algorithms for quantum +chemistry, it is desirable to employ only particle-conserving gates that guarantee that the +states of the system remain valid. This raises the questions: what are the simplest +particle-conserving unitaries? Like Legos, how can they be used to construct any quantum circuit for +quantum chemistry applications? + +Givens rotations +---------------- + +Consider single-qubit gates. In their most general form, they perform the transformation + +.. math:: + + U|0\rangle &= a |0\rangle + b |1\rangle,\\ + U|1\rangle &= c |1\rangle + d |0\rangle, + +where :math:`|a|^2+|b|^2=|c|^2+|d|^2=1` and :math:`ab^* + cd^*=0.` This gate is +particle-conserving only if :math:`b=d=0,` which means that the only single-qubit gates that +preserve particle number are diagonal gates of the form + +.. math:: + + U = \begin{pmatrix} + e^{i\theta} & 0\\ + 0 & e^{i\phi} + \end{pmatrix}. + +On their own, these gates are not very interesting. They can only be used to change the +relative phases of states in a superposition; they cannot be used to create and control such +superpositions. So let's take a look at two-qubit gates. + +Basis states of two qubits can be categorized depending on +their number of particles. + +We have: + +- :math:`|00\rangle` with zero particles, +- :math:`|01\rangle,|10\rangle` with one particle, and +- :math:`|11\rangle` with two particles. + +We can now consider transformations that couple the states :math:`|01\rangle,|10\rangle.` These +are gates of the form + +.. math:: + + U|01\rangle &= a |01\rangle + b |10\rangle\\ + U|10\rangle &= c |10\rangle + d |01\rangle. + +This should be familiar: the unitary has the same form as a single-qubit gate, except that the +states :math:`|01\rangle, |10\rangle` respectively take the place of :math:`|0\rangle, +|1\rangle`. This correspondence has a name: the `dual-rail qubit +`_, where a two-level system is constructed +by specifying in which of two possible systems a single particle is located. The +difference compared to single-qubit gates is that any values of the parameters :math:`a, +b,c,d` give rise to a valid particle-conserving unitary. Take for instance the two-qubit gate + +.. math:: + + G(\theta)=\begin{pmatrix} + 1 & 0 & 0 & 0\\ + 0 & \cos (\theta/2) & -\sin (\theta/2) & 0\\ + 0 & \sin(\theta/2) & \cos(\theta/2) & 0\\ + 0 & 0 & 0 & 1 + \end{pmatrix}. + + +This is an example of a `Givens rotation `_: a +rotation in a two-dimensional subspace of a larger Hilbert space. In this case, we are performing a +Givens rotation in a two-dimensional subspace of the four-dimensional space of two-qubit states. +This gate allows us to create superpositions by exchanging the particle +between the two qubits. Such transformations can be interpreted as a **single excitation**, +where we view the exchange from :math:`|10\rangle` to :math:`|01\rangle` as exciting the electron +from the first to the second qubit. + +.. figure:: ../_static/demonstration_assets/givens_rotations/Givens_rotations_1.png + :align: center + :width: 35% + + A Givens rotation can be used to couple states that differ by a single excitation. + +This gate is implemented in PennyLane as the :class:`~.pennylane.SingleExcitation` operation. +We can use it to prepare an equal superposition of three-qubit states with a single particle +:math:`\frac{1}{\sqrt{3}}(|001\rangle + |010\rangle + |100\rangle).` We apply two single +excitation gates with parameters :math:`\theta, \phi` to the initial state :math:`|100\rangle.` +The excitation gates act on qubits 0,1 and 0,2 respectively. This yields the state + +.. math:: + + |\psi\rangle = \cos(\theta/2)\cos(\phi/2)|100\rangle - \sin(\theta/2)|010\rangle - + \cos(\theta/2)\sin(\phi/2)|001\rangle. + +Since the amplitude of :math:`|010\rangle` must be :math:`1/\sqrt{3},` we conclude that +:math:`-\sin(\theta/2)=1/\sqrt{3}.` This in turn implies that :math:`\cos(\theta/2)=\sqrt{2/3}` and +therefore :math:`-\sin(\phi/2)=1/\sqrt{2}.` Thus, to prepare an equal superposition state we +choose the angles of rotation to be + +.. math:: + + \theta &= - 2 \arcsin(1/\sqrt{3}),\\ + \phi &= - 2 \arcsin(1/\sqrt{2}). + +This can be implemented in PennyLane as follows: +""" + +import pennylane as qml +from jax import numpy as jnp +import jax + +jax.config.update("jax_enable_x64", True) +dev = qml.device('lightning.qubit', wires=3) + +@qml.qnode(dev, interface="jax") +def circuit(x, y): + # prepares the reference state |100> + qml.BasisState(jnp.array([1, 0, 0]), wires=[0, 1, 2]) + # applies the single excitations + qml.SingleExcitation(x, wires=[0, 1]) + qml.SingleExcitation(y, wires=[0, 2]) + return qml.state() + +x = -2 * jnp.arcsin(jnp.sqrt(1/3)) +y = -2 * jnp.arcsin(jnp.sqrt(1/2)) +print(circuit(x, y)) + +############################################################################## +# The components of the output state are ordered according to their binary +# representation, so entry 1 is :math:`|001\rangle`, entry 2 is :math:`|010\rangle,` and entry 4 is +# :math:`|100\rangle,` meaning we indeed prepared the desired state. We can check this by +# reshaping the output state + +tensor_state = circuit(x, y).reshape(2, 2, 2) +print("Amplitude of state |001> = ", tensor_state[0, 0, 1]) +print("Amplitude of state |010> = ", tensor_state[0, 1, 0]) +print("Amplitude of state |100> = ", tensor_state[1, 0, 0]) + +############################################################################## +# We can also study **double excitations** involving the transfer of two particles. For example, +# consider a Givens rotation in the subspace spanned by the states +# :math:`|1100\rangle` and :math:`|0011\rangle.` These +# states differ by a double excitation since we can map :math:`|1100\rangle` to +# :math:`|0011\rangle` by exciting the particles from the first two qubits to the last two. +# Mathematically, this gate can be represented by a unitary :math:`G^{(2)}(\theta)` that performs +# the mapping +# +# .. math:: +# +# G^{(2)}|0011\rangle &= \cos (\theta/2)|0011\rangle + \sin (\theta/2)|1100\rangle\\ +# G^{(2)}|1100\rangle &= \cos (\theta/2)|1100\rangle - \sin (\theta/2)|0011\rangle, +# +# while leaving all other basis states unchanged. This gate is implemented in PennyLane as the +# :class:`~.pennylane.DoubleExcitation` operation. +# +# +# .. figure:: ../_static/demonstration_assets/givens_rotations/Givens_rotations_2.png +# :align: center +# :width: 35% +# +# A Givens rotation can also be used to couple states that differ by a double excitation. +# +# +# In the context of quantum chemistry, it is common to consider excitations on a fixed reference +# state and include only the excitations that preserve the spin orientation of the electron. +# For a system with :math:`n` qubits and :math:`k` particles, this reference state is typically +# chosen as the state with the first :math:`k` qubits in state :math:`|1\rangle,` and the remainder +# in state :math:`|0\rangle.` +# PennyLane allows you to obtain all such excitations using the function +# :func:`~.pennylane.qchem.excitations`. Let's employ it to build a circuit that includes +# all single and double excitations acting on a reference state of three particles in six qubits. +# We apply a random rotation for each gate: + +nr_particles = 3 +nr_qubits = 6 + +singles, doubles = qml.qchem.excitations(3, 6) +print(f"Single excitations = {singles}") +print(f"Double excitations = {doubles}") + +############################################################################## +# Now we continue to build the circuit: + +from jax import random + +dev2 = qml.device('lightning.qubit', wires=6) + +@qml.qnode(dev2, interface="jax") +def circuit2(x, y): + # prepares reference state + qml.BasisState(jnp.array([1, 1, 1, 0, 0, 0]), wires=[0, 1, 2, 3, 4, 5]) + # apply all single excitations + for i, s in enumerate(singles): + qml.SingleExcitation(x[i], wires=s) + # apply all double excitations + for j, d in enumerate(doubles): + qml.DoubleExcitation(y[j], wires=d) + return qml.state() + +# random angles of rotation +key = random.PRNGKey(0) +key_x, key_y = random.split(key) +x = random.normal(key_x, shape=(len(singles),)) +y = random.normal(key_y, shape=(len(singles),)) + +output = circuit2(x, y) + +############################################################################## +# We can check which basis states appear in the resulting superposition to confirm that they +# involve only states with three particles. + +# constructs binary representation of states with non-zero amplitude +import numpy as np + +states = [np.binary_repr(i, width=6) for i in range(len(output)) if output[i] != 0] +print(states) + +############################################################################## +# Besides these Givens rotations, there are other versions that have been +# reported in the literature and used to construct circuits for quantum chemistry. For instance, +# Ref. [#anselmetti]_ considers a different sign convention for single-excitation gates, +# +# .. math:: +# G(\theta)=\begin{pmatrix} +# 1 & 0 & 0 & 0\\ +# 0 & \cos (\theta/2) & \sin (\theta/2) & 0\\ +# 0 & -\sin(\theta/2) & \cos(\theta/2) & 0\\ +# 0 & 0 & 0 & 1 +# \end{pmatrix}, +# +# and Ref. [#barkoutsos]_ introduces the particle-conserving gates listed below, which are all +# Givens rotations +# +# .. math:: +# U_1(\theta, \phi) &= \begin{pmatrix} +# 1 & 0 & 0 & 0\\ +# 0 & \cos (\theta) & e^{i\phi}\sin (\theta) & 0\\ +# 0 & e^{-i\phi}\sin(\theta) & -\cos(\theta) & 0\\ +# 0 & 0 & 0 & 1 +# \end{pmatrix},\\ +# +# U_2(\theta) &= \begin{pmatrix} +# 1 & 0 & 0 & 0\\ +# 0 & \cos (2\theta) & -i\sin (2\theta) & 0\\ +# 0 & -i\sin(2\theta) & \cos(2\theta) & 0\\ +# 0 & 0 & 0 & 1 +# \end{pmatrix}. +# +# Givens rotations are a powerful abstraction for understanding +# quantum circuits for quantum chemistry. Instead of thinking of single-qubit gates and CNOTs as the +# building-blocks of quantum circuits, we can be more clever and select two-dimensional subspaces +# spanned by states with an equal number of particles, and use Givens rotations in that subspace +# to construct the circuits. 🧠 +# +# Controlled excitation gates +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Single-qubit gates and CNOT gates are universal for quantum +# computing: they can be used to implement any conceivable quantum computation. If Givens +# rotations are analogous to single-qubit gates, then **controlled** Givens rotations are +# analogous to two-qubit gates. In universality constructions, the ability to control operations +# based on the states of other qubits is essential, so for this reason it's natural to study +# controlled Givens rotations. The simplest of these are controlled single-excitation gates, +# which are three-qubit gates that perform the mapping +# +# .. math:: +# CG(\theta) |101\rangle &= \cos (\theta/2)|101\rangle + \sin (\theta/2)|110\rangle,\\ +# CG(\theta) |110\rangle &= \cos (\theta/2)|110\rangle - \sin (\theta/2)|101\rangle, +# +# while leaving all other basis states unchanged. This gate only excites a particle +# from the second to third qubit, and vice versa, if the first (control) qubit is in state +# :math:`|1\rangle.` This is a useful property: as the name suggests, it provides us with better +# control over the transformations we want to apply. Suppose we aim to prepare the state +# +# .. math:: +# |\psi\rangle = \frac{1}{2}(|110000\rangle + |001100\rangle + |000011\rangle + |100100\rangle). +# +# Some inspection is enough to see that the states :math:`|001100\rangle` and :math:`|000011\rangle` +# differ by a double excitation from the reference state :math:`|110000\rangle.` Meanwhile, the state +# :math:`|100100\rangle` differs by a single excitation. It is thus tempting to think that applying +# two double-excitation gates and a single-excitation gate can be used to prepare the target state. +# It won't work! Applying the single-excitation gate on qubits 1 and 3 will also lead to an +# undesired contribution for the state :math:`|011000\rangle` through a coupling with +# :math:`|001100\rangle.` Let's check that this is the case: + +dev = qml.device('default.qubit', wires=6) + +@qml.qnode(dev, interface="jax") +def circuit3(x, y, z): + qml.BasisState(jnp.array([1, 1, 0, 0, 0, 0]), wires=[i for i in range(6)]) + qml.DoubleExcitation(x, wires=[0, 1, 2, 3]) + qml.DoubleExcitation(y, wires=[0, 1, 4, 5]) + qml.SingleExcitation(z, wires=[1, 3]) + return qml.state() + +x = -2 * jnp.arcsin(jnp.sqrt(1/4)) +y = -2 * jnp.arcsin(jnp.sqrt(1/3)) +z = -2 * jnp.arcsin(jnp.sqrt(1/2)) + +output = circuit3(x, y, z) +states = [np.binary_repr(i, width=6) for i in range(len(output)) if output[i] != 0] +print(states) + +############################################################################## +# Indeed, we have a non-zero coefficient for :math:`|011000\rangle.` To address this problem, +# we can instead apply the single-excitation gate controlled on the +# state of the first qubit. This ensures that there is no coupling with the state :math:`| +# 001100\rangle` since here the first qubit is in state :math:`|0\rangle.` Let's implement the circuit +# above, this time controlling on the state of the first qubit and verify that we can prepare the +# desired state. To perform the control, we use the :func:`~.pennylane.ctrl` transform: + +@qml.qnode(dev, interface="jax") +def circuit4(x, y, z): + qml.BasisState(jnp.array([1, 1, 0, 0, 0, 0]), wires=[i for i in range(6)]) + qml.DoubleExcitation(x, wires=[0, 1, 2, 3]) + qml.DoubleExcitation(y, wires=[0, 1, 4, 5]) + # single excitation controlled on qubit 0 + qml.ctrl(qml.SingleExcitation, control=0)(z, wires=[1, 3]) + return qml.state() + +output = circuit4(x, y, z) +states = [np.binary_repr(i, width=6) for i in range(len(output)) if output[i] != 0] +print(states) + +############################################################################## +# It was proven in Ref. [#arrazola]_ that controlled single-excitation gates are universal for +# particle-conserving unitaries. With enough ingenuity, you can use these operations to construct +# any kind of circuit for quantum chemistry applications. What more could you ask from a gate? +# +# State preparation +# ----------------- +# +# We can bring all these pieces together and implement a circuit capable of preparing +# four-qubit states of two particles with real coefficients. The main idea is that we can +# perform the construction one basis state at a time by applying a suitable excitation gate, +# which may need to be controlled. +# +# +# .. figure:: ../_static/demonstration_assets/givens_rotations/circuit.png +# :align: center +# :width: 70% +# +# A circuit for preparing four-qubit states with two particles. +# +# +# Starting from the reference state :math:`|1100\rangle,` we create a superposition +# with the state :math:`|1010\rangle` by applying a single-excitation gate on qubits 1 and 2. +# Similarly, we create a superposition with the state :math:`|1001\rangle` with a single +# excitation between qubits 1 and 3. This leaves us with a state of the form +# +# .. math:: +# |\psi\rangle = a |1100\rangle + b |1010\rangle + c |1001\rangle. +# +# We can now perform two single excitations from qubit 0 to qubits 2 and 3. These will have +# to be controlled on the state of qubit 1. Finally, applying a double-excitation gate on all qubits +# can create a superposition of the form +# +# .. math:: +# +# |\psi\rangle = c_1|1100\rangle + c_2|1010\rangle + c_3|1001\rangle + c_4|0110\rangle + +# c_5|0101\rangle + c_6|0011\rangle, +# +# which is our intended outcome. Let's use this approach to create an equal superposition over +# all two-particle states on four qubits. We follow the same strategy as before, setting the angle +# of the :math:`k`-th Givens rotation as :math:`-2 \arcsin(1/\sqrt{n-k}),` where :math:`n` is the +# number of basis states in the superposition. + +dev = qml.device('default.qubit', wires=4) + +@qml.qnode(dev, interface="jax") +def state_preparation(params): + qml.BasisState(jnp.array([1, 1, 0, 0]), wires=[0, 1, 2, 3]) + qml.SingleExcitation(params[0], wires=[1, 2]) + qml.SingleExcitation(params[1], wires=[1, 3]) + # single excitations controlled on qubit 1 + qml.ctrl(qml.SingleExcitation, control=1)(params[2], wires=[0, 2]) + qml.ctrl(qml.SingleExcitation, control=1)(params[3], wires=[0, 3]) + qml.DoubleExcitation(params[4], wires=[0, 1, 2, 3]) + return qml.state() + +n = 6 +params = jnp.array([-2 * jnp.arcsin(1/jnp.sqrt(n-i)) for i in range(n-1)]) + +output = state_preparation(params) +# sets very small coefficients to zero +output = jnp.where(jnp.abs(output) < 1e-10, 0.0, output) +states = [np.binary_repr(i, width=4) for i in range(len(output)) if output[i] != 0] +print("Basis states = ", states) +print("Output state =", output) + +############################################################################## +# Success! This is the equal superposition state we wanted to prepare. 🚀 +# +# When it comes to quantum circuits for quantum chemistry, a wide variety of +# architectures have been proposed. Researchers in the field are faced with the apparent +# choice of making a selection among these circuits to conduct their computations and benchmark new +# algorithms. Like a kid in a toy store, it is challenging to pick just one. +# +# Ultimately, the aim of this tutorial is to provide you with the conceptual and software tools +# to implement any of these proposed circuits, and **also to design your own**. It's not only fun +# to play with toys; it's also fun to build them. +# +# +# References +# ---------- +# +# .. [#anselmetti] +# +# G-L. R. Anselmetti, D. Wierichs, C. Gogolin, Christian, and R. M. Parrish, +# "Local, Expressive, Quantum-Number-Preserving VQE Ansatze +# for Fermionic Systems", arXiv:2104.05695, (2021). +# +# .. [#barkoutsos] +# +# P. KL. Barkoutsos, Panagiotis, et al., "Quantum algorithms for electronic structure +# calculations: Particle-hole Hamiltonian and optimized wave-function expansions", Physical +# Review A 98(2), 022322, (2018). +# +# .. [#arrazola] +# +# J. M. Arrazola, O. Di Matteo, N. Quesada, S. Jahangiri, A. Delgado, N. Killoran, "Universal +# quantum circuits for quantum chemistry", arXiv:2106.13839, (2021) +# +# +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_haar_measure/demo.py b/demonstrations_v2/tutorial_haar_measure/demo.py index edde00d2d7..5807b5ab34 100644 --- a/demonstrations_v2/tutorial_haar_measure/demo.py +++ b/demonstrations_v2/tutorial_haar_measure/demo.py @@ -1,812 +1,812 @@ -r""".. role:: html(raw) - :format: html - -Understanding the Haar measure -============================== - -.. meta:: - :property="og:description": Learn all about the Haar measure and how to randomly sample quantum states. - - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/spherical_int_dtheta.png - -.. related:: - - tutorial_unitary_designs Unitary designs - quantum_volume Quantum volume - qsim_beyond_classical Beyond classical computing with qsim - tutorial_barren_plateaus Barren plateaus in quantum neural networks - - -*Author: Olivia Di Matteo — Posted: 22 March 2021. Last updated: 22 March 2021.* - -If you've ever dug into the literature about random quantum circuits, -variational ansatz structure, or anything related to the structure and -properties of unitary operations, you've likely come across a statement like the -following: "Assume that :math:`U` is sampled uniformly at random from the Haar -measure". In this demo, we're going to unravel this cryptic statement and take -an in-depth look at what it means. You'll gain an understanding of the general -concept of a *measure*, the Haar measure and its special properties, and you'll -learn how to sample from it using tools available in PennyLane and other -scientific computing frameworks. By the end of this demo, you'll be able to -include that important statement in your own work with confidence! - -.. note:: - - To get the most out of this demo, it is helpful if you are familiar with - `integration of multi-dimensional functions - `__, the `Bloch sphere - `__, and the conceptual ideas - behind `decompositions - `__ and factorizations of - unitary matrices (see, e.g., 4.5.1 and 4.5.2 of [#NandC2000]_). - -Measure -------- - -`Measure theory `__ is a -branch of mathematics that studies things that are measurable—think length, -area, or volume, but generalized to mathematical spaces and even higher -dimensions. Loosely, the measure tells you about how "stuff" is distributed and -concentrated in a mathematical set or space. An intuitive way to understand -the measure is to think about a sphere. An arbitrary point on a sphere can be -parametrized by three numbers—depending on what you're doing, you may use -Cartesian coordinates :math:`(x, y, z),` or it may be more convenient to use -spherical coordinates :math:`(\rho, \phi, \theta).` - -Suppose you wanted to compute the volume of a solid sphere with radius -:math:`r.` This can be done by integrating over the three coordinates -:math:`\rho, \phi,` and :math:`\theta.` Your first thought here may be to simply -integrate each parameter over its full range, like so: - -.. math:: - - V = \int_0^{r} \int_0^{2\pi} \int_0^{\pi} d\rho~ d\phi~ d\theta = 2\pi^2 r - -But, we know that the volume of a sphere of radius :math:`r` is -:math:`\frac{4}{3}\pi r^3,` so what we got from this integral is clearly wrong! -Taking the integral naively like this doesn't take into account the structure of -the sphere with respect to the parameters. For example, consider -two small, infinitesimal elements of area with the same difference in -:math:`\theta` and :math:`\phi,` but at different values of :math:`\theta:` - -.. figure:: /_static/demonstration_assets/haar_measure/spherical_int_dtheta.png - :align: center - :width: 50% - - -Even though the differences :math:`d\theta` and :math:`d\phi` themselves are the -same, there is way more "stuff" near the equator of the sphere than there is -near the poles. We must take into account the value of :math:`\theta` when -computing the integral! Specifically, we multiply by the function -:math:`\sin\theta`---the properties of the :math:`\sin` function mean that the -most weight will occur around the equator where :math:`\theta=\pi/2,` and the -least weight near the poles where :math:`\theta=0` and :math:`\theta=\pi.` - -Similar care must be taken for :math:`\rho.` The contribution to volume of -parts of the sphere with a large :math:`\rho` is far more than for a small -:math:`\rho`---we should expect the contribution to be proportional to -:math:`\rho^2,` given that the surface area of a sphere of radius :math:`r` is -:math:`4\pi r^2.` - -On the other hand, for a fixed :math:`\rho` and :math:`\theta,` the length of -the :math:`d\phi` is the same all around the circle. If put all these facts -together, we find that the actual expression for the integral should look like -this: - -.. math:: - - V = \int_0^r \int_0^{2\pi} \int_0^{\pi} \rho^2 \sin \theta~ d\rho~ d\phi~ - d\theta = \frac{4}{3}\pi r^3 - -These extra terms that we had to add to the integral, :math:`\rho^2 \sin -\theta`, constitute the *measure*. The measure weights portions of the sphere -differently depending on where they are in the space. While we need to know the -measure to properly integrate over the sphere, knowledge of the measure also -gives us the means to perform another important task, that of sampling points in -the space uniformly at random. We can't simply sample each parameter from the -uniform distribution over its domain—as we experienced already, this doesn't -take into account how the sphere is spread out over space. The measure describes -the distribution of each parameter and gives a recipe for sampling them in order -to obtain something properly uniform. - -The Haar measure ----------------- - -Operations in quantum computing are described by unitary matrices. -Unitary matrices, like points on a sphere, can be expressed in terms of a fixed -set of coordinates, or parameters. For example, the most general single-qubit rotation -implemented in PennyLane (:class:`~.pennylane.Rot`) is expressed in terms of three -parameters like so, - -.. math:: - - U(\phi, \theta, \omega) = \begin{pmatrix} e^{-i(\phi + \omega)/2} - \cos(\theta/2) & -e^{i(\phi - \omega)/2} \sin(\theta/2) - \\ e^{-i(\phi - \omega)/2} \sin(\theta/2) & e^{i(\phi + - \omega)/2} \cos(\theta/2) \end{pmatrix}. - -For every dimension :math:`N,` the unitary matrices of size :math:`N \times N` -constitute the *unitary group* :math:`U(N).` We can perform operations on -elements of this group, such as apply functions to them, integrate over them, or -sample uniformly over them, just as we can do to points on a sphere. When we do -such tasks with respect to the sphere, we have to add the measure in order to -properly weight the different regions of space. The *Haar measure* provides the -analogous terms we need for working with the unitary group. - -For an :math:`N`-dimensional system, the Haar measure, often denoted by -:math:`\mu_N,` tells us how to weight the elements of :math:`U(N).` For -example, suppose :math:`f` is a function that acts on elements of :math:`U(N),` -and we would like to take its integral over the group. We must write this -integral with respect to the Haar measure, like so: - -.. math:: - - \int_{V \in U(N)} f(V) d\mu_N(V). - -As with the measure term of the sphere, :math:`d\mu_N` itself can be broken down -into components depending on individual parameters. While the Haar -measure can be defined for every dimension :math:`N,` the mathematical form gets -quite hairy for larger dimensions—in general, an :math:`N`-dimensional unitary -requires at least :math:`N^2 - 1` parameters, which is a lot to keep track of! -Therefore we'll start with the case of a single qubit :math:`(N=2),` then show -how things generalize. - -Single-qubit Haar measure -~~~~~~~~~~~~~~~~~~~~~~~~~ - -The single-qubit case provides a particularly nice entry point because we can -continue our comparison to spheres by visualizing single-qubit states on the -Bloch sphere. As expressed above, the measure provides a recipe for sampling -elements of the unitary group in a properly uniform manner, given the structure -of the group. One useful consequence of this is that it provides a method to -sample quantum *states* uniformly at random—we simply generate Haar-random -unitaries, and apply them to a fixed basis state such as :math:`\vert 0\rangle.` - -We'll see how this works in good time. First, we'll take a look at what happens -when we ignore the measure and do things *wrong*. Suppose we sample quantum -states by applying unitaries obtained by the parametrization above, but sample -the angles :math:`\omega, \phi,` and :math:`\theta` from the flat uniform -distribution between :math:`[0, 2\pi)` (fun fact: there is a measure implicit in -this kind of sampling too! It just has a constant value, because each point is -equally likely to be sampled). - -""" - -import pennylane as qml -import numpy as np -import matplotlib.pyplot as plt - -# set the random seed -np.random.seed(42) - -# Use the mixed state simulator to save some steps in plotting later -dev = qml.device('default.mixed', wires=1) - -@qml.qnode(dev) -def not_a_haar_random_unitary(): - # Sample all parameters from their flat uniform distribution - phi, theta, omega = 2 * np.pi * np.random.uniform(size=3) - qml.Rot(phi, theta, omega, wires=0) - return qml.state() - -num_samples = 2021 - -not_haar_samples = [not_a_haar_random_unitary() for _ in range(num_samples)] - -###################################################################### -# In order to plot these on the Bloch sphere, we'll need to do one more -# step, and convert the quantum states into Bloch vectors. -# - -X = np.array([[0, 1], [1, 0]]) -Y = np.array([[0, -1j], [1j, 0]]) -Z = np.array([[1, 0], [0, -1]]) - -# Used the mixed state simulator so we could have the density matrix for this part! -def convert_to_bloch_vector(rho): - """Convert a density matrix to a Bloch vector.""" - ax = np.trace(np.dot(rho, X)).real - ay = np.trace(np.dot(rho, Y)).real - az = np.trace(np.dot(rho, Z)).real - return [ax, ay, az] - -not_haar_bloch_vectors = np.array([convert_to_bloch_vector(s) for s in not_haar_samples]) - -###################################################################### -# With this done, let's find out where our "uniformly random" states ended up: - -def plot_bloch_sphere(bloch_vectors): - """ Helper function to plot vectors on a sphere.""" - fig = plt.figure(figsize=(6, 6)) - ax = fig.add_subplot(111, projection='3d') - fig.subplots_adjust(left=0, right=1, bottom=0, top=1) - - ax.grid(False) - ax.set_axis_off() - ax.view_init(30, 45) - - # Draw the axes (source: https://github.com/matplotlib/matplotlib/issues/13575) - x, y, z = np.array([[-1.5,0,0], [0,-1.5,0], [0,0,-1.5]]) - u, v, w = np.array([[3,0,0], [0,3,0], [0,0,3]]) - ax.quiver(x, y, z, u, v, w, arrow_length_ratio=0.05, color="black", linewidth=0.5) - - ax.text(0, 0, 1.7, r"|0⟩", color="black", fontsize=16) - ax.text(0, 0, -1.9, r"|1⟩", color="black", fontsize=16) - ax.text(1.9, 0, 0, r"|+⟩", color="black", fontsize=16) - ax.text(-1.7, 0, 0, r"|–⟩", color="black", fontsize=16) - ax.text(0, 1.7, 0, r"|i+⟩", color="black", fontsize=16) - ax.text(0,-1.9, 0, r"|i–⟩", color="black", fontsize=16) - - ax.scatter( - bloch_vectors[:,0], bloch_vectors[:,1], bloch_vectors[:, 2], c='#e29d9e', alpha=0.3 - ) - plt.show() - -plot_bloch_sphere(not_haar_bloch_vectors) - -###################################################################### -# You can see from this plot that even though our parameters were sampled from a -# uniform distribution, there is a noticeable amount of clustering around the poles -# of the sphere. Despite the input parameters being uniform, the output is very -# much *not* uniform. Just like the regular sphere, the measure is larger near -# the equator, and if we just sample uniformly, we won't end up populating that -# area as much. To take that into account we will need to sample from the proper -# Haar measure, and weight the different parameters appropriately. -# -# For a single qubit, the Haar measure looks much like the case of a sphere, -# minus the radial component. Intuitively, all qubit state vectors have length -# 1, so it makes sense that this wouldn't play a role here. The parameter that -# we will have to weight differently is :math:`\theta,` and in fact the -# adjustment in measure is identical to that we had to do with the polar axis of -# the sphere, i.e., :math:`\sin \theta.` In order to sample the :math:`\theta` -# uniformly at random in this context, we must sample from the distribution -# :math:`\hbox{Pr}(\theta) = \sin \theta.` We can accomplish this by setting up -# a custom probability distribution with -# `rv_continuous `__ -# in ``scipy``. - -from scipy.stats import rv_continuous - -class sin_prob_dist(rv_continuous): - def _pdf(self, theta): - # The 0.5 is so that the distribution is normalized - return 0.5 * np.sin(theta) - -# Samples of theta should be drawn from between 0 and pi -sin_sampler = sin_prob_dist(a=0, b=np.pi) - -@qml.qnode(dev) -def haar_random_unitary(): - phi, omega = 2 * np.pi * np.random.uniform(size=2) # Sample phi and omega as normal - theta = sin_sampler.rvs(size=1)[0] # Sample theta from our new distribution - qml.Rot(phi, theta, omega, wires=0) - return qml.state() - -haar_samples = [haar_random_unitary() for _ in range(num_samples)] -haar_bloch_vectors = np.array([convert_to_bloch_vector(s) for s in haar_samples]) - -plot_bloch_sphere(haar_bloch_vectors) - -###################################################################### -# We see that when we use the correct measure, our qubit states are now -# much better distributed over the sphere. Putting this information together, -# we can now write the explicit form for the single-qubit Haar measure: -# -# .. math:: -# -# d\mu_2 = \sin \theta d\theta \cdot d\omega \cdot d\phi. -# -# Show me more math! -# ~~~~~~~~~~~~~~~~~~ -# -# While we can easily visualize the single-qubit case, this is no longer -# possible when we increase the number of qubits. Regardless, we can still -# obtain a mathematical expression for the Haar measure in arbitrary -# dimensions. In the previous section, we expressed the Haar measure in terms of -# a set of parameters that can be used to specify the unitary group -# :math:`U(2).` Such a parametrization is not unique, and in fact there are -# multiple ways to *factorize*, or decompose an :math:`N`-dimensional unitary -# operation into a set of parameters. -# -# Many of these parametrizations come to us from the study of photonics. Here, -# arbitrary operations are broken down into elementary operations involving only -# a few parameters which correspond directly to parameters of the physical -# apparatus used to implement them (beamsplitters and phase shifts). Rather than -# qubits, such operations act on modes, or *qumodes*. They are expressed as -# elements of the :math:`N`-dimensional `special unitary group -# `__. This group, written -# as :math:`SU(N),` is the continuous group consisting of all :math:`N \times N` -# unitary operations with determinant 1 (essentially like :math:`U(N),` minus -# a potential global phase). -# -# -# .. note:: -# -# Elements of :math:`SU(N)` and :math:`U(N)` can still be considered as -# multi-qubit operations in the cases where :math:`N` is a power of 2, but -# they must be translated from continuous-variable operations into qubit -# operations. (In PennyLane, this can be done by feeding the unitaries to -# the :class:`~.pennylane.QubitUnitary` operation directly. Alternatively, -# one can use *quantum compilation* to express the operations as a sequence -# of elementary gates such as Pauli rotations and CNOTs.) -# -# .. admonition:: Tip -# -# If you haven't had many opportunities to work in terms of qumodes, the -# `Strawberry Fields documentation -# `__ is a -# good starting point. -# -# For example, we saw already above that for :math:`N=2,` we can write -# -# .. math:: -# -# U(\phi, \theta, \omega) = \begin{pmatrix} e^{-i(\phi + \omega)/2} -# \cos(\theta/2) & -e^{i(\phi - \omega)/2} \sin(\theta/2) -# \\ e^{-i(\phi - \omega)/2} \sin(\theta/2) & e^{i(\phi + -# \omega)/2} \cos(\theta/2) \end{pmatrix}. -# -# -# This unitary can be factorized as follows: -# -# .. math:: -# -# U(\phi, \theta, \omega) = -# \begin{pmatrix} -# e^{-i\omega/2} & 0 \\ 0 & e^{i\omega/2} -# \end{pmatrix} -# \begin{pmatrix} -# \cos(\theta/2) & -\sin(\theta/2) \\ \sin(\theta/2) & \cos(\theta/2) -# \end{pmatrix} -# \begin{pmatrix} -# e^{-i\phi/2} & 0 \\ 0 & e^{i\phi/2} -# \end{pmatrix} -# -# The middle operation is a beamsplitter; the other two operations are phase -# shifts. We saw earlier that for :math:`N=2,` :math:`d\mu_2 = \sin\theta -# d\theta d\omega d\phi`---note how the parameter in the beamsplitter -# contributes to the measure in a different way than those of the phase -# shifts. As mentioned above, for larger values of :math:`N` there are multiple -# ways to decompose the unitary. Such decompositions rewrite elements in -# :math:`SU(N)` acting on :math:`N` modes as a sequence of operations acting -# only on 2 modes, :math:`SU(2),` and single-mode phase shifts. Shown below are -# three examples [#deGuise2018]_, [#Clements2016]_, [#Reck1994]_: -# -# .. figure:: /_static/demonstration_assets/haar_measure/unitaries.png -# :align: center -# :width: 95% -# -# -# In these graphics, every wire is a different mode. Every box represents an -# operation on one or more modes, and the number in the box indicates the number -# of parameters. The boxes containing a ``1`` are simply phase shifts on -# individual modes. The blocks containing a ``3`` are :math:`SU(2)` transforms -# with 3 parameters, such as the :math:`U(\phi, \theta, \omega)` above. Those -# containing a ``2`` are :math:`SU(2)` transforms on pairs of modes with 2 -# parameters, similar to the 3-parameter ones but with :math:`\omega = \phi.` -# -# Although the decompositions all produce the same set of operations, their -# structure and parametrization may have consequences in practice. The first [#deGuise2018]_ -# has a particularly convenient form that leads to a recursive definition -# of the Haar measure. The decomposition is formulated recursively such that an -# :math:`SU(N)` operation can be implemented by sandwiching an :math:`SU(2)` -# transformation between two :math:`SU(N-1)` transformations, like so: -# -# | -# -# .. figure:: /_static/demonstration_assets/haar_measure/sun.svg -# :align: center -# :width: 80% -# -# | -# -# The Haar measure is then constructed recursively as a product of 3 -# terms. The first term depends on the parameters in the first :math:`SU(N-1)` -# transformation; the second depends on the parameters in the lone :math:`SU(2)` -# transformation; and the third term depends on the parameters in the other -# :math:`SU(N-1)` transformation. -# -# :math:`SU(2)` is the "base case" of the recursion—we simply have the Haar measure -# as expressed above. -# -# | -# -# .. figure:: /_static/demonstration_assets/haar_measure/su2_haar.svg -# :align: center -# :width: 25% -# -# | -# -# Moving on up, we can write elements of :math:`SU(3)` as a sequence of three -# :math:`SU(2)` transformations. The Haar measure :math:`d\mu_3` then consists -# of two copies of :math:`d\mu_2,` with an extra term in between to take into -# account the middle transformation. -# -# | -# -# .. figure:: /_static/demonstration_assets/haar_measure/su3_haar.svg -# :align: center -# :width: 80% -# -# | -# -# For :math:`SU(4)` and upwards, the form changes slightly, but still follows -# the pattern of two copies of :math:`d\mu_{N-1}` with a term in between. -# -# | -# -# .. figure:: /_static/demonstration_assets/haar_measure/su4_premerge.svg -# :align: center -# :width: 90% -# -# | -# -# For larger systems, however, the recursive composition allows for some of the -# :math:`SU(2)` transformations on the lower modes to be grouped. We can take -# advantage of this and aggregate some of the parameters: -# -# | -# -# .. figure:: /_static/demonstration_assets/haar_measure/su4_triangle_merge.svg -# :align: center -# :width: 100% -# -# | -# -# This leads to one copy of :math:`d\mu_{N-1},` which we'll denote as -# :math:`d\mu_{N-1}^\prime,` containing only a portion of the full set of terms -# (as detailed in [#deGuise2018]_, this is called a *coset measure*). -# -# | -# -# .. figure:: /_static/demonstration_assets/haar_measure/su4_haar.svg -# :align: center -# :width: 100% -# -# | -# -# Putting everything together, we have that -# -# .. math:: -# -# d\mu_N = d\mu_{N-1}^\prime \times \sin \theta_{N-1} -# \sin^{2(N-2)}\left(\frac{\theta_{N-1}}{2}\right) d\theta_{N-1} d\omega_{N-1} \times d\mu_{N-1} -# -# The middle portion depends on the value of :math:`N,` and the parameters -# :math:`\theta_{N-1}` and :math:`\omega_{N-1}` contained in the :math:`(N-1)`'th -# :math:`SU(N)` transformation. This is thus a convenient, systematic way to -# construct the :math:`N`-dimensional Haar measure for the unitary group. As a -# final note, even though unitaries can be parametrized in different ways, the -# underlying Haar measure is *unique*. This is a consequence of it being an -# invariant measure, as will be shown later. -# -# Haar-random matrices from the :math:`QR` decomposition -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# -# Nice-looking math aside, sometimes you just need to generate a large number of -# high-dimensional Haar-random matrices. It would be very cumbersome to sample -# and keep track of the distributions of so many parameters; furthermore, the -# measure above requires you to parametrize your operations in a fixed way. -# There is a much quicker way to perform the sampling by taking a (slightly -# modified) `QR decomposition -# `__ of complex-valued -# matrices. This algorithm is detailed in [#Mezzadri2006]_, and consists of the -# following steps: -# -# 1. Generate an :math:`N \times N` matrix :math:`Z` with complex numbers :math:`a+bi` -# where both :math:`a` and :math:`b` are normally distributed with mean 0 and variance 1 -# (this is sampling from the distribution known as the *Ginibre ensemble*). -# 2. Compute a QR decomposition :math:`Z = QR.` -# 3. Compute the diagonal matrix :math:`\Lambda = \hbox{diag}(R_{ii}/|R_{ii}|).` -# 4. Compute :math:`Q^\prime = Q \Lambda,` which will be Haar-random. -# -# - -from numpy.linalg import qr - -def qr_haar(N): - """Generate a Haar-random matrix using the QR decomposition.""" - # Step 1 - A, B = np.random.normal(size=(N, N)), np.random.normal(size=(N, N)) - Z = A + 1j * B - - # Step 2 - Q, R = qr(Z) - - # Step 3 - Lambda = np.diag([R[i, i] / np.abs(R[i, i]) for i in range(N)]) - - # Step 4 - return np.dot(Q, Lambda) - -###################################################################### -# Let's check that this method actually generates Haar-random unitaries -# by trying it out for :math:`N=2` and plotting on the Bloch sphere. -# - -@qml.qnode(dev) -def qr_haar_random_unitary(): - qml.QubitUnitary(qr_haar(2), wires=0) - return qml.state() - -qr_haar_samples = [qr_haar_random_unitary() for _ in range(num_samples)] -qr_haar_bloch_vectors = np.array([convert_to_bloch_vector(s) for s in qr_haar_samples]) -plot_bloch_sphere(qr_haar_bloch_vectors) - -###################################################################### -# As expected, we find our qubit states are distributed uniformly over the -# sphere. This particular method is what's implemented in packages like -# ``scipy``'s `unitary_group -# `__ -# function. -# -# Now, it's clear that this method works, but it is also important to -# understand *why* it works. Step 1 is fairly straightforward—the base of our -# samples is a matrix full of complex values chosen from a typical -# distribution. This isn't enough by itself, since unitary matrices also -# have constraints—their rows and columns must be orthonormal. -# These constraints are where step 2 comes in—the outcome of a generic -# QR decomposition consists of an *orthonormal* matrix :math:`Q,` and and upper -# triangular matrix :math:`R.` Since our original matrix was complex-valued, we end -# up with a :math:`Q` that is in fact already unitary. But why not stop there? Why -# do we then perform steps 3 and 4? -# -# Steps 3 and 4 are needed because, while the QR decomposition yields a unitary, -# it is not a unitary that is properly Haar-random. In [#Mezzadri2006]_, it is -# explained that a uniform distribution over unitary matrices should also yield -# a uniform distribution over the *eigenvalues* of those matrices, i.e., every -# eigenvalue should be equally likely. Just using the QR decomposition out of -# the box produces an *uneven* distribution of eigenvalues of the unitaries! -# This discrepancy stems from the fact that the QR decomposition is not unique. -# We can take any unitary diagonal matrix :math:`\Lambda,` and re-express the decomposition -# as :math:`QR = Q\Lambda \Lambda^\dagger R = Q^\prime R^\prime.` Step 3 removes this -# redundancy by fixing a :math:`\Lambda` that depends on :math:`R,` leading to a unique -# value of :math:`Q^\prime = Q \Lambda,` and a uniform distribution of eigenvalues. -# -# .. admonition:: Try it! -# -# Use the ``qr_haar`` function above to generate random unitaries and construct -# a distribution of their eigenvalues. Then, comment out the lines for steps 3 and -# 4 and do the same—you'll find that the distribution is no longer uniform. -# Check out reference [#Mezzadri2006]_ for additional details and examples. - -###################################################################### -# Fun (and not-so-fun) facts -# -------------------------- -# -# We've now learned what the Haar measure is, and both an analytical and -# numerical means of sampling quantum states and unitary operations uniformly at -# random. The Haar measure also has many neat properties that play a role in -# quantum computing. -# -# Invariance of the Haar measure -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# -# Earlier, we showed that the Haar measure is used when integrating functions over -# the unitary group: -# -# .. math:: -# -# \int_{V \in U(N)} f(V) d\mu_N(V). -# -# One of the defining features of the Haar measure is that it is both left and -# right *invariant* under unitary transformations. That is, -# -# .. math:: -# -# \int_{V \in U(N)} f(\color{red}{W}V) d\mu_N(V) = \int_{V \in U(N)} f(V\color{red}{W}) d\mu_N(V) = \int_{V \in U(N)} f(V) d\mu_N(V). -# -# This holds true for *any* other :math:`N\times N` unitary :math:`W!` A -# consequence of such invariance is that if :math:`V` is Haar-random, then so is -# :math:`V^T,` :math:`V^\dagger,` and any product of another unitary matrix and -# :math:`V` (where the product may be taken on either side). -# -# Another consequence of this invariance has to do with the structure of the entries -# themselves: they must all come from the same distribution. This is because the -# measure remains invariant under permutations, since permutations are unitary--- -# the whole thing still has to be Haar random no matter how the entries are ordered, -# so all distributions must be the same. The specific distribution is complex -# numbers :math:`a+bi` where both :math:`a` and :math:`b` has mean 0 and variance -# :math:`1/N` [#Meckes2014]_ (so, much like Ginibre ensemble we used in the QR decomposition -# above, but with a different variance and constraints due to orthonormality). -# -# Concentration of measure -# ~~~~~~~~~~~~~~~~~~~~~~~~ -# -# An unfortunate (although interesting) property of the Haar measure is that it -# suffers from the phenomenon of `concentration of measure -# `__. Most of the -# "stuff" in the space concentrates around a certain area, and this gets worse -# as the size of the system increases. You can see the beginnings of by looking -# at the sphere. For the 3-dimensional sphere, we saw graphically how there is -# concentration around the equator, and how the measure takes that into account -# with the additional factor of :math:`\sin \theta.` This property becomes -# increasingly prominent for `higher-dimensional spheres -# `__. -# -# .. important:: -# -# The concentration described here is not referring to what we witnessed -# earlier on, when we sampled quantum states (points on the Bloch sphere) -# incorrectly and found that they clustered around the poles. However, that -# is not unrelated. Concentration of measure refers to where the measure -# itself is concentrated, and which parts of the space should be more heavily -# weighted. For the case of the sphere, it is the equatorial area, and when -# we didn't sample properly and take that concentration into account, we -# obtained an uneven distribution. -# -# Let's consider an :math:`N`-dimensional unit sphere. Points on the sphere, or -# vectors in this space, are parametrized by :math:`N-1` real coordinates. -# Suppose we have some function :math:`f` that maps points on that sphere to -# real numbers. Sample a point :math:`x` on that sphere from the uniform -# measure, and compute the value of :math:`f(x).` How close do you think the -# result will be to the mean value of the function, :math:`E[f],` over the -# entire sphere? -# -# A result called `Levy's lemma -# `__ -# [#Gerken2013]_, [#Hayden2006]_ expresses how likely it is that :math:`f(x)` is a specific -# distance away from the mean. It states that, for an :math:`x` selected -# uniformly at random, the probability that :math:`f(x)` deviates from -# :math:`E[f]` by some amount :math:`\epsilon` is bounded by: -# -# .. math:: -# -# \hbox{Pr}(|f(x) - E[f]| \ge \epsilon) \leq 2 \exp\left[-\frac{N\epsilon^2}{9\pi^3 \eta^2}\right]. -# -# A constraint on the function :math:`f` is that it must be `Lipschitz -# continuous `__, where -# :math:`\eta` is the *Lipschitz constant* of the function. The important aspect -# here is the likelihood of deviating significantly from the mean by an amount -# :math:`\epsilon` decreases exponentially with :math:`\epsilon.` Furthermore, -# increasing the dimension :math:`N` also makes the deviation exponentially less -# likely. -# -# Now, this result seems unrelated to quantum states—it concerns higher- -# dimensional spheres. However, recall that a quantum state vector is a complex -# vector whose squared values sum to 1, similar to vectors on a sphere. If you -# "unroll" a quantum state vector of dimension :math:`N = 2^n` by stacking its -# real and complex parts, you end with a vector of length :math:`2 \cdot 2^{n}` -# which ends up behaving just like a unit vector on the sphere in this -# dimension. Given that measure concentrates on spheres, and quantum state -# vectors can be converted to vectors on spheres, functions on random quantum -# states will also demonstrate concentration. -# -# This is bad news! To do useful things in quantum computing, we need a lot of -# qubits. But the more qubits we have, the more our randomly sampled states will -# look the same (specifically, random states will concentrate around the -# maximally entangled state [#Hayden2006]_). This has important consequences for -# near-term algorithms (as detailed in the next section), and any algorithm that -# involves uniform sampling of quantum states and operations. -# -# Haar measure and barren plateaus -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# -# Suppose you are venturing out to solve a new problem using an algorithm such -# as the :doc:`variational quantum eigensolver `. A -# critical component of such methods is the choice of :doc:`variational ansatz -# `. Having now learned a bit about the properties of -# the Haar measure, you may think it would make sense to use this for the -# parametrization. Variational ansaetze are, after all, parametrized quantum -# circuits, so why not choose an ansatz that corresponds directly to a -# parametrization for Haar-random unitaries? The initial parameter selection -# will give you a state in the Hilbert space uniformly at random. Then, since -# this ansatz spans the entire Hilbert space, you're guaranteed to be able to -# represent the target ground state with your ansatz, and it should be able to -# find it with no issue ... right? -# -# Unfortunately, while such an ansatz is extremely *expressive* (i.e., it is -# capable of representing any possible state), these ansaetze actually suffer -# the most from the barren plateau problem [#McClean2018]_, [#Holmes2021]_. -# :doc:`Barren plateaus ` are regions in the -# cost landscape of a parametrized circuit where both the gradient and its -# variance approach 0, leading the optimizer to get stuck in a local minimum. -# This was explored recently in the work of [#Holmes2021]_, wherein closeness to -# the Haar measure was actually used as a metric for expressivity. The closer -# things are to the Haar measure, the more expressive they are, but they are -# also more prone to exhibiting barren plateaus. -# -# -# .. figure:: /_static/demonstration_assets/haar_measure/holmes-costlandscapes.png -# :align: center -# :width: 50% -# -# Image source: [#Holmes2021]_. A highly expressive ansatz that can access -# much of the space of possible unitaries (i.e., an ansatz capable of -# producing unitaries in something close to a Haar-random manner) is very -# likely to have flat cost landscapes and suffer from the barren plateau -# problem. -# -# It turns out that the types of ansaetze know as *hardware-efficient ansaetze* -# also suffer from this problem if they are "random enough" (this notion will be -# formalized in a future demo). It was shown in [#McClean2018]_ that this is a -# consequence of the concentration of measure phenomenon described above. The -# values of gradients and variances can be computed for classes of circuits on -# average by integrating with respect to the Haar measure, and it is shown that -# these values decrease exponentially in the number of qubits, and thus huge -# swaths of the cost landscape are simply and fundamentally flat. -# -# Conclusion -# ---------- -# -# The Haar measure plays an important role in quantum computing—anywhere -# you might be dealing with sampling random circuits, or averaging over -# all possible unitary operations, you'll want to do so with respect -# to the Haar measure. -# -# There are two important aspects of this that we have yet to touch upon, -# however. The first is whether it is efficient to sample from the Haar measure—given -# that the number of parameters to keep track of is exponential in the -# number of qubits, certainly not. But a more interesting question is do we -# *need* to always sample from the full Haar measure? The answer to this is -# "no" in a very interesting way. Depending on the task at hand, you may be able -# to take a shortcut using something called a *unitary design*. In an upcoming -# demo, we will explore the amazing world of unitary designs and their -# applications! -# -# References -# ---------- -# -# .. [#NandC2000] -# -# M. A. Nielsen, and I. L. Chuang (2000) "Quantum Computation and Quantum Information", -# Cambridge University Press. -# -# .. [#deGuise2018] -# -# H. de Guise, O. Di Matteo, and L. L. Sánchez-Soto. (2018) "Simple factorization -# of unitary transformations", `Phys. Rev. A 97 022328 -# `__. -# (`arXiv `__) -# -# .. [#Clements2016] -# -# W. R. Clements, P. C. Humphreys, B. J. Metcalf, W. S. Kolthammer, and -# I. A. Walmsley (2016) “Optimal design for universal multiport -# interferometers”, \ `Optica 3, 1460–1465 -# `__. -# (`arXiv `__) -# -# .. [#Reck1994] -# -# M. Reck, A. Zeilinger, H. J. Bernstein, and P. Bertani (1994) “Experimental -# realization of any discrete unitary operator”, `Phys. Rev. Lett.73, 58–61 -# `__. -# -# .. [#Mezzadri2006] -# -# F. Mezzadri (2006) "How to generate random matrices from the classical compact groups". -# (`arXiv `__) -# -# .. [#Meckes2014] -# -# E. Meckes (2019) `"The Random Matrix Theory of the Classical Compact Groups" -# `_, Cambridge University Press. -# -# .. [#Gerken2013] -# -# M. Gerken (2013) "Measure concentration: Levy's Lemma" -# (`lecture notes `__). -# -# -# .. [#Hayden2006] -# -# P. Hayden, D. W. Leung, and A. Winter (2006) "Aspects of generic -# entanglement", `Comm. Math. Phys. Vol. 265, No. 1, pp. 95-117 -# `__. -# (`arXiv `__) -# -# .. [#McClean2018] -# -# J. R. McClean, S. Boixo, V. N. Smelyanskiy, R. Babbush, and H. Neven -# (2018) "Barren plateaus in quantum neural network training -# landscapes", `Nature Communications, 9(1) -# `__. -# (`arXiv `__) -# -# .. [#Holmes2021] -# -# Z. Holmes, K. Sharma, M. Cerezo, and P. J. Coles (2021) "Connecting ansatz -# expressibility to gradient magnitudes and barren plateaus". (`arXiv -# `__) +r""".. role:: html(raw) + :format: html + +Understanding the Haar measure +============================== + +.. meta:: + :property="og:description": Learn all about the Haar measure and how to randomly sample quantum states. + + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/spherical_int_dtheta.png + +.. related:: + + tutorial_unitary_designs Unitary designs + quantum_volume Quantum volume + qsim_beyond_classical Beyond classical computing with qsim + tutorial_barren_plateaus Barren plateaus in quantum neural networks + + +*Author: Olivia Di Matteo — Posted: 22 March 2021. Last updated: 22 March 2021.* + +If you've ever dug into the literature about random quantum circuits, +variational ansatz structure, or anything related to the structure and +properties of unitary operations, you've likely come across a statement like the +following: "Assume that :math:`U` is sampled uniformly at random from the Haar +measure". In this demo, we're going to unravel this cryptic statement and take +an in-depth look at what it means. You'll gain an understanding of the general +concept of a *measure*, the Haar measure and its special properties, and you'll +learn how to sample from it using tools available in PennyLane and other +scientific computing frameworks. By the end of this demo, you'll be able to +include that important statement in your own work with confidence! + +.. note:: + + To get the most out of this demo, it is helpful if you are familiar with + `integration of multi-dimensional functions + `__, the `Bloch sphere + `__, and the conceptual ideas + behind `decompositions + `__ and factorizations of + unitary matrices (see, e.g., 4.5.1 and 4.5.2 of [#NandC2000]_). + +Measure +------- + +`Measure theory `__ is a +branch of mathematics that studies things that are measurable—think length, +area, or volume, but generalized to mathematical spaces and even higher +dimensions. Loosely, the measure tells you about how "stuff" is distributed and +concentrated in a mathematical set or space. An intuitive way to understand +the measure is to think about a sphere. An arbitrary point on a sphere can be +parametrized by three numbers—depending on what you're doing, you may use +Cartesian coordinates :math:`(x, y, z),` or it may be more convenient to use +spherical coordinates :math:`(\rho, \phi, \theta).` + +Suppose you wanted to compute the volume of a solid sphere with radius +:math:`r.` This can be done by integrating over the three coordinates +:math:`\rho, \phi,` and :math:`\theta.` Your first thought here may be to simply +integrate each parameter over its full range, like so: + +.. math:: + + V = \int_0^{r} \int_0^{2\pi} \int_0^{\pi} d\rho~ d\phi~ d\theta = 2\pi^2 r + +But, we know that the volume of a sphere of radius :math:`r` is +:math:`\frac{4}{3}\pi r^3,` so what we got from this integral is clearly wrong! +Taking the integral naively like this doesn't take into account the structure of +the sphere with respect to the parameters. For example, consider +two small, infinitesimal elements of area with the same difference in +:math:`\theta` and :math:`\phi,` but at different values of :math:`\theta:` + +.. figure:: /_static/demonstration_assets/haar_measure/spherical_int_dtheta.png + :align: center + :width: 50% + + +Even though the differences :math:`d\theta` and :math:`d\phi` themselves are the +same, there is way more "stuff" near the equator of the sphere than there is +near the poles. We must take into account the value of :math:`\theta` when +computing the integral! Specifically, we multiply by the function +:math:`\sin\theta`---the properties of the :math:`\sin` function mean that the +most weight will occur around the equator where :math:`\theta=\pi/2,` and the +least weight near the poles where :math:`\theta=0` and :math:`\theta=\pi.` + +Similar care must be taken for :math:`\rho.` The contribution to volume of +parts of the sphere with a large :math:`\rho` is far more than for a small +:math:`\rho`---we should expect the contribution to be proportional to +:math:`\rho^2,` given that the surface area of a sphere of radius :math:`r` is +:math:`4\pi r^2.` + +On the other hand, for a fixed :math:`\rho` and :math:`\theta,` the length of +the :math:`d\phi` is the same all around the circle. If put all these facts +together, we find that the actual expression for the integral should look like +this: + +.. math:: + + V = \int_0^r \int_0^{2\pi} \int_0^{\pi} \rho^2 \sin \theta~ d\rho~ d\phi~ + d\theta = \frac{4}{3}\pi r^3 + +These extra terms that we had to add to the integral, :math:`\rho^2 \sin +\theta`, constitute the *measure*. The measure weights portions of the sphere +differently depending on where they are in the space. While we need to know the +measure to properly integrate over the sphere, knowledge of the measure also +gives us the means to perform another important task, that of sampling points in +the space uniformly at random. We can't simply sample each parameter from the +uniform distribution over its domain—as we experienced already, this doesn't +take into account how the sphere is spread out over space. The measure describes +the distribution of each parameter and gives a recipe for sampling them in order +to obtain something properly uniform. + +The Haar measure +---------------- + +Operations in quantum computing are described by unitary matrices. +Unitary matrices, like points on a sphere, can be expressed in terms of a fixed +set of coordinates, or parameters. For example, the most general single-qubit rotation +implemented in PennyLane (:class:`~.pennylane.Rot`) is expressed in terms of three +parameters like so, + +.. math:: + + U(\phi, \theta, \omega) = \begin{pmatrix} e^{-i(\phi + \omega)/2} + \cos(\theta/2) & -e^{i(\phi - \omega)/2} \sin(\theta/2) + \\ e^{-i(\phi - \omega)/2} \sin(\theta/2) & e^{i(\phi + + \omega)/2} \cos(\theta/2) \end{pmatrix}. + +For every dimension :math:`N,` the unitary matrices of size :math:`N \times N` +constitute the *unitary group* :math:`U(N).` We can perform operations on +elements of this group, such as apply functions to them, integrate over them, or +sample uniformly over them, just as we can do to points on a sphere. When we do +such tasks with respect to the sphere, we have to add the measure in order to +properly weight the different regions of space. The *Haar measure* provides the +analogous terms we need for working with the unitary group. + +For an :math:`N`-dimensional system, the Haar measure, often denoted by +:math:`\mu_N,` tells us how to weight the elements of :math:`U(N).` For +example, suppose :math:`f` is a function that acts on elements of :math:`U(N),` +and we would like to take its integral over the group. We must write this +integral with respect to the Haar measure, like so: + +.. math:: + + \int_{V \in U(N)} f(V) d\mu_N(V). + +As with the measure term of the sphere, :math:`d\mu_N` itself can be broken down +into components depending on individual parameters. While the Haar +measure can be defined for every dimension :math:`N,` the mathematical form gets +quite hairy for larger dimensions—in general, an :math:`N`-dimensional unitary +requires at least :math:`N^2 - 1` parameters, which is a lot to keep track of! +Therefore we'll start with the case of a single qubit :math:`(N=2),` then show +how things generalize. + +Single-qubit Haar measure +~~~~~~~~~~~~~~~~~~~~~~~~~ + +The single-qubit case provides a particularly nice entry point because we can +continue our comparison to spheres by visualizing single-qubit states on the +Bloch sphere. As expressed above, the measure provides a recipe for sampling +elements of the unitary group in a properly uniform manner, given the structure +of the group. One useful consequence of this is that it provides a method to +sample quantum *states* uniformly at random—we simply generate Haar-random +unitaries, and apply them to a fixed basis state such as :math:`\vert 0\rangle.` + +We'll see how this works in good time. First, we'll take a look at what happens +when we ignore the measure and do things *wrong*. Suppose we sample quantum +states by applying unitaries obtained by the parametrization above, but sample +the angles :math:`\omega, \phi,` and :math:`\theta` from the flat uniform +distribution between :math:`[0, 2\pi)` (fun fact: there is a measure implicit in +this kind of sampling too! It just has a constant value, because each point is +equally likely to be sampled). + +""" + +import pennylane as qml +import numpy as np +import matplotlib.pyplot as plt + +# set the random seed +np.random.seed(42) + +# Use the mixed state simulator to save some steps in plotting later +dev = qml.device('default.mixed', wires=1) + +@qml.qnode(dev) +def not_a_haar_random_unitary(): + # Sample all parameters from their flat uniform distribution + phi, theta, omega = 2 * np.pi * np.random.uniform(size=3) + qml.Rot(phi, theta, omega, wires=0) + return qml.state() + +num_samples = 2021 + +not_haar_samples = [not_a_haar_random_unitary() for _ in range(num_samples)] + +###################################################################### +# In order to plot these on the Bloch sphere, we'll need to do one more +# step, and convert the quantum states into Bloch vectors. +# + +X = np.array([[0, 1], [1, 0]]) +Y = np.array([[0, -1j], [1j, 0]]) +Z = np.array([[1, 0], [0, -1]]) + +# Used the mixed state simulator so we could have the density matrix for this part! +def convert_to_bloch_vector(rho): + """Convert a density matrix to a Bloch vector.""" + ax = np.trace(np.dot(rho, X)).real + ay = np.trace(np.dot(rho, Y)).real + az = np.trace(np.dot(rho, Z)).real + return [ax, ay, az] + +not_haar_bloch_vectors = np.array([convert_to_bloch_vector(s) for s in not_haar_samples]) + +###################################################################### +# With this done, let's find out where our "uniformly random" states ended up: + +def plot_bloch_sphere(bloch_vectors): + """ Helper function to plot vectors on a sphere.""" + fig = plt.figure(figsize=(6, 6)) + ax = fig.add_subplot(111, projection='3d') + fig.subplots_adjust(left=0, right=1, bottom=0, top=1) + + ax.grid(False) + ax.set_axis_off() + ax.view_init(30, 45) + + # Draw the axes (source: https://github.com/matplotlib/matplotlib/issues/13575) + x, y, z = np.array([[-1.5,0,0], [0,-1.5,0], [0,0,-1.5]]) + u, v, w = np.array([[3,0,0], [0,3,0], [0,0,3]]) + ax.quiver(x, y, z, u, v, w, arrow_length_ratio=0.05, color="black", linewidth=0.5) + + ax.text(0, 0, 1.7, r"|0⟩", color="black", fontsize=16) + ax.text(0, 0, -1.9, r"|1⟩", color="black", fontsize=16) + ax.text(1.9, 0, 0, r"|+⟩", color="black", fontsize=16) + ax.text(-1.7, 0, 0, r"|–⟩", color="black", fontsize=16) + ax.text(0, 1.7, 0, r"|i+⟩", color="black", fontsize=16) + ax.text(0,-1.9, 0, r"|i–⟩", color="black", fontsize=16) + + ax.scatter( + bloch_vectors[:,0], bloch_vectors[:,1], bloch_vectors[:, 2], c='#e29d9e', alpha=0.3 + ) + plt.show() + +plot_bloch_sphere(not_haar_bloch_vectors) + +###################################################################### +# You can see from this plot that even though our parameters were sampled from a +# uniform distribution, there is a noticeable amount of clustering around the poles +# of the sphere. Despite the input parameters being uniform, the output is very +# much *not* uniform. Just like the regular sphere, the measure is larger near +# the equator, and if we just sample uniformly, we won't end up populating that +# area as much. To take that into account we will need to sample from the proper +# Haar measure, and weight the different parameters appropriately. +# +# For a single qubit, the Haar measure looks much like the case of a sphere, +# minus the radial component. Intuitively, all qubit state vectors have length +# 1, so it makes sense that this wouldn't play a role here. The parameter that +# we will have to weight differently is :math:`\theta,` and in fact the +# adjustment in measure is identical to that we had to do with the polar axis of +# the sphere, i.e., :math:`\sin \theta.` In order to sample the :math:`\theta` +# uniformly at random in this context, we must sample from the distribution +# :math:`\hbox{Pr}(\theta) = \sin \theta.` We can accomplish this by setting up +# a custom probability distribution with +# `rv_continuous `__ +# in ``scipy``. + +from scipy.stats import rv_continuous + +class sin_prob_dist(rv_continuous): + def _pdf(self, theta): + # The 0.5 is so that the distribution is normalized + return 0.5 * np.sin(theta) + +# Samples of theta should be drawn from between 0 and pi +sin_sampler = sin_prob_dist(a=0, b=np.pi) + +@qml.qnode(dev) +def haar_random_unitary(): + phi, omega = 2 * np.pi * np.random.uniform(size=2) # Sample phi and omega as normal + theta = sin_sampler.rvs(size=1)[0] # Sample theta from our new distribution + qml.Rot(phi, theta, omega, wires=0) + return qml.state() + +haar_samples = [haar_random_unitary() for _ in range(num_samples)] +haar_bloch_vectors = np.array([convert_to_bloch_vector(s) for s in haar_samples]) + +plot_bloch_sphere(haar_bloch_vectors) + +###################################################################### +# We see that when we use the correct measure, our qubit states are now +# much better distributed over the sphere. Putting this information together, +# we can now write the explicit form for the single-qubit Haar measure: +# +# .. math:: +# +# d\mu_2 = \sin \theta d\theta \cdot d\omega \cdot d\phi. +# +# Show me more math! +# ~~~~~~~~~~~~~~~~~~ +# +# While we can easily visualize the single-qubit case, this is no longer +# possible when we increase the number of qubits. Regardless, we can still +# obtain a mathematical expression for the Haar measure in arbitrary +# dimensions. In the previous section, we expressed the Haar measure in terms of +# a set of parameters that can be used to specify the unitary group +# :math:`U(2).` Such a parametrization is not unique, and in fact there are +# multiple ways to *factorize*, or decompose an :math:`N`-dimensional unitary +# operation into a set of parameters. +# +# Many of these parametrizations come to us from the study of photonics. Here, +# arbitrary operations are broken down into elementary operations involving only +# a few parameters which correspond directly to parameters of the physical +# apparatus used to implement them (beamsplitters and phase shifts). Rather than +# qubits, such operations act on modes, or *qumodes*. They are expressed as +# elements of the :math:`N`-dimensional `special unitary group +# `__. This group, written +# as :math:`SU(N),` is the continuous group consisting of all :math:`N \times N` +# unitary operations with determinant 1 (essentially like :math:`U(N),` minus +# a potential global phase). +# +# +# .. note:: +# +# Elements of :math:`SU(N)` and :math:`U(N)` can still be considered as +# multi-qubit operations in the cases where :math:`N` is a power of 2, but +# they must be translated from continuous-variable operations into qubit +# operations. (In PennyLane, this can be done by feeding the unitaries to +# the :class:`~.pennylane.QubitUnitary` operation directly. Alternatively, +# one can use *quantum compilation* to express the operations as a sequence +# of elementary gates such as Pauli rotations and CNOTs.) +# +# .. admonition:: Tip +# +# If you haven't had many opportunities to work in terms of qumodes, the +# `Strawberry Fields documentation +# `__ is a +# good starting point. +# +# For example, we saw already above that for :math:`N=2,` we can write +# +# .. math:: +# +# U(\phi, \theta, \omega) = \begin{pmatrix} e^{-i(\phi + \omega)/2} +# \cos(\theta/2) & -e^{i(\phi - \omega)/2} \sin(\theta/2) +# \\ e^{-i(\phi - \omega)/2} \sin(\theta/2) & e^{i(\phi + +# \omega)/2} \cos(\theta/2) \end{pmatrix}. +# +# +# This unitary can be factorized as follows: +# +# .. math:: +# +# U(\phi, \theta, \omega) = +# \begin{pmatrix} +# e^{-i\omega/2} & 0 \\ 0 & e^{i\omega/2} +# \end{pmatrix} +# \begin{pmatrix} +# \cos(\theta/2) & -\sin(\theta/2) \\ \sin(\theta/2) & \cos(\theta/2) +# \end{pmatrix} +# \begin{pmatrix} +# e^{-i\phi/2} & 0 \\ 0 & e^{i\phi/2} +# \end{pmatrix} +# +# The middle operation is a beamsplitter; the other two operations are phase +# shifts. We saw earlier that for :math:`N=2,` :math:`d\mu_2 = \sin\theta +# d\theta d\omega d\phi`---note how the parameter in the beamsplitter +# contributes to the measure in a different way than those of the phase +# shifts. As mentioned above, for larger values of :math:`N` there are multiple +# ways to decompose the unitary. Such decompositions rewrite elements in +# :math:`SU(N)` acting on :math:`N` modes as a sequence of operations acting +# only on 2 modes, :math:`SU(2),` and single-mode phase shifts. Shown below are +# three examples [#deGuise2018]_, [#Clements2016]_, [#Reck1994]_: +# +# .. figure:: /_static/demonstration_assets/haar_measure/unitaries.png +# :align: center +# :width: 95% +# +# +# In these graphics, every wire is a different mode. Every box represents an +# operation on one or more modes, and the number in the box indicates the number +# of parameters. The boxes containing a ``1`` are simply phase shifts on +# individual modes. The blocks containing a ``3`` are :math:`SU(2)` transforms +# with 3 parameters, such as the :math:`U(\phi, \theta, \omega)` above. Those +# containing a ``2`` are :math:`SU(2)` transforms on pairs of modes with 2 +# parameters, similar to the 3-parameter ones but with :math:`\omega = \phi.` +# +# Although the decompositions all produce the same set of operations, their +# structure and parametrization may have consequences in practice. The first [#deGuise2018]_ +# has a particularly convenient form that leads to a recursive definition +# of the Haar measure. The decomposition is formulated recursively such that an +# :math:`SU(N)` operation can be implemented by sandwiching an :math:`SU(2)` +# transformation between two :math:`SU(N-1)` transformations, like so: +# +# | +# +# .. figure:: /_static/demonstration_assets/haar_measure/sun.svg +# :align: center +# :width: 80% +# +# | +# +# The Haar measure is then constructed recursively as a product of 3 +# terms. The first term depends on the parameters in the first :math:`SU(N-1)` +# transformation; the second depends on the parameters in the lone :math:`SU(2)` +# transformation; and the third term depends on the parameters in the other +# :math:`SU(N-1)` transformation. +# +# :math:`SU(2)` is the "base case" of the recursion—we simply have the Haar measure +# as expressed above. +# +# | +# +# .. figure:: /_static/demonstration_assets/haar_measure/su2_haar.svg +# :align: center +# :width: 25% +# +# | +# +# Moving on up, we can write elements of :math:`SU(3)` as a sequence of three +# :math:`SU(2)` transformations. The Haar measure :math:`d\mu_3` then consists +# of two copies of :math:`d\mu_2,` with an extra term in between to take into +# account the middle transformation. +# +# | +# +# .. figure:: /_static/demonstration_assets/haar_measure/su3_haar.svg +# :align: center +# :width: 80% +# +# | +# +# For :math:`SU(4)` and upwards, the form changes slightly, but still follows +# the pattern of two copies of :math:`d\mu_{N-1}` with a term in between. +# +# | +# +# .. figure:: /_static/demonstration_assets/haar_measure/su4_premerge.svg +# :align: center +# :width: 90% +# +# | +# +# For larger systems, however, the recursive composition allows for some of the +# :math:`SU(2)` transformations on the lower modes to be grouped. We can take +# advantage of this and aggregate some of the parameters: +# +# | +# +# .. figure:: /_static/demonstration_assets/haar_measure/su4_triangle_merge.svg +# :align: center +# :width: 100% +# +# | +# +# This leads to one copy of :math:`d\mu_{N-1},` which we'll denote as +# :math:`d\mu_{N-1}^\prime,` containing only a portion of the full set of terms +# (as detailed in [#deGuise2018]_, this is called a *coset measure*). +# +# | +# +# .. figure:: /_static/demonstration_assets/haar_measure/su4_haar.svg +# :align: center +# :width: 100% +# +# | +# +# Putting everything together, we have that +# +# .. math:: +# +# d\mu_N = d\mu_{N-1}^\prime \times \sin \theta_{N-1} +# \sin^{2(N-2)}\left(\frac{\theta_{N-1}}{2}\right) d\theta_{N-1} d\omega_{N-1} \times d\mu_{N-1} +# +# The middle portion depends on the value of :math:`N,` and the parameters +# :math:`\theta_{N-1}` and :math:`\omega_{N-1}` contained in the :math:`(N-1)`'th +# :math:`SU(N)` transformation. This is thus a convenient, systematic way to +# construct the :math:`N`-dimensional Haar measure for the unitary group. As a +# final note, even though unitaries can be parametrized in different ways, the +# underlying Haar measure is *unique*. This is a consequence of it being an +# invariant measure, as will be shown later. +# +# Haar-random matrices from the :math:`QR` decomposition +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Nice-looking math aside, sometimes you just need to generate a large number of +# high-dimensional Haar-random matrices. It would be very cumbersome to sample +# and keep track of the distributions of so many parameters; furthermore, the +# measure above requires you to parametrize your operations in a fixed way. +# There is a much quicker way to perform the sampling by taking a (slightly +# modified) `QR decomposition +# `__ of complex-valued +# matrices. This algorithm is detailed in [#Mezzadri2006]_, and consists of the +# following steps: +# +# 1. Generate an :math:`N \times N` matrix :math:`Z` with complex numbers :math:`a+bi` +# where both :math:`a` and :math:`b` are normally distributed with mean 0 and variance 1 +# (this is sampling from the distribution known as the *Ginibre ensemble*). +# 2. Compute a QR decomposition :math:`Z = QR.` +# 3. Compute the diagonal matrix :math:`\Lambda = \hbox{diag}(R_{ii}/|R_{ii}|).` +# 4. Compute :math:`Q^\prime = Q \Lambda,` which will be Haar-random. +# +# + +from numpy.linalg import qr + +def qr_haar(N): + """Generate a Haar-random matrix using the QR decomposition.""" + # Step 1 + A, B = np.random.normal(size=(N, N)), np.random.normal(size=(N, N)) + Z = A + 1j * B + + # Step 2 + Q, R = qr(Z) + + # Step 3 + Lambda = np.diag([R[i, i] / np.abs(R[i, i]) for i in range(N)]) + + # Step 4 + return np.dot(Q, Lambda) + +###################################################################### +# Let's check that this method actually generates Haar-random unitaries +# by trying it out for :math:`N=2` and plotting on the Bloch sphere. +# + +@qml.qnode(dev) +def qr_haar_random_unitary(): + qml.QubitUnitary(qr_haar(2), wires=0) + return qml.state() + +qr_haar_samples = [qr_haar_random_unitary() for _ in range(num_samples)] +qr_haar_bloch_vectors = np.array([convert_to_bloch_vector(s) for s in qr_haar_samples]) +plot_bloch_sphere(qr_haar_bloch_vectors) + +###################################################################### +# As expected, we find our qubit states are distributed uniformly over the +# sphere. This particular method is what's implemented in packages like +# ``scipy``'s `unitary_group +# `__ +# function. +# +# Now, it's clear that this method works, but it is also important to +# understand *why* it works. Step 1 is fairly straightforward—the base of our +# samples is a matrix full of complex values chosen from a typical +# distribution. This isn't enough by itself, since unitary matrices also +# have constraints—their rows and columns must be orthonormal. +# These constraints are where step 2 comes in—the outcome of a generic +# QR decomposition consists of an *orthonormal* matrix :math:`Q,` and and upper +# triangular matrix :math:`R.` Since our original matrix was complex-valued, we end +# up with a :math:`Q` that is in fact already unitary. But why not stop there? Why +# do we then perform steps 3 and 4? +# +# Steps 3 and 4 are needed because, while the QR decomposition yields a unitary, +# it is not a unitary that is properly Haar-random. In [#Mezzadri2006]_, it is +# explained that a uniform distribution over unitary matrices should also yield +# a uniform distribution over the *eigenvalues* of those matrices, i.e., every +# eigenvalue should be equally likely. Just using the QR decomposition out of +# the box produces an *uneven* distribution of eigenvalues of the unitaries! +# This discrepancy stems from the fact that the QR decomposition is not unique. +# We can take any unitary diagonal matrix :math:`\Lambda,` and re-express the decomposition +# as :math:`QR = Q\Lambda \Lambda^\dagger R = Q^\prime R^\prime.` Step 3 removes this +# redundancy by fixing a :math:`\Lambda` that depends on :math:`R,` leading to a unique +# value of :math:`Q^\prime = Q \Lambda,` and a uniform distribution of eigenvalues. +# +# .. admonition:: Try it! +# +# Use the ``qr_haar`` function above to generate random unitaries and construct +# a distribution of their eigenvalues. Then, comment out the lines for steps 3 and +# 4 and do the same—you'll find that the distribution is no longer uniform. +# Check out reference [#Mezzadri2006]_ for additional details and examples. + +###################################################################### +# Fun (and not-so-fun) facts +# -------------------------- +# +# We've now learned what the Haar measure is, and both an analytical and +# numerical means of sampling quantum states and unitary operations uniformly at +# random. The Haar measure also has many neat properties that play a role in +# quantum computing. +# +# Invariance of the Haar measure +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Earlier, we showed that the Haar measure is used when integrating functions over +# the unitary group: +# +# .. math:: +# +# \int_{V \in U(N)} f(V) d\mu_N(V). +# +# One of the defining features of the Haar measure is that it is both left and +# right *invariant* under unitary transformations. That is, +# +# .. math:: +# +# \int_{V \in U(N)} f(\color{red}{W}V) d\mu_N(V) = \int_{V \in U(N)} f(V\color{red}{W}) d\mu_N(V) = \int_{V \in U(N)} f(V) d\mu_N(V). +# +# This holds true for *any* other :math:`N\times N` unitary :math:`W!` A +# consequence of such invariance is that if :math:`V` is Haar-random, then so is +# :math:`V^T,` :math:`V^\dagger,` and any product of another unitary matrix and +# :math:`V` (where the product may be taken on either side). +# +# Another consequence of this invariance has to do with the structure of the entries +# themselves: they must all come from the same distribution. This is because the +# measure remains invariant under permutations, since permutations are unitary--- +# the whole thing still has to be Haar random no matter how the entries are ordered, +# so all distributions must be the same. The specific distribution is complex +# numbers :math:`a+bi` where both :math:`a` and :math:`b` has mean 0 and variance +# :math:`1/N` [#Meckes2014]_ (so, much like Ginibre ensemble we used in the QR decomposition +# above, but with a different variance and constraints due to orthonormality). +# +# Concentration of measure +# ~~~~~~~~~~~~~~~~~~~~~~~~ +# +# An unfortunate (although interesting) property of the Haar measure is that it +# suffers from the phenomenon of `concentration of measure +# `__. Most of the +# "stuff" in the space concentrates around a certain area, and this gets worse +# as the size of the system increases. You can see the beginnings of by looking +# at the sphere. For the 3-dimensional sphere, we saw graphically how there is +# concentration around the equator, and how the measure takes that into account +# with the additional factor of :math:`\sin \theta.` This property becomes +# increasingly prominent for `higher-dimensional spheres +# `__. +# +# .. important:: +# +# The concentration described here is not referring to what we witnessed +# earlier on, when we sampled quantum states (points on the Bloch sphere) +# incorrectly and found that they clustered around the poles. However, that +# is not unrelated. Concentration of measure refers to where the measure +# itself is concentrated, and which parts of the space should be more heavily +# weighted. For the case of the sphere, it is the equatorial area, and when +# we didn't sample properly and take that concentration into account, we +# obtained an uneven distribution. +# +# Let's consider an :math:`N`-dimensional unit sphere. Points on the sphere, or +# vectors in this space, are parametrized by :math:`N-1` real coordinates. +# Suppose we have some function :math:`f` that maps points on that sphere to +# real numbers. Sample a point :math:`x` on that sphere from the uniform +# measure, and compute the value of :math:`f(x).` How close do you think the +# result will be to the mean value of the function, :math:`E[f],` over the +# entire sphere? +# +# A result called `Levy's lemma +# `__ +# [#Gerken2013]_, [#Hayden2006]_ expresses how likely it is that :math:`f(x)` is a specific +# distance away from the mean. It states that, for an :math:`x` selected +# uniformly at random, the probability that :math:`f(x)` deviates from +# :math:`E[f]` by some amount :math:`\epsilon` is bounded by: +# +# .. math:: +# +# \hbox{Pr}(|f(x) - E[f]| \ge \epsilon) \leq 2 \exp\left[-\frac{N\epsilon^2}{9\pi^3 \eta^2}\right]. +# +# A constraint on the function :math:`f` is that it must be `Lipschitz +# continuous `__, where +# :math:`\eta` is the *Lipschitz constant* of the function. The important aspect +# here is the likelihood of deviating significantly from the mean by an amount +# :math:`\epsilon` decreases exponentially with :math:`\epsilon.` Furthermore, +# increasing the dimension :math:`N` also makes the deviation exponentially less +# likely. +# +# Now, this result seems unrelated to quantum states—it concerns higher- +# dimensional spheres. However, recall that a quantum state vector is a complex +# vector whose squared values sum to 1, similar to vectors on a sphere. If you +# "unroll" a quantum state vector of dimension :math:`N = 2^n` by stacking its +# real and complex parts, you end with a vector of length :math:`2 \cdot 2^{n}` +# which ends up behaving just like a unit vector on the sphere in this +# dimension. Given that measure concentrates on spheres, and quantum state +# vectors can be converted to vectors on spheres, functions on random quantum +# states will also demonstrate concentration. +# +# This is bad news! To do useful things in quantum computing, we need a lot of +# qubits. But the more qubits we have, the more our randomly sampled states will +# look the same (specifically, random states will concentrate around the +# maximally entangled state [#Hayden2006]_). This has important consequences for +# near-term algorithms (as detailed in the next section), and any algorithm that +# involves uniform sampling of quantum states and operations. +# +# Haar measure and barren plateaus +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Suppose you are venturing out to solve a new problem using an algorithm such +# as the :doc:`variational quantum eigensolver `. A +# critical component of such methods is the choice of :doc:`variational ansatz +# `. Having now learned a bit about the properties of +# the Haar measure, you may think it would make sense to use this for the +# parametrization. Variational ansaetze are, after all, parametrized quantum +# circuits, so why not choose an ansatz that corresponds directly to a +# parametrization for Haar-random unitaries? The initial parameter selection +# will give you a state in the Hilbert space uniformly at random. Then, since +# this ansatz spans the entire Hilbert space, you're guaranteed to be able to +# represent the target ground state with your ansatz, and it should be able to +# find it with no issue ... right? +# +# Unfortunately, while such an ansatz is extremely *expressive* (i.e., it is +# capable of representing any possible state), these ansaetze actually suffer +# the most from the barren plateau problem [#McClean2018]_, [#Holmes2021]_. +# :doc:`Barren plateaus ` are regions in the +# cost landscape of a parametrized circuit where both the gradient and its +# variance approach 0, leading the optimizer to get stuck in a local minimum. +# This was explored recently in the work of [#Holmes2021]_, wherein closeness to +# the Haar measure was actually used as a metric for expressivity. The closer +# things are to the Haar measure, the more expressive they are, but they are +# also more prone to exhibiting barren plateaus. +# +# +# .. figure:: /_static/demonstration_assets/haar_measure/holmes-costlandscapes.png +# :align: center +# :width: 50% +# +# Image source: [#Holmes2021]_. A highly expressive ansatz that can access +# much of the space of possible unitaries (i.e., an ansatz capable of +# producing unitaries in something close to a Haar-random manner) is very +# likely to have flat cost landscapes and suffer from the barren plateau +# problem. +# +# It turns out that the types of ansaetze know as *hardware-efficient ansaetze* +# also suffer from this problem if they are "random enough" (this notion will be +# formalized in a future demo). It was shown in [#McClean2018]_ that this is a +# consequence of the concentration of measure phenomenon described above. The +# values of gradients and variances can be computed for classes of circuits on +# average by integrating with respect to the Haar measure, and it is shown that +# these values decrease exponentially in the number of qubits, and thus huge +# swaths of the cost landscape are simply and fundamentally flat. +# +# Conclusion +# ---------- +# +# The Haar measure plays an important role in quantum computing—anywhere +# you might be dealing with sampling random circuits, or averaging over +# all possible unitary operations, you'll want to do so with respect +# to the Haar measure. +# +# There are two important aspects of this that we have yet to touch upon, +# however. The first is whether it is efficient to sample from the Haar measure—given +# that the number of parameters to keep track of is exponential in the +# number of qubits, certainly not. But a more interesting question is do we +# *need* to always sample from the full Haar measure? The answer to this is +# "no" in a very interesting way. Depending on the task at hand, you may be able +# to take a shortcut using something called a *unitary design*. In an upcoming +# demo, we will explore the amazing world of unitary designs and their +# applications! +# +# References +# ---------- +# +# .. [#NandC2000] +# +# M. A. Nielsen, and I. L. Chuang (2000) "Quantum Computation and Quantum Information", +# Cambridge University Press. +# +# .. [#deGuise2018] +# +# H. de Guise, O. Di Matteo, and L. L. Sánchez-Soto. (2018) "Simple factorization +# of unitary transformations", `Phys. Rev. A 97 022328 +# `__. +# (`arXiv `__) +# +# .. [#Clements2016] +# +# W. R. Clements, P. C. Humphreys, B. J. Metcalf, W. S. Kolthammer, and +# I. A. Walmsley (2016) “Optimal design for universal multiport +# interferometers”, \ `Optica 3, 1460–1465 +# `__. +# (`arXiv `__) +# +# .. [#Reck1994] +# +# M. Reck, A. Zeilinger, H. J. Bernstein, and P. Bertani (1994) “Experimental +# realization of any discrete unitary operator”, `Phys. Rev. Lett.73, 58–61 +# `__. +# +# .. [#Mezzadri2006] +# +# F. Mezzadri (2006) "How to generate random matrices from the classical compact groups". +# (`arXiv `__) +# +# .. [#Meckes2014] +# +# E. Meckes (2019) `"The Random Matrix Theory of the Classical Compact Groups" +# `_, Cambridge University Press. +# +# .. [#Gerken2013] +# +# M. Gerken (2013) "Measure concentration: Levy's Lemma" +# (`lecture notes `__). +# +# +# .. [#Hayden2006] +# +# P. Hayden, D. W. Leung, and A. Winter (2006) "Aspects of generic +# entanglement", `Comm. Math. Phys. Vol. 265, No. 1, pp. 95-117 +# `__. +# (`arXiv `__) +# +# .. [#McClean2018] +# +# J. R. McClean, S. Boixo, V. N. Smelyanskiy, R. Babbush, and H. Neven +# (2018) "Barren plateaus in quantum neural network training +# landscapes", `Nature Communications, 9(1) +# `__. +# (`arXiv `__) +# +# .. [#Holmes2021] +# +# Z. Holmes, K. Sharma, M. Cerezo, and P. J. Coles (2021) "Connecting ansatz +# expressibility to gradient magnitudes and barren plateaus". (`arXiv +# `__) # \ No newline at end of file diff --git a/demonstrations_v2/tutorial_here_comes_the_sun/demo.py b/demonstrations_v2/tutorial_here_comes_the_sun/demo.py index 5278e01fd1..88da27d3e5 100644 --- a/demonstrations_v2/tutorial_here_comes_the_sun/demo.py +++ b/demonstrations_v2/tutorial_here_comes_the_sun/demo.py @@ -1,576 +1,576 @@ -r""" - -Here comes the SU(N): multivariate quantum gates and gradients -============================================================== - -.. meta:: - :property="og:description": Learn about multivariate quantum gates for optimization - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_here_comes_the_sun.png - -.. related:: - - tutorial_vqe A brief overview of VQE - tutorial_general_parshift General parameter-shift rules for quantum gradients - tutorial_unitary_designs Unitary designs and their uses in quantum computing - - -*Author: David Wierichs — Posted: 03 April 2023.* - -How do we choose an ansatz when designing a quantum circuit for a variational -quantum algorithm? And what happens if we do not start with elementary hardware-friendly -gates and compose them, but we instead use a more complex building block for local qubit -interactions and allow for multi-parameter gates from the start? -Can we differentiate such circuits, and how do they perform in optimization tasks? - -Let's find out! - -In this tutorial, you will learn about the :math:`\mathrm{SU}(N)` gate -:class:`~pennylane.SpecialUnitary`, a particular quantum gate which -can act like *any* gate on its qubits by choosing the parameters accordingly. -We will look at a custom derivative rule [#wiersema]_ for this gate and compare it to two -alternative differentiation strategies, namely finite differences and the `stochastic -parameter-shift rule `_. -Finally, we will compare the performance of -``qml.SpecialUnitary`` for a toy minimization problem to that of two other general -local gates. That is, we compare the trainability of equally expressive ansätze. - -Ansätze, so many ansätze ------------------------- - -Variational quantum algorithms have been promoted to be useful for many applications. -When designing these algorithms, a central task is to choose the quantum circuit ansatz, -which provides a parameterization of quantum states. In the course of a variational algorithm, -the circuit parameters are then optimized in order to minimize some cost function. -The choice of the ansatz can have a big impact on the quantum states that can be found -by the algorithm (expressivity) and on the optimization's behaviour (trainability). - -Typically, it also affects the -computational cost of executing the algorithm on quantum hardware and the strength of the noise -that enters the computation. Finally, the application itself influences, or -even fixes, the choice of ansatz for some variational quantum algorithms, -which can lead to constraints in the ansatz design. - -.. figure:: ../_static/demonstration_assets/here_comes_the_sun/SUN_demo_Ansatz.png - :align: center - :width: 90% - -While a number of best practices for ansatz design have been developed, -a lot is still unknown about the connection between circuit structures and the -resulting properties. Therefore, circuit design is often also based on intuition or heuristics; -an ansatz reported in the literature might just have turned out -to work particularly well for a given problem or might fall into a "standard" -category of circuits. - -If the application does not constrain the choice of ansatz, we may want to avoid choosing -somewhat arbitrary circuit ansätze that may introduce undesirable biases. -Instead, we will want to reflect the generic structure of the problem by performing a -fully general operation on the qubit register. -However, if we were to do so, the number of parameters required to produce such a general -operation would grow much too quickly. Instead, we want to consider fully general operations -*on a few qubits* and compose them into a fabric of local gates. For two-qubit operations, -the fabric could look like this: - -.. figure:: ../_static/demonstration_assets/here_comes_the_sun/SUN_demo_SU4.png - :align: center - :width: 60% - -The general local operation can be implemented by composing a suitable combination -of elementary gates, like single-qubit rotations and CNOT gates. Alternatively, we may -choose a canonical parameterization of the group that contains all local operations, and we will -see that this is an advantageous approach for the trainability of the ansatz. - -.. figure:: ../_static/demonstration_assets/here_comes_the_sun/SUN_demo_optimization.png - :align: center - :width: 60% - -Before we can use the :math:`\mathrm{SU}(N)` gate in training, we will need to -learn how to differentiate it in a quantum circuit. But first things first: -let's start with a brief math intro — no really, just a *Liettle* bit. - -The special unitary group SU(N) and its Lie algebra ---------------------------------------------------- - -The gate we will look at is given by a specific parameterization of the -`special unitary group `__ -:math:`\mathrm{SU}(N),` where :math:`N=2^n` is the Hilbert space dimension of the gate -for :math:`n` qubits. Mathematically, the group can be defined as the set of operators -(or matrices) that can be inverted by taking their adjoint and that have -determinant :math:`1.` In general, all quantum gates acting on :math:`n` qubits are -elements of :math:`\mathrm{SU}(N)` up to a global phase. - -The group :math:`\mathrm{SU}(N)` is a `Lie group `__, -and its associated `Lie algebra `__ -is :math:`\mathfrak{su}(N).` For our purposes, it will be sufficient to look at a matrix -representation of the algebra and we may define it as - -.. math:: - - \mathfrak{su}(N) = - \{\Omega \in \mathbb{C}^{N\times N}: \Omega^\dagger=-\Omega, \operatorname{Tr}[\Omega]=0\}. - -The conditions are that the elements :math:`\Omega` are *skew-Hermitian* and that their trace vanishes. -We will use so-called canonical coordinates for the algebra which are simply the coefficients -in the Pauli basis. That is, we consider the Pauli basis elements multiplied with the -imaginary unit :math:`i,` except for the identity: - -.. math:: - - G_m \in \mathcal{P}^{(n)} = i \left\{I,X,Y,Z\right\}^n \setminus \{i I^n\}. - -A Lie algebra element :math:`\Omega` can be written as - -.. math:: - - \Omega = \sum_{m=1}^d \theta_m G_m,\quad \theta_m \in \mathbb{R} - -and those coefficients :math:`\theta` are precisely the canonical coordinates. -You may ask why we included the prefactor :math:`i` in the definition of :math:`G_m` and why we excluded -the identity (times :math:`i`). This was done to match the properties of :math:`\mathfrak{su}(N);` -the prefactor makes the basis elements skew-Hermitian and the identity would not have a -vanishing trace. Indeed, one can check that the dimension of :math:`\mathfrak{su}(N)` is -:math:`4^n-1` and that there are :math:`4^n` Pauli words, so that one Pauli word — the identity — had to go -in any case... We can use the canonical coordinates of the algebra to express a *group element* in -:math:`\mathrm{SU}(N)` as well, and the ``qml.SpecialUnitary`` gate we will use is defined as - -.. math:: - - U(\boldsymbol{\theta}) = \exp\left\{\sum_{m=1}^d \theta_m G_m \right\}. - -The number of coordinates and Pauli words in :math:`\mathcal{P}^{(n)}` is :math:`d=4^n-1.` -Therefore, this will be the number of parameters that a single ``qml.SpecialUnitary`` gate acting on -:math:`n` qubits will take. For example, it takes just three parameters for a single qubit, which -is why :class:`~pennylane.Rot` and :class:`~pennylane.U3` take three parameters and may -produce *any* single-qubit rotation. It takes a modest 15 parameters for two qubits, -but it already requires 63 parameters for three qubits. - -For unitaries generated by a single operator, i.e. of the form :math:`\exp(i\theta G),` -there is a plethora of differentiation techniques that allow us to compute its derivative. -However, a standard parameter-shift rule, for example, will not do the job if there are -non-commuting terms :math:`G_m` in the multi-parameter gate :math:`U(\boldsymbol{\theta})` above. -So how *do* we compute the derivative? - -Obtaining the gradient ----------------------- - -In variational quantum algorithms, we typically use the circuit to prepare a quantum state and -then we measure some observable :math:`H.` The resulting real-valued output is considered to be the -cost function :math:`C` that should be minimized. If we want to use gradient-based optimization for -this task, we need a method to compute the gradient :math:`\nabla C` in addition to the cost -function itself. As derived in the publication [#wiersema]_, this is possible on quantum hardware -for :math:`\mathrm{SU}(N)` gates as long as the gates themselves can be implemented. -The implementation in PennyLane follows the decomposition idea described in App. F3, but the -main text of [#wiersema]_ proposes an additional method that scales better in some scenarios -(the caveat being that this method requires additional gates to be available on the quantum hardware). -Here, we will focus on the former method. -We will not go through the entire derivation, but note the following key points: - -- The gradient with respect to all :math:`d` parameters of an :math:`\mathrm{SU}(N)` gate can be - computed using :math:`2d` auxiliary circuits. Each of the circuits contains one additional - operation compared to the original circuit, namely a ``qml.PauliRot`` gate with rotation - angles of :math:`\pm\frac{\pi}{2}.` Note that these Pauli rotations act on up to :math:`n` - qubits. -- This differentiation method uses automatic differentiation during compilation and - classical coprocessing steps, but is compatible with quantum hardware. For large :math:`n,` - the classical processing steps can quickly become prohibitively expensive. -- The computed gradient is not an approximative technique but allows for an exact computation - of the gradient on simulators. On quantum hardware, this leads to unbiased gradient - estimators. - -The implementation in PennyLane takes care of creating the additional circuits and evaluating -them, and with adequate post-processing we get the gradient :math:`\nabla C.` - -Comparing gradient methods --------------------------- - -Before we dive into using ``qml.SpecialUnitary`` in an optimization task, let's compare -a few methods to compute the gradient with respect to the parameters of such a gate. -In particular, we will look at a finite difference (FD) approach, the stochastic parameter-shift -rule, and the custom gradient method we described above. - -For the first approach, we will use the standard central difference recipe given by - -.. math:: - - \partial_{\text{FD},\theta_j}C(\boldsymbol{\theta}) - =\left[C\left(\boldsymbol{\theta}+\frac{\delta}{2}\boldsymbol{e}_j\right) - -C\left(\boldsymbol{\theta}-\frac{\delta}{2}\boldsymbol{e}_j\right)\right] / \delta. - -Here, :math:`\delta` is a shift parameter that we need to choose and :math:`\boldsymbol{e}_j` is the -:math:`j`-th canonical basis vector, i.e. the all-zeros vector with a one in the -:math:`j`-th entry. This approach is agnostic to the differentiated function and does -not exploit its structure. - -In contrast, the stochastic parameter-shift rule is a differentiation recipe developed particularly -for multi-parameter gates like the :math:`\mathrm{SU}(N)` gates [#banchi]_. It involves the -approximate evaluation of an integral by sampling *splitting times* :math:`\tau` and -evaluating an expression close to the non-stochastic parameter-shift rule for each sample. -For more details, also consider the -:doc:`demo on the stochastic parameter-shift rule `. - -So, let's dive into a toy example and explore the three gradient methods! -We start by defining a simple one-qubit circuit that contains a single :math:`\mathrm{SU}(2)` -gate and measures the expectation value of :math:`H=\frac{3}{5} Z - \frac{4}{5} Y.` -As ``qml.SpecialUnitary`` requires automatic differentiation subroutines even for the -hardware-ready derivative recipe, we will make use of JAX. -""" - -import pennylane as qml -import numpy as np -import jax - -jax.config.update("jax_enable_x64", True) -jax.config.update("jax_platform_name", "cpu") -jnp = jax.numpy - -dev = qml.device("default.qubit", wires=1) -H = 0.6 * qml.PauliZ(0) - 0.8 * qml.PauliY(0) - - -def qfunc(theta): - qml.SpecialUnitary(theta, wires=0) - return qml.expval(H) - - -circuit = qml.QNode(qfunc, dev, interface="jax", diff_method="parameter-shift") - -theta = jnp.array([0.4, 0.2, -0.5]) - -############################################################################## -# Now we need to set up the differentiation methods. For this demonstration, we will -# keep the first and last entry of ``theta`` fixed and only compute the gradient for the -# second parameter. This allows us to visualize the results easily and keeps the -# computational effort to a minimum. -# -# We start with the finite-difference -# recipe, using a shift scale of :math:`\delta=0.75.` This choice of :math:`\delta,` -# which is much larger than usual for numerical differentiation on classical computers, -# is adapted to the scenario of shot-based gradients (see App. F2 of [#wiersema]_). -# We compute the derivative with respect to the second entry of theta, so we will use -# the unit vector :math:`e_2:` - -unit_vector = np.array([0.0, 1.0, 0.0]) - - -def central_diff_grad(theta, delta): - plus_eval = circuit(theta + delta / 2 * unit_vector) - minus_eval = circuit(theta - delta / 2 * unit_vector) - return (plus_eval - minus_eval) / delta - - -delta = 0.75 -print(f"Central difference: {central_diff_grad(theta, delta):.5f}") - -############################################################################## -# Next up, we implement the stochastic parameter-shift rule. Of course we do not do -# so in full generality, but for the particular circuit in this example. We will -# sample ten splitting times to obtain the gradient entry. For each splitting time, -# we need to insert a Pauli-:math:`Y` rotation because :math:`\theta_2` belongs to -# the Pauli-:math:`Y` component of :math:`A(\boldsymbol{\theta}).` For this, we define -# an auxiliary circuit. - - -@jax.jit -@qml.qnode(dev, interface="jax") -def aux_circuit(theta, tau, sign): - qml.SpecialUnitary(tau * theta, wires=0) - # This corresponds to the parameter-shift evaluations of RY at 0 - qml.RY(-sign * np.pi / 2, wires=0) - qml.SpecialUnitary((1 - tau) * theta, wires=0) - return qml.expval(H) - - -def stochastic_parshift_grad(theta, num_samples): - grad = 0 - splitting_times = np.random.random(size=num_samples) - for tau in splitting_times: - # Evaluate the two-term parameter-shift rule of the auxiliar circuit - grad += aux_circuit(theta, tau, 1.0) - aux_circuit(theta, tau, -1.0) - return grad / num_samples - - -num_samples = 10 -print(f"Stochastic parameter-shift: {stochastic_parshift_grad(theta, num_samples):.5f}") - -############################################################################## -# Finally, we can make use of the custom parameter-shift rule introduced in -# [#wiersema]_, which is readily available in PennyLane. Due to the implementation -# chosen internally, the full gradient is returned; we need to pick the second -# gradient entry manually. For this small toy problem, this is -# not an issue. - -sun_grad = jax.grad(circuit) -print(f"Custom SU(N) gradient: {sun_grad(theta)[1]:.5f}") - -############################################################################## -# We obtained three values for the gradient of interest, and they do not agree. -# So what is going on here? First, let's use automatic differentiation to compute -# the exact value and see which method agrees with it (we again need to extract the -# corresponding entry from the full gradient). - -autodiff_circuit = qml.QNode(qfunc, dev, interface="jax", diff_method="parameter-shift") -exact_grad = jax.grad(autodiff_circuit)(theta)[1] -print(f"Exact gradient: {exact_grad:.5f}") - -############################################################################## -# As we can see, automatic differentiation confirmed that the custom differentiation method -# gave us the correct result. Why do the other methods disagree? -# This is because the finite difference recipe is an *approximate* gradient -# method. This means it has an error even if all circuit evaluations are -# made exact (up to numerical precision) like in the example above. -# As for the stochastic parameter-shift rule, you may already guess why there is -# a deviation: indeed, the *stochastic* nature of this method leads to derivative -# values that are scattered around the true value. It is an unbiased estimator, -# so the average will approach the exact value with increasingly many evaluations. -# To demonstrate this, let's compute the same derivative many times and plot -# a histogram of what we get. We'll do so for ``num_samples=2``, ``10`` and ``100``. - -import matplotlib.pyplot as plt - -plt.rcParams.update({"font.size": 12}) - -fig, ax = plt.subplots(1, 1, figsize=(6, 4)) -colors = ["#ACE3FF", "#FF87EB", "#FFE096"] -for num_samples, color in zip([2, 10, 100], colors): - grads = [stochastic_parshift_grad(theta, num_samples) for _ in range(1000)] - ax.hist(grads, label=f"{num_samples} samples", alpha=0.9, color=color) -ylim = ax.get_ylim() -ax.plot([exact_grad] * 2, ylim, ls="--", c="k", label="Exact") -ax.set(xlabel=r"$\partial_{SPS,\theta_2}C(\theta)$", ylabel="Frequency", ylim=ylim) -ax.legend(loc="upper left") -plt.tight_layout() -plt.show() - -############################################################################## -# As we can see, the stochastic parameter-shift rule comes with a variance -# that can be reduced at the additional cost of evaluating the auxiliary circuit -# for more splitting times. -# -# On quantum hardware, all measurement results are statistical in nature anyway. -# So how does this stochasticity combine with the -# three differentiation methods? We will not go into detail here, but refer -# to [#wiersema]_ to see how the custom differentiation rule proposed in the -# main text leads to the lowest mean squared error. For a single-qubit circuit -# similar to the one above, but with the single gate :math:`U(\boldsymbol{\theta})=\exp(iaX+ibY),` -# the derivative and its expected variance are shown in the following -# (recoloured) plot from the manuscript: -# -# .. figure:: ../_static/demonstration_assets/here_comes_the_sun/sampled_grad.png -# :align: center -# :width: 70% -# -# As we can see, the custom :math:`\mathrm{SU}(N)` parameter-shift rule produces the -# gradient estimates with the smallest variance. For small values of the parameter -# :math:`b,` which is fixed for each panel, the custom shift rule and the stochastic -# shift rule approach the standard two-term parameter-shift rule, which would be exact -# for :math:`b=0.` -# The finite difference gradient shown here was obtained using the shift -# scale :math:`\delta=0.75,` as well. As we can see, this suppresses the variance down to -# a level comparable to those of the shift rule derivatives and this shift scale is a -# reasonable trade-off between the variance and the systematic error we observed earlier. -# As shown in App. F3 of [#wiersema]_, this scale is indeed close to the optimal choice -# if we were to compute the gradient with 100 shots per circuit. -# -# Comparing ansatz structures -# --------------------------- -# -# We discussed above that there are many circuit architectures available and that choosing -# a suitable ansatz is important but can be difficult. Here, we will compare a simple ansatz -# based on the ``qml.SpecialUnitary`` gate discussed above to other approaches that fully -# parametrize the special unitary group for the respective number of qubits. -# In particular, we will compare ``qml.SpecialUnitary`` to standard decompositions from the -# literature that parametrize :math:`\mathrm{SU}(N)` with elementary gates, as well as to a -# sequence of Pauli rotation gates that also allows us to create any special unitary. -# Let us start by defining the decomposition of a two-qubit unitary. -# We choose the decomposition, which is optimal but not unique, from [#vatan]_. -# The Pauli rotation sequence is available in PennyLane -# via ``qml.ArbitraryUnitary`` and we will not need to implement it ourselves. - - -def two_qubit_decomp(params, wires): - """Implement an arbitrary SU(4) gate on two qubits - using the decomposition from Theorem 5 in - https://arxiv.org/pdf/quant-ph/0308006.pdf""" - i, j = wires - # Single U(2) parameterization on both qubits separately - qml.Rot(*params[:3], wires=i) - qml.Rot(*params[3:6], wires=j) - qml.CNOT(wires=[j, i]) # First CNOT - qml.RZ(params[6], wires=i) - qml.RY(params[7], wires=j) - qml.CNOT(wires=[i, j]) # Second CNOT - qml.RY(params[8], wires=j) - qml.CNOT(wires=[j, i]) # Third CNOT - # Single U(2) parameterization on both qubits separately - qml.Rot(*params[9:12], wires=i) - qml.Rot(*params[12:15], wires=j) - - -# The three building blocks on two qubits we will compare are: -operations = { - ("Decomposition", "decomposition"): two_qubit_decomp, - ("PauliRot sequence",) * 2: qml.ArbitraryUnitary, - ("$\mathrm{SU}(N)$ gate", "SU(N) gate"): qml.SpecialUnitary, -} - -############################################################################## -# Now that we have the template for the composition approach in place, we construct a toy -# problem to solve using the ansätze. We will sample a random Hamiltonian in the Pauli basis -# (this time without the prefactor :math:`i,` as we want to construct a Hermitian operator) -# with independent coefficients that follow a normal distribution: -# -# .. math:: -# -# H = \sum_{m=1}^d h_m G_m,\quad h_m\sim \mathcal{N}(0,1). -# -# We will work with six qubits. - -num_wires = 6 -wires = list(range(num_wires)) -np.random.seed(62213) - -coefficients = np.random.randn(4**num_wires - 1) -# Create the matrices for the entire Pauli basis -basis = qml.ops.qubit.special_unitary.pauli_basis_matrices(num_wires) -# Construct the Hamiltonian from the normal random coefficients and the basis -H_matrix = qml.math.tensordot(coefficients, basis, axes=[[0], [0]]) -H = qml.Hermitian(H_matrix, wires=wires) -# Compute the ground state energy -E_min = min(qml.eigvals(H)) -print(f"Ground state energy: {E_min:.5f}") - -############################################################################## -# Using the toy problem Hamiltonian and the three ansätze for :math:`\mathrm{SU}(N)` operations -# from above, we create a circuit template that applies these operations in a brick-layer -# architecture with two blocks and each operation acting on ``loc=2`` qubits. -# For this we define a ``QNode``: - -loc = 2 -d = loc**4 - 1 # d = 15 for two-qubit operations -dev = qml.device("default.qubit", wires=num_wires) -# two blocks with two layers. Each layer contains three operations with d parameters -param_shape = (2, 2, 3, d) -init_params = np.zeros(param_shape) - - -def circuit(params, operation=None): - """Apply an operation in a brickwall-like pattern to a qubit register and measure H. - Parameters are assumed to have the dimensions (number of blocks, number of - wires per operation, number of operations per layer, and number of parameters - per operation), in that order. - """ - for params_block in params: - for i, params_layer in enumerate(params_block): - for j, params_op in enumerate(params_layer): - wires_op = [w % num_wires for w in range(loc * j + i, loc * (j + 1) + i)] - operation(params_op, wires_op) - return qml.expval(H) - - -qnode = qml.QNode(circuit, dev, interface="jax") -print(qml.draw(qnode)(init_params, qml.SpecialUnitary)) - -############################################################################## -# We can now proceed to prepare the optimization task using this circuit -# and an optimization routine of our choice. For simplicity, we run a vanilla gradient -# descent optimization with a fixed learning rate for 500 steps. Again, we use JAX - -# for auto-differentiation. - -learning_rate = 5e-4 -num_steps = 500 -init_params = jax.numpy.array(init_params) -grad_fn = jax.jit(jax.jacobian(qnode), static_argnums=1) -qnode = jax.jit(qnode, static_argnums=1) - -############################################################################## -# With this configuration, let's run the optimization! - -energies = {} -for (name, print_name), operation in operations.items(): - print(f"Running the optimization for the {print_name}") - params = init_params.copy() - energy = [] - for step in range(num_steps): - cost = qnode(params, operation) - params = params - learning_rate * grad_fn(params, operation) - energy.append(cost) # Store energy value - if step % 50 == 0: # Report current energy - print(f"{step:3d} Steps: {cost:.6f}") - - energy.append(qnode(params, operation)) # Final energy value - energies[name] = energy - -############################################################################## -# So, did it work? Judging from the intermediate energy values, it seems that the optimization -# outcomes differ notably. But let's take a look at the relative error in energy across the -# optimization process. - -fig, ax = plt.subplots(1, 1) -styles = [":", "--", "-"] -colors = ["#70CEFF", "#C756B2", "#FFE096"] -for (name, energy), c, ls in zip(energies.items(), colors, styles): - error = (energy - E_min) / abs(E_min) - ax.plot(list(range(len(error))), error, label=name, c=c, ls=ls, lw=2.5) - -ax.set(xlabel="Iteration", ylabel="Relative error") -ax.legend() -plt.show() - -############################################################################## -# We find that the optimization indeed performs significantly better for ``qml.SpecialUnitary`` -# than for the other two general unitaries, while using the same number of parameters and -# preserving the expressibility of the circuit ansatz. This -# means that we found a particularly well-trainable parameterization of the local unitaries which -# allows us to reduce the energy of the prepared quantum state more easily while maintaining the -# number of parameters. -# -# -# Conclusion -# ---------- -# -# To summarize, in this tutorial we introduced ``qml.SpecialUnitary``, a multi-parameter -# gate that can act like *any* gate on the qubits it is applied to and that is constructed -# with Lie theory in mind. We discussed three methods of differentiating quantum circuits -# that use this gate, showing that a new custom parameter-shift rule presented in -# [#wiersema]_ is particularly suitable to produce unbiased gradient estimates with the -# lowest variance. Afterwards, we used this differentiation technique when comparing -# the performance of ``qml.SpecialUnitary`` to that of other gates that can act -# like *any* gate locally. For this, we ran a gradient-based optimization for a toy model -# Hamiltonian and found that ``qml.SpecialUnitary`` is particularly well-trainable, achieving -# lower energies significantly quicker than the other tested gates. -# -# There are still exciting questions to answer about ``qml.SpecialUnitary``: How can the -# custom parameter-shift rule be used for other gates, and what does the so-called -# *Dynamical Lie algebra* of these gates have to do with it? How can we implement -# the ``qml.SpecialUnitary`` gate on hardware? Is the unitary time evolution implemented -# by this gate special in a physical sense? -# -# The answers to some, but not all, of these questions can be found in [#wiersema]_. -# We are certain that there are many more interesting aspects of this gate to be uncovered! -# If you want to learn more, consider the other literature references below, -# as well as the documentation of :class:`~pennylane.SpecialUnitary`. -# -# References -# ---------- -# -# .. [#vatan] -# -# Farrokh Vatan and Colin Williams, -# "Optimal Quantum Circuits for General Two-Qubit Gates", -# `arXiv:quant-ph/0308006 `__ (2003). -# -# .. [#wiersema] -# -# R. Wiersema, D. Lewis, D. Wierichs, J. F. Carrasquilla, and N. Killoran. -# "Here comes the SU(N): multivariate quantum gates and gradients" -# `arXiv:2303.11355 `__ (2023). -# -# .. [#banchi] -# -# Leonardo Banchi and Gavin E. Crooks. "Measuring Analytic Gradients of -# General Quantum Evolution with the Stochastic Parameter Shift Rule." -# `Quantum 5, 386 `__ (2021). -# -# About the author -# ---------------- -# +r""" + +Here comes the SU(N): multivariate quantum gates and gradients +============================================================== + +.. meta:: + :property="og:description": Learn about multivariate quantum gates for optimization + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_here_comes_the_sun.png + +.. related:: + + tutorial_vqe A brief overview of VQE + tutorial_general_parshift General parameter-shift rules for quantum gradients + tutorial_unitary_designs Unitary designs and their uses in quantum computing + + +*Author: David Wierichs — Posted: 03 April 2023.* + +How do we choose an ansatz when designing a quantum circuit for a variational +quantum algorithm? And what happens if we do not start with elementary hardware-friendly +gates and compose them, but we instead use a more complex building block for local qubit +interactions and allow for multi-parameter gates from the start? +Can we differentiate such circuits, and how do they perform in optimization tasks? + +Let's find out! + +In this tutorial, you will learn about the :math:`\mathrm{SU}(N)` gate +:class:`~pennylane.SpecialUnitary`, a particular quantum gate which +can act like *any* gate on its qubits by choosing the parameters accordingly. +We will look at a custom derivative rule [#wiersema]_ for this gate and compare it to two +alternative differentiation strategies, namely finite differences and the `stochastic +parameter-shift rule `_. +Finally, we will compare the performance of +``qml.SpecialUnitary`` for a toy minimization problem to that of two other general +local gates. That is, we compare the trainability of equally expressive ansätze. + +Ansätze, so many ansätze +------------------------ + +Variational quantum algorithms have been promoted to be useful for many applications. +When designing these algorithms, a central task is to choose the quantum circuit ansatz, +which provides a parameterization of quantum states. In the course of a variational algorithm, +the circuit parameters are then optimized in order to minimize some cost function. +The choice of the ansatz can have a big impact on the quantum states that can be found +by the algorithm (expressivity) and on the optimization's behaviour (trainability). + +Typically, it also affects the +computational cost of executing the algorithm on quantum hardware and the strength of the noise +that enters the computation. Finally, the application itself influences, or +even fixes, the choice of ansatz for some variational quantum algorithms, +which can lead to constraints in the ansatz design. + +.. figure:: ../_static/demonstration_assets/here_comes_the_sun/SUN_demo_Ansatz.png + :align: center + :width: 90% + +While a number of best practices for ansatz design have been developed, +a lot is still unknown about the connection between circuit structures and the +resulting properties. Therefore, circuit design is often also based on intuition or heuristics; +an ansatz reported in the literature might just have turned out +to work particularly well for a given problem or might fall into a "standard" +category of circuits. + +If the application does not constrain the choice of ansatz, we may want to avoid choosing +somewhat arbitrary circuit ansätze that may introduce undesirable biases. +Instead, we will want to reflect the generic structure of the problem by performing a +fully general operation on the qubit register. +However, if we were to do so, the number of parameters required to produce such a general +operation would grow much too quickly. Instead, we want to consider fully general operations +*on a few qubits* and compose them into a fabric of local gates. For two-qubit operations, +the fabric could look like this: + +.. figure:: ../_static/demonstration_assets/here_comes_the_sun/SUN_demo_SU4.png + :align: center + :width: 60% + +The general local operation can be implemented by composing a suitable combination +of elementary gates, like single-qubit rotations and CNOT gates. Alternatively, we may +choose a canonical parameterization of the group that contains all local operations, and we will +see that this is an advantageous approach for the trainability of the ansatz. + +.. figure:: ../_static/demonstration_assets/here_comes_the_sun/SUN_demo_optimization.png + :align: center + :width: 60% + +Before we can use the :math:`\mathrm{SU}(N)` gate in training, we will need to +learn how to differentiate it in a quantum circuit. But first things first: +let's start with a brief math intro — no really, just a *Liettle* bit. + +The special unitary group SU(N) and its Lie algebra +--------------------------------------------------- + +The gate we will look at is given by a specific parameterization of the +`special unitary group `__ +:math:`\mathrm{SU}(N),` where :math:`N=2^n` is the Hilbert space dimension of the gate +for :math:`n` qubits. Mathematically, the group can be defined as the set of operators +(or matrices) that can be inverted by taking their adjoint and that have +determinant :math:`1.` In general, all quantum gates acting on :math:`n` qubits are +elements of :math:`\mathrm{SU}(N)` up to a global phase. + +The group :math:`\mathrm{SU}(N)` is a `Lie group `__, +and its associated `Lie algebra `__ +is :math:`\mathfrak{su}(N).` For our purposes, it will be sufficient to look at a matrix +representation of the algebra and we may define it as + +.. math:: + + \mathfrak{su}(N) = + \{\Omega \in \mathbb{C}^{N\times N}: \Omega^\dagger=-\Omega, \operatorname{Tr}[\Omega]=0\}. + +The conditions are that the elements :math:`\Omega` are *skew-Hermitian* and that their trace vanishes. +We will use so-called canonical coordinates for the algebra which are simply the coefficients +in the Pauli basis. That is, we consider the Pauli basis elements multiplied with the +imaginary unit :math:`i,` except for the identity: + +.. math:: + + G_m \in \mathcal{P}^{(n)} = i \left\{I,X,Y,Z\right\}^n \setminus \{i I^n\}. + +A Lie algebra element :math:`\Omega` can be written as + +.. math:: + + \Omega = \sum_{m=1}^d \theta_m G_m,\quad \theta_m \in \mathbb{R} + +and those coefficients :math:`\theta` are precisely the canonical coordinates. +You may ask why we included the prefactor :math:`i` in the definition of :math:`G_m` and why we excluded +the identity (times :math:`i`). This was done to match the properties of :math:`\mathfrak{su}(N);` +the prefactor makes the basis elements skew-Hermitian and the identity would not have a +vanishing trace. Indeed, one can check that the dimension of :math:`\mathfrak{su}(N)` is +:math:`4^n-1` and that there are :math:`4^n` Pauli words, so that one Pauli word — the identity — had to go +in any case... We can use the canonical coordinates of the algebra to express a *group element* in +:math:`\mathrm{SU}(N)` as well, and the ``qml.SpecialUnitary`` gate we will use is defined as + +.. math:: + + U(\boldsymbol{\theta}) = \exp\left\{\sum_{m=1}^d \theta_m G_m \right\}. + +The number of coordinates and Pauli words in :math:`\mathcal{P}^{(n)}` is :math:`d=4^n-1.` +Therefore, this will be the number of parameters that a single ``qml.SpecialUnitary`` gate acting on +:math:`n` qubits will take. For example, it takes just three parameters for a single qubit, which +is why :class:`~pennylane.Rot` and :class:`~pennylane.U3` take three parameters and may +produce *any* single-qubit rotation. It takes a modest 15 parameters for two qubits, +but it already requires 63 parameters for three qubits. + +For unitaries generated by a single operator, i.e. of the form :math:`\exp(i\theta G),` +there is a plethora of differentiation techniques that allow us to compute its derivative. +However, a standard parameter-shift rule, for example, will not do the job if there are +non-commuting terms :math:`G_m` in the multi-parameter gate :math:`U(\boldsymbol{\theta})` above. +So how *do* we compute the derivative? + +Obtaining the gradient +---------------------- + +In variational quantum algorithms, we typically use the circuit to prepare a quantum state and +then we measure some observable :math:`H.` The resulting real-valued output is considered to be the +cost function :math:`C` that should be minimized. If we want to use gradient-based optimization for +this task, we need a method to compute the gradient :math:`\nabla C` in addition to the cost +function itself. As derived in the publication [#wiersema]_, this is possible on quantum hardware +for :math:`\mathrm{SU}(N)` gates as long as the gates themselves can be implemented. +The implementation in PennyLane follows the decomposition idea described in App. F3, but the +main text of [#wiersema]_ proposes an additional method that scales better in some scenarios +(the caveat being that this method requires additional gates to be available on the quantum hardware). +Here, we will focus on the former method. +We will not go through the entire derivation, but note the following key points: + +- The gradient with respect to all :math:`d` parameters of an :math:`\mathrm{SU}(N)` gate can be + computed using :math:`2d` auxiliary circuits. Each of the circuits contains one additional + operation compared to the original circuit, namely a ``qml.PauliRot`` gate with rotation + angles of :math:`\pm\frac{\pi}{2}.` Note that these Pauli rotations act on up to :math:`n` + qubits. +- This differentiation method uses automatic differentiation during compilation and + classical coprocessing steps, but is compatible with quantum hardware. For large :math:`n,` + the classical processing steps can quickly become prohibitively expensive. +- The computed gradient is not an approximative technique but allows for an exact computation + of the gradient on simulators. On quantum hardware, this leads to unbiased gradient + estimators. + +The implementation in PennyLane takes care of creating the additional circuits and evaluating +them, and with adequate post-processing we get the gradient :math:`\nabla C.` + +Comparing gradient methods +-------------------------- + +Before we dive into using ``qml.SpecialUnitary`` in an optimization task, let's compare +a few methods to compute the gradient with respect to the parameters of such a gate. +In particular, we will look at a finite difference (FD) approach, the stochastic parameter-shift +rule, and the custom gradient method we described above. + +For the first approach, we will use the standard central difference recipe given by + +.. math:: + + \partial_{\text{FD},\theta_j}C(\boldsymbol{\theta}) + =\left[C\left(\boldsymbol{\theta}+\frac{\delta}{2}\boldsymbol{e}_j\right) + -C\left(\boldsymbol{\theta}-\frac{\delta}{2}\boldsymbol{e}_j\right)\right] / \delta. + +Here, :math:`\delta` is a shift parameter that we need to choose and :math:`\boldsymbol{e}_j` is the +:math:`j`-th canonical basis vector, i.e. the all-zeros vector with a one in the +:math:`j`-th entry. This approach is agnostic to the differentiated function and does +not exploit its structure. + +In contrast, the stochastic parameter-shift rule is a differentiation recipe developed particularly +for multi-parameter gates like the :math:`\mathrm{SU}(N)` gates [#banchi]_. It involves the +approximate evaluation of an integral by sampling *splitting times* :math:`\tau` and +evaluating an expression close to the non-stochastic parameter-shift rule for each sample. +For more details, also consider the +:doc:`demo on the stochastic parameter-shift rule `. + +So, let's dive into a toy example and explore the three gradient methods! +We start by defining a simple one-qubit circuit that contains a single :math:`\mathrm{SU}(2)` +gate and measures the expectation value of :math:`H=\frac{3}{5} Z - \frac{4}{5} Y.` +As ``qml.SpecialUnitary`` requires automatic differentiation subroutines even for the +hardware-ready derivative recipe, we will make use of JAX. +""" + +import pennylane as qml +import numpy as np +import jax + +jax.config.update("jax_enable_x64", True) +jax.config.update("jax_platform_name", "cpu") +jnp = jax.numpy + +dev = qml.device("default.qubit", wires=1) +H = 0.6 * qml.PauliZ(0) - 0.8 * qml.PauliY(0) + + +def qfunc(theta): + qml.SpecialUnitary(theta, wires=0) + return qml.expval(H) + + +circuit = qml.QNode(qfunc, dev, interface="jax", diff_method="parameter-shift") + +theta = jnp.array([0.4, 0.2, -0.5]) + +############################################################################## +# Now we need to set up the differentiation methods. For this demonstration, we will +# keep the first and last entry of ``theta`` fixed and only compute the gradient for the +# second parameter. This allows us to visualize the results easily and keeps the +# computational effort to a minimum. +# +# We start with the finite-difference +# recipe, using a shift scale of :math:`\delta=0.75.` This choice of :math:`\delta,` +# which is much larger than usual for numerical differentiation on classical computers, +# is adapted to the scenario of shot-based gradients (see App. F2 of [#wiersema]_). +# We compute the derivative with respect to the second entry of theta, so we will use +# the unit vector :math:`e_2:` + +unit_vector = np.array([0.0, 1.0, 0.0]) + + +def central_diff_grad(theta, delta): + plus_eval = circuit(theta + delta / 2 * unit_vector) + minus_eval = circuit(theta - delta / 2 * unit_vector) + return (plus_eval - minus_eval) / delta + + +delta = 0.75 +print(f"Central difference: {central_diff_grad(theta, delta):.5f}") + +############################################################################## +# Next up, we implement the stochastic parameter-shift rule. Of course we do not do +# so in full generality, but for the particular circuit in this example. We will +# sample ten splitting times to obtain the gradient entry. For each splitting time, +# we need to insert a Pauli-:math:`Y` rotation because :math:`\theta_2` belongs to +# the Pauli-:math:`Y` component of :math:`A(\boldsymbol{\theta}).` For this, we define +# an auxiliary circuit. + + +@jax.jit +@qml.qnode(dev, interface="jax") +def aux_circuit(theta, tau, sign): + qml.SpecialUnitary(tau * theta, wires=0) + # This corresponds to the parameter-shift evaluations of RY at 0 + qml.RY(-sign * np.pi / 2, wires=0) + qml.SpecialUnitary((1 - tau) * theta, wires=0) + return qml.expval(H) + + +def stochastic_parshift_grad(theta, num_samples): + grad = 0 + splitting_times = np.random.random(size=num_samples) + for tau in splitting_times: + # Evaluate the two-term parameter-shift rule of the auxiliar circuit + grad += aux_circuit(theta, tau, 1.0) - aux_circuit(theta, tau, -1.0) + return grad / num_samples + + +num_samples = 10 +print(f"Stochastic parameter-shift: {stochastic_parshift_grad(theta, num_samples):.5f}") + +############################################################################## +# Finally, we can make use of the custom parameter-shift rule introduced in +# [#wiersema]_, which is readily available in PennyLane. Due to the implementation +# chosen internally, the full gradient is returned; we need to pick the second +# gradient entry manually. For this small toy problem, this is +# not an issue. + +sun_grad = jax.grad(circuit) +print(f"Custom SU(N) gradient: {sun_grad(theta)[1]:.5f}") + +############################################################################## +# We obtained three values for the gradient of interest, and they do not agree. +# So what is going on here? First, let's use automatic differentiation to compute +# the exact value and see which method agrees with it (we again need to extract the +# corresponding entry from the full gradient). + +autodiff_circuit = qml.QNode(qfunc, dev, interface="jax", diff_method="parameter-shift") +exact_grad = jax.grad(autodiff_circuit)(theta)[1] +print(f"Exact gradient: {exact_grad:.5f}") + +############################################################################## +# As we can see, automatic differentiation confirmed that the custom differentiation method +# gave us the correct result. Why do the other methods disagree? +# This is because the finite difference recipe is an *approximate* gradient +# method. This means it has an error even if all circuit evaluations are +# made exact (up to numerical precision) like in the example above. +# As for the stochastic parameter-shift rule, you may already guess why there is +# a deviation: indeed, the *stochastic* nature of this method leads to derivative +# values that are scattered around the true value. It is an unbiased estimator, +# so the average will approach the exact value with increasingly many evaluations. +# To demonstrate this, let's compute the same derivative many times and plot +# a histogram of what we get. We'll do so for ``num_samples=2``, ``10`` and ``100``. + +import matplotlib.pyplot as plt + +plt.rcParams.update({"font.size": 12}) + +fig, ax = plt.subplots(1, 1, figsize=(6, 4)) +colors = ["#ACE3FF", "#FF87EB", "#FFE096"] +for num_samples, color in zip([2, 10, 100], colors): + grads = [stochastic_parshift_grad(theta, num_samples) for _ in range(1000)] + ax.hist(grads, label=f"{num_samples} samples", alpha=0.9, color=color) +ylim = ax.get_ylim() +ax.plot([exact_grad] * 2, ylim, ls="--", c="k", label="Exact") +ax.set(xlabel=r"$\partial_{SPS,\theta_2}C(\theta)$", ylabel="Frequency", ylim=ylim) +ax.legend(loc="upper left") +plt.tight_layout() +plt.show() + +############################################################################## +# As we can see, the stochastic parameter-shift rule comes with a variance +# that can be reduced at the additional cost of evaluating the auxiliary circuit +# for more splitting times. +# +# On quantum hardware, all measurement results are statistical in nature anyway. +# So how does this stochasticity combine with the +# three differentiation methods? We will not go into detail here, but refer +# to [#wiersema]_ to see how the custom differentiation rule proposed in the +# main text leads to the lowest mean squared error. For a single-qubit circuit +# similar to the one above, but with the single gate :math:`U(\boldsymbol{\theta})=\exp(iaX+ibY),` +# the derivative and its expected variance are shown in the following +# (recoloured) plot from the manuscript: +# +# .. figure:: ../_static/demonstration_assets/here_comes_the_sun/sampled_grad.png +# :align: center +# :width: 70% +# +# As we can see, the custom :math:`\mathrm{SU}(N)` parameter-shift rule produces the +# gradient estimates with the smallest variance. For small values of the parameter +# :math:`b,` which is fixed for each panel, the custom shift rule and the stochastic +# shift rule approach the standard two-term parameter-shift rule, which would be exact +# for :math:`b=0.` +# The finite difference gradient shown here was obtained using the shift +# scale :math:`\delta=0.75,` as well. As we can see, this suppresses the variance down to +# a level comparable to those of the shift rule derivatives and this shift scale is a +# reasonable trade-off between the variance and the systematic error we observed earlier. +# As shown in App. F3 of [#wiersema]_, this scale is indeed close to the optimal choice +# if we were to compute the gradient with 100 shots per circuit. +# +# Comparing ansatz structures +# --------------------------- +# +# We discussed above that there are many circuit architectures available and that choosing +# a suitable ansatz is important but can be difficult. Here, we will compare a simple ansatz +# based on the ``qml.SpecialUnitary`` gate discussed above to other approaches that fully +# parametrize the special unitary group for the respective number of qubits. +# In particular, we will compare ``qml.SpecialUnitary`` to standard decompositions from the +# literature that parametrize :math:`\mathrm{SU}(N)` with elementary gates, as well as to a +# sequence of Pauli rotation gates that also allows us to create any special unitary. +# Let us start by defining the decomposition of a two-qubit unitary. +# We choose the decomposition, which is optimal but not unique, from [#vatan]_. +# The Pauli rotation sequence is available in PennyLane +# via ``qml.ArbitraryUnitary`` and we will not need to implement it ourselves. + + +def two_qubit_decomp(params, wires): + """Implement an arbitrary SU(4) gate on two qubits + using the decomposition from Theorem 5 in + https://arxiv.org/pdf/quant-ph/0308006.pdf""" + i, j = wires + # Single U(2) parameterization on both qubits separately + qml.Rot(*params[:3], wires=i) + qml.Rot(*params[3:6], wires=j) + qml.CNOT(wires=[j, i]) # First CNOT + qml.RZ(params[6], wires=i) + qml.RY(params[7], wires=j) + qml.CNOT(wires=[i, j]) # Second CNOT + qml.RY(params[8], wires=j) + qml.CNOT(wires=[j, i]) # Third CNOT + # Single U(2) parameterization on both qubits separately + qml.Rot(*params[9:12], wires=i) + qml.Rot(*params[12:15], wires=j) + + +# The three building blocks on two qubits we will compare are: +operations = { + ("Decomposition", "decomposition"): two_qubit_decomp, + ("PauliRot sequence",) * 2: qml.ArbitraryUnitary, + ("$\mathrm{SU}(N)$ gate", "SU(N) gate"): qml.SpecialUnitary, +} + +############################################################################## +# Now that we have the template for the composition approach in place, we construct a toy +# problem to solve using the ansätze. We will sample a random Hamiltonian in the Pauli basis +# (this time without the prefactor :math:`i,` as we want to construct a Hermitian operator) +# with independent coefficients that follow a normal distribution: +# +# .. math:: +# +# H = \sum_{m=1}^d h_m G_m,\quad h_m\sim \mathcal{N}(0,1). +# +# We will work with six qubits. + +num_wires = 6 +wires = list(range(num_wires)) +np.random.seed(62213) + +coefficients = np.random.randn(4**num_wires - 1) +# Create the matrices for the entire Pauli basis +basis = qml.ops.qubit.special_unitary.pauli_basis_matrices(num_wires) +# Construct the Hamiltonian from the normal random coefficients and the basis +H_matrix = qml.math.tensordot(coefficients, basis, axes=[[0], [0]]) +H = qml.Hermitian(H_matrix, wires=wires) +# Compute the ground state energy +E_min = min(qml.eigvals(H)) +print(f"Ground state energy: {E_min:.5f}") + +############################################################################## +# Using the toy problem Hamiltonian and the three ansätze for :math:`\mathrm{SU}(N)` operations +# from above, we create a circuit template that applies these operations in a brick-layer +# architecture with two blocks and each operation acting on ``loc=2`` qubits. +# For this we define a ``QNode``: + +loc = 2 +d = loc**4 - 1 # d = 15 for two-qubit operations +dev = qml.device("default.qubit", wires=num_wires) +# two blocks with two layers. Each layer contains three operations with d parameters +param_shape = (2, 2, 3, d) +init_params = np.zeros(param_shape) + + +def circuit(params, operation=None): + """Apply an operation in a brickwall-like pattern to a qubit register and measure H. + Parameters are assumed to have the dimensions (number of blocks, number of + wires per operation, number of operations per layer, and number of parameters + per operation), in that order. + """ + for params_block in params: + for i, params_layer in enumerate(params_block): + for j, params_op in enumerate(params_layer): + wires_op = [w % num_wires for w in range(loc * j + i, loc * (j + 1) + i)] + operation(params_op, wires_op) + return qml.expval(H) + + +qnode = qml.QNode(circuit, dev, interface="jax") +print(qml.draw(qnode)(init_params, qml.SpecialUnitary)) + +############################################################################## +# We can now proceed to prepare the optimization task using this circuit +# and an optimization routine of our choice. For simplicity, we run a vanilla gradient +# descent optimization with a fixed learning rate for 500 steps. Again, we use JAX + +# for auto-differentiation. + +learning_rate = 5e-4 +num_steps = 500 +init_params = jax.numpy.array(init_params) +grad_fn = jax.jit(jax.jacobian(qnode), static_argnums=1) +qnode = jax.jit(qnode, static_argnums=1) + +############################################################################## +# With this configuration, let's run the optimization! + +energies = {} +for (name, print_name), operation in operations.items(): + print(f"Running the optimization for the {print_name}") + params = init_params.copy() + energy = [] + for step in range(num_steps): + cost = qnode(params, operation) + params = params - learning_rate * grad_fn(params, operation) + energy.append(cost) # Store energy value + if step % 50 == 0: # Report current energy + print(f"{step:3d} Steps: {cost:.6f}") + + energy.append(qnode(params, operation)) # Final energy value + energies[name] = energy + +############################################################################## +# So, did it work? Judging from the intermediate energy values, it seems that the optimization +# outcomes differ notably. But let's take a look at the relative error in energy across the +# optimization process. + +fig, ax = plt.subplots(1, 1) +styles = [":", "--", "-"] +colors = ["#70CEFF", "#C756B2", "#FFE096"] +for (name, energy), c, ls in zip(energies.items(), colors, styles): + error = (energy - E_min) / abs(E_min) + ax.plot(list(range(len(error))), error, label=name, c=c, ls=ls, lw=2.5) + +ax.set(xlabel="Iteration", ylabel="Relative error") +ax.legend() +plt.show() + +############################################################################## +# We find that the optimization indeed performs significantly better for ``qml.SpecialUnitary`` +# than for the other two general unitaries, while using the same number of parameters and +# preserving the expressibility of the circuit ansatz. This +# means that we found a particularly well-trainable parameterization of the local unitaries which +# allows us to reduce the energy of the prepared quantum state more easily while maintaining the +# number of parameters. +# +# +# Conclusion +# ---------- +# +# To summarize, in this tutorial we introduced ``qml.SpecialUnitary``, a multi-parameter +# gate that can act like *any* gate on the qubits it is applied to and that is constructed +# with Lie theory in mind. We discussed three methods of differentiating quantum circuits +# that use this gate, showing that a new custom parameter-shift rule presented in +# [#wiersema]_ is particularly suitable to produce unbiased gradient estimates with the +# lowest variance. Afterwards, we used this differentiation technique when comparing +# the performance of ``qml.SpecialUnitary`` to that of other gates that can act +# like *any* gate locally. For this, we ran a gradient-based optimization for a toy model +# Hamiltonian and found that ``qml.SpecialUnitary`` is particularly well-trainable, achieving +# lower energies significantly quicker than the other tested gates. +# +# There are still exciting questions to answer about ``qml.SpecialUnitary``: How can the +# custom parameter-shift rule be used for other gates, and what does the so-called +# *Dynamical Lie algebra* of these gates have to do with it? How can we implement +# the ``qml.SpecialUnitary`` gate on hardware? Is the unitary time evolution implemented +# by this gate special in a physical sense? +# +# The answers to some, but not all, of these questions can be found in [#wiersema]_. +# We are certain that there are many more interesting aspects of this gate to be uncovered! +# If you want to learn more, consider the other literature references below, +# as well as the documentation of :class:`~pennylane.SpecialUnitary`. +# +# References +# ---------- +# +# .. [#vatan] +# +# Farrokh Vatan and Colin Williams, +# "Optimal Quantum Circuits for General Two-Qubit Gates", +# `arXiv:quant-ph/0308006 `__ (2003). +# +# .. [#wiersema] +# +# R. Wiersema, D. Lewis, D. Wierichs, J. F. Carrasquilla, and N. Killoran. +# "Here comes the SU(N): multivariate quantum gates and gradients" +# `arXiv:2303.11355 `__ (2023). +# +# .. [#banchi] +# +# Leonardo Banchi and Gavin E. Crooks. "Measuring Analytic Gradients of +# General Quantum Evolution with the Stochastic Parameter Shift Rule." +# `Quantum 5, 386 `__ (2021). +# +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_initial_state_preparation/demo.py b/demonstrations_v2/tutorial_initial_state_preparation/demo.py index dc50c939c0..8a2cb89e5c 100644 --- a/demonstrations_v2/tutorial_initial_state_preparation/demo.py +++ b/demonstrations_v2/tutorial_initial_state_preparation/demo.py @@ -1,364 +1,364 @@ -r""" - -Initial state preparation for quantum chemistry -=============================================== - -A high-quality initial state can significantly reduce the runtime of many quantum algorithms. From -the variational quantum eigensolver (VQE) to quantum phase estimation (QPE) and even the recent -`intermediate-scale quantum (ISQ) `_ algorithms, obtaining the ground state of a chemical system requires -a good initial state. For instance, in the case of VQE, a good initial state directly translates into fewer -optimization steps. In QPE, the probability of measuring the ground-state energy is directly -proportional to the overlap squared of the initial and ground states. Even beyond quantum chemistry, -good initial guesses are important for algorithms like quantum approximate optimization (QAOA) -and Grover search. - -Much like searching for a needle in a haystack, there are a lot of things you might try -to prepare a good guess for the ground state in the high-dimensional Hilbert space. In this -tutorial, we show how to use traditional computational chemistry techniques to -get a good initial state. Such an initial state will not be exactly -the ground state, but it will certainly be better than the standard guess of a computational -basis state :math:`|0\rangle^{\otimes N}` or the Hartree-Fock state. - -.. figure:: ../_static/demonstration_assets/initial_state_preparation/qchem_input_states.png - :align: center - :width: 65% - :target: javascript:void(0) - -Importing initial states ------------------------- -We can import initial states obtained from several post-Hartree-Fock quantum chemistry methods -to PennyLane. These methods are incredibly diverse in terms of their outputs, not always returning -an object that is easy to turn into a PennyLane state vector. - -We have already done this hard conversion work: all that you need to do is run these methods and -pass their outputs to PennyLane's :func:`~.pennylane.qchem.import_state` function. The currently -supported methods are configuration interaction with singles and doubles (CISD), coupled cluster -(CCSD), density-matrix renormalization group (DMRG) and semistochastic heat-bath configuration -interaction (SHCI). - -We now show how this works on the linear :math:`\text{H}_3^+` molecule as an example. - - -CISD states -~~~~~~~~~~~ -The first line of attack for initial state preparation is often a CISD calculation, performed with the `PySCF `_ -library. CISD is unsophisticated, but it is fast. It will not be of much help for strongly correlated molecules, -but it is better than Hartree-Fock. Here is the code example using the restricted Hartree-Fock -orbitals (:func:`~.pennylane.qchem.import_state` works for unrestricted orbitals, too). -""" - -from pyscf import gto, scf, ci -from pennylane.qchem import import_state -import numpy as np - -R = 1.2 -# create the H3+ molecule -mol = gto.M(atom=[["H", (0, 0, 0)], ["H", (0, 0, R)], ["H", (0, 0, 2 * R)]], charge=1) -# perfrom restricted Hartree-Fock and then CISD -myhf = scf.RHF(mol).run() -myci = ci.CISD(myhf).run() -wf_cisd = import_state(myci, tol=1e-1) -print(f"CISD-based state vector: \n{np.round(wf_cisd.real, 4)}") - -############################################################################## -# The final object, PennyLane's state vector ``wf_cisd``, is ready to be used as an -# initial state in a quantum circuit in PennyLane--we will showcase this below for VQE. -# -# Conversion for CISD to a state vector is straightforward: simply assign the PySCF-stored -# CI coefficients to appropriate entries in the state vector based on the determinant they correspond to. -# The second attribute passed to ``import_state()``, ``tol``, specifies the cutoff beyond -# which contributions to the wavefunctions are neglected. Internally, wavefunctions are -# stored in their Slater determinant representation. If their prefactor coefficient -# is below ``tol``, those determinants are dropped from the expression. - -############################################################################## -# CCSD states -# ~~~~~~~~~~~ -# The function :func:`~.pennylane.qchem.import_state` is general and works similarly for CCSD. It can -# automatically detect the input type and apply the appropriate conversion protocol. - -from pyscf import cc - -mycc = cc.CCSD(myhf).run() -wf_ccsd = import_state(mycc, tol=1e-1) -print(f"CCSD-based state vector: \n{np.round(wf_ccsd.real, 4)}") - -############################################################################## -# For CCSD conversion, at present the exponential form is expanded and terms are collected **to -# second order** to obtain the CI coefficients. -# -# DMRG states -# ~~~~~~~~~~~ -# For more complex or more correlated molecules, initial states from DMRG or -# SHCI will be better options. DMRG calculations involve using the library `Block2 `_, -# which can be installed with ``pip``: -# -# .. code-block:: bash -# -# pip install block2 -# -# The DMRG calculation is run on top of the molecular orbitals obtained by Hartree-Fock, -# stored in the ``myhf`` object, which we can reuse from before. -# -# .. code-block:: python -# -# from pyscf import mcscf -# from pyblock2.driver.core import DMRGDriver, SymmetryTypes -# from pyblock2._pyscf.ao2mo import integrals as itg -# -# # obtain molecular integrals and other parameters for DMRG -# mc = mcscf.CASCI(myhf, mol.nao, mol.nelectron) -# ncas, n_elec, spin, ecore, h1e, g2e, orb_sym = \ -# itg.get_rhf_integrals(myhf, mc.ncore, mc.ncas, g2e_symm=8) -# -# # initialize the DMRG solver, Hamiltonian (as matrix-product operator, MPO) and -# # state (as matrix-product state, MPS) -# driver = DMRGDriver(scratch="./dmrg_temp", symm_type=SymmetryTypes.SZ) -# driver.initialize_system(n_sites=ncas, n_elec=n_elec, spin=spin, orb_sym=orb_sym) -# mpo = driver.get_qc_mpo(h1e=h1e, g2e=g2e, ecore=ecore, iprint=0) -# ket = driver.get_random_mps(tag="GS") -# -# # execute DMRG by modifying the ket state in-place to minimize the energy -# driver.dmrg(mpo, ket, n_sweeps=30,bond_dims=[100,200],\ -# noises=[1e-3,1e-5],thrds=[1e-6,1e-7],tol=1e-6) -# -# # post-process the MPS to get an initial state -# dets, coeffs = driver.get_csf_coefficients(ket, iprint=0) -# dets = dets.tolist() -# wf_dmrg = import_state((dets, coeffs), tol=1e-1) -# print(f"DMRG-based state vector: \n {np.round(wf_dmrg, 4)}") -# -# .. code-block:: bash -# -# DMRG-based state vector -# [ 0. 0. 0. 0. 0. 0. 0. 0. 0. -# 0. 0. 0. -0.2243 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.9745 0. 0. 0. 0. 0. -# 0. 0. 0. 0. 0. 0. 0. 0. 0. -# 0. ] - -############################################################################## -# The crucial part is calling ``get_csf_coefficients()`` on the solution stored in -# MPS form in the ``ket``. This triggers an internal reconstruction calculation that -# converts the MPS to the sum of Slater determinants form, returning the output -# as a tuple ``(array([int]), array(float]))``. The first element expresses a given Slater -# determinant using Fock occupation vectors of length equal to the number of spatial -# orbitals. In Block2 notation, the entries can be ``0`` (orbital unoccupied), ``1`` (orbital occupied with spin-up -# electron), ``2`` (occupied with spin-down), and ``3`` (doubly occupied). The first -# element, ``array([int])``, must be converted to ``list`` -# for :func:`~.pennylane.qchem.import_state` to accept it. -# The second element stores the CI coefficients. -# -# In principle, this functionality can be used to generate any initial state, provided -# the user specifies a list of Slater determinants and their coefficients in this form. -# Let's take this opportunity to create the Hartree-Fock initial state, to compare the -# other states against it later on. - -hf_primer = ([[3, 0, 0]], np.array([1.0])) -wf_hf = import_state(hf_primer) - -############################################################################## -# SHCI states -# ~~~~~~~~~~~ -# -# The SHCI calculations utilize the library `Dice `_, and can be run -# using PySCF through the interface module `SHCI-SCF `_. -# For Dice, the execution process is similar to that of DMRG: -# -# .. code-block:: python -# -# from pyscf.shciscf import shci -# -# # prepare PySCF CASCI object, whose solver will be the SHCI method -# ncas, nelecas_a, nelecas_b = mol.nao, mol.nelectron // 2, mol.nelectron // 2 -# myshci = mcscf.CASCI(myhf, ncas, (nelecas_a, nelecas_b)) -# -# # set up essentials for the SHCI solver -# output_file = f"shci_output.out" -# myshci.fcisolver = shci.SHCI(myhf.mol) -# myshci.fcisolver.outputFile = output_file -# -# # execute SHCI through the PySCF interface -# e_tot, e_ci, ss, mo_coeff, mo_energies = myshci.kernel(verbose=5) -# -# # post-process the shci_output.out to extract the wave function -# # results and create the tuple of dets (list([str])) and coeffs (array([float])) -# # shci_data = (dets, coeffs) -# wf_shci = import_state(shci_data, tol=1e-1) -# print(f"SHCI-based state vector\n{wf_shci}") -# -# .. code-block:: bash -# -# SHCI-based state vector -# [ 0. 0. 0. 0. 0. 0. 0. 0. 0. -# 0. 0. 0. 0.2243 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.9745 0. 0. 0. 0. 0. -# 0. 0. 0. 0. 0. 0. 0. 0. 0. -# 0. ] - -############################################################################## -# The Dice output file prints determinants using symbols ``0`` (unoccupied orbital), -# ``a`` and ``b`` (orbital occupied with spin-up and spin-down electron, respectively), -# and ``2`` (doubly occupied orbital). These determinant outputs, and corresponding -# coefficients, should be extracted and arranged as ``(list([str]), array(float]))``, -# where each string combines all the determinant symbols ``0, a, b, 2`` for a single -# determinant with no spaces. For example, for the HF state we created in the DMRG section, -# the SHCI output should read ``([["200"]], np.array([1.]))`` - -############################################################################## -# Application: speed up VQE -# ------------------------- -# Let us now demonstrate how the choice of a better initial state shortens the runtime -# of VQE for obtaining the ground-state energy of a molecule. As a first step, create our -# linear :math:`\text{H}_3^+` molecule, a device, and a simple VQE circuit with single and double excitations: - -import pennylane as qml -from pennylane import qchem -from jax import numpy as jnp - -# generate the molecular Hamiltonian for H3+ -symbols = ["H", "H", "H"] -geometry = jnp.array([[0, 0, 0], [0, 0, R/0.529], [0, 0, 2*R/0.529]]) -molecule = qchem.Molecule(symbols, geometry, charge=1) - -H2mol, qubits = qchem.molecular_hamiltonian(molecule) -wires = list(range(qubits)) -dev = qml.device("default.qubit", wires=qubits) - -# create all possible excitations in H3+ -singles, doubles = qchem.excitations(2, qubits) -excitations = singles + doubles - -############################################################################## -# Now let's run VQE with the Hartree-Fock initial state. We first build the VQE circuit: - - -@qml.qnode(dev) -def circuit_VQE(theta, initial_state): - qml.StatePrep(initial_state, wires=wires) - for i, excitation in enumerate(excitations): - if len(excitation) == 4: - qml.DoubleExcitation(theta[i], wires=excitation) - else: - qml.SingleExcitation(theta[i], wires=excitation) - return qml.expval(H2mol) - - -def cost_fn(param): - return circuit_VQE(param, initial_state=wf_hf) - - -############################################################################## -# Next, we create the VQE optimizer, initialize the variational parameters and run the VQE optimization. -import optax -import jax -jax.config.update("jax_enable_x64", True) - -opt = optax.sgd(learning_rate=0.4) # sgd stands for StochasticGradientDescent -theta = jnp.array(jnp.zeros(len(excitations))) -delta_E, iteration = 10, 0 -results_hf = [] -opt_state = opt.init(theta) -prev_energy = cost_fn(theta) - -# run the VQE optimization loop until convergence threshold is reached -while abs(delta_E) > 1e-5: - gradient = jax.grad(cost_fn)(theta) - updates, opt_state = opt.update(gradient, opt_state) - theta = optax.apply_updates(theta, updates) - new_energy = cost_fn(theta) - delta_E = new_energy - prev_energy - prev_energy = new_energy - results_hf.append(new_energy) - if len(results_hf) % 5 == 0: - print(f"Step = {len(results_hf)}, Energy = {new_energy:.6f} Ha") -print(f"Starting with HF state took {len(results_hf)} iterations until convergence.") - -############################################################################## -# And compare with how things go when you run it with the CISD initial state: - - -def cost_fn_cisd(param): - return circuit_VQE(param, initial_state=wf_cisd) - - -theta = jnp.array(jnp.zeros(len(excitations))) -delta_E, iteration = 10, 0 -results_cisd = [] -opt_state = opt.init(theta) -prev_energy = cost_fn_cisd(theta) - -while abs(delta_E) > 1e-5: - gradient = jax.grad(cost_fn_cisd)(theta) - updates, opt_state = opt.update(gradient, opt_state) - theta = optax.apply_updates(theta, updates) - new_energy = cost_fn_cisd(theta) - delta_E = new_energy - prev_energy - prev_energy = new_energy - results_cisd.append(new_energy) - if len(results_cisd) % 5 == 0: - print(f"Step = {len(results_cisd)}, Energy = {new_energy:.6f} Ha") -print( - f"Starting with CISD state took {len(results_cisd)} iterations until convergence." -) - -############################################################################## -# Let's visualize the comparison between the two initial states, and see that indeed -# we get to the ground state much faster by starting with the CISD state. - -import matplotlib.pyplot as plt - -fig, ax = plt.subplots() -ax.plot(range(len(results_hf)), results_hf, color="r", marker="o", label="wf_hf") -ax.plot(range(len(results_cisd)), results_cisd, color="b", marker="o", label="wf_cisd") -ax.legend(fontsize=16) -ax.tick_params(axis="both", labelsize=16) -ax.set_xlabel("Iteration", fontsize=20) -ax.set_ylabel("Energy, Ha", fontsize=20) -plt.tight_layout() -plt.show() - -############################################################################## -# Indeed, the CISD state significantly shortens the VQE runtime. -# -# It is sometimes possible to foresee the extent of this speed-up of a particular initial state -# by computing its overlap with the ground state--a traditional metric of success for initial -# states in quantum algorithms. Because in our examples the states are regular arrays, computing an -# overlap between different states is as easy as computing a dot product - -print(np.dot(wf_cisd, wf_hf).real) -print(np.dot(wf_ccsd, wf_hf).real) -print(np.dot(wf_cisd, wf_ccsd).real) - -############################################################################## -# In this particular case of :math:`\text{H}_3^+,` even CISD gives the exact wavefunction, hence both overlaps -# with the HF state are identical. In more correlated molecules, overlaps will show that the more -# multireference methods DMRG and SHCI are farther away from the Hartree-Fock state, -# allowing them to perform better (you can check this by printing the overlaps with -# DMRG and SHCI in a more correlated molecule). If a ground state in such a case was known, -# the overlap to it could tell us directly the quality of the initial state. - -############################################################################## -# Conclusion -# ----------- -# This demo shows how to import initial states from outputs of traditional quantum chemistry methods -# for use in PennyLane. We showcased simple workflows for how to run -# a variety of state-of-the-art post-Hartree-Fock methods, from libraries such as -# `PySCF `_, -# `Block2 `_ and -# `Dice `_, to generate outputs that can then be -# converted to PennyLane's state vector format with a single line of code. With these -# initial states, we use the example of VQE to demonstrate how a better choice -# of initial state can lead to improved algorithmic performance. For the molecule -# used in our example, the CISD state was sufficient: however, in more correlated -# molecules, DMRG and SHCI initial states typically provide the best speed-ups. -# -# About the author -# ---------------- -# +r""" + +Initial state preparation for quantum chemistry +=============================================== + +A high-quality initial state can significantly reduce the runtime of many quantum algorithms. From +the variational quantum eigensolver (VQE) to quantum phase estimation (QPE) and even the recent +`intermediate-scale quantum (ISQ) `_ algorithms, obtaining the ground state of a chemical system requires +a good initial state. For instance, in the case of VQE, a good initial state directly translates into fewer +optimization steps. In QPE, the probability of measuring the ground-state energy is directly +proportional to the overlap squared of the initial and ground states. Even beyond quantum chemistry, +good initial guesses are important for algorithms like quantum approximate optimization (QAOA) +and Grover search. + +Much like searching for a needle in a haystack, there are a lot of things you might try +to prepare a good guess for the ground state in the high-dimensional Hilbert space. In this +tutorial, we show how to use traditional computational chemistry techniques to +get a good initial state. Such an initial state will not be exactly +the ground state, but it will certainly be better than the standard guess of a computational +basis state :math:`|0\rangle^{\otimes N}` or the Hartree-Fock state. + +.. figure:: ../_static/demonstration_assets/initial_state_preparation/qchem_input_states.png + :align: center + :width: 65% + :target: javascript:void(0) + +Importing initial states +------------------------ +We can import initial states obtained from several post-Hartree-Fock quantum chemistry methods +to PennyLane. These methods are incredibly diverse in terms of their outputs, not always returning +an object that is easy to turn into a PennyLane state vector. + +We have already done this hard conversion work: all that you need to do is run these methods and +pass their outputs to PennyLane's :func:`~.pennylane.qchem.import_state` function. The currently +supported methods are configuration interaction with singles and doubles (CISD), coupled cluster +(CCSD), density-matrix renormalization group (DMRG) and semistochastic heat-bath configuration +interaction (SHCI). + +We now show how this works on the linear :math:`\text{H}_3^+` molecule as an example. + + +CISD states +~~~~~~~~~~~ +The first line of attack for initial state preparation is often a CISD calculation, performed with the `PySCF `_ +library. CISD is unsophisticated, but it is fast. It will not be of much help for strongly correlated molecules, +but it is better than Hartree-Fock. Here is the code example using the restricted Hartree-Fock +orbitals (:func:`~.pennylane.qchem.import_state` works for unrestricted orbitals, too). +""" + +from pyscf import gto, scf, ci +from pennylane.qchem import import_state +import numpy as np + +R = 1.2 +# create the H3+ molecule +mol = gto.M(atom=[["H", (0, 0, 0)], ["H", (0, 0, R)], ["H", (0, 0, 2 * R)]], charge=1) +# perfrom restricted Hartree-Fock and then CISD +myhf = scf.RHF(mol).run() +myci = ci.CISD(myhf).run() +wf_cisd = import_state(myci, tol=1e-1) +print(f"CISD-based state vector: \n{np.round(wf_cisd.real, 4)}") + +############################################################################## +# The final object, PennyLane's state vector ``wf_cisd``, is ready to be used as an +# initial state in a quantum circuit in PennyLane--we will showcase this below for VQE. +# +# Conversion for CISD to a state vector is straightforward: simply assign the PySCF-stored +# CI coefficients to appropriate entries in the state vector based on the determinant they correspond to. +# The second attribute passed to ``import_state()``, ``tol``, specifies the cutoff beyond +# which contributions to the wavefunctions are neglected. Internally, wavefunctions are +# stored in their Slater determinant representation. If their prefactor coefficient +# is below ``tol``, those determinants are dropped from the expression. + +############################################################################## +# CCSD states +# ~~~~~~~~~~~ +# The function :func:`~.pennylane.qchem.import_state` is general and works similarly for CCSD. It can +# automatically detect the input type and apply the appropriate conversion protocol. + +from pyscf import cc + +mycc = cc.CCSD(myhf).run() +wf_ccsd = import_state(mycc, tol=1e-1) +print(f"CCSD-based state vector: \n{np.round(wf_ccsd.real, 4)}") + +############################################################################## +# For CCSD conversion, at present the exponential form is expanded and terms are collected **to +# second order** to obtain the CI coefficients. +# +# DMRG states +# ~~~~~~~~~~~ +# For more complex or more correlated molecules, initial states from DMRG or +# SHCI will be better options. DMRG calculations involve using the library `Block2 `_, +# which can be installed with ``pip``: +# +# .. code-block:: bash +# +# pip install block2 +# +# The DMRG calculation is run on top of the molecular orbitals obtained by Hartree-Fock, +# stored in the ``myhf`` object, which we can reuse from before. +# +# .. code-block:: python +# +# from pyscf import mcscf +# from pyblock2.driver.core import DMRGDriver, SymmetryTypes +# from pyblock2._pyscf.ao2mo import integrals as itg +# +# # obtain molecular integrals and other parameters for DMRG +# mc = mcscf.CASCI(myhf, mol.nao, mol.nelectron) +# ncas, n_elec, spin, ecore, h1e, g2e, orb_sym = \ +# itg.get_rhf_integrals(myhf, mc.ncore, mc.ncas, g2e_symm=8) +# +# # initialize the DMRG solver, Hamiltonian (as matrix-product operator, MPO) and +# # state (as matrix-product state, MPS) +# driver = DMRGDriver(scratch="./dmrg_temp", symm_type=SymmetryTypes.SZ) +# driver.initialize_system(n_sites=ncas, n_elec=n_elec, spin=spin, orb_sym=orb_sym) +# mpo = driver.get_qc_mpo(h1e=h1e, g2e=g2e, ecore=ecore, iprint=0) +# ket = driver.get_random_mps(tag="GS") +# +# # execute DMRG by modifying the ket state in-place to minimize the energy +# driver.dmrg(mpo, ket, n_sweeps=30,bond_dims=[100,200],\ +# noises=[1e-3,1e-5],thrds=[1e-6,1e-7],tol=1e-6) +# +# # post-process the MPS to get an initial state +# dets, coeffs = driver.get_csf_coefficients(ket, iprint=0) +# dets = dets.tolist() +# wf_dmrg = import_state((dets, coeffs), tol=1e-1) +# print(f"DMRG-based state vector: \n {np.round(wf_dmrg, 4)}") +# +# .. code-block:: bash +# +# DMRG-based state vector +# [ 0. 0. 0. 0. 0. 0. 0. 0. 0. +# 0. 0. 0. -0.2243 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.9745 0. 0. 0. 0. 0. +# 0. 0. 0. 0. 0. 0. 0. 0. 0. +# 0. ] + +############################################################################## +# The crucial part is calling ``get_csf_coefficients()`` on the solution stored in +# MPS form in the ``ket``. This triggers an internal reconstruction calculation that +# converts the MPS to the sum of Slater determinants form, returning the output +# as a tuple ``(array([int]), array(float]))``. The first element expresses a given Slater +# determinant using Fock occupation vectors of length equal to the number of spatial +# orbitals. In Block2 notation, the entries can be ``0`` (orbital unoccupied), ``1`` (orbital occupied with spin-up +# electron), ``2`` (occupied with spin-down), and ``3`` (doubly occupied). The first +# element, ``array([int])``, must be converted to ``list`` +# for :func:`~.pennylane.qchem.import_state` to accept it. +# The second element stores the CI coefficients. +# +# In principle, this functionality can be used to generate any initial state, provided +# the user specifies a list of Slater determinants and their coefficients in this form. +# Let's take this opportunity to create the Hartree-Fock initial state, to compare the +# other states against it later on. + +hf_primer = ([[3, 0, 0]], np.array([1.0])) +wf_hf = import_state(hf_primer) + +############################################################################## +# SHCI states +# ~~~~~~~~~~~ +# +# The SHCI calculations utilize the library `Dice `_, and can be run +# using PySCF through the interface module `SHCI-SCF `_. +# For Dice, the execution process is similar to that of DMRG: +# +# .. code-block:: python +# +# from pyscf.shciscf import shci +# +# # prepare PySCF CASCI object, whose solver will be the SHCI method +# ncas, nelecas_a, nelecas_b = mol.nao, mol.nelectron // 2, mol.nelectron // 2 +# myshci = mcscf.CASCI(myhf, ncas, (nelecas_a, nelecas_b)) +# +# # set up essentials for the SHCI solver +# output_file = f"shci_output.out" +# myshci.fcisolver = shci.SHCI(myhf.mol) +# myshci.fcisolver.outputFile = output_file +# +# # execute SHCI through the PySCF interface +# e_tot, e_ci, ss, mo_coeff, mo_energies = myshci.kernel(verbose=5) +# +# # post-process the shci_output.out to extract the wave function +# # results and create the tuple of dets (list([str])) and coeffs (array([float])) +# # shci_data = (dets, coeffs) +# wf_shci = import_state(shci_data, tol=1e-1) +# print(f"SHCI-based state vector\n{wf_shci}") +# +# .. code-block:: bash +# +# SHCI-based state vector +# [ 0. 0. 0. 0. 0. 0. 0. 0. 0. +# 0. 0. 0. 0.2243 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.9745 0. 0. 0. 0. 0. +# 0. 0. 0. 0. 0. 0. 0. 0. 0. +# 0. ] + +############################################################################## +# The Dice output file prints determinants using symbols ``0`` (unoccupied orbital), +# ``a`` and ``b`` (orbital occupied with spin-up and spin-down electron, respectively), +# and ``2`` (doubly occupied orbital). These determinant outputs, and corresponding +# coefficients, should be extracted and arranged as ``(list([str]), array(float]))``, +# where each string combines all the determinant symbols ``0, a, b, 2`` for a single +# determinant with no spaces. For example, for the HF state we created in the DMRG section, +# the SHCI output should read ``([["200"]], np.array([1.]))`` + +############################################################################## +# Application: speed up VQE +# ------------------------- +# Let us now demonstrate how the choice of a better initial state shortens the runtime +# of VQE for obtaining the ground-state energy of a molecule. As a first step, create our +# linear :math:`\text{H}_3^+` molecule, a device, and a simple VQE circuit with single and double excitations: + +import pennylane as qml +from pennylane import qchem +from jax import numpy as jnp + +# generate the molecular Hamiltonian for H3+ +symbols = ["H", "H", "H"] +geometry = jnp.array([[0, 0, 0], [0, 0, R/0.529], [0, 0, 2*R/0.529]]) +molecule = qchem.Molecule(symbols, geometry, charge=1) + +H2mol, qubits = qchem.molecular_hamiltonian(molecule) +wires = list(range(qubits)) +dev = qml.device("default.qubit", wires=qubits) + +# create all possible excitations in H3+ +singles, doubles = qchem.excitations(2, qubits) +excitations = singles + doubles + +############################################################################## +# Now let's run VQE with the Hartree-Fock initial state. We first build the VQE circuit: + + +@qml.qnode(dev) +def circuit_VQE(theta, initial_state): + qml.StatePrep(initial_state, wires=wires) + for i, excitation in enumerate(excitations): + if len(excitation) == 4: + qml.DoubleExcitation(theta[i], wires=excitation) + else: + qml.SingleExcitation(theta[i], wires=excitation) + return qml.expval(H2mol) + + +def cost_fn(param): + return circuit_VQE(param, initial_state=wf_hf) + + +############################################################################## +# Next, we create the VQE optimizer, initialize the variational parameters and run the VQE optimization. +import optax +import jax +jax.config.update("jax_enable_x64", True) + +opt = optax.sgd(learning_rate=0.4) # sgd stands for StochasticGradientDescent +theta = jnp.array(jnp.zeros(len(excitations))) +delta_E, iteration = 10, 0 +results_hf = [] +opt_state = opt.init(theta) +prev_energy = cost_fn(theta) + +# run the VQE optimization loop until convergence threshold is reached +while abs(delta_E) > 1e-5: + gradient = jax.grad(cost_fn)(theta) + updates, opt_state = opt.update(gradient, opt_state) + theta = optax.apply_updates(theta, updates) + new_energy = cost_fn(theta) + delta_E = new_energy - prev_energy + prev_energy = new_energy + results_hf.append(new_energy) + if len(results_hf) % 5 == 0: + print(f"Step = {len(results_hf)}, Energy = {new_energy:.6f} Ha") +print(f"Starting with HF state took {len(results_hf)} iterations until convergence.") + +############################################################################## +# And compare with how things go when you run it with the CISD initial state: + + +def cost_fn_cisd(param): + return circuit_VQE(param, initial_state=wf_cisd) + + +theta = jnp.array(jnp.zeros(len(excitations))) +delta_E, iteration = 10, 0 +results_cisd = [] +opt_state = opt.init(theta) +prev_energy = cost_fn_cisd(theta) + +while abs(delta_E) > 1e-5: + gradient = jax.grad(cost_fn_cisd)(theta) + updates, opt_state = opt.update(gradient, opt_state) + theta = optax.apply_updates(theta, updates) + new_energy = cost_fn_cisd(theta) + delta_E = new_energy - prev_energy + prev_energy = new_energy + results_cisd.append(new_energy) + if len(results_cisd) % 5 == 0: + print(f"Step = {len(results_cisd)}, Energy = {new_energy:.6f} Ha") +print( + f"Starting with CISD state took {len(results_cisd)} iterations until convergence." +) + +############################################################################## +# Let's visualize the comparison between the two initial states, and see that indeed +# we get to the ground state much faster by starting with the CISD state. + +import matplotlib.pyplot as plt + +fig, ax = plt.subplots() +ax.plot(range(len(results_hf)), results_hf, color="r", marker="o", label="wf_hf") +ax.plot(range(len(results_cisd)), results_cisd, color="b", marker="o", label="wf_cisd") +ax.legend(fontsize=16) +ax.tick_params(axis="both", labelsize=16) +ax.set_xlabel("Iteration", fontsize=20) +ax.set_ylabel("Energy, Ha", fontsize=20) +plt.tight_layout() +plt.show() + +############################################################################## +# Indeed, the CISD state significantly shortens the VQE runtime. +# +# It is sometimes possible to foresee the extent of this speed-up of a particular initial state +# by computing its overlap with the ground state--a traditional metric of success for initial +# states in quantum algorithms. Because in our examples the states are regular arrays, computing an +# overlap between different states is as easy as computing a dot product + +print(np.dot(wf_cisd, wf_hf).real) +print(np.dot(wf_ccsd, wf_hf).real) +print(np.dot(wf_cisd, wf_ccsd).real) + +############################################################################## +# In this particular case of :math:`\text{H}_3^+,` even CISD gives the exact wavefunction, hence both overlaps +# with the HF state are identical. In more correlated molecules, overlaps will show that the more +# multireference methods DMRG and SHCI are farther away from the Hartree-Fock state, +# allowing them to perform better (you can check this by printing the overlaps with +# DMRG and SHCI in a more correlated molecule). If a ground state in such a case was known, +# the overlap to it could tell us directly the quality of the initial state. + +############################################################################## +# Conclusion +# ----------- +# This demo shows how to import initial states from outputs of traditional quantum chemistry methods +# for use in PennyLane. We showcased simple workflows for how to run +# a variety of state-of-the-art post-Hartree-Fock methods, from libraries such as +# `PySCF `_, +# `Block2 `_ and +# `Dice `_, to generate outputs that can then be +# converted to PennyLane's state vector format with a single line of code. With these +# initial states, we use the example of VQE to demonstrate how a better choice +# of initial state can lead to improved algorithmic performance. For the molecule +# used in our example, the CISD state was sufficient: however, in more correlated +# molecules, DMRG and SHCI initial states typically provide the best speed-ups. +# +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_jax_transformations/demo.py b/demonstrations_v2/tutorial_jax_transformations/demo.py index 9c13c37271..476e8fa3a4 100644 --- a/demonstrations_v2/tutorial_jax_transformations/demo.py +++ b/demonstrations_v2/tutorial_jax_transformations/demo.py @@ -1,311 +1,311 @@ -r""" -Using JAX with PennyLane -======================== - -.. meta:: - :property="og:description": Learn how to use JAX with PennyLane. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/jax.png - -.. related:: - - tutorial_qubit_rotation Basic tutorial: qubit rotation - tutorial_vqe A brief overview of VQE - tutorial_vqt Variational Quantum Thermalizer - -*Author: Chase Roberts — Posted: 12 April 2021. Last updated: 12 April 2021.* - -JAX is an incredibly powerful scientific computing library that has been gaining traction in -both the physics and deep learning communities. While JAX was originally designed for -classical machine learning (ML), many of its transformations are also useful -for quantum machine learning (QML), and can be used directly with PennyLane. -""" - -############################################################################## -# .. figure:: ../_static/demonstration_assets/jax_logo/jax.png -# :width: 50% -# :align: center -# -# In this tutorial, we'll go over a number of JAX transformations and show how you can -# use them to build and optimize quantum circuits. We'll show examples of how to -# do gradient descent with ``jax.grad``, run quantum circuits in parallel -# using ``jax.vmap``, compile and optimize simulations with ``jax.jit``, -# and control and seed the random nature of quantum computer simulations -# with ``jax.random``. By the end of this tutorial you should feel just as comfortable -# transforming quantum computing programs with JAX as you do transforming your -# neural networks. -# -# If this is your first time reading PennyLane code, we recommend going through -# the :doc:`basic tutorial ` -# first. It's all in vanilla NumPy, so you should be able to -# easily transfer what you learn to JAX when you come back. -# -# With that said, we begin by importing PennyLane, JAX, the JAX-provided version of NumPy and -# set up a two-qubit device for computations. We'll be using the ``default.qubit`` device -# for the first part of this tutorial. - -import jax -import jax.numpy as jnp -import pennylane as qml - -# Added to silence some warnings. -jax.config.update("jax_enable_x64", True) - -dev = qml.device("default.qubit", wires=2) - -############################################################################## -# Let's start with a simple example circuit that generates a two-qubit entangled state, -# then evaluates the expectation value of the Pauli-Z operator on the first wire. - - -@qml.qnode(dev, interface="jax") -def circuit(param): - # These two gates represent our QML model. - qml.RX(param, wires=0) - qml.CNOT(wires=[0, 1]) - - # The expval here will be the "cost function" we try to minimize. - # Usually, this would be defined by the problem we want to solve, - # but for this example we'll just use a single PauliZ. - return qml.expval(qml.PauliZ(0)) - - -############################################################################## -# We can now execute the circuit just like any other python function. -print(f"Result: {repr(circuit(0.123))}") - -############################################################################## -# Notice that the output of the circuit is a JAX ``DeviceArray``. -# In fact, when we use the ``default.qubit`` device, the entire computation -# is done in JAX, so we can use all of the JAX tools out of the box! -# -# Now let's move on to an example of a transformation. The code we wrote above is entirely -# differentiable, so let's calculate its gradient with ``jax.grad``. -print("\nGradient Descent") -print("---------------") - -# We use jax.grad here to transform our circuit method into one -# that calcuates the gradient of the output relative to the input. - -grad_circuit = jax.grad(circuit) -print(f"grad_circuit(jnp.pi / 2): {grad_circuit(jnp.pi / 2):0.3f}") - -# We can then use this grad_circuit function to optimize the parameter value -# via gradient descent. -param = 0.123 # Some initial value. - -print(f"Initial param: {param:0.3f}") -print(f"Initial cost: {circuit(param):0.3f}") - -for _ in range(100): # Run for 100 steps. - param -= grad_circuit(param) # Gradient-descent update. - -print(f"Tuned param: {param:0.3f}") -print(f"Tuned cost: {circuit(param):0.3f}") - -############################################################################# -# And that's QML in a nutshell! If you've done classical machine learning before, -# the above training loop should feel very familiar to you. The only difference is -# that we used a quantum computer (or rather, a simulation of one) as part of our -# model and cost calculation. In the end, almost all QML problems involve tuning some -# parameters and minimizing some cost function, just like classical ML. -# While classical ML focuses on learning classical systems like language or vision, -# QML is most useful for learning about quantum systems. For example, -# :doc:`finding chemical ground states ` -# or learning to :doc:`sample thermal energy states `. - - -############################################################################## -# Batching and Evolutionary Strategies -# ------------------------------------- -# -# .. figure:: ../_static/demonstration_assets/jax_logo/jaxvmap.png -# :width: 50% -# :align: center -# -# We just showed how we can use gradient methods to learn a parameter value, -# but on real quantum computing hardware, calculating gradients can be really expensive and noisy. -# Another approach is to use `evolutionary strategies `__ -# (ES) to learn these parameters. -# Here, we will be using the ``jax.vmap`` `transform `__ -# to make running batches of circuits much easier. ``vmap`` essentially transforms a single quantum computer into -# multiple running in parallel! - -print("\n\nBatching and Evolutionary Strategies") -print("------------------------------------") - -# Create a vectorized version of our original circuit. -vcircuit = jax.vmap(circuit) - -# Now, we call the ``vcircuit`` with multiple parameters at once and get back a -# batch of expectations. -# This examples runs 3 quantum circuits in parallel. -batch_params = jnp.array([1.02, 0.123, -0.571]) - -batched_results = vcircuit(batch_params) -print(f"Batched result: {batched_results}") - -############################################################################## -# Let's now set up our ES training loop. The idea is pretty simple. First, we -# calculate the expected values of each of our parameters. The cost values -# then determine the "weight" of that example. The lower the cost, the larger the weight. -# These batches are then used to generate a new set of parameters. - -# Needed to do randomness with JAX. -# For more info on how JAX handles randomness, see the documentation. -# https://jax.readthedocs.io/en/latest/jax.random.html -key = jax.random.PRNGKey(0) - -# Generate our first set of samples. -params = jax.random.normal(key, (100,)) -mean = jnp.average(params) -var = 1.0 -print(f"Initial value: {mean:0.3f}") -print(f"Initial cost: {circuit(mean):0.3f}") - -for _ in range(200): - # In this line, we run all 100 circuits in parallel. - costs = vcircuit(params) - - # Use exp(-x) here since the costs could be negative. - weights = jnp.exp(-costs) - mean = jnp.average(params, weights=weights) - - # We decrease the variance as we converge to a solution. - var = var * 0.97 - - # Split the PRNGKey to generate a new set of random samples. - key, split = jax.random.split(key) - params = jax.random.normal(split, (100,)) * var + mean - -print(f"Final value: {mean:0.3f}") -print(f"Final cost: {circuit(mean):0.3f}") - - -############################################################################# -# How to use jax.jit: Compiling Circuit Execution -# ----------------------------------------------- -# .. figure:: ../_static/demonstration_assets/jax_logo/jaxjit.png -# :width: 50% -# :align: center -# -# JAX is built on top of `XLA `__, a powerful -# numerics library that can optimize and cross compile computations to different hardware, -# including CPUs, GPUs, etc. JAX can compile its computation to XLA via the ``jax.jit`` -# `transform. `__ -# -# When compiling an XLA program, the compiler will do several rounds of optimization -# passes to enhance the performance of the computation. Because of this compilation overhead, -# you'll generally find the first time calling the function to be slow, but all subsequent -# calls are much, much faster. You'll likely want to do it if you're running -# the same circuit over and over but with different parameters, like you would find in almost -# all variational quantum algorithms. - - -print("\n\nJit Example") -print("-----------") - - -@qml.qnode(dev, interface="jax") -def circuit(param): - qml.RX(param, wires=0) - qml.CNOT(wires=[0, 1]) - return qml.expval(qml.PauliZ(0)) - - -# Compiling your circuit with JAX is very easy, just add jax.jit! -jit_circuit = jax.jit(circuit) - -import time - -# No jit. -start = time.time() -# JAX runs async, so .block_until_ready() blocks until the computation -# is actually finished. You'll only need to use this if you're doing benchmarking. -circuit(0.123).block_until_ready() -no_jit_time = time.time() - start - -# First call with jit. -start = time.time() -jit_circuit(0.123).block_until_ready() -first_time = time.time() - start - -# Second call with jit. -start = time.time() -jit_circuit(0.123).block_until_ready() -second_time = time.time() - start - - -print(f"No jit time: {no_jit_time:0.8f} seconds") -# Compilation overhead will make the first call slower than without jit... -print(f"First run time: {first_time:0.8f} seconds") -# ... but the second run time is >100x faster than the first! -print(f"Second run time: {second_time:0.8f} seconds") - - -# You can see that for the cost of some compilation overhead, we can -# greatly increase our performance of our simulation by orders of magnitude. - -############################################################################## -# Shots and Sampling with JAX -# ---------------------------- -# -# JAX was designed to enable experiments to be as repeatable as possible. Because of this, -# JAX requires us to seed all randomly generated values (as you saw in the above -# batching example). Sadly, the universe doesn't allow us to seed real quantum computers, -# so if we want our JAX to mimic a real device, we'll have to handle randomness ourselves. -# -# To learn more about how JAX handles randomness, visit their -# `documentation site. `__ -# -# .. note:: -# This example only applies if you are using ``jax.jit``. Otherwise, PennyLane -# automatically seeds and resets the random-number-generator for you on each call. -# -# To set the random number generating key, you'll have to pass the ``jax.random.PRNGKey`` -# when constructing the device. Because of this, if you want to use ``jax.jit`` with randomness, -# the device construction will have to happen within that jitted method. - -print("\n\nRandomness") -print("----------") - - -# Let's create our circuit with randomness and compile it with jax.jit. -@jax.jit -def circuit(key, param): - # Notice how the device construction now happens within the jitted method. - dev = qml.device("default.qubit", wires=2, shots=10, seed=key) - - # Now we can create our qnode within the circuit function. - @qml.qnode(dev, interface="jax", diff_method=None) - def my_circuit(): - qml.RX(param, wires=0) - qml.CNOT(wires=[0, 1]) - return qml.sample(qml.PauliZ(0)) - - return my_circuit() - - -key1 = jax.random.PRNGKey(0) -key2 = jax.random.PRNGKey(1) - -# Notice that the first two runs return exactly the same results, -print(f"key1: {circuit(key1, jnp.pi/2)}") -print(f"key1: {circuit(key1, jnp.pi/2)}") - -# The second run has different results. -print(f"key2: {circuit(key2, jnp.pi/2)}") - -################################################ -# Closing Remarks -# ---------------- -# By now, using JAX with PennyLane should feel very natural. They -# complement each other very nicely; JAX with its powerful transforms, and PennyLane -# with its easy access to quantum computers. We're still in early days of -# development, but we hope to continue to grow our ecosystem around JAX, -# and by extension, grow JAX into quantum computing and quantum machine learning. -# The future looks bright for this field, and we're excited to see what you build! -# -# -# About the author -# ---------------- -# +r""" +Using JAX with PennyLane +======================== + +.. meta:: + :property="og:description": Learn how to use JAX with PennyLane. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/jax.png + +.. related:: + + tutorial_qubit_rotation Basic tutorial: qubit rotation + tutorial_vqe A brief overview of VQE + tutorial_vqt Variational Quantum Thermalizer + +*Author: Chase Roberts — Posted: 12 April 2021. Last updated: 12 April 2021.* + +JAX is an incredibly powerful scientific computing library that has been gaining traction in +both the physics and deep learning communities. While JAX was originally designed for +classical machine learning (ML), many of its transformations are also useful +for quantum machine learning (QML), and can be used directly with PennyLane. +""" + +############################################################################## +# .. figure:: ../_static/demonstration_assets/jax_logo/jax.png +# :width: 50% +# :align: center +# +# In this tutorial, we'll go over a number of JAX transformations and show how you can +# use them to build and optimize quantum circuits. We'll show examples of how to +# do gradient descent with ``jax.grad``, run quantum circuits in parallel +# using ``jax.vmap``, compile and optimize simulations with ``jax.jit``, +# and control and seed the random nature of quantum computer simulations +# with ``jax.random``. By the end of this tutorial you should feel just as comfortable +# transforming quantum computing programs with JAX as you do transforming your +# neural networks. +# +# If this is your first time reading PennyLane code, we recommend going through +# the :doc:`basic tutorial ` +# first. It's all in vanilla NumPy, so you should be able to +# easily transfer what you learn to JAX when you come back. +# +# With that said, we begin by importing PennyLane, JAX, the JAX-provided version of NumPy and +# set up a two-qubit device for computations. We'll be using the ``default.qubit`` device +# for the first part of this tutorial. + +import jax +import jax.numpy as jnp +import pennylane as qml + +# Added to silence some warnings. +jax.config.update("jax_enable_x64", True) + +dev = qml.device("default.qubit", wires=2) + +############################################################################## +# Let's start with a simple example circuit that generates a two-qubit entangled state, +# then evaluates the expectation value of the Pauli-Z operator on the first wire. + + +@qml.qnode(dev, interface="jax") +def circuit(param): + # These two gates represent our QML model. + qml.RX(param, wires=0) + qml.CNOT(wires=[0, 1]) + + # The expval here will be the "cost function" we try to minimize. + # Usually, this would be defined by the problem we want to solve, + # but for this example we'll just use a single PauliZ. + return qml.expval(qml.PauliZ(0)) + + +############################################################################## +# We can now execute the circuit just like any other python function. +print(f"Result: {repr(circuit(0.123))}") + +############################################################################## +# Notice that the output of the circuit is a JAX ``DeviceArray``. +# In fact, when we use the ``default.qubit`` device, the entire computation +# is done in JAX, so we can use all of the JAX tools out of the box! +# +# Now let's move on to an example of a transformation. The code we wrote above is entirely +# differentiable, so let's calculate its gradient with ``jax.grad``. +print("\nGradient Descent") +print("---------------") + +# We use jax.grad here to transform our circuit method into one +# that calcuates the gradient of the output relative to the input. + +grad_circuit = jax.grad(circuit) +print(f"grad_circuit(jnp.pi / 2): {grad_circuit(jnp.pi / 2):0.3f}") + +# We can then use this grad_circuit function to optimize the parameter value +# via gradient descent. +param = 0.123 # Some initial value. + +print(f"Initial param: {param:0.3f}") +print(f"Initial cost: {circuit(param):0.3f}") + +for _ in range(100): # Run for 100 steps. + param -= grad_circuit(param) # Gradient-descent update. + +print(f"Tuned param: {param:0.3f}") +print(f"Tuned cost: {circuit(param):0.3f}") + +############################################################################# +# And that's QML in a nutshell! If you've done classical machine learning before, +# the above training loop should feel very familiar to you. The only difference is +# that we used a quantum computer (or rather, a simulation of one) as part of our +# model and cost calculation. In the end, almost all QML problems involve tuning some +# parameters and minimizing some cost function, just like classical ML. +# While classical ML focuses on learning classical systems like language or vision, +# QML is most useful for learning about quantum systems. For example, +# :doc:`finding chemical ground states ` +# or learning to :doc:`sample thermal energy states `. + + +############################################################################## +# Batching and Evolutionary Strategies +# ------------------------------------- +# +# .. figure:: ../_static/demonstration_assets/jax_logo/jaxvmap.png +# :width: 50% +# :align: center +# +# We just showed how we can use gradient methods to learn a parameter value, +# but on real quantum computing hardware, calculating gradients can be really expensive and noisy. +# Another approach is to use `evolutionary strategies `__ +# (ES) to learn these parameters. +# Here, we will be using the ``jax.vmap`` `transform `__ +# to make running batches of circuits much easier. ``vmap`` essentially transforms a single quantum computer into +# multiple running in parallel! + +print("\n\nBatching and Evolutionary Strategies") +print("------------------------------------") + +# Create a vectorized version of our original circuit. +vcircuit = jax.vmap(circuit) + +# Now, we call the ``vcircuit`` with multiple parameters at once and get back a +# batch of expectations. +# This examples runs 3 quantum circuits in parallel. +batch_params = jnp.array([1.02, 0.123, -0.571]) + +batched_results = vcircuit(batch_params) +print(f"Batched result: {batched_results}") + +############################################################################## +# Let's now set up our ES training loop. The idea is pretty simple. First, we +# calculate the expected values of each of our parameters. The cost values +# then determine the "weight" of that example. The lower the cost, the larger the weight. +# These batches are then used to generate a new set of parameters. + +# Needed to do randomness with JAX. +# For more info on how JAX handles randomness, see the documentation. +# https://jax.readthedocs.io/en/latest/jax.random.html +key = jax.random.PRNGKey(0) + +# Generate our first set of samples. +params = jax.random.normal(key, (100,)) +mean = jnp.average(params) +var = 1.0 +print(f"Initial value: {mean:0.3f}") +print(f"Initial cost: {circuit(mean):0.3f}") + +for _ in range(200): + # In this line, we run all 100 circuits in parallel. + costs = vcircuit(params) + + # Use exp(-x) here since the costs could be negative. + weights = jnp.exp(-costs) + mean = jnp.average(params, weights=weights) + + # We decrease the variance as we converge to a solution. + var = var * 0.97 + + # Split the PRNGKey to generate a new set of random samples. + key, split = jax.random.split(key) + params = jax.random.normal(split, (100,)) * var + mean + +print(f"Final value: {mean:0.3f}") +print(f"Final cost: {circuit(mean):0.3f}") + + +############################################################################# +# How to use jax.jit: Compiling Circuit Execution +# ----------------------------------------------- +# .. figure:: ../_static/demonstration_assets/jax_logo/jaxjit.png +# :width: 50% +# :align: center +# +# JAX is built on top of `XLA `__, a powerful +# numerics library that can optimize and cross compile computations to different hardware, +# including CPUs, GPUs, etc. JAX can compile its computation to XLA via the ``jax.jit`` +# `transform. `__ +# +# When compiling an XLA program, the compiler will do several rounds of optimization +# passes to enhance the performance of the computation. Because of this compilation overhead, +# you'll generally find the first time calling the function to be slow, but all subsequent +# calls are much, much faster. You'll likely want to do it if you're running +# the same circuit over and over but with different parameters, like you would find in almost +# all variational quantum algorithms. + + +print("\n\nJit Example") +print("-----------") + + +@qml.qnode(dev, interface="jax") +def circuit(param): + qml.RX(param, wires=0) + qml.CNOT(wires=[0, 1]) + return qml.expval(qml.PauliZ(0)) + + +# Compiling your circuit with JAX is very easy, just add jax.jit! +jit_circuit = jax.jit(circuit) + +import time + +# No jit. +start = time.time() +# JAX runs async, so .block_until_ready() blocks until the computation +# is actually finished. You'll only need to use this if you're doing benchmarking. +circuit(0.123).block_until_ready() +no_jit_time = time.time() - start + +# First call with jit. +start = time.time() +jit_circuit(0.123).block_until_ready() +first_time = time.time() - start + +# Second call with jit. +start = time.time() +jit_circuit(0.123).block_until_ready() +second_time = time.time() - start + + +print(f"No jit time: {no_jit_time:0.8f} seconds") +# Compilation overhead will make the first call slower than without jit... +print(f"First run time: {first_time:0.8f} seconds") +# ... but the second run time is >100x faster than the first! +print(f"Second run time: {second_time:0.8f} seconds") + + +# You can see that for the cost of some compilation overhead, we can +# greatly increase our performance of our simulation by orders of magnitude. + +############################################################################## +# Shots and Sampling with JAX +# ---------------------------- +# +# JAX was designed to enable experiments to be as repeatable as possible. Because of this, +# JAX requires us to seed all randomly generated values (as you saw in the above +# batching example). Sadly, the universe doesn't allow us to seed real quantum computers, +# so if we want our JAX to mimic a real device, we'll have to handle randomness ourselves. +# +# To learn more about how JAX handles randomness, visit their +# `documentation site. `__ +# +# .. note:: +# This example only applies if you are using ``jax.jit``. Otherwise, PennyLane +# automatically seeds and resets the random-number-generator for you on each call. +# +# To set the random number generating key, you'll have to pass the ``jax.random.PRNGKey`` +# when constructing the device. Because of this, if you want to use ``jax.jit`` with randomness, +# the device construction will have to happen within that jitted method. + +print("\n\nRandomness") +print("----------") + + +# Let's create our circuit with randomness and compile it with jax.jit. +@jax.jit +def circuit(key, param): + # Notice how the device construction now happens within the jitted method. + dev = qml.device("default.qubit", wires=2, shots=10, seed=key) + + # Now we can create our qnode within the circuit function. + @qml.qnode(dev, interface="jax", diff_method=None) + def my_circuit(): + qml.RX(param, wires=0) + qml.CNOT(wires=[0, 1]) + return qml.sample(qml.PauliZ(0)) + + return my_circuit() + + +key1 = jax.random.PRNGKey(0) +key2 = jax.random.PRNGKey(1) + +# Notice that the first two runs return exactly the same results, +print(f"key1: {circuit(key1, jnp.pi/2)}") +print(f"key1: {circuit(key1, jnp.pi/2)}") + +# The second run has different results. +print(f"key2: {circuit(key2, jnp.pi/2)}") + +################################################ +# Closing Remarks +# ---------------- +# By now, using JAX with PennyLane should feel very natural. They +# complement each other very nicely; JAX with its powerful transforms, and PennyLane +# with its easy access to quantum computers. We're still in early days of +# development, but we hope to continue to grow our ecosystem around JAX, +# and by extension, grow JAX into quantum computing and quantum machine learning. +# The future looks bright for this field, and we're excited to see what you build! +# +# +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_mapping/demo.py b/demonstrations_v2/tutorial_mapping/demo.py index 8e88a64420..96a757a506 100644 --- a/demonstrations_v2/tutorial_mapping/demo.py +++ b/demonstrations_v2/tutorial_mapping/demo.py @@ -1,348 +1,348 @@ -r""" - -Mapping fermionic operators to qubit operators -============================================== - -Simulating quantum systems stands as one of the most anticipated applications of quantum -chemistry with the potential to transform our understanding of chemical and physical systems. These -simulations typically require mapping schemes that transform fermionic representations into qubit -representations. There are a variety of mapping schemes used in quantum computing but the -conventional ones are the Jordan-Wigner, Parity, and Bravyi-Kitaev transformations [#Tranter]_. In -this demo, you will learn about these mapping schemes and their implementation in PennyLane. You -will also learn how to use these mappings in the context of computing the ground state energy -of a molecular system. - -.. figure:: ../_static/demonstration_assets/mapping/long_image.png - :align: center - :width: 80% - :target: javascript:void(0) - -Jordan-Wigner Mapping ---------------------- -The state of a quantum system in the `second quantized `__ -formalism is typically represented in the occupation-number basis. For fermions, the occupation -number is either :math:`0` or :math:`1` as a result of the Pauli exclusion principle. The -occupation-number basis states can be represented by a vector that is constructed by -applying the fermionic creation operators, :math:`a^\dagger,` to a vacuum state: - -.. math:: - - a^\dagger | 0 \rangle = | 1 \rangle. - -Similarly, electrons can be removed from a state by applying the fermionic annihilation -operators :math:`a:` - -.. math:: - - a | 1 \rangle = | 0 \rangle. - -An intuitive way to represent a fermionic system in the qubit basis is to store the fermionic -occupation numbers in qubit states. This requires constructing qubit creation and annihilation -operators that can be applied to an initial state to provide the desired occupation number state. -These operators are defined as - -.. math:: - - Q^{\dagger}_j = \frac{1}{2}(X_j - iY_j), - -and - -.. math:: - - Q_j = \frac{1}{2}(X_j + iY_j), - -where :math:`X` and :math:`Y` are Pauli operators. However, an important property of fermionic -creation and annihilation operators is the anti-commutation relations between them, which is not -preserved by directly using the analogous qubit operators. - -.. math:: - - [a^{\dagger}_j, a^{\dagger}_k]_+ = 0, \:\:\:\:\:\:\: [a_j, a_k]_+=0, \:\:\:\:\:\:\: [a_j, a^{\dagger}_k]_+ = \delta_{jk} I, - -where :math:`I` is the identity operator. These relations are essential for capturing the Pauli -exclusion principle which requires the fermionic wave function to be antisymmetric. The -anti-commutation relations between fermionic operators can be incorporated by adding a sequence of -Pauli :math:`Z` operators to the qubit operators (can you verify that?). According to these, -the fermionic creation and annihilation operators can be represented in the basis of Pauli operators: - -.. math:: - - a_{j}^{\dagger} = \frac{1}{2}(X_j - iY_j) \otimes_{k`__, -:math:`a_{5}^{\dagger},` which creates an electron in the fifth orbital. One -way to do this in PennyLane is to use :func:`~.pennylane.fermi.from_string`. We -then map the operator using :func:`~.pennylane.fermi.jordan_wigner`. -""" - -import pennylane as qml -from pennylane.fermi import from_string, jordan_wigner - -qubits = 10 -fermi_op = from_string("5+") -pauli_jw = jordan_wigner(fermi_op, ps=True) -pauli_jw - -############################################################################### -# The long sequence of the :math:`Z` operations can significantly increase the -# resources needed to implement the operator on quantum hardware, as it may require using entangling -# operations across multiple qubits, which can be challenging to implement efficiently. One way to -# avoid having such long tails of :math:`Z` operations is to work in the parity basis where the -# fermionic state stores the parity instead of the occupation number. -# -# Parity Mapping -# -------------- -# In the Parity representation, the state of a fermionic system is represented with a binary -# vector that corresponds to the parity of the orbitals. Note that parity for a set of orbitals -# is defined as the sum (mod 2) of the occupation numbers. Let's look at an example using the -# PennyLane function :func:`~.pennylane.qchem.hf_state` to obtain the state of a system with -# :math:`10` orbitals and :math:`5` electrons in both the occupation-number and parity bases. - -orbitals = 10 -electrons = 5 -state_number = qml.qchem.hf_state(electrons, orbitals) -state_parity = qml.qchem.hf_state(electrons, orbitals, basis="parity") - -print("State in occupation number basis:\n", state_number) -print("State in parity basis:\n", state_parity) - -############################################################################## -# Note that Parity mapping solves the non-locality problem of the parity information by storing -# the parity of orbital :math:`j` in qubit :math:`j` while the occupation information for the -# orbital is stored non-locally. In the parity basis, we cannot represent the creation or -# annihilation of a particle in orbital -# :math:`j` by simply operating with qubit creation or annihilation operators. In fact, the state of -# the :math:`(j − 1)`-th qubit provides information about the occupation state of qubit :math:`j` -# and whether we need to act with a creation or annihilation operator. Similarly, the creation or -# annihilation of a particle in qubit :math:`j` changes the parity of all qubits following it. -# As a result, the operator that is equivalent to creation and annihilation operators in -# the parity basis is a two-qubit operator acting on qubits :math:`j` and :math:`j − 1,` and -# an update operator which updates the parity of all qubits with index larger than j: -# -# .. math:: -# -# a_{j}^{\dagger} = \frac{1}{2}(Z_{j-1} \otimes X_j - iY_j) \otimes_{k>j} X_{k} -# -# and -# -# .. math:: -# -# a_{j} = \frac{1}{2}(Z_{j-1} \otimes X_j + iY_j) \otimes_{k>j} X_{k} -# -# Let's now look at an example where we map our fermionic operator :math:`a_{5}^{\dagger}` in a -# :math:`10` qubit system with -# Parity mapping using :func:`~.pennylane.fermi.parity_transform` in PennyLane. - -qubits = 10 -pauli_pr = qml.parity_transform(fermi_op, qubits, ps=True) -pauli_pr - -############################################################################## -# It is evident from this example that the Parity mapping doesn't improve upon the -# the Jordan-Wigner mapping as the long :math:`Z` strings are now replaced by :math:`X` -# strings. However, a very important advantage of using parity mapping is the ability to taper two -# qubits by leveraging symmetries of molecular Hamiltonians. You can find -# more information about this in our -# `qubit tapering `__ demo. -# Let's look at an example. - -generators = [qml.prod(*[qml.Z(i) for i in range(qubits-1)]), qml.Z(qubits-1)] -paulixops = qml.paulix_ops(generators, qubits) -paulix_sector = [1, 1] -taper_op = qml.taper(pauli_pr, generators, paulixops, paulix_sector) -qml.simplify(taper_op) - -############################################################################### -# Note that the tapered operator doesn't include qubit :math:`8` and :math:`9.` -# -# Bravyi-Kitaev Mapping -# --------------------- -# The Bravyi-Kitaev mapping aims to improve the linear scaling of the Jordan-Wigner and Parity -# mappings, in the number of qubits, -# by storing both the occupation number and the parity non-locally. In this formalism, even-labelled -# qubits store the occupation number of orbitals and odd-labelled qubits store parity -# through partial sums of occupation numbers. The corresponding creation and annihilation operators -# are defined `here `__. -# Let's use the :func:`~.pennylane.fermi.bravyi_kitaev` function to map our :math:`a_{5}^{\dagger}` -# operator. - -pauli_bk = qml.bravyi_kitaev(fermi_op, qubits, ps=True) -pauli_bk - -############################################################################## -# It is clear that the local nature of the transformation in the Bravyi-Kitaev mapping helps to -# improve the number of qubits. This advantage becomes even more clear if you -# work with a larger qubit -# system. We now use the Bravyi-Kitaev mapping to construct a qubit Hamiltonian and -# compute its ground state energy with the `VQE `__ -# method. -# -# Energy Calculation -# ------------------ -# To perform a VQE calculation for a desired Hamiltonian, we need an initial state typically set to -# a Hartree-Fock state, and a set of excitation operators to build an ansatz that allows us to -# obtain the ground state and then compute the expectation value of the Hamiltonian. It is important -# to note that the initial state and the excitation operators should be consistent with the -# mapping scheme used for obtaining the qubit Hamiltonian. Let's now build these three components -# for :math:`H_2` and compute its ground state energy. For this example, we will use the -# Bravyi-Kitaev transformation but similar calculations can be run with the other mappings. -# -# Molecular Hamiltonian -# ^^^^^^^^^^^^^^^^^^^^^ -# First, let's build the molecular Hamiltonian. This requires defining the atomic symbols and -# coordinates. - -from pennylane import qchem -from jax import numpy as jnp -import jax - -jax.config.update("jax_enable_x64", True) - -symbols = ['H', 'H'] -geometry = jnp.array([[0.0, 0.0, -0.69434785], - [0.0, 0.0, 0.69434785]]) - -mol = qchem.Molecule(symbols, geometry) - -############################################################################## -# We then use the :func:`~pennylane.qchem.fermionic_hamiltonian` function to build -# the fermionic Hamiltonian for our molecule. - -h_fermi = qchem.fermionic_hamiltonian(mol)() - -############################################################################## -# We now use :func:`~pennylane.fermi.bravyi_kitaev` to transform the fermionic Hamiltonian to its -# qubit representation. - -electrons = 2 -qubits = len(h_fermi.wires) -h_pauli = qml.bravyi_kitaev(h_fermi, qubits, tol=1e-16) - -############################################################################## -# Initial state -# ^^^^^^^^^^^^^ -# We now need the initial state that has the correct number of electrons. We use the Hartree-Fock -# state which can be obtained in a user-defined basis by using :func:`~.pennylane.qchem.hf_state` in -# PennyLane. For that, we need to specify the number of electrons, the number of orbitals and the -# desired mapping. - -hf_state = qchem.hf_state(electrons, qubits, basis="bravyi_kitaev") - -############################################################################## -# Excitation operators -# ^^^^^^^^^^^^^^^^^^^^ -# We now build our quantum circuit with the :class:`~.pennylane.UCCSD` ansatz. This ansatz is -# constructed with a set of single and double excitation operators. In PennyLane, we have -# :class:`~.pennylane.SingleExcitation` and :class:`~.pennylane.DoubleExcitation` operators which -# are very efficient, but they are only compatible with the Jordan-Wigner mapping. Here we construct -# the excitation operators manually. We start from the fermionic single and double excitation -# operators defined as [#Yordanov]_ -# -# .. math:: -# -# T_i^k(\theta) = \theta(a_k^{\dagger}a_i - a_i^{\dagger}a_k) -# -# and -# -# .. math:: -# -# T_{ij}^{kl}(\theta) = \theta(a_k^{\dagger}a_l^{\dagger}a_i a_j - -# a_i^{\dagger}a_j^{\dagger}a_k a_l), -# -# where :math:`\theta` is an adjustable parameter. We can easily construct these fermionic -# excitation operators in PennyLane and then map them to the qubit basis with -# :func:`~pennylane.fermi.bravyi_kitaev`, similar to the way we transformed the fermionic -# Hamiltonian. - -from pennylane.fermi import from_string - -singles, doubles = qchem.excitations(electrons, qubits) - -singles_fermi = [] -for ex in singles: - singles_fermi.append(from_string(f"{ex[1]}+ {ex[0]}-") - - from_string(f"{ex[0]}+ {ex[1]}-")) - -doubles_fermi = [] -for ex in doubles: - doubles_fermi.append(from_string(f"{ex[3]}+ {ex[2]}+ {ex[1]}- {ex[0]}-") - - from_string(f"{ex[0]}+ {ex[1]}+ {ex[2]}- {ex[3]}-")) - -############################################################################## -# The fermionic operators are now mapped to qubit operators. - -singles_pauli = [] -for op in singles_fermi: - singles_pauli.append(qml.bravyi_kitaev(op, qubits, ps=True)) - -doubles_pauli = [] -for op in doubles_fermi: - doubles_pauli.append(qml.bravyi_kitaev(op, qubits, ps=True)) - -############################################################################## -# Note that we need to exponentiate these operators to be able to use them in the circuit -# [#Yordanov]_. We also use a set of pre-defined parameters to construct the excitation gates. - -params = jnp.array([0.22347661, 0.0, 0.0]) - -dev = qml.device("default.qubit", wires=qubits) - -@qml.qnode(dev) -def circuit(params): - qml.BasisState(hf_state, wires=range(qubits)) - - for i, excitation in enumerate(doubles_pauli): - qml.exp((excitation * params[i] / 2).operation()), range(qubits) - - for j, excitation in enumerate(singles_pauli): - qml.exp((excitation * params[i + j + 1] / 2).operation()), range(qubits) - - return qml.expval(h_pauli) - -print('Energy =', circuit(params)) - -############################################################################## -# Using the above circuit, we produce the ground state energy of :math:`H_2` molecule. -# -# Conclusion -# --------------- -# In this demo, we learned about various mapping schemes available in PennyLane and how -# they can be used to convert fermionic operators to qubits operators. We also learned -# the pros and cons associated with each scheme. The Jordan-Wigner mapping provides an intuitive -# approach while parity mapping allows tapering qubits in molecular systems. However, these two -# methods usually give qubit operators with a long chain of Pauli operators, which makes them -# challenging to implement on quantum hardware. The Bravyi-Kitaev mapping, on the other hand, -# emphasizes locality and resource efficiency, making it an attractive option for certain -# applications. Through this demonstration, we recognize the importance of choosing an appropriate -# mapping scheme tailored to the specific problem at hand and the available quantum -# resources. Lastly, we showed how a user can employ these different mappings in ground state energy -# calculations through an example. We would like to encourage the interested readers to run -# calculations for different molecular systems and observe how the scaling in the number of qubits -# is influenced by the selected mapping techniques. -# -# References -# ---------- -# -# .. [#Tranter] -# -# A. Tranter, S. Sofia, *et al.*, "The Bravyi–Kitaev Transformation: -# Properties and Applications". `International Journal of Quantum Chemistry 115.19 (2015). -# `__ -# -# .. [#Yordanov] -# -# Y. S. Yordanov, *et al.*, "Efficient quantum circuits for quantum computational chemistry". -# `Physical Review A 102.6 (2020). -# `__ -# -# About the author -# ---------------- +r""" + +Mapping fermionic operators to qubit operators +============================================== + +Simulating quantum systems stands as one of the most anticipated applications of quantum +chemistry with the potential to transform our understanding of chemical and physical systems. These +simulations typically require mapping schemes that transform fermionic representations into qubit +representations. There are a variety of mapping schemes used in quantum computing but the +conventional ones are the Jordan-Wigner, Parity, and Bravyi-Kitaev transformations [#Tranter]_. In +this demo, you will learn about these mapping schemes and their implementation in PennyLane. You +will also learn how to use these mappings in the context of computing the ground state energy +of a molecular system. + +.. figure:: ../_static/demonstration_assets/mapping/long_image.png + :align: center + :width: 80% + :target: javascript:void(0) + +Jordan-Wigner Mapping +--------------------- +The state of a quantum system in the `second quantized `__ +formalism is typically represented in the occupation-number basis. For fermions, the occupation +number is either :math:`0` or :math:`1` as a result of the Pauli exclusion principle. The +occupation-number basis states can be represented by a vector that is constructed by +applying the fermionic creation operators, :math:`a^\dagger,` to a vacuum state: + +.. math:: + + a^\dagger | 0 \rangle = | 1 \rangle. + +Similarly, electrons can be removed from a state by applying the fermionic annihilation +operators :math:`a:` + +.. math:: + + a | 1 \rangle = | 0 \rangle. + +An intuitive way to represent a fermionic system in the qubit basis is to store the fermionic +occupation numbers in qubit states. This requires constructing qubit creation and annihilation +operators that can be applied to an initial state to provide the desired occupation number state. +These operators are defined as + +.. math:: + + Q^{\dagger}_j = \frac{1}{2}(X_j - iY_j), + +and + +.. math:: + + Q_j = \frac{1}{2}(X_j + iY_j), + +where :math:`X` and :math:`Y` are Pauli operators. However, an important property of fermionic +creation and annihilation operators is the anti-commutation relations between them, which is not +preserved by directly using the analogous qubit operators. + +.. math:: + + [a^{\dagger}_j, a^{\dagger}_k]_+ = 0, \:\:\:\:\:\:\: [a_j, a_k]_+=0, \:\:\:\:\:\:\: [a_j, a^{\dagger}_k]_+ = \delta_{jk} I, + +where :math:`I` is the identity operator. These relations are essential for capturing the Pauli +exclusion principle which requires the fermionic wave function to be antisymmetric. The +anti-commutation relations between fermionic operators can be incorporated by adding a sequence of +Pauli :math:`Z` operators to the qubit operators (can you verify that?). According to these, +the fermionic creation and annihilation operators can be represented in the basis of Pauli operators: + +.. math:: + + a_{j}^{\dagger} = \frac{1}{2}(X_j - iY_j) \otimes_{k`__, +:math:`a_{5}^{\dagger},` which creates an electron in the fifth orbital. One +way to do this in PennyLane is to use :func:`~.pennylane.fermi.from_string`. We +then map the operator using :func:`~.pennylane.fermi.jordan_wigner`. +""" + +import pennylane as qml +from pennylane.fermi import from_string, jordan_wigner + +qubits = 10 +fermi_op = from_string("5+") +pauli_jw = jordan_wigner(fermi_op, ps=True) +pauli_jw + +############################################################################### +# The long sequence of the :math:`Z` operations can significantly increase the +# resources needed to implement the operator on quantum hardware, as it may require using entangling +# operations across multiple qubits, which can be challenging to implement efficiently. One way to +# avoid having such long tails of :math:`Z` operations is to work in the parity basis where the +# fermionic state stores the parity instead of the occupation number. +# +# Parity Mapping +# -------------- +# In the Parity representation, the state of a fermionic system is represented with a binary +# vector that corresponds to the parity of the orbitals. Note that parity for a set of orbitals +# is defined as the sum (mod 2) of the occupation numbers. Let's look at an example using the +# PennyLane function :func:`~.pennylane.qchem.hf_state` to obtain the state of a system with +# :math:`10` orbitals and :math:`5` electrons in both the occupation-number and parity bases. + +orbitals = 10 +electrons = 5 +state_number = qml.qchem.hf_state(electrons, orbitals) +state_parity = qml.qchem.hf_state(electrons, orbitals, basis="parity") + +print("State in occupation number basis:\n", state_number) +print("State in parity basis:\n", state_parity) + +############################################################################## +# Note that Parity mapping solves the non-locality problem of the parity information by storing +# the parity of orbital :math:`j` in qubit :math:`j` while the occupation information for the +# orbital is stored non-locally. In the parity basis, we cannot represent the creation or +# annihilation of a particle in orbital +# :math:`j` by simply operating with qubit creation or annihilation operators. In fact, the state of +# the :math:`(j − 1)`-th qubit provides information about the occupation state of qubit :math:`j` +# and whether we need to act with a creation or annihilation operator. Similarly, the creation or +# annihilation of a particle in qubit :math:`j` changes the parity of all qubits following it. +# As a result, the operator that is equivalent to creation and annihilation operators in +# the parity basis is a two-qubit operator acting on qubits :math:`j` and :math:`j − 1,` and +# an update operator which updates the parity of all qubits with index larger than j: +# +# .. math:: +# +# a_{j}^{\dagger} = \frac{1}{2}(Z_{j-1} \otimes X_j - iY_j) \otimes_{k>j} X_{k} +# +# and +# +# .. math:: +# +# a_{j} = \frac{1}{2}(Z_{j-1} \otimes X_j + iY_j) \otimes_{k>j} X_{k} +# +# Let's now look at an example where we map our fermionic operator :math:`a_{5}^{\dagger}` in a +# :math:`10` qubit system with +# Parity mapping using :func:`~.pennylane.fermi.parity_transform` in PennyLane. + +qubits = 10 +pauli_pr = qml.parity_transform(fermi_op, qubits, ps=True) +pauli_pr + +############################################################################## +# It is evident from this example that the Parity mapping doesn't improve upon the +# the Jordan-Wigner mapping as the long :math:`Z` strings are now replaced by :math:`X` +# strings. However, a very important advantage of using parity mapping is the ability to taper two +# qubits by leveraging symmetries of molecular Hamiltonians. You can find +# more information about this in our +# `qubit tapering `__ demo. +# Let's look at an example. + +generators = [qml.prod(*[qml.Z(i) for i in range(qubits-1)]), qml.Z(qubits-1)] +paulixops = qml.paulix_ops(generators, qubits) +paulix_sector = [1, 1] +taper_op = qml.taper(pauli_pr, generators, paulixops, paulix_sector) +qml.simplify(taper_op) + +############################################################################### +# Note that the tapered operator doesn't include qubit :math:`8` and :math:`9.` +# +# Bravyi-Kitaev Mapping +# --------------------- +# The Bravyi-Kitaev mapping aims to improve the linear scaling of the Jordan-Wigner and Parity +# mappings, in the number of qubits, +# by storing both the occupation number and the parity non-locally. In this formalism, even-labelled +# qubits store the occupation number of orbitals and odd-labelled qubits store parity +# through partial sums of occupation numbers. The corresponding creation and annihilation operators +# are defined `here `__. +# Let's use the :func:`~.pennylane.fermi.bravyi_kitaev` function to map our :math:`a_{5}^{\dagger}` +# operator. + +pauli_bk = qml.bravyi_kitaev(fermi_op, qubits, ps=True) +pauli_bk + +############################################################################## +# It is clear that the local nature of the transformation in the Bravyi-Kitaev mapping helps to +# improve the number of qubits. This advantage becomes even more clear if you +# work with a larger qubit +# system. We now use the Bravyi-Kitaev mapping to construct a qubit Hamiltonian and +# compute its ground state energy with the `VQE `__ +# method. +# +# Energy Calculation +# ------------------ +# To perform a VQE calculation for a desired Hamiltonian, we need an initial state typically set to +# a Hartree-Fock state, and a set of excitation operators to build an ansatz that allows us to +# obtain the ground state and then compute the expectation value of the Hamiltonian. It is important +# to note that the initial state and the excitation operators should be consistent with the +# mapping scheme used for obtaining the qubit Hamiltonian. Let's now build these three components +# for :math:`H_2` and compute its ground state energy. For this example, we will use the +# Bravyi-Kitaev transformation but similar calculations can be run with the other mappings. +# +# Molecular Hamiltonian +# ^^^^^^^^^^^^^^^^^^^^^ +# First, let's build the molecular Hamiltonian. This requires defining the atomic symbols and +# coordinates. + +from pennylane import qchem +from jax import numpy as jnp +import jax + +jax.config.update("jax_enable_x64", True) + +symbols = ['H', 'H'] +geometry = jnp.array([[0.0, 0.0, -0.69434785], + [0.0, 0.0, 0.69434785]]) + +mol = qchem.Molecule(symbols, geometry) + +############################################################################## +# We then use the :func:`~pennylane.qchem.fermionic_hamiltonian` function to build +# the fermionic Hamiltonian for our molecule. + +h_fermi = qchem.fermionic_hamiltonian(mol)() + +############################################################################## +# We now use :func:`~pennylane.fermi.bravyi_kitaev` to transform the fermionic Hamiltonian to its +# qubit representation. + +electrons = 2 +qubits = len(h_fermi.wires) +h_pauli = qml.bravyi_kitaev(h_fermi, qubits, tol=1e-16) + +############################################################################## +# Initial state +# ^^^^^^^^^^^^^ +# We now need the initial state that has the correct number of electrons. We use the Hartree-Fock +# state which can be obtained in a user-defined basis by using :func:`~.pennylane.qchem.hf_state` in +# PennyLane. For that, we need to specify the number of electrons, the number of orbitals and the +# desired mapping. + +hf_state = qchem.hf_state(electrons, qubits, basis="bravyi_kitaev") + +############################################################################## +# Excitation operators +# ^^^^^^^^^^^^^^^^^^^^ +# We now build our quantum circuit with the :class:`~.pennylane.UCCSD` ansatz. This ansatz is +# constructed with a set of single and double excitation operators. In PennyLane, we have +# :class:`~.pennylane.SingleExcitation` and :class:`~.pennylane.DoubleExcitation` operators which +# are very efficient, but they are only compatible with the Jordan-Wigner mapping. Here we construct +# the excitation operators manually. We start from the fermionic single and double excitation +# operators defined as [#Yordanov]_ +# +# .. math:: +# +# T_i^k(\theta) = \theta(a_k^{\dagger}a_i - a_i^{\dagger}a_k) +# +# and +# +# .. math:: +# +# T_{ij}^{kl}(\theta) = \theta(a_k^{\dagger}a_l^{\dagger}a_i a_j - +# a_i^{\dagger}a_j^{\dagger}a_k a_l), +# +# where :math:`\theta` is an adjustable parameter. We can easily construct these fermionic +# excitation operators in PennyLane and then map them to the qubit basis with +# :func:`~pennylane.fermi.bravyi_kitaev`, similar to the way we transformed the fermionic +# Hamiltonian. + +from pennylane.fermi import from_string + +singles, doubles = qchem.excitations(electrons, qubits) + +singles_fermi = [] +for ex in singles: + singles_fermi.append(from_string(f"{ex[1]}+ {ex[0]}-") + - from_string(f"{ex[0]}+ {ex[1]}-")) + +doubles_fermi = [] +for ex in doubles: + doubles_fermi.append(from_string(f"{ex[3]}+ {ex[2]}+ {ex[1]}- {ex[0]}-") + - from_string(f"{ex[0]}+ {ex[1]}+ {ex[2]}- {ex[3]}-")) + +############################################################################## +# The fermionic operators are now mapped to qubit operators. + +singles_pauli = [] +for op in singles_fermi: + singles_pauli.append(qml.bravyi_kitaev(op, qubits, ps=True)) + +doubles_pauli = [] +for op in doubles_fermi: + doubles_pauli.append(qml.bravyi_kitaev(op, qubits, ps=True)) + +############################################################################## +# Note that we need to exponentiate these operators to be able to use them in the circuit +# [#Yordanov]_. We also use a set of pre-defined parameters to construct the excitation gates. + +params = jnp.array([0.22347661, 0.0, 0.0]) + +dev = qml.device("default.qubit", wires=qubits) + +@qml.qnode(dev) +def circuit(params): + qml.BasisState(hf_state, wires=range(qubits)) + + for i, excitation in enumerate(doubles_pauli): + qml.exp((excitation * params[i] / 2).operation()), range(qubits) + + for j, excitation in enumerate(singles_pauli): + qml.exp((excitation * params[i + j + 1] / 2).operation()), range(qubits) + + return qml.expval(h_pauli) + +print('Energy =', circuit(params)) + +############################################################################## +# Using the above circuit, we produce the ground state energy of :math:`H_2` molecule. +# +# Conclusion +# --------------- +# In this demo, we learned about various mapping schemes available in PennyLane and how +# they can be used to convert fermionic operators to qubits operators. We also learned +# the pros and cons associated with each scheme. The Jordan-Wigner mapping provides an intuitive +# approach while parity mapping allows tapering qubits in molecular systems. However, these two +# methods usually give qubit operators with a long chain of Pauli operators, which makes them +# challenging to implement on quantum hardware. The Bravyi-Kitaev mapping, on the other hand, +# emphasizes locality and resource efficiency, making it an attractive option for certain +# applications. Through this demonstration, we recognize the importance of choosing an appropriate +# mapping scheme tailored to the specific problem at hand and the available quantum +# resources. Lastly, we showed how a user can employ these different mappings in ground state energy +# calculations through an example. We would like to encourage the interested readers to run +# calculations for different molecular systems and observe how the scaling in the number of qubits +# is influenced by the selected mapping techniques. +# +# References +# ---------- +# +# .. [#Tranter] +# +# A. Tranter, S. Sofia, *et al.*, "The Bravyi–Kitaev Transformation: +# Properties and Applications". `International Journal of Quantum Chemistry 115.19 (2015). +# `__ +# +# .. [#Yordanov] +# +# Y. S. Yordanov, *et al.*, "Efficient quantum circuits for quantum computational chemistry". +# `Physical Review A 102.6 (2020). +# `__ +# +# About the author +# ---------------- diff --git a/demonstrations_v2/tutorial_measurement_optimize/demo.py b/demonstrations_v2/tutorial_measurement_optimize/demo.py index f43645c3ba..198b7d2bfd 100644 --- a/demonstrations_v2/tutorial_measurement_optimize/demo.py +++ b/demonstrations_v2/tutorial_measurement_optimize/demo.py @@ -1,844 +1,844 @@ -r""" -Measurement optimization -======================== - -.. meta:: - :property="og:description": Optimize and reduce the number of measurements required to evaluate a variational algorithm cost function. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/grouping.png - -.. related:: - - tutorial_vqe A brief overview of VQE - tutorial_quantum_chemistry Building molecular Hamiltonians - tutorial_qaoa_intro Intro to QAOA - -*Author: Josh Izaac — Posted: 18 January 2021. Last updated: 29 August 2023.* - -The variational quantum eigensolver (VQE) is the OG variational quantum algorithm. Harnessing -near-term quantum hardware to solve for the electronic structure of molecules, VQE is *the* -algorithm that sparked the variational circuit craze of the last 5 years, and holds great -promise for showcasing a quantum advantage on near-term quantum hardware. It has also inspired -other quantum algorithms such as the :doc:`Quantum Approximate Optimization Algorithm (QAOA) -`. - -To scale VQE beyond the regime of classical computation, however, we need to solve for the -ground state of increasingly larger molecules. A consequence is that the number of -measurements we need to make on the quantum hardware also grows polynomially—a huge bottleneck, -especially when quantum hardware access is limited and expensive. - -To mitigate this 'measurement problem', a plethora of recent research dropped over the course of -2019 and 2020 [#yen2020]_ [#izmaylov2019]_ [#huggins2019]_ [#gokhale2020]_ [#verteletskyi2020]_ , -exploring potential strategies to minimize the number of measurements required. In fact, by grouping -commuting terms of the Hamiltonian, we can significantly reduce the number of -measurements needed—in some cases, reducing the number of measurements by up to 90%! - -.. figure:: /_static/demonstration_assets/measurement_optimize/grouping.png - :width: 90% - :align: center - -In this demonstration, we revisit the VQE algorithm, see first-hand how the required number of -measurements scales as molecule size increases, and finally use these measurement optimization -strategies to minimize the number of measurements we need to make. These techniques are valuable -beyond just VQE, allowing you to add measurement optimization to your toolkit of techniques to -perform variational algorithms more efficiently. - -Revisiting VQE --------------- - -The study of :doc:`variational quantum algorithms ` was spearheaded -by the introduction of the :doc:`variational quantum eigensolver ` (VQE) algorithm in -2014 [#peruzzo2014]_. While classical variational techniques have been known for decades to estimate -the ground state energy of a molecule, VQE allowed this variational technique to be applied using -quantum computers. Since then, the field of variational quantum algorithms has evolved -significantly, with larger and more complex models being proposed (such as -:doc:`quantum neural networks `, :doc:`QGANs `, and -:doc:`variational classifiers `). However, quantum chemistry -remains one of the flagship use-cases for variational quantum algorithms, and VQE the standard-bearer. - -Part of the appeal of VQE lies within its simplicity. A circuit ansatz :math:`U(\theta)` is chosen -(typically the Unitary Coupled-Cluster Singles and Doubles -(:func:`~pennylane.templates.subroutines.UCCSD`) ansatz), and the qubit representation of the -molecular Hamiltonian is computed: - -.. math:: H = \sum_i c_i h_i, - -where :math:`h_i` are the terms of the Hamiltonian written as a tensor product of Pauli operators or the identity -acting on wire :math:`n,` :math:`P_n \in \{I, \sigma_x, \sigma_y, \sigma_z\}:` - -.. math:: h_i = \bigotimes_{n=0}^{N-1} P_n. - -(The :math:`h_i` product of Pauli terms is often referred to as a 'Pauli word' in the literature.) The cost -function of the VQE is then simply the expectation value of this Hamiltonian on the state obtained -after running the variational quantum circuit: - -.. math:: \text{cost}(\theta) = \langle 0 | U(\theta)^\dagger H U(\theta) | 0 \rangle. - -By using a classical optimizer to *minimize* this quantity, we can estimate -the ground state energy of the Hamiltonian :math:`H:` - -.. math:: H U(\theta_{min}) |0\rangle = E_{min} U(\theta_{min}) |0\rangle. - -In practice, when we are using quantum hardware to compute these expectation values we expand out -the Hamiltonian as its summation, resulting in separate expectation values that need to be calculated for each term: - -.. math:: - - \text{cost}(\theta) = \langle 0 | U(\theta)^\dagger \left(\sum_i c_i h_i\right) U(\theta) | 0 \rangle - = \sum_i c_i \langle 0 | U(\theta)^\dagger h_i U(\theta) | 0 \rangle. - -.. note:: - - How do we compute the qubit representation of the molecular Hamiltonian? This is a more - complicated story that involves applying a self-consistent field method (such as Hartree-Fock), - and then performing a fermionic-to-qubit mapping such as the Jordan-Wigner or Bravyi-Kitaev - transformations. - - For more details on this process, check out the :doc:`/demos/tutorial_quantum_chemistry` - tutorial. - -The measurement problem ------------------------ - -For small molecules, the VQE algorithm scales and performs exceedingly well. For example, for the -Hydrogen molecule :math:`\text{H}_2,` the final Hamiltonian in its qubit representation -has 15 terms that need to be measured. Let's obtain the Hamiltonian from -`PennyLane's dataset library `__ -to verify the number of terms. In this tutorial, we use the :func:`~.pennylane.data.load` -function to download the dataset of the molecule. - -""" - -import functools -import warnings -import jax -from jax import numpy as jnp -import pennylane as qml - -jax.config.update("jax_enable_x64", True) - -dataset = qml.data.load("qchem", molname="H2", bondlength=0.7)[0] -H, num_qubits = dataset.hamiltonian, len(dataset.hamiltonian.wires) - -print("Required number of qubits:", num_qubits) -print(H) - -############################################################################### -# Here, we can see that the Hamiltonian involves 15 terms, so we expect to compute 15 expectation values -# on hardware. Let's generate the cost function to check this. - -# Create a 4 qubit simulator -dev = qml.device("default.qubit", shots=1000, seed=904932) - -# number of electrons -electrons = 2 - -# Define the Hartree-Fock initial state for our variational circuit -initial_state = qml.qchem.hf_state(electrons, num_qubits) - -# Construct the UCCSD ansatz -singles, doubles = qml.qchem.excitations(electrons, num_qubits) -s_wires, d_wires = qml.qchem.excitations_to_wires(singles, doubles) -ansatz = functools.partial( - qml.UCCSD, init_state=initial_state, s_wires=s_wires, d_wires=d_wires -) - -# generate the cost function -@qml.qnode(dev, interface="jax") -def cost_circuit(params): - ansatz(params, wires=range(num_qubits)) - return qml.expval(H) - -############################################################################## -# If we evaluate this cost function, we can see that it corresponds to 15 different -# executions under the hood—one per expectation value: - -from jax import random as random -key, scale = random.PRNGKey(0), jnp.pi -params = random.normal(key, shape=(len(singles) + len(doubles),)) * scale -with qml.Tracker(dev) as tracker: # track the number of executions - print("Cost function value:", cost_circuit(params)) - -print("Number of quantum evaluations:", tracker.totals['executions']) - -############################################################################## -# How about a larger molecule? Let's try the -# `water molecule `__: - -dataset = qml.data.load('qchem', molname="H2O")[0] -H, num_qubits = dataset.hamiltonian, len(dataset.hamiltonian.wires) - -print("Required number of qubits:", num_qubits) -print("Number of Hamiltonian terms/required measurements:", len(H.terms()[0])) - -print("\n", H) - - -############################################################################## -# Simply going from two atoms in :math:`\text{H}_2` to three in :math:`\text{H}_2 \text{O}` -# resulted in over triple the number of qubits required and 1086 measurements that must be made! -# -# We can see that as the size of our molecule increases, we run into a problem: larger molecules -# result in Hamiltonians that not only require a larger number of qubits :math:`N` in their -# representation, but the number of terms in the Hamiltonian scales like -# :math:`\mathcal{O}(N^4)!` 😱😱😱 -# -# We can mitigate this somewhat by choosing smaller `basis sets -# `__ to represent the electronic structure -# wavefunction, however this would be done at the cost of solution accuracy, and doesn't reduce the number of -# measurements significantly enough to allow us to scale to classically intractable problems. -# -# .. figure:: /_static/demonstration_assets/measurement_optimize/n4.png -# :width: 70% -# :align: center -# -# The number of qubit Hamiltonian terms required to represent various molecules in the specified -# basis sets (adapted from `Minimizing State Preparations for VQE by Gokhale et al. -# `__) - - -############################################################################## -# Simultaneously measuring observables -# ------------------------------------ -# -# One of the assumptions we made above was that every term in the Hamiltonian must be measured independently. -# However, this might not be the case. From the `Heisenberg uncertainty relationship -# `__ for two -# observables :math:`\hat{A}` and :math:`\hat{B},` we know that -# -# .. math:: \sigma_A^2 \sigma_B^2 \geq \frac{1}{2}\left|\left\langle [\hat{A}, \hat{B}] \right\rangle\right|, -# -# where :math:`\sigma^2_A` and :math:`\sigma^2_B` are the variances of measuring the expectation value of the -# associated observables, and -# -# .. math:: [\hat{A}, \hat{B}] = \hat{A}\hat{B}-\hat{B}\hat{A} -# -# is the commutator. Therefore, -# -# - If the two observables :math:`\hat{A}` and :math:`\hat{B}` do not commute (:math:`[\hat{A}, -# \hat{B}] \neq 0`), then :math:`\sigma_A^2 -# \sigma_B^2 > 0` and we cannot simultaneously measure the expectation values of the two -# observables. -# -# .. -# -# - If :math:`\hat{A}` and :math:`\hat{B}` **do** commute (:math:`[\hat{A}, -# \hat{B}] = 0`), then :math:`\sigma_A^2 -# \sigma_B^2 \geq 0` and there exists a measurement basis where we can **simultaneously measure** the -# expectation value of both observables on the same state. -# -# To explore why commutativity and simultaneous measurement are related, let's assume that there -# is a complete, orthonormal eigenbasis :math:`|\phi_n\rangle` that *simultaneously -# diagonalizes* both :math:`\hat{A}` and :math:`\hat{B}:` -# -# .. math:: -# -# ① ~~ \hat{A} |\phi_n\rangle &= \lambda_{A,n} |\phi_n\rangle,\\ -# ② ~~ \hat{B} |\phi_n\rangle &= \lambda_{B,n} |\phi_n\rangle. -# -# where :math:`\lambda_{A,n}` and :math:`\lambda_{B,n}` are the corresponding eigenvalues. -# If we pre-multiply the first equation by :math:`\hat{B},` and the second by :math:`\hat{A}` -# (both denoted in blue): -# -# .. math:: -# -# \color{blue}{\hat{B}}\hat{A} |\phi_n\rangle &= \lambda_{A,n} \color{blue}{\hat{B}} -# |\phi_n\rangle = \lambda_{A,n} \color{blue}{\lambda_{B,n}} |\phi_n\rangle,\\ -# \color{blue}{\hat{A}}\hat{B} |\phi_n\rangle &= \lambda_{B,n} \color{blue}{\hat{A}} -# |\phi_n\rangle = \lambda_{A,n} \color{blue}{\lambda_{B,n}} |\phi_n\rangle. -# -# We can see that assuming a simultaneous eigenbasis requires that -# :math:`\hat{A}\hat{B}|\phi_n\rangle = \hat{B}\hat{A}|\phi_n\rangle.` Or, rearranging, -# -# .. math:: (\hat{A}\hat{B} - \hat{B}\hat{A}) |\phi_n\rangle = [\hat{A}, \hat{B}]|\phi_n\rangle = 0. -# -# Our assumption that :math:`|\phi_n\rangle` simultaneously diagonalizes both :math:`\hat{A}` and -# :math:`\hat{B}` only holds true if the two observables commute. -# -# So far, this seems awfully theoretical. What does this mean in practice? -# -# In the realm of variational circuits, we typically want to compute expectation values of an -# observable on a given state :math:`|\psi\rangle.` If we have two commuting observables, we now know that -# they share a simultaneous eigenbasis: -# -# .. math:: -# -# \hat{A} &= \sum_n \lambda_{A, n} |\phi_n\rangle\langle \phi_n|,\\ -# \hat{B} &= \sum_n \lambda_{B, n} |\phi_n\rangle\langle \phi_n|. -# -# Substituting this into the expression for the expectation values: -# -# .. math:: -# -# \langle\hat{A}\rangle &= \langle \psi | \hat{A} | \psi \rangle = \langle \psi | \left( \sum_n -# \lambda_{A, n} |\phi_n\rangle\langle \phi_n| \right) | \psi \rangle = \sum_n \lambda_{A,n} -# |\langle \phi_n|\psi\rangle|^2,\\ -# \langle\hat{B}\rangle &= \langle \psi | \hat{B} | \psi \rangle = \langle \psi | \left( \sum_n -# \lambda_{B, n} |\phi_n\rangle\langle \phi_n| \right) | \psi \rangle = \sum_n \lambda_{B,n} -# |\langle \phi_n|\psi\rangle|^2. -# -# So, assuming we know the eigenvalues of the commuting observables in advance, if we perform a -# measurement in their shared eigenbasis (the :math:`|\phi_n\rangle`), we only need to perform a **single measurement** of the -# probabilities :math:`|\langle \phi_n|\psi\rangle|^2` in order to recover both expectation values! 😍 -# -# Fantastic! But, can we use this to reduce the number of measurements we need to perform in the VQE algorithm? -# To do so, we must find the answer to two questions: -# -# 1. How do we determine which terms of the cost Hamiltonian commute? -# -# 2. How do we rotate the circuit into the shared eigenbasis prior to measurement? -# -# The answers to these questions aren't necessarily easy nor straightforward. Thankfully, there are -# some recent techniques we can harness to address both. - -############################################################################## -# Qubit-wise commuting Pauli terms -# -------------------------------- -# -# Back when we summarized the VQE algorithm, we saw that each term of the Hamiltonian is generally represented -# as a tensor product of Pauli operators: -# -# .. math:: h_i = \bigotimes_{n=0}^{N-1} P_n. -# -# Luckily, this tensor product structure allows us to take a bit of a shortcut. Rather than consider -# **full commutativity**, we can consider a more strict condition known as **qubit-wise -# commutativity** (QWC). -# -# To start with, let's consider single-qubit Pauli operators and the identity. We know that the Pauli operators -# commute with themselves as well as the identity, but they do *not* commute with -# each other: -# -# .. math:: -# -# [\sigma_i, I] = 0, ~~~ [\sigma_i, \sigma_i] = 0, ~~~ [\sigma_i, \sigma_j] = c \sigma_k \delta_{ij}. -# -# Now consider two tensor products of Pauli terms, for example :math:`X\otimes Y \otimes I` and -# :math:`X\otimes I \otimes Z.` We say that these two terms are qubit-wise commuting, since, if -# we compare each subsystem in the tensor product, we see that every one commutes: -# -# .. math:: -# -# \begin{array}{ | *1c | *1c | *1c | *1c | *1c |} -# X &\otimes &Y &\otimes &I\\ -# X &\otimes &I &\otimes &Z -# \end{array} ~~~~ \Rightarrow ~~~~ [X, X] = 0, ~~~ [Y, I] = 0, ~~~ [I, Z] = 0. -# -# As a consequence, both terms must commute: -# -# .. math:: [X\otimes Y \otimes I, X\otimes I \otimes Z] = 0. -# -# .. important:: -# -# Qubit-wise commutativity is a **sufficient** but not **necessary** condition -# for full commutativity. For example, the two Pauli terms :math:`Y\otimes Y` and -# :math:`X\otimes X` are not qubit-wise commuting, but do commute (have a go verifying this!). -# -# Once we have identified a qubit-wise commuting pair of Pauli terms, it is also straightforward to -# find the gates to rotate the circuit into the shared eigenbasis. To do so, we simply rotate -# each qubit one-by-one depending on the Pauli operator we are measuring on that wire: -# -# .. raw:: html -# -# -#
-# -# .. rst-class:: docstable -# -# +------------------+-------------------------------+ -# | Observable | Rotation gate | -# +==================+===============================+ -# | :math:`X` | :math:`RY(-\pi/2) = H` | -# +------------------+-------------------------------+ -# | :math:`Y` | :math:`RX(\pi/2)=HS^{-1}=HSZ` | -# +------------------+-------------------------------+ -# | :math:`Z` | :math:`I` | -# +------------------+-------------------------------+ -# | :math:`I` | :math:`I` | -# +------------------+-------------------------------+ -# -# .. raw:: html -# -#
-# -# Therefore, in this particular example: -# -# * Wire 0: we are measuring both terms in the :math:`X` basis, apply the Hadamard gate -# * Wire 1: we are measuring both terms in the :math:`Y` basis, apply a :math:`RX(\pi/2)` gate -# * Wire 2: we are measuring both terms in the :math:`Z` basis (the computational basis), no gate needs to be applied. -# -# Let's use PennyLane to verify this. - - -obs = [ - qml.PauliX(0) @ qml.PauliY(1), - qml.PauliX(0) @ qml.PauliZ(2) -] - - -############################################################################## -# First, let's naively use two separate circuit evaluations to measure -# the two QWC terms. - - -dev = qml.device("default.qubit", wires=3) - -@qml.qnode(dev, interface="jax") -def circuit1(weights): - qml.StronglyEntanglingLayers(weights, wires=range(3)) - return qml.expval(obs[0]) - - -@qml.qnode(dev, interface="jax") -def circuit2(weights): - qml.StronglyEntanglingLayers(weights, wires=range(3)) - return qml.expval(obs[1]) - -param_shape = qml.templates.StronglyEntanglingLayers.shape(n_layers=3, n_wires=3) -key, scale = random.PRNGKey(192933), 0.1 -weights = scale * random.normal(key, shape=param_shape) - -print("Expectation value of XYI = ", circuit1(weights)) -print("Expectation value of XIZ = ", circuit2(weights)) - -############################################################################## -# Now, let's use our QWC approach to reduce this down to a *single* measurement -# of the probabilities in the shared eigenbasis of both QWC observables: - -@qml.qnode(dev, interface="jax") -def circuit_qwc(weights): - qml.StronglyEntanglingLayers(weights, wires=range(3)) - - # rotate wire 0 into the shared eigenbasis - qml.RY(-jnp.pi / 2, wires=0) - - # rotate wire 1 into the shared eigenbasis - qml.RX(jnp.pi / 2, wires=1) - - # wire 2 does not require a rotation - - # measure probabilities in the computational basis - return qml.probs(wires=range(3)) - - -rotated_probs = circuit_qwc(weights) -print(rotated_probs) - -############################################################################## -# We're not quite there yet; we have only calculated the probabilities of the variational circuit -# rotated into the shared eigenbasis—the :math:`|\langle \phi_n |\psi\rangle|^2.` To recover the -# *expectation values* of the two QWC observables from the probabilities, recall that we need one -# final piece of information: their eigenvalues :math:`\lambda_{A, n}` and :math:`\lambda_{B, n}.` -# -# We know that the single-qubit Pauli operators each have eigenvalues :math:`(1, -1),` while the identity -# operator has eigenvalues :math:`(1, 1).` We can make use of ``np.kron`` to quickly -# generate the eigenvalues of the full Pauli terms, making sure that the order -# of the eigenvalues in the Kronecker product corresponds to the tensor product. - -eigenvalues_XYI = jnp.kron(jnp.kron(jnp.array([1, -1]), jnp.array([1, -1])), jnp.array([1, 1])) -eigenvalues_XIZ = jnp.kron(jnp.kron(jnp.array([1, -1]), jnp.array([1, 1])), jnp.array([1, -1])) - -# Taking the linear combination of the eigenvalues and the probabilities -print("Expectation value of XYI = ", jnp.dot(eigenvalues_XYI, rotated_probs)) -print("Expectation value of XIZ = ", jnp.dot(eigenvalues_XIZ, rotated_probs)) - - -############################################################################## -# Compare this to the result when we used two circuit evaluations. We have successfully used a -# single circuit evaluation to recover both expectation values! -# -# Luckily, PennyLane automatically performs this QWC grouping under the hood. We simply -# return the two QWC Pauli terms from the QNode: - -@qml.qnode(dev, interface="jax") -def circuit(weights): - qml.StronglyEntanglingLayers(weights, wires=range(3)) - return [ - qml.expval(qml.PauliX(0) @ qml.PauliY(1)), - qml.expval(qml.PauliX(0) @ qml.PauliZ(2)) - ] - - -print(circuit(weights)) - - -############################################################################## -# Behind the scenes, PennyLane is making use of our built-in -# :mod:`qml.pauli ` module, which contains functions for diagonalizing QWC -# terms: - -rotations, new_obs = qml.pauli.diagonalize_qwc_pauli_words(obs) - -print(rotations) -print(new_obs) - - -############################################################################## -# Here, the first line corresponds to the basis rotations that were discussed above, written in -# terms of ``RX`` and ``RY`` rotations. Check out the :mod:`qml.pauli ` -# documentation for more details on its provided functionality and how it works. -# -# Given a Hamiltonian containing a large number of Pauli terms, -# there is a high likelihood of there being a significant number of terms that qubit-wise commute. Can -# we somehow partition the terms into **fewest** number of QWC groups to minimize the number of measurements -# we need to take? - -############################################################################## -# Grouping QWC terms -# ------------------ -# -# A nice example is provided in [#verteletskyi2020]_ showing how we might tackle this. Say we have -# the following Hamiltonian defined over four qubits: -# -# .. math:: H = Z_0 + Z_0 Z_1 + Z_0 Z_1 Z_2 + Z_0 Z_1 Z_2 Z_3 + X_2 X_3 + Y_0 X_2 X_3 + Y_0 Y_1 X_2 X_3, -# -# where we are using the shorthand :math:`P_0 P_2 = P\otimes I \otimes P \otimes I` for brevity. -# If we go through and work out which Pauli terms are qubit-wise commuting, we can represent -# this in a neat way using a graph: -# -# .. figure:: /_static/demonstration_assets/measurement_optimize/graph1.png -# :width: 70% -# :align: center -# -# In the above graph, every node represents an individual Pauli term of the Hamiltonian, with -# edges connecting terms that are qubit-wise commuting. Groups of qubit-wise commuting terms are -# represented as **complete subgraphs**. Straight away, we can make an observation: -# there is no unique solution for partitioning the Hamiltonian into groups of qubit-wise commuting -# terms! In fact, there are several solutions: -# -# .. figure:: /_static/demonstration_assets/measurement_optimize/graph2.png -# :width: 90% -# :align: center -# -# Of course, of the potential solutions above, there is one that is more optimal than the others --- -# on the bottom left, we have partitioned the graph into *two* complete subgraphs, as opposed to the -# other solutions that require three complete subgraphs. If we were to go with this solution, -# we would be able to measure the expectation value of the Hamiltonian using two circuit evaluations. -# -# This problem—finding the minimum number of complete subgraphs of a graph—is actually quite well -# known in graph theory, where it is referred to as the `minimum clique cover problem -# `__ (with 'clique' being another term for a complete subgraph). -# -# Unfortunately, that's where our good fortune ends—the minimum clique cover problem is known to -# be `NP-hard `__, meaning there is no known (classical) -# solution to finding the optimum/minimum clique cover in polynomial time. -# -# Thankfully, there is a silver lining: we know of polynomial-time algorithms for finding -# *approximate* solutions to the minimum clique cover problem. These heuristic approaches, while -# not guaranteed to find the optimum solution, scale quadratically with the number of nodes in the -# graph/terms in the Hamiltonian [#yen2020]_, so work reasonably well in practice. -# -# Many of these heuristic approaches have roots in another graph problem known as `graph -# colouring `__; the assignment of colours to -# the graph's vertices such that no adjacent vertices have the same colour. How is this related -# to the minimum clique cover problem, though? If we take our QWC graph above, and generate the -# `complement graph `__ by drawing edges -# between all *non*-adjacent nodes, -# -# .. figure:: /_static/demonstration_assets/measurement_optimize/graph3.png -# :width: 100% -# :align: center -# -# we see that solving the minimum clique cover problem on the QWC graph is equivalent to solving the -# graph colouring problem on the complement graph using the minimum possible number of colours. -# While there are various different heuristic algorithms, a common one is `greedy colouring -# `__; in fact, the open-source graph -# package `NetworkX even provides a function for greedy colouring -# `__, -# ``nx.greedy_color``. -# -# Let's give this a go, using NetworkX to solve the minimum clique problem for observable grouping. -# First, we'll need to generate the QWC graph (with each node corresponding to a Hamiltonian -# term, and edges indicating two terms that are QWC). - -import networkx as nx -from matplotlib import pyplot as plt - -terms = [ - qml.PauliZ(0), - qml.PauliZ(0) @ qml.PauliZ(1), - qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2), - qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3), - qml.PauliX(2) @ qml.PauliX(3), - qml.PauliY(0) @ qml.PauliX(2) @ qml.PauliX(3), - qml.PauliY(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliX(3) -] - -def format_pauli_word(term): - """Convenience function that nicely formats a PennyLane - tensor observable as a Pauli word""" - if isinstance(term, qml.ops.Prod): - return " ".join([format_pauli_word(t) for t in term]) - - return f"{term.name[-1]}{term.wires.tolist()[0]}" - -G = nx.Graph() - -with warnings.catch_warnings(): - # Muting irrelevant warnings - warnings.filterwarnings( - "ignore", - message="The behaviour of operator ", - category=UserWarning, - ) - - # add the terms to the graph - G.add_nodes_from(terms) - - # add QWC edges - G.add_edges_from([ - [terms[0], terms[1]], # Z0 <--> Z0 Z1 - [terms[0], terms[2]], # Z0 <--> Z0 Z1 Z2 - [terms[0], terms[3]], # Z0 <--> Z0 Z1 Z2 Z3 - [terms[1], terms[2]], # Z0 Z1 <--> Z0 Z1 Z2 - [terms[2], terms[3]], # Z0 Z1 Z2 <--> Z0 Z1 Z2 Z3 - [terms[1], terms[3]], # Z0 Z1 <--> Z0 Z1 Z2 Z3 - [terms[0], terms[4]], # Z0 <--> X2 X3 - [terms[1], terms[4]], # Z0 Z1 <--> X2 X3 - [terms[4], terms[5]], # X2 X3 <--> Y0 X2 X3 - [terms[4], terms[6]], # X2 X3 <--> Y0 Y1 X2 X3 - [terms[5], terms[6]], # Y0 X2 X3 <--> Y0 Y1 X2 X3 - ]) - - plt.margins(x=0.1) - coords = nx.spring_layout(G, seed=1) - nx.draw( - G, - coords, - labels={node: format_pauli_word(node) for node in terms}, - with_labels=True, - node_size=500, - font_size=8, - node_color="#9eded1", - edge_color="#c1c1c1", - ) - - ############################################################################## - # We can now generate the complement graph (compare this to our handdrawn - # version above!): - - C = nx.complement(G) - coords = nx.spring_layout(C, seed=1) - - nx.draw( - C, - coords, - labels={node: format_pauli_word(node) for node in terms}, - with_labels=True, - node_size=500, - font_size=8, - node_color="#9eded1", - edge_color="#c1c1c1" - ) - - ############################################################################## - # Now that we have the complement graph, we can perform a greedy coloring to - # determine the minimum number of QWC groups: - - groups = nx.coloring.greedy_color(C, strategy="largest_first") - - # plot the complement graph with the greedy colouring - nx.draw( - C, - coords, - labels={node: format_pauli_word(node) for node in terms}, - with_labels=True, - node_size=500, - font_size=8, - node_color=[("#9eded1", "#aad4f0")[groups[node]] for node in C], - edge_color="#c1c1c1" - ) - - -num_groups = len(set(groups.values())) -print("Minimum number of QWC groupings found:", num_groups) - - -for i in range(num_groups): - print(f"\nGroup {i}:") - - for term, group_id in groups.items(): - if group_id == i: - print(format_pauli_word(term)) - -############################################################################## -# Putting it all together -# ----------------------- -# -# So, we now have a strategy for minimizing the number of measurements we need to perform -# for our VQE problem: -# -# 1. Determine which terms of the Hamiltonian are qubit-wise commuting, and use -# this to construct a graph representing the QWC relationship. -# -# 2. Construct the complement QWC graph. -# -# 3. Use a graph colouring heuristic algorithm to determine a graph colouring for the complement graph -# with a minimum number of colours. Each coloured vertex set corresponds to a -# qubit-wise commuting group of Hamiltonian terms. -# -# 4. Generate and evaluate the circuit ansatz (with additional rotations) per -# QWC grouping, extracting probability distributions. -# -# 5. Finally, post-process the probability distributions with the observable eigenvalues -# to recover the Hamiltonian expectation value. -# -# Luckily, the PennyLane ``pauli`` module makes this relatively easy. Let's walk through -# the entire process using the provided grouping functions. -# -# Steps 1-3 (finding and grouping QWC terms in the Hamiltonian) can be done via the -# :func:`qml.pauli.group_observables ` function: - -obs_groupings = qml.pauli.group_observables(terms, grouping_type='qwc', method='rlf') - - -############################################################################## -# The ``grouping_type`` argument allows us to choose how the commuting terms -# are determined (more on that later!) whereas ``method`` determines the colouring -# heuristic (in this case, ``"rlf"`` refers to Recursive Largest First, a variant of largest first colouring heuristic). -# -# If we want to see what the required rotations and measurements are, we can use the -# :func:`qml.pauli.diagonalize_qwc_groupings ` -# function: - -rotations, measurements = qml.pauli.diagonalize_qwc_groupings(obs_groupings) - -############################################################################## -# However, this isn't strictly necessary—recall previously that the QNode -# has the capability to *automatically* measure qubit-wise commuting observables! - -dev = qml.device("lightning.qubit", wires=4) - -@qml.qnode(dev, interface="jax") -def circuit(weights, group=None, **kwargs): - qml.StronglyEntanglingLayers(weights, wires=range(4)) - return [qml.expval(o) for o in group] - -param_shape = qml.templates.StronglyEntanglingLayers.shape(n_layers=3, n_wires=4) -key = random.PRNGKey(1) -weights = random.normal(key, shape=param_shape) * 0.1 -result = [jnp.array(circuit(weights, group=g)) for g in obs_groupings] - -print("Term expectation values:") -for group, expvals in enumerate(result): - print(f"Group {group} expectation values:", expvals) - -# Since all the coefficients of the Hamiltonian are unity, -# we can simply sum the expectation values. -print(" = ", jnp.sum(jnp.hstack(result))) - - -############################################################################## -# Finally, we don't need to go through this process manually every time; if our cost function can be -# written in the form of an expectation value of a Hamiltonian (as is the case for most VQE and QAOA -# problems), we can use the option ``grouping_type="qwc"`` in :class:`~.pennylane.Hamiltonian` to -# automatically optimize the measurements. - -H = qml.Hamiltonian(coeffs=jnp.ones(len(terms)), observables=terms, grouping_type="qwc") -_, H_ops = H.terms() -@qml.qnode(dev, interface="jax") -def cost_fn(weights): - qml.StronglyEntanglingLayers(weights, wires=range(4)) - return qml.expval(H) -print(cost_fn(weights)) - -############################################################################## -# Beyond VQE -# ---------- -# -# Wait, hang on. We dove so deeply into measurement grouping and optimization, we forgot to check -# how this affects the number of measurements required to perform the VQE on :math:`\text{H}_2 \text{O}!` -# Let's use our new-found knowledge to see what happens. - -dataset = qml.data.load('qchem', molname="H2O")[0] -H, num_qubits = dataset.hamiltonian, len(dataset.hamiltonian.wires) -print("Number of Hamiltonian terms/required measurements:", len(H_ops)) - -# grouping -groups = qml.pauli.group_observables(H_ops, grouping_type='qwc', method='rlf') -print("Number of required measurements after optimization:", len(groups)) - -############################################################################## -# We went from 1086 required measurements/circuit evaluations to 320 (just over *one thousand* -# down to *three hundred* 😱😱😱). -# -# As impressive as this is, however, this is just the beginning of the optimization. -# -# While finding qubit-wise commutating terms is relatively straightforward, with a little -# extra computation we can push this number down even further. Recent work has explored -# the savings that can be made by considering *full* commutativity [#yen2020]_, unitary -# partitioning [#izmaylov2019]_, and Fermionic basis rotation grouping [#huggins2019]_. -# Work has also been performed to reduce the classical overhead associated with measurement -# optimization, allowing the classical measurement grouping to be performed in linear time -# [#gokhale2020]_. For example, recall that qubit-wise commutativity is only a subset of -# full commutativity; if we consider full commutativity instead, we can further reduce the -# number of groups required. -# -# Finally, it is worth pointing out that, as the field of variational quantum algorithms grows, this -# problem of measurement optimization no longer just applies to the VQE algorithm (the algorithm it -# was born from). Instead, there are a multitude of algorithms that could benefit from these -# measurement optimization techniques (QAOA being a prime example). -# -# So the next time you are working on a variational quantum algorithm and the number -# of measurements required begins to explode—stop, take a deep breath 😤, and consider grouping -# and optimizing your measurements. -# -# .. note:: -# -# Qubit-wise commuting group information for a wide variety of molecules has been -# pre-computed, and is available for download in -# in the `PennyLane Datasets library `__. - -############################################################################## -# References -# ---------- -# -# .. [#peruzzo2014] -# -# Alberto Peruzzo, Jarrod McClean *et al.*, "A variational eigenvalue solver on a photonic -# quantum processor". `Nature Communications 5, 4213 (2014). -# `__ -# -# .. [#yen2020] -# -# Tzu-Ching Yen, Vladyslav Verteletskyi, and Artur F. Izmaylov. "Measuring all compatible -# operators in one series of single-qubit measurements using unitary transformations." `Journal of -# Chemical Theory and Computation 16.4 (2020): 2400-2409. -# `__ -# -# .. [#izmaylov2019] -# -# Artur F. Izmaylov, *et al.* "Unitary partitioning approach to the measurement problem in the -# variational quantum eigensolver method." `Journal of Chemical Theory and Computation 16.1 (2019): -# 190-195. `__ -# -# .. [#huggins2019] -# -# William J. Huggins, *et al.* "Efficient and noise resilient measurements for quantum chemistry -# on near-term quantum computers." `arXiv preprint arXiv:1907.13117 (2019). -# `__ -# -# .. [#gokhale2020] -# -# Pranav Gokhale, *et al.* "Minimizing state preparations in variational quantum eigensolver by -# partitioning into commuting families." `arXiv preprint arXiv:1907.13623 (2019). -# `__ -# -# .. [#verteletskyi2020] -# -# Vladyslav Verteletskyi, Tzu-Ching Yen, and Artur F. Izmaylov. "Measurement optimization in the -# variational quantum eigensolver using a minimum clique cover." `The Journal of Chemical Physics -# 152.12 (2020): 124114. `__ -# -# -# About the author -# ---------------- -# +r""" +Measurement optimization +======================== + +.. meta:: + :property="og:description": Optimize and reduce the number of measurements required to evaluate a variational algorithm cost function. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/grouping.png + +.. related:: + + tutorial_vqe A brief overview of VQE + tutorial_quantum_chemistry Building molecular Hamiltonians + tutorial_qaoa_intro Intro to QAOA + +*Author: Josh Izaac — Posted: 18 January 2021. Last updated: 29 August 2023.* + +The variational quantum eigensolver (VQE) is the OG variational quantum algorithm. Harnessing +near-term quantum hardware to solve for the electronic structure of molecules, VQE is *the* +algorithm that sparked the variational circuit craze of the last 5 years, and holds great +promise for showcasing a quantum advantage on near-term quantum hardware. It has also inspired +other quantum algorithms such as the :doc:`Quantum Approximate Optimization Algorithm (QAOA) +`. + +To scale VQE beyond the regime of classical computation, however, we need to solve for the +ground state of increasingly larger molecules. A consequence is that the number of +measurements we need to make on the quantum hardware also grows polynomially—a huge bottleneck, +especially when quantum hardware access is limited and expensive. + +To mitigate this 'measurement problem', a plethora of recent research dropped over the course of +2019 and 2020 [#yen2020]_ [#izmaylov2019]_ [#huggins2019]_ [#gokhale2020]_ [#verteletskyi2020]_ , +exploring potential strategies to minimize the number of measurements required. In fact, by grouping +commuting terms of the Hamiltonian, we can significantly reduce the number of +measurements needed—in some cases, reducing the number of measurements by up to 90%! + +.. figure:: /_static/demonstration_assets/measurement_optimize/grouping.png + :width: 90% + :align: center + +In this demonstration, we revisit the VQE algorithm, see first-hand how the required number of +measurements scales as molecule size increases, and finally use these measurement optimization +strategies to minimize the number of measurements we need to make. These techniques are valuable +beyond just VQE, allowing you to add measurement optimization to your toolkit of techniques to +perform variational algorithms more efficiently. + +Revisiting VQE +-------------- + +The study of :doc:`variational quantum algorithms ` was spearheaded +by the introduction of the :doc:`variational quantum eigensolver ` (VQE) algorithm in +2014 [#peruzzo2014]_. While classical variational techniques have been known for decades to estimate +the ground state energy of a molecule, VQE allowed this variational technique to be applied using +quantum computers. Since then, the field of variational quantum algorithms has evolved +significantly, with larger and more complex models being proposed (such as +:doc:`quantum neural networks `, :doc:`QGANs `, and +:doc:`variational classifiers `). However, quantum chemistry +remains one of the flagship use-cases for variational quantum algorithms, and VQE the standard-bearer. + +Part of the appeal of VQE lies within its simplicity. A circuit ansatz :math:`U(\theta)` is chosen +(typically the Unitary Coupled-Cluster Singles and Doubles +(:func:`~pennylane.templates.subroutines.UCCSD`) ansatz), and the qubit representation of the +molecular Hamiltonian is computed: + +.. math:: H = \sum_i c_i h_i, + +where :math:`h_i` are the terms of the Hamiltonian written as a tensor product of Pauli operators or the identity +acting on wire :math:`n,` :math:`P_n \in \{I, \sigma_x, \sigma_y, \sigma_z\}:` + +.. math:: h_i = \bigotimes_{n=0}^{N-1} P_n. + +(The :math:`h_i` product of Pauli terms is often referred to as a 'Pauli word' in the literature.) The cost +function of the VQE is then simply the expectation value of this Hamiltonian on the state obtained +after running the variational quantum circuit: + +.. math:: \text{cost}(\theta) = \langle 0 | U(\theta)^\dagger H U(\theta) | 0 \rangle. + +By using a classical optimizer to *minimize* this quantity, we can estimate +the ground state energy of the Hamiltonian :math:`H:` + +.. math:: H U(\theta_{min}) |0\rangle = E_{min} U(\theta_{min}) |0\rangle. + +In practice, when we are using quantum hardware to compute these expectation values we expand out +the Hamiltonian as its summation, resulting in separate expectation values that need to be calculated for each term: + +.. math:: + + \text{cost}(\theta) = \langle 0 | U(\theta)^\dagger \left(\sum_i c_i h_i\right) U(\theta) | 0 \rangle + = \sum_i c_i \langle 0 | U(\theta)^\dagger h_i U(\theta) | 0 \rangle. + +.. note:: + + How do we compute the qubit representation of the molecular Hamiltonian? This is a more + complicated story that involves applying a self-consistent field method (such as Hartree-Fock), + and then performing a fermionic-to-qubit mapping such as the Jordan-Wigner or Bravyi-Kitaev + transformations. + + For more details on this process, check out the :doc:`/demos/tutorial_quantum_chemistry` + tutorial. + +The measurement problem +----------------------- + +For small molecules, the VQE algorithm scales and performs exceedingly well. For example, for the +Hydrogen molecule :math:`\text{H}_2,` the final Hamiltonian in its qubit representation +has 15 terms that need to be measured. Let's obtain the Hamiltonian from +`PennyLane's dataset library `__ +to verify the number of terms. In this tutorial, we use the :func:`~.pennylane.data.load` +function to download the dataset of the molecule. + +""" + +import functools +import warnings +import jax +from jax import numpy as jnp +import pennylane as qml + +jax.config.update("jax_enable_x64", True) + +dataset = qml.data.load("qchem", molname="H2", bondlength=0.7)[0] +H, num_qubits = dataset.hamiltonian, len(dataset.hamiltonian.wires) + +print("Required number of qubits:", num_qubits) +print(H) + +############################################################################### +# Here, we can see that the Hamiltonian involves 15 terms, so we expect to compute 15 expectation values +# on hardware. Let's generate the cost function to check this. + +# Create a 4 qubit simulator +dev = qml.device("default.qubit", shots=1000, seed=904932) + +# number of electrons +electrons = 2 + +# Define the Hartree-Fock initial state for our variational circuit +initial_state = qml.qchem.hf_state(electrons, num_qubits) + +# Construct the UCCSD ansatz +singles, doubles = qml.qchem.excitations(electrons, num_qubits) +s_wires, d_wires = qml.qchem.excitations_to_wires(singles, doubles) +ansatz = functools.partial( + qml.UCCSD, init_state=initial_state, s_wires=s_wires, d_wires=d_wires +) + +# generate the cost function +@qml.qnode(dev, interface="jax") +def cost_circuit(params): + ansatz(params, wires=range(num_qubits)) + return qml.expval(H) + +############################################################################## +# If we evaluate this cost function, we can see that it corresponds to 15 different +# executions under the hood—one per expectation value: + +from jax import random as random +key, scale = random.PRNGKey(0), jnp.pi +params = random.normal(key, shape=(len(singles) + len(doubles),)) * scale +with qml.Tracker(dev) as tracker: # track the number of executions + print("Cost function value:", cost_circuit(params)) + +print("Number of quantum evaluations:", tracker.totals['executions']) + +############################################################################## +# How about a larger molecule? Let's try the +# `water molecule `__: + +dataset = qml.data.load('qchem', molname="H2O")[0] +H, num_qubits = dataset.hamiltonian, len(dataset.hamiltonian.wires) + +print("Required number of qubits:", num_qubits) +print("Number of Hamiltonian terms/required measurements:", len(H.terms()[0])) + +print("\n", H) + + +############################################################################## +# Simply going from two atoms in :math:`\text{H}_2` to three in :math:`\text{H}_2 \text{O}` +# resulted in over triple the number of qubits required and 1086 measurements that must be made! +# +# We can see that as the size of our molecule increases, we run into a problem: larger molecules +# result in Hamiltonians that not only require a larger number of qubits :math:`N` in their +# representation, but the number of terms in the Hamiltonian scales like +# :math:`\mathcal{O}(N^4)!` 😱😱😱 +# +# We can mitigate this somewhat by choosing smaller `basis sets +# `__ to represent the electronic structure +# wavefunction, however this would be done at the cost of solution accuracy, and doesn't reduce the number of +# measurements significantly enough to allow us to scale to classically intractable problems. +# +# .. figure:: /_static/demonstration_assets/measurement_optimize/n4.png +# :width: 70% +# :align: center +# +# The number of qubit Hamiltonian terms required to represent various molecules in the specified +# basis sets (adapted from `Minimizing State Preparations for VQE by Gokhale et al. +# `__) + + +############################################################################## +# Simultaneously measuring observables +# ------------------------------------ +# +# One of the assumptions we made above was that every term in the Hamiltonian must be measured independently. +# However, this might not be the case. From the `Heisenberg uncertainty relationship +# `__ for two +# observables :math:`\hat{A}` and :math:`\hat{B},` we know that +# +# .. math:: \sigma_A^2 \sigma_B^2 \geq \frac{1}{2}\left|\left\langle [\hat{A}, \hat{B}] \right\rangle\right|, +# +# where :math:`\sigma^2_A` and :math:`\sigma^2_B` are the variances of measuring the expectation value of the +# associated observables, and +# +# .. math:: [\hat{A}, \hat{B}] = \hat{A}\hat{B}-\hat{B}\hat{A} +# +# is the commutator. Therefore, +# +# - If the two observables :math:`\hat{A}` and :math:`\hat{B}` do not commute (:math:`[\hat{A}, +# \hat{B}] \neq 0`), then :math:`\sigma_A^2 +# \sigma_B^2 > 0` and we cannot simultaneously measure the expectation values of the two +# observables. +# +# .. +# +# - If :math:`\hat{A}` and :math:`\hat{B}` **do** commute (:math:`[\hat{A}, +# \hat{B}] = 0`), then :math:`\sigma_A^2 +# \sigma_B^2 \geq 0` and there exists a measurement basis where we can **simultaneously measure** the +# expectation value of both observables on the same state. +# +# To explore why commutativity and simultaneous measurement are related, let's assume that there +# is a complete, orthonormal eigenbasis :math:`|\phi_n\rangle` that *simultaneously +# diagonalizes* both :math:`\hat{A}` and :math:`\hat{B}:` +# +# .. math:: +# +# ① ~~ \hat{A} |\phi_n\rangle &= \lambda_{A,n} |\phi_n\rangle,\\ +# ② ~~ \hat{B} |\phi_n\rangle &= \lambda_{B,n} |\phi_n\rangle. +# +# where :math:`\lambda_{A,n}` and :math:`\lambda_{B,n}` are the corresponding eigenvalues. +# If we pre-multiply the first equation by :math:`\hat{B},` and the second by :math:`\hat{A}` +# (both denoted in blue): +# +# .. math:: +# +# \color{blue}{\hat{B}}\hat{A} |\phi_n\rangle &= \lambda_{A,n} \color{blue}{\hat{B}} +# |\phi_n\rangle = \lambda_{A,n} \color{blue}{\lambda_{B,n}} |\phi_n\rangle,\\ +# \color{blue}{\hat{A}}\hat{B} |\phi_n\rangle &= \lambda_{B,n} \color{blue}{\hat{A}} +# |\phi_n\rangle = \lambda_{A,n} \color{blue}{\lambda_{B,n}} |\phi_n\rangle. +# +# We can see that assuming a simultaneous eigenbasis requires that +# :math:`\hat{A}\hat{B}|\phi_n\rangle = \hat{B}\hat{A}|\phi_n\rangle.` Or, rearranging, +# +# .. math:: (\hat{A}\hat{B} - \hat{B}\hat{A}) |\phi_n\rangle = [\hat{A}, \hat{B}]|\phi_n\rangle = 0. +# +# Our assumption that :math:`|\phi_n\rangle` simultaneously diagonalizes both :math:`\hat{A}` and +# :math:`\hat{B}` only holds true if the two observables commute. +# +# So far, this seems awfully theoretical. What does this mean in practice? +# +# In the realm of variational circuits, we typically want to compute expectation values of an +# observable on a given state :math:`|\psi\rangle.` If we have two commuting observables, we now know that +# they share a simultaneous eigenbasis: +# +# .. math:: +# +# \hat{A} &= \sum_n \lambda_{A, n} |\phi_n\rangle\langle \phi_n|,\\ +# \hat{B} &= \sum_n \lambda_{B, n} |\phi_n\rangle\langle \phi_n|. +# +# Substituting this into the expression for the expectation values: +# +# .. math:: +# +# \langle\hat{A}\rangle &= \langle \psi | \hat{A} | \psi \rangle = \langle \psi | \left( \sum_n +# \lambda_{A, n} |\phi_n\rangle\langle \phi_n| \right) | \psi \rangle = \sum_n \lambda_{A,n} +# |\langle \phi_n|\psi\rangle|^2,\\ +# \langle\hat{B}\rangle &= \langle \psi | \hat{B} | \psi \rangle = \langle \psi | \left( \sum_n +# \lambda_{B, n} |\phi_n\rangle\langle \phi_n| \right) | \psi \rangle = \sum_n \lambda_{B,n} +# |\langle \phi_n|\psi\rangle|^2. +# +# So, assuming we know the eigenvalues of the commuting observables in advance, if we perform a +# measurement in their shared eigenbasis (the :math:`|\phi_n\rangle`), we only need to perform a **single measurement** of the +# probabilities :math:`|\langle \phi_n|\psi\rangle|^2` in order to recover both expectation values! 😍 +# +# Fantastic! But, can we use this to reduce the number of measurements we need to perform in the VQE algorithm? +# To do so, we must find the answer to two questions: +# +# 1. How do we determine which terms of the cost Hamiltonian commute? +# +# 2. How do we rotate the circuit into the shared eigenbasis prior to measurement? +# +# The answers to these questions aren't necessarily easy nor straightforward. Thankfully, there are +# some recent techniques we can harness to address both. + +############################################################################## +# Qubit-wise commuting Pauli terms +# -------------------------------- +# +# Back when we summarized the VQE algorithm, we saw that each term of the Hamiltonian is generally represented +# as a tensor product of Pauli operators: +# +# .. math:: h_i = \bigotimes_{n=0}^{N-1} P_n. +# +# Luckily, this tensor product structure allows us to take a bit of a shortcut. Rather than consider +# **full commutativity**, we can consider a more strict condition known as **qubit-wise +# commutativity** (QWC). +# +# To start with, let's consider single-qubit Pauli operators and the identity. We know that the Pauli operators +# commute with themselves as well as the identity, but they do *not* commute with +# each other: +# +# .. math:: +# +# [\sigma_i, I] = 0, ~~~ [\sigma_i, \sigma_i] = 0, ~~~ [\sigma_i, \sigma_j] = c \sigma_k \delta_{ij}. +# +# Now consider two tensor products of Pauli terms, for example :math:`X\otimes Y \otimes I` and +# :math:`X\otimes I \otimes Z.` We say that these two terms are qubit-wise commuting, since, if +# we compare each subsystem in the tensor product, we see that every one commutes: +# +# .. math:: +# +# \begin{array}{ | *1c | *1c | *1c | *1c | *1c |} +# X &\otimes &Y &\otimes &I\\ +# X &\otimes &I &\otimes &Z +# \end{array} ~~~~ \Rightarrow ~~~~ [X, X] = 0, ~~~ [Y, I] = 0, ~~~ [I, Z] = 0. +# +# As a consequence, both terms must commute: +# +# .. math:: [X\otimes Y \otimes I, X\otimes I \otimes Z] = 0. +# +# .. important:: +# +# Qubit-wise commutativity is a **sufficient** but not **necessary** condition +# for full commutativity. For example, the two Pauli terms :math:`Y\otimes Y` and +# :math:`X\otimes X` are not qubit-wise commuting, but do commute (have a go verifying this!). +# +# Once we have identified a qubit-wise commuting pair of Pauli terms, it is also straightforward to +# find the gates to rotate the circuit into the shared eigenbasis. To do so, we simply rotate +# each qubit one-by-one depending on the Pauli operator we are measuring on that wire: +# +# .. raw:: html +# +# +#
+# +# .. rst-class:: docstable +# +# +------------------+-------------------------------+ +# | Observable | Rotation gate | +# +==================+===============================+ +# | :math:`X` | :math:`RY(-\pi/2) = H` | +# +------------------+-------------------------------+ +# | :math:`Y` | :math:`RX(\pi/2)=HS^{-1}=HSZ` | +# +------------------+-------------------------------+ +# | :math:`Z` | :math:`I` | +# +------------------+-------------------------------+ +# | :math:`I` | :math:`I` | +# +------------------+-------------------------------+ +# +# .. raw:: html +# +#
+# +# Therefore, in this particular example: +# +# * Wire 0: we are measuring both terms in the :math:`X` basis, apply the Hadamard gate +# * Wire 1: we are measuring both terms in the :math:`Y` basis, apply a :math:`RX(\pi/2)` gate +# * Wire 2: we are measuring both terms in the :math:`Z` basis (the computational basis), no gate needs to be applied. +# +# Let's use PennyLane to verify this. + + +obs = [ + qml.PauliX(0) @ qml.PauliY(1), + qml.PauliX(0) @ qml.PauliZ(2) +] + + +############################################################################## +# First, let's naively use two separate circuit evaluations to measure +# the two QWC terms. + + +dev = qml.device("default.qubit", wires=3) + +@qml.qnode(dev, interface="jax") +def circuit1(weights): + qml.StronglyEntanglingLayers(weights, wires=range(3)) + return qml.expval(obs[0]) + + +@qml.qnode(dev, interface="jax") +def circuit2(weights): + qml.StronglyEntanglingLayers(weights, wires=range(3)) + return qml.expval(obs[1]) + +param_shape = qml.templates.StronglyEntanglingLayers.shape(n_layers=3, n_wires=3) +key, scale = random.PRNGKey(192933), 0.1 +weights = scale * random.normal(key, shape=param_shape) + +print("Expectation value of XYI = ", circuit1(weights)) +print("Expectation value of XIZ = ", circuit2(weights)) + +############################################################################## +# Now, let's use our QWC approach to reduce this down to a *single* measurement +# of the probabilities in the shared eigenbasis of both QWC observables: + +@qml.qnode(dev, interface="jax") +def circuit_qwc(weights): + qml.StronglyEntanglingLayers(weights, wires=range(3)) + + # rotate wire 0 into the shared eigenbasis + qml.RY(-jnp.pi / 2, wires=0) + + # rotate wire 1 into the shared eigenbasis + qml.RX(jnp.pi / 2, wires=1) + + # wire 2 does not require a rotation + + # measure probabilities in the computational basis + return qml.probs(wires=range(3)) + + +rotated_probs = circuit_qwc(weights) +print(rotated_probs) + +############################################################################## +# We're not quite there yet; we have only calculated the probabilities of the variational circuit +# rotated into the shared eigenbasis—the :math:`|\langle \phi_n |\psi\rangle|^2.` To recover the +# *expectation values* of the two QWC observables from the probabilities, recall that we need one +# final piece of information: their eigenvalues :math:`\lambda_{A, n}` and :math:`\lambda_{B, n}.` +# +# We know that the single-qubit Pauli operators each have eigenvalues :math:`(1, -1),` while the identity +# operator has eigenvalues :math:`(1, 1).` We can make use of ``np.kron`` to quickly +# generate the eigenvalues of the full Pauli terms, making sure that the order +# of the eigenvalues in the Kronecker product corresponds to the tensor product. + +eigenvalues_XYI = jnp.kron(jnp.kron(jnp.array([1, -1]), jnp.array([1, -1])), jnp.array([1, 1])) +eigenvalues_XIZ = jnp.kron(jnp.kron(jnp.array([1, -1]), jnp.array([1, 1])), jnp.array([1, -1])) + +# Taking the linear combination of the eigenvalues and the probabilities +print("Expectation value of XYI = ", jnp.dot(eigenvalues_XYI, rotated_probs)) +print("Expectation value of XIZ = ", jnp.dot(eigenvalues_XIZ, rotated_probs)) + + +############################################################################## +# Compare this to the result when we used two circuit evaluations. We have successfully used a +# single circuit evaluation to recover both expectation values! +# +# Luckily, PennyLane automatically performs this QWC grouping under the hood. We simply +# return the two QWC Pauli terms from the QNode: + +@qml.qnode(dev, interface="jax") +def circuit(weights): + qml.StronglyEntanglingLayers(weights, wires=range(3)) + return [ + qml.expval(qml.PauliX(0) @ qml.PauliY(1)), + qml.expval(qml.PauliX(0) @ qml.PauliZ(2)) + ] + + +print(circuit(weights)) + + +############################################################################## +# Behind the scenes, PennyLane is making use of our built-in +# :mod:`qml.pauli ` module, which contains functions for diagonalizing QWC +# terms: + +rotations, new_obs = qml.pauli.diagonalize_qwc_pauli_words(obs) + +print(rotations) +print(new_obs) + + +############################################################################## +# Here, the first line corresponds to the basis rotations that were discussed above, written in +# terms of ``RX`` and ``RY`` rotations. Check out the :mod:`qml.pauli ` +# documentation for more details on its provided functionality and how it works. +# +# Given a Hamiltonian containing a large number of Pauli terms, +# there is a high likelihood of there being a significant number of terms that qubit-wise commute. Can +# we somehow partition the terms into **fewest** number of QWC groups to minimize the number of measurements +# we need to take? + +############################################################################## +# Grouping QWC terms +# ------------------ +# +# A nice example is provided in [#verteletskyi2020]_ showing how we might tackle this. Say we have +# the following Hamiltonian defined over four qubits: +# +# .. math:: H = Z_0 + Z_0 Z_1 + Z_0 Z_1 Z_2 + Z_0 Z_1 Z_2 Z_3 + X_2 X_3 + Y_0 X_2 X_3 + Y_0 Y_1 X_2 X_3, +# +# where we are using the shorthand :math:`P_0 P_2 = P\otimes I \otimes P \otimes I` for brevity. +# If we go through and work out which Pauli terms are qubit-wise commuting, we can represent +# this in a neat way using a graph: +# +# .. figure:: /_static/demonstration_assets/measurement_optimize/graph1.png +# :width: 70% +# :align: center +# +# In the above graph, every node represents an individual Pauli term of the Hamiltonian, with +# edges connecting terms that are qubit-wise commuting. Groups of qubit-wise commuting terms are +# represented as **complete subgraphs**. Straight away, we can make an observation: +# there is no unique solution for partitioning the Hamiltonian into groups of qubit-wise commuting +# terms! In fact, there are several solutions: +# +# .. figure:: /_static/demonstration_assets/measurement_optimize/graph2.png +# :width: 90% +# :align: center +# +# Of course, of the potential solutions above, there is one that is more optimal than the others --- +# on the bottom left, we have partitioned the graph into *two* complete subgraphs, as opposed to the +# other solutions that require three complete subgraphs. If we were to go with this solution, +# we would be able to measure the expectation value of the Hamiltonian using two circuit evaluations. +# +# This problem—finding the minimum number of complete subgraphs of a graph—is actually quite well +# known in graph theory, where it is referred to as the `minimum clique cover problem +# `__ (with 'clique' being another term for a complete subgraph). +# +# Unfortunately, that's where our good fortune ends—the minimum clique cover problem is known to +# be `NP-hard `__, meaning there is no known (classical) +# solution to finding the optimum/minimum clique cover in polynomial time. +# +# Thankfully, there is a silver lining: we know of polynomial-time algorithms for finding +# *approximate* solutions to the minimum clique cover problem. These heuristic approaches, while +# not guaranteed to find the optimum solution, scale quadratically with the number of nodes in the +# graph/terms in the Hamiltonian [#yen2020]_, so work reasonably well in practice. +# +# Many of these heuristic approaches have roots in another graph problem known as `graph +# colouring `__; the assignment of colours to +# the graph's vertices such that no adjacent vertices have the same colour. How is this related +# to the minimum clique cover problem, though? If we take our QWC graph above, and generate the +# `complement graph `__ by drawing edges +# between all *non*-adjacent nodes, +# +# .. figure:: /_static/demonstration_assets/measurement_optimize/graph3.png +# :width: 100% +# :align: center +# +# we see that solving the minimum clique cover problem on the QWC graph is equivalent to solving the +# graph colouring problem on the complement graph using the minimum possible number of colours. +# While there are various different heuristic algorithms, a common one is `greedy colouring +# `__; in fact, the open-source graph +# package `NetworkX even provides a function for greedy colouring +# `__, +# ``nx.greedy_color``. +# +# Let's give this a go, using NetworkX to solve the minimum clique problem for observable grouping. +# First, we'll need to generate the QWC graph (with each node corresponding to a Hamiltonian +# term, and edges indicating two terms that are QWC). + +import networkx as nx +from matplotlib import pyplot as plt + +terms = [ + qml.PauliZ(0), + qml.PauliZ(0) @ qml.PauliZ(1), + qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2), + qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3), + qml.PauliX(2) @ qml.PauliX(3), + qml.PauliY(0) @ qml.PauliX(2) @ qml.PauliX(3), + qml.PauliY(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliX(3) +] + +def format_pauli_word(term): + """Convenience function that nicely formats a PennyLane + tensor observable as a Pauli word""" + if isinstance(term, qml.ops.Prod): + return " ".join([format_pauli_word(t) for t in term]) + + return f"{term.name[-1]}{term.wires.tolist()[0]}" + +G = nx.Graph() + +with warnings.catch_warnings(): + # Muting irrelevant warnings + warnings.filterwarnings( + "ignore", + message="The behaviour of operator ", + category=UserWarning, + ) + + # add the terms to the graph + G.add_nodes_from(terms) + + # add QWC edges + G.add_edges_from([ + [terms[0], terms[1]], # Z0 <--> Z0 Z1 + [terms[0], terms[2]], # Z0 <--> Z0 Z1 Z2 + [terms[0], terms[3]], # Z0 <--> Z0 Z1 Z2 Z3 + [terms[1], terms[2]], # Z0 Z1 <--> Z0 Z1 Z2 + [terms[2], terms[3]], # Z0 Z1 Z2 <--> Z0 Z1 Z2 Z3 + [terms[1], terms[3]], # Z0 Z1 <--> Z0 Z1 Z2 Z3 + [terms[0], terms[4]], # Z0 <--> X2 X3 + [terms[1], terms[4]], # Z0 Z1 <--> X2 X3 + [terms[4], terms[5]], # X2 X3 <--> Y0 X2 X3 + [terms[4], terms[6]], # X2 X3 <--> Y0 Y1 X2 X3 + [terms[5], terms[6]], # Y0 X2 X3 <--> Y0 Y1 X2 X3 + ]) + + plt.margins(x=0.1) + coords = nx.spring_layout(G, seed=1) + nx.draw( + G, + coords, + labels={node: format_pauli_word(node) for node in terms}, + with_labels=True, + node_size=500, + font_size=8, + node_color="#9eded1", + edge_color="#c1c1c1", + ) + + ############################################################################## + # We can now generate the complement graph (compare this to our handdrawn + # version above!): + + C = nx.complement(G) + coords = nx.spring_layout(C, seed=1) + + nx.draw( + C, + coords, + labels={node: format_pauli_word(node) for node in terms}, + with_labels=True, + node_size=500, + font_size=8, + node_color="#9eded1", + edge_color="#c1c1c1" + ) + + ############################################################################## + # Now that we have the complement graph, we can perform a greedy coloring to + # determine the minimum number of QWC groups: + + groups = nx.coloring.greedy_color(C, strategy="largest_first") + + # plot the complement graph with the greedy colouring + nx.draw( + C, + coords, + labels={node: format_pauli_word(node) for node in terms}, + with_labels=True, + node_size=500, + font_size=8, + node_color=[("#9eded1", "#aad4f0")[groups[node]] for node in C], + edge_color="#c1c1c1" + ) + + +num_groups = len(set(groups.values())) +print("Minimum number of QWC groupings found:", num_groups) + + +for i in range(num_groups): + print(f"\nGroup {i}:") + + for term, group_id in groups.items(): + if group_id == i: + print(format_pauli_word(term)) + +############################################################################## +# Putting it all together +# ----------------------- +# +# So, we now have a strategy for minimizing the number of measurements we need to perform +# for our VQE problem: +# +# 1. Determine which terms of the Hamiltonian are qubit-wise commuting, and use +# this to construct a graph representing the QWC relationship. +# +# 2. Construct the complement QWC graph. +# +# 3. Use a graph colouring heuristic algorithm to determine a graph colouring for the complement graph +# with a minimum number of colours. Each coloured vertex set corresponds to a +# qubit-wise commuting group of Hamiltonian terms. +# +# 4. Generate and evaluate the circuit ansatz (with additional rotations) per +# QWC grouping, extracting probability distributions. +# +# 5. Finally, post-process the probability distributions with the observable eigenvalues +# to recover the Hamiltonian expectation value. +# +# Luckily, the PennyLane ``pauli`` module makes this relatively easy. Let's walk through +# the entire process using the provided grouping functions. +# +# Steps 1-3 (finding and grouping QWC terms in the Hamiltonian) can be done via the +# :func:`qml.pauli.group_observables ` function: + +obs_groupings = qml.pauli.group_observables(terms, grouping_type='qwc', method='rlf') + + +############################################################################## +# The ``grouping_type`` argument allows us to choose how the commuting terms +# are determined (more on that later!) whereas ``method`` determines the colouring +# heuristic (in this case, ``"rlf"`` refers to Recursive Largest First, a variant of largest first colouring heuristic). +# +# If we want to see what the required rotations and measurements are, we can use the +# :func:`qml.pauli.diagonalize_qwc_groupings ` +# function: + +rotations, measurements = qml.pauli.diagonalize_qwc_groupings(obs_groupings) + +############################################################################## +# However, this isn't strictly necessary—recall previously that the QNode +# has the capability to *automatically* measure qubit-wise commuting observables! + +dev = qml.device("lightning.qubit", wires=4) + +@qml.qnode(dev, interface="jax") +def circuit(weights, group=None, **kwargs): + qml.StronglyEntanglingLayers(weights, wires=range(4)) + return [qml.expval(o) for o in group] + +param_shape = qml.templates.StronglyEntanglingLayers.shape(n_layers=3, n_wires=4) +key = random.PRNGKey(1) +weights = random.normal(key, shape=param_shape) * 0.1 +result = [jnp.array(circuit(weights, group=g)) for g in obs_groupings] + +print("Term expectation values:") +for group, expvals in enumerate(result): + print(f"Group {group} expectation values:", expvals) + +# Since all the coefficients of the Hamiltonian are unity, +# we can simply sum the expectation values. +print(" = ", jnp.sum(jnp.hstack(result))) + + +############################################################################## +# Finally, we don't need to go through this process manually every time; if our cost function can be +# written in the form of an expectation value of a Hamiltonian (as is the case for most VQE and QAOA +# problems), we can use the option ``grouping_type="qwc"`` in :class:`~.pennylane.Hamiltonian` to +# automatically optimize the measurements. + +H = qml.Hamiltonian(coeffs=jnp.ones(len(terms)), observables=terms, grouping_type="qwc") +_, H_ops = H.terms() +@qml.qnode(dev, interface="jax") +def cost_fn(weights): + qml.StronglyEntanglingLayers(weights, wires=range(4)) + return qml.expval(H) +print(cost_fn(weights)) + +############################################################################## +# Beyond VQE +# ---------- +# +# Wait, hang on. We dove so deeply into measurement grouping and optimization, we forgot to check +# how this affects the number of measurements required to perform the VQE on :math:`\text{H}_2 \text{O}!` +# Let's use our new-found knowledge to see what happens. + +dataset = qml.data.load('qchem', molname="H2O")[0] +H, num_qubits = dataset.hamiltonian, len(dataset.hamiltonian.wires) +print("Number of Hamiltonian terms/required measurements:", len(H_ops)) + +# grouping +groups = qml.pauli.group_observables(H_ops, grouping_type='qwc', method='rlf') +print("Number of required measurements after optimization:", len(groups)) + +############################################################################## +# We went from 1086 required measurements/circuit evaluations to 320 (just over *one thousand* +# down to *three hundred* 😱😱😱). +# +# As impressive as this is, however, this is just the beginning of the optimization. +# +# While finding qubit-wise commutating terms is relatively straightforward, with a little +# extra computation we can push this number down even further. Recent work has explored +# the savings that can be made by considering *full* commutativity [#yen2020]_, unitary +# partitioning [#izmaylov2019]_, and Fermionic basis rotation grouping [#huggins2019]_. +# Work has also been performed to reduce the classical overhead associated with measurement +# optimization, allowing the classical measurement grouping to be performed in linear time +# [#gokhale2020]_. For example, recall that qubit-wise commutativity is only a subset of +# full commutativity; if we consider full commutativity instead, we can further reduce the +# number of groups required. +# +# Finally, it is worth pointing out that, as the field of variational quantum algorithms grows, this +# problem of measurement optimization no longer just applies to the VQE algorithm (the algorithm it +# was born from). Instead, there are a multitude of algorithms that could benefit from these +# measurement optimization techniques (QAOA being a prime example). +# +# So the next time you are working on a variational quantum algorithm and the number +# of measurements required begins to explode—stop, take a deep breath 😤, and consider grouping +# and optimizing your measurements. +# +# .. note:: +# +# Qubit-wise commuting group information for a wide variety of molecules has been +# pre-computed, and is available for download in +# in the `PennyLane Datasets library `__. + +############################################################################## +# References +# ---------- +# +# .. [#peruzzo2014] +# +# Alberto Peruzzo, Jarrod McClean *et al.*, "A variational eigenvalue solver on a photonic +# quantum processor". `Nature Communications 5, 4213 (2014). +# `__ +# +# .. [#yen2020] +# +# Tzu-Ching Yen, Vladyslav Verteletskyi, and Artur F. Izmaylov. "Measuring all compatible +# operators in one series of single-qubit measurements using unitary transformations." `Journal of +# Chemical Theory and Computation 16.4 (2020): 2400-2409. +# `__ +# +# .. [#izmaylov2019] +# +# Artur F. Izmaylov, *et al.* "Unitary partitioning approach to the measurement problem in the +# variational quantum eigensolver method." `Journal of Chemical Theory and Computation 16.1 (2019): +# 190-195. `__ +# +# .. [#huggins2019] +# +# William J. Huggins, *et al.* "Efficient and noise resilient measurements for quantum chemistry +# on near-term quantum computers." `arXiv preprint arXiv:1907.13117 (2019). +# `__ +# +# .. [#gokhale2020] +# +# Pranav Gokhale, *et al.* "Minimizing state preparations in variational quantum eigensolver by +# partitioning into commuting families." `arXiv preprint arXiv:1907.13623 (2019). +# `__ +# +# .. [#verteletskyi2020] +# +# Vladyslav Verteletskyi, Tzu-Ching Yen, and Artur F. Izmaylov. "Measurement optimization in the +# variational quantum eigensolver using a minimum clique cover." `The Journal of Chemical Physics +# 152.12 (2020): 124114. `__ +# +# +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_pasqal/demo.py b/demonstrations_v2/tutorial_pasqal/demo.py index 8618d05f4b..b49898f392 100644 --- a/demonstrations_v2/tutorial_pasqal/demo.py +++ b/demonstrations_v2/tutorial_pasqal/demo.py @@ -1,362 +1,362 @@ -r""" -Quantum computation with neutral atoms -====================================== - -.. meta:: - :property="og:description": Neutral atom quantum devices allow you to place - qubits within interesting three-dimensional configurations. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/pasqal_thumbnail.png - -.. related:: - ahs_aquila Pulse programming on neutral atom hardware - -*Author: Nathan Killoran — Posted: 13 October 2020. Last updated: 21 January 2021.* - -Quantum computing architectures come in many flavours: superconducting qubits, ion traps, -photonics, silicon, and more. One very interesting physical substrate is *neutral atoms*. These -quantum devices have some basic similarities to ion traps. Ion-trap devices make use of atoms -that have an imbalance between protons (positively charged) and electrons (negatively charged). -Neutral atoms, on the other hand, have an equal number of protons and electrons. - -In neutral-atom systems, the individual atoms can be easily programmed into various two- or -three-dimensional configurations. Like ion-trap systems, individual qubits can be encoded into -the energy levels of the atoms. The qubits thus retain the three-dimensional geometry of their -host atoms. Qubits that are nearby in space can be programmed to interact with one another -via two-qubit gates. This opens up some tantalizing possibilities for exotic quantum-computing -circuit topologies. - -.. figure:: https://raw.githubusercontent.com/lhenriet/cirq-pasqal/fc4f9c7792a8737fde76d4c05828aa538be8452e/pasqal-tutorials/files/eiffel_tower.png - :align: center - :width: 50% - - .. - - Neutral atoms (green dots) arranged in various configurations. These atoms can be - used to encode qubits and carry out quantum computations. Image originally from [#barredo2017]_. - -The startup `Pasqal `_ is one of the companies working to bring -neutral-atom quantum computing devices to the world. To support this new class of devices, -Pasqal has contributed some new features to the quantum software library `Cirq `_. - -In this demo, we will use PennyLane, Cirq, and TensorFlow to show off the unique abilities of -neutral atom devices, leveraging them to make a variational quantum circuit which has a -very unique topology: *the Eiffel tower*. Specifically, we will build a simple toy -circuit whose qubits are arranged like the Eiffel tower. The girders between -the points on the tower will represent two-qubit gates, with the final output of our -variational circuit coming at the very peak of the tower. - -Let's get to it! - -.. note:: - - To run this demo locally, you will need to install `Cirq - `_, (version >= 0.9.1), and the - `PennyLane-cirq plugin `_ - (version >= 0.13). You will also need to download a copy of the data, which - is available `here - `_. - -""" - -############################################################################## -# Building the Eiffel tower -# ------------------------- -# -# Our first step will be to load and visualize the data for the Eiffel tower -# configuration, which was generously provided by the team at Pasqal. -# (If running locally, the line below should be updated with the local -# path where you have saved the downloaded data). - -import numpy as np -coords = np.loadtxt("../_static/demonstration_assets/pasqal/Eiffel_tower_data.dat") -xs = coords[:,0] -ys = coords[:,1] -zs = coords[:,2] - -import matplotlib.pyplot as plt -fig = plt.figure() -ax = fig.add_subplot(111, projection='3d') -ax.view_init(40, 15) -fig.subplots_adjust(left=0, right=1, bottom=0, top=1) -ax.set_xlim(-20, 20) -ax.set_ylim(-20, 20) -ax.set_zlim(-40, 10) -plt.axis('off') -ax.scatter(xs, ys, zs, c='g',alpha=0.3) -plt.show() - -############################################################################## -# This dataset contains 126 points. Each point represents a distinct -# neutral-atom qubit. Simulating this many qubits would be outside the -# reach of Cirq's built-in simulators, so for this demo, -# we will pare down to just 9 points, evenly spaced around the tower. -# These are highlighted in red below. -# - -fig = plt.figure() -ax = fig.add_subplot(111, projection='3d') -ax.view_init(40, 15) -fig.subplots_adjust(left=0, right=1, bottom=0, top=1) -ax.set_xlim(-20, 20) -ax.set_ylim(-20, 20) -ax.set_zlim(-40, 10) -plt.axis('off') -ax.scatter(xs, ys, zs, c='g', alpha=0.3) - -base_mask = [3, 7, 11, 15] -qubit_mask = [48, 51, 60, 63, 96, 97, 98, 99, 125] -input_coords = coords[base_mask] # we'll need this for a plot later -qubit_coords = coords[qubit_mask] - -subset_xs = qubit_coords[:, 0] -subset_ys = qubit_coords[:, 1] -subset_zs = qubit_coords[:, 2] -ax.scatter(subset_xs, subset_ys, subset_zs, c='r', alpha=1.0) -plt.show() - -############################################################################## -# Converting to Cirq qubits -# ------------------------- -# -# Our next step will be to convert these datapoints into objects that -# Cirq understands as qubits. For neutral-atom devices in Cirq, we can use the -# ``ThreeDQubit`` class, which carries information about the three-dimensional -# arrangement of the qubits. -# -# Now, neutral-atom devices come with some physical restrictions. -# Specifically, in a particular three-dimensional configuration, qubits that -# are too distant from one another can't easily interact. Instead, there is -# a notion of a *control radius;* any atoms which are within the system's -# control radius can interact with one another. Qubits separated by a -# distance larger than the control radius cannot interact. -# -# In order to allow our Eiffel tower qubits to interact with -# one another more easily, we will artificially scale some dimensions -# when placing the atoms. - -from cirq_pasqal import ThreeDQubit -xy_scale = 1.5 -z_scale = 0.75 -qubits = [ThreeDQubit(xy_scale * x, xy_scale * y, z_scale * z) - for x, y, z in qubit_coords] - -############################################################################## -# To simulate a neutral-atom quantum computation, we can use the -# ``"cirq.pasqal"`` device, available via the -# `PennyLane-Cirq plugin `_. -# We will need to provide this device with the ``ThreeDQubit`` object that we created -# above. We also need to instantiate the device with a fixed control radius. - -import pennylane as qml - -num_wires = len(qubits) -control_radius = 32.4 -dev = qml.device("cirq.pasqal", control_radius=control_radius, - qubits=qubits, wires=num_wires) - -############################################################################## -# Creating a quantum circuit -# -------------------------- -# -# We will now make a variational circuit out of the Eiffel tower configuration -# from above. Each of the 9 qubits we are using can be thought of -# as a single wire in a quantum circuit. We will cause these qubits to interact by applying -# a sequence of two-qubit gates. Specifically, the circuit consists of several -# stages: -# -# i. Input classical data is converted into quantum information at the first -# (lowest) vertical level of qubits. In this example, our classical data -# will be simple bit strings, which we can embed by using single-qubit -# bit flips (a simple -# `data-embedding `_ -# strategy). -# -# ii. For each corner of the tower, CNOTs are enacted between the first- -# and second-level qubits. -# -# iii. All qubits from the second level interact with a single "peak" qubit -# using a parametrized controlled-rotation operation. The free parameters -# of our variational circuit enter here. -# -# The output of our circuit is determined via a Pauli-Z measurement on -# the final "peak" qubit. -# -# That's a few things to keep track of, so let's show the circuit via a -# three-dimensional image: - -first_lvl_coords = qubit_coords[:4] -second_lvl_coords = qubit_coords[4:8] -peak_coords = qubit_coords[8] - -input_x, input_y, input_z = [input_coords[:, idx] - for idx in range(3)] -second_x, second_y, second_z = [first_lvl_coords[:, idx] - for idx in range(3)] -third_x, third_y, third_z = [second_lvl_coords[:, idx] - for idx in range(3)] -peak_x, peak_y, peak_z = peak_coords - -fig = plt.figure() -ax = fig.add_subplot(111, projection='3d') -ax.view_init(40, 15) -fig.subplots_adjust(left=0, right=1, bottom=0, top=1) -ax.set_xlim(-20, 20) -ax.set_ylim(-20, 20) -ax.set_zlim(-40, 10) -plt.axis('off') - -ax.scatter(xs, ys, zs, c='g', alpha=0.3) -ax.scatter(subset_xs, subset_ys, subset_zs, c='r', alpha=1.0); - -# Two-qubit gates between second and third levels -for corner in range(4): - ax.plot(xs=[second_x[corner], third_x[corner]], - ys=[second_y[corner], third_y[corner]], - zs=[second_z[corner], third_z[corner]], - c='k'); - -# Two-qubit gates between third level and peak -for corner in range(4): - ax.plot(xs=[third_x[corner], peak_x], - ys=[third_y[corner], peak_y], - zs=[third_z[corner], peak_z], - c='k'); - -# Additional lines to guide the eye -for corner in range(4): - ax.plot(xs=[input_x[corner], second_x[corner]], - ys=[input_y[corner], second_y[corner]], - zs=[input_z[corner], second_z[corner]], - c='grey', linestyle='--'); - ax.plot(xs=[second_x[corner], second_x[(corner + 1) % 4]], - ys=[second_y[corner], second_y[(corner + 1) % 4]], - zs=[second_z[corner], second_z[(corner + 1) % 4]], - c='grey', linestyle='--'); - ax.plot(xs=[third_x[corner], third_x[(corner + 1) % 4]], - ys=[third_y[corner], third_y[(corner + 1) % 4]], - zs=[third_z[corner], third_z[(corner + 1) % 4]], - c='grey', linestyle='--'); - -plt.show() - -############################################################################## -# In this figure, the red dots represent the specific qubits we will use in -# our circuit (the green dots are not used in this demo). -# -# The solid black lines indicate two-qubit gates between these qubits. -# The dashed grey lines are meant to guide the eye, but could also be -# used to make a more complex model by adding further two-qubit gates. -# -# Classical data is loaded in at the bottom qubits (the "tower legs") and -# the final measurement result is read out from the top "peak" qubit. -# The order of gate execution proceeds vertically from bottom to top, and -# clockwise at each level. -# -# The code below creates this particular quantum circuit configuration in -# PennyLane: - -peak_qubit = 8 - -def controlled_rotation(phi, wires): - qml.RY(phi, wires=wires[1]) - qml.CNOT(wires=wires) - qml.RY(-phi, wires=wires[1]) - qml.CNOT(wires=wires) - -@qml.qnode(dev, interface="tf") -def circuit(weights, data): - - # Input classical data loaded into qubits at second level - for idx in range(4): - if data[idx]: - qml.PauliX(wires=idx) - - # Interact qubits from second and third levels - for idx in range(4): - qml.CNOT(wires=[idx, idx + 4]) - - # Interact qubits from third level with peak using parameterized gates - for idx, wire in enumerate(range(4, 8)): - controlled_rotation(weights[idx], wires=[wire, peak_qubit]) - - return qml.expval(qml.PauliZ(wires=peak_qubit)) - - -############################################################################## -# Training the circuit -# -------------------- -# -# Let's now leverage this variational circuit to tackle a toy classification -# problem. -# For the purposes of this demo, we will consider a very simple classifier: -# -# * if the first input qubit is in the state :math:`\mid 0 \rangle,` the model -# should make the prediction "0", and -# -# * if the first input qubit is in the state :math:`\mid 1 \rangle,` the model -# should predict "1" (independent of the states of all other qubits). -# -# In other words, the idealized trained model should learn an -# identity transformation between the first qubit and the final one, while -# ignoring the states of all other qubits. -# -# With this goal in mind, we can create a basic cost function. This cost -# function randomly samples possible 4-bit input bitstrings, and compares -# the circuit's output with the value of the first bit. The other bits -# can be thought of as noise that we don't want our model to learn. - - -import tensorflow as tf -np.random.seed(143) -init_weights = np.pi * np.random.rand(4) - -weights = tf.Variable(init_weights, dtype=tf.float64) - -data = np.random.randint(0, 2, size=4) - -def cost(): - data = np.random.randint(0, 2, size=4) - label = data[0] - output = (-circuit(weights, data) + 1) / 2 - return tf.abs(output - label) ** 2 - -opt = tf.keras.optimizers.Adam(learning_rate=0.1) - -for step in range(100): - opt.minimize(cost, [weights]) - if step % 5 == 0: - print("Step {}: cost={}".format(step, cost())) - -print("Final cost value: {}".format(cost())) - -############################################################################## -# Success! The circuit has learned to transfer the state of the first qubit -# to the state of the last qubit, while ignoring the state of all other input -# qubits. -# -# The programmable three-dimensional configurations of neutral-atom quantum -# computers provide a special tool that is hard to replicate in other -# platforms. Could the physical -# arrangement of qubits, in particular the third dimension, be leveraged to -# make quantum algorithms more sparse or efficient? Could neutral-atom -# systems—with their unique programmability of the geometry—allow us to -# rapidly prototype and experiment with new circuit topologies? What -# possibilities could this open up for quantum computing, quantum chemistry, -# or quantum machine learning? -# - -############################################################################## -# References -# ---------- -# -# .. [#barredo2017] -# -# Daniel Barredo, Vincent Lienhard, Sylvain de Leseleuc, Thierry Lahaye, and Antoine Browaeys. -# "Synthetic three-dimensional atomic structures assembled atom by atom." -# `arXiv:1712.02727 -# `__, 2017. -# -# -# About the author -# ---------------- +r""" +Quantum computation with neutral atoms +====================================== + +.. meta:: + :property="og:description": Neutral atom quantum devices allow you to place + qubits within interesting three-dimensional configurations. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/pasqal_thumbnail.png + +.. related:: + ahs_aquila Pulse programming on neutral atom hardware + +*Author: Nathan Killoran — Posted: 13 October 2020. Last updated: 21 January 2021.* + +Quantum computing architectures come in many flavours: superconducting qubits, ion traps, +photonics, silicon, and more. One very interesting physical substrate is *neutral atoms*. These +quantum devices have some basic similarities to ion traps. Ion-trap devices make use of atoms +that have an imbalance between protons (positively charged) and electrons (negatively charged). +Neutral atoms, on the other hand, have an equal number of protons and electrons. + +In neutral-atom systems, the individual atoms can be easily programmed into various two- or +three-dimensional configurations. Like ion-trap systems, individual qubits can be encoded into +the energy levels of the atoms. The qubits thus retain the three-dimensional geometry of their +host atoms. Qubits that are nearby in space can be programmed to interact with one another +via two-qubit gates. This opens up some tantalizing possibilities for exotic quantum-computing +circuit topologies. + +.. figure:: https://raw.githubusercontent.com/lhenriet/cirq-pasqal/fc4f9c7792a8737fde76d4c05828aa538be8452e/pasqal-tutorials/files/eiffel_tower.png + :align: center + :width: 50% + + .. + + Neutral atoms (green dots) arranged in various configurations. These atoms can be + used to encode qubits and carry out quantum computations. Image originally from [#barredo2017]_. + +The startup `Pasqal `_ is one of the companies working to bring +neutral-atom quantum computing devices to the world. To support this new class of devices, +Pasqal has contributed some new features to the quantum software library `Cirq `_. + +In this demo, we will use PennyLane, Cirq, and TensorFlow to show off the unique abilities of +neutral atom devices, leveraging them to make a variational quantum circuit which has a +very unique topology: *the Eiffel tower*. Specifically, we will build a simple toy +circuit whose qubits are arranged like the Eiffel tower. The girders between +the points on the tower will represent two-qubit gates, with the final output of our +variational circuit coming at the very peak of the tower. + +Let's get to it! + +.. note:: + + To run this demo locally, you will need to install `Cirq + `_, (version >= 0.9.1), and the + `PennyLane-cirq plugin `_ + (version >= 0.13). You will also need to download a copy of the data, which + is available `here + `_. + +""" + +############################################################################## +# Building the Eiffel tower +# ------------------------- +# +# Our first step will be to load and visualize the data for the Eiffel tower +# configuration, which was generously provided by the team at Pasqal. +# (If running locally, the line below should be updated with the local +# path where you have saved the downloaded data). + +import numpy as np +coords = np.loadtxt("../_static/demonstration_assets/pasqal/Eiffel_tower_data.dat") +xs = coords[:,0] +ys = coords[:,1] +zs = coords[:,2] + +import matplotlib.pyplot as plt +fig = plt.figure() +ax = fig.add_subplot(111, projection='3d') +ax.view_init(40, 15) +fig.subplots_adjust(left=0, right=1, bottom=0, top=1) +ax.set_xlim(-20, 20) +ax.set_ylim(-20, 20) +ax.set_zlim(-40, 10) +plt.axis('off') +ax.scatter(xs, ys, zs, c='g',alpha=0.3) +plt.show() + +############################################################################## +# This dataset contains 126 points. Each point represents a distinct +# neutral-atom qubit. Simulating this many qubits would be outside the +# reach of Cirq's built-in simulators, so for this demo, +# we will pare down to just 9 points, evenly spaced around the tower. +# These are highlighted in red below. +# + +fig = plt.figure() +ax = fig.add_subplot(111, projection='3d') +ax.view_init(40, 15) +fig.subplots_adjust(left=0, right=1, bottom=0, top=1) +ax.set_xlim(-20, 20) +ax.set_ylim(-20, 20) +ax.set_zlim(-40, 10) +plt.axis('off') +ax.scatter(xs, ys, zs, c='g', alpha=0.3) + +base_mask = [3, 7, 11, 15] +qubit_mask = [48, 51, 60, 63, 96, 97, 98, 99, 125] +input_coords = coords[base_mask] # we'll need this for a plot later +qubit_coords = coords[qubit_mask] + +subset_xs = qubit_coords[:, 0] +subset_ys = qubit_coords[:, 1] +subset_zs = qubit_coords[:, 2] +ax.scatter(subset_xs, subset_ys, subset_zs, c='r', alpha=1.0) +plt.show() + +############################################################################## +# Converting to Cirq qubits +# ------------------------- +# +# Our next step will be to convert these datapoints into objects that +# Cirq understands as qubits. For neutral-atom devices in Cirq, we can use the +# ``ThreeDQubit`` class, which carries information about the three-dimensional +# arrangement of the qubits. +# +# Now, neutral-atom devices come with some physical restrictions. +# Specifically, in a particular three-dimensional configuration, qubits that +# are too distant from one another can't easily interact. Instead, there is +# a notion of a *control radius;* any atoms which are within the system's +# control radius can interact with one another. Qubits separated by a +# distance larger than the control radius cannot interact. +# +# In order to allow our Eiffel tower qubits to interact with +# one another more easily, we will artificially scale some dimensions +# when placing the atoms. + +from cirq_pasqal import ThreeDQubit +xy_scale = 1.5 +z_scale = 0.75 +qubits = [ThreeDQubit(xy_scale * x, xy_scale * y, z_scale * z) + for x, y, z in qubit_coords] + +############################################################################## +# To simulate a neutral-atom quantum computation, we can use the +# ``"cirq.pasqal"`` device, available via the +# `PennyLane-Cirq plugin `_. +# We will need to provide this device with the ``ThreeDQubit`` object that we created +# above. We also need to instantiate the device with a fixed control radius. + +import pennylane as qml + +num_wires = len(qubits) +control_radius = 32.4 +dev = qml.device("cirq.pasqal", control_radius=control_radius, + qubits=qubits, wires=num_wires) + +############################################################################## +# Creating a quantum circuit +# -------------------------- +# +# We will now make a variational circuit out of the Eiffel tower configuration +# from above. Each of the 9 qubits we are using can be thought of +# as a single wire in a quantum circuit. We will cause these qubits to interact by applying +# a sequence of two-qubit gates. Specifically, the circuit consists of several +# stages: +# +# i. Input classical data is converted into quantum information at the first +# (lowest) vertical level of qubits. In this example, our classical data +# will be simple bit strings, which we can embed by using single-qubit +# bit flips (a simple +# `data-embedding `_ +# strategy). +# +# ii. For each corner of the tower, CNOTs are enacted between the first- +# and second-level qubits. +# +# iii. All qubits from the second level interact with a single "peak" qubit +# using a parametrized controlled-rotation operation. The free parameters +# of our variational circuit enter here. +# +# The output of our circuit is determined via a Pauli-Z measurement on +# the final "peak" qubit. +# +# That's a few things to keep track of, so let's show the circuit via a +# three-dimensional image: + +first_lvl_coords = qubit_coords[:4] +second_lvl_coords = qubit_coords[4:8] +peak_coords = qubit_coords[8] + +input_x, input_y, input_z = [input_coords[:, idx] + for idx in range(3)] +second_x, second_y, second_z = [first_lvl_coords[:, idx] + for idx in range(3)] +third_x, third_y, third_z = [second_lvl_coords[:, idx] + for idx in range(3)] +peak_x, peak_y, peak_z = peak_coords + +fig = plt.figure() +ax = fig.add_subplot(111, projection='3d') +ax.view_init(40, 15) +fig.subplots_adjust(left=0, right=1, bottom=0, top=1) +ax.set_xlim(-20, 20) +ax.set_ylim(-20, 20) +ax.set_zlim(-40, 10) +plt.axis('off') + +ax.scatter(xs, ys, zs, c='g', alpha=0.3) +ax.scatter(subset_xs, subset_ys, subset_zs, c='r', alpha=1.0); + +# Two-qubit gates between second and third levels +for corner in range(4): + ax.plot(xs=[second_x[corner], third_x[corner]], + ys=[second_y[corner], third_y[corner]], + zs=[second_z[corner], third_z[corner]], + c='k'); + +# Two-qubit gates between third level and peak +for corner in range(4): + ax.plot(xs=[third_x[corner], peak_x], + ys=[third_y[corner], peak_y], + zs=[third_z[corner], peak_z], + c='k'); + +# Additional lines to guide the eye +for corner in range(4): + ax.plot(xs=[input_x[corner], second_x[corner]], + ys=[input_y[corner], second_y[corner]], + zs=[input_z[corner], second_z[corner]], + c='grey', linestyle='--'); + ax.plot(xs=[second_x[corner], second_x[(corner + 1) % 4]], + ys=[second_y[corner], second_y[(corner + 1) % 4]], + zs=[second_z[corner], second_z[(corner + 1) % 4]], + c='grey', linestyle='--'); + ax.plot(xs=[third_x[corner], third_x[(corner + 1) % 4]], + ys=[third_y[corner], third_y[(corner + 1) % 4]], + zs=[third_z[corner], third_z[(corner + 1) % 4]], + c='grey', linestyle='--'); + +plt.show() + +############################################################################## +# In this figure, the red dots represent the specific qubits we will use in +# our circuit (the green dots are not used in this demo). +# +# The solid black lines indicate two-qubit gates between these qubits. +# The dashed grey lines are meant to guide the eye, but could also be +# used to make a more complex model by adding further two-qubit gates. +# +# Classical data is loaded in at the bottom qubits (the "tower legs") and +# the final measurement result is read out from the top "peak" qubit. +# The order of gate execution proceeds vertically from bottom to top, and +# clockwise at each level. +# +# The code below creates this particular quantum circuit configuration in +# PennyLane: + +peak_qubit = 8 + +def controlled_rotation(phi, wires): + qml.RY(phi, wires=wires[1]) + qml.CNOT(wires=wires) + qml.RY(-phi, wires=wires[1]) + qml.CNOT(wires=wires) + +@qml.qnode(dev, interface="tf") +def circuit(weights, data): + + # Input classical data loaded into qubits at second level + for idx in range(4): + if data[idx]: + qml.PauliX(wires=idx) + + # Interact qubits from second and third levels + for idx in range(4): + qml.CNOT(wires=[idx, idx + 4]) + + # Interact qubits from third level with peak using parameterized gates + for idx, wire in enumerate(range(4, 8)): + controlled_rotation(weights[idx], wires=[wire, peak_qubit]) + + return qml.expval(qml.PauliZ(wires=peak_qubit)) + + +############################################################################## +# Training the circuit +# -------------------- +# +# Let's now leverage this variational circuit to tackle a toy classification +# problem. +# For the purposes of this demo, we will consider a very simple classifier: +# +# * if the first input qubit is in the state :math:`\mid 0 \rangle,` the model +# should make the prediction "0", and +# +# * if the first input qubit is in the state :math:`\mid 1 \rangle,` the model +# should predict "1" (independent of the states of all other qubits). +# +# In other words, the idealized trained model should learn an +# identity transformation between the first qubit and the final one, while +# ignoring the states of all other qubits. +# +# With this goal in mind, we can create a basic cost function. This cost +# function randomly samples possible 4-bit input bitstrings, and compares +# the circuit's output with the value of the first bit. The other bits +# can be thought of as noise that we don't want our model to learn. + + +import tensorflow as tf +np.random.seed(143) +init_weights = np.pi * np.random.rand(4) + +weights = tf.Variable(init_weights, dtype=tf.float64) + +data = np.random.randint(0, 2, size=4) + +def cost(): + data = np.random.randint(0, 2, size=4) + label = data[0] + output = (-circuit(weights, data) + 1) / 2 + return tf.abs(output - label) ** 2 + +opt = tf.keras.optimizers.Adam(learning_rate=0.1) + +for step in range(100): + opt.minimize(cost, [weights]) + if step % 5 == 0: + print("Step {}: cost={}".format(step, cost())) + +print("Final cost value: {}".format(cost())) + +############################################################################## +# Success! The circuit has learned to transfer the state of the first qubit +# to the state of the last qubit, while ignoring the state of all other input +# qubits. +# +# The programmable three-dimensional configurations of neutral-atom quantum +# computers provide a special tool that is hard to replicate in other +# platforms. Could the physical +# arrangement of qubits, in particular the third dimension, be leveraged to +# make quantum algorithms more sparse or efficient? Could neutral-atom +# systems—with their unique programmability of the geometry—allow us to +# rapidly prototype and experiment with new circuit topologies? What +# possibilities could this open up for quantum computing, quantum chemistry, +# or quantum machine learning? +# + +############################################################################## +# References +# ---------- +# +# .. [#barredo2017] +# +# Daniel Barredo, Vincent Lienhard, Sylvain de Leseleuc, Thierry Lahaye, and Antoine Browaeys. +# "Synthetic three-dimensional atomic structures assembled atom by atom." +# `arXiv:1712.02727 +# `__, 2017. +# +# +# About the author +# ---------------- # \ No newline at end of file diff --git a/demonstrations_v2/tutorial_qchem_external/demo.py b/demonstrations_v2/tutorial_qchem_external/demo.py index 36bc3094f3..ae25f561bf 100644 --- a/demonstrations_v2/tutorial_qchem_external/demo.py +++ b/demonstrations_v2/tutorial_qchem_external/demo.py @@ -1,230 +1,230 @@ -r""" - -Using PennyLane with PySCF and OpenFermion -========================================== - -.. meta:: - :property="og:description": Learn how to integrate external quantum chemistry libraries with PennyLane. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_external_libs.png - - -.. related:: - tutorial_quantum_chemistry Quantum chemistry with PennyLane - tutorial_vqe A brief overview of VQE - tutorial_givens_rotations Givens rotations for quantum chemistry - tutorial_adaptive_circuits Adaptive circuits for quantum chemistry - -*Author: Soran Jahangiri — Posted: 3 January 2023.* - -The quantum chemistry module in PennyLane, :mod:`qchem `, provides built-in -methods to compute molecular integrals, solve Hartree-Fock equations, and construct -`fully-differentiable `_ molecular -Hamiltonians. PennyLane also lets you take advantage of various -external resources and libraries to build upon existing tools. In this demo we will show you how -to integrate PennyLane with `PySCF `_ and -`OpenFermion `_ to compute molecular integrals, -construct molecular Hamiltonians, and import initial states. - -Building molecular Hamiltonians -------------------------------- -In PennyLane, Hamiltonians for quantum chemistry are built with the -:func:`~.pennylane.qchem.molecular_hamiltonian` function by specifying a backend for solving the -Hartree–Fock equations. The default backend is the differentiable Hartree–Fock solver of the -:mod:`qchem ` module. A molecular Hamiltonian can also be constructed with -non-differentiable backends that use the electronic structure package -`PySCF `_ or the -`OpenFermion-PySCF `_ plugin. These -backends can be selected by setting the keyword argument ``method='pyscf'`` or -``method='openfermion'`` in :func:`~.pennylane.qchem.molecular_hamiltonian`. This requires -``PySCF`` or ``OpenFermion-PySCF`` to be installed by the user depending on the desired backend: - -.. code-block:: bash - - pip install pyscf # for method='pyscf` - pip install openfermionpyscf # for method='openfermion` - -For example, the molecular Hamiltonian for a water molecule can be constructed with the ``pyscf`` -backend as: -""" - -import pennylane as qml -import numpy as np - -symbols = ["H", "O", "H"] -geometry = np.array([[-0.0399, -0.0038, 0.0000], - [ 1.5780, 0.8540, 0.0000], - [ 2.7909, -0.5159, 0.0000]]) -molecule = qml.qchem.Molecule(symbols, geometry) - -H, qubits = qml.qchem.molecular_hamiltonian(molecule, method="pyscf") -print(H) - -############################################################################## -# This generates a PennyLane :class:`~.pennylane.Hamiltonian` that can be used in a VQE workflow or -# converted to a -# `sparse matrix `_ -# in the computational basis. -# -# Additionally, if you have built your electronic Hamiltonian independently using -# `OpenFermion `_ tools, it can -# be readily converted to a PennyLane observable using the -# :func:`~.pennylane.import_operator` function. Here is an example: - -from openfermion.ops import QubitOperator - -H = 0.1 * QubitOperator('X0 X1') + 0.2 * QubitOperator('Z0') -H = qml.qchem.import_operator(H) - -print(f'Type: \n {type(H)} \n') -print(f'Hamiltonian: \n {H}') - -############################################################################## -# Computing molecular integrals -# ----------------------------- -# In order to build a -# `molecular Hamiltonian `_, we need -# one- and two-electron integrals in the molecular orbital basis. These integrals are used to -# construct a fermionic Hamiltonian which is then mapped onto the qubit basis. These molecular -# integrals can be computed with the -# :func:`~.pennylane.qchem.electron_integrals` function of PennyLane. Alternatively, the integrals -# can be computed with the `PySCF `_ package and used in PennyLane -# workflows such as building a -# `fermionic Hamiltonian `_ or -# quantum `resource estimation `_. -# Let's use water as an example. -# -# First, we define the PySCF molecule object and run a restricted Hartree-Fock -# calculation: - -from pyscf import gto, ao2mo, scf - -mol_pyscf = gto.M(atom = '''H -0.02111417 -0.00201087 0.; - O 0.83504162 0.45191733 0.; - H 1.47688065 -0.27300252 0.''') -rhf = scf.RHF(mol_pyscf) -energy = rhf.kernel() - -############################################################################## -# We obtain the molecular integrals ``one_ao`` and ``two_ao`` in the basis of atomic orbitals -# by following the example `here `_: - -one_ao = mol_pyscf.intor_symmetric('int1e_kin') + mol_pyscf.intor_symmetric('int1e_nuc') -two_ao = mol_pyscf.intor('int2e_sph') - -############################################################################## -# These integrals are then mapped to the basis of molecular orbitals: - -one_mo = np.einsum('pi,pq,qj->ij', rhf.mo_coeff, one_ao, rhf.mo_coeff) -two_mo = ao2mo.incore.full(two_ao, rhf.mo_coeff) - -############################################################################## -# Note that the two-electron integral tensor is represented in -# `chemists' notation `_. To use it -# in PennyLane, we need to convert it into the so-called -# *physicists' notation*: - -two_mo = np.swapaxes(two_mo, 1, 3) - -############################################################################## -# Let's now look at an example where these molecular integrals are used to build the fermionic -# Hamiltonian of water. To do that we also need to compute the nuclear energy contribution: - -core_constant = np.array([rhf.energy_nuc()]) - -############################################################################## -# We now use the integrals to construct a fermionic Hamiltonian with PennyLane's powerful tools -# for creating and manipulating -# `fermionic operators `_: - -H_fermionic = qml.qchem.fermionic_observable(core_constant, one_mo, two_mo) - -############################################################################## -# The Hamiltonian can be mapped to the qubit basis with the :func:`~.pennylane.jordan_wigner` -# function: - -H = qml.jordan_wigner(H_fermionic) - -############################################################################## -# Importing initial states -# ------------------------ -# Simulating molecules with quantum algorithms requires defining an initial state that should have -# non-zero overlap with the molecular ground state. A trivial choice for the initial state is the -# Hartree-Fock state which is obtained by putting the electrons in the lowest-energy molecular -# orbitals. For molecules with a complicated electronic structure, the Hartree-Fock state has -# only a small overlap with the ground state, which makes executing quantum algorithms -# inefficient. -# -# Initial states obtained from affordable post-Hartree-Fock calculations can be used to make the -# quantum workflow more performant. For instance, configuration interaction (CI) and coupled cluster -# (CC) calculations with single and double (SD) excitations can be performed using PySCF and the -# resulting wave function can be used as the initial state in the quantum algorithm. PennyLane -# provides the :func:`~.pennylane.qchem.import_state` function that takes a PySCF solver object, -# extracts the wave function and returns a state vector in the computational basis that can be used -# in a quantum circuit. Let’s look at an example. -# -# First, we run CCSD calculations for the hydrogen molecule to obtain the solver object. - -from pyscf import gto, scf, cc - -mol = gto.M(atom=[['H', (0, 0, 0)], ['H', (0, 0, 0.7)]]) -myhf = scf.RHF(mol).run() -mycc = cc.CCSD(myhf).run() - -############################################################################## -# Then, we use the :func:`~.pennylane.qchem.import_state` function to obtain the -# state vector. - -state = qml.qchem.import_state(mycc) -print(state) - -############################################################################## -# You can verify that this state is a superposition of the Hartree-Fock state and a doubly-excited -# state. -# -# Converting fermionic operators -# ------------------------------ -# Fermionic operators are commonly used to construct observables for molecules and spin systems. -# You can easily convert between fermionic operators created with PennyLane and OpenFermion by using -# the :func:`~.pennylane.from_openfermion` and :func:`~.pennylane.to_openfermion` functions. Let's -# look at some examples. First, we create a fermionic operator with OpenFermion and convert it to a -# PennyLane fermionic operator. - -from openfermion import FermionOperator -openfermion_op = 0.5 * FermionOperator('0^ 2') + FermionOperator('0 2^') -pennylane_op = qml.from_openfermion(openfermion_op) -print(pennylane_op) - -############################################################################## -# The resulting operator can be used in PennyLane like any other fermionic object. We now take this -# PennyLane fermionic operator and convert it back to an OpenFermion operator. - -openfermion_op = qml.to_openfermion(pennylane_op) -print(openfermion_op) - -############################################################################## -# The :func:`~.pennylane.from_openfermion` and :func:`~.pennylane.to_openfermion` functions support -# converting several operator types. You can look at the function documentations for more details -# and examples. - -############################################################################## -# Conclusions -# ----------- -# This tutorial demonstrates how to use PennyLane with external quantum chemistry libraries such as -# `PySCF `_ and -# `OpenFermion `_. -# -# To summarize: -# -# 1. We can construct molecular Hamiltonians in PennyLane by using a user-installed version of PySCF -# by passing the argument ``method=pyscf`` to the :func:`~.pennylane.qchem.molecular_hamiltonian` -# function. -# 2. We can directly use one- and two-electron integrals from PySCF, but we need to convert the -# tensor containing the two-electron integrals from chemists' notation to physicists' notation. -# 3. We can easily convert between OpenFermion operators and PennyLane operators using the -# :func:`~.pennylane.from_openfermion` and :func:`~.pennylane.to_openfermion` functions. -# 4. Finally, we can convert PySCF wave functions to PennyLane state vectors using the -# :func:`~.pennylane.qchem.import_state` function. -# -# About the author -# ---------------- -# +r""" + +Using PennyLane with PySCF and OpenFermion +========================================== + +.. meta:: + :property="og:description": Learn how to integrate external quantum chemistry libraries with PennyLane. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_external_libs.png + + +.. related:: + tutorial_quantum_chemistry Quantum chemistry with PennyLane + tutorial_vqe A brief overview of VQE + tutorial_givens_rotations Givens rotations for quantum chemistry + tutorial_adaptive_circuits Adaptive circuits for quantum chemistry + +*Author: Soran Jahangiri — Posted: 3 January 2023.* + +The quantum chemistry module in PennyLane, :mod:`qchem `, provides built-in +methods to compute molecular integrals, solve Hartree-Fock equations, and construct +`fully-differentiable `_ molecular +Hamiltonians. PennyLane also lets you take advantage of various +external resources and libraries to build upon existing tools. In this demo we will show you how +to integrate PennyLane with `PySCF `_ and +`OpenFermion `_ to compute molecular integrals, +construct molecular Hamiltonians, and import initial states. + +Building molecular Hamiltonians +------------------------------- +In PennyLane, Hamiltonians for quantum chemistry are built with the +:func:`~.pennylane.qchem.molecular_hamiltonian` function by specifying a backend for solving the +Hartree–Fock equations. The default backend is the differentiable Hartree–Fock solver of the +:mod:`qchem ` module. A molecular Hamiltonian can also be constructed with +non-differentiable backends that use the electronic structure package +`PySCF `_ or the +`OpenFermion-PySCF `_ plugin. These +backends can be selected by setting the keyword argument ``method='pyscf'`` or +``method='openfermion'`` in :func:`~.pennylane.qchem.molecular_hamiltonian`. This requires +``PySCF`` or ``OpenFermion-PySCF`` to be installed by the user depending on the desired backend: + +.. code-block:: bash + + pip install pyscf # for method='pyscf` + pip install openfermionpyscf # for method='openfermion` + +For example, the molecular Hamiltonian for a water molecule can be constructed with the ``pyscf`` +backend as: +""" + +import pennylane as qml +import numpy as np + +symbols = ["H", "O", "H"] +geometry = np.array([[-0.0399, -0.0038, 0.0000], + [ 1.5780, 0.8540, 0.0000], + [ 2.7909, -0.5159, 0.0000]]) +molecule = qml.qchem.Molecule(symbols, geometry) + +H, qubits = qml.qchem.molecular_hamiltonian(molecule, method="pyscf") +print(H) + +############################################################################## +# This generates a PennyLane :class:`~.pennylane.Hamiltonian` that can be used in a VQE workflow or +# converted to a +# `sparse matrix `_ +# in the computational basis. +# +# Additionally, if you have built your electronic Hamiltonian independently using +# `OpenFermion `_ tools, it can +# be readily converted to a PennyLane observable using the +# :func:`~.pennylane.import_operator` function. Here is an example: + +from openfermion.ops import QubitOperator + +H = 0.1 * QubitOperator('X0 X1') + 0.2 * QubitOperator('Z0') +H = qml.qchem.import_operator(H) + +print(f'Type: \n {type(H)} \n') +print(f'Hamiltonian: \n {H}') + +############################################################################## +# Computing molecular integrals +# ----------------------------- +# In order to build a +# `molecular Hamiltonian `_, we need +# one- and two-electron integrals in the molecular orbital basis. These integrals are used to +# construct a fermionic Hamiltonian which is then mapped onto the qubit basis. These molecular +# integrals can be computed with the +# :func:`~.pennylane.qchem.electron_integrals` function of PennyLane. Alternatively, the integrals +# can be computed with the `PySCF `_ package and used in PennyLane +# workflows such as building a +# `fermionic Hamiltonian `_ or +# quantum `resource estimation `_. +# Let's use water as an example. +# +# First, we define the PySCF molecule object and run a restricted Hartree-Fock +# calculation: + +from pyscf import gto, ao2mo, scf + +mol_pyscf = gto.M(atom = '''H -0.02111417 -0.00201087 0.; + O 0.83504162 0.45191733 0.; + H 1.47688065 -0.27300252 0.''') +rhf = scf.RHF(mol_pyscf) +energy = rhf.kernel() + +############################################################################## +# We obtain the molecular integrals ``one_ao`` and ``two_ao`` in the basis of atomic orbitals +# by following the example `here `_: + +one_ao = mol_pyscf.intor_symmetric('int1e_kin') + mol_pyscf.intor_symmetric('int1e_nuc') +two_ao = mol_pyscf.intor('int2e_sph') + +############################################################################## +# These integrals are then mapped to the basis of molecular orbitals: + +one_mo = np.einsum('pi,pq,qj->ij', rhf.mo_coeff, one_ao, rhf.mo_coeff) +two_mo = ao2mo.incore.full(two_ao, rhf.mo_coeff) + +############################################################################## +# Note that the two-electron integral tensor is represented in +# `chemists' notation `_. To use it +# in PennyLane, we need to convert it into the so-called +# *physicists' notation*: + +two_mo = np.swapaxes(two_mo, 1, 3) + +############################################################################## +# Let's now look at an example where these molecular integrals are used to build the fermionic +# Hamiltonian of water. To do that we also need to compute the nuclear energy contribution: + +core_constant = np.array([rhf.energy_nuc()]) + +############################################################################## +# We now use the integrals to construct a fermionic Hamiltonian with PennyLane's powerful tools +# for creating and manipulating +# `fermionic operators `_: + +H_fermionic = qml.qchem.fermionic_observable(core_constant, one_mo, two_mo) + +############################################################################## +# The Hamiltonian can be mapped to the qubit basis with the :func:`~.pennylane.jordan_wigner` +# function: + +H = qml.jordan_wigner(H_fermionic) + +############################################################################## +# Importing initial states +# ------------------------ +# Simulating molecules with quantum algorithms requires defining an initial state that should have +# non-zero overlap with the molecular ground state. A trivial choice for the initial state is the +# Hartree-Fock state which is obtained by putting the electrons in the lowest-energy molecular +# orbitals. For molecules with a complicated electronic structure, the Hartree-Fock state has +# only a small overlap with the ground state, which makes executing quantum algorithms +# inefficient. +# +# Initial states obtained from affordable post-Hartree-Fock calculations can be used to make the +# quantum workflow more performant. For instance, configuration interaction (CI) and coupled cluster +# (CC) calculations with single and double (SD) excitations can be performed using PySCF and the +# resulting wave function can be used as the initial state in the quantum algorithm. PennyLane +# provides the :func:`~.pennylane.qchem.import_state` function that takes a PySCF solver object, +# extracts the wave function and returns a state vector in the computational basis that can be used +# in a quantum circuit. Let’s look at an example. +# +# First, we run CCSD calculations for the hydrogen molecule to obtain the solver object. + +from pyscf import gto, scf, cc + +mol = gto.M(atom=[['H', (0, 0, 0)], ['H', (0, 0, 0.7)]]) +myhf = scf.RHF(mol).run() +mycc = cc.CCSD(myhf).run() + +############################################################################## +# Then, we use the :func:`~.pennylane.qchem.import_state` function to obtain the +# state vector. + +state = qml.qchem.import_state(mycc) +print(state) + +############################################################################## +# You can verify that this state is a superposition of the Hartree-Fock state and a doubly-excited +# state. +# +# Converting fermionic operators +# ------------------------------ +# Fermionic operators are commonly used to construct observables for molecules and spin systems. +# You can easily convert between fermionic operators created with PennyLane and OpenFermion by using +# the :func:`~.pennylane.from_openfermion` and :func:`~.pennylane.to_openfermion` functions. Let's +# look at some examples. First, we create a fermionic operator with OpenFermion and convert it to a +# PennyLane fermionic operator. + +from openfermion import FermionOperator +openfermion_op = 0.5 * FermionOperator('0^ 2') + FermionOperator('0 2^') +pennylane_op = qml.from_openfermion(openfermion_op) +print(pennylane_op) + +############################################################################## +# The resulting operator can be used in PennyLane like any other fermionic object. We now take this +# PennyLane fermionic operator and convert it back to an OpenFermion operator. + +openfermion_op = qml.to_openfermion(pennylane_op) +print(openfermion_op) + +############################################################################## +# The :func:`~.pennylane.from_openfermion` and :func:`~.pennylane.to_openfermion` functions support +# converting several operator types. You can look at the function documentations for more details +# and examples. + +############################################################################## +# Conclusions +# ----------- +# This tutorial demonstrates how to use PennyLane with external quantum chemistry libraries such as +# `PySCF `_ and +# `OpenFermion `_. +# +# To summarize: +# +# 1. We can construct molecular Hamiltonians in PennyLane by using a user-installed version of PySCF +# by passing the argument ``method=pyscf`` to the :func:`~.pennylane.qchem.molecular_hamiltonian` +# function. +# 2. We can directly use one- and two-electron integrals from PySCF, but we need to convert the +# tensor containing the two-electron integrals from chemists' notation to physicists' notation. +# 3. We can easily convert between OpenFermion operators and PennyLane operators using the +# :func:`~.pennylane.from_openfermion` and :func:`~.pennylane.to_openfermion` functions. +# 4. Finally, we can convert PySCF wave functions to PennyLane state vectors using the +# :func:`~.pennylane.qchem.import_state` function. +# +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_qft_arithmetics/demo.py b/demonstrations_v2/tutorial_qft_arithmetics/demo.py index 92929830f5..3d67048454 100644 --- a/demonstrations_v2/tutorial_qft_arithmetics/demo.py +++ b/demonstrations_v2/tutorial_qft_arithmetics/demo.py @@ -1,433 +1,433 @@ -r""".. _qft_arithmetics: - -Basic arithmetic with the quantum Fourier transform (QFT) -======================================= - -.. meta:: - :property="og:description": Learn how to use the quantum Fourier transform (QFT) to do basic arithmetic - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qft_arithmetics_thumbnail.png - -.. related:: - tutorial_qubit_rotation Basis tutorial: qubit rotation - - - -*Author: Guillermo Alonso-Linaje — Posted: 07 November 2022.* - -Arithmetic is a fundamental branch of mathematics that consists of the study of the main operations with numbers such as -addition, multiplication, subtraction and division. Using arithmetic operations we can understand the world around us -and solve many of our daily tasks. - -Arithmetic is crucial for the implementation of any kind of algorithm in classical computer science, but also in quantum computing. For this reason, in this demo we are going to show -an approach to defining arithmetic operations on quantum computers. The simplest and most direct way to achieve this goal is to use the -quantum Fourier transform (QFT), which we will demonstrate on a basic level. - -In this demo we will not focus on understanding how the QFT is built, -as we can find a great explanation in the -`PennyLane Codebook `__. Instead, we will develop the -intuition for how it works and how we can best take advantage of it. - -Motivation ----------- - -The first question we have to ask ourselves is whether it makes sense to perform these basic operations on a quantum computer. Is the -goal purely academic or is it really something that is needed in certain algorithms? Why implement in a quantum computer -something that we can do with a calculator? - -When it comes to basic quantum computing algorithms like the Deustch–Jozsa or -Grover's algorithm, we might think that we have never needed to apply any arithmetic operations such as addition or multiplication. -However, the reality is different. When we learn about these algorithms from an academic point of view, -we work with a ready-made operator that we never have to worry about, the *oracle*. -Unfortunately, in real-world situations, we will have to build this seemingly magical operator by hand. -As an example, let's imagine that we want to use Grover's algorithm to search for `magic squares `__. -To define the oracle, which determines whether a solution is valid or not, we must perform sums over the rows and -columns to check that they all have the same value. Therefore, to create this oracle, -we will need to define a sum operator within the quantum computer. - -The second question we face is why we should work with the QFT at all. There are other procedures that could be used to perform these basic operations; for example, by -imitating the classical algorithm. But, as we can see in [#Draper2000]_, it has already been proven that the QFT needs fewer qubits to perform these operations, which is -nowadays of vital importance. - -We will organize the demo as follows. Initially, we will talk about the Fourier basis to give an intuitive idea of how -it works, after which we will address different basic arithmetic operations. Finally, we will move on to a practical example -in which we will factor numbers using Grover's algorithm. - - -QFT representation ------------------ - -To apply the QFT to basic arithmetic operations, our objective now is to learn how to add, -subtract and multiply numbers using quantum devices. As we are working with qubits, -—which, like bits, can take the -values :math:`0` or :math:`1`—we will represent the numbers in binary. For the -purposes of this tutorial, we will assume that we are working only with -integers. Therefore, if we have :math:`n` qubits, we will be able to -represent the numbers from :math:`0` to :math:`2^n-1.` - -The first thing we need to know is PennyLane's -standard for encoding numbers in a binary format. A binary number can be -represented as a string of 1s and 0s, which we can represent as the multi-qubit state - -.. math:: \vert m \rangle = \vert \overline{q_0q_1...q_{n-1}}\rangle, - -where the formula to obtain the equivalent decimal number :math:`m` will be: - -.. math:: m= \sum_{i = 0}^{n-1}2^{n-1-i}q_i. - -Note that :math:`\vert m \rangle` refers to the basic state -generated by the binary encoding of the number :math:`m.` -For instance, the natural number :math:`6` -is represented by the quantum state :math:`\vert 110\rangle,` since :math:`\vert 110 \rangle = 1 \cdot 2^2 + 1\cdot 2^1+0\cdot 2^0 = 6.` - -Let’s see how we would represent all the integers from :math:`0` to :math:`7` using the product state of three qubits, visualized by separate Bloch spheres for each qubit. - -.. figure:: /_static/demonstration_assets/qft_arithmetics/3_qubits_computational_basis.gif - :width: 90% - :align: center - - Representation of integers using a computational basis of three qubits. - -.. note:: - - The `Bloch sphere `_ - is a way of graphically representing the state of a qubit. - At the top of the sphere we place the state :math:`\vert 0 \rangle,` at the bottom - :math:`\vert 1 \rangle,` and in the rest of the - sphere we will place the possible states in superposition. It is a very useful - representation that helps better visualize and interpret quantum gates such as rotations. - -We can use -the :class:`qml.BasisEmbedding ` -template to obtain the binary representation in a simple way. -Let's see how we would code the number :math:`6.` -""" - -import pennylane as qml -import matplotlib.pyplot as plt - -dev = qml.device("default.qubit", wires=3) - -@qml.compile -@qml.qnode(dev) -def basis_embedding_circuit(m): - qml.BasisEmbedding(m, wires=range(3)) - return qml.state() - -m = 6 # number to be encoded - -qml.draw_mpl(basis_embedding_circuit, show_all_wires=True)(m) -plt.show() - -###################################################################### -# -# As we can see, the first qubit—the :math:`0`-th wire—is placed on top and the rest of the qubits are -# below it. However, this is not the only way we could represent numbers. -# We can also represent them in different bases, such as the so-called *Fourier base*. -# -# In this case, all the states of the basis will be represented via qubits in -# the XY-plane of the Bloch sphere, each rotated by a certain -# amount. -# -# -# How do we know how much we must rotate each qubit to represent a certain number? -# It is actually very easy! Suppose we are working with -# :math:`n` qubits and we want to represent the number :math:`m` in the -# Fourier basis. Then the :math:`j`-th qubit will have the phase: -# -# .. math:: \alpha_j = \frac{2m\pi}{2^{j}}. -# -# Now we can represent numbers in the Fourier basis using three qubits: -# -# .. figure:: /_static/demonstration_assets/qft_arithmetics/3_qubits_fourier_basis.gif -# :width: 90% -# :align: center -# -# Representation of integers using the Fourier basis with three qubits -# -# As we can see, the third qubit will rotate -# :math:`\frac{1}{8}` of a turn counterclockwise with each number. The next qubit -# rotates :math:`\frac{1}{4}` of a full turn and, finally, the first qubit rotates -# half a turn for each increase in number. -# -# Adding a number to a register -# ------------------------------ -# -# The fact that the states encoding the numbers are now in phase gives us great -# flexibility in carrying out our arithmetic operations. To see this in practice, -# let’s look at the situation in which want to create an operator Sum -# such that: -# -# .. math:: \text{Sum(k)}\vert m \rangle = \vert m + k \rangle. -# -# The procedure to implement this unitary operation is the following: -# -# #. We convert the state from the computational basis into the Fourier basis by applying the QFT to the :math:`\vert m \rangle` state via the :class:`~pennylane.QFT` operator. -# -# #. We rotate the :math:`j`-th qubit by the angle :math:`\frac{2k\pi}{2^{j}}` using the :math:`R_Z` gate, which leads to the new phases, :math:`\frac{2(m + k)\pi}{2^{j}}.` -# -# #. We apply the QFT inverse to return to the computational basis and obtain :math:`m+k.` -# -# -# Let's see how this process would look in PennyLane. -# - -import pennylane as qml -import numpy as np - -n_wires = 4 -dev = qml.device("default.qubit", wires=n_wires, shots=1) - -def add_k_fourier(k, wires): - for j in range(len(wires)): - qml.RZ(k * np.pi / (2**j), wires=wires[j]) - -@qml.qnode(dev) -def sum(m, k): - qml.BasisEmbedding(m, wires=range(n_wires)) # m encoding - - qml.QFT(wires=range(n_wires)) # step 1 - - add_k_fourier(k, range(n_wires)) # step 2 - - qml.adjoint(qml.QFT)(wires=range(n_wires)) # step 3 - - return qml.sample() - - -print(f"The ket representation of the sum of 3 and 4 is {sum(3,4)}") - -###################################################################### -# Perfect, we have obtained :math:`\vert 0111 \rangle,` which is equivalent to the number :math:`7` in binary! -# -# Note that this is a deterministic algorithm, which means that we have obtained the desired solution by executing a single shot. -# On the other hand, if the result of an operation is greater than the maximum -# value :math:`2^n-1,` we will start again from zero, that is to say, we -# will calculate the sum modulo :math:`2^n-1.` For instance, in our three-qubit example, suppose that -# we want to calculate :math:`6+3.` We see that we do not have -# enough memory space, as :math:`6+3 = 9 > 2^3-1.` The result we will get will -# be :math:`9 \pmod 8 = 1,` or :math:`\vert 001 \rangle` in binary. Make sure to use -# enough qubits to represent your solutions! -# Finally, it is important to point out that it is not necessary to know how the -# QFT is constructed in order to use it. By knowing the properties of the -# new basis, we can use it in a simple way. -# -# Adding two different registers -# ------------------------------ -# -# In this previous algorithm, we had to pass to the operator the value of :math:`k` that we wanted to add to the starting state. -# But at other times, instead of passing that value to the operator, we would like it to pull the information from another register. -# That is, we are looking for a new operator :math:`\text{Sum}_2` such that -# -# .. math:: \text{Sum}_2\vert m \rangle \vert k \rangle \vert 0 \rangle = \vert m \rangle \vert k \rangle \vert m+k \rangle. -# -# In this case, we can understand the third register (which is initially -# at :math:`0`) as a counter that will tally as many units as :math:`m` and -# :math:`k` combined. The binary decomposition will -# make this simple. If we have :math:`\vert m \rangle = \vert \overline{q_0q_1q_2} \rangle,` we will -# have to add :math:`1` to the counter if :math:`q_2 = 1` and nothing -# otherwise. In general, we should add :math:`2^{n-i-1}` units if the :math:`i`-th -# qubit is in state :math:`\vert 1 \rangle` and 0 otherwise. As we can see, this is the same idea that is also -# behind the concept of a controlled gate. Indeed, observe that we will, indeed, apply a corresponding -# phase if indeed the control qubit is in the state :math:`\vert 1\rangle.` -# Let us now code the :math:`\text{Sum}_2` operator. - -wires_m = [0, 1, 2] # qubits needed to encode m -wires_k = [3, 4, 5] # qubits needed to encode k -wires_solution = [6, 7, 8, 9] # qubits needed to encode the solution - -dev = qml.device("default.qubit", wires=wires_m + wires_k + wires_solution, shots=1) - -n_wires = len(dev.wires) # total number of qubits used - -def addition(wires_m, wires_k, wires_solution): - # prepare solution qubits to counting - qml.QFT(wires=wires_solution) - - # add m to the counter - for i in range(len(wires_m)): - qml.ctrl(add_k_fourier, control=wires_m[i])(2 **(len(wires_m) - i - 1), wires_solution) - - # add k to the counter - for i in range(len(wires_k)): - qml.ctrl(add_k_fourier, control=wires_k[i])(2 **(len(wires_k) - i - 1), wires_solution) - - # return to computational basis - qml.adjoint(qml.QFT)(wires=wires_solution) - -@qml.qnode(dev) -def sum2(m, k, wires_m, wires_k, wires_solution): - # m and k codification - qml.BasisEmbedding(m, wires=wires_m) - qml.BasisEmbedding(k, wires=wires_k) - - # apply the addition circuit - addition(wires_m, wires_k, wires_solution) - - return qml.sample(wires=wires_solution) - -print(f"The ket representation of the sum of 7 and 3 is " - f"{sum2(7, 3, wires_m, wires_k, wires_solution)}") - -qml.draw_mpl(sum2, show_all_wires=True)(7, 3, wires_m, wires_k, wires_solution) -plt.show() - -###################################################################### -# Great! We have just seen how to add a number to a counter. In the example above, -# we added :math:`3 + 7` to get :math:`10,` which in binary -# is :math:`\vert 1010 \rangle.` -# -# Multiplying qubits -# ------------------- -# -# Following the same idea, we will see how easily we can -# implement multiplication. For this purpose we'll take two arbitrary numbers :math:`m` and :math:`k` -# to carry out the operation. This time, we look for an operator Mul such that -# -# .. math:: \text{Mul}\vert m \rangle \vert k \rangle \vert 0 \rangle = \vert m \rangle \vert k \rangle \vert m\cdot k \rangle. -# -# To understand the multiplication process, let's work with the binary decomposition of -# :math:`k:=\sum_{i=0}^{n-1}2^{n-i-1}k_i` and -# :math:`m:=\sum_{j=0}^{l-1}2^{l-j-1}m_i.` In this case, the product would -# be: -# -# .. math:: k \cdot m = \sum_{i=0}^{n-1}\sum_{j = 0}^{l-1}m_ik_i (2^{n-i-1} \cdot 2^{l-j-1}). -# -# In other words, if :math:`k_i = 1` and :math:`m_i = 1,` we would add -# :math:`2^{n-i-1} \cdot 2^{l-j-1}` units to the counter, where :math:`n` and :math:`l` -# are the number of qubits with which we encode :math:`m` and :math:`k` respectively. -# Let's code to see how it works! - -wires_m = [0, 1, 2] # qubits needed to encode m -wires_k = [3, 4, 5] # qubits needed to encode k -wires_solution = [6, 7, 8, 9, 10] # qubits needed to encode the solution - -dev = qml.device("default.qubit", wires=wires_m + wires_k + wires_solution, shots=1) - -n_wires = len(dev.wires) - -def multiplication(wires_m, wires_k, wires_solution): - # prepare sol-qubits to counting - qml.QFT(wires=wires_solution) - - # add m to the counter - for i in range(len(wires_k)): - for j in range(len(wires_m)): - coeff = 2 ** (len(wires_m) + len(wires_k) - i - j - 2) - qml.ctrl(add_k_fourier, control=[wires_k[i], wires_m[j]])(coeff, wires_solution) - - # return to computational basis - qml.adjoint(qml.QFT)(wires=wires_solution) - -@qml.qnode(dev) -def mul(m, k): - # m and k codification - qml.BasisEmbedding(m, wires=wires_m) - qml.BasisEmbedding(k, wires=wires_k) - - # Apply multiplication - multiplication(wires_m, wires_k, wires_solution) - - return qml.sample(wires=wires_solution) - - -print(f"The ket representation of the multiplication of 3 and 7 is {mul(3,7)}") - -qml.draw_mpl(mul, show_all_wires=True)(3, 7) -plt.show() - - -###################################################################### -# Awesome! We have multiplied :math:`7 \cdot 3` and, as a result, we have -# :math:`\vert 10101 \rangle,` which is :math:`21` in binary. -# -# -# Factorization with Grover -# ------------------------- -# -# With this, we have already gained a large repertoire of interesting -# operations that we can do, but we can give the idea one more twist and -# apply what we have learned in an example. -# -# Let’s imagine now that we want just the opposite: to factor the -# number :math:`21` as a product of two terms. Is this something we could do -# using our previous reasoning? The answer is yes! We can make use of -# `Grover's algorithm `_ to -# amplify the states whose product is the number we -# are looking for. All we would need is to construct the oracle :math:`U,` i.e., an -# operator such that -# -# .. math:: U\vert m \rangle \vert k \rangle = \vert m \rangle \vert k \rangle \text{ if }m\cdot k \not = 21, -# -# .. math:: U\vert m \rangle \vert k \rangle = -\vert m \rangle \vert k \rangle \text{ if }m\cdot k = 21 -# -# The idea of the oracle is as simple as this: -# -# #. use auxiliary registers to store the product, -# #. check if the product state is :math:`\vert 10101 \rangle` and, in that case, change the sign, -# #. execute the inverse of the circuit to clear the auxiliary qubits. -# #. calculate the probabilities and see which states have been amplified. -# -# Let's go back to PennyLane to implement this idea. - -n = 21 # number we want to factor - -wires_m = [0, 1, 2] # qubits needed to encode m -wires_k = [3, 4, 5] # qubits needed to encode k -wires_solution = [6, 7, 8, 9, 10] # qubits needed to encode the solution - -dev = qml.device("default.qubit", wires=wires_m + wires_k + wires_solution) - -n_wires = len(dev.wires) - -@qml.qnode(dev) -def factorization(n, wires_m, wires_k, wires_solution): - # Superposition of the input - for wire in wires_m: - qml.Hadamard(wires=wire) - - for wire in wires_k: - qml.Hadamard(wires=wire) - - # Apply the multiplication - multiplication(wires_m, wires_k, wires_solution) - - # Change sign of n - qml.FlipSign(n, wires=wires_solution) - - # Uncompute multiplication - qml.adjoint(multiplication)(wires_m, wires_k, wires_solution) - - # Apply Grover operator - qml.GroverOperator(wires=wires_m + wires_k) - - return qml.probs(wires=wires_m) - - -plt.bar(range(2 ** len(wires_m)), factorization(n, wires_m, wires_k, wires_solution)) -plt.xlabel("Basic states") -plt.ylabel("Probability") -plt.show() - -###################################################################### -# By plotting the probabilities of obtaining each basic state we see that -# prime factors have been amplified! Factorization via Grover’s algorithm -# does not achieve exponential improvement that -# `Shor's algorithm `_ does, but we -# can see that this construction is simple and a great example to -# illustrate basic arithmetic! -# -# I hope we can now all see that oracles are not something magical and that there -# is a lot of work behind their construction! This will help us in the future to build -# more complicated operators, but until then, let’s keep on learning. 🚀 -# -# References -# ---------- -# -# .. [#Draper2000] -# -# Thomas G. Draper, "Addition on a Quantum Computer". `arXiv:quant-ph/0008033 `__. -# -# -# About the author -# ---------------- -# +r""".. _qft_arithmetics: + +Basic arithmetic with the quantum Fourier transform (QFT) +======================================= + +.. meta:: + :property="og:description": Learn how to use the quantum Fourier transform (QFT) to do basic arithmetic + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qft_arithmetics_thumbnail.png + +.. related:: + tutorial_qubit_rotation Basis tutorial: qubit rotation + + + +*Author: Guillermo Alonso-Linaje — Posted: 07 November 2022.* + +Arithmetic is a fundamental branch of mathematics that consists of the study of the main operations with numbers such as +addition, multiplication, subtraction and division. Using arithmetic operations we can understand the world around us +and solve many of our daily tasks. + +Arithmetic is crucial for the implementation of any kind of algorithm in classical computer science, but also in quantum computing. For this reason, in this demo we are going to show +an approach to defining arithmetic operations on quantum computers. The simplest and most direct way to achieve this goal is to use the +quantum Fourier transform (QFT), which we will demonstrate on a basic level. + +In this demo we will not focus on understanding how the QFT is built, +as we can find a great explanation in the +`PennyLane Codebook `__. Instead, we will develop the +intuition for how it works and how we can best take advantage of it. + +Motivation +---------- + +The first question we have to ask ourselves is whether it makes sense to perform these basic operations on a quantum computer. Is the +goal purely academic or is it really something that is needed in certain algorithms? Why implement in a quantum computer +something that we can do with a calculator? + +When it comes to basic quantum computing algorithms like the Deustch–Jozsa or +Grover's algorithm, we might think that we have never needed to apply any arithmetic operations such as addition or multiplication. +However, the reality is different. When we learn about these algorithms from an academic point of view, +we work with a ready-made operator that we never have to worry about, the *oracle*. +Unfortunately, in real-world situations, we will have to build this seemingly magical operator by hand. +As an example, let's imagine that we want to use Grover's algorithm to search for `magic squares `__. +To define the oracle, which determines whether a solution is valid or not, we must perform sums over the rows and +columns to check that they all have the same value. Therefore, to create this oracle, +we will need to define a sum operator within the quantum computer. + +The second question we face is why we should work with the QFT at all. There are other procedures that could be used to perform these basic operations; for example, by +imitating the classical algorithm. But, as we can see in [#Draper2000]_, it has already been proven that the QFT needs fewer qubits to perform these operations, which is +nowadays of vital importance. + +We will organize the demo as follows. Initially, we will talk about the Fourier basis to give an intuitive idea of how +it works, after which we will address different basic arithmetic operations. Finally, we will move on to a practical example +in which we will factor numbers using Grover's algorithm. + + +QFT representation +----------------- + +To apply the QFT to basic arithmetic operations, our objective now is to learn how to add, +subtract and multiply numbers using quantum devices. As we are working with qubits, +—which, like bits, can take the +values :math:`0` or :math:`1`—we will represent the numbers in binary. For the +purposes of this tutorial, we will assume that we are working only with +integers. Therefore, if we have :math:`n` qubits, we will be able to +represent the numbers from :math:`0` to :math:`2^n-1.` + +The first thing we need to know is PennyLane's +standard for encoding numbers in a binary format. A binary number can be +represented as a string of 1s and 0s, which we can represent as the multi-qubit state + +.. math:: \vert m \rangle = \vert \overline{q_0q_1...q_{n-1}}\rangle, + +where the formula to obtain the equivalent decimal number :math:`m` will be: + +.. math:: m= \sum_{i = 0}^{n-1}2^{n-1-i}q_i. + +Note that :math:`\vert m \rangle` refers to the basic state +generated by the binary encoding of the number :math:`m.` +For instance, the natural number :math:`6` +is represented by the quantum state :math:`\vert 110\rangle,` since :math:`\vert 110 \rangle = 1 \cdot 2^2 + 1\cdot 2^1+0\cdot 2^0 = 6.` + +Let’s see how we would represent all the integers from :math:`0` to :math:`7` using the product state of three qubits, visualized by separate Bloch spheres for each qubit. + +.. figure:: /_static/demonstration_assets/qft_arithmetics/3_qubits_computational_basis.gif + :width: 90% + :align: center + + Representation of integers using a computational basis of three qubits. + +.. note:: + + The `Bloch sphere `_ + is a way of graphically representing the state of a qubit. + At the top of the sphere we place the state :math:`\vert 0 \rangle,` at the bottom + :math:`\vert 1 \rangle,` and in the rest of the + sphere we will place the possible states in superposition. It is a very useful + representation that helps better visualize and interpret quantum gates such as rotations. + +We can use +the :class:`qml.BasisEmbedding ` +template to obtain the binary representation in a simple way. +Let's see how we would code the number :math:`6.` +""" + +import pennylane as qml +import matplotlib.pyplot as plt + +dev = qml.device("default.qubit", wires=3) + +@qml.compile +@qml.qnode(dev) +def basis_embedding_circuit(m): + qml.BasisEmbedding(m, wires=range(3)) + return qml.state() + +m = 6 # number to be encoded + +qml.draw_mpl(basis_embedding_circuit, show_all_wires=True)(m) +plt.show() + +###################################################################### +# +# As we can see, the first qubit—the :math:`0`-th wire—is placed on top and the rest of the qubits are +# below it. However, this is not the only way we could represent numbers. +# We can also represent them in different bases, such as the so-called *Fourier base*. +# +# In this case, all the states of the basis will be represented via qubits in +# the XY-plane of the Bloch sphere, each rotated by a certain +# amount. +# +# +# How do we know how much we must rotate each qubit to represent a certain number? +# It is actually very easy! Suppose we are working with +# :math:`n` qubits and we want to represent the number :math:`m` in the +# Fourier basis. Then the :math:`j`-th qubit will have the phase: +# +# .. math:: \alpha_j = \frac{2m\pi}{2^{j}}. +# +# Now we can represent numbers in the Fourier basis using three qubits: +# +# .. figure:: /_static/demonstration_assets/qft_arithmetics/3_qubits_fourier_basis.gif +# :width: 90% +# :align: center +# +# Representation of integers using the Fourier basis with three qubits +# +# As we can see, the third qubit will rotate +# :math:`\frac{1}{8}` of a turn counterclockwise with each number. The next qubit +# rotates :math:`\frac{1}{4}` of a full turn and, finally, the first qubit rotates +# half a turn for each increase in number. +# +# Adding a number to a register +# ------------------------------ +# +# The fact that the states encoding the numbers are now in phase gives us great +# flexibility in carrying out our arithmetic operations. To see this in practice, +# let’s look at the situation in which want to create an operator Sum +# such that: +# +# .. math:: \text{Sum(k)}\vert m \rangle = \vert m + k \rangle. +# +# The procedure to implement this unitary operation is the following: +# +# #. We convert the state from the computational basis into the Fourier basis by applying the QFT to the :math:`\vert m \rangle` state via the :class:`~pennylane.QFT` operator. +# +# #. We rotate the :math:`j`-th qubit by the angle :math:`\frac{2k\pi}{2^{j}}` using the :math:`R_Z` gate, which leads to the new phases, :math:`\frac{2(m + k)\pi}{2^{j}}.` +# +# #. We apply the QFT inverse to return to the computational basis and obtain :math:`m+k.` +# +# +# Let's see how this process would look in PennyLane. +# + +import pennylane as qml +import numpy as np + +n_wires = 4 +dev = qml.device("default.qubit", wires=n_wires, shots=1) + +def add_k_fourier(k, wires): + for j in range(len(wires)): + qml.RZ(k * np.pi / (2**j), wires=wires[j]) + +@qml.qnode(dev) +def sum(m, k): + qml.BasisEmbedding(m, wires=range(n_wires)) # m encoding + + qml.QFT(wires=range(n_wires)) # step 1 + + add_k_fourier(k, range(n_wires)) # step 2 + + qml.adjoint(qml.QFT)(wires=range(n_wires)) # step 3 + + return qml.sample() + + +print(f"The ket representation of the sum of 3 and 4 is {sum(3,4)}") + +###################################################################### +# Perfect, we have obtained :math:`\vert 0111 \rangle,` which is equivalent to the number :math:`7` in binary! +# +# Note that this is a deterministic algorithm, which means that we have obtained the desired solution by executing a single shot. +# On the other hand, if the result of an operation is greater than the maximum +# value :math:`2^n-1,` we will start again from zero, that is to say, we +# will calculate the sum modulo :math:`2^n-1.` For instance, in our three-qubit example, suppose that +# we want to calculate :math:`6+3.` We see that we do not have +# enough memory space, as :math:`6+3 = 9 > 2^3-1.` The result we will get will +# be :math:`9 \pmod 8 = 1,` or :math:`\vert 001 \rangle` in binary. Make sure to use +# enough qubits to represent your solutions! +# Finally, it is important to point out that it is not necessary to know how the +# QFT is constructed in order to use it. By knowing the properties of the +# new basis, we can use it in a simple way. +# +# Adding two different registers +# ------------------------------ +# +# In this previous algorithm, we had to pass to the operator the value of :math:`k` that we wanted to add to the starting state. +# But at other times, instead of passing that value to the operator, we would like it to pull the information from another register. +# That is, we are looking for a new operator :math:`\text{Sum}_2` such that +# +# .. math:: \text{Sum}_2\vert m \rangle \vert k \rangle \vert 0 \rangle = \vert m \rangle \vert k \rangle \vert m+k \rangle. +# +# In this case, we can understand the third register (which is initially +# at :math:`0`) as a counter that will tally as many units as :math:`m` and +# :math:`k` combined. The binary decomposition will +# make this simple. If we have :math:`\vert m \rangle = \vert \overline{q_0q_1q_2} \rangle,` we will +# have to add :math:`1` to the counter if :math:`q_2 = 1` and nothing +# otherwise. In general, we should add :math:`2^{n-i-1}` units if the :math:`i`-th +# qubit is in state :math:`\vert 1 \rangle` and 0 otherwise. As we can see, this is the same idea that is also +# behind the concept of a controlled gate. Indeed, observe that we will, indeed, apply a corresponding +# phase if indeed the control qubit is in the state :math:`\vert 1\rangle.` +# Let us now code the :math:`\text{Sum}_2` operator. + +wires_m = [0, 1, 2] # qubits needed to encode m +wires_k = [3, 4, 5] # qubits needed to encode k +wires_solution = [6, 7, 8, 9] # qubits needed to encode the solution + +dev = qml.device("default.qubit", wires=wires_m + wires_k + wires_solution, shots=1) + +n_wires = len(dev.wires) # total number of qubits used + +def addition(wires_m, wires_k, wires_solution): + # prepare solution qubits to counting + qml.QFT(wires=wires_solution) + + # add m to the counter + for i in range(len(wires_m)): + qml.ctrl(add_k_fourier, control=wires_m[i])(2 **(len(wires_m) - i - 1), wires_solution) + + # add k to the counter + for i in range(len(wires_k)): + qml.ctrl(add_k_fourier, control=wires_k[i])(2 **(len(wires_k) - i - 1), wires_solution) + + # return to computational basis + qml.adjoint(qml.QFT)(wires=wires_solution) + +@qml.qnode(dev) +def sum2(m, k, wires_m, wires_k, wires_solution): + # m and k codification + qml.BasisEmbedding(m, wires=wires_m) + qml.BasisEmbedding(k, wires=wires_k) + + # apply the addition circuit + addition(wires_m, wires_k, wires_solution) + + return qml.sample(wires=wires_solution) + +print(f"The ket representation of the sum of 7 and 3 is " + f"{sum2(7, 3, wires_m, wires_k, wires_solution)}") + +qml.draw_mpl(sum2, show_all_wires=True)(7, 3, wires_m, wires_k, wires_solution) +plt.show() + +###################################################################### +# Great! We have just seen how to add a number to a counter. In the example above, +# we added :math:`3 + 7` to get :math:`10,` which in binary +# is :math:`\vert 1010 \rangle.` +# +# Multiplying qubits +# ------------------- +# +# Following the same idea, we will see how easily we can +# implement multiplication. For this purpose we'll take two arbitrary numbers :math:`m` and :math:`k` +# to carry out the operation. This time, we look for an operator Mul such that +# +# .. math:: \text{Mul}\vert m \rangle \vert k \rangle \vert 0 \rangle = \vert m \rangle \vert k \rangle \vert m\cdot k \rangle. +# +# To understand the multiplication process, let's work with the binary decomposition of +# :math:`k:=\sum_{i=0}^{n-1}2^{n-i-1}k_i` and +# :math:`m:=\sum_{j=0}^{l-1}2^{l-j-1}m_i.` In this case, the product would +# be: +# +# .. math:: k \cdot m = \sum_{i=0}^{n-1}\sum_{j = 0}^{l-1}m_ik_i (2^{n-i-1} \cdot 2^{l-j-1}). +# +# In other words, if :math:`k_i = 1` and :math:`m_i = 1,` we would add +# :math:`2^{n-i-1} \cdot 2^{l-j-1}` units to the counter, where :math:`n` and :math:`l` +# are the number of qubits with which we encode :math:`m` and :math:`k` respectively. +# Let's code to see how it works! + +wires_m = [0, 1, 2] # qubits needed to encode m +wires_k = [3, 4, 5] # qubits needed to encode k +wires_solution = [6, 7, 8, 9, 10] # qubits needed to encode the solution + +dev = qml.device("default.qubit", wires=wires_m + wires_k + wires_solution, shots=1) + +n_wires = len(dev.wires) + +def multiplication(wires_m, wires_k, wires_solution): + # prepare sol-qubits to counting + qml.QFT(wires=wires_solution) + + # add m to the counter + for i in range(len(wires_k)): + for j in range(len(wires_m)): + coeff = 2 ** (len(wires_m) + len(wires_k) - i - j - 2) + qml.ctrl(add_k_fourier, control=[wires_k[i], wires_m[j]])(coeff, wires_solution) + + # return to computational basis + qml.adjoint(qml.QFT)(wires=wires_solution) + +@qml.qnode(dev) +def mul(m, k): + # m and k codification + qml.BasisEmbedding(m, wires=wires_m) + qml.BasisEmbedding(k, wires=wires_k) + + # Apply multiplication + multiplication(wires_m, wires_k, wires_solution) + + return qml.sample(wires=wires_solution) + + +print(f"The ket representation of the multiplication of 3 and 7 is {mul(3,7)}") + +qml.draw_mpl(mul, show_all_wires=True)(3, 7) +plt.show() + + +###################################################################### +# Awesome! We have multiplied :math:`7 \cdot 3` and, as a result, we have +# :math:`\vert 10101 \rangle,` which is :math:`21` in binary. +# +# +# Factorization with Grover +# ------------------------- +# +# With this, we have already gained a large repertoire of interesting +# operations that we can do, but we can give the idea one more twist and +# apply what we have learned in an example. +# +# Let’s imagine now that we want just the opposite: to factor the +# number :math:`21` as a product of two terms. Is this something we could do +# using our previous reasoning? The answer is yes! We can make use of +# `Grover's algorithm `_ to +# amplify the states whose product is the number we +# are looking for. All we would need is to construct the oracle :math:`U,` i.e., an +# operator such that +# +# .. math:: U\vert m \rangle \vert k \rangle = \vert m \rangle \vert k \rangle \text{ if }m\cdot k \not = 21, +# +# .. math:: U\vert m \rangle \vert k \rangle = -\vert m \rangle \vert k \rangle \text{ if }m\cdot k = 21 +# +# The idea of the oracle is as simple as this: +# +# #. use auxiliary registers to store the product, +# #. check if the product state is :math:`\vert 10101 \rangle` and, in that case, change the sign, +# #. execute the inverse of the circuit to clear the auxiliary qubits. +# #. calculate the probabilities and see which states have been amplified. +# +# Let's go back to PennyLane to implement this idea. + +n = 21 # number we want to factor + +wires_m = [0, 1, 2] # qubits needed to encode m +wires_k = [3, 4, 5] # qubits needed to encode k +wires_solution = [6, 7, 8, 9, 10] # qubits needed to encode the solution + +dev = qml.device("default.qubit", wires=wires_m + wires_k + wires_solution) + +n_wires = len(dev.wires) + +@qml.qnode(dev) +def factorization(n, wires_m, wires_k, wires_solution): + # Superposition of the input + for wire in wires_m: + qml.Hadamard(wires=wire) + + for wire in wires_k: + qml.Hadamard(wires=wire) + + # Apply the multiplication + multiplication(wires_m, wires_k, wires_solution) + + # Change sign of n + qml.FlipSign(n, wires=wires_solution) + + # Uncompute multiplication + qml.adjoint(multiplication)(wires_m, wires_k, wires_solution) + + # Apply Grover operator + qml.GroverOperator(wires=wires_m + wires_k) + + return qml.probs(wires=wires_m) + + +plt.bar(range(2 ** len(wires_m)), factorization(n, wires_m, wires_k, wires_solution)) +plt.xlabel("Basic states") +plt.ylabel("Probability") +plt.show() + +###################################################################### +# By plotting the probabilities of obtaining each basic state we see that +# prime factors have been amplified! Factorization via Grover’s algorithm +# does not achieve exponential improvement that +# `Shor's algorithm `_ does, but we +# can see that this construction is simple and a great example to +# illustrate basic arithmetic! +# +# I hope we can now all see that oracles are not something magical and that there +# is a lot of work behind their construction! This will help us in the future to build +# more complicated operators, but until then, let’s keep on learning. 🚀 +# +# References +# ---------- +# +# .. [#Draper2000] +# +# Thomas G. Draper, "Addition on a Quantum Computer". `arXiv:quant-ph/0008033 `__. +# +# +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_quantum_chemistry/demo.py b/demonstrations_v2/tutorial_quantum_chemistry/demo.py index 6925c9fe1b..b015fb33b7 100644 --- a/demonstrations_v2/tutorial_quantum_chemistry/demo.py +++ b/demonstrations_v2/tutorial_quantum_chemistry/demo.py @@ -1,331 +1,331 @@ -r""" -Building molecular Hamiltonians -=============================== - - -.. meta:: - :property="og:description": Learn how to build electronic Hamiltonians of molecules. - - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/water_structure.png - -.. related:: - tutorial_vqe A brief overview of VQE - -*Author: Alain Delgado — Posted: 02 August 2020. Last updated: 29 August 2023.* - -.. note:: - - A wide variety of molecular data, including Hamiltonians, is - available on the `PennyLane Datasets service `__. - -The ultimate goal of computational quantum chemistry is to unravel the -quantum effects that determine the structure and properties of molecules. Reaching -this goal is challenging since the characteristic energies associated with -these effects, e.g., the electronic correlation energy, are typically a tiny fraction -of the total energy of the molecule. - -Accurate molecular properties can be computed from the wave function describing the -interacting electrons in a molecule. The **electronic** wave function -:math:`\Psi(r)` satisfies the `Schrödinger equation -`_ - -.. math:: - H_e \Psi(r) = E \Psi(r), - -where :math:`H_e` and :math:`E` denote the electronic Hamiltonian and the -total energy of the molecule, respectively. When solving the latter equation, -the nuclei of the molecule can be treated as point particles whose coordinates -are fixed [#BornOpp1927]_. In this approximation, both the total energy and -the electronic Hamiltonian depend parametrically on the nuclear coordinates. - - -In this tutorial, you will learn how to use PennyLane to build a -representation of the electronic Hamiltonian :math:`H_e` that can be used to perform -**quantum** simulations of molecules [#yudong2019]_. First, we show how to define -the structure of the molecule in terms of the symbols and the coordinates of -the atoms. Next, we describe how to solve the `Hartree-Fock -equations `_ for the target -molecule. Finally, we discuss some advanced features that can be used to simulate -more complicated systems. - -Let's get started! - -Defining the molecular structure --------------------------------- -In this example we construct the electronic Hamiltonian of the water molecule. - - -.. figure:: ../_static/demonstration_assets/quantum_chemistry/water_structure.png - :width: 30% - :align: center - -The structure of a molecule is defined by the symbols and the nuclear coordinates of -its constituent atoms. It can be specified using different `chemical file formats -`_. Within PennyLane, the molecular -structure is defined by providing a list with the atomic symbols and a one-dimensional -array with the nuclear coordinates in -`atomic units `_. -""" -import numpy as np - -symbols = ["H", "O", "H"] -coordinates = np.array([[-0.0399, -0.0038, 0.0], [1.5780, 0.8540, 0.0], [2.7909, -0.5159, 0.0]]) - -############################################################################## -# The :func:`~.pennylane.qchem.read_structure` function can also be used to read the -# molecular geometry from an external file. - - -from pennylane import qchem - -symbols, coordinates = qchem.read_structure("quantum_chemistry/h2o.xyz") - -############################################################################## -# The xyz format is supported. -# -# Solving the Hartree-Fock equations -# ---------------------------------- -# The molecule's electronic Hamiltonian is commonly represented using the -# `second-quantization `_ formalism, -# which we will explore in more detail in the -# next section. To that aim, a basis of **single-particle** states needs to be chosen. -# In quantum chemistry these states are the -# `molecular orbitals `_ -# which describe the wave function of a single electron in the molecule. -# -# Molecular orbitals are typically represented as a linear combination of **atomic orbitals**. -# The expansion coefficients in the atomic basis are calculated using the -# `Hartree-Fock (HF) method `_. -# In the HF approximation, each electron in the molecule is treated as an **independent** -# particle that moves under the influence of the Coulomb potential due to the nuclei, and a mean -# field generated by all other electrons [#pople1977]_. The optimized coefficients are precisely -# what we need to build the second-quantized Hamiltonian. -# -# PennyLane provides a differentiable Hartree-Fock solver and the functionality to construct a -# fully-differentiable molecular Hamiltonian. -# -# Building the Hamiltonian -# ------------------------ -# In the second quantization formalism, the electronic wave function of the molecule -# is represented in the occupation number basis. For :math:`M` *spin* molecular -# orbitals, the elements of this basis are labelled as -# :math:`\vert n_0, n_1, \dots, n_{M-1} \rangle,` where :math:`n_i = 0,1` -# indicates the occupation of each orbital. In this representation, the electronic -# Hamiltonian is given by -# -# .. math:: -# H = \sum_{p,q} h_{pq} c_p^\dagger c_q + -# \frac{1}{2} \sum_{p,q,r,s} h_{pqrs} c_p^\dagger c_q^\dagger c_r c_s, -# -# where :math:`c^\dagger` and :math:`c` are the electron creation -# and annihilation operators, respectively, and the coefficients -# :math:`h_{pq}` and :math:`h_{pqrs}` denote the one- and two-electron -# Coulomb integrals [#ref_integrals]_ evaluated using the Hartree-Fock -# orbitals. -# -# We can use the states of :math:`M` qubits to encode any element -# of the occupation number basis -# -# .. math:: -# \vert n_0, n_1, \dots, n_{M-1} \rangle \rightarrow \vert q_0 q_1 \cdots q_{M-1} \rangle. -# -# This implies that we need to map the fermionic operators onto operators -# that act on qubits. This can be done by using the -# `Jordan-Wigner `_ -# transformation [#seeley2012]_ which allows us to decompose the fermionic Hamiltonian -# into a linear combination of the tensor product of Pauli operators -# -# .. math:: -# H = \sum_j C_j \otimes_i \sigma_i^{(j)}, -# -# where :math:`C_j` is a scalar coefficient and :math:`\sigma_i` represents an -# element of the Pauli group :math:`\{ I, X, Y, Z \}.` -# -# In PennyLane we have the :func:`~.pennylane.qchem.molecular_hamiltonian` -# function which encapsulates all the steps explained above. It simplifies the process of building -# the electronic Hamiltonian to a single line of code. We just need to input -# the molecule, as shown below: - -molecule = qchem.Molecule(symbols, coordinates) -H, qubits = qchem.molecular_hamiltonian(molecule) -print("Number of qubits: {:}".format(qubits)) -print("Qubit Hamiltonian") -print(H) - -############################################################################## -# Advanced features -# ----------------- -# The :class:`~.pennylane.qchem.Molecule` object allows us to define additional -# keyword arguments to solve the Hartree-Fock equations of more complicated systems. -# The net charge of the molecule may be specified to simulate positively or negatively -# charged molecules. For a neutral system we choose - -charge = 0 - -############################################################################## -# We can also specify the -# `spin multiplicity `_. For the -# water molecule, which contains ten electrons, the `Slater determinant -# `_ resulting from occupying the five -# lowest-energy orbitals with two *paired* electrons per orbital has spin multiplicity one. -# Alternatively, if we define an occupation where the first four orbitals are doubly occupied -# and the next two are singly occupied by *unpaired* electrons, the HF state will have -# multiplicity three. -# -# | -# -# .. figure:: ../_static/demonstration_assets/quantum_chemistry/hf_references.png -# :width: 50% -# :align: center -# -# | -# -# For the neutral water molecule we have, - -multiplicity = 1 - -############################################################################## -# As mentioned above, molecular orbitals are represented as a linear combination -# of atomic orbitals which are typically modeled as `Gaussian-type orbitals -# `_. We can specify different types -# of `Gaussian atomic bases `_. In this example we -# choose a `minimal basis set -# `_. - -basis_set = "sto-3g" - -############################################################################## -# PennyLane also allows us to define an active space [#truhlar2018]_ to perform quantum -# simulations with a reduced number of qubits. This is done by classifying the molecular -# orbitals as core, active, and external orbitals: -# -# * Core orbitals are always occupied by two electrons. -# * Active orbitals can be occupied by zero, one, or two electrons. -# * The external orbitals are never occupied. -# -# Within this approximation, a certain number of **active electrons** are allowed to -# populate a finite set of **active orbitals**. -# -# .. figure:: ../_static/demonstration_assets/quantum_chemistry/sketch_active_space.png -# :width: 40% -# :align: center -# -# .. note:: -# The number of active **spin-orbitals** determines the **number of qubits** required -# to perform the quantum simulations. -# -# For the water molecule in a minimal basis set we have a total of ten electrons -# and seven molecular orbitals. In this example we define a symmetric active space with -# four electrons and four active orbitals using -# the :func:`~.pennylane.qchem.active_space` function: - -electrons = 10 -orbitals = 7 -core, active = qchem.active_space(electrons, orbitals, active_electrons=4, active_orbitals=4) - -############################################################################## -# Viewing the results: - -print("List of core orbitals: {:}".format(core)) -print("List of active orbitals: {:}".format(active)) -print("Number of qubits: {:}".format(2 * len(active))) - -############################################################################## -# Finally, we use the :func:`~.pennylane.qchem.molecular_hamiltonian` function to -# build the resulting Hamiltonian of the water molecule: - -molecule = qchem.Molecule( - symbols, - coordinates, - charge=charge, - mult=multiplicity, - basis_name=basis_set -) - -H, qubits = qchem.molecular_hamiltonian( - molecule, - active_electrons=4, - active_orbitals=4, -) - -print("Number of qubits required to perform quantum simulations: {:}".format(qubits)) -print("Hamiltonian of the water molecule") -print(H) - -############################################################################## -# In this case, since we have truncated the basis of molecular orbitals, the resulting -# observable is an approximation of the Hamiltonian generated in the -# section `Building the Hamiltonian `__. -# -# OpenFermion-PySCF backend -# ------------------------- -# The :func:`~.pennylane.qchem.molecular_hamiltonian` function can also be used to construct the -# molecular Hamiltonian with a non-differentiable backend that uses the -# `OpenFermion-PySCF `_ plugin interfaced with the -# electronic structure package `PySCF `_. This -# backend can be selected by setting ``method='pyscf'`` in -# :func:`~.pennylane.qchem.molecular_hamiltonian`: - -molecule = qchem.Molecule(symbols, coordinates) -H, qubits = qchem.molecular_hamiltonian(molecule, method="pyscf") -print(H) - -############################################################################## -# This backend requires the ``OpenFermion-PySCF`` plugin to be installed by the user with -# -# .. code-block:: bash -# -# pip install openfermionpyscf -# -# Additionally, if you have built your electronic Hamiltonian independently using -# `OpenFermion `_ tools, it can -# be readily converted to a PennyLane observable using the -# :func:`~.pennylane.qchem.import_operator` function. -# -# You have completed the tutorial! Now, select your favorite molecule and build its electronic -# Hamiltonian. -# To see how simple it is to implement the VQE algorithm to compute the ground-state energy of -# your molecule using PennyLane, take a look at the tutorial :doc:`tutorial_vqe`. -# -# References -# ---------- -# -# .. [#yudong2019] -# -# Yudong Cao, Jonathan Romero, *et al.*, "Quantum Chemistry in the Age of Quantum Computing". -# `Chem. Rev. 2019, 119, 19, 10856-10915. -# `_ -# -# .. [#BornOpp1927] -# -# M. Born, J.R. Oppenheimer, "Quantum Theory of the Molecules". -# `Annalen der Physik 84, 457-484 (1927) -# `_ -# -# .. [#pople1977] -# -# Rolf Seeger, John Pople. "Self‐consistent molecular orbital methods. XVIII. Constraints and -# stability in Hartree–Fock theory". `Journal of Chemical Physics 66, -# 3045 (1977). `_ -# -# .. [#ref_integrals] -# -# J.T. Fermann, E.F. Valeev, "Fundamentals of Molecular Integrals Evaluation". -# `arXiv:2007.12057 `_ -# -# .. [#seeley2012] -# -# Jacob T. Seeley, Martin J. Richard, Peter J. Love. "The Bravyi-Kitaev transformation for -# quantum computation of electronic structure". `Journal of Chemical Physics 137, 224109 (2012). -# `_ -# -# .. [#truhlar2018] -# -# J.J. Bao, S.S. Dong, L. Gagliardi, D.G. Truhlar. "Automatic Selection of an -# Active Space for Calculating Electronic Excitation Spectra by MS-CASPT2 or MC-PDFT". -# `Journal of Chemical Theory and Computation 14, 2017 (2018). -# `_ -# -# About the author -# ---------------- -# +r""" +Building molecular Hamiltonians +=============================== + + +.. meta:: + :property="og:description": Learn how to build electronic Hamiltonians of molecules. + + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/water_structure.png + +.. related:: + tutorial_vqe A brief overview of VQE + +*Author: Alain Delgado — Posted: 02 August 2020. Last updated: 29 August 2023.* + +.. note:: + + A wide variety of molecular data, including Hamiltonians, is + available on the `PennyLane Datasets service `__. + +The ultimate goal of computational quantum chemistry is to unravel the +quantum effects that determine the structure and properties of molecules. Reaching +this goal is challenging since the characteristic energies associated with +these effects, e.g., the electronic correlation energy, are typically a tiny fraction +of the total energy of the molecule. + +Accurate molecular properties can be computed from the wave function describing the +interacting electrons in a molecule. The **electronic** wave function +:math:`\Psi(r)` satisfies the `Schrödinger equation +`_ + +.. math:: + H_e \Psi(r) = E \Psi(r), + +where :math:`H_e` and :math:`E` denote the electronic Hamiltonian and the +total energy of the molecule, respectively. When solving the latter equation, +the nuclei of the molecule can be treated as point particles whose coordinates +are fixed [#BornOpp1927]_. In this approximation, both the total energy and +the electronic Hamiltonian depend parametrically on the nuclear coordinates. + + +In this tutorial, you will learn how to use PennyLane to build a +representation of the electronic Hamiltonian :math:`H_e` that can be used to perform +**quantum** simulations of molecules [#yudong2019]_. First, we show how to define +the structure of the molecule in terms of the symbols and the coordinates of +the atoms. Next, we describe how to solve the `Hartree-Fock +equations `_ for the target +molecule. Finally, we discuss some advanced features that can be used to simulate +more complicated systems. + +Let's get started! + +Defining the molecular structure +-------------------------------- +In this example we construct the electronic Hamiltonian of the water molecule. + + +.. figure:: ../_static/demonstration_assets/quantum_chemistry/water_structure.png + :width: 30% + :align: center + +The structure of a molecule is defined by the symbols and the nuclear coordinates of +its constituent atoms. It can be specified using different `chemical file formats +`_. Within PennyLane, the molecular +structure is defined by providing a list with the atomic symbols and a one-dimensional +array with the nuclear coordinates in +`atomic units `_. +""" +import numpy as np + +symbols = ["H", "O", "H"] +coordinates = np.array([[-0.0399, -0.0038, 0.0], [1.5780, 0.8540, 0.0], [2.7909, -0.5159, 0.0]]) + +############################################################################## +# The :func:`~.pennylane.qchem.read_structure` function can also be used to read the +# molecular geometry from an external file. + + +from pennylane import qchem + +symbols, coordinates = qchem.read_structure("quantum_chemistry/h2o.xyz") + +############################################################################## +# The xyz format is supported. +# +# Solving the Hartree-Fock equations +# ---------------------------------- +# The molecule's electronic Hamiltonian is commonly represented using the +# `second-quantization `_ formalism, +# which we will explore in more detail in the +# next section. To that aim, a basis of **single-particle** states needs to be chosen. +# In quantum chemistry these states are the +# `molecular orbitals `_ +# which describe the wave function of a single electron in the molecule. +# +# Molecular orbitals are typically represented as a linear combination of **atomic orbitals**. +# The expansion coefficients in the atomic basis are calculated using the +# `Hartree-Fock (HF) method `_. +# In the HF approximation, each electron in the molecule is treated as an **independent** +# particle that moves under the influence of the Coulomb potential due to the nuclei, and a mean +# field generated by all other electrons [#pople1977]_. The optimized coefficients are precisely +# what we need to build the second-quantized Hamiltonian. +# +# PennyLane provides a differentiable Hartree-Fock solver and the functionality to construct a +# fully-differentiable molecular Hamiltonian. +# +# Building the Hamiltonian +# ------------------------ +# In the second quantization formalism, the electronic wave function of the molecule +# is represented in the occupation number basis. For :math:`M` *spin* molecular +# orbitals, the elements of this basis are labelled as +# :math:`\vert n_0, n_1, \dots, n_{M-1} \rangle,` where :math:`n_i = 0,1` +# indicates the occupation of each orbital. In this representation, the electronic +# Hamiltonian is given by +# +# .. math:: +# H = \sum_{p,q} h_{pq} c_p^\dagger c_q + +# \frac{1}{2} \sum_{p,q,r,s} h_{pqrs} c_p^\dagger c_q^\dagger c_r c_s, +# +# where :math:`c^\dagger` and :math:`c` are the electron creation +# and annihilation operators, respectively, and the coefficients +# :math:`h_{pq}` and :math:`h_{pqrs}` denote the one- and two-electron +# Coulomb integrals [#ref_integrals]_ evaluated using the Hartree-Fock +# orbitals. +# +# We can use the states of :math:`M` qubits to encode any element +# of the occupation number basis +# +# .. math:: +# \vert n_0, n_1, \dots, n_{M-1} \rangle \rightarrow \vert q_0 q_1 \cdots q_{M-1} \rangle. +# +# This implies that we need to map the fermionic operators onto operators +# that act on qubits. This can be done by using the +# `Jordan-Wigner `_ +# transformation [#seeley2012]_ which allows us to decompose the fermionic Hamiltonian +# into a linear combination of the tensor product of Pauli operators +# +# .. math:: +# H = \sum_j C_j \otimes_i \sigma_i^{(j)}, +# +# where :math:`C_j` is a scalar coefficient and :math:`\sigma_i` represents an +# element of the Pauli group :math:`\{ I, X, Y, Z \}.` +# +# In PennyLane we have the :func:`~.pennylane.qchem.molecular_hamiltonian` +# function which encapsulates all the steps explained above. It simplifies the process of building +# the electronic Hamiltonian to a single line of code. We just need to input +# the molecule, as shown below: + +molecule = qchem.Molecule(symbols, coordinates) +H, qubits = qchem.molecular_hamiltonian(molecule) +print("Number of qubits: {:}".format(qubits)) +print("Qubit Hamiltonian") +print(H) + +############################################################################## +# Advanced features +# ----------------- +# The :class:`~.pennylane.qchem.Molecule` object allows us to define additional +# keyword arguments to solve the Hartree-Fock equations of more complicated systems. +# The net charge of the molecule may be specified to simulate positively or negatively +# charged molecules. For a neutral system we choose + +charge = 0 + +############################################################################## +# We can also specify the +# `spin multiplicity `_. For the +# water molecule, which contains ten electrons, the `Slater determinant +# `_ resulting from occupying the five +# lowest-energy orbitals with two *paired* electrons per orbital has spin multiplicity one. +# Alternatively, if we define an occupation where the first four orbitals are doubly occupied +# and the next two are singly occupied by *unpaired* electrons, the HF state will have +# multiplicity three. +# +# | +# +# .. figure:: ../_static/demonstration_assets/quantum_chemistry/hf_references.png +# :width: 50% +# :align: center +# +# | +# +# For the neutral water molecule we have, + +multiplicity = 1 + +############################################################################## +# As mentioned above, molecular orbitals are represented as a linear combination +# of atomic orbitals which are typically modeled as `Gaussian-type orbitals +# `_. We can specify different types +# of `Gaussian atomic bases `_. In this example we +# choose a `minimal basis set +# `_. + +basis_set = "sto-3g" + +############################################################################## +# PennyLane also allows us to define an active space [#truhlar2018]_ to perform quantum +# simulations with a reduced number of qubits. This is done by classifying the molecular +# orbitals as core, active, and external orbitals: +# +# * Core orbitals are always occupied by two electrons. +# * Active orbitals can be occupied by zero, one, or two electrons. +# * The external orbitals are never occupied. +# +# Within this approximation, a certain number of **active electrons** are allowed to +# populate a finite set of **active orbitals**. +# +# .. figure:: ../_static/demonstration_assets/quantum_chemistry/sketch_active_space.png +# :width: 40% +# :align: center +# +# .. note:: +# The number of active **spin-orbitals** determines the **number of qubits** required +# to perform the quantum simulations. +# +# For the water molecule in a minimal basis set we have a total of ten electrons +# and seven molecular orbitals. In this example we define a symmetric active space with +# four electrons and four active orbitals using +# the :func:`~.pennylane.qchem.active_space` function: + +electrons = 10 +orbitals = 7 +core, active = qchem.active_space(electrons, orbitals, active_electrons=4, active_orbitals=4) + +############################################################################## +# Viewing the results: + +print("List of core orbitals: {:}".format(core)) +print("List of active orbitals: {:}".format(active)) +print("Number of qubits: {:}".format(2 * len(active))) + +############################################################################## +# Finally, we use the :func:`~.pennylane.qchem.molecular_hamiltonian` function to +# build the resulting Hamiltonian of the water molecule: + +molecule = qchem.Molecule( + symbols, + coordinates, + charge=charge, + mult=multiplicity, + basis_name=basis_set +) + +H, qubits = qchem.molecular_hamiltonian( + molecule, + active_electrons=4, + active_orbitals=4, +) + +print("Number of qubits required to perform quantum simulations: {:}".format(qubits)) +print("Hamiltonian of the water molecule") +print(H) + +############################################################################## +# In this case, since we have truncated the basis of molecular orbitals, the resulting +# observable is an approximation of the Hamiltonian generated in the +# section `Building the Hamiltonian `__. +# +# OpenFermion-PySCF backend +# ------------------------- +# The :func:`~.pennylane.qchem.molecular_hamiltonian` function can also be used to construct the +# molecular Hamiltonian with a non-differentiable backend that uses the +# `OpenFermion-PySCF `_ plugin interfaced with the +# electronic structure package `PySCF `_. This +# backend can be selected by setting ``method='pyscf'`` in +# :func:`~.pennylane.qchem.molecular_hamiltonian`: + +molecule = qchem.Molecule(symbols, coordinates) +H, qubits = qchem.molecular_hamiltonian(molecule, method="pyscf") +print(H) + +############################################################################## +# This backend requires the ``OpenFermion-PySCF`` plugin to be installed by the user with +# +# .. code-block:: bash +# +# pip install openfermionpyscf +# +# Additionally, if you have built your electronic Hamiltonian independently using +# `OpenFermion `_ tools, it can +# be readily converted to a PennyLane observable using the +# :func:`~.pennylane.qchem.import_operator` function. +# +# You have completed the tutorial! Now, select your favorite molecule and build its electronic +# Hamiltonian. +# To see how simple it is to implement the VQE algorithm to compute the ground-state energy of +# your molecule using PennyLane, take a look at the tutorial :doc:`tutorial_vqe`. +# +# References +# ---------- +# +# .. [#yudong2019] +# +# Yudong Cao, Jonathan Romero, *et al.*, "Quantum Chemistry in the Age of Quantum Computing". +# `Chem. Rev. 2019, 119, 19, 10856-10915. +# `_ +# +# .. [#BornOpp1927] +# +# M. Born, J.R. Oppenheimer, "Quantum Theory of the Molecules". +# `Annalen der Physik 84, 457-484 (1927) +# `_ +# +# .. [#pople1977] +# +# Rolf Seeger, John Pople. "Self‐consistent molecular orbital methods. XVIII. Constraints and +# stability in Hartree–Fock theory". `Journal of Chemical Physics 66, +# 3045 (1977). `_ +# +# .. [#ref_integrals] +# +# J.T. Fermann, E.F. Valeev, "Fundamentals of Molecular Integrals Evaluation". +# `arXiv:2007.12057 `_ +# +# .. [#seeley2012] +# +# Jacob T. Seeley, Martin J. Richard, Peter J. Love. "The Bravyi-Kitaev transformation for +# quantum computation of electronic structure". `Journal of Chemical Physics 137, 224109 (2012). +# `_ +# +# .. [#truhlar2018] +# +# J.J. Bao, S.S. Dong, L. Gagliardi, D.G. Truhlar. "Automatic Selection of an +# Active Space for Calculating Electronic Excitation Spectra by MS-CASPT2 or MC-PDFT". +# `Journal of Chemical Theory and Computation 14, 2017 (2018). +# `_ +# +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_quantum_dropout/demo.py b/demonstrations_v2/tutorial_quantum_dropout/demo.py index e0ea5e2ba2..cebca15758 100644 --- a/demonstrations_v2/tutorial_quantum_dropout/demo.py +++ b/demonstrations_v2/tutorial_quantum_dropout/demo.py @@ -1,702 +1,702 @@ -r"""Dropout for Quantum Neural Networks -=================================== -""" - -###################################################################### -# Are you struggling with overfitting while training Quantum Neural Networks (QNNs)? -# -# In this demo, we show how to exploit the quantum version of the dropout technique to avoid the problem of -# overfitting in overparametrized QNNs. What follows is based on the paper “A General -# Approach to Dropout in Quantum Neural Networks” by F. Scala, et al. [#dropout]_. -# -# .. figure:: ../_static/demonstration_assets/quantum_dropout/socialthumbnail_large_QuantumDropout_2024-03-07.png -# :align: center -# :width: 60% -# :target: javascript:void(0) -# -# -# What is overfitting and dropout? -# --------------------------------- -# -# Neural Networks (NNs) usually require highly flexible models with lots of trainable parameters in -# order to *learn* a certain underlying function (or data distribution). -# However, being able to learn with low in-sample error is not enough; *generalization* — the ability to provide -# good predictions on previously unseen data — is also desirable. -# -# Highly expressive models may suffer from **overfitting**, which means that -# they are trained too well on the training data, and as a result perform poorly on new, unseen -# data. This happens because the model has learned the noise in the training data, rather than the -# underlying pattern that is generalizable to new data. -# -# **Dropout** is a common technique for classical Deep Neural Networks (DNNs) preventing computational units -# from becoming too specialized and reducing the risk of overfitting [#Hinton2012]_, [#Srivastava2014]_. It consists of randomly removing -# neurons or connections *only during training* to block the flow of information. Once the -# model is trained, the DNN is employed in its original form. -# -# Why dropout for Quantum Neural Networks? -# ---------------------------------------- -# -# Recently, it has been shown that the use of overparametrized QNN models -# changes the optimization landscape by removing lots of local minima [#Kiani2020]_, [#Larocca2023]_. On the one hand, this increased number of -# parameters leads to faster and easier training, but on the other hand, it may drive -# the model to overfit the data. This is also strictly related to the `repeated encoding `__ of classical -# data to achieve nonlinearity in the computation. This is why, inspired from classical DNNs, one -# can think of applying some sort of dropout to QNNs. This would correspond to randomly dropping some -# (groups of) parameterized gates during training to achieve better generalization. -# -# Quantum dropout of rotations in a sine regression -# -------------------------------------------------- -# -# In this demo we will exploit quantum dropout to avoid overfitting during the regression of noisy -# data originally coming from the *sinusoidal* function. In particular, we will randomly “drop” -# rotations during the training phase. In practice, this will correspond to temporarily setting parameters to a value of 0. -# -# Let’s start by importing Pennylane and ``numpy`` and fixing the random seed for reproducibility: -# - -import numpy as np -import pennylane as qml - -seed = 12345 -np.random.seed(seed=seed) - -###################################################################### -# The circuit -# ~~~~~~~~~~~ -# -# Now we define the embedding of classical data and the variational ansatz that will then be combined -# to construct our QNN. Dropout will happen inside the variational ansatz. Obtaining dropout with standard -# Pennylane would be quite straightforward by means of some "if statements", but the training procedure -# will take ages. Here we will leverage JAX in order to speed up the training process with -# Just In Time (JIT) compilation. The drawback is that the definition of the variational ansatz becomes a -# little elaborated, since JAX has its own language for conditional statements. For this purpose we -# define two functions ``true_cond`` and ``false_cond`` to work with ``jax.lax.cond```, which is the JAX -# conditional statement. See this `demo `__ -# for additional insights on how to optimize QNNs with JAX. -# -# Practically speaking, rotation dropout will be performed by passing a list to the ansatz. -# The single qubit rotations are applied depending on the values stored in this list: -# if the value is negative the rotation is dropped (rotation dropout), otherwise it is applied. -# How to produce this list will be explained later in this demo (see the ``make_dropout`` function). - -import jax # require for Just In Time (JIT) compilation -import jax.numpy as jnp - -jax.config.update("jax_platform_name", "cpu") -jax.config.update("jax_enable_x64", True) - - -def embedding(x, wires): - # Encodes the datum multiple times in the register, - # employing also nonlinear functions - assert len(x) == 1 # check feature is 1-D - for i in wires: - qml.RY(jnp.arcsin(x), wires=i) - for i in wires: - qml.RZ(jnp.arccos(x ** 2), wires=i) - - -def true_cond(angle): - # necessary for using an if statement within jitted function - # exploiting jax.lax.cond - # if this function is assessed the rotation is dropped - return 0.0 - - -def false_cond(angle): - # necessary for using an if statement within jitted function - # exploiting jax.lax.cond - # if this function is assessed the rotation is kept - return angle - - -def var_ansatz( - theta, wires, rotations=[qml.RX, qml.RZ, qml.RX], entangler=qml.CNOT, keep_rotation=None -): - - """Single layer of the variational ansatz for our QNN. - We have a single qubit rotation per each qubit (wire) followed by - a linear chain of entangling gates (entangler). This structure is repeated per each rotation in `rotations` - (defining `inner_layers`). - The single qubit rotations are applied depending on the values stored in `keep_rotation`: - if the value is negative the rotation is dropped (rotation dropout), otherwise it is applied. - - Params: - - theta: variational angles that will undergo optimization - - wires: list of qubits (wires) - - rotations: list of rotation kind per each `inner_layer` - - entangler: entangling gate - - keep_rotation: list of lists. There is one list per each `inner_layer`. - In each list there are indexes of the rotations that we want to apply. - Some of these values may be substituted by -1 value - which means that the rotation gate wont be applied (dropout). - """ - - # the length of `rotations` defines the number of inner layers - N = len(wires) - assert len(theta) == 3 * N - wires = list(wires) - - counter = 0 - # keep_rotations contains a list per each inner_layer - for rots in keep_rotation: - # we cicle over the elements of the lists inside keep_rotation - for qb, keep_or_drop in enumerate(rots): - rot = rotations[counter] # each inner layer can have a different rotation - - angle = theta[counter * N + qb] - # conditional statement implementing dropout - # if `keep_or_drop` is negative the rotation is dropped - angle_drop = jax.lax.cond(keep_or_drop < 0, true_cond, false_cond, angle) - rot(angle_drop, wires=wires[qb]) - for qb in wires[:-1]: - entangler(wires=[wires[qb], wires[qb + 1]]) - counter += 1 - - -###################################################################### -# And then we define the hyperparameters of our QNN, namely the number of qubits, -# the number of sublayers in the variational ansatz (``inner_layers``) and the resulting -# number of parameters per layer: -# - -n_qubits = 5 -inner_layers = 3 -params_per_layer = n_qubits * inner_layers - -###################################################################### -# Now we actually build the QNN: -# - - -def create_circuit(n_qubits, layers): - device = qml.device("default.qubit", wires=n_qubits) - - @qml.qnode(device) - def circuit(x, theta, keep_rot): - # print(x) - # print(theta) - - for i in range(layers): - embedding(x, wires=range(n_qubits)) - - keep_rotation = keep_rot[i] - - var_ansatz( - theta[i * params_per_layer : (i + 1) * params_per_layer], - wires=range(n_qubits), - entangler=qml.CNOT, - keep_rotation=keep_rotation, - ) - - return qml.expval(qml.PauliZ(wires=0)) # we measure only the first qubit - - return circuit - - -###################################################################### -# Let’s have a look at a single layer of our QNN: -# -import matplotlib.pyplot as plt - - -plt.style.use("pennylane.drawer.plot") # set pennylane theme, which is nice to see - -# create the circuit with given number of qubits and layers -layers = 1 -circ = create_circuit(n_qubits, layers=layers) - -# for the moment let's keep all the rotations in all sublayers -keep_all_rot = [ - [list(range((n_qubits))) for j in range(1, inner_layers + 1)], -] -# we count the parameters -numbered_params = np.array(range(params_per_layer * layers), dtype=float) -# we encode a single coordinate -single_sample = np.array([0]) - -qml.draw_mpl(circ, decimals=2,)(single_sample, numbered_params, keep_all_rot) - -plt.show() - -###################################################################### -# We now build the model that we will employ for the regression task. -# Since we want to have an overparametrized QNN, we will add 10 layers and we exploit -# ``JAX`` to speed the training up: -# - -layers = 10 -qnn_tmp = create_circuit(n_qubits, layers) -qnn_tmp = jax.jit(qnn_tmp) -qnn_batched = jax.vmap( - qnn_tmp, (0, None, None) -) # we want to vmap on 0-axis of the first circuit param -# in this way we process in parallel all the inputs -# We jit for faster execution -qnn = jax.jit(qnn_batched) - - -###################################################################### -# Dropping rotations -# ~~~~~~~~~~~~~~~~~~ -# -# As anticipated, we need to set some random parameters to 0 at each optimization step. Given a layer -# dropout rate :math:`p_L` (this will be called ``layer_drop_rate``) and the gate dropout rate :math:`p_G` -# (this will be called ``rot_drop_rate``), the probability :math:`p` that a -# gate is dropped in a layer can be calculated with the conditioned probability law: -# -# .. math:: -# -# -# p=p(A\cap B)=p(A|B)p(B)=p_Gp_L -# -# where :math:`B` represents the selection of a specific layer and -# :math:`A` the selection of a specific gate within the chosen layer. -# -# In the following cell we define a function that produces the list of the indices of rotation gates that -# are kept. For gates which are dropped, the value ``-1`` is assigned to the corresponding index. The structure of the list -# is nested; we have one list per ``inner_layer`` inside one list per each layer, all contained in another list. -# This function will be called at each iteration. -# - - -def make_dropout(key): - drop_layers = [] - - for lay in range(layers): - # each layer has prob p_L=layer_drop_rate of being dropped - # according to that for every layer we sample - # if we have to appy dropout in it or not - out = jax.random.choice( - key, jnp.array(range(2)), p=jnp.array([1 - layer_drop_rate, layer_drop_rate]) - ) - key = jax.random.split(key)[0] # update the random key - - if out == 1: # if it has to be dropped - drop_layers.append(lay) - - keep_rot = [] - # we make list of indexes corresponding to the rotations gates - # that are kept in the computation during a single train step - for i in range(layers): - # each list is divded in layers and then in "inner layers" - # this is strictly related to the QNN architecture that we use - keep_rot_layer = [list(range((n_qubits))) for j in range(1, inner_layers + 1)] - - if i in drop_layers: # if dropout has to be applied in this layer - keep_rot_layer = [] # list of indexes for a single layer - inner_keep_r = [] # list of indexes for a single inner layer - for param in range(params_per_layer): - # each rotation within the layer has prob p=rot_drop_rate of being dropped - # according to that for every parameter (rotation) we sample - # if we have to drop it or not - out = jax.random.choice( - key, jnp.array(range(2)), p=jnp.array([1 - rot_drop_rate, rot_drop_rate]) - ) - key = jax.random.split(key)[0] # update the random key - - if out == 0: # if we have to keep it - inner_keep_r.append(param % n_qubits) # % is required because we work - # inner layer by inner layer - else: # if the rotation has to be dropped - inner_keep_r.append(-1) # we assign the value -1 - - if param % n_qubits == n_qubits - 1: # if it's the last qubit of the register - # append the inner layer list - keep_rot_layer.append(inner_keep_r) - # and reset it - inner_keep_r = [] - - keep_rot.append(keep_rot_layer) - - return jnp.array(keep_rot) - - -###################################################################### -# We can check the output of the ``make_dropout`` function: -# - -# setting the drop probability -layer_drop_rate, rot_drop_rate = 0.5, 0.3 # 15% probability of dropping a gate - -# JAX random key -key = jax.random.PRNGKey(12345) -# create the list of indexes, -# -1 implies we are dropping a gate -keep_rot = make_dropout(key) - -# let's just print the list for first layer -print(keep_rot[0]) - -###################################################################### -# Noisy sinusoidal function -# ~~~~~~~~~~~~~~~~~~~~~~~~~ -# -# To test the effectiveness of the dropout technique, we will use a prototypical dataset -# with which it is very easy to overfit: the sinusoidal function. We produce some -# points according to the :math:`\sin` function and then we add some white Gaussian noise -# (noise that follows a normal distribution) :math:`\epsilon.` The noise is essential to obtain overfitting; -# when our model is extremely expressive, it is capable of exactly fit each point and some parameters -# become hyper-specialized in recognizing the noisy features. This makes predictions on new unseen -# data difficult, since the overfitting model did not learn the true underlying data distribution. -# The dropout technique will help in avoiding co-adaptation and hyper-specialization, -# effectively reducing overfitting. -# - -from sklearn.model_selection import train_test_split - - -def make_sin_dataset(dataset_size=100, test_size=0.4, noise_value=0.4, plot=False): - """1D regression problem y=sin(x*\pi)""" - # x-axis - x_ax = np.linspace(-1, 1, dataset_size) - y = [[np.sin(x * np.pi)] for x in x_ax] - np.random.seed(123) - # noise vector - noise = np.array([np.random.normal(0, 0.5, 1) for i in y]) * noise_value - X = np.array(x_ax) - y = np.array(y + noise) # apply noise - - # split the dataset - X_train, X_test, y_train, y_test = train_test_split( - X, y, test_size=test_size, random_state=40, shuffle=True - ) - - X_train = X_train.reshape(-1, 1) - X_test = X_test.reshape(-1, 1) - - y_train = y_train.reshape(-1, 1) - y_test = y_test.reshape(-1, 1) - - return X_train, X_test, y_train, y_test - - -from matplotlib import ticker - -X, X_test, y, y_test = make_sin_dataset(dataset_size=20, test_size=0.25) - - -fig, ax = plt.subplots() -plt.plot(X, y, "o", label="Training") -plt.plot(X_test, y_test, "o", label="Test") - -plt.plot( - np.linspace(-1, 1, 100), - [np.sin(x * np.pi) for x in np.linspace(-1, 1, 100)], - linestyle="dotted", - label=r"$\sin(x)$", -) -plt.ylabel(r"$y = \sin(\pi\cdot x) + \epsilon$") -plt.xlabel(r"$x$") -ax.xaxis.set_major_locator(ticker.MultipleLocator(0.5)) -ax.yaxis.set_major_locator(ticker.MultipleLocator(0.5)) -plt.legend() - -plt.show() - -###################################################################### -# Since our circuit is only able to provide outputs in the range :math:`[-1,1],` we rescale all the -# noisy data within this range. To do this we leverage the `MinMaxScaler` from `sklearn`. -# It is common practice to fit the scaler only from training data and then apply it also to the -# test. The reason behind this is that in general one only has knowledge about the training dataset. -# (If the training dataset is not exhaustively representative of the underlying distribution, -# this preprocessing may lead to some outliers in the test set to be scaled out of the desired range.) -# - -from sklearn.preprocessing import MinMaxScaler - -scaler = MinMaxScaler(feature_range=(-1, 1)) -y = scaler.fit_transform(y) -y_test = scaler.transform(y_test) - -# reshaping for computation -y = y.reshape(-1,) -y_test = y_test.reshape(-1,) - -###################################################################### -# Optimization -# ~~~~~~~~~~~~ -# -# At this point we have to set the hyperparameters of the optimization, namely the number of epochs, the -# learning rate, and the optimizer: -# - -import optax # optimization using jax - -epochs = 700 -optimizer = optax.adam(learning_rate=0.01) - -###################################################################### -# We define the cost function as the Mean Square Error: -# - - -@jax.jit -def calculate_mse_cost(X, y, theta, keep_rot): - yp = qnn(X, theta, keep_rot) - # depending on your version of Pennylane you may require the following line - ##### - yp = jnp.array(yp).T - ##### - cost = jnp.mean((yp - y) ** 2) - - return cost - - -# Optimization update step -@jax.jit -def optimizer_update(opt_state, params, x, y, keep_rot): - loss, grads = jax.value_and_grad(lambda theta: calculate_mse_cost(x, y, theta, keep_rot))( - params - ) - updates, opt_state = optimizer.update(grads, opt_state) - - params = optax.apply_updates(params, updates) - return params, opt_state, loss - - -###################################################################### -# Training the model -# ------------------ -# -# And now we can try to train the model. We execute different runs of the training to understand the -# average behaviour of quantum dropout. To see the effect of dropout we can set different values of -# ``layer_drop_rate`` and ``rot_drop_rate``: -# - -n_run = 3 -drop_rates = [(0.0, 0.0), (0.3, 0.2), (0.7, 0.7)] - -train_history = {} -test_history = {} -opt_params = {} - - -for layer_drop_rate, rot_drop_rate in drop_rates: - # initialization of some lists to store data - costs_per_comb = [] - test_costs_per_comb = [] - opt_params_per_comb = [] - # we execute multiple runs in order to see the average behaviour - for tmp_seed in range(seed, seed + n_run): - key = jax.random.PRNGKey(tmp_seed) - assert len(X.shape) == 2 # X must be a matrix - assert len(y.shape) == 1 # y must be an array - assert X.shape[0] == y.shape[0] # compatibility check - - # parameters initialization with gaussian ditribution - initial_params = jax.random.normal(key, shape=(layers * params_per_layer,)) - # update the random key - key = jax.random.split(key)[0] - - params = jnp.copy(initial_params) - - # optimizer initialization - opt_state = optimizer.init(initial_params) - - # lists for saving single run training and test cost trend - costs = [] - test_costs = [] - - for epoch in range(epochs): - # generate the list for dropout - keep_rot = make_dropout(key) - # update the random key - key = jax.random.split(key)[0] - - # optimization step - params, opt_state, cost = optimizer_update(opt_state, params, X, y, keep_rot) - - ############## performance evaluation ############# - # inference is done with the original model - # with all the gates - keep_rot = jnp.array( - [ - [list(range((n_qubits))) for j in range(1, inner_layers + 1)] - for i in range(layers) - ] - ) - # inference on train set - cost = calculate_mse_cost(X, y, params, keep_rot) - - costs.append(cost) - - # inference on test set - test_cost = calculate_mse_cost(X_test, y_test, params, keep_rot) - test_costs.append(test_cost) - - # we print updates every 5 iterations - if epoch % 5 == 0: - print( - f"{layer_drop_rate:.1f}-{rot_drop_rate:.1f}", - f"run {tmp_seed-seed} - epoch {epoch}/{epochs}", - f"--- Train cost:{cost:.5f}", - f"--- Test cost:{test_cost:.5f}", - end="\r", - ) - - costs_per_comb.append(costs) - test_costs_per_comb.append(test_costs) - opt_params_per_comb.append(params) - print() - costs_per_comb = np.array(costs_per_comb) - test_costs_per_comb = np.array(test_costs_per_comb) - opt_params_per_comb = np.array(opt_params_per_comb) - - train_history[(layer_drop_rate, rot_drop_rate)] = costs_per_comb - test_history[(layer_drop_rate, rot_drop_rate)] = test_costs_per_comb - opt_params[(layer_drop_rate, rot_drop_rate)] = opt_params_per_comb - -###################################################################### -# Performance evaluation -# ---------------------- -# -# Let’s compare the difference in performance with a plot: -# - -fig, axs = plt.subplots(1, 2, figsize=(12, 4)) -plt.subplots_adjust(wspace=0.05) -axs[0].set_title("MSE train") -for k, v in train_history.items(): - train_losses = np.array(v) - mean_train_history = np.mean(train_losses, axis=0) - std_train_history = np.std(train_losses, axis=0,) - - mean_train_history = mean_train_history.reshape((epochs,)) - std_train_history = std_train_history.reshape((epochs,)) - - # shadow standard deviation - axs[0].fill_between( - range(epochs), - mean_train_history - std_train_history, - mean_train_history + std_train_history, - alpha=0.2, - ) - # average trend - axs[0].plot(range(epochs), mean_train_history, label=f"{k}") # Avg Loss - -axs[1].set_title("MSE test") -for k, v in test_history.items(): - test_losses = np.array(v) - mean_test_history = np.mean(test_losses, axis=0) - std_test_history = np.std(test_losses, axis=0,) - - mean_test_history = mean_test_history.reshape((epochs,)) - std_test_history = std_test_history.reshape((epochs,)) - - # shadow standard deviation - axs[1].fill_between( - range(epochs), - mean_test_history - std_test_history, - mean_test_history + std_test_history, - alpha=0.2, - ) - # averange trend - axs[1].plot(range(epochs), mean_test_history, label=f"{k}") # Avg Loss - -axs[0].legend(loc="upper center", bbox_to_anchor=(1.01, 1.25), ncol=4, fancybox=True, shadow=True) - -for ax in axs.flat: - ax.set_xlabel("Epochs") - ax.set_ylabel("MSE") - ax.set_yscale("log") - ax.set_ylim([1e-3, 0.6]) - ax.label_outer() - -plt.subplots_adjust(bottom=0.3) - -plt.show() - -###################################################################### -# On the left you can see that without dropout there is a deep minimization of the training loss, -# moderate values of dropout converge, whereas high drop probabilities impede any learning. On -# the right, we can see the difference in generalization during the optimization process. Standard -# training without dropout initially reaches a low value of generalization error, but as the -# model starts to learn the noise in the training data (overfitting), the generalization error grows -# back. Oppositely, moderate values of dropout enable generalization errors comparable to the respective -# training ones. As the learning is not successful for elevated drop probabilities, the generalization -# error is huge. It is interesting to notice that the “not-learning” error is very close to the final -# error of the QNN trained without dropout. -# -# Hence, one can conclude that low values of dropout greatly improve the generalization performance of -# the model and remove overfitting, even if the randomness of the technique inevitably makes the -# training a little noisy. On the other hand, high drop probabilities only hinder the training -# process. -# -# Validation -# ~~~~~~~~~~ -# -# To validate the technique we can also check how the model predicts in the whole :math:`[-1,1]` range -# with and without quantum dropout. -# - -X, X_test, y, y_test = make_sin_dataset(dataset_size=20, test_size=0.25) - -# spanning the whole range -x_ax = jnp.linspace(-1, 1, 100).reshape(100, 1) - -# selecting which run we want to plot -run = 1 - -fig, ax = plt.subplots() -styles = ["dashed", "-.", "solid", "-."] -for i, k in enumerate(train_history.keys()): - if k[0] == 0.3: - alpha = 1 - else: - alpha = 0.5 - # predicting and rescaling - yp = scaler.inverse_transform(qnn(x_ax, opt_params[k][run], keep_rot).reshape(-1, 1)) - plt.plot([[i] for i in np.linspace(-1, 1, 100)], yp, label=k, alpha=alpha, linestyle=styles[i]) - -plt.scatter(X, y, label="Training", zorder=10) -plt.scatter(X_test, y_test, label="Test", zorder=10) - -ylabel = r"$y = \sin(\pi\cdot x) + \epsilon$" -plt.xlabel("x", fontsize="medium") -plt.ylabel(ylabel, fontsize="medium") -plt.legend() -ax.xaxis.set_major_locator(ticker.MultipleLocator(0.5)) -ax.yaxis.set_major_locator(ticker.MultipleLocator(0.5)) - -plt.show() - -###################################################################### -# The model without dropout overfits the noisy data by trying to exactly predict each of them, -# whereas dropout actually mitigates overfitting and makes the approximation of the underlying sinusoidal -# function way smoother. -# -# Conclusion -# ---------------------- -# In this demo, we explained the basic idea behind quantum dropout and -# how to avoid overfitting by randomly "dropping" some rotation gates -# of a QNN during the training phase. We invite you to check out the paper [#dropout]_ -# for more dropout techniques and additional analysis. Try it yourself and develop new -# dropout strategies. -# -# -# References -# ---------- -# -# .. [#dropout] Scala, F., Ceschini, A., Panella, M., & Gerace, D. (2023). -# *A General Approach to Dropout in Quantum Neural Networks*. -# `Adv. Quantum Technol., 2300220 `__. -# -# .. [#Hinton2012] Hinton, G., Srivastava, N., Krizhevsky, A., Sutskever, I., & Salakhutdinov, R. (2012). -# *Improving neural networks by preventing co-adaptation of feature detectors*. -# `arXiv:1207.0580. `__. -# -# .. [#Srivastava2014] Srivastava, N., Hinton, G., Krizhevsky, A., Sutskever, I., & Salakhutdinov, R. (2014). -# *Dropout: A Simple Way to Prevent Neural Networks from Overfitting*. -# `Journal of Machine Learning Research, 15(56):1929−1958. `__. -# -# .. [#Kiani2020] Kiani,B. T., Lloyd, S., & Maity, R. (2020). -# *Learning Unitaries by Gradient Descent*. -# `arXiv: 2001.11897. `__. -# -# .. [#Larocca2023] Larocca, M., Ju, N., García-Martín, D., Coles, P. J., & Cerezo, M. (2023). -# *Theory of overparametrization in quantum neural networks*. -# `Nat. Comp. Science, 3, 542–551. `__. -# -# About the author -# ---------------- +r"""Dropout for Quantum Neural Networks +=================================== +""" + +###################################################################### +# Are you struggling with overfitting while training Quantum Neural Networks (QNNs)? +# +# In this demo, we show how to exploit the quantum version of the dropout technique to avoid the problem of +# overfitting in overparametrized QNNs. What follows is based on the paper “A General +# Approach to Dropout in Quantum Neural Networks” by F. Scala, et al. [#dropout]_. +# +# .. figure:: ../_static/demonstration_assets/quantum_dropout/socialthumbnail_large_QuantumDropout_2024-03-07.png +# :align: center +# :width: 60% +# :target: javascript:void(0) +# +# +# What is overfitting and dropout? +# --------------------------------- +# +# Neural Networks (NNs) usually require highly flexible models with lots of trainable parameters in +# order to *learn* a certain underlying function (or data distribution). +# However, being able to learn with low in-sample error is not enough; *generalization* — the ability to provide +# good predictions on previously unseen data — is also desirable. +# +# Highly expressive models may suffer from **overfitting**, which means that +# they are trained too well on the training data, and as a result perform poorly on new, unseen +# data. This happens because the model has learned the noise in the training data, rather than the +# underlying pattern that is generalizable to new data. +# +# **Dropout** is a common technique for classical Deep Neural Networks (DNNs) preventing computational units +# from becoming too specialized and reducing the risk of overfitting [#Hinton2012]_, [#Srivastava2014]_. It consists of randomly removing +# neurons or connections *only during training* to block the flow of information. Once the +# model is trained, the DNN is employed in its original form. +# +# Why dropout for Quantum Neural Networks? +# ---------------------------------------- +# +# Recently, it has been shown that the use of overparametrized QNN models +# changes the optimization landscape by removing lots of local minima [#Kiani2020]_, [#Larocca2023]_. On the one hand, this increased number of +# parameters leads to faster and easier training, but on the other hand, it may drive +# the model to overfit the data. This is also strictly related to the `repeated encoding `__ of classical +# data to achieve nonlinearity in the computation. This is why, inspired from classical DNNs, one +# can think of applying some sort of dropout to QNNs. This would correspond to randomly dropping some +# (groups of) parameterized gates during training to achieve better generalization. +# +# Quantum dropout of rotations in a sine regression +# -------------------------------------------------- +# +# In this demo we will exploit quantum dropout to avoid overfitting during the regression of noisy +# data originally coming from the *sinusoidal* function. In particular, we will randomly “drop” +# rotations during the training phase. In practice, this will correspond to temporarily setting parameters to a value of 0. +# +# Let’s start by importing Pennylane and ``numpy`` and fixing the random seed for reproducibility: +# + +import numpy as np +import pennylane as qml + +seed = 12345 +np.random.seed(seed=seed) + +###################################################################### +# The circuit +# ~~~~~~~~~~~ +# +# Now we define the embedding of classical data and the variational ansatz that will then be combined +# to construct our QNN. Dropout will happen inside the variational ansatz. Obtaining dropout with standard +# Pennylane would be quite straightforward by means of some "if statements", but the training procedure +# will take ages. Here we will leverage JAX in order to speed up the training process with +# Just In Time (JIT) compilation. The drawback is that the definition of the variational ansatz becomes a +# little elaborated, since JAX has its own language for conditional statements. For this purpose we +# define two functions ``true_cond`` and ``false_cond`` to work with ``jax.lax.cond```, which is the JAX +# conditional statement. See this `demo `__ +# for additional insights on how to optimize QNNs with JAX. +# +# Practically speaking, rotation dropout will be performed by passing a list to the ansatz. +# The single qubit rotations are applied depending on the values stored in this list: +# if the value is negative the rotation is dropped (rotation dropout), otherwise it is applied. +# How to produce this list will be explained later in this demo (see the ``make_dropout`` function). + +import jax # require for Just In Time (JIT) compilation +import jax.numpy as jnp + +jax.config.update("jax_platform_name", "cpu") +jax.config.update("jax_enable_x64", True) + + +def embedding(x, wires): + # Encodes the datum multiple times in the register, + # employing also nonlinear functions + assert len(x) == 1 # check feature is 1-D + for i in wires: + qml.RY(jnp.arcsin(x), wires=i) + for i in wires: + qml.RZ(jnp.arccos(x ** 2), wires=i) + + +def true_cond(angle): + # necessary for using an if statement within jitted function + # exploiting jax.lax.cond + # if this function is assessed the rotation is dropped + return 0.0 + + +def false_cond(angle): + # necessary for using an if statement within jitted function + # exploiting jax.lax.cond + # if this function is assessed the rotation is kept + return angle + + +def var_ansatz( + theta, wires, rotations=[qml.RX, qml.RZ, qml.RX], entangler=qml.CNOT, keep_rotation=None +): + + """Single layer of the variational ansatz for our QNN. + We have a single qubit rotation per each qubit (wire) followed by + a linear chain of entangling gates (entangler). This structure is repeated per each rotation in `rotations` + (defining `inner_layers`). + The single qubit rotations are applied depending on the values stored in `keep_rotation`: + if the value is negative the rotation is dropped (rotation dropout), otherwise it is applied. + + Params: + - theta: variational angles that will undergo optimization + - wires: list of qubits (wires) + - rotations: list of rotation kind per each `inner_layer` + - entangler: entangling gate + - keep_rotation: list of lists. There is one list per each `inner_layer`. + In each list there are indexes of the rotations that we want to apply. + Some of these values may be substituted by -1 value + which means that the rotation gate wont be applied (dropout). + """ + + # the length of `rotations` defines the number of inner layers + N = len(wires) + assert len(theta) == 3 * N + wires = list(wires) + + counter = 0 + # keep_rotations contains a list per each inner_layer + for rots in keep_rotation: + # we cicle over the elements of the lists inside keep_rotation + for qb, keep_or_drop in enumerate(rots): + rot = rotations[counter] # each inner layer can have a different rotation + + angle = theta[counter * N + qb] + # conditional statement implementing dropout + # if `keep_or_drop` is negative the rotation is dropped + angle_drop = jax.lax.cond(keep_or_drop < 0, true_cond, false_cond, angle) + rot(angle_drop, wires=wires[qb]) + for qb in wires[:-1]: + entangler(wires=[wires[qb], wires[qb + 1]]) + counter += 1 + + +###################################################################### +# And then we define the hyperparameters of our QNN, namely the number of qubits, +# the number of sublayers in the variational ansatz (``inner_layers``) and the resulting +# number of parameters per layer: +# + +n_qubits = 5 +inner_layers = 3 +params_per_layer = n_qubits * inner_layers + +###################################################################### +# Now we actually build the QNN: +# + + +def create_circuit(n_qubits, layers): + device = qml.device("default.qubit", wires=n_qubits) + + @qml.qnode(device) + def circuit(x, theta, keep_rot): + # print(x) + # print(theta) + + for i in range(layers): + embedding(x, wires=range(n_qubits)) + + keep_rotation = keep_rot[i] + + var_ansatz( + theta[i * params_per_layer : (i + 1) * params_per_layer], + wires=range(n_qubits), + entangler=qml.CNOT, + keep_rotation=keep_rotation, + ) + + return qml.expval(qml.PauliZ(wires=0)) # we measure only the first qubit + + return circuit + + +###################################################################### +# Let’s have a look at a single layer of our QNN: +# +import matplotlib.pyplot as plt + + +plt.style.use("pennylane.drawer.plot") # set pennylane theme, which is nice to see + +# create the circuit with given number of qubits and layers +layers = 1 +circ = create_circuit(n_qubits, layers=layers) + +# for the moment let's keep all the rotations in all sublayers +keep_all_rot = [ + [list(range((n_qubits))) for j in range(1, inner_layers + 1)], +] +# we count the parameters +numbered_params = np.array(range(params_per_layer * layers), dtype=float) +# we encode a single coordinate +single_sample = np.array([0]) + +qml.draw_mpl(circ, decimals=2,)(single_sample, numbered_params, keep_all_rot) + +plt.show() + +###################################################################### +# We now build the model that we will employ for the regression task. +# Since we want to have an overparametrized QNN, we will add 10 layers and we exploit +# ``JAX`` to speed the training up: +# + +layers = 10 +qnn_tmp = create_circuit(n_qubits, layers) +qnn_tmp = jax.jit(qnn_tmp) +qnn_batched = jax.vmap( + qnn_tmp, (0, None, None) +) # we want to vmap on 0-axis of the first circuit param +# in this way we process in parallel all the inputs +# We jit for faster execution +qnn = jax.jit(qnn_batched) + + +###################################################################### +# Dropping rotations +# ~~~~~~~~~~~~~~~~~~ +# +# As anticipated, we need to set some random parameters to 0 at each optimization step. Given a layer +# dropout rate :math:`p_L` (this will be called ``layer_drop_rate``) and the gate dropout rate :math:`p_G` +# (this will be called ``rot_drop_rate``), the probability :math:`p` that a +# gate is dropped in a layer can be calculated with the conditioned probability law: +# +# .. math:: +# +# +# p=p(A\cap B)=p(A|B)p(B)=p_Gp_L +# +# where :math:`B` represents the selection of a specific layer and +# :math:`A` the selection of a specific gate within the chosen layer. +# +# In the following cell we define a function that produces the list of the indices of rotation gates that +# are kept. For gates which are dropped, the value ``-1`` is assigned to the corresponding index. The structure of the list +# is nested; we have one list per ``inner_layer`` inside one list per each layer, all contained in another list. +# This function will be called at each iteration. +# + + +def make_dropout(key): + drop_layers = [] + + for lay in range(layers): + # each layer has prob p_L=layer_drop_rate of being dropped + # according to that for every layer we sample + # if we have to appy dropout in it or not + out = jax.random.choice( + key, jnp.array(range(2)), p=jnp.array([1 - layer_drop_rate, layer_drop_rate]) + ) + key = jax.random.split(key)[0] # update the random key + + if out == 1: # if it has to be dropped + drop_layers.append(lay) + + keep_rot = [] + # we make list of indexes corresponding to the rotations gates + # that are kept in the computation during a single train step + for i in range(layers): + # each list is divded in layers and then in "inner layers" + # this is strictly related to the QNN architecture that we use + keep_rot_layer = [list(range((n_qubits))) for j in range(1, inner_layers + 1)] + + if i in drop_layers: # if dropout has to be applied in this layer + keep_rot_layer = [] # list of indexes for a single layer + inner_keep_r = [] # list of indexes for a single inner layer + for param in range(params_per_layer): + # each rotation within the layer has prob p=rot_drop_rate of being dropped + # according to that for every parameter (rotation) we sample + # if we have to drop it or not + out = jax.random.choice( + key, jnp.array(range(2)), p=jnp.array([1 - rot_drop_rate, rot_drop_rate]) + ) + key = jax.random.split(key)[0] # update the random key + + if out == 0: # if we have to keep it + inner_keep_r.append(param % n_qubits) # % is required because we work + # inner layer by inner layer + else: # if the rotation has to be dropped + inner_keep_r.append(-1) # we assign the value -1 + + if param % n_qubits == n_qubits - 1: # if it's the last qubit of the register + # append the inner layer list + keep_rot_layer.append(inner_keep_r) + # and reset it + inner_keep_r = [] + + keep_rot.append(keep_rot_layer) + + return jnp.array(keep_rot) + + +###################################################################### +# We can check the output of the ``make_dropout`` function: +# + +# setting the drop probability +layer_drop_rate, rot_drop_rate = 0.5, 0.3 # 15% probability of dropping a gate + +# JAX random key +key = jax.random.PRNGKey(12345) +# create the list of indexes, +# -1 implies we are dropping a gate +keep_rot = make_dropout(key) + +# let's just print the list for first layer +print(keep_rot[0]) + +###################################################################### +# Noisy sinusoidal function +# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# To test the effectiveness of the dropout technique, we will use a prototypical dataset +# with which it is very easy to overfit: the sinusoidal function. We produce some +# points according to the :math:`\sin` function and then we add some white Gaussian noise +# (noise that follows a normal distribution) :math:`\epsilon.` The noise is essential to obtain overfitting; +# when our model is extremely expressive, it is capable of exactly fit each point and some parameters +# become hyper-specialized in recognizing the noisy features. This makes predictions on new unseen +# data difficult, since the overfitting model did not learn the true underlying data distribution. +# The dropout technique will help in avoiding co-adaptation and hyper-specialization, +# effectively reducing overfitting. +# + +from sklearn.model_selection import train_test_split + + +def make_sin_dataset(dataset_size=100, test_size=0.4, noise_value=0.4, plot=False): + """1D regression problem y=sin(x*\pi)""" + # x-axis + x_ax = np.linspace(-1, 1, dataset_size) + y = [[np.sin(x * np.pi)] for x in x_ax] + np.random.seed(123) + # noise vector + noise = np.array([np.random.normal(0, 0.5, 1) for i in y]) * noise_value + X = np.array(x_ax) + y = np.array(y + noise) # apply noise + + # split the dataset + X_train, X_test, y_train, y_test = train_test_split( + X, y, test_size=test_size, random_state=40, shuffle=True + ) + + X_train = X_train.reshape(-1, 1) + X_test = X_test.reshape(-1, 1) + + y_train = y_train.reshape(-1, 1) + y_test = y_test.reshape(-1, 1) + + return X_train, X_test, y_train, y_test + + +from matplotlib import ticker + +X, X_test, y, y_test = make_sin_dataset(dataset_size=20, test_size=0.25) + + +fig, ax = plt.subplots() +plt.plot(X, y, "o", label="Training") +plt.plot(X_test, y_test, "o", label="Test") + +plt.plot( + np.linspace(-1, 1, 100), + [np.sin(x * np.pi) for x in np.linspace(-1, 1, 100)], + linestyle="dotted", + label=r"$\sin(x)$", +) +plt.ylabel(r"$y = \sin(\pi\cdot x) + \epsilon$") +plt.xlabel(r"$x$") +ax.xaxis.set_major_locator(ticker.MultipleLocator(0.5)) +ax.yaxis.set_major_locator(ticker.MultipleLocator(0.5)) +plt.legend() + +plt.show() + +###################################################################### +# Since our circuit is only able to provide outputs in the range :math:`[-1,1],` we rescale all the +# noisy data within this range. To do this we leverage the `MinMaxScaler` from `sklearn`. +# It is common practice to fit the scaler only from training data and then apply it also to the +# test. The reason behind this is that in general one only has knowledge about the training dataset. +# (If the training dataset is not exhaustively representative of the underlying distribution, +# this preprocessing may lead to some outliers in the test set to be scaled out of the desired range.) +# + +from sklearn.preprocessing import MinMaxScaler + +scaler = MinMaxScaler(feature_range=(-1, 1)) +y = scaler.fit_transform(y) +y_test = scaler.transform(y_test) + +# reshaping for computation +y = y.reshape(-1,) +y_test = y_test.reshape(-1,) + +###################################################################### +# Optimization +# ~~~~~~~~~~~~ +# +# At this point we have to set the hyperparameters of the optimization, namely the number of epochs, the +# learning rate, and the optimizer: +# + +import optax # optimization using jax + +epochs = 700 +optimizer = optax.adam(learning_rate=0.01) + +###################################################################### +# We define the cost function as the Mean Square Error: +# + + +@jax.jit +def calculate_mse_cost(X, y, theta, keep_rot): + yp = qnn(X, theta, keep_rot) + # depending on your version of Pennylane you may require the following line + ##### + yp = jnp.array(yp).T + ##### + cost = jnp.mean((yp - y) ** 2) + + return cost + + +# Optimization update step +@jax.jit +def optimizer_update(opt_state, params, x, y, keep_rot): + loss, grads = jax.value_and_grad(lambda theta: calculate_mse_cost(x, y, theta, keep_rot))( + params + ) + updates, opt_state = optimizer.update(grads, opt_state) + + params = optax.apply_updates(params, updates) + return params, opt_state, loss + + +###################################################################### +# Training the model +# ------------------ +# +# And now we can try to train the model. We execute different runs of the training to understand the +# average behaviour of quantum dropout. To see the effect of dropout we can set different values of +# ``layer_drop_rate`` and ``rot_drop_rate``: +# + +n_run = 3 +drop_rates = [(0.0, 0.0), (0.3, 0.2), (0.7, 0.7)] + +train_history = {} +test_history = {} +opt_params = {} + + +for layer_drop_rate, rot_drop_rate in drop_rates: + # initialization of some lists to store data + costs_per_comb = [] + test_costs_per_comb = [] + opt_params_per_comb = [] + # we execute multiple runs in order to see the average behaviour + for tmp_seed in range(seed, seed + n_run): + key = jax.random.PRNGKey(tmp_seed) + assert len(X.shape) == 2 # X must be a matrix + assert len(y.shape) == 1 # y must be an array + assert X.shape[0] == y.shape[0] # compatibility check + + # parameters initialization with gaussian ditribution + initial_params = jax.random.normal(key, shape=(layers * params_per_layer,)) + # update the random key + key = jax.random.split(key)[0] + + params = jnp.copy(initial_params) + + # optimizer initialization + opt_state = optimizer.init(initial_params) + + # lists for saving single run training and test cost trend + costs = [] + test_costs = [] + + for epoch in range(epochs): + # generate the list for dropout + keep_rot = make_dropout(key) + # update the random key + key = jax.random.split(key)[0] + + # optimization step + params, opt_state, cost = optimizer_update(opt_state, params, X, y, keep_rot) + + ############## performance evaluation ############# + # inference is done with the original model + # with all the gates + keep_rot = jnp.array( + [ + [list(range((n_qubits))) for j in range(1, inner_layers + 1)] + for i in range(layers) + ] + ) + # inference on train set + cost = calculate_mse_cost(X, y, params, keep_rot) + + costs.append(cost) + + # inference on test set + test_cost = calculate_mse_cost(X_test, y_test, params, keep_rot) + test_costs.append(test_cost) + + # we print updates every 5 iterations + if epoch % 5 == 0: + print( + f"{layer_drop_rate:.1f}-{rot_drop_rate:.1f}", + f"run {tmp_seed-seed} - epoch {epoch}/{epochs}", + f"--- Train cost:{cost:.5f}", + f"--- Test cost:{test_cost:.5f}", + end="\r", + ) + + costs_per_comb.append(costs) + test_costs_per_comb.append(test_costs) + opt_params_per_comb.append(params) + print() + costs_per_comb = np.array(costs_per_comb) + test_costs_per_comb = np.array(test_costs_per_comb) + opt_params_per_comb = np.array(opt_params_per_comb) + + train_history[(layer_drop_rate, rot_drop_rate)] = costs_per_comb + test_history[(layer_drop_rate, rot_drop_rate)] = test_costs_per_comb + opt_params[(layer_drop_rate, rot_drop_rate)] = opt_params_per_comb + +###################################################################### +# Performance evaluation +# ---------------------- +# +# Let’s compare the difference in performance with a plot: +# + +fig, axs = plt.subplots(1, 2, figsize=(12, 4)) +plt.subplots_adjust(wspace=0.05) +axs[0].set_title("MSE train") +for k, v in train_history.items(): + train_losses = np.array(v) + mean_train_history = np.mean(train_losses, axis=0) + std_train_history = np.std(train_losses, axis=0,) + + mean_train_history = mean_train_history.reshape((epochs,)) + std_train_history = std_train_history.reshape((epochs,)) + + # shadow standard deviation + axs[0].fill_between( + range(epochs), + mean_train_history - std_train_history, + mean_train_history + std_train_history, + alpha=0.2, + ) + # average trend + axs[0].plot(range(epochs), mean_train_history, label=f"{k}") # Avg Loss + +axs[1].set_title("MSE test") +for k, v in test_history.items(): + test_losses = np.array(v) + mean_test_history = np.mean(test_losses, axis=0) + std_test_history = np.std(test_losses, axis=0,) + + mean_test_history = mean_test_history.reshape((epochs,)) + std_test_history = std_test_history.reshape((epochs,)) + + # shadow standard deviation + axs[1].fill_between( + range(epochs), + mean_test_history - std_test_history, + mean_test_history + std_test_history, + alpha=0.2, + ) + # averange trend + axs[1].plot(range(epochs), mean_test_history, label=f"{k}") # Avg Loss + +axs[0].legend(loc="upper center", bbox_to_anchor=(1.01, 1.25), ncol=4, fancybox=True, shadow=True) + +for ax in axs.flat: + ax.set_xlabel("Epochs") + ax.set_ylabel("MSE") + ax.set_yscale("log") + ax.set_ylim([1e-3, 0.6]) + ax.label_outer() + +plt.subplots_adjust(bottom=0.3) + +plt.show() + +###################################################################### +# On the left you can see that without dropout there is a deep minimization of the training loss, +# moderate values of dropout converge, whereas high drop probabilities impede any learning. On +# the right, we can see the difference in generalization during the optimization process. Standard +# training without dropout initially reaches a low value of generalization error, but as the +# model starts to learn the noise in the training data (overfitting), the generalization error grows +# back. Oppositely, moderate values of dropout enable generalization errors comparable to the respective +# training ones. As the learning is not successful for elevated drop probabilities, the generalization +# error is huge. It is interesting to notice that the “not-learning” error is very close to the final +# error of the QNN trained without dropout. +# +# Hence, one can conclude that low values of dropout greatly improve the generalization performance of +# the model and remove overfitting, even if the randomness of the technique inevitably makes the +# training a little noisy. On the other hand, high drop probabilities only hinder the training +# process. +# +# Validation +# ~~~~~~~~~~ +# +# To validate the technique we can also check how the model predicts in the whole :math:`[-1,1]` range +# with and without quantum dropout. +# + +X, X_test, y, y_test = make_sin_dataset(dataset_size=20, test_size=0.25) + +# spanning the whole range +x_ax = jnp.linspace(-1, 1, 100).reshape(100, 1) + +# selecting which run we want to plot +run = 1 + +fig, ax = plt.subplots() +styles = ["dashed", "-.", "solid", "-."] +for i, k in enumerate(train_history.keys()): + if k[0] == 0.3: + alpha = 1 + else: + alpha = 0.5 + # predicting and rescaling + yp = scaler.inverse_transform(qnn(x_ax, opt_params[k][run], keep_rot).reshape(-1, 1)) + plt.plot([[i] for i in np.linspace(-1, 1, 100)], yp, label=k, alpha=alpha, linestyle=styles[i]) + +plt.scatter(X, y, label="Training", zorder=10) +plt.scatter(X_test, y_test, label="Test", zorder=10) + +ylabel = r"$y = \sin(\pi\cdot x) + \epsilon$" +plt.xlabel("x", fontsize="medium") +plt.ylabel(ylabel, fontsize="medium") +plt.legend() +ax.xaxis.set_major_locator(ticker.MultipleLocator(0.5)) +ax.yaxis.set_major_locator(ticker.MultipleLocator(0.5)) + +plt.show() + +###################################################################### +# The model without dropout overfits the noisy data by trying to exactly predict each of them, +# whereas dropout actually mitigates overfitting and makes the approximation of the underlying sinusoidal +# function way smoother. +# +# Conclusion +# ---------------------- +# In this demo, we explained the basic idea behind quantum dropout and +# how to avoid overfitting by randomly "dropping" some rotation gates +# of a QNN during the training phase. We invite you to check out the paper [#dropout]_ +# for more dropout techniques and additional analysis. Try it yourself and develop new +# dropout strategies. +# +# +# References +# ---------- +# +# .. [#dropout] Scala, F., Ceschini, A., Panella, M., & Gerace, D. (2023). +# *A General Approach to Dropout in Quantum Neural Networks*. +# `Adv. Quantum Technol., 2300220 `__. +# +# .. [#Hinton2012] Hinton, G., Srivastava, N., Krizhevsky, A., Sutskever, I., & Salakhutdinov, R. (2012). +# *Improving neural networks by preventing co-adaptation of feature detectors*. +# `arXiv:1207.0580. `__. +# +# .. [#Srivastava2014] Srivastava, N., Hinton, G., Krizhevsky, A., Sutskever, I., & Salakhutdinov, R. (2014). +# *Dropout: A Simple Way to Prevent Neural Networks from Overfitting*. +# `Journal of Machine Learning Research, 15(56):1929−1958. `__. +# +# .. [#Kiani2020] Kiani,B. T., Lloyd, S., & Maity, R. (2020). +# *Learning Unitaries by Gradient Descent*. +# `arXiv: 2001.11897. `__. +# +# .. [#Larocca2023] Larocca, M., Ju, N., García-Martín, D., Coles, P. J., & Cerezo, M. (2023). +# *Theory of overparametrization in quantum neural networks*. +# `Nat. Comp. Science, 3, 542–551. `__. +# +# About the author +# ---------------- diff --git a/demonstrations_v2/tutorial_quantum_natural_gradient/demo.py b/demonstrations_v2/tutorial_quantum_natural_gradient/demo.py index d65bd9e2d5..f693b3d3aa 100644 --- a/demonstrations_v2/tutorial_quantum_natural_gradient/demo.py +++ b/demonstrations_v2/tutorial_quantum_natural_gradient/demo.py @@ -1,499 +1,499 @@ -r""" - -.. _quantum_natural_gradient: - -Quantum natural gradient -======================== - -.. meta:: - :property="og:description": The quantum natural gradient method can achieve faster convergence for quantum machine - learning problems by taking into account the intrinsic geometry of qubits. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qng_optimization.png - -.. related:: - - tutorial_backprop Quantum gradients with backpropagation - tutorial_vqe_qng Accelerating VQE with quantum natural gradient - -*Author: Josh Izaac — Posted: 11 October 2019. Last updated: 25 January 2021.* - -This example demonstrates the quantum natural gradient optimization technique -for variational quantum circuits, originally proposed in -`Stokes et al. (2019) `__. - -Background ----------- - -The most successful class of quantum algorithms for use on near-term noisy quantum hardware -is the so-called variational quantum algorithm. As laid out in the -`Concepts section `__, in variational quantum algorithms -a low-depth parametrized quantum circuit ansatz is chosen, and a problem-specific -observable measured. A classical optimization loop is then used to find -the set of quantum parameters that *minimize* a particular measurement expectation value -of the quantum device. Examples of such algorithms include the :doc:`variational quantum -eigensolver (VQE) `, the -`quantum approximate optimization algorithm (QAOA) `__, -and :ref:`quantum neural networks (QNN) `. - -Most recent demonstrations -of variational quantum algorithms have used gradient-free classical optimization -methods, such as the Nelder-Mead algorithm. However, the parameter-shift rule -(as implemented in PennyLane) allows the user to automatically compute -analytic gradients of quantum circuits. This opens up the possibility to train -quantum computing hardware using gradient descent—the same method used to train -deep learning models. -Though one caveat has surfaced with gradient descent — how do we choose the optimal -step size for our variational quantum algorithms, to ensure successful and -efficient optimization? - -The natural gradient -^^^^^^^^^^^^^^^^^^^^ - -In standard gradient descent, each optimization step is given by - -.. math:: \theta_{t+1} = \theta_t -\eta \nabla \mathcal{L}(\theta), - -where :math:`\mathcal{L}(\theta)` is the cost as a function of -the parameters :math:`\theta,` and :math:`\eta` is the learning rate -or step size. In essence, each optimization step calculates the -steepest descent direction around the local value of :math:`\theta_t` -in the parameter space, and updates :math:`\theta_t\rightarrow \theta_{t+1}` -by this vector. - -The problem with the above approach is that each optimization step -is strongly connected to a *Euclidean geometry* on the parameter space. -The parametrization is not unique, and different parametrizations can distort -distances within the optimization landscape. - -For example, consider the following cost function :math:`\mathcal{L},` parametrized -using two different coordinate systems, :math:`(\theta_0, \theta_1),` and -:math:`(\phi_0, \phi_1):` - -| - -.. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng7.png - :align: center - :width: 90% - :target: javascript:void(0) - -| - -Performing gradient descent in the :math:`(\theta_0, \theta_1)` parameter -space, we are updating each parameter by the same Euclidean distance, -and not taking into account the fact that the cost function might vary at a different -rate with respect to each parameter. - -Instead, if we perform a change of coordinate system (re-parametrization) -of the cost function, we might find a parameter space where variations in :math:`\mathcal{L}` -are similar across different parameters. This is the case with the new parametrization -:math:`(\phi_0, \phi_1);` the cost function is unchanged, -but we now have a nicer geometry in which to perform gradient descent, and a more -informative stepsize. This leads to faster convergence, and can help avoid optimization -becoming stuck in local minima. For a more in-depth explanation, -including why the parameter space might not be best represented by a Euclidean space, -see `Yamamoto (2019) `__. - -However, what if we avoid gradient descent in the parameter space altogether? -If we instead consider the optimization problem as a -probability distribution of possible output values given an input -(i.e., `maximum likelihood estimation `_), -a better approach is to perform the gradient descent in the *distribution space*, which is -dimensionless and invariant with respect to the parametrization. As a result, -each optimization step will always choose the optimum step-size for every -parameter, regardless of the parametrization. - -In classical neural networks, the above process is known as -*natural gradient descent*, and was first introduced by -`Amari (1998) `__. -The standard gradient descent is modified as follows: - -.. math:: \theta_{t+1} = \theta_t - \eta F^{-1}\nabla \mathcal{L}(\theta), - -where :math:`F` is the `Fisher information matrix `__. -The Fisher information matrix acts as a metric tensor, transforming the -steepest descent in the Euclidean parameter space to the steepest descent in the -distribution space. - -The quantum analog -^^^^^^^^^^^^^^^^^^ - -In a similar vein, it has been shown that the standard Euclidean geometry -is sub-optimal for optimization of quantum variational algorithms -`(Harrow and Napp, 2019) `__. -The space of quantum states instead possesses a unique invariant metric -tensor known as the Fubini-Study metric tensor :math:`g_{ij},` which can be used to -construct a quantum analog to natural gradient descent: - -.. math:: \theta_{t+1} = \theta_t - \eta g^{+}(\theta_t)\nabla \mathcal{L}(\theta), - -where :math:`g^{+}` refers to the pseudo-inverse. - -.. note:: - - It can be shown that the Fubini-Study metric tensor reduces - to the Fisher information matrix in the classical limit. - - Furthermore, in the limit where :math:`\eta\rightarrow 0,` - the dynamics of the system are equivalent to imaginary-time - evolution within the variational subspace, as proposed in - `McArdle et al. (2018) `__. - -""" - -############################################################################## -# Block-diagonal metric tensor -# ---------------------------- -# -# A block-diagonal approximation to the Fubini-Study metric tensor -# of a variational quantum circuit can be evaluated on quantum hardware. -# -# Consider a variational quantum circuit -# -# .. math:: -# -# U(\mathbf{\theta})|\psi_0\rangle = V_L(\theta_L) W_L V_{L-1}(\theta_{L-1}) W_{L-1} -# \cdots V_{\ell}(\theta_{\ell}) W_{\ell} \cdots V_{0}(\theta_{0}) W_{0} |\psi_0\rangle -# -# where -# -# * :math:`|\psi_0\rangle` is the initial state, -# * :math:`W_\ell` are layers of non-parametrized quantum gates, -# * :math:`V_\ell(\theta_\ell)` are layers of parametrized quantum gates -# with :math:`n_\ell` parameters :math:`\theta_\ell = \{\theta^{(\ell)}_0, \dots, \theta^{(\ell)}_n\}.` -# -# Further, assume all parametrized gates can be written in the form -# :math:`X(\theta^{(\ell)}_{i}) = e^{i\theta^{(\ell)}_{i} K^{(\ell)}_i},` -# where :math:`K^{(\ell)}_i` is the *generator* of the parametrized operation. -# -# For each parametric layer :math:`\ell` in the variational quantum circuit -# the :math:`n_\ell\times n_\ell` block-diagonal submatrix -# of the Fubini-Study tensor :math:`g_{ij}^{(\ell)}` is calculated by: -# -# .. math:: -# -# g_{ij}^{(\ell)} = \langle \psi_{\ell-1} | K_i K_j | \psi_{\ell-1} \rangle -# - \langle \psi_{\ell-1} | K_i | \psi_{\ell-1}\rangle -# \langle \psi_{\ell-1} |K_j | \psi_{\ell-1}\rangle -# -# where -# -# .. math:: -# -# | \psi_{\ell-1}\rangle = V_{\ell-1}(\theta_{\ell-1}) W_{\ell-1} \cdots V_{0}(\theta_{0}) W_{0} |\psi_0\rangle. -# -# (that is, :math:`|\psi_{\ell-1}\rangle` is the quantum state prior to the application -# of parameterized layer :math:`\ell`), and we have :math:`K_i \equiv K_i^{(\ell)}` for brevity. -# -# Let's consider a small variational quantum circuit example coded in PennyLane: - -import numpy as np -import pennylane as qml -from pennylane import numpy as pnp - -dev = qml.device("lightning.qubit", wires=3) - - -@qml.qnode(dev, interface="autograd", diff_method="parameter-shift") -def circuit(params): - # |psi_0>: state preparation - qml.RY(np.pi / 4, wires=0) - qml.RY(np.pi / 3, wires=1) - qml.RY(np.pi / 7, wires=2) - - # V0(theta0, theta1): Parametrized layer 0 - qml.RZ(params[0], wires=0) - qml.RZ(params[1], wires=1) - - # W1: non-parametrized gates - qml.CNOT(wires=[0, 1]) - qml.CNOT(wires=[1, 2]) - - # V_1(theta2, theta3): Parametrized layer 1 - qml.RY(params[2], wires=1) - qml.RX(params[3], wires=2) - - # W2: non-parametrized gates - qml.CNOT(wires=[0, 1]) - qml.CNOT(wires=[1, 2]) - - return qml.expval(qml.PauliY(0)) - -# Use pennylane.numpy for trainable parameters -params = pnp.array([0.432, -0.123, 0.543, 0.233]) - -############################################################################## -# The above circuit consists of 4 parameters, with two distinct parametrized -# layers of 2 parameters each. -# -# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng1.png -# :align: center -# :width: 90% -# :target: javascript:void(0) -# -# | -# -# (Note that in this example, the first non-parametrized layer :math:`W_0` -# is simply the identity.) Since there are two layers, each with two parameters, -# the block-diagonal approximation consists of two -# :math:`2\times 2` matrices, :math:`g^{(0)}` and :math:`g^{(1)}.` -# -# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng2.png -# :align: center -# :width: 30% -# :target: javascript:void(0) -# -# To compute the first block-diagonal :math:`g^{(0)},` we create subcircuits consisting -# of all gates prior to the layer, and observables corresponding to -# the *generators* of the gates in the layer: -# -# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng3.png -# :align: center -# :width: 30% -# :target: javascript:void(0) - -g0 = np.zeros([2, 2]) - - -def layer0_subcircuit(params): - """This function contains all gates that - precede parametrized layer 0""" - qml.RY(np.pi / 4, wires=0) - qml.RY(np.pi / 3, wires=1) - qml.RY(np.pi / 7, wires=2) - - -############################################################################## -# We then post-process the measurement results in order to determine :math:`g^{(0)},` -# as follows. -# -# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng4.png -# :align: center -# :width: 50% -# :target: javascript:void(0) -# -# We can see that the diagonal terms are simply given by the variance: - - -@qml.qnode(dev, interface="autograd") -def layer0_diag(params): - layer0_subcircuit(params) - return qml.var(qml.PauliZ(0)), qml.var(qml.PauliZ(1)) - - -# calculate the diagonal terms -varK0, varK1 = layer0_diag(params) -g0[0, 0] = varK0 / 4 -g0[1, 1] = varK1 / 4 - -############################################################################## -# The following two subcircuits are then used to calculate the -# off-diagonal covariance terms of :math:`g^{(0)}:` - - -@qml.qnode(dev, interface="autograd") -def layer0_off_diag_single(params): - layer0_subcircuit(params) - return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)) - - -@qml.qnode(dev, interface="autograd") -def layer0_off_diag_double(params): - layer0_subcircuit(params) - ZZ = np.kron(np.diag([1, -1]), np.diag([1, -1])) - return qml.expval(qml.Hermitian(ZZ, wires=[0, 1])) - - -# calculate the off-diagonal terms -exK0, exK1 = layer0_off_diag_single(params) -exK0K1 = layer0_off_diag_double(params) - -g0[0, 1] = (exK0K1 - exK0 * exK1) / 4 -g0[1, 0] = (exK0K1 - exK0 * exK1) / 4 - -############################################################################## -# Note that, by definition, the block-diagonal matrices must be real and -# symmetric. -# -# We can repeat the above process to compute :math:`g^{(1)}.` The subcircuit -# required is given by -# -# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng8.png -# :align: center -# :width: 50% -# :target: javascript:void(0) - -g1 = np.zeros([2, 2]) - - -def layer1_subcircuit(params): - """This function contains all gates that - precede parametrized layer 1""" - # |psi_0>: state preparation - qml.RY(np.pi / 4, wires=0) - qml.RY(np.pi / 3, wires=1) - qml.RY(np.pi / 7, wires=2) - - # V0(theta0, theta1): Parametrized layer 0 - qml.RZ(params[0], wires=0) - qml.RZ(params[1], wires=1) - - # W1: non-parametrized gates - qml.CNOT(wires=[0, 1]) - qml.CNOT(wires=[1, 2]) - - -############################################################################## -# Using this subcircuit, we can now generate the submatrix :math:`g^{(1)}.` -# -# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng5.png -# :align: center -# :width: 50% -# :target: javascript:void(0) - - -@qml.qnode(dev, interface="autograd") -def layer1_diag(params): - layer1_subcircuit(params) - return qml.var(qml.PauliY(1)), qml.var(qml.PauliX(2)) - - -############################################################################## -# As previously, the diagonal terms are simply given by the variance, - -varK0, varK1 = layer1_diag(params) -g1[0, 0] = varK0 / 4 -g1[1, 1] = varK1 / 4 - - -############################################################################## -# while the off-diagonal terms require covariance between the two -# observables to be computed. - - -@qml.qnode(dev, interface="autograd") -def layer1_off_diag_single(params): - layer1_subcircuit(params) - return qml.expval(qml.PauliY(1)), qml.expval(qml.PauliX(2)) - - -@qml.qnode(dev, interface="autograd") -def layer1_off_diag_double(params): - layer1_subcircuit(params) - X = np.array([[0, 1], [1, 0]]) - Y = np.array([[0, -1j], [1j, 0]]) - YX = np.kron(Y, X) - return qml.expval(qml.Hermitian(YX, wires=[1, 2])) - - -# calculate the off-diagonal terms -exK0, exK1 = layer1_off_diag_single(params) -exK0K1 = layer1_off_diag_double(params) - -g1[0, 1] = (exK0K1 - exK0 * exK1) / 4 -g1[1, 0] = g1[0, 1] - - -############################################################################## -# Putting this altogether, the block-diagonal approximation to the Fubini-Study -# metric tensor for this variational quantum circuit is -from scipy.linalg import block_diag - -g = block_diag(g0, g1) -print(np.round(g, 8)) - - -############################################################################## -# PennyLane contains a built-in function for computing the Fubini-Study metric -# tensor, :func:`~.pennylane.metric_tensor`, which -# we can use to verify this result: -print(np.round(qml.metric_tensor(circuit, approx="block-diag")(params), 8)) - -############################################################################## -# As opposed to our manual computation, which required 6 different quantum -# evaluations, the PennyLane Fubini-Study metric tensor implementation -# requires only 2 quantum evaluations, one per layer. This is done by -# automatically detecting the layer structure, and noting that every -# observable that must be measured commutes, allowing for simultaneous measurement. -# -# Therefore, by combining the quantum natural gradient optimizer with the analytic -# parameter-shift rule to optimize a variational circuit with :math:`d` parameters -# and :math:`L` parametrized layers, a total of :math:`2d+L` quantum evaluations -# are required per optimization step. -# -# Note that the :func:`~.pennylane.metric_tensor` function also supports computing the diagonal -# approximation to the metric tensor: -print(qml.metric_tensor(circuit, approx='diag')(params)) - -############################################################################## -# Furthermore, the returned metric tensor is **full differentiable**; include it -# in your cost function, and train or optimize its value! - -############################################################################## -# Quantum natural gradient optimization -# ------------------------------------- -# -# PennyLane provides an implementation of the quantum natural gradient -# optimizer, :class:`~.pennylane.QNGOptimizer`. Let's compare the optimization convergence -# of the QNG Optimizer and the :class:`~.pennylane.GradientDescentOptimizer` for the simple variational -# circuit above. - -steps = 200 -init_params = pnp.array([0.432, -0.123, 0.543, 0.233], requires_grad=True) - -############################################################################## -# Performing vanilla gradient descent: - -gd_cost = [] -opt = qml.GradientDescentOptimizer(0.01) - -theta = init_params -for _ in range(steps): - theta = opt.step(circuit, theta) - gd_cost.append(circuit(theta)) - -############################################################################## -# Performing quantum natural gradient descent: - -qng_cost = [] -opt = qml.QNGOptimizer(0.01) - -theta = init_params -for _ in range(steps): - theta = opt.step(circuit, theta) - qng_cost.append(circuit(theta)) - - -############################################################################## -# Plotting the cost vs optimization step for both optimization strategies: -from matplotlib import pyplot as plt - -plt.style.use("seaborn") -plt.plot(gd_cost, "b", label="Vanilla gradient descent") -plt.plot(qng_cost, "g", label="Quantum natural gradient descent") - -plt.ylabel("Cost function value") -plt.xlabel("Optimization steps") -plt.legend() -plt.show() - -############################################################################## -# References -# ---------- -# -# 1. Shun-Ichi Amari. "Natural gradient works efficiently in learning." -# `Neural computation 10.2, 251-276 `__, 1998. -# -# 2. James Stokes, Josh Izaac, Nathan Killoran, Giuseppe Carleo. -# "Quantum Natural Gradient." `arXiv:1909.02108 `__, 2019. -# -# 3. Aram Harrow and John Napp. "Low-depth gradient measurements can improve -# convergence in variational hybrid quantum-classical algorithms." -# `arXiv:1901.05374 `__, 2019. -# -# 4. Naoki Yamamoto. "On the natural gradient for variational quantum eigensolver." -# `arXiv:1909.05074 `__, 2019. -# -# -# About the author -# ---------------- +r""" + +.. _quantum_natural_gradient: + +Quantum natural gradient +======================== + +.. meta:: + :property="og:description": The quantum natural gradient method can achieve faster convergence for quantum machine + learning problems by taking into account the intrinsic geometry of qubits. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qng_optimization.png + +.. related:: + + tutorial_backprop Quantum gradients with backpropagation + tutorial_vqe_qng Accelerating VQE with quantum natural gradient + +*Author: Josh Izaac — Posted: 11 October 2019. Last updated: 25 January 2021.* + +This example demonstrates the quantum natural gradient optimization technique +for variational quantum circuits, originally proposed in +`Stokes et al. (2019) `__. + +Background +---------- + +The most successful class of quantum algorithms for use on near-term noisy quantum hardware +is the so-called variational quantum algorithm. As laid out in the +`Concepts section `__, in variational quantum algorithms +a low-depth parametrized quantum circuit ansatz is chosen, and a problem-specific +observable measured. A classical optimization loop is then used to find +the set of quantum parameters that *minimize* a particular measurement expectation value +of the quantum device. Examples of such algorithms include the :doc:`variational quantum +eigensolver (VQE) `, the +`quantum approximate optimization algorithm (QAOA) `__, +and :ref:`quantum neural networks (QNN) `. + +Most recent demonstrations +of variational quantum algorithms have used gradient-free classical optimization +methods, such as the Nelder-Mead algorithm. However, the parameter-shift rule +(as implemented in PennyLane) allows the user to automatically compute +analytic gradients of quantum circuits. This opens up the possibility to train +quantum computing hardware using gradient descent—the same method used to train +deep learning models. +Though one caveat has surfaced with gradient descent — how do we choose the optimal +step size for our variational quantum algorithms, to ensure successful and +efficient optimization? + +The natural gradient +^^^^^^^^^^^^^^^^^^^^ + +In standard gradient descent, each optimization step is given by + +.. math:: \theta_{t+1} = \theta_t -\eta \nabla \mathcal{L}(\theta), + +where :math:`\mathcal{L}(\theta)` is the cost as a function of +the parameters :math:`\theta,` and :math:`\eta` is the learning rate +or step size. In essence, each optimization step calculates the +steepest descent direction around the local value of :math:`\theta_t` +in the parameter space, and updates :math:`\theta_t\rightarrow \theta_{t+1}` +by this vector. + +The problem with the above approach is that each optimization step +is strongly connected to a *Euclidean geometry* on the parameter space. +The parametrization is not unique, and different parametrizations can distort +distances within the optimization landscape. + +For example, consider the following cost function :math:`\mathcal{L},` parametrized +using two different coordinate systems, :math:`(\theta_0, \theta_1),` and +:math:`(\phi_0, \phi_1):` + +| + +.. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng7.png + :align: center + :width: 90% + :target: javascript:void(0) + +| + +Performing gradient descent in the :math:`(\theta_0, \theta_1)` parameter +space, we are updating each parameter by the same Euclidean distance, +and not taking into account the fact that the cost function might vary at a different +rate with respect to each parameter. + +Instead, if we perform a change of coordinate system (re-parametrization) +of the cost function, we might find a parameter space where variations in :math:`\mathcal{L}` +are similar across different parameters. This is the case with the new parametrization +:math:`(\phi_0, \phi_1);` the cost function is unchanged, +but we now have a nicer geometry in which to perform gradient descent, and a more +informative stepsize. This leads to faster convergence, and can help avoid optimization +becoming stuck in local minima. For a more in-depth explanation, +including why the parameter space might not be best represented by a Euclidean space, +see `Yamamoto (2019) `__. + +However, what if we avoid gradient descent in the parameter space altogether? +If we instead consider the optimization problem as a +probability distribution of possible output values given an input +(i.e., `maximum likelihood estimation `_), +a better approach is to perform the gradient descent in the *distribution space*, which is +dimensionless and invariant with respect to the parametrization. As a result, +each optimization step will always choose the optimum step-size for every +parameter, regardless of the parametrization. + +In classical neural networks, the above process is known as +*natural gradient descent*, and was first introduced by +`Amari (1998) `__. +The standard gradient descent is modified as follows: + +.. math:: \theta_{t+1} = \theta_t - \eta F^{-1}\nabla \mathcal{L}(\theta), + +where :math:`F` is the `Fisher information matrix `__. +The Fisher information matrix acts as a metric tensor, transforming the +steepest descent in the Euclidean parameter space to the steepest descent in the +distribution space. + +The quantum analog +^^^^^^^^^^^^^^^^^^ + +In a similar vein, it has been shown that the standard Euclidean geometry +is sub-optimal for optimization of quantum variational algorithms +`(Harrow and Napp, 2019) `__. +The space of quantum states instead possesses a unique invariant metric +tensor known as the Fubini-Study metric tensor :math:`g_{ij},` which can be used to +construct a quantum analog to natural gradient descent: + +.. math:: \theta_{t+1} = \theta_t - \eta g^{+}(\theta_t)\nabla \mathcal{L}(\theta), + +where :math:`g^{+}` refers to the pseudo-inverse. + +.. note:: + + It can be shown that the Fubini-Study metric tensor reduces + to the Fisher information matrix in the classical limit. + + Furthermore, in the limit where :math:`\eta\rightarrow 0,` + the dynamics of the system are equivalent to imaginary-time + evolution within the variational subspace, as proposed in + `McArdle et al. (2018) `__. + +""" + +############################################################################## +# Block-diagonal metric tensor +# ---------------------------- +# +# A block-diagonal approximation to the Fubini-Study metric tensor +# of a variational quantum circuit can be evaluated on quantum hardware. +# +# Consider a variational quantum circuit +# +# .. math:: +# +# U(\mathbf{\theta})|\psi_0\rangle = V_L(\theta_L) W_L V_{L-1}(\theta_{L-1}) W_{L-1} +# \cdots V_{\ell}(\theta_{\ell}) W_{\ell} \cdots V_{0}(\theta_{0}) W_{0} |\psi_0\rangle +# +# where +# +# * :math:`|\psi_0\rangle` is the initial state, +# * :math:`W_\ell` are layers of non-parametrized quantum gates, +# * :math:`V_\ell(\theta_\ell)` are layers of parametrized quantum gates +# with :math:`n_\ell` parameters :math:`\theta_\ell = \{\theta^{(\ell)}_0, \dots, \theta^{(\ell)}_n\}.` +# +# Further, assume all parametrized gates can be written in the form +# :math:`X(\theta^{(\ell)}_{i}) = e^{i\theta^{(\ell)}_{i} K^{(\ell)}_i},` +# where :math:`K^{(\ell)}_i` is the *generator* of the parametrized operation. +# +# For each parametric layer :math:`\ell` in the variational quantum circuit +# the :math:`n_\ell\times n_\ell` block-diagonal submatrix +# of the Fubini-Study tensor :math:`g_{ij}^{(\ell)}` is calculated by: +# +# .. math:: +# +# g_{ij}^{(\ell)} = \langle \psi_{\ell-1} | K_i K_j | \psi_{\ell-1} \rangle +# - \langle \psi_{\ell-1} | K_i | \psi_{\ell-1}\rangle +# \langle \psi_{\ell-1} |K_j | \psi_{\ell-1}\rangle +# +# where +# +# .. math:: +# +# | \psi_{\ell-1}\rangle = V_{\ell-1}(\theta_{\ell-1}) W_{\ell-1} \cdots V_{0}(\theta_{0}) W_{0} |\psi_0\rangle. +# +# (that is, :math:`|\psi_{\ell-1}\rangle` is the quantum state prior to the application +# of parameterized layer :math:`\ell`), and we have :math:`K_i \equiv K_i^{(\ell)}` for brevity. +# +# Let's consider a small variational quantum circuit example coded in PennyLane: + +import numpy as np +import pennylane as qml +from pennylane import numpy as pnp + +dev = qml.device("lightning.qubit", wires=3) + + +@qml.qnode(dev, interface="autograd", diff_method="parameter-shift") +def circuit(params): + # |psi_0>: state preparation + qml.RY(np.pi / 4, wires=0) + qml.RY(np.pi / 3, wires=1) + qml.RY(np.pi / 7, wires=2) + + # V0(theta0, theta1): Parametrized layer 0 + qml.RZ(params[0], wires=0) + qml.RZ(params[1], wires=1) + + # W1: non-parametrized gates + qml.CNOT(wires=[0, 1]) + qml.CNOT(wires=[1, 2]) + + # V_1(theta2, theta3): Parametrized layer 1 + qml.RY(params[2], wires=1) + qml.RX(params[3], wires=2) + + # W2: non-parametrized gates + qml.CNOT(wires=[0, 1]) + qml.CNOT(wires=[1, 2]) + + return qml.expval(qml.PauliY(0)) + +# Use pennylane.numpy for trainable parameters +params = pnp.array([0.432, -0.123, 0.543, 0.233]) + +############################################################################## +# The above circuit consists of 4 parameters, with two distinct parametrized +# layers of 2 parameters each. +# +# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng1.png +# :align: center +# :width: 90% +# :target: javascript:void(0) +# +# | +# +# (Note that in this example, the first non-parametrized layer :math:`W_0` +# is simply the identity.) Since there are two layers, each with two parameters, +# the block-diagonal approximation consists of two +# :math:`2\times 2` matrices, :math:`g^{(0)}` and :math:`g^{(1)}.` +# +# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng2.png +# :align: center +# :width: 30% +# :target: javascript:void(0) +# +# To compute the first block-diagonal :math:`g^{(0)},` we create subcircuits consisting +# of all gates prior to the layer, and observables corresponding to +# the *generators* of the gates in the layer: +# +# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng3.png +# :align: center +# :width: 30% +# :target: javascript:void(0) + +g0 = np.zeros([2, 2]) + + +def layer0_subcircuit(params): + """This function contains all gates that + precede parametrized layer 0""" + qml.RY(np.pi / 4, wires=0) + qml.RY(np.pi / 3, wires=1) + qml.RY(np.pi / 7, wires=2) + + +############################################################################## +# We then post-process the measurement results in order to determine :math:`g^{(0)},` +# as follows. +# +# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng4.png +# :align: center +# :width: 50% +# :target: javascript:void(0) +# +# We can see that the diagonal terms are simply given by the variance: + + +@qml.qnode(dev, interface="autograd") +def layer0_diag(params): + layer0_subcircuit(params) + return qml.var(qml.PauliZ(0)), qml.var(qml.PauliZ(1)) + + +# calculate the diagonal terms +varK0, varK1 = layer0_diag(params) +g0[0, 0] = varK0 / 4 +g0[1, 1] = varK1 / 4 + +############################################################################## +# The following two subcircuits are then used to calculate the +# off-diagonal covariance terms of :math:`g^{(0)}:` + + +@qml.qnode(dev, interface="autograd") +def layer0_off_diag_single(params): + layer0_subcircuit(params) + return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)) + + +@qml.qnode(dev, interface="autograd") +def layer0_off_diag_double(params): + layer0_subcircuit(params) + ZZ = np.kron(np.diag([1, -1]), np.diag([1, -1])) + return qml.expval(qml.Hermitian(ZZ, wires=[0, 1])) + + +# calculate the off-diagonal terms +exK0, exK1 = layer0_off_diag_single(params) +exK0K1 = layer0_off_diag_double(params) + +g0[0, 1] = (exK0K1 - exK0 * exK1) / 4 +g0[1, 0] = (exK0K1 - exK0 * exK1) / 4 + +############################################################################## +# Note that, by definition, the block-diagonal matrices must be real and +# symmetric. +# +# We can repeat the above process to compute :math:`g^{(1)}.` The subcircuit +# required is given by +# +# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng8.png +# :align: center +# :width: 50% +# :target: javascript:void(0) + +g1 = np.zeros([2, 2]) + + +def layer1_subcircuit(params): + """This function contains all gates that + precede parametrized layer 1""" + # |psi_0>: state preparation + qml.RY(np.pi / 4, wires=0) + qml.RY(np.pi / 3, wires=1) + qml.RY(np.pi / 7, wires=2) + + # V0(theta0, theta1): Parametrized layer 0 + qml.RZ(params[0], wires=0) + qml.RZ(params[1], wires=1) + + # W1: non-parametrized gates + qml.CNOT(wires=[0, 1]) + qml.CNOT(wires=[1, 2]) + + +############################################################################## +# Using this subcircuit, we can now generate the submatrix :math:`g^{(1)}.` +# +# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng5.png +# :align: center +# :width: 50% +# :target: javascript:void(0) + + +@qml.qnode(dev, interface="autograd") +def layer1_diag(params): + layer1_subcircuit(params) + return qml.var(qml.PauliY(1)), qml.var(qml.PauliX(2)) + + +############################################################################## +# As previously, the diagonal terms are simply given by the variance, + +varK0, varK1 = layer1_diag(params) +g1[0, 0] = varK0 / 4 +g1[1, 1] = varK1 / 4 + + +############################################################################## +# while the off-diagonal terms require covariance between the two +# observables to be computed. + + +@qml.qnode(dev, interface="autograd") +def layer1_off_diag_single(params): + layer1_subcircuit(params) + return qml.expval(qml.PauliY(1)), qml.expval(qml.PauliX(2)) + + +@qml.qnode(dev, interface="autograd") +def layer1_off_diag_double(params): + layer1_subcircuit(params) + X = np.array([[0, 1], [1, 0]]) + Y = np.array([[0, -1j], [1j, 0]]) + YX = np.kron(Y, X) + return qml.expval(qml.Hermitian(YX, wires=[1, 2])) + + +# calculate the off-diagonal terms +exK0, exK1 = layer1_off_diag_single(params) +exK0K1 = layer1_off_diag_double(params) + +g1[0, 1] = (exK0K1 - exK0 * exK1) / 4 +g1[1, 0] = g1[0, 1] + + +############################################################################## +# Putting this altogether, the block-diagonal approximation to the Fubini-Study +# metric tensor for this variational quantum circuit is +from scipy.linalg import block_diag + +g = block_diag(g0, g1) +print(np.round(g, 8)) + + +############################################################################## +# PennyLane contains a built-in function for computing the Fubini-Study metric +# tensor, :func:`~.pennylane.metric_tensor`, which +# we can use to verify this result: +print(np.round(qml.metric_tensor(circuit, approx="block-diag")(params), 8)) + +############################################################################## +# As opposed to our manual computation, which required 6 different quantum +# evaluations, the PennyLane Fubini-Study metric tensor implementation +# requires only 2 quantum evaluations, one per layer. This is done by +# automatically detecting the layer structure, and noting that every +# observable that must be measured commutes, allowing for simultaneous measurement. +# +# Therefore, by combining the quantum natural gradient optimizer with the analytic +# parameter-shift rule to optimize a variational circuit with :math:`d` parameters +# and :math:`L` parametrized layers, a total of :math:`2d+L` quantum evaluations +# are required per optimization step. +# +# Note that the :func:`~.pennylane.metric_tensor` function also supports computing the diagonal +# approximation to the metric tensor: +print(qml.metric_tensor(circuit, approx='diag')(params)) + +############################################################################## +# Furthermore, the returned metric tensor is **full differentiable**; include it +# in your cost function, and train or optimize its value! + +############################################################################## +# Quantum natural gradient optimization +# ------------------------------------- +# +# PennyLane provides an implementation of the quantum natural gradient +# optimizer, :class:`~.pennylane.QNGOptimizer`. Let's compare the optimization convergence +# of the QNG Optimizer and the :class:`~.pennylane.GradientDescentOptimizer` for the simple variational +# circuit above. + +steps = 200 +init_params = pnp.array([0.432, -0.123, 0.543, 0.233], requires_grad=True) + +############################################################################## +# Performing vanilla gradient descent: + +gd_cost = [] +opt = qml.GradientDescentOptimizer(0.01) + +theta = init_params +for _ in range(steps): + theta = opt.step(circuit, theta) + gd_cost.append(circuit(theta)) + +############################################################################## +# Performing quantum natural gradient descent: + +qng_cost = [] +opt = qml.QNGOptimizer(0.01) + +theta = init_params +for _ in range(steps): + theta = opt.step(circuit, theta) + qng_cost.append(circuit(theta)) + + +############################################################################## +# Plotting the cost vs optimization step for both optimization strategies: +from matplotlib import pyplot as plt + +plt.style.use("seaborn") +plt.plot(gd_cost, "b", label="Vanilla gradient descent") +plt.plot(qng_cost, "g", label="Quantum natural gradient descent") + +plt.ylabel("Cost function value") +plt.xlabel("Optimization steps") +plt.legend() +plt.show() + +############################################################################## +# References +# ---------- +# +# 1. Shun-Ichi Amari. "Natural gradient works efficiently in learning." +# `Neural computation 10.2, 251-276 `__, 1998. +# +# 2. James Stokes, Josh Izaac, Nathan Killoran, Giuseppe Carleo. +# "Quantum Natural Gradient." `arXiv:1909.02108 `__, 2019. +# +# 3. Aram Harrow and John Napp. "Low-depth gradient measurements can improve +# convergence in variational hybrid quantum-classical algorithms." +# `arXiv:1901.05374 `__, 2019. +# +# 4. Naoki Yamamoto. "On the natural gradient for variational quantum eigensolver." +# `arXiv:1909.05074 `__, 2019. +# +# +# About the author +# ---------------- # \ No newline at end of file diff --git a/demonstrations_v2/tutorial_quantum_transfer_learning/demo.py b/demonstrations_v2/tutorial_quantum_transfer_learning/demo.py index 031d61ed35..ee3790941c 100644 --- a/demonstrations_v2/tutorial_quantum_transfer_learning/demo.py +++ b/demonstrations_v2/tutorial_quantum_transfer_learning/demo.py @@ -1,620 +1,620 @@ -r""" -.. _quantum_transfer_learning: - -Quantum transfer learning -========================= - -.. meta:: - :property="og:description": Combine PyTorch and PennyLane to train a hybrid quantum-classical image - classifier using transfer learning. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/transfer_images.png - -*Author: Andrea Mari — Posted: 19 December 2019. Last updated: 28 January 2021.* - -In this tutorial we apply a machine learning method, known as *transfer learning*, to an -image classifier based on a hybrid classical-quantum network. - -This example follows the general structure of the PyTorch -`tutorial on transfer learning `_ -by Sasank Chilamkurthy, with the crucial difference of using a quantum circuit to perform the -final classification task. - -More details on this topic can be found in the research paper [1] (`Mari et al. (2019) `_). - - -Introduction ------------- - -Transfer learning is a well-established technique for training artificial neural networks (see e.g., Ref. [2]), -which is based on the general intuition that if a pre-trained network is good at solving a -given problem, then, with just a bit of additional training, it can be used to also solve a different -but related problem. - -As discussed in Ref. [1], this idea can be formalized in terms of two abstract networks :math:`A` -and :math:`B,` independently from their quantum or classical physical nature. - -| - - -.. figure:: ../_static/demonstration_assets/quantum_transfer_learning/transfer_learning_general.png - :scale: 45% - :alt: transfer_general - :align: center - -| - -As sketched in the above figure, one can give the following **general definition of the -transfer learning method**: - -1. Take a network :math:`A` that has been pre-trained on a dataset :math:`D_A` and for a given - task :math:`T_A.` - -2. Remove some of the final layers. In this way, the resulting truncated network :math:`A'` - can be used as a feature extractor. - -3. Connect a new trainable network :math:`B` at the end of the pre-trained network :math:`A'.` - -4. Keep the weights of :math:`A'` constant, and train the final block :math:`B` with a - new dataset :math:`D_B` and/or for a new task of interest :math:`T_B.` - -When dealing with hybrid systems, depending on the physical nature (classical or quantum) of the -networks :math:`A` and :math:`B,` one can have different implementations of transfer learning as - -summarized in following table: - -| - -.. rst-class:: docstable - -+-----------+-----------+-----------------------------------------------------+ -| Network A | Network B | Transfer learning scheme | -+===========+===========+=====================================================+ -| Classical | Classical | CC - Standard classical method. See e.g., Ref. [2]. | -+-----------+-----------+-----------------------------------------------------+ -| Classical | Quantum | CQ - **Hybrid model presented in this tutorial.** | -+-----------+-----------+-----------------------------------------------------+ -| Quantum | Classical | QC - Model studied in Ref. [1]. | -+-----------+-----------+-----------------------------------------------------+ -| Quantum | Quantum | QQ - Model studied in Ref. [1]. | -+-----------+-----------+-----------------------------------------------------+ - -Classical-to-quantum transfer learning --------------------------------------- - -We focus on the CQ transfer learning scheme discussed in the previous section and we give a specific example. - -1. As pre-trained network :math:`A` we use **ResNet18**, a deep residual neural network introduced by - Microsoft in Ref. [3], which is pre-trained on the *ImageNet* dataset. - -2. After removing its final layer we obtain :math:`A',` a pre-processing block which maps any - input high-resolution image into 512 abstract features. - -3. Such features are classified by a 4-qubit "dressed quantum circuit" :math:`B,` i.e., a - variational quantum circuit sandwiched between two classical layers. - -4. The hybrid model is trained, keeping :math:`A'` constant, on the *Hymenoptera* dataset - (a small subclass of ImageNet) containing images of *ants* and *bees*. - -A graphical representation of the full data processing pipeline is given in the figure below. - -.. figure:: ../_static/demonstration_assets/quantum_transfer_learning/transfer_learning_c2q.png - :scale: 55% - :alt: transfer_c2q - :align: center - -""" - -############################################################################## -# General setup -# ------------------------ -# -# .. note:: -# -# To use the PyTorch interface in PennyLane, you must first -# `install PyTorch `_. -# -# In addition to *PennyLane*, we will also need some standard *PyTorch* libraries and the -# plotting library *matplotlib*. - -# Some parts of this code are based on the Python script: -# https://github.com/pytorch/tutorials/blob/master/beginner_source/transfer_learning_tutorial.py -# License: BSD - -import time -import os -import copy -import urllib.request -import shutil - -# PyTorch -import torch -import torch.nn as nn -import torch.optim as optim -from torch.optim import lr_scheduler -import torchvision -from torchvision import datasets, transforms - -# Pennylane -import pennylane as qml -from pennylane import numpy as np - -torch.manual_seed(42) -np.random.seed(42) - -# Plotting -import matplotlib.pyplot as plt - -# OpenMP: number of parallel threads. -os.environ["OMP_NUM_THREADS"] = "1" - - -############################################################################## -# Setting of the main hyper-parameters of the model -# ------------------------------------------------------------ -# -# .. note:: -# To reproduce the results of Ref. [1], ``num_epochs`` should be set to ``30`` which may take a long time. -# We suggest to first try with ``num_epochs=1`` and, if everything runs smoothly, increase it to a larger value. - - -n_qubits = 4 # Number of qubits -step = 0.0004 # Learning rate -batch_size = 4 # Number of samples for each training step -num_epochs = 3 # Number of training epochs -q_depth = 6 # Depth of the quantum circuit (number of variational layers) -gamma_lr_scheduler = 0.1 # Learning rate reduction applied every 10 epochs. -q_delta = 0.01 # Initial spread of random quantum weights -start_time = time.time() # Start of the computation timer - -############################################################################## -# We initialize a PennyLane device with a ``default.qubit`` backend. - -dev = qml.device("default.qubit", wires=n_qubits) - -############################################################################## -# We configure PyTorch to use CUDA only if available. Otherwise the CPU is used. - -device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") - -############################################################################## -# Dataset loading -# ------------------------------------------------------------ -# -# .. note:: -# The dataset containing images of *ants* and *bees* can be downloaded -# `here `_ and -# should be extracted in the subfolder ``../_data/hymenoptera_data``. -# -# This is a very small dataset (roughly 250 images), too small for training from scratch a -# classical or quantum model, however it is enough when using *transfer learning* approach. -# -# The PyTorch packages ``torchvision`` and ``torch.utils.data`` are used for loading the dataset -# and performing standard preliminary image operations: resize, center, crop, normalize, *etc.* - -data_transforms = { - "train": transforms.Compose( - [ - # transforms.RandomResizedCrop(224), # uncomment for data augmentation - # transforms.RandomHorizontalFlip(), # uncomment for data augmentation - transforms.Resize(256), - transforms.CenterCrop(224), - transforms.ToTensor(), - # Normalize input channels using mean values and standard deviations of ImageNet. - transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), - ] - ), - "val": transforms.Compose( - [ - transforms.Resize(256), - transforms.CenterCrop(224), - transforms.ToTensor(), - transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), - ] - ), -} - -data_dir = "hymenoptera_data" -if not os.path.exists(data_dir): - urllib.request.urlretrieve( - "https://download.pytorch.org/tutorial/hymenoptera_data.zip", f"{data_dir}.zip" - ) - shutil.unpack_archive(f"{data_dir}.zip") - -image_datasets = { - x if x == "train" else "validation": datasets.ImageFolder( - os.path.join(data_dir, x), data_transforms[x] - ) - for x in ["train", "val"] -} -dataset_sizes = {x: len(image_datasets[x]) for x in ["train", "validation"]} -class_names = image_datasets["train"].classes - -# Initialize dataloader -dataloaders = { - x: torch.utils.data.DataLoader(image_datasets[x], batch_size=batch_size, shuffle=True) - for x in ["train", "validation"] -} - -# function to plot images -def imshow(inp, title=None): - """Display image from tensor.""" - inp = inp.numpy().transpose((1, 2, 0)) - # Inverse of the initial normalization operation. - mean = np.array([0.485, 0.456, 0.406]) - std = np.array([0.229, 0.224, 0.225]) - inp = std * inp + mean - inp = np.clip(inp, 0, 1) - plt.imshow(inp) - if title is not None: - plt.title(title) - - -############################################################################## -# Let us show a batch of the test data, just to have an idea of the classification problem. - -# Get a batch of training data -inputs, classes = next(iter(dataloaders["validation"])) - -# Make a grid from batch -out = torchvision.utils.make_grid(inputs) - -imshow(out, title=[class_names[x] for x in classes]) - -dataloaders = { - x: torch.utils.data.DataLoader(image_datasets[x], batch_size=batch_size, shuffle=True) - for x in ["train", "validation"] -} - - -############################################################################## -# Variational quantum circuit -# ------------------------------------ -# We first define some quantum layers that will compose the quantum circuit. - - -def H_layer(nqubits): - """Layer of single-qubit Hadamard gates. - """ - for idx in range(nqubits): - qml.Hadamard(wires=idx) - - -def RY_layer(w): - """Layer of parametrized qubit rotations around the y axis. - """ - for idx, element in enumerate(w): - qml.RY(element, wires=idx) - - -def entangling_layer(nqubits): - """Layer of CNOTs followed by another shifted layer of CNOT. - """ - # In other words it should apply something like : - # CNOT CNOT CNOT CNOT... CNOT - # CNOT CNOT CNOT... CNOT - for i in range(0, nqubits - 1, 2): # Loop over even indices: i=0,2,...N-2 - qml.CNOT(wires=[i, i + 1]) - for i in range(1, nqubits - 1, 2): # Loop over odd indices: i=1,3,...N-3 - qml.CNOT(wires=[i, i + 1]) - - -############################################################################## -# Now we define the quantum circuit through the PennyLane `qnode` decorator . -# -# The structure is that of a typical variational quantum circuit: -# -# * **Embedding layer:** All qubits are first initialized in a balanced superposition -# of *up* and *down* states, then they are rotated according to the input parameters -# (local embedding). -# -# * **Variational layers:** A sequence of trainable rotation layers and constant -# entangling layers is applied. -# -# * **Measurement layer:** For each qubit, the local expectation value of the :math:`Z` -# operator is measured. This produces a classical output vector, suitable for -# additional post-processing. - - -@qml.qnode(dev) -def quantum_net(q_input_features, q_weights_flat): - """ - The variational quantum circuit. - """ - - # Reshape weights - q_weights = q_weights_flat.reshape(q_depth, n_qubits) - - # Start from state |+> , unbiased w.r.t. |0> and |1> - H_layer(n_qubits) - - # Embed features in the quantum node - RY_layer(q_input_features) - - # Sequence of trainable variational layers - for k in range(q_depth): - entangling_layer(n_qubits) - RY_layer(q_weights[k]) - - # Expectation values in the Z basis - exp_vals = [qml.expval(qml.PauliZ(position)) for position in range(n_qubits)] - return tuple(exp_vals) - - -############################################################################## -# Dressed quantum circuit -# ------------------------ -# -# We can now define a custom ``torch.nn.Module`` representing a *dressed* quantum circuit. -# -# This is a concatenation of: -# -# * A classical pre-processing layer (``nn.Linear``). -# * A classical activation function (``torch.tanh``). -# * A constant ``np.pi/2.0`` scaling. -# * The previously defined quantum circuit (``quantum_net``). -# * A classical post-processing layer (``nn.Linear``). -# -# The input of the module is a batch of vectors with 512 real parameters (features) and -# the output is a batch of vectors with two real outputs (associated with the two classes -# of images: *ants* and *bees*). - - -class DressedQuantumNet(nn.Module): - """ - Torch module implementing the *dressed* quantum net. - """ - - def __init__(self): - """ - Definition of the *dressed* layout. - """ - - super().__init__() - self.pre_net = nn.Linear(512, n_qubits) - self.q_params = nn.Parameter(q_delta * torch.randn(q_depth * n_qubits)) - self.post_net = nn.Linear(n_qubits, 2) - - def forward(self, input_features): - """ - Defining how tensors are supposed to move through the *dressed* quantum - net. - """ - - # obtain the input features for the quantum circuit - # by reducing the feature dimension from 512 to 4 - pre_out = self.pre_net(input_features) - q_in = torch.tanh(pre_out) * np.pi / 2.0 - - # Apply the quantum circuit to each element of the batch and append to q_out - q_out = torch.Tensor(0, n_qubits) - q_out = q_out.to(device) - for elem in q_in: - q_out_elem = torch.hstack(quantum_net(elem, self.q_params)).float().unsqueeze(0) - q_out = torch.cat((q_out, q_out_elem)) - - # return the two-dimensional prediction from the postprocessing layer - return self.post_net(q_out) - - -############################################################################## -# Hybrid classical-quantum model -# ------------------------------------ -# -# We are finally ready to build our full hybrid classical-quantum network. -# We follow the *transfer learning* approach: -# -# 1. First load the classical pre-trained network *ResNet18* from the ``torchvision.models`` zoo. -# 2. Freeze all the weights since they should not be trained. -# 3. Replace the last fully connected layer with our trainable dressed quantum circuit (``DressedQuantumNet``). -# -# .. note:: -# The *ResNet18* model is automatically downloaded by PyTorch and it may take several minutes (only the first time). -# - -weights = torchvision.models.ResNet18_Weights.IMAGENET1K_V1 -model_hybrid = torchvision.models.resnet18(weights=weights) - -for param in model_hybrid.parameters(): - param.requires_grad = False - - -# Notice that model_hybrid.fc is the last layer of ResNet18 -model_hybrid.fc = DressedQuantumNet() - -# Use CUDA or CPU according to the "device" object. -model_hybrid = model_hybrid.to(device) - -############################################################################## -# Training and results -# ------------------------ -# -# Before training the network we need to specify the *loss* function. -# -# We use, as usual in classification problem, the *cross-entropy* which is -# directly available within ``torch.nn``. - - -criterion = nn.CrossEntropyLoss() - -############################################################################## -# We also initialize the *Adam optimizer* which is called at each training step -# in order to update the weights of the model. - - -optimizer_hybrid = optim.Adam(model_hybrid.fc.parameters(), lr=step) - -############################################################################## -# We schedule to reduce the learning rate by a factor of ``gamma_lr_scheduler`` -# every 10 epochs. - - -exp_lr_scheduler = lr_scheduler.StepLR( - optimizer_hybrid, step_size=10, gamma=gamma_lr_scheduler -) - -############################################################################## -# What follows is a training function that will be called later. -# This function should return a trained model that can be used to make predictions -# (classifications). - - -def train_model(model, criterion, optimizer, scheduler, num_epochs): - since = time.time() - best_model_wts = copy.deepcopy(model.state_dict()) - best_acc = 0.0 - best_loss = 10000.0 # Large arbitrary number - best_acc_train = 0.0 - best_loss_train = 10000.0 # Large arbitrary number - print("Training started:") - - for epoch in range(num_epochs): - - # Each epoch has a training and validation phase - for phase in ["train", "validation"]: - if phase == "train": - # Set model to training mode - model.train() - else: - # Set model to evaluate mode - model.eval() - running_loss = 0.0 - running_corrects = 0 - - # Iterate over data. - n_batches = dataset_sizes[phase] // batch_size - it = 0 - for inputs, labels in dataloaders[phase]: - since_batch = time.time() - batch_size_ = len(inputs) - inputs = inputs.to(device) - labels = labels.to(device) - optimizer.zero_grad() - - # Track/compute gradient and make an optimization step only when training - with torch.set_grad_enabled(phase == "train"): - outputs = model(inputs) - _, preds = torch.max(outputs, 1) - loss = criterion(outputs, labels) - if phase == "train": - loss.backward() - optimizer.step() - - # Print iteration results - running_loss += loss.item() * batch_size_ - batch_corrects = torch.sum(preds == labels.data).item() - running_corrects += batch_corrects - print( - "Phase: {} Epoch: {}/{} Iter: {}/{} Batch time: {:.4f}".format( - phase, - epoch + 1, - num_epochs, - it + 1, - n_batches + 1, - time.time() - since_batch, - ), - end="\r", - flush=True, - ) - it += 1 - - # Print epoch results - epoch_loss = running_loss / dataset_sizes[phase] - epoch_acc = running_corrects / dataset_sizes[phase] - print( - "Phase: {} Epoch: {}/{} Loss: {:.4f} Acc: {:.4f} ".format( - "train" if phase == "train" else "validation ", - epoch + 1, - num_epochs, - epoch_loss, - epoch_acc, - ) - ) - - # Check if this is the best model wrt previous epochs - if phase == "validation" and epoch_acc > best_acc: - best_acc = epoch_acc - best_model_wts = copy.deepcopy(model.state_dict()) - if phase == "validation" and epoch_loss < best_loss: - best_loss = epoch_loss - if phase == "train" and epoch_acc > best_acc_train: - best_acc_train = epoch_acc - if phase == "train" and epoch_loss < best_loss_train: - best_loss_train = epoch_loss - - # Update learning rate - if phase == "train": - scheduler.step() - - # Print final results - model.load_state_dict(best_model_wts) - time_elapsed = time.time() - since - print( - "Training completed in {:.0f}m {:.0f}s".format(time_elapsed // 60, time_elapsed % 60) - ) - print("Best test loss: {:.4f} | Best test accuracy: {:.4f}".format(best_loss, best_acc)) - return model - - -############################################################################## -# We are ready to perform the actual training process. - -model_hybrid = train_model( - model_hybrid, criterion, optimizer_hybrid, exp_lr_scheduler, num_epochs=num_epochs -) - -############################################################################## -# Visualizing the model predictions -# ------------------------------------ - -############################################################################## -# We first define a visualization function for a batch of test data. - - -def visualize_model(model, num_images=6, fig_name="Predictions"): - images_so_far = 0 - _fig = plt.figure(fig_name) - model.eval() - with torch.no_grad(): - for _i, (inputs, labels) in enumerate(dataloaders["validation"]): - inputs = inputs.to(device) - labels = labels.to(device) - outputs = model(inputs) - _, preds = torch.max(outputs, 1) - for j in range(inputs.size()[0]): - images_so_far += 1 - ax = plt.subplot(num_images // 2, 2, images_so_far) - ax.axis("off") - ax.set_title("[{}]".format(class_names[preds[j]])) - imshow(inputs.cpu().data[j]) - if images_so_far == num_images: - return - - -############################################################################## -# Finally, we can run the previous function to see a batch of images -# with the corresponding predictions. -# -visualize_model(model_hybrid, num_images=batch_size) -plt.show() - -############################################################################## -# References -# ------------ -# -# [1] Andrea Mari, Thomas R. Bromley, Josh Izaac, Maria Schuld, and Nathan Killoran. -# *Transfer learning in hybrid classical-quantum neural networks*. arXiv:1912.08278 (2019). -# -# [2] Rajat Raina, Alexis Battle, Honglak Lee, Benjamin Packer, and Andrew Y Ng. -# *Self-taught learning: transfer learning from unlabeled data*. -# Proceedings of the 24th International Conference on Machine Learning*, 759–766 (2007). -# -# [3] Kaiming He, Xiangyu Zhang, Shaoqing ren and Jian Sun. *Deep residual learning for image recognition*. -# Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition, 770-778 (2016). -# -# [4] Ville Bergholm, Josh Izaac, Maria Schuld, Christian Gogolin, Carsten Blank, Keri McKiernan, and Nathan Killoran. -# *PennyLane: Automatic differentiation of hybrid quantum-classical computations*. arXiv:1811.04968 (2018). -# -# -# About the author -# ---------------- -# +r""" +.. _quantum_transfer_learning: + +Quantum transfer learning +========================= + +.. meta:: + :property="og:description": Combine PyTorch and PennyLane to train a hybrid quantum-classical image + classifier using transfer learning. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/transfer_images.png + +*Author: Andrea Mari — Posted: 19 December 2019. Last updated: 28 January 2021.* + +In this tutorial we apply a machine learning method, known as *transfer learning*, to an +image classifier based on a hybrid classical-quantum network. + +This example follows the general structure of the PyTorch +`tutorial on transfer learning `_ +by Sasank Chilamkurthy, with the crucial difference of using a quantum circuit to perform the +final classification task. + +More details on this topic can be found in the research paper [1] (`Mari et al. (2019) `_). + + +Introduction +------------ + +Transfer learning is a well-established technique for training artificial neural networks (see e.g., Ref. [2]), +which is based on the general intuition that if a pre-trained network is good at solving a +given problem, then, with just a bit of additional training, it can be used to also solve a different +but related problem. + +As discussed in Ref. [1], this idea can be formalized in terms of two abstract networks :math:`A` +and :math:`B,` independently from their quantum or classical physical nature. + +| + + +.. figure:: ../_static/demonstration_assets/quantum_transfer_learning/transfer_learning_general.png + :scale: 45% + :alt: transfer_general + :align: center + +| + +As sketched in the above figure, one can give the following **general definition of the +transfer learning method**: + +1. Take a network :math:`A` that has been pre-trained on a dataset :math:`D_A` and for a given + task :math:`T_A.` + +2. Remove some of the final layers. In this way, the resulting truncated network :math:`A'` + can be used as a feature extractor. + +3. Connect a new trainable network :math:`B` at the end of the pre-trained network :math:`A'.` + +4. Keep the weights of :math:`A'` constant, and train the final block :math:`B` with a + new dataset :math:`D_B` and/or for a new task of interest :math:`T_B.` + +When dealing with hybrid systems, depending on the physical nature (classical or quantum) of the +networks :math:`A` and :math:`B,` one can have different implementations of transfer learning as + +summarized in following table: + +| + +.. rst-class:: docstable + ++-----------+-----------+-----------------------------------------------------+ +| Network A | Network B | Transfer learning scheme | ++===========+===========+=====================================================+ +| Classical | Classical | CC - Standard classical method. See e.g., Ref. [2]. | ++-----------+-----------+-----------------------------------------------------+ +| Classical | Quantum | CQ - **Hybrid model presented in this tutorial.** | ++-----------+-----------+-----------------------------------------------------+ +| Quantum | Classical | QC - Model studied in Ref. [1]. | ++-----------+-----------+-----------------------------------------------------+ +| Quantum | Quantum | QQ - Model studied in Ref. [1]. | ++-----------+-----------+-----------------------------------------------------+ + +Classical-to-quantum transfer learning +-------------------------------------- + +We focus on the CQ transfer learning scheme discussed in the previous section and we give a specific example. + +1. As pre-trained network :math:`A` we use **ResNet18**, a deep residual neural network introduced by + Microsoft in Ref. [3], which is pre-trained on the *ImageNet* dataset. + +2. After removing its final layer we obtain :math:`A',` a pre-processing block which maps any + input high-resolution image into 512 abstract features. + +3. Such features are classified by a 4-qubit "dressed quantum circuit" :math:`B,` i.e., a + variational quantum circuit sandwiched between two classical layers. + +4. The hybrid model is trained, keeping :math:`A'` constant, on the *Hymenoptera* dataset + (a small subclass of ImageNet) containing images of *ants* and *bees*. + +A graphical representation of the full data processing pipeline is given in the figure below. + +.. figure:: ../_static/demonstration_assets/quantum_transfer_learning/transfer_learning_c2q.png + :scale: 55% + :alt: transfer_c2q + :align: center + +""" + +############################################################################## +# General setup +# ------------------------ +# +# .. note:: +# +# To use the PyTorch interface in PennyLane, you must first +# `install PyTorch `_. +# +# In addition to *PennyLane*, we will also need some standard *PyTorch* libraries and the +# plotting library *matplotlib*. + +# Some parts of this code are based on the Python script: +# https://github.com/pytorch/tutorials/blob/master/beginner_source/transfer_learning_tutorial.py +# License: BSD + +import time +import os +import copy +import urllib.request +import shutil + +# PyTorch +import torch +import torch.nn as nn +import torch.optim as optim +from torch.optim import lr_scheduler +import torchvision +from torchvision import datasets, transforms + +# Pennylane +import pennylane as qml +from pennylane import numpy as np + +torch.manual_seed(42) +np.random.seed(42) + +# Plotting +import matplotlib.pyplot as plt + +# OpenMP: number of parallel threads. +os.environ["OMP_NUM_THREADS"] = "1" + + +############################################################################## +# Setting of the main hyper-parameters of the model +# ------------------------------------------------------------ +# +# .. note:: +# To reproduce the results of Ref. [1], ``num_epochs`` should be set to ``30`` which may take a long time. +# We suggest to first try with ``num_epochs=1`` and, if everything runs smoothly, increase it to a larger value. + + +n_qubits = 4 # Number of qubits +step = 0.0004 # Learning rate +batch_size = 4 # Number of samples for each training step +num_epochs = 3 # Number of training epochs +q_depth = 6 # Depth of the quantum circuit (number of variational layers) +gamma_lr_scheduler = 0.1 # Learning rate reduction applied every 10 epochs. +q_delta = 0.01 # Initial spread of random quantum weights +start_time = time.time() # Start of the computation timer + +############################################################################## +# We initialize a PennyLane device with a ``default.qubit`` backend. + +dev = qml.device("default.qubit", wires=n_qubits) + +############################################################################## +# We configure PyTorch to use CUDA only if available. Otherwise the CPU is used. + +device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") + +############################################################################## +# Dataset loading +# ------------------------------------------------------------ +# +# .. note:: +# The dataset containing images of *ants* and *bees* can be downloaded +# `here `_ and +# should be extracted in the subfolder ``../_data/hymenoptera_data``. +# +# This is a very small dataset (roughly 250 images), too small for training from scratch a +# classical or quantum model, however it is enough when using *transfer learning* approach. +# +# The PyTorch packages ``torchvision`` and ``torch.utils.data`` are used for loading the dataset +# and performing standard preliminary image operations: resize, center, crop, normalize, *etc.* + +data_transforms = { + "train": transforms.Compose( + [ + # transforms.RandomResizedCrop(224), # uncomment for data augmentation + # transforms.RandomHorizontalFlip(), # uncomment for data augmentation + transforms.Resize(256), + transforms.CenterCrop(224), + transforms.ToTensor(), + # Normalize input channels using mean values and standard deviations of ImageNet. + transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), + ] + ), + "val": transforms.Compose( + [ + transforms.Resize(256), + transforms.CenterCrop(224), + transforms.ToTensor(), + transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), + ] + ), +} + +data_dir = "hymenoptera_data" +if not os.path.exists(data_dir): + urllib.request.urlretrieve( + "https://download.pytorch.org/tutorial/hymenoptera_data.zip", f"{data_dir}.zip" + ) + shutil.unpack_archive(f"{data_dir}.zip") + +image_datasets = { + x if x == "train" else "validation": datasets.ImageFolder( + os.path.join(data_dir, x), data_transforms[x] + ) + for x in ["train", "val"] +} +dataset_sizes = {x: len(image_datasets[x]) for x in ["train", "validation"]} +class_names = image_datasets["train"].classes + +# Initialize dataloader +dataloaders = { + x: torch.utils.data.DataLoader(image_datasets[x], batch_size=batch_size, shuffle=True) + for x in ["train", "validation"] +} + +# function to plot images +def imshow(inp, title=None): + """Display image from tensor.""" + inp = inp.numpy().transpose((1, 2, 0)) + # Inverse of the initial normalization operation. + mean = np.array([0.485, 0.456, 0.406]) + std = np.array([0.229, 0.224, 0.225]) + inp = std * inp + mean + inp = np.clip(inp, 0, 1) + plt.imshow(inp) + if title is not None: + plt.title(title) + + +############################################################################## +# Let us show a batch of the test data, just to have an idea of the classification problem. + +# Get a batch of training data +inputs, classes = next(iter(dataloaders["validation"])) + +# Make a grid from batch +out = torchvision.utils.make_grid(inputs) + +imshow(out, title=[class_names[x] for x in classes]) + +dataloaders = { + x: torch.utils.data.DataLoader(image_datasets[x], batch_size=batch_size, shuffle=True) + for x in ["train", "validation"] +} + + +############################################################################## +# Variational quantum circuit +# ------------------------------------ +# We first define some quantum layers that will compose the quantum circuit. + + +def H_layer(nqubits): + """Layer of single-qubit Hadamard gates. + """ + for idx in range(nqubits): + qml.Hadamard(wires=idx) + + +def RY_layer(w): + """Layer of parametrized qubit rotations around the y axis. + """ + for idx, element in enumerate(w): + qml.RY(element, wires=idx) + + +def entangling_layer(nqubits): + """Layer of CNOTs followed by another shifted layer of CNOT. + """ + # In other words it should apply something like : + # CNOT CNOT CNOT CNOT... CNOT + # CNOT CNOT CNOT... CNOT + for i in range(0, nqubits - 1, 2): # Loop over even indices: i=0,2,...N-2 + qml.CNOT(wires=[i, i + 1]) + for i in range(1, nqubits - 1, 2): # Loop over odd indices: i=1,3,...N-3 + qml.CNOT(wires=[i, i + 1]) + + +############################################################################## +# Now we define the quantum circuit through the PennyLane `qnode` decorator . +# +# The structure is that of a typical variational quantum circuit: +# +# * **Embedding layer:** All qubits are first initialized in a balanced superposition +# of *up* and *down* states, then they are rotated according to the input parameters +# (local embedding). +# +# * **Variational layers:** A sequence of trainable rotation layers and constant +# entangling layers is applied. +# +# * **Measurement layer:** For each qubit, the local expectation value of the :math:`Z` +# operator is measured. This produces a classical output vector, suitable for +# additional post-processing. + + +@qml.qnode(dev) +def quantum_net(q_input_features, q_weights_flat): + """ + The variational quantum circuit. + """ + + # Reshape weights + q_weights = q_weights_flat.reshape(q_depth, n_qubits) + + # Start from state |+> , unbiased w.r.t. |0> and |1> + H_layer(n_qubits) + + # Embed features in the quantum node + RY_layer(q_input_features) + + # Sequence of trainable variational layers + for k in range(q_depth): + entangling_layer(n_qubits) + RY_layer(q_weights[k]) + + # Expectation values in the Z basis + exp_vals = [qml.expval(qml.PauliZ(position)) for position in range(n_qubits)] + return tuple(exp_vals) + + +############################################################################## +# Dressed quantum circuit +# ------------------------ +# +# We can now define a custom ``torch.nn.Module`` representing a *dressed* quantum circuit. +# +# This is a concatenation of: +# +# * A classical pre-processing layer (``nn.Linear``). +# * A classical activation function (``torch.tanh``). +# * A constant ``np.pi/2.0`` scaling. +# * The previously defined quantum circuit (``quantum_net``). +# * A classical post-processing layer (``nn.Linear``). +# +# The input of the module is a batch of vectors with 512 real parameters (features) and +# the output is a batch of vectors with two real outputs (associated with the two classes +# of images: *ants* and *bees*). + + +class DressedQuantumNet(nn.Module): + """ + Torch module implementing the *dressed* quantum net. + """ + + def __init__(self): + """ + Definition of the *dressed* layout. + """ + + super().__init__() + self.pre_net = nn.Linear(512, n_qubits) + self.q_params = nn.Parameter(q_delta * torch.randn(q_depth * n_qubits)) + self.post_net = nn.Linear(n_qubits, 2) + + def forward(self, input_features): + """ + Defining how tensors are supposed to move through the *dressed* quantum + net. + """ + + # obtain the input features for the quantum circuit + # by reducing the feature dimension from 512 to 4 + pre_out = self.pre_net(input_features) + q_in = torch.tanh(pre_out) * np.pi / 2.0 + + # Apply the quantum circuit to each element of the batch and append to q_out + q_out = torch.Tensor(0, n_qubits) + q_out = q_out.to(device) + for elem in q_in: + q_out_elem = torch.hstack(quantum_net(elem, self.q_params)).float().unsqueeze(0) + q_out = torch.cat((q_out, q_out_elem)) + + # return the two-dimensional prediction from the postprocessing layer + return self.post_net(q_out) + + +############################################################################## +# Hybrid classical-quantum model +# ------------------------------------ +# +# We are finally ready to build our full hybrid classical-quantum network. +# We follow the *transfer learning* approach: +# +# 1. First load the classical pre-trained network *ResNet18* from the ``torchvision.models`` zoo. +# 2. Freeze all the weights since they should not be trained. +# 3. Replace the last fully connected layer with our trainable dressed quantum circuit (``DressedQuantumNet``). +# +# .. note:: +# The *ResNet18* model is automatically downloaded by PyTorch and it may take several minutes (only the first time). +# + +weights = torchvision.models.ResNet18_Weights.IMAGENET1K_V1 +model_hybrid = torchvision.models.resnet18(weights=weights) + +for param in model_hybrid.parameters(): + param.requires_grad = False + + +# Notice that model_hybrid.fc is the last layer of ResNet18 +model_hybrid.fc = DressedQuantumNet() + +# Use CUDA or CPU according to the "device" object. +model_hybrid = model_hybrid.to(device) + +############################################################################## +# Training and results +# ------------------------ +# +# Before training the network we need to specify the *loss* function. +# +# We use, as usual in classification problem, the *cross-entropy* which is +# directly available within ``torch.nn``. + + +criterion = nn.CrossEntropyLoss() + +############################################################################## +# We also initialize the *Adam optimizer* which is called at each training step +# in order to update the weights of the model. + + +optimizer_hybrid = optim.Adam(model_hybrid.fc.parameters(), lr=step) + +############################################################################## +# We schedule to reduce the learning rate by a factor of ``gamma_lr_scheduler`` +# every 10 epochs. + + +exp_lr_scheduler = lr_scheduler.StepLR( + optimizer_hybrid, step_size=10, gamma=gamma_lr_scheduler +) + +############################################################################## +# What follows is a training function that will be called later. +# This function should return a trained model that can be used to make predictions +# (classifications). + + +def train_model(model, criterion, optimizer, scheduler, num_epochs): + since = time.time() + best_model_wts = copy.deepcopy(model.state_dict()) + best_acc = 0.0 + best_loss = 10000.0 # Large arbitrary number + best_acc_train = 0.0 + best_loss_train = 10000.0 # Large arbitrary number + print("Training started:") + + for epoch in range(num_epochs): + + # Each epoch has a training and validation phase + for phase in ["train", "validation"]: + if phase == "train": + # Set model to training mode + model.train() + else: + # Set model to evaluate mode + model.eval() + running_loss = 0.0 + running_corrects = 0 + + # Iterate over data. + n_batches = dataset_sizes[phase] // batch_size + it = 0 + for inputs, labels in dataloaders[phase]: + since_batch = time.time() + batch_size_ = len(inputs) + inputs = inputs.to(device) + labels = labels.to(device) + optimizer.zero_grad() + + # Track/compute gradient and make an optimization step only when training + with torch.set_grad_enabled(phase == "train"): + outputs = model(inputs) + _, preds = torch.max(outputs, 1) + loss = criterion(outputs, labels) + if phase == "train": + loss.backward() + optimizer.step() + + # Print iteration results + running_loss += loss.item() * batch_size_ + batch_corrects = torch.sum(preds == labels.data).item() + running_corrects += batch_corrects + print( + "Phase: {} Epoch: {}/{} Iter: {}/{} Batch time: {:.4f}".format( + phase, + epoch + 1, + num_epochs, + it + 1, + n_batches + 1, + time.time() - since_batch, + ), + end="\r", + flush=True, + ) + it += 1 + + # Print epoch results + epoch_loss = running_loss / dataset_sizes[phase] + epoch_acc = running_corrects / dataset_sizes[phase] + print( + "Phase: {} Epoch: {}/{} Loss: {:.4f} Acc: {:.4f} ".format( + "train" if phase == "train" else "validation ", + epoch + 1, + num_epochs, + epoch_loss, + epoch_acc, + ) + ) + + # Check if this is the best model wrt previous epochs + if phase == "validation" and epoch_acc > best_acc: + best_acc = epoch_acc + best_model_wts = copy.deepcopy(model.state_dict()) + if phase == "validation" and epoch_loss < best_loss: + best_loss = epoch_loss + if phase == "train" and epoch_acc > best_acc_train: + best_acc_train = epoch_acc + if phase == "train" and epoch_loss < best_loss_train: + best_loss_train = epoch_loss + + # Update learning rate + if phase == "train": + scheduler.step() + + # Print final results + model.load_state_dict(best_model_wts) + time_elapsed = time.time() - since + print( + "Training completed in {:.0f}m {:.0f}s".format(time_elapsed // 60, time_elapsed % 60) + ) + print("Best test loss: {:.4f} | Best test accuracy: {:.4f}".format(best_loss, best_acc)) + return model + + +############################################################################## +# We are ready to perform the actual training process. + +model_hybrid = train_model( + model_hybrid, criterion, optimizer_hybrid, exp_lr_scheduler, num_epochs=num_epochs +) + +############################################################################## +# Visualizing the model predictions +# ------------------------------------ + +############################################################################## +# We first define a visualization function for a batch of test data. + + +def visualize_model(model, num_images=6, fig_name="Predictions"): + images_so_far = 0 + _fig = plt.figure(fig_name) + model.eval() + with torch.no_grad(): + for _i, (inputs, labels) in enumerate(dataloaders["validation"]): + inputs = inputs.to(device) + labels = labels.to(device) + outputs = model(inputs) + _, preds = torch.max(outputs, 1) + for j in range(inputs.size()[0]): + images_so_far += 1 + ax = plt.subplot(num_images // 2, 2, images_so_far) + ax.axis("off") + ax.set_title("[{}]".format(class_names[preds[j]])) + imshow(inputs.cpu().data[j]) + if images_so_far == num_images: + return + + +############################################################################## +# Finally, we can run the previous function to see a batch of images +# with the corresponding predictions. +# +visualize_model(model_hybrid, num_images=batch_size) +plt.show() + +############################################################################## +# References +# ------------ +# +# [1] Andrea Mari, Thomas R. Bromley, Josh Izaac, Maria Schuld, and Nathan Killoran. +# *Transfer learning in hybrid classical-quantum neural networks*. arXiv:1912.08278 (2019). +# +# [2] Rajat Raina, Alexis Battle, Honglak Lee, Benjamin Packer, and Andrew Y Ng. +# *Self-taught learning: transfer learning from unlabeled data*. +# Proceedings of the 24th International Conference on Machine Learning*, 759–766 (2007). +# +# [3] Kaiming He, Xiangyu Zhang, Shaoqing ren and Jian Sun. *Deep residual learning for image recognition*. +# Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition, 770-778 (2016). +# +# [4] Ville Bergholm, Josh Izaac, Maria Schuld, Christian Gogolin, Carsten Blank, Keri McKiernan, and Nathan Killoran. +# *PennyLane: Automatic differentiation of hybrid quantum-classical computations*. arXiv:1811.04968 (2018). +# +# +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_qubit_tapering/demo.py b/demonstrations_v2/tutorial_qubit_tapering/demo.py index a09519630b..5c55be064e 100644 --- a/demonstrations_v2/tutorial_qubit_tapering/demo.py +++ b/demonstrations_v2/tutorial_qubit_tapering/demo.py @@ -1,330 +1,330 @@ -r""" - -Qubit tapering -============== - -.. meta:: - :property="og:description": Learn how to taper off qubits - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qubit_tapering.png - -.. related:: - tutorial_quantum_chemistry Building molecular Hamiltonians - tutorial_vqe A brief overview of VQE - tutorial_givens_rotations Givens rotations for quantum chemistry - tutorial_adaptive_circuits Adaptive circuits for quantum chemistry - tutorial_differentiable_HF Differentiable Hartree-Fock - -*Authors: Utkarsh Azad and Soran Jahangiri. Posted: 16 May 2022. Last updated: 08 Nov 2022* - -The performance of variational quantum algorithms is considerably limited by the number of qubits -required to represent wave functions. In the context of quantum chemistry, this -limitation hinders the treatment of large molecules with algorithms such as the :doc:`variational quantum -eigensolver (VQE) `. Several approaches have been developed to reduce the qubit requirements for -quantum chemistry calculations. In this tutorial, we demonstrate the symmetry-based qubit -tapering approach which allows reducing the number of qubits required to perform molecular quantum -simulations based on the :math:`\mathbb{Z}_2` symmetries present in molecular Hamiltonians -[#bravyi2017]_ [#setia2019]_. - -A molecular Hamiltonian in the qubit basis can be expressed as a linear combination of Pauli words -as - -.. math:: H = \sum_{i=1}^r h_i P_i, - -where :math:`h_i` is a real coefficient and :math:`P_i` is a tensor product of Pauli and -identity operators acting on :math:`M` qubits - -.. math:: P_i \in \pm \left \{ I, X, Y, Z \right \} ^ {\bigotimes M}. - -The main idea in the symmetry-based qubit tapering approach is to find a unitary operator :math:`U` -that transforms :math:`H` to a new Hamiltonian :math:`H'` which has the same eigenvalues as -:math:`H` - -.. math:: H' = U^{\dagger} H U = \sum_{i=1}^r c_i \mu_i, - -such that each :math:`\mu_i` term in the new Hamiltonian always acts trivially, e.g., with an -identity or a Pauli operator, on a set of qubits. This allows tapering-off those qubits from the -Hamiltonian. - -For instance, consider the following Hamiltonian - -.. math:: H' = Z_0 X_1 - X_1 + Y_0 X_1, - -where all terms in the Hamiltonian act on the second qubit with the :math:`X` operator. It is -straightforward to show that each term in the Hamiltonian commutes with :math:`I_0 X_1` and the -ground-state eigenvector of :math:`H'` is also an eigenvector of :math:`I_0 X_1` with eigenvalues -:math:`\pm 1.` We can also rewrite the Hamiltonian as - -.. math:: H' = (Z_0 I_1 - I_0 I_1 + Y_0 I_1) I_0 X_1, - -which gives us - -.. math:: H'|\psi \rangle = \pm1 (Z_0 I_1 - I_0 I_1 + Y_0 I_1)|\psi \rangle, - -where :math:`|\psi \rangle` is an eigenvector of :math:`H'.` This means that the Hamiltonian -:math:`H` can be simplified as - -.. math:: H_{tapered} = \pm1 (Z_0 - I_0 + Y_0). - -The tapered Hamiltonian :math:`H_{tapered}` has the eigenvalues - -.. math:: [-2.41421, 0.41421], - -and - -.. math:: [2.41421, -0.41421], - -depending on the value of the :math:`\pm 1` prefactor. The eigenvalues of the original Hamiltonian -:math:`H` are - -.. math:: [2.41421, -2.41421, 0.41421, -0.41421], - -which are thus reproduced by the tapered Hamiltonian. - -More generally, we can construct the unitary :math:`U` such that each :math:`\mu_i` term acts with a -Pauli-X operator on a set of qubits -:math:`\left \{ j \right \}, j \in \left \{ l, ..., k \right \}` where :math:`j` is the qubit label. -This guarantees that each term of the transformed Hamiltonian commutes with each of the Pauli-X -operators applied to the :math:`j`-th qubit: - -.. math:: [H', X^j] = 0, - -and the eigenvectors of the transformed Hamiltonian :math:`H'` are also eigenvectors of each of the -:math:`X^{j}` operators. Then we can factor out all of the :math:`X^{j}` operators from the -transformed Hamiltonian and replace them with their eigenvalues :math:`\pm 1.` This gives us a -set of tapered Hamiltonians depending on which eigenvalue :math:`\pm 1` we chose for each of the -:math:`X^{j}` operators. For instance, in the case of two tapered qubits, we have four eigenvalue -sectors: :math:`[+1, +1]`, :math:`[-1, +1]`, :math:`[+1, -1],` :math:`[-1, -1].` In these tapered -Hamiltonians, the set of :math:`\left \{ j \right \}, j \in \left \{ l, ..., k \right \}` qubits -are eliminated. For tapered molecular Hamiltonians, it is possible to determine the optimal sector -of the eigenvalues that corresponds to the ground state. This is explained in more detail in the -following sections. - -The unitary operator :math:`U` can be constructed as a -`Clifford `__ operator [#bravyi2017]_ - -.. math:: U = \Pi_j \left [\frac{1}{\sqrt{2}} \left (X^{q(j)} + \tau_j \right) \right], - -where :math:`\tau` denotes the generators of the symmetry group of :math:`H` and -:math:`X^{q}` operators act on those qubits that will be ultimately tapered off from -the Hamiltonian. The symmetry group of the Hamiltonian is defined as an Abelian group of Pauli words that commute -with each term in the Hamiltonian (excluding :math:`−I`). The -`generators `__ of the symmetry group are -those elements of the group that can be combined, along with their inverses, to create any other -member of the group. - -Let's use the qubit tapering method and obtain the ground state energy of the `Helium hydride -cation `__ :math:`\textrm{HeH}^+.` - -Tapering the molecular Hamiltonian ----------------------------------- - -In PennyLane, a :doc:`molecular Hamiltonian ` can be created by specifying the atomic symbols and -coordinates. -""" -import pennylane as qml -from jax import numpy as jnp -import jax - -jax.config.update("jax_enable_x64", True) -symbols = ["He", "H"] -geometry = jnp.array([[0.00000000, 0.00000000, -0.87818361], - [0.00000000, 0.00000000, 0.87818362]]) - -molecule = qml.qchem.Molecule(symbols, geometry, charge=1) -H, qubits = qml.qchem.molecular_hamiltonian(molecule) -H - -############################################################################## -# This Hamiltonian contains 27 terms where each term acts on up to four qubits. -# -# We can now obtain the symmetry generators and the :math:`X^{j}` operators that are -# used to construct the unitary :math:`U` operator that transforms the :math:`\textrm{HeH}^+` -# Hamiltonian. In PennyLane, these are constructed by using the -# :func:`~.pennylane.symmetry_generators` and :func:`~.pennylane.paulix_ops` functions. - -generators = qml.symmetry_generators(H) -paulixops = qml.paulix_ops(generators, qubits) - -for idx, generator in enumerate(generators): - print(f"generator {idx+1}: {generator}, paulix_op: {paulixops[idx]}") - -############################################################################## -# Once the operator :math:`U` is applied, each of the Hamiltonian terms will act on the qubits -# :math:`q_2, q_3` either with the identity or with a Pauli-X operator. For each of these qubits, -# we can simply replace the Pauli-X operator with one of its eigenvalues :math:`+1` or :math:`-1.` -# This results in a total number of :math:`2^k` Hamiltonians, where :math:`k` is the number of -# tapered-off qubits and each Hamiltonian corresponds to one eigenvalue sector. The optimal sector -# corresponding to the ground-state energy of the molecule can be obtained by using the -# :func:`~.pennylane.qchem.optimal_sector` function. - - -n_electrons = 2 -paulix_sector = qml.qchem.optimal_sector(H, generators, n_electrons) -print(paulix_sector) - -############################################################################## -# The optimal eigenvalues are :math:`-1, -1` for qubits :math:`q_2, q_3,` respectively. We can now -# build the tapered Hamiltonian with the :func:`~.pennylane.taper` function which -# constructs the operator :math:`U,` applies it to the Hamiltonian and finally tapers off the -# qubits :math:`q_2, q_3` by replacing the Pauli-X operators acting on those qubits with the optimal -# eigenvalues. - -H_tapered = qml.taper(H, generators, paulixops, paulix_sector) -H_tapered_coeffs, H_tapered_ops = H_tapered.terms() -H_tapered = qml.Hamiltonian(jnp.real(jnp.array(H_tapered_coeffs)), H_tapered_ops) -print(H_tapered) - -############################################################################## -# The new Hamiltonian has only 9 non-zero terms acting on only 2 qubits! We can verify that the -# original and the tapered Hamiltonian both give the correct ground state energy of the -# :math:`\textrm{HeH}^+` cation, which is :math:`-2.862595242378` Ha computed with the full -# configuration interaction (FCI) method. In PennyLane, it's possible to build a sparse matrix -# representation of Hamiltonians. This allows us to directly diagonalize them to obtain exact values -# of the ground-state energies. - -H_sparse = qml.SparseHamiltonian(H.sparse_matrix(), wires=H.wires) -H_tapered_sparse = qml.SparseHamiltonian(H_tapered.sparse_matrix(), wires=H_tapered.wires) - -print("Eigenvalues of H:\n", qml.eigvals(H_sparse, k=16)) -print("\nEigenvalues of H_tapered:\n", qml.eigvals(H_tapered_sparse, k=4)) - -############################################################################## -# Note that a second-quantized Hamiltonian is independent of the number of electrons and its -# eigenspectrum contains the energies of the neutral and charged molecules. Therefore, the -# smallest eigenvalue returned by :func:`~.pennylane.eigvals` for a molecular Hamiltonian -# might correspond to the neutral or charged molecule. While in the case of :math:`\textrm{HeH}^+,` -# qubit tapering allows specifying the optimal sector of the eigenvectors corresponding only to the -# correct number of electrons, it is generally guaranteed that the optimal sector covers all -# eigenvectors with the correct number of electrons, but may contain additional eigenvectors of -# different charge. Therefore, the ground-state energy of the :math:`\textrm{HeH}^+` cation is -# the smallest eigenvalue of the tapered Hamiltonian. -# -# Tapering the reference state -# ---------------------------- -# The ground state Hartree-Fock energy of :math:`\textrm{HeH}^+` can be computed by directly -# applying the Hamiltonians to the Hartree-Fock state. For the tapered Hamiltonian, this requires -# transforming the Hartree-Fock state with the same symmetries obtained for the original -# Hamiltonian. This reduces the number of qubits in the Hartree-Fock state to match that of the -# tapered Hamiltonian. It can be done with the :func:`~.pennylane.qchem.taper_hf` function. - -state_tapered = qml.qchem.taper_hf(generators, paulixops, paulix_sector, - num_electrons=n_electrons, num_wires=len(H.wires)) -print(state_tapered) - -############################################################################## -# Recall that the original Hartree-Fock state for the :math:`\textrm{HeH}^+` cation is -# :math:`[1 1 0 0].` We can now generate the qubit representation of these states and compute the -# Hartree-Fock energies for each Hamiltonian. - -dev = qml.device("default.qubit", wires=H.wires) -@qml.qnode(dev, interface="jax") -def circuit(): - qml.BasisState(jnp.array([1, 1, 0, 0]), wires=H.wires) - return qml.state() - -qubit_state = circuit() -HF_energy = qubit_state.T @ H.sparse_matrix().toarray() @ qubit_state -print(f"HF energy: {jnp.real(HF_energy):.8f} Ha") - -dev = qml.device("lightning.qubit", wires=H_tapered.wires) -@qml.qnode(dev, interface="jax") -def circuit(): - qml.BasisState(jnp.array([1, 1]), wires=H_tapered.wires) - return qml.state() - -qubit_state = circuit() -HF_energy = qubit_state.T @ H_tapered.sparse_matrix().toarray() @ qubit_state -print(f"HF energy (tapered): {jnp.real(HF_energy):.8f} Ha") - -############################################################################## -# These values are identical to the reference Hartree-Fock energy :math:`-2.8543686493` Ha. -# -# VQE simulation -# -------------- -# Finally, we can use the tapered Hamiltonian and the tapered reference state to perform a VQE -# simulation and compute the ground-state energy of the :math:`\textrm{HeH}^+` cation. We build a -# tapered variational ansatz `[3] `__ -# that prepares an entangled state by evolving the tapered Hartree-Fock state using the tapered -# particle-conserving gates, i.e., the :func:`~.pennylane.SingleExcitation` and -# :func:`~.pennylane.DoubleExcitation` operations tapered using -# :func:`~.pennylane.qchem.taper_operation`. - -singles, doubles = qml.qchem.excitations(n_electrons, len(H.wires)) -tapered_doubles = [ - qml.taper_operation(qml.DoubleExcitation, generators, paulixops, paulix_sector, - wire_order=H.wires, op_wires=double) for double in doubles -] -tapered_singles = [ - qml.taper_operation(qml.SingleExcitation, generators, paulixops, paulix_sector, - wire_order=H.wires, op_wires=single) for single in singles -] - -dev = qml.device("lightning.qubit", wires=H_tapered.wires) - -@qml.qnode(dev, interface="jax") -def tapered_circuit(params): - qml.BasisState(state_tapered, wires=H_tapered.wires) - for idx, tapered_op in enumerate(tapered_doubles + tapered_singles): - tapered_op(params[idx]) - return qml.expval(H_tapered) - -############################################################################## -# We define an optimizer and the initial values of the circuit parameters and optimize the circuit -# parameters with respect to the ground state energy. - -import optax -import catalyst - -opt = optax.sgd(learning_rate=0.8) # sgd stands for StochasticGradientDescent -init_params = jnp.zeros(len(doubles) + len(singles)) - -def update_step(i, params, opt_state): - """Perform a single gradient update step""" - grads = catalyst.grad(tapered_circuit)(params) - updates, opt_state = opt.update(grads, opt_state) - params = optax.apply_updates(params, updates) - return (params, opt_state) - -loss_history = [] - -opt_state = opt.init(init_params) -params = init_params - -for i in range(1, 41): - params, opt_state = update_step(i, params, opt_state) - energy = tapered_circuit(params) - if not i % 5: - print(f"n: {i}, E: {energy:.8f} Ha, Params: {params}") - -############################################################################## -# The computed energy matches the FCI energy, :math:`-2.862595242378` Ha, while the number of qubits -# and the number of Hamiltonian terms are significantly reduced with respect to their original -# values. -# -# Conclusions -# ----------- -# Molecular Hamiltonians possess symmetries that can be leveraged to reduce the number of qubits -# required in quantum computing simulations. This tutorial introduces a PennyLane functionality that -# can be used for qubit tapering based on :math:`\mathbb{Z}_2` symmetries. The procedure includes -# obtaining tapered Hamiltonians and tapered reference states that can be used in variational -# quantum algorithms such as VQE. -# -# References -# ---------- -# -# .. [#bravyi2017] -# -# Sergey Bravyi, Jay M. Gambetta, Antonio Mezzacapo, Kristan Temme, "Tapering off qubits to -# simulate fermionic Hamiltonians". `arXiv:1701.08213 `__ -# -# .. [#setia2019] -# -# Kanav Setia, Richard Chen, Julia E. Rice, Antonio Mezzacapo, Marco Pistoia, James Whitfield, -# "Reducing qubit requirements for quantum simulation using molecular point group symmetries". -# `arXiv:1910.14644 `__ -# -# -# -# About the author -# ---------------- -# +r""" + +Qubit tapering +============== + +.. meta:: + :property="og:description": Learn how to taper off qubits + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qubit_tapering.png + +.. related:: + tutorial_quantum_chemistry Building molecular Hamiltonians + tutorial_vqe A brief overview of VQE + tutorial_givens_rotations Givens rotations for quantum chemistry + tutorial_adaptive_circuits Adaptive circuits for quantum chemistry + tutorial_differentiable_HF Differentiable Hartree-Fock + +*Authors: Utkarsh Azad and Soran Jahangiri. Posted: 16 May 2022. Last updated: 08 Nov 2022* + +The performance of variational quantum algorithms is considerably limited by the number of qubits +required to represent wave functions. In the context of quantum chemistry, this +limitation hinders the treatment of large molecules with algorithms such as the :doc:`variational quantum +eigensolver (VQE) `. Several approaches have been developed to reduce the qubit requirements for +quantum chemistry calculations. In this tutorial, we demonstrate the symmetry-based qubit +tapering approach which allows reducing the number of qubits required to perform molecular quantum +simulations based on the :math:`\mathbb{Z}_2` symmetries present in molecular Hamiltonians +[#bravyi2017]_ [#setia2019]_. + +A molecular Hamiltonian in the qubit basis can be expressed as a linear combination of Pauli words +as + +.. math:: H = \sum_{i=1}^r h_i P_i, + +where :math:`h_i` is a real coefficient and :math:`P_i` is a tensor product of Pauli and +identity operators acting on :math:`M` qubits + +.. math:: P_i \in \pm \left \{ I, X, Y, Z \right \} ^ {\bigotimes M}. + +The main idea in the symmetry-based qubit tapering approach is to find a unitary operator :math:`U` +that transforms :math:`H` to a new Hamiltonian :math:`H'` which has the same eigenvalues as +:math:`H` + +.. math:: H' = U^{\dagger} H U = \sum_{i=1}^r c_i \mu_i, + +such that each :math:`\mu_i` term in the new Hamiltonian always acts trivially, e.g., with an +identity or a Pauli operator, on a set of qubits. This allows tapering-off those qubits from the +Hamiltonian. + +For instance, consider the following Hamiltonian + +.. math:: H' = Z_0 X_1 - X_1 + Y_0 X_1, + +where all terms in the Hamiltonian act on the second qubit with the :math:`X` operator. It is +straightforward to show that each term in the Hamiltonian commutes with :math:`I_0 X_1` and the +ground-state eigenvector of :math:`H'` is also an eigenvector of :math:`I_0 X_1` with eigenvalues +:math:`\pm 1.` We can also rewrite the Hamiltonian as + +.. math:: H' = (Z_0 I_1 - I_0 I_1 + Y_0 I_1) I_0 X_1, + +which gives us + +.. math:: H'|\psi \rangle = \pm1 (Z_0 I_1 - I_0 I_1 + Y_0 I_1)|\psi \rangle, + +where :math:`|\psi \rangle` is an eigenvector of :math:`H'.` This means that the Hamiltonian +:math:`H` can be simplified as + +.. math:: H_{tapered} = \pm1 (Z_0 - I_0 + Y_0). + +The tapered Hamiltonian :math:`H_{tapered}` has the eigenvalues + +.. math:: [-2.41421, 0.41421], + +and + +.. math:: [2.41421, -0.41421], + +depending on the value of the :math:`\pm 1` prefactor. The eigenvalues of the original Hamiltonian +:math:`H` are + +.. math:: [2.41421, -2.41421, 0.41421, -0.41421], + +which are thus reproduced by the tapered Hamiltonian. + +More generally, we can construct the unitary :math:`U` such that each :math:`\mu_i` term acts with a +Pauli-X operator on a set of qubits +:math:`\left \{ j \right \}, j \in \left \{ l, ..., k \right \}` where :math:`j` is the qubit label. +This guarantees that each term of the transformed Hamiltonian commutes with each of the Pauli-X +operators applied to the :math:`j`-th qubit: + +.. math:: [H', X^j] = 0, + +and the eigenvectors of the transformed Hamiltonian :math:`H'` are also eigenvectors of each of the +:math:`X^{j}` operators. Then we can factor out all of the :math:`X^{j}` operators from the +transformed Hamiltonian and replace them with their eigenvalues :math:`\pm 1.` This gives us a +set of tapered Hamiltonians depending on which eigenvalue :math:`\pm 1` we chose for each of the +:math:`X^{j}` operators. For instance, in the case of two tapered qubits, we have four eigenvalue +sectors: :math:`[+1, +1]`, :math:`[-1, +1]`, :math:`[+1, -1],` :math:`[-1, -1].` In these tapered +Hamiltonians, the set of :math:`\left \{ j \right \}, j \in \left \{ l, ..., k \right \}` qubits +are eliminated. For tapered molecular Hamiltonians, it is possible to determine the optimal sector +of the eigenvalues that corresponds to the ground state. This is explained in more detail in the +following sections. + +The unitary operator :math:`U` can be constructed as a +`Clifford `__ operator [#bravyi2017]_ + +.. math:: U = \Pi_j \left [\frac{1}{\sqrt{2}} \left (X^{q(j)} + \tau_j \right) \right], + +where :math:`\tau` denotes the generators of the symmetry group of :math:`H` and +:math:`X^{q}` operators act on those qubits that will be ultimately tapered off from +the Hamiltonian. The symmetry group of the Hamiltonian is defined as an Abelian group of Pauli words that commute +with each term in the Hamiltonian (excluding :math:`−I`). The +`generators `__ of the symmetry group are +those elements of the group that can be combined, along with their inverses, to create any other +member of the group. + +Let's use the qubit tapering method and obtain the ground state energy of the `Helium hydride +cation `__ :math:`\textrm{HeH}^+.` + +Tapering the molecular Hamiltonian +---------------------------------- + +In PennyLane, a :doc:`molecular Hamiltonian ` can be created by specifying the atomic symbols and +coordinates. +""" +import pennylane as qml +from jax import numpy as jnp +import jax + +jax.config.update("jax_enable_x64", True) +symbols = ["He", "H"] +geometry = jnp.array([[0.00000000, 0.00000000, -0.87818361], + [0.00000000, 0.00000000, 0.87818362]]) + +molecule = qml.qchem.Molecule(symbols, geometry, charge=1) +H, qubits = qml.qchem.molecular_hamiltonian(molecule) +H + +############################################################################## +# This Hamiltonian contains 27 terms where each term acts on up to four qubits. +# +# We can now obtain the symmetry generators and the :math:`X^{j}` operators that are +# used to construct the unitary :math:`U` operator that transforms the :math:`\textrm{HeH}^+` +# Hamiltonian. In PennyLane, these are constructed by using the +# :func:`~.pennylane.symmetry_generators` and :func:`~.pennylane.paulix_ops` functions. + +generators = qml.symmetry_generators(H) +paulixops = qml.paulix_ops(generators, qubits) + +for idx, generator in enumerate(generators): + print(f"generator {idx+1}: {generator}, paulix_op: {paulixops[idx]}") + +############################################################################## +# Once the operator :math:`U` is applied, each of the Hamiltonian terms will act on the qubits +# :math:`q_2, q_3` either with the identity or with a Pauli-X operator. For each of these qubits, +# we can simply replace the Pauli-X operator with one of its eigenvalues :math:`+1` or :math:`-1.` +# This results in a total number of :math:`2^k` Hamiltonians, where :math:`k` is the number of +# tapered-off qubits and each Hamiltonian corresponds to one eigenvalue sector. The optimal sector +# corresponding to the ground-state energy of the molecule can be obtained by using the +# :func:`~.pennylane.qchem.optimal_sector` function. + + +n_electrons = 2 +paulix_sector = qml.qchem.optimal_sector(H, generators, n_electrons) +print(paulix_sector) + +############################################################################## +# The optimal eigenvalues are :math:`-1, -1` for qubits :math:`q_2, q_3,` respectively. We can now +# build the tapered Hamiltonian with the :func:`~.pennylane.taper` function which +# constructs the operator :math:`U,` applies it to the Hamiltonian and finally tapers off the +# qubits :math:`q_2, q_3` by replacing the Pauli-X operators acting on those qubits with the optimal +# eigenvalues. + +H_tapered = qml.taper(H, generators, paulixops, paulix_sector) +H_tapered_coeffs, H_tapered_ops = H_tapered.terms() +H_tapered = qml.Hamiltonian(jnp.real(jnp.array(H_tapered_coeffs)), H_tapered_ops) +print(H_tapered) + +############################################################################## +# The new Hamiltonian has only 9 non-zero terms acting on only 2 qubits! We can verify that the +# original and the tapered Hamiltonian both give the correct ground state energy of the +# :math:`\textrm{HeH}^+` cation, which is :math:`-2.862595242378` Ha computed with the full +# configuration interaction (FCI) method. In PennyLane, it's possible to build a sparse matrix +# representation of Hamiltonians. This allows us to directly diagonalize them to obtain exact values +# of the ground-state energies. + +H_sparse = qml.SparseHamiltonian(H.sparse_matrix(), wires=H.wires) +H_tapered_sparse = qml.SparseHamiltonian(H_tapered.sparse_matrix(), wires=H_tapered.wires) + +print("Eigenvalues of H:\n", qml.eigvals(H_sparse, k=16)) +print("\nEigenvalues of H_tapered:\n", qml.eigvals(H_tapered_sparse, k=4)) + +############################################################################## +# Note that a second-quantized Hamiltonian is independent of the number of electrons and its +# eigenspectrum contains the energies of the neutral and charged molecules. Therefore, the +# smallest eigenvalue returned by :func:`~.pennylane.eigvals` for a molecular Hamiltonian +# might correspond to the neutral or charged molecule. While in the case of :math:`\textrm{HeH}^+,` +# qubit tapering allows specifying the optimal sector of the eigenvectors corresponding only to the +# correct number of electrons, it is generally guaranteed that the optimal sector covers all +# eigenvectors with the correct number of electrons, but may contain additional eigenvectors of +# different charge. Therefore, the ground-state energy of the :math:`\textrm{HeH}^+` cation is +# the smallest eigenvalue of the tapered Hamiltonian. +# +# Tapering the reference state +# ---------------------------- +# The ground state Hartree-Fock energy of :math:`\textrm{HeH}^+` can be computed by directly +# applying the Hamiltonians to the Hartree-Fock state. For the tapered Hamiltonian, this requires +# transforming the Hartree-Fock state with the same symmetries obtained for the original +# Hamiltonian. This reduces the number of qubits in the Hartree-Fock state to match that of the +# tapered Hamiltonian. It can be done with the :func:`~.pennylane.qchem.taper_hf` function. + +state_tapered = qml.qchem.taper_hf(generators, paulixops, paulix_sector, + num_electrons=n_electrons, num_wires=len(H.wires)) +print(state_tapered) + +############################################################################## +# Recall that the original Hartree-Fock state for the :math:`\textrm{HeH}^+` cation is +# :math:`[1 1 0 0].` We can now generate the qubit representation of these states and compute the +# Hartree-Fock energies for each Hamiltonian. + +dev = qml.device("default.qubit", wires=H.wires) +@qml.qnode(dev, interface="jax") +def circuit(): + qml.BasisState(jnp.array([1, 1, 0, 0]), wires=H.wires) + return qml.state() + +qubit_state = circuit() +HF_energy = qubit_state.T @ H.sparse_matrix().toarray() @ qubit_state +print(f"HF energy: {jnp.real(HF_energy):.8f} Ha") + +dev = qml.device("lightning.qubit", wires=H_tapered.wires) +@qml.qnode(dev, interface="jax") +def circuit(): + qml.BasisState(jnp.array([1, 1]), wires=H_tapered.wires) + return qml.state() + +qubit_state = circuit() +HF_energy = qubit_state.T @ H_tapered.sparse_matrix().toarray() @ qubit_state +print(f"HF energy (tapered): {jnp.real(HF_energy):.8f} Ha") + +############################################################################## +# These values are identical to the reference Hartree-Fock energy :math:`-2.8543686493` Ha. +# +# VQE simulation +# -------------- +# Finally, we can use the tapered Hamiltonian and the tapered reference state to perform a VQE +# simulation and compute the ground-state energy of the :math:`\textrm{HeH}^+` cation. We build a +# tapered variational ansatz `[3] `__ +# that prepares an entangled state by evolving the tapered Hartree-Fock state using the tapered +# particle-conserving gates, i.e., the :func:`~.pennylane.SingleExcitation` and +# :func:`~.pennylane.DoubleExcitation` operations tapered using +# :func:`~.pennylane.qchem.taper_operation`. + +singles, doubles = qml.qchem.excitations(n_electrons, len(H.wires)) +tapered_doubles = [ + qml.taper_operation(qml.DoubleExcitation, generators, paulixops, paulix_sector, + wire_order=H.wires, op_wires=double) for double in doubles +] +tapered_singles = [ + qml.taper_operation(qml.SingleExcitation, generators, paulixops, paulix_sector, + wire_order=H.wires, op_wires=single) for single in singles +] + +dev = qml.device("lightning.qubit", wires=H_tapered.wires) + +@qml.qnode(dev, interface="jax") +def tapered_circuit(params): + qml.BasisState(state_tapered, wires=H_tapered.wires) + for idx, tapered_op in enumerate(tapered_doubles + tapered_singles): + tapered_op(params[idx]) + return qml.expval(H_tapered) + +############################################################################## +# We define an optimizer and the initial values of the circuit parameters and optimize the circuit +# parameters with respect to the ground state energy. + +import optax +import catalyst + +opt = optax.sgd(learning_rate=0.8) # sgd stands for StochasticGradientDescent +init_params = jnp.zeros(len(doubles) + len(singles)) + +def update_step(i, params, opt_state): + """Perform a single gradient update step""" + grads = catalyst.grad(tapered_circuit)(params) + updates, opt_state = opt.update(grads, opt_state) + params = optax.apply_updates(params, updates) + return (params, opt_state) + +loss_history = [] + +opt_state = opt.init(init_params) +params = init_params + +for i in range(1, 41): + params, opt_state = update_step(i, params, opt_state) + energy = tapered_circuit(params) + if not i % 5: + print(f"n: {i}, E: {energy:.8f} Ha, Params: {params}") + +############################################################################## +# The computed energy matches the FCI energy, :math:`-2.862595242378` Ha, while the number of qubits +# and the number of Hamiltonian terms are significantly reduced with respect to their original +# values. +# +# Conclusions +# ----------- +# Molecular Hamiltonians possess symmetries that can be leveraged to reduce the number of qubits +# required in quantum computing simulations. This tutorial introduces a PennyLane functionality that +# can be used for qubit tapering based on :math:`\mathbb{Z}_2` symmetries. The procedure includes +# obtaining tapered Hamiltonians and tapered reference states that can be used in variational +# quantum algorithms such as VQE. +# +# References +# ---------- +# +# .. [#bravyi2017] +# +# Sergey Bravyi, Jay M. Gambetta, Antonio Mezzacapo, Kristan Temme, "Tapering off qubits to +# simulate fermionic Hamiltonians". `arXiv:1701.08213 `__ +# +# .. [#setia2019] +# +# Kanav Setia, Richard Chen, Julia E. Rice, Antonio Mezzacapo, Marco Pistoia, James Whitfield, +# "Reducing qubit requirements for quantum simulation using molecular point group symmetries". +# `arXiv:1910.14644 `__ +# +# +# +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_qutrits_bernstein_vazirani/demo.py b/demonstrations_v2/tutorial_qutrits_bernstein_vazirani/demo.py index 2664509a1f..e2da31eb6e 100644 --- a/demonstrations_v2/tutorial_qutrits_bernstein_vazirani/demo.py +++ b/demonstrations_v2/tutorial_qutrits_bernstein_vazirani/demo.py @@ -1,409 +1,409 @@ -r""" - -Qutrits and quantum algorithms -============================== - -.. meta:: - :property="og:description": Learn how to interpret the Bernstein-Vazirani algorithm with qutrits - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_qutrits_bernstein_vazirani.png - -.. related:: - - -*Author: Guillermo Alonso-Linaje — Posted: 9 May 2023. Last updated: 9 May 2023.* - -A qutrit is a basic quantum unit that can exist in a superposition of three possible quantum states, represented as :math:`|0\rangle`, :math:`|1\rangle`, and :math:`|2\rangle,` which functions as a generalization of the qubit. -There are many problems to which we can apply these units, among which we can highlight an improved decomposition of the Toffoli gate. -Using only qubits, it would take at least 6 CNOTs to decompose the gate, whereas with qutrits it would be enough to use 3 [#toffoli_qutrits]_. -This is one of the reasons why it is important to develop the intuition behind this basic unit of information, to see where qutrits can provide an advantage. The goal of this demo is to start working with qutrits from an algorithmic point of view. To do so, we will start with the Bernstein–Vazirani algorithm, which we will initially explore using qubits, and later using qutrits. - - - -Bernstein–Vazirani algorithm ------------------------------- - -The Bernstein–Vazirani algorithm is a quantum algorithm developed by Ethan Bernstein and Umesh Vazirani [#bv]_. -It was one of the first examples that demonstrated an exponential advantage of a quantum computer over a traditional one. So, in this first section we will understand the problem that they tackled. - - -Suppose there is some hidden bit string "a" that we are trying to learn, and that we have access to a function :math:`f(\vec{x})` that implements the following scalar product: - -.. math:: - f(\vec{x}) := \vec{a}\cdot\vec{x} \pmod 2, - -where :math:`\vec{a}=(a_0,a_1,...,a_{n-1})` and :math:`\vec{x}=(x_0,x_1,...,x_{n-1})` are bit strings of length :math:`n` with :math:`a_i, x_i \in \{0,1\}`. Our challenge will be to discover the hidden value of :math:`\vec{a}` by using the function :math:`f.` We don't know anything about :math:`\vec{a}` so the only thing we can do is to evaluate :math:`f` at different points :math:`\vec{x}` with the idea of gaining hidden information. - - -To give an example, let's imagine that we take :math:`\vec{x}=(1,0,1)` and get the value :math:`f(\vec{x}) = 0`. Although it may not seem obvious, knowing the structure that :math:`f` has, this gives us some information about :math:`\vec{a}.` In this case, :math:`a_0` and :math:`a_2` have the same value. This is because taking that value of :math:`\vec{x}`, the function will be equivalent to :math:`a_0 + a_2 \pmod 2,` which will only take the value 0 if they are equal. -I invite you to take your time to think of a possible strategy (at the classical level) in order to determine :math:`\vec{a}` with the minimum number of evaluations of the function :math:`f.` - -The optimal solution requires only :math:`n` calls to the function! Let's see how we can do this. -Knowing the form of :math:`\vec{a}` and :math:`\vec{x},` we can rewrite :math:`f` as: - -.. math:: - f(\vec{x})=\sum_{i=0}^{n-1}a_ix_i \pmod 2. - -The strategy will be to deduce one element of :math:`\vec{a}` with each call to the function. Imagine that we want to determine the value :math:`a_i.` We can simply choose :math:`\vec{x}` as a vector of all zeros except a one in the i-th position, since in this case: - - - -.. math:: - f(\vec{x})= 0\cdot a_0 + 0\cdot a_1 + ... + 1\cdot a_i + ... + 0\cdot a_{n-1} \pmod 2 \quad= a_i. - -It is trivial to see, therefore, that :math:`n` evaluations of :math:`f` are needed. The question is: can we do it more efficiently with a quantum computer? The answer is yes, and in fact, we only need to make one call to the function! - -The first step is to see how we can represent this statement in a circuit. In this case, we will assume that we have an oracle :math:`U_f` that encodes the function, as we can see in the picture below. - - -.. figure:: ../_static/demonstration_assets/qutrits_bernstein_vazirani/oracle_qutrit.jpg - :scale: 35% - :alt: Oracle definition. - :align: center - - Oracle representation of the function. - - -In general, :math:`U_f` sends the state :math:`|\vec{x} \rangle |y\rangle` to the state :math:`| \vec{x} \rangle |y + \vec{a} \cdot \vec{x} \pmod{2} \rangle.` - -Suppose, for example, that :math:`\vec{a}=[0,1,0]`. Then :math:`U_f|111\rangle |0\rangle = |111\rangle|1\rangle`, since we are evaluating :math:`f` at the point :math:`\vec{x} = [1,1,1]`. The scalar product between the two values is :math:`1,` so the last qubit of the output will take the value :math:`1.` - -The Bernstein–Vazirani algorithm makes use of this oracle according to the following circuit: - -.. figure:: ../_static/demonstration_assets/qutrits_bernstein_vazirani/bernstein_vazirani_algorithm.jpg - :scale: 35% - :alt: Bernstein-Vazirani's algorithm - :align: center - - Bernstein–Vazirani algorithm. - - -What we can see is that, by simply using Hadamard gates before and after the oracle, after a single run, the output of the circuit is exactly the hidden value of :math:`\vec{a}.` Let's do a little math to verify that this is so. - -First, the input to our circuit is :math:`|0001\rangle.` The second step is to apply Hadamard gates to this state, and for this we must use the following property: - -.. math:: - H^{\otimes n}|\vec{x}\rangle = \frac{1}{\sqrt{2^n}}\sum_{\vec{z} \in \{0,1\}^n}(-1)^{\vec{x}\cdot\vec{z}}|\vec{z}\rangle. - -Taking as input the value :math:`|0001\rangle,` we obtain the state - -.. math:: - |\phi_1\rangle=H^{\otimes 4}|0001\rangle = H^{\otimes 3}|000\rangle\otimes H|1\rangle = \frac{1}{\sqrt{2^3}}\left(\sum_{z \in \{0,1\}^3}|\vec{z}\rangle\right)\left(\frac{|0\rangle-|1\rangle}{\sqrt{2}}\right). - -As you can see, we have separated the first three qubits from the fourth for clarity. -If we now apply our operator :math:`U_f,` - -.. math:: - |\phi_2\rangle= U_f |\phi_1\rangle = \frac{1}{\sqrt{2^3}}\left(\sum_{\vec{z} \in \{0,1\}^3}|\vec{z}\rangle\frac{|\vec{a}\cdot\vec{z} \pmod 2\rangle-|1 + \vec{a}\cdot\vec{z} \pmod 2\rangle}{\sqrt{2}}\right). - -Depending on the value of :math:`f(\vec{x}),` the final part of the expression can take two values and it can be checked that - -.. math:: - |\phi_2\rangle = \frac{1}{\sqrt{2^3}}\left(\sum_{\vec{z} \in \{0,1\}^3}|\vec{z}\rangle(-1)^{\vec{a}\cdot\vec{z}}\frac{|0\rangle-|1\rangle}{\sqrt{2}}\right). - -This is because, if :math:`\vec{a}\cdot\vec{z}` takes the value :math:`0`, we will have the :math:`\frac{|0\rangle - |1\rangle}{\sqrt{2}}`, and if it takes the value :math:`1,` the result will be :math:`\frac{|1\rangle - |0\rangle}{\sqrt{2}} = - \frac{|0\rangle - |1\rangle}{\sqrt{2}}.` Therefore, by calculating :math:`(-1)^{\vec{a}\cdot\vec{z}}` we cover both cases. -After this, we can include the :math:`(-1)^{\vec{a}\cdot\vec{z}}` factor in the :math:`|\vec{z}\rangle` term and disregard the last qubit, since we are not going to use it again: - -.. math:: - |\phi_2\rangle =\frac{1}{\sqrt{2^3}}\sum_{\vec{z} \in \{0,1\}^3}(-1)^{\vec{a}\cdot\vec{z}}|\vec{z}\rangle. - -Note that you cannot always disregard a qubit. In cases where there is entanglement with other qubits that is not possible, but in this case the state is separable. - -Finally, we will reapply the property of the first step to calculate the result after using the Hadamard: - -.. math:: - |\phi_3\rangle = H^{\otimes 3}|\phi_2\rangle = \frac{1}{2^3}\sum_{\vec{z} \in \{0,1\}^3}(-1)^{\vec{a}\cdot\vec{z}}\left(\sum_{\vec{y} \in \{0,1\}^3}(-1)^{\vec{z}\cdot\vec{y}}|\vec{y}\rangle\right). - -Rearranging this expression, we obtain: - -.. math:: - |\phi_3\rangle = \frac{1}{2^3}\sum_{\vec{y} \in \{0,1\}^3}\left(\sum_{\vec{z} \in \{0,1\}^3}(-1)^{\vec{a}\cdot\vec{z}+\vec{y}\cdot\vec{z}}\right)|\vec{y}\rangle. - -Perfect! The only thing left to check is that, in fact, the previous state is exactly :math:`|\vec{a}\rangle`. It may seem complicated, but I invite you to demonstrate it by showing that :math:`\langle \vec{a}|\phi_3\rangle = 1.` Let's go to the code and check that it works. - -Algorithm coding with qubits ------------------------------- - -We will first code the classical solution. We will do this inside a quantum circuit to understand how to use the oracle, but we are really just programming the qubits as bits. - -""" - - -import pennylane as qml - -dev = qml.device("default.qubit", wires = 4, shots = 1) - -def Uf(): - # The oracle in charge of encoding a hidden "a" value. - qml.CNOT(wires=[1, 3]) - qml.CNOT(wires=[2 ,3]) - - -@qml.qnode(dev) -def circuit0(): - """Circuit used to derive a0""" - - - # Initialize x = [1,0,0] - qml.PauliX(wires = 0) - - # Apply our oracle - - Uf() - - # We measure the last qubit - return qml.sample(wires = 3) - -@qml.qnode(dev) -def circuit1(): - # Circuit used to derive a1 - - # Initialize x = [0,1,0] - qml.PauliX(wires = 1) - - # We apply our oracle - Uf() - - # We measure the last qubit - return qml.sample(wires = 3) - -@qml.qnode(dev) -def circuit2(): - # Circuit used to derive a2 - # Initialize x = [0,0,1] - qml.PauliX(wires = 2) - - # We apply our oracle - Uf() - - # We measure the last qubit - return qml.sample(wires = 3) - -# We run for x = [1,0,0] -a0 = circuit0() - -# We run for x = [0,1,0] -a1 = circuit1() - -# We run for x = [0,0,1] -a2 = circuit2() - -print(f"The value of 'a' is [{a0},{a1},{a2}]") - -############################################################################## -# -# In this case, with 3 queries (:math:`n=3`), we have discovered the value of :math:`\vec{a}.` Let's run the Bernstein–Vazirani subroutine (using qubits as qubits this time) to check that one call is enough: - - -@qml.qnode(dev) -def circuit(): - - # We initialize to |0001> - qml.PauliX(wires = 3) - - # We run the Hadamards - for i in range(4): - qml.Hadamard(wires = i) - - # We apply our function - Uf() - - # We run the Hadamards - for i in range(3): - qml.Hadamard(wires = i) - - # We measure the first 3 qubits - return qml.sample(wires = range(3)) - -a = circuit() - -print(f"The value of a is {a}") - - -############################################################################## -# Great! Everything works as expected, and we have successfully executed the Bernstein–Vazirani algorithm. -# It is important to note that, because of how we defined our device, we are only using a single shot to find this value! -# -# Generalization to qutrits -# ------------------------------ -# -# To make things more interesting, let's imagine a new scenario. We are given a function of the form :math:`f(\vec{x}) := \vec{a}\cdot\vec{x} \pmod 3` where, :math:`\vec{a}=(a_0,a_1,...,a_{n-1})` and :math:`\vec{x}=(x_0,x_1,...,x_{n-1})` are strings of length :math:`n` with :math:`a_i, x_i \in \{0,1,2\}`. How can we minimize the number of calls to the function to discover :math:`\vec{a}?` In this case, the classical procedure to detect the value of :math:`\vec{a}` is the same as in the case of qubits: we will evaluate the output of the inputs :math:`[1,0,0],` :math:`[0,1,0]` and :math:`[0,0,1].` -# -# But how can we work with these kinds of functions in a simple way? To do this we must use a qutrit and its operators. -# By using this new unit of information and unlocking the third orthogonal state, we will have states represented with a vector of dimension :math:`3^n` and the operators will be :math:`3^n \times 3^n` matrices where :math:`n` is the number of qutrits. -# Specifically, we will use the :class:`~.pennylane.TShift` gate, which is equivalent to the :class:`~.pennylane.PauliX` gate for qutrits. It has the following property: -# -# .. math:: -# \text{TShift}|0\rangle = |1\rangle -# -# .. math:: -# \text{TShift}|1\rangle = |2\rangle -# -# .. math:: -# \text{TShift}|2\rangle = |0\rangle -# -# This means we can use this gate to initialize each of the states. -# Another gate that we will use for the oracle definition is the :class:`~.pennylane.TAdd` gate, which is the generalization of the :class:`~.pennylane.Toffoli` gate for qutrits. -# These generalizations simply adjust the addition operation to be performed in modulo 3 instead of modulo 2. -# So, with these ingredients, we are ready to go to the code. - -dev = qml.device("default.qutrit", wires=4, shots=1) - -def Uf(): - # The oracle in charge of encoding a hidden "a" value. - qml.TAdd(wires = [1,3]) - qml.TAdd(wires = [1,3]) - qml.TAdd(wires = [2,3]) - -@qml.qnode(dev) -def circuit0(): - - # Initialize x = [1,0,0] - qml.TShift(wires = 0) - - # We apply our oracle - Uf() - - # We measure the last qutrit - return qml.sample(wires = 3) - -@qml.qnode(dev) -def circuit1(): - - # Initialize x = [0,1,0] - qml.TShift(wires = 1) - - # We apply our oracle - Uf() - - # We measure the last qutrit - return qml.sample(wires = 3) - -@qml.qnode(dev) -def circuit2(): - - # Initialize x = [0,0,1] - qml.TShift(wires = 2) - - # We apply our oracle - Uf() - - # We measure the last qutrit - return qml.sample(wires = 3) - -# Run to obtain the three trits of a -a0 = circuit0() -a1 = circuit1() -a2 = circuit2() - - -print(f"The value of a is [{a0},{a1},{a2}]") - -############################################################################## -# -# The question is, can we perform the same procedure as we have done before to find :math:`\vec{a}` using a single shot? The Hadamard gate also generalizes to qutrits (also denoted as :class:`~.pennylane.THadamard`), so we could try to simply substitute it and see what happens! -# -# -# The definition of the Hadamard gate in this space is: -# -# .. math:: -# \text{THadamard}=\frac{-i}{\sqrt{3}}\begin{pmatrix} -# 1 & 1 & 1\\ -# 1 & w & w^2\\ -# 1 & w^2 & w -# \end{pmatrix}, -# -# where :math:`w = e^{\frac{2 \pi i}{3}}.` -# Let's go to the code and see how to run this in PennyLane. - - -@qml.qnode(dev) -def circuit(): - - # We initialize to |0001> - qml.TShift(wires = 3) - - # We run the THadamard - for i in range(4): - qml.THadamard(wires = i) - -# We run the oracle - Uf() - -# We run the THadamard again - for i in range(3): - qml.THadamard(wires = i) - - # We measure the first 3 qutrits - return qml.sample(wires = range(3)) - -a = circuit() - -print(f"The value of a is {a}") - -############################################################################## -# -# Awesome! The Bernstein–Vazirani algorithm generalizes perfectly to qutrits! Let's do the mathematical calculations again to check that it does indeed make sense. -# -# As before, the input of our circuit is :math:`|0001\rangle.` -# We will then use the Hadamard definition applied to qutrits: -# -# .. math:: -# H^{\otimes n}|\vec{x}\rangle = \frac{1}{\sqrt{3^n}}\sum_{\vec{z} \in \{0,1,2\}^n}w^{\vec{x}\cdot\vec{z}}|\vec{z}\rangle. -# -# In this case, we are disregarding the global phase of :math:`-i` for simplicity. -# Applying this to the state :math:`|0001\rangle,` we obtain -# -# .. math:: -# |\phi_1\rangle=H^{\otimes 4}|0001\rangle = H^{\otimes 3}|000\rangle\otimes H|1\rangle = \frac{1}{\sqrt{3^3}}\left(\sum_{z \in \{0,1,2\}^3}|\vec{z}\rangle\frac{|0\rangle+w|1\rangle+w^2|2\rangle}{\sqrt{3}}\right). -# -# After that, we apply the operator :math:`U_f` to obtain -# -# .. math:: -# |\phi_2\rangle= U_f |\phi_1\rangle = \frac{1}{\sqrt{3^3}}\left(\sum_{\vec{z} \in \{0,1,2\}^3}|\vec{z}\rangle\frac{|0 + \vec{a}\cdot\vec{z} \pmod 3 \rangle+w|1+ \vec{a}\cdot\vec{z} \pmod 3 \rangle+w^2|2+ \vec{a}\cdot\vec{z} \pmod 3 \rangle}{\sqrt{3}}\right). -# -# Depending on the value of :math:`f(\vec{x}),` as before, we obtain three possible states: -# -# - If :math:`\vec{a}\cdot\vec{z} = 0,` we have :math:`\frac{1}{\sqrt{3}}\left(|0\rangle+w|1\rangle+w^2|2\rangle\right).` -# - If :math:`\vec{a}\cdot\vec{z} = 1,` we have :math:`\frac{w^2}{\sqrt{3}}\left(|0\rangle+|1\rangle+w|2\rangle\right).` -# - If :math:`\vec{a}\cdot\vec{z} = 2,` we have :math:`\frac{w}{\sqrt{3}}\left(|0\rangle+w^2|1\rangle+|2\rangle\right).` -# -# Based on this, we can group the three states as :math:`\frac{1}{\sqrt{3}}w^{-\vec{a}\cdot\vec{z}}\left(|0\rangle+w|1\rangle+w^2|2\rangle\right).` -# -# After this, we can enter the coefficient in the :math:`|\vec{z}\rangle` term and, as before, disregard the last qutrit, since we are not going to use it again: -# -# .. math:: -# |\phi_2\rangle =\frac{1}{\sqrt{3^3}}\sum_{\vec{z} \in \{0,1,2\}^3}w^{-\vec{a}\cdot\vec{z}}|\vec{z}\rangle. -# -# Finally, we reapply the THadamard: -# -# .. math:: -# |\phi_3\rangle := H^{\otimes 3}|\phi_2\rangle = \frac{1}{3^3}\sum_{\vec{z} \in \{0,1,2\}^3}w^{-\vec{a}\cdot\vec{z}}\left(\sum_{\vec{y} \in \{0,1,2\}^3}w^{\vec{z}\cdot\vec{y}}|\vec{y}\rangle\right). -# -# Rearranging this expression, we obtain: -# -# .. math:: -# |\phi_3\rangle = \frac{1}{3^3}\sum_{\vec{y} \in \{0,1,2\}^3}\left(\sum_{\vec{z} \in \{0,1,2\}^3}w^{-\vec{a}\cdot\vec{z}+\vec{y}\cdot\vec{z}}\right)|\vec{y}\rangle. -# -# -# In the same way as before, it can be easily checked that :math:`\langle \vec{a}|\phi_3\rangle = 1` and therefore, when measuring, one shot will be enough to obtain the value of :math:`\vec{a}!` -# -# Conclusion -# ---------- -# -# In this demo, we have practised the use of basic qutrit gates such as TShift or THadamard by applying the Bernstein–Vazirani algorithm. In this case, the generalization has been straightforward and we have found that it makes mathematical sense, but we cannot always substitute qubit gates for qutrit gates as we have seen in the demo. To give an easy example of this, we know the property that :math:`X = HZH`, but it turns out that this property does not generalize! The general property is actually :math:`X = H^{\dagger}ZH.` In the case of qubits it holds that :math:`H = H^{\dagger},` but in other dimensions it does not. I invite you to continue practising with other types of algorithms. For instance, will the `Deutsch–Jozsa algorithm `__ generalize well? Take a pen and paper and check it out! -# -# References -# ---------- -# -# .. [#bv] -# -# Ethan Bernstein, Umesh Vazirani, "Quantum Complexity Theory". `SIAM Journal on Computing Vol. 26 (1997). -# `__ -# -# .. [#toffoli_qutrits] -# Alexey Galda, Michael Cubeddu, Naoki Kanazawa, Prineha Narang, Nathan Earnest-Noble, `"Implementing a Ternary Decomposition of the Toffoli Gate on Fixed-FrequencyTransmon Qutrits". -# `__ -# About the author -# ---------------- -# - +r""" + +Qutrits and quantum algorithms +============================== + +.. meta:: + :property="og:description": Learn how to interpret the Bernstein-Vazirani algorithm with qutrits + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_qutrits_bernstein_vazirani.png + +.. related:: + + +*Author: Guillermo Alonso-Linaje — Posted: 9 May 2023. Last updated: 9 May 2023.* + +A qutrit is a basic quantum unit that can exist in a superposition of three possible quantum states, represented as :math:`|0\rangle`, :math:`|1\rangle`, and :math:`|2\rangle,` which functions as a generalization of the qubit. +There are many problems to which we can apply these units, among which we can highlight an improved decomposition of the Toffoli gate. +Using only qubits, it would take at least 6 CNOTs to decompose the gate, whereas with qutrits it would be enough to use 3 [#toffoli_qutrits]_. +This is one of the reasons why it is important to develop the intuition behind this basic unit of information, to see where qutrits can provide an advantage. The goal of this demo is to start working with qutrits from an algorithmic point of view. To do so, we will start with the Bernstein–Vazirani algorithm, which we will initially explore using qubits, and later using qutrits. + + + +Bernstein–Vazirani algorithm +------------------------------ + +The Bernstein–Vazirani algorithm is a quantum algorithm developed by Ethan Bernstein and Umesh Vazirani [#bv]_. +It was one of the first examples that demonstrated an exponential advantage of a quantum computer over a traditional one. So, in this first section we will understand the problem that they tackled. + + +Suppose there is some hidden bit string "a" that we are trying to learn, and that we have access to a function :math:`f(\vec{x})` that implements the following scalar product: + +.. math:: + f(\vec{x}) := \vec{a}\cdot\vec{x} \pmod 2, + +where :math:`\vec{a}=(a_0,a_1,...,a_{n-1})` and :math:`\vec{x}=(x_0,x_1,...,x_{n-1})` are bit strings of length :math:`n` with :math:`a_i, x_i \in \{0,1\}`. Our challenge will be to discover the hidden value of :math:`\vec{a}` by using the function :math:`f.` We don't know anything about :math:`\vec{a}` so the only thing we can do is to evaluate :math:`f` at different points :math:`\vec{x}` with the idea of gaining hidden information. + + +To give an example, let's imagine that we take :math:`\vec{x}=(1,0,1)` and get the value :math:`f(\vec{x}) = 0`. Although it may not seem obvious, knowing the structure that :math:`f` has, this gives us some information about :math:`\vec{a}.` In this case, :math:`a_0` and :math:`a_2` have the same value. This is because taking that value of :math:`\vec{x}`, the function will be equivalent to :math:`a_0 + a_2 \pmod 2,` which will only take the value 0 if they are equal. +I invite you to take your time to think of a possible strategy (at the classical level) in order to determine :math:`\vec{a}` with the minimum number of evaluations of the function :math:`f.` + +The optimal solution requires only :math:`n` calls to the function! Let's see how we can do this. +Knowing the form of :math:`\vec{a}` and :math:`\vec{x},` we can rewrite :math:`f` as: + +.. math:: + f(\vec{x})=\sum_{i=0}^{n-1}a_ix_i \pmod 2. + +The strategy will be to deduce one element of :math:`\vec{a}` with each call to the function. Imagine that we want to determine the value :math:`a_i.` We can simply choose :math:`\vec{x}` as a vector of all zeros except a one in the i-th position, since in this case: + + + +.. math:: + f(\vec{x})= 0\cdot a_0 + 0\cdot a_1 + ... + 1\cdot a_i + ... + 0\cdot a_{n-1} \pmod 2 \quad= a_i. + +It is trivial to see, therefore, that :math:`n` evaluations of :math:`f` are needed. The question is: can we do it more efficiently with a quantum computer? The answer is yes, and in fact, we only need to make one call to the function! + +The first step is to see how we can represent this statement in a circuit. In this case, we will assume that we have an oracle :math:`U_f` that encodes the function, as we can see in the picture below. + + +.. figure:: ../_static/demonstration_assets/qutrits_bernstein_vazirani/oracle_qutrit.jpg + :scale: 35% + :alt: Oracle definition. + :align: center + + Oracle representation of the function. + + +In general, :math:`U_f` sends the state :math:`|\vec{x} \rangle |y\rangle` to the state :math:`| \vec{x} \rangle |y + \vec{a} \cdot \vec{x} \pmod{2} \rangle.` + +Suppose, for example, that :math:`\vec{a}=[0,1,0]`. Then :math:`U_f|111\rangle |0\rangle = |111\rangle|1\rangle`, since we are evaluating :math:`f` at the point :math:`\vec{x} = [1,1,1]`. The scalar product between the two values is :math:`1,` so the last qubit of the output will take the value :math:`1.` + +The Bernstein–Vazirani algorithm makes use of this oracle according to the following circuit: + +.. figure:: ../_static/demonstration_assets/qutrits_bernstein_vazirani/bernstein_vazirani_algorithm.jpg + :scale: 35% + :alt: Bernstein-Vazirani's algorithm + :align: center + + Bernstein–Vazirani algorithm. + + +What we can see is that, by simply using Hadamard gates before and after the oracle, after a single run, the output of the circuit is exactly the hidden value of :math:`\vec{a}.` Let's do a little math to verify that this is so. + +First, the input to our circuit is :math:`|0001\rangle.` The second step is to apply Hadamard gates to this state, and for this we must use the following property: + +.. math:: + H^{\otimes n}|\vec{x}\rangle = \frac{1}{\sqrt{2^n}}\sum_{\vec{z} \in \{0,1\}^n}(-1)^{\vec{x}\cdot\vec{z}}|\vec{z}\rangle. + +Taking as input the value :math:`|0001\rangle,` we obtain the state + +.. math:: + |\phi_1\rangle=H^{\otimes 4}|0001\rangle = H^{\otimes 3}|000\rangle\otimes H|1\rangle = \frac{1}{\sqrt{2^3}}\left(\sum_{z \in \{0,1\}^3}|\vec{z}\rangle\right)\left(\frac{|0\rangle-|1\rangle}{\sqrt{2}}\right). + +As you can see, we have separated the first three qubits from the fourth for clarity. +If we now apply our operator :math:`U_f,` + +.. math:: + |\phi_2\rangle= U_f |\phi_1\rangle = \frac{1}{\sqrt{2^3}}\left(\sum_{\vec{z} \in \{0,1\}^3}|\vec{z}\rangle\frac{|\vec{a}\cdot\vec{z} \pmod 2\rangle-|1 + \vec{a}\cdot\vec{z} \pmod 2\rangle}{\sqrt{2}}\right). + +Depending on the value of :math:`f(\vec{x}),` the final part of the expression can take two values and it can be checked that + +.. math:: + |\phi_2\rangle = \frac{1}{\sqrt{2^3}}\left(\sum_{\vec{z} \in \{0,1\}^3}|\vec{z}\rangle(-1)^{\vec{a}\cdot\vec{z}}\frac{|0\rangle-|1\rangle}{\sqrt{2}}\right). + +This is because, if :math:`\vec{a}\cdot\vec{z}` takes the value :math:`0`, we will have the :math:`\frac{|0\rangle - |1\rangle}{\sqrt{2}}`, and if it takes the value :math:`1,` the result will be :math:`\frac{|1\rangle - |0\rangle}{\sqrt{2}} = - \frac{|0\rangle - |1\rangle}{\sqrt{2}}.` Therefore, by calculating :math:`(-1)^{\vec{a}\cdot\vec{z}}` we cover both cases. +After this, we can include the :math:`(-1)^{\vec{a}\cdot\vec{z}}` factor in the :math:`|\vec{z}\rangle` term and disregard the last qubit, since we are not going to use it again: + +.. math:: + |\phi_2\rangle =\frac{1}{\sqrt{2^3}}\sum_{\vec{z} \in \{0,1\}^3}(-1)^{\vec{a}\cdot\vec{z}}|\vec{z}\rangle. + +Note that you cannot always disregard a qubit. In cases where there is entanglement with other qubits that is not possible, but in this case the state is separable. + +Finally, we will reapply the property of the first step to calculate the result after using the Hadamard: + +.. math:: + |\phi_3\rangle = H^{\otimes 3}|\phi_2\rangle = \frac{1}{2^3}\sum_{\vec{z} \in \{0,1\}^3}(-1)^{\vec{a}\cdot\vec{z}}\left(\sum_{\vec{y} \in \{0,1\}^3}(-1)^{\vec{z}\cdot\vec{y}}|\vec{y}\rangle\right). + +Rearranging this expression, we obtain: + +.. math:: + |\phi_3\rangle = \frac{1}{2^3}\sum_{\vec{y} \in \{0,1\}^3}\left(\sum_{\vec{z} \in \{0,1\}^3}(-1)^{\vec{a}\cdot\vec{z}+\vec{y}\cdot\vec{z}}\right)|\vec{y}\rangle. + +Perfect! The only thing left to check is that, in fact, the previous state is exactly :math:`|\vec{a}\rangle`. It may seem complicated, but I invite you to demonstrate it by showing that :math:`\langle \vec{a}|\phi_3\rangle = 1.` Let's go to the code and check that it works. + +Algorithm coding with qubits +------------------------------ + +We will first code the classical solution. We will do this inside a quantum circuit to understand how to use the oracle, but we are really just programming the qubits as bits. + +""" + + +import pennylane as qml + +dev = qml.device("default.qubit", wires = 4, shots = 1) + +def Uf(): + # The oracle in charge of encoding a hidden "a" value. + qml.CNOT(wires=[1, 3]) + qml.CNOT(wires=[2 ,3]) + + +@qml.qnode(dev) +def circuit0(): + """Circuit used to derive a0""" + + + # Initialize x = [1,0,0] + qml.PauliX(wires = 0) + + # Apply our oracle + + Uf() + + # We measure the last qubit + return qml.sample(wires = 3) + +@qml.qnode(dev) +def circuit1(): + # Circuit used to derive a1 + + # Initialize x = [0,1,0] + qml.PauliX(wires = 1) + + # We apply our oracle + Uf() + + # We measure the last qubit + return qml.sample(wires = 3) + +@qml.qnode(dev) +def circuit2(): + # Circuit used to derive a2 + # Initialize x = [0,0,1] + qml.PauliX(wires = 2) + + # We apply our oracle + Uf() + + # We measure the last qubit + return qml.sample(wires = 3) + +# We run for x = [1,0,0] +a0 = circuit0() + +# We run for x = [0,1,0] +a1 = circuit1() + +# We run for x = [0,0,1] +a2 = circuit2() + +print(f"The value of 'a' is [{a0},{a1},{a2}]") + +############################################################################## +# +# In this case, with 3 queries (:math:`n=3`), we have discovered the value of :math:`\vec{a}.` Let's run the Bernstein–Vazirani subroutine (using qubits as qubits this time) to check that one call is enough: + + +@qml.qnode(dev) +def circuit(): + + # We initialize to |0001> + qml.PauliX(wires = 3) + + # We run the Hadamards + for i in range(4): + qml.Hadamard(wires = i) + + # We apply our function + Uf() + + # We run the Hadamards + for i in range(3): + qml.Hadamard(wires = i) + + # We measure the first 3 qubits + return qml.sample(wires = range(3)) + +a = circuit() + +print(f"The value of a is {a}") + + +############################################################################## +# Great! Everything works as expected, and we have successfully executed the Bernstein–Vazirani algorithm. +# It is important to note that, because of how we defined our device, we are only using a single shot to find this value! +# +# Generalization to qutrits +# ------------------------------ +# +# To make things more interesting, let's imagine a new scenario. We are given a function of the form :math:`f(\vec{x}) := \vec{a}\cdot\vec{x} \pmod 3` where, :math:`\vec{a}=(a_0,a_1,...,a_{n-1})` and :math:`\vec{x}=(x_0,x_1,...,x_{n-1})` are strings of length :math:`n` with :math:`a_i, x_i \in \{0,1,2\}`. How can we minimize the number of calls to the function to discover :math:`\vec{a}?` In this case, the classical procedure to detect the value of :math:`\vec{a}` is the same as in the case of qubits: we will evaluate the output of the inputs :math:`[1,0,0],` :math:`[0,1,0]` and :math:`[0,0,1].` +# +# But how can we work with these kinds of functions in a simple way? To do this we must use a qutrit and its operators. +# By using this new unit of information and unlocking the third orthogonal state, we will have states represented with a vector of dimension :math:`3^n` and the operators will be :math:`3^n \times 3^n` matrices where :math:`n` is the number of qutrits. +# Specifically, we will use the :class:`~.pennylane.TShift` gate, which is equivalent to the :class:`~.pennylane.PauliX` gate for qutrits. It has the following property: +# +# .. math:: +# \text{TShift}|0\rangle = |1\rangle +# +# .. math:: +# \text{TShift}|1\rangle = |2\rangle +# +# .. math:: +# \text{TShift}|2\rangle = |0\rangle +# +# This means we can use this gate to initialize each of the states. +# Another gate that we will use for the oracle definition is the :class:`~.pennylane.TAdd` gate, which is the generalization of the :class:`~.pennylane.Toffoli` gate for qutrits. +# These generalizations simply adjust the addition operation to be performed in modulo 3 instead of modulo 2. +# So, with these ingredients, we are ready to go to the code. + +dev = qml.device("default.qutrit", wires=4, shots=1) + +def Uf(): + # The oracle in charge of encoding a hidden "a" value. + qml.TAdd(wires = [1,3]) + qml.TAdd(wires = [1,3]) + qml.TAdd(wires = [2,3]) + +@qml.qnode(dev) +def circuit0(): + + # Initialize x = [1,0,0] + qml.TShift(wires = 0) + + # We apply our oracle + Uf() + + # We measure the last qutrit + return qml.sample(wires = 3) + +@qml.qnode(dev) +def circuit1(): + + # Initialize x = [0,1,0] + qml.TShift(wires = 1) + + # We apply our oracle + Uf() + + # We measure the last qutrit + return qml.sample(wires = 3) + +@qml.qnode(dev) +def circuit2(): + + # Initialize x = [0,0,1] + qml.TShift(wires = 2) + + # We apply our oracle + Uf() + + # We measure the last qutrit + return qml.sample(wires = 3) + +# Run to obtain the three trits of a +a0 = circuit0() +a1 = circuit1() +a2 = circuit2() + + +print(f"The value of a is [{a0},{a1},{a2}]") + +############################################################################## +# +# The question is, can we perform the same procedure as we have done before to find :math:`\vec{a}` using a single shot? The Hadamard gate also generalizes to qutrits (also denoted as :class:`~.pennylane.THadamard`), so we could try to simply substitute it and see what happens! +# +# +# The definition of the Hadamard gate in this space is: +# +# .. math:: +# \text{THadamard}=\frac{-i}{\sqrt{3}}\begin{pmatrix} +# 1 & 1 & 1\\ +# 1 & w & w^2\\ +# 1 & w^2 & w +# \end{pmatrix}, +# +# where :math:`w = e^{\frac{2 \pi i}{3}}.` +# Let's go to the code and see how to run this in PennyLane. + + +@qml.qnode(dev) +def circuit(): + + # We initialize to |0001> + qml.TShift(wires = 3) + + # We run the THadamard + for i in range(4): + qml.THadamard(wires = i) + +# We run the oracle + Uf() + +# We run the THadamard again + for i in range(3): + qml.THadamard(wires = i) + + # We measure the first 3 qutrits + return qml.sample(wires = range(3)) + +a = circuit() + +print(f"The value of a is {a}") + +############################################################################## +# +# Awesome! The Bernstein–Vazirani algorithm generalizes perfectly to qutrits! Let's do the mathematical calculations again to check that it does indeed make sense. +# +# As before, the input of our circuit is :math:`|0001\rangle.` +# We will then use the Hadamard definition applied to qutrits: +# +# .. math:: +# H^{\otimes n}|\vec{x}\rangle = \frac{1}{\sqrt{3^n}}\sum_{\vec{z} \in \{0,1,2\}^n}w^{\vec{x}\cdot\vec{z}}|\vec{z}\rangle. +# +# In this case, we are disregarding the global phase of :math:`-i` for simplicity. +# Applying this to the state :math:`|0001\rangle,` we obtain +# +# .. math:: +# |\phi_1\rangle=H^{\otimes 4}|0001\rangle = H^{\otimes 3}|000\rangle\otimes H|1\rangle = \frac{1}{\sqrt{3^3}}\left(\sum_{z \in \{0,1,2\}^3}|\vec{z}\rangle\frac{|0\rangle+w|1\rangle+w^2|2\rangle}{\sqrt{3}}\right). +# +# After that, we apply the operator :math:`U_f` to obtain +# +# .. math:: +# |\phi_2\rangle= U_f |\phi_1\rangle = \frac{1}{\sqrt{3^3}}\left(\sum_{\vec{z} \in \{0,1,2\}^3}|\vec{z}\rangle\frac{|0 + \vec{a}\cdot\vec{z} \pmod 3 \rangle+w|1+ \vec{a}\cdot\vec{z} \pmod 3 \rangle+w^2|2+ \vec{a}\cdot\vec{z} \pmod 3 \rangle}{\sqrt{3}}\right). +# +# Depending on the value of :math:`f(\vec{x}),` as before, we obtain three possible states: +# +# - If :math:`\vec{a}\cdot\vec{z} = 0,` we have :math:`\frac{1}{\sqrt{3}}\left(|0\rangle+w|1\rangle+w^2|2\rangle\right).` +# - If :math:`\vec{a}\cdot\vec{z} = 1,` we have :math:`\frac{w^2}{\sqrt{3}}\left(|0\rangle+|1\rangle+w|2\rangle\right).` +# - If :math:`\vec{a}\cdot\vec{z} = 2,` we have :math:`\frac{w}{\sqrt{3}}\left(|0\rangle+w^2|1\rangle+|2\rangle\right).` +# +# Based on this, we can group the three states as :math:`\frac{1}{\sqrt{3}}w^{-\vec{a}\cdot\vec{z}}\left(|0\rangle+w|1\rangle+w^2|2\rangle\right).` +# +# After this, we can enter the coefficient in the :math:`|\vec{z}\rangle` term and, as before, disregard the last qutrit, since we are not going to use it again: +# +# .. math:: +# |\phi_2\rangle =\frac{1}{\sqrt{3^3}}\sum_{\vec{z} \in \{0,1,2\}^3}w^{-\vec{a}\cdot\vec{z}}|\vec{z}\rangle. +# +# Finally, we reapply the THadamard: +# +# .. math:: +# |\phi_3\rangle := H^{\otimes 3}|\phi_2\rangle = \frac{1}{3^3}\sum_{\vec{z} \in \{0,1,2\}^3}w^{-\vec{a}\cdot\vec{z}}\left(\sum_{\vec{y} \in \{0,1,2\}^3}w^{\vec{z}\cdot\vec{y}}|\vec{y}\rangle\right). +# +# Rearranging this expression, we obtain: +# +# .. math:: +# |\phi_3\rangle = \frac{1}{3^3}\sum_{\vec{y} \in \{0,1,2\}^3}\left(\sum_{\vec{z} \in \{0,1,2\}^3}w^{-\vec{a}\cdot\vec{z}+\vec{y}\cdot\vec{z}}\right)|\vec{y}\rangle. +# +# +# In the same way as before, it can be easily checked that :math:`\langle \vec{a}|\phi_3\rangle = 1` and therefore, when measuring, one shot will be enough to obtain the value of :math:`\vec{a}!` +# +# Conclusion +# ---------- +# +# In this demo, we have practised the use of basic qutrit gates such as TShift or THadamard by applying the Bernstein–Vazirani algorithm. In this case, the generalization has been straightforward and we have found that it makes mathematical sense, but we cannot always substitute qubit gates for qutrit gates as we have seen in the demo. To give an easy example of this, we know the property that :math:`X = HZH`, but it turns out that this property does not generalize! The general property is actually :math:`X = H^{\dagger}ZH.` In the case of qubits it holds that :math:`H = H^{\dagger},` but in other dimensions it does not. I invite you to continue practising with other types of algorithms. For instance, will the `Deutsch–Jozsa algorithm `__ generalize well? Take a pen and paper and check it out! +# +# References +# ---------- +# +# .. [#bv] +# +# Ethan Bernstein, Umesh Vazirani, "Quantum Complexity Theory". `SIAM Journal on Computing Vol. 26 (1997). +# `__ +# +# .. [#toffoli_qutrits] +# Alexey Galda, Michael Cubeddu, Naoki Kanazawa, Prineha Narang, Nathan Earnest-Noble, `"Implementing a Ternary Decomposition of the Toffoli Gate on Fixed-FrequencyTransmon Qutrits". +# `__ +# About the author +# ---------------- +# + diff --git a/demonstrations_v2/tutorial_resource_estimation/demo.py b/demonstrations_v2/tutorial_resource_estimation/demo.py index 8deb382e9c..0c033e4dee 100644 --- a/demonstrations_v2/tutorial_resource_estimation/demo.py +++ b/demonstrations_v2/tutorial_resource_estimation/demo.py @@ -1,321 +1,321 @@ -r""" - -Resource estimation for quantum chemistry -========================================= - -.. meta:: - :property="og:description": Learn how to estimate the number of qubits and gates needed to - implement quantum algorithms - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/resource_estimation.jpeg - -.. related:: - tutorial_quantum_chemistry Quantum chemistry with PennyLane - tutorial_vqe A brief overview of VQE - - -*Author: Soran Jahangiri — Posted: 21 November 2022.* - -Quantum algorithms such as -`quantum phase estimation `_ -(QPE) and the `variational quantum eigensolver `_ (VQE) -are widely studied in quantum chemistry as potential avenues to tackle problems that are intractable -for conventional computers. However, we currently do not have quantum computers or simulators -capable of implementing large-scale -versions of these algorithms. This makes it difficult to properly explore their accuracy and -efficiency for problem sizes where the actual advantage of quantum algorithms can potentially occur. -Despite these difficulties, it is still possible to perform **resource estimation** -to assess what we need to implement such quantum algorithms. - -In this demo, we describe how to use PennyLane's resource estimation functionality to estimate the total number of logical qubits and gates required to -implement the QPE algorithm for simulating molecular Hamiltonians represented in first and second -quantization. We focus on `non-Clifford gates `_, which -are the most expensive to implement in a fault-tolerant setting. We also explain how to estimate the -total number of measurements needed to compute expectation values using algorithms such as VQE. - -Quantum Phase Estimation ------------------------- -The QPE algorithm can be used to compute the phase associated with an eigenstate of a unitary -operator. For the purpose of quantum simulation, the unitary operator :math:`U` can be chosen to -share eigenvectors with a molecular Hamiltonian :math:`H,` for example by setting -:math:`U = e^{-iH}.` Estimating the phase of such a unitary then permits recovering the -corresponding eigenvalue of the Hamiltonian. A conceptual QPE circuit diagram is shown below. - -.. figure:: /_static/demonstration_assets/resource_estimation/qpe.png - :width: 60% - :align: center - - Circuit representing the quantum phase estimation algorithm. - -For most cases of interest, this algorithm requires more qubits and longer circuit depths than what -can be implemented on existing hardware. The PennyLane functionality in the -:mod:`qml.resource ` module allows us to estimate the number of logical qubits -and the number of non-Clifford gates that are needed to implement the algorithm. We can estimate -these resources by simply defining system specifications and a target error for estimation. Let's -see how! - -QPE cost for simulating molecules -********************************* -We study the double low-rank Hamiltonian factorization algorithm of [#vonburg2021]_ and use its cost -equations as provided in APPENDIX C of [#lee2021]_. -This algorithm requires the one- and two-electron -`integrals `_ -as input. These integrals can be obtained in different ways and here we use PennyLane to compute -them. We first need to define the atomic symbols and coordinates for the given molecule. Let's use -the water molecule at its equilibrium geometry with the -`6-31g basis set `_ as an example. -""" -import pennylane as qml -import numpy as np -import jax - -jax.config.update("jax_enable_x64", True) - -symbols = ['O', 'H', 'H'] -geometry = np.array([[0.00000000, 0.00000000, 0.28377432], - [0.00000000, 1.45278171, -1.00662237], - [0.00000000, -1.45278171, -1.00662237]]) - -############################################################################## -# Then we construct a molecule object and compute the one- and two-electron -# integrals in the molecular orbital basis. - -mol = qml.qchem.Molecule(symbols, geometry, basis_name='6-31g') -core, one, two = qml.qchem.electron_integrals(mol)() - -############################################################################## -# We now create an instance of the :class:`~.pennylane.resource.DoubleFactorization` class - -algo = qml.resource.DoubleFactorization(one, two) - -############################################################################## -# and obtain the estimated number of non-Clifford gates and logical qubits. - -print(f'Estimated gates : {algo.gates:.2e} \nEstimated qubits: {algo.qubits}') - -############################################################################## -# This estimation is for a target error that is set to the chemical accuracy, 0.0016 -# :math:`\text{Ha},` by default. We can change the target error to a larger value which leads to a -# smaller number of non-Clifford gates and logical qubits. - -chemical_accuracy = 0.0016 -error = chemical_accuracy * 10 -algo = qml.resource.DoubleFactorization(one, two, error=error) -print(f'Estimated gates : {algo.gates:.2e} \nEstimated qubits: {algo.qubits}') - -############################################################################## -# We can also estimate the number of non-Clifford gates with respect to the threshold error values -# for discarding the negligible factors in the factorized Hamiltonian [#vonburg2021]_ and plot the -# estimated numbers. - -threshold = [10**-n for n in range(10)] -n_gates = [] -n_qubits = [] - -for tol in threshold: - algo_ = qml.resource.DoubleFactorization(one, two, tol_factor=tol, tol_eigval=tol) - n_gates.append(algo_.gates) - n_qubits.append(algo_.qubits) - -import matplotlib.pyplot as plt - -fig, ax = plt.subplots(figsize=(5, 3)) -ax.plot(threshold, n_gates, ':o', markerfacecolor='none', color='teal') - -ax.set_ylabel('n gates') -ax.set_xlabel('threshold') -ax.set_xscale('log') -fig.tight_layout() - -############################################################################## -# QPE cost for simulating periodic materials -# ****************************************** -# For periodic materials, we estimate the cost of implementing the QPE algorithm of [#zini2023]_ -# using Hamiltonians represented in first quantization and in a plane wave basis. We first need to -# define the number of plane waves, the number of electrons, and the lattice vectors that construct -# the unit cell of the periodic material. Let's use dilithium iron silicate -# :math:`\text{Li}_2\text{FeSiO}_4` as an example taken from [#delgado2022]_. For this material, the -# unit cell contains 156 electrons and has dimensions :math:`9.49 \times 10.20 \times 11.83` in -# `atomic units `_. We also use :math:`10^5` plane waves. - -planewaves = 100000 -electrons = 156 -vectors = np.array([[9.49, 0.00, 0.00], - [0.00, 10.20, 0.00], - [0.00, 0.00, 11.83]]) - -############################################################################## -# We now create an instance of the :class:`~.pennylane.resource.FirstQuantization` class -algo = qml.resource.FirstQuantization(planewaves, electrons, vectors=vectors) - -############################################################################## -# and obtain the estimated number of non-Clifford gates and logical qubits. -print(f'Estimated gates : {algo.gates:.2e} \nEstimated qubits: {algo.qubits}') - -############################################################################## -# We can also plot the estimated numbers as a function of the number of plane waves for different -# target errors - -error = [0.1, 0.01, 0.001] # in atomic units -planewaves = [10 ** n for n in range(1, 10)] -n_gates = [] -n_qubits = [] - -for er in error: - n_gates_ = [] - n_qubits_ = [] - - for pw in planewaves: - algo_ = qml.resource.FirstQuantization(pw, electrons, vectors=vectors, error=er) - n_gates_.append(algo_.gates) - n_qubits_.append(algo_.qubits) - n_gates.append(n_gates_) - n_qubits.append(n_qubits_) - -fig, ax = plt.subplots(2, 1) - -for i in range(len(n_gates)): - ax[0].plot(planewaves, n_gates[i], ':o', markerfacecolor='none', label=error[i]) -ax[1].plot(planewaves, n_qubits[i], ':o', markerfacecolor='none', label=error[-1]) - -ax[0].set_ylabel('n gates') -ax[1].set_ylabel('n qubits') - -for i in [0, 1]: - ax[i].set_xlabel('n planewaves') - ax[i].tick_params(axis='x') - ax[0].set_yscale('log') - ax[i].set_xscale('log') - ax[i].legend(title='error [Ha]') - -fig.tight_layout() - -############################################################################## -# The algorithm uses a decomposition of the Hamiltonian as a linear combination of unitaries, -# -# .. math:: H=\sum_{i} c_i U_i. -# -# The parameter :math:`\lambda=\sum_i c_i,` which can be interpreted as the 1-norm of the -# Hamiltonian, plays an important role in determining the cost of implementing the QPE -# algorithm [#delgado2022]_. In PennyLane, the 1-norm of the Hamiltonian can be obtained with - -print(f'1-norm of the Hamiltonian: {algo.lamb}') - -############################################################################## -# PennyLane allows you to get more detailed information about the cost of the algorithms as -# explained in the documentation of :class:`~.pennylane.resource.FirstQuantization` -# and :class:`~.pennylane.resource.DoubleFactorization` classes. -# -# Variational quantum eigensolver -# ------------------------------------------ -# In variational quantum algorithms such as VQE, the expectation value of an observable is -# typically computed by decomposing the observable into a linear combination of Pauli words, -# which are tensor products of Pauli and Identity operators. The expectation values are calculated -# through linearity by measuring the expectation value for each of these terms and combining the -# results. The number of qubits required for the measurement is trivially determined by -# the number of qubits the observable acts on. The number of gates required to implement the -# variational algorithm is determined by a circuit ansatz that is also known a priori. However, -# estimating the number of circuit evaluations, i.e. the number of shots, required to achieve a -# certain error in computing the expectation value is not as straightforward. Let's now use -# PennyLane to estimate the number of shots needed to compute the expectation value of the water -# Hamiltonian. -# -# First, we construct the molecular Hamiltonian. - -molecule = qml.qchem.Molecule(symbols, geometry) -H = qml.qchem.molecular_hamiltonian(molecule)[0] -H_coeffs, H_ops = H.terms() - -############################################################################## -# The number of measurements needed to compute :math:`\left \langle H \right \rangle` can be -# obtained with the :func:`~.pennylane.resource.estimate_shots` function, which requires the -# Hamiltonian coefficients as input. The number of measurements required to compute -# :math:`\left \langle H \right \rangle` with a target error set to the chemical accuracy, 0.0016 -# :math:`\text{Ha},` is obtained as follows. - -m = qml.resource.estimate_shots(H_coeffs) -print(f'Shots : {m:.2e}') - -############################################################################## -# This number corresponds to the measurement process where each term in the Hamiltonian is measured -# independently. The number can be reduced by using -# :func:`~.pennylane.pauli.group_observables()`, which partitions the Pauli words into -# groups of commuting terms that can be measured simultaneously. - -ops, coeffs = qml.pauli.group_observables(H_ops, H_coeffs) -coeffs = [np.array(c) for c in coeffs] # cast as numpy array - -m = qml.resource.estimate_shots(coeffs) -print(f'Shots : {m:.2e}') - -############################################################################## -# It is also interesting to illustrate how the number of shots depends on the target error. - -error = np.array([0.02, 0.015, 0.01, 0.005, 0.001]) -m = [qml.resource.estimate_shots(H_coeffs, error=er) for er in error] - -e_ = np.linspace(error[0], error[-1], num=50) -m_ = 1.4e4 / np.linspace(error[0], error[-1], num=50)**2 - -fig, ax = plt.subplots(figsize=(5, 3)) -ax.plot(error, m, 'o', markerfacecolor='none', color='teal', label='estimated') -ax.plot(e_, m_, ':', markerfacecolor='none', color='olive', label='$ 1.4e4 * 1/\epsilon^2 $') - -ax.set_ylabel('shots') -ax.set_xlabel('error [Ha]') -ax.set_yscale('log') -ax.tick_params(axis='x', labelrotation = 90) -ax.legend() -fig.tight_layout() - -############################################################################## -# We have added a line showing the dependency of the shots to the error, as -# :math:`\text{shots} = 1.4\text{e}4 \times 1/\epsilon^2,` for comparison. Can you draw any -# interesting information form the plot? -# -# Conclusions -# ----------- -# This tutorial shows how to use the resource estimation functionality in PennyLane to compute the -# total number of non-Clifford gates and logical qubits required to simulate a Hamiltonian with -# quantum phase estimation algorithms. The estimation can be performed for second-quantized -# molecular Hamiltonians obtained with a double low-rank factorization algorithm, -# and first-quantized Hamiltonians of periodic materials in the plane wave basis. We also discussed -# the estimation of the total number of shots required to obtain the expectation value of an -# observable using the variational quantum eigensolver algorithm. The functionality allows one to -# obtain interesting results about the cost of implementing important quantum algorithms. For -# instance, we estimated the costs with respect to factors such as the target error in obtaining -# energies and the number of basis functions used to simulate a system. Can you think of other -# interesting information that can be obtained using this PennyLane functionality? -# -# References -# ---------- -# -# .. [#vonburg2021] -# -# Vera von Burg, Guang Hao Low, Thomas Haner, Damian S. Steiger, *et al.*, -# "Quantum computing enhanced computational catalysis". -# `Phys. Rev. Research 3, 033055 (2021) -# `__ -# -# .. [#lee2021] -# -# Joonho Lee, Dominic W. Berry, Craig Gidney, William J. Huggins, *et al.*, -# "Even More Efficient Quantum Computations of Chemistry Through Tensor Hypercontraction". -# `PRX Quantum 2, 030305 (2021) -# `__ -# -# .. [#zini2023] -# -# Modjtaba Shokrian Zini, Alain Delgado, Roberto dos Reis, Pablo A. M. Casares, -# Jonathan E. Mueller, Arne-Christian Voigt, Juan Miguel Arrazola, -# "Quantum simulation of battery materials using ionic pseudopotentials". -# `Quantum 7, 1049 (2023) `__ -# -# .. [#delgado2022] -# -# Alain Delgado, Pablo A. M. Casares, Roberto dos Reis, Modjtaba Shokrian Zini, *et al.*, -# "Simulating key properties of lithium-ion batteries with a fault-tolerant quantum computer". -# `Phys. Rev. A 106, 032428 (2022) -# `__ -# About the author -# ---------------- -# +r""" + +Resource estimation for quantum chemistry +========================================= + +.. meta:: + :property="og:description": Learn how to estimate the number of qubits and gates needed to + implement quantum algorithms + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/resource_estimation.jpeg + +.. related:: + tutorial_quantum_chemistry Quantum chemistry with PennyLane + tutorial_vqe A brief overview of VQE + + +*Author: Soran Jahangiri — Posted: 21 November 2022.* + +Quantum algorithms such as +`quantum phase estimation `_ +(QPE) and the `variational quantum eigensolver `_ (VQE) +are widely studied in quantum chemistry as potential avenues to tackle problems that are intractable +for conventional computers. However, we currently do not have quantum computers or simulators +capable of implementing large-scale +versions of these algorithms. This makes it difficult to properly explore their accuracy and +efficiency for problem sizes where the actual advantage of quantum algorithms can potentially occur. +Despite these difficulties, it is still possible to perform **resource estimation** +to assess what we need to implement such quantum algorithms. + +In this demo, we describe how to use PennyLane's resource estimation functionality to estimate the total number of logical qubits and gates required to +implement the QPE algorithm for simulating molecular Hamiltonians represented in first and second +quantization. We focus on `non-Clifford gates `_, which +are the most expensive to implement in a fault-tolerant setting. We also explain how to estimate the +total number of measurements needed to compute expectation values using algorithms such as VQE. + +Quantum Phase Estimation +------------------------ +The QPE algorithm can be used to compute the phase associated with an eigenstate of a unitary +operator. For the purpose of quantum simulation, the unitary operator :math:`U` can be chosen to +share eigenvectors with a molecular Hamiltonian :math:`H,` for example by setting +:math:`U = e^{-iH}.` Estimating the phase of such a unitary then permits recovering the +corresponding eigenvalue of the Hamiltonian. A conceptual QPE circuit diagram is shown below. + +.. figure:: /_static/demonstration_assets/resource_estimation/qpe.png + :width: 60% + :align: center + + Circuit representing the quantum phase estimation algorithm. + +For most cases of interest, this algorithm requires more qubits and longer circuit depths than what +can be implemented on existing hardware. The PennyLane functionality in the +:mod:`qml.resource ` module allows us to estimate the number of logical qubits +and the number of non-Clifford gates that are needed to implement the algorithm. We can estimate +these resources by simply defining system specifications and a target error for estimation. Let's +see how! + +QPE cost for simulating molecules +********************************* +We study the double low-rank Hamiltonian factorization algorithm of [#vonburg2021]_ and use its cost +equations as provided in APPENDIX C of [#lee2021]_. +This algorithm requires the one- and two-electron +`integrals `_ +as input. These integrals can be obtained in different ways and here we use PennyLane to compute +them. We first need to define the atomic symbols and coordinates for the given molecule. Let's use +the water molecule at its equilibrium geometry with the +`6-31g basis set `_ as an example. +""" +import pennylane as qml +import numpy as np +import jax + +jax.config.update("jax_enable_x64", True) + +symbols = ['O', 'H', 'H'] +geometry = np.array([[0.00000000, 0.00000000, 0.28377432], + [0.00000000, 1.45278171, -1.00662237], + [0.00000000, -1.45278171, -1.00662237]]) + +############################################################################## +# Then we construct a molecule object and compute the one- and two-electron +# integrals in the molecular orbital basis. + +mol = qml.qchem.Molecule(symbols, geometry, basis_name='6-31g') +core, one, two = qml.qchem.electron_integrals(mol)() + +############################################################################## +# We now create an instance of the :class:`~.pennylane.resource.DoubleFactorization` class + +algo = qml.resource.DoubleFactorization(one, two) + +############################################################################## +# and obtain the estimated number of non-Clifford gates and logical qubits. + +print(f'Estimated gates : {algo.gates:.2e} \nEstimated qubits: {algo.qubits}') + +############################################################################## +# This estimation is for a target error that is set to the chemical accuracy, 0.0016 +# :math:`\text{Ha},` by default. We can change the target error to a larger value which leads to a +# smaller number of non-Clifford gates and logical qubits. + +chemical_accuracy = 0.0016 +error = chemical_accuracy * 10 +algo = qml.resource.DoubleFactorization(one, two, error=error) +print(f'Estimated gates : {algo.gates:.2e} \nEstimated qubits: {algo.qubits}') + +############################################################################## +# We can also estimate the number of non-Clifford gates with respect to the threshold error values +# for discarding the negligible factors in the factorized Hamiltonian [#vonburg2021]_ and plot the +# estimated numbers. + +threshold = [10**-n for n in range(10)] +n_gates = [] +n_qubits = [] + +for tol in threshold: + algo_ = qml.resource.DoubleFactorization(one, two, tol_factor=tol, tol_eigval=tol) + n_gates.append(algo_.gates) + n_qubits.append(algo_.qubits) + +import matplotlib.pyplot as plt + +fig, ax = plt.subplots(figsize=(5, 3)) +ax.plot(threshold, n_gates, ':o', markerfacecolor='none', color='teal') + +ax.set_ylabel('n gates') +ax.set_xlabel('threshold') +ax.set_xscale('log') +fig.tight_layout() + +############################################################################## +# QPE cost for simulating periodic materials +# ****************************************** +# For periodic materials, we estimate the cost of implementing the QPE algorithm of [#zini2023]_ +# using Hamiltonians represented in first quantization and in a plane wave basis. We first need to +# define the number of plane waves, the number of electrons, and the lattice vectors that construct +# the unit cell of the periodic material. Let's use dilithium iron silicate +# :math:`\text{Li}_2\text{FeSiO}_4` as an example taken from [#delgado2022]_. For this material, the +# unit cell contains 156 electrons and has dimensions :math:`9.49 \times 10.20 \times 11.83` in +# `atomic units `_. We also use :math:`10^5` plane waves. + +planewaves = 100000 +electrons = 156 +vectors = np.array([[9.49, 0.00, 0.00], + [0.00, 10.20, 0.00], + [0.00, 0.00, 11.83]]) + +############################################################################## +# We now create an instance of the :class:`~.pennylane.resource.FirstQuantization` class +algo = qml.resource.FirstQuantization(planewaves, electrons, vectors=vectors) + +############################################################################## +# and obtain the estimated number of non-Clifford gates and logical qubits. +print(f'Estimated gates : {algo.gates:.2e} \nEstimated qubits: {algo.qubits}') + +############################################################################## +# We can also plot the estimated numbers as a function of the number of plane waves for different +# target errors + +error = [0.1, 0.01, 0.001] # in atomic units +planewaves = [10 ** n for n in range(1, 10)] +n_gates = [] +n_qubits = [] + +for er in error: + n_gates_ = [] + n_qubits_ = [] + + for pw in planewaves: + algo_ = qml.resource.FirstQuantization(pw, electrons, vectors=vectors, error=er) + n_gates_.append(algo_.gates) + n_qubits_.append(algo_.qubits) + n_gates.append(n_gates_) + n_qubits.append(n_qubits_) + +fig, ax = plt.subplots(2, 1) + +for i in range(len(n_gates)): + ax[0].plot(planewaves, n_gates[i], ':o', markerfacecolor='none', label=error[i]) +ax[1].plot(planewaves, n_qubits[i], ':o', markerfacecolor='none', label=error[-1]) + +ax[0].set_ylabel('n gates') +ax[1].set_ylabel('n qubits') + +for i in [0, 1]: + ax[i].set_xlabel('n planewaves') + ax[i].tick_params(axis='x') + ax[0].set_yscale('log') + ax[i].set_xscale('log') + ax[i].legend(title='error [Ha]') + +fig.tight_layout() + +############################################################################## +# The algorithm uses a decomposition of the Hamiltonian as a linear combination of unitaries, +# +# .. math:: H=\sum_{i} c_i U_i. +# +# The parameter :math:`\lambda=\sum_i c_i,` which can be interpreted as the 1-norm of the +# Hamiltonian, plays an important role in determining the cost of implementing the QPE +# algorithm [#delgado2022]_. In PennyLane, the 1-norm of the Hamiltonian can be obtained with + +print(f'1-norm of the Hamiltonian: {algo.lamb}') + +############################################################################## +# PennyLane allows you to get more detailed information about the cost of the algorithms as +# explained in the documentation of :class:`~.pennylane.resource.FirstQuantization` +# and :class:`~.pennylane.resource.DoubleFactorization` classes. +# +# Variational quantum eigensolver +# ------------------------------------------ +# In variational quantum algorithms such as VQE, the expectation value of an observable is +# typically computed by decomposing the observable into a linear combination of Pauli words, +# which are tensor products of Pauli and Identity operators. The expectation values are calculated +# through linearity by measuring the expectation value for each of these terms and combining the +# results. The number of qubits required for the measurement is trivially determined by +# the number of qubits the observable acts on. The number of gates required to implement the +# variational algorithm is determined by a circuit ansatz that is also known a priori. However, +# estimating the number of circuit evaluations, i.e. the number of shots, required to achieve a +# certain error in computing the expectation value is not as straightforward. Let's now use +# PennyLane to estimate the number of shots needed to compute the expectation value of the water +# Hamiltonian. +# +# First, we construct the molecular Hamiltonian. + +molecule = qml.qchem.Molecule(symbols, geometry) +H = qml.qchem.molecular_hamiltonian(molecule)[0] +H_coeffs, H_ops = H.terms() + +############################################################################## +# The number of measurements needed to compute :math:`\left \langle H \right \rangle` can be +# obtained with the :func:`~.pennylane.resource.estimate_shots` function, which requires the +# Hamiltonian coefficients as input. The number of measurements required to compute +# :math:`\left \langle H \right \rangle` with a target error set to the chemical accuracy, 0.0016 +# :math:`\text{Ha},` is obtained as follows. + +m = qml.resource.estimate_shots(H_coeffs) +print(f'Shots : {m:.2e}') + +############################################################################## +# This number corresponds to the measurement process where each term in the Hamiltonian is measured +# independently. The number can be reduced by using +# :func:`~.pennylane.pauli.group_observables()`, which partitions the Pauli words into +# groups of commuting terms that can be measured simultaneously. + +ops, coeffs = qml.pauli.group_observables(H_ops, H_coeffs) +coeffs = [np.array(c) for c in coeffs] # cast as numpy array + +m = qml.resource.estimate_shots(coeffs) +print(f'Shots : {m:.2e}') + +############################################################################## +# It is also interesting to illustrate how the number of shots depends on the target error. + +error = np.array([0.02, 0.015, 0.01, 0.005, 0.001]) +m = [qml.resource.estimate_shots(H_coeffs, error=er) for er in error] + +e_ = np.linspace(error[0], error[-1], num=50) +m_ = 1.4e4 / np.linspace(error[0], error[-1], num=50)**2 + +fig, ax = plt.subplots(figsize=(5, 3)) +ax.plot(error, m, 'o', markerfacecolor='none', color='teal', label='estimated') +ax.plot(e_, m_, ':', markerfacecolor='none', color='olive', label='$ 1.4e4 * 1/\epsilon^2 $') + +ax.set_ylabel('shots') +ax.set_xlabel('error [Ha]') +ax.set_yscale('log') +ax.tick_params(axis='x', labelrotation = 90) +ax.legend() +fig.tight_layout() + +############################################################################## +# We have added a line showing the dependency of the shots to the error, as +# :math:`\text{shots} = 1.4\text{e}4 \times 1/\epsilon^2,` for comparison. Can you draw any +# interesting information form the plot? +# +# Conclusions +# ----------- +# This tutorial shows how to use the resource estimation functionality in PennyLane to compute the +# total number of non-Clifford gates and logical qubits required to simulate a Hamiltonian with +# quantum phase estimation algorithms. The estimation can be performed for second-quantized +# molecular Hamiltonians obtained with a double low-rank factorization algorithm, +# and first-quantized Hamiltonians of periodic materials in the plane wave basis. We also discussed +# the estimation of the total number of shots required to obtain the expectation value of an +# observable using the variational quantum eigensolver algorithm. The functionality allows one to +# obtain interesting results about the cost of implementing important quantum algorithms. For +# instance, we estimated the costs with respect to factors such as the target error in obtaining +# energies and the number of basis functions used to simulate a system. Can you think of other +# interesting information that can be obtained using this PennyLane functionality? +# +# References +# ---------- +# +# .. [#vonburg2021] +# +# Vera von Burg, Guang Hao Low, Thomas Haner, Damian S. Steiger, *et al.*, +# "Quantum computing enhanced computational catalysis". +# `Phys. Rev. Research 3, 033055 (2021) +# `__ +# +# .. [#lee2021] +# +# Joonho Lee, Dominic W. Berry, Craig Gidney, William J. Huggins, *et al.*, +# "Even More Efficient Quantum Computations of Chemistry Through Tensor Hypercontraction". +# `PRX Quantum 2, 030305 (2021) +# `__ +# +# .. [#zini2023] +# +# Modjtaba Shokrian Zini, Alain Delgado, Roberto dos Reis, Pablo A. M. Casares, +# Jonathan E. Mueller, Arne-Christian Voigt, Juan Miguel Arrazola, +# "Quantum simulation of battery materials using ionic pseudopotentials". +# `Quantum 7, 1049 (2023) `__ +# +# .. [#delgado2022] +# +# Alain Delgado, Pablo A. M. Casares, Roberto dos Reis, Modjtaba Shokrian Zini, *et al.*, +# "Simulating key properties of lithium-ion batteries with a fault-tolerant quantum computer". +# `Phys. Rev. A 106, 032428 (2022) +# `__ +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_rosalin/demo.py b/demonstrations_v2/tutorial_rosalin/demo.py index 08b548c590..aed37d6224 100644 --- a/demonstrations_v2/tutorial_rosalin/demo.py +++ b/demonstrations_v2/tutorial_rosalin/demo.py @@ -1,666 +1,666 @@ -r""" -Frugal shot optimization with Rosalin -===================================== - -.. meta:: - :property="og:description": The Rosalin optimizer uses a measurement-frugal optimization strategy to minimize the - number of times a quantum computer is accessed. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/sphx_glr_tutorial_rosalin_002.png - -.. related:: - - tutorial_vqe A brief overview of VQE - tutorial_quantum_natural_gradient Quantum natural gradient - tutorial_doubly_stochastic Doubly stochastic gradient descent - tutorial_rotoselect Quantum circuit structure learning - -*Author: Josh Izaac — Posted: 19 May 2020. Last updated: 30 January 2023.* - -In this tutorial we investigate and implement the Rosalin (Random Operator Sampling for -Adaptive Learning with Individual Number of shots) from -Arrasmith et al. [#arrasmith2020]_. In this paper, a strategy -is introduced for reducing the number of shots required when optimizing variational quantum -algorithms, by both: - -* Frugally adapting the number of shots used per parameter update, and -* Performing a weighted sampling of operators from the cost Hamiltonian. - -.. note:: - - The Rosalin optimizer is available in PennyLane via the - :class:`~.pennylane.ShotAdaptiveOptimizer`. - -Background ----------- - -While a large number of papers in variational quantum algorithms focus on the -choice of circuit ansatz, cost function, gradient computation, or initialization method, -the optimization strategy—an important component affecting both convergence time and -quantum resource dependence—is not as frequently considered. Instead, common -'out-of-the-box' classical optimization techniques, such as gradient-free -methods (COBLYA, Nelder-Mead), gradient-descent, and Hessian-free methods (L-BFGS) tend to be used. - -However, for variational algorithms such as :doc:`VQE `, which involve evaluating -a large number of non-commuting operators in the cost function, decreasing the number of -quantum evaluations required for convergence, while still minimizing statistical noise, can -be a delicate balance. - -Recent work has highlighted that 'quantum-aware' optimization techniques -can lead to marked improvements when training variational quantum algorithms: - -* :doc:`/demos/tutorial_quantum_natural_gradient` descent by Stokes et al. [#stokes2019]_, which - takes into account the quantum geometry during the gradient-descent update step. - -* The work of Sweke et al. [#sweke2019]_, which shows - that quantum gradient descent with a finite number of shots is equivalent to - `stochastic gradient descent `_, - and has guaranteed convergence. Furthermore, combining a finite number of shots with - weighted sampling of the cost function terms leads to :doc:`/demos/tutorial_doubly_stochastic`. - -* The iCANS (individual Coupled Adaptive Number of Shots) optimization technique by - Jonas Kuebler et al. [#kubler2020]_ adapts the number - of shots measurements during training, by maximizing the expected gain per shot. - -In this latest result by Arrasmith et al. [#arrasmith2020]_, the -idea of doubly stochastic gradient descent has been used to extend the iCANS optimizer, -resulting in faster convergence. - -Over the course of this tutorial, we will explore their results; beginning first with a -demonstration of *weighted random sampling* of the cost Hamiltonian operators, before -combining this with the shot-frugal iCANS optimizer to perform doubly stochastic -Rosalin optimization. - -Weighted random sampling ------------------------- - -Consider a Hamiltonian :math:`H` expanded as a weighted sum of operators :math:`h_i` that can -be directly measured: - -.. math:: H = \sum_{i=1}^N c_i h_i. - -Due to the linearity of expectation values, the expectation value of this Hamiltonian -can be expressed as the weighted sum of each individual term: - -.. math:: \langle H\rangle = \sum_{i=1}^N c_i \langle h_i\rangle. - -In the :doc:`doubly stochastic gradient descent demonstration `, -we estimated this expectation value by **uniformly sampling** a subset of the terms -at each optimization step, and evaluating each term by using the same finite number of shots -:math:`N.` - -However, what happens if we use a weighted approach to determine how to distribute -our samples across the terms of the Hamiltonian? In **weighted random sampling** (WRS), -the number of shots used to determine the expectation value :math:`\langle h_i\rangle` -is a discrete random variable distributed according to a -`multinomial distribution `__, - -.. math:: S \sim \text{Multinomial}(p_i), - -with event probabilities - -.. math:: p_i = \frac{|c_i|}{\sum_i |c_i|}. - -That is, the number of shots assigned to the measurement of the expectation value of the -:math:`i\text{th}` term of the Hamiltonian is drawn from a probability distribution -*proportional to the magnitude of its coefficient* :math:`c_i.` - -To see this strategy in action, consider the Hamiltonian - -.. math:: H = 2I\otimes X + 4 I\otimes Z - X\otimes X + 5Y\otimes Y + 2 Z\otimes X. - -We can solve for the ground state energy using the variational quantum eigensolver (VQE) algorithm. - -First, let's import NumPy and PennyLane, and define our Hamiltonian. -""" -import pennylane as qml -from pennylane import numpy as np - -# set the random seed -np.random.seed(4) - -coeffs = [2, 4, -1, 5, 2] - -obs = [ - qml.PauliX(1), - qml.PauliZ(1), - qml.PauliX(0) @ qml.PauliX(1), - qml.PauliY(0) @ qml.PauliY(1), - qml.PauliZ(0) @ qml.PauliZ(1) -] - - -############################################################################## -# We can now create our quantum device (let's use the ``default.qubit`` simulator). - -num_layers = 2 -num_wires = 2 - -# create a device that estimates expectation values using a finite number of shots -non_analytic_dev = qml.device("default.qubit", wires=num_wires, shots=100, seed=432423) - -# create a device that calculates exact expectation values -analytic_dev = qml.device("default.qubit", wires=num_wires, shots=None) - -############################################################################## -# Now, let's set the total number of shots, and determine the probability -# for sampling each Hamiltonian term. - -total_shots = 8000 -prob_shots = np.abs(coeffs) / np.sum(np.abs(coeffs)) -print(prob_shots) - -############################################################################## -# We can now use SciPy to create our multinomial distributed random variable -# :math:`S,` using the number of trials (total shot number) and probability values: - -from scipy.stats import multinomial - -si = multinomial(n=total_shots, p=prob_shots) - -############################################################################## -# Sampling from this distribution will provide the number of shots used to -# sample each term in the Hamiltonian: - -samples = si.rvs()[0] -print(samples) -print(sum(samples)) - -############################################################################## -# As expected, if we sum the sampled shots per term, we recover the total number of shots. -# -# Let's now create our cost function. Recall that the cost function must do the -# following: -# -# 1. It must sample from the multinomial distribution we created above, -# to determine the number of shots :math:`s_i` to use to estimate the expectation -# value of the ith Hamiltonian term. -# -# 2. It then must estimate the expectation value :math:`\langle h_i\rangle` -# by creating the required QNode. For our ansatz, we'll use the -# :class:`~.pennylane.templates.layers.StronglyEntanglingLayers`. -# -# 3. And, last but not least, estimate the expectation value -# :math:`\langle H\rangle = \sum_i c_i\langle h_i\rangle.` -# - -from pennylane.templates.layers import StronglyEntanglingLayers - - -@qml.qnode(non_analytic_dev, diff_method="parameter-shift", interface="autograd") -def qnode(weights, observable): - StronglyEntanglingLayers(weights, wires=non_analytic_dev.wires) - return qml.expval(observable) - -def cost(params): - # sample from the multinomial distribution - shots_per_term = si.rvs()[0] - - result = 0 - - for o, c, s in zip(obs, coeffs, shots_per_term): - # evaluate the QNode corresponding to - # the Hamiltonian term, and add it on to our running sum - result += c * qnode(params, o, shots=int(s)) - - return result - - -############################################################################## -# Evaluating our cost function with some initial parameters, we can test out -# that our cost function evaluates correctly. - -param_shape = StronglyEntanglingLayers.shape(n_layers=num_layers, n_wires=num_wires) -init_params = np.random.uniform(low=0.0, high=2*np.pi, size=param_shape, requires_grad=True) -print(cost(init_params)) - - -############################################################################## -# Performing the optimization, with the number of shots randomly -# determined at each optimization step: - -opt = qml.AdamOptimizer(0.05) -params = init_params - -cost_wrs = [] -shots_wrs = [] - -for i in range(100): - params, _cost = opt.step_and_cost(cost, params) - cost_wrs.append(_cost) - shots_wrs.append(total_shots*i) - print("Step {}: cost = {} shots used = {}".format(i, cost_wrs[-1], shots_wrs[-1])) - -############################################################################## -# Let's compare this against an optimization not using weighted random sampling. -# Here, we will split the 8000 total shots evenly across all Hamiltonian terms, -# also known as *uniform deterministic sampling*. - -@qml.qnode(non_analytic_dev, diff_method="parameter-shift", interface="autograd") -def qnode(weights, obs): - StronglyEntanglingLayers(weights, wires=non_analytic_dev.wires) - return qml.expval(obs) - -def cost(params): - shots_per_term = int(total_shots / len(coeffs)) - - result = 0 - - for o, c in zip(obs, coeffs): - - # evaluate the QNode corresponding to - # the Hamiltonian term, and add it on to our running sum - result += c * qnode(params, o, shots=shots_per_term) - - return result - -opt = qml.AdamOptimizer(0.05) -params = init_params - -cost_adam = [] -shots_adam = [] - -for i in range(100): - params, _cost = opt.step_and_cost(cost, params) - cost_adam.append(_cost) - shots_adam.append(total_shots*i) - print("Step {}: cost = {} shots used = {}".format(i, cost_adam[-1], shots_adam[-1])) - -############################################################################## -# Comparing these two techniques: - -from matplotlib import pyplot as plt - -plt.style.use("seaborn-v0_8") -plt.plot(shots_wrs, cost_wrs, "b", label="Adam WRS") -plt.plot(shots_adam, cost_adam, "g", label="Adam") - -plt.ylabel("Cost function value") -plt.xlabel("Number of shots") -plt.legend() -plt.show() - -############################################################################## -# We can see that weighted random sampling performs just as well as the uniform -# deterministic sampling. However, weighted random sampling begins to show a -# non-negligible improvement over deterministic sampling for large Hamiltonians -# with highly non-uniform coefficients. For example, see Fig (3) and (4) of -# Arrasmith et al. [#arrasmith2020]_, comparing weighted random sampling VQE optimization -# for both :math:`\text{H}_2` and :math:`\text{LiH}` molecules. -# -# .. note:: -# -# While not covered here, another approach that could be taken is -# *weighted deterministic sampling*. Here, the number of shots is distributed -# across terms as per -# -# .. math:: s_i = \left\lfloor N \frac{|c_i|}{\sum_i |c_i|}\right\rfloor, -# -# where :math:`N` is the total number of shots. -# - -############################################################################## -# Rosalin: Frugal shot optimization -# --------------------------------- -# -# We can see above that both methods optimize fairly well; weighted random -# sampling converges just as well as evenly distributing the shots across -# all Hamiltonian terms. However, deterministic shot distribution approaches -# will always have a minimum shot value required per expectation value, as below -# this threshold they become biased estimators. This is not the case with random -# sampling; as we saw in the -# :doc:`doubly stochastic gradient descent demonstration `, -# the introduction of randomness allows for as little -# as a single shot per expectation term, while still remaining an unbiased estimator. -# -# Using this insight, Arrasmith et al. [#arrasmith2020]_ modified the iCANS frugal -# shot-optimization technique [#kubler2020]_ to include weighted random sampling, making it -# 'doubly stochastic'. -# -# iCANS optimizer -# ~~~~~~~~~~~~~~~ -# -# Two variants of the iCANS optimizer were introduced in Kübler et al., iCANS1 and iCANS2. -# The iCANS1 optimizer, on which Rosalin is based, frugally distributes a shot budget -# across the partial derivatives of each parameter, which are computed using the -# :doc:`parameter-shift rule `. It works roughly as follows: -# -# 1. The initial step of the optimizer is performed with some specified minimum -# number of shots, :math:`s_{min},` for all partial derivatives. -# -# 2. The parameter-shift rule is then used to estimate the gradient :math:`g_i` -# for each parameter :math:`\theta_i,` parameters, as well as the *variances* -# :math:`v_i` of the estimated gradients. -# -# 3. Gradient descent is performed for each parameter :math:`\theta_i,` using -# the pre-defined learning rate :math:`\alpha` and the gradient information :math:`g_i:` -# -# .. math:: \theta_i = \theta_i - \alpha g_i. -# -# 4. The improvement in the cost function per shot, for a specific parameter value, -# is then calculated via -# -# .. math:: -# -# \gamma_i = \frac{1}{s_i} \left[ \left(\alpha - \frac{1}{2} L\alpha^2\right) -# g_i^2 - \frac{L\alpha^2}{2s_i}v_i \right], -# -# where: -# -# * :math:`L \leq \sum_i|c_i|` is the bound on the `Lipschitz constant -# `__ of the variational quantum algorithm objective function, -# -# * :math:`c_i` are the coefficients of the Hamiltonian, and -# -# * :math:`\alpha` is the learning rate, and *must* be bound such that :math:`\alpha < 2/L` -# for the above expression to hold. -# -# 5. Finally, the new values of :math:`s_i` (shots for partial derivative of parameter -# :math:`\theta_i`) is given by: -# -# .. math:: -# -# s_i = \frac{2L\alpha}{2-L\alpha}\left(\frac{v_i}{g_i^2}\right)\propto -# \frac{v_i}{g_i^2}. -# -# In addition to the above, to counteract the presence of noise in the system, a -# running average of :math:`g_i` and :math:`s_i` (:math:`\chi_i` and :math:`\xi_i` respectively) -# are used when computing :math:`\gamma_i` and :math:`s_i.` -# -# .. note:: -# -# In classical machine learning, the Lipschitz constant of the cost function is generally -# unknown. However, for a variational quantum algorithm with cost of the form -# :math:`f(x) = \langle \psi(x) | \hat{H} |\psi(x)\rangle,` -# an upper bound on the Lipschitz constant is given by :math:`L < \sum_i|c_i|,` -# where :math:`c_i` are the coefficients of :math:`\hat{H}` when decomposed -# into a linear combination of Pauli-operator tensor products. -# -# Rosalin implementation -# ~~~~~~~~~~~~~~~~~~~~~~ -# -# Let's now modify iCANS above to incorporate weighted random sampling of Hamiltonian -# terms — the Rosalin frugal shot optimizer. -# -# Rosalin takes several hyper-parameters: -# -# * ``min_shots``: the minimum number of shots used to estimate the expectations -# of each term in the Hamiltonian. Note that this must be larger than 2 for the variance -# of the gradients to be computed. -# -# * ``mu``: The running average constant :math:`\mu\in[0, 1].` Used to control how quickly the -# number of shots recommended for each gradient component changes. -# -# * ``b``: Regularization bias. The bias should be kept small, but non-zero. -# -# * ``lr``: The learning rate. Recall from above that the learning rate *must* be such -# that :math:`\alpha < 2/L = 2/\sum_i|c_i|.` -# -# Since the Rosalin optimizer has a state that must be preserved between optimization steps, -# let's use a class to create our optimizer. -# - -class Rosalin: - - def __init__(self, obs, coeffs, min_shots, mu=0.99, b=1e-6, lr=0.07): - self.obs = obs - self.coeffs = coeffs - - self.lipschitz = np.sum(np.abs(coeffs)) - - if lr > 2 / self.lipschitz: - raise ValueError("The learning rate must be less than ", 2 / self.lipschitz) - - # hyperparameters - self.min_shots = min_shots - self.mu = mu # running average constant - self.b = b # regularization bias - self.lr = lr # learning rate - - # keep track of the total number of shots used - self.shots_used = 0 - # total number of iterations - self.k = 0 - # Number of shots per parameter - self.s = np.zeros_like(params, dtype=np.float64) + min_shots - - # Running average of the parameter gradients - self.chi = None - # Running average of the variance of the parameter gradients - self.xi = None - - def estimate_hamiltonian(self, params, shots): - """Returns an array containing length ``shots`` single-shot estimates - of the Hamiltonian. The shots are distributed randomly over - the terms in the Hamiltonian, as per a Multinomial distribution. - - Since we are performing single-shot estimates, the QNodes must be - set to 'sample' mode. - """ - # note that convergence depends on seed for random number generation - rosalin_device = qml.device("default.qubit", wires=num_wires, shots=100, seed=93754352) - - # determine the shot probability per term - prob_shots = np.abs(coeffs) / np.sum(np.abs(coeffs)) - - # construct the multinomial distribution, and sample - # from it to determine how many shots to apply per term - si = multinomial(n=shots, p=prob_shots) - shots_per_term = si.rvs()[0] - - results = [] - - @qml.qnode(rosalin_device, diff_method="parameter-shift", interface="autograd") - def qnode(weights, observable): - StronglyEntanglingLayers(weights, wires=rosalin_device.wires) - return qml.sample(observable) - - for o, c, p, s in zip(self.obs, self.coeffs, prob_shots, shots_per_term): - - # if the number of shots is 0, do nothing - if s == 0: - continue - - # evaluate the QNode corresponding to - # the Hamiltonian term - res = qnode(params, o, shots=int(s)) - - if s == 1: - res = np.array([res]) - - # Note that, unlike above, we divide each term by the - # probability per shot. This is because we are sampling one at a time. - results.append(c * res / p) - - return np.concatenate(results) - - def evaluate_grad_var(self, i, params, shots): - """Evaluate the gradient, as well as the variance in the gradient, - for the ith parameter in params, using the parameter-shift rule. - """ - shift = np.zeros_like(params) - shift[i] = np.pi / 2 - - shift_forward = self.estimate_hamiltonian(params + shift, shots) - shift_backward = self.estimate_hamiltonian(params - shift, shots) - - g = np.mean(shift_forward - shift_backward) / 2 - s = np.var((shift_forward - shift_backward) / 2, ddof=1) - - return g, s - - def step(self, params): - """Perform a single step of the Rosalin optimizer.""" - # keep track of the number of shots run - self.shots_used += int(2 * np.sum(self.s)) - - # compute the gradient, as well as the variance in the gradient, - # using the number of shots determined by the array s. - grad = [] - S = [] - - p_ind = list(np.ndindex(*params.shape)) - - for l in p_ind: - # loop through each parameter, performing - # the parameter-shift rule - g_, s_ = self.evaluate_grad_var(l, params, self.s[l]) - grad.append(g_) - S.append(s_) - - grad = np.reshape(np.stack(grad), params.shape) - S = np.reshape(np.stack(S), params.shape) - - # gradient descent update - params = params - self.lr * grad - - if self.xi is None: - self.chi = np.zeros_like(params, dtype=np.float64) - self.xi = np.zeros_like(params, dtype=np.float64) - - # running average of the gradient variance - self.xi = self.mu * self.xi + (1 - self.mu) * S - xi = self.xi / (1 - self.mu ** (self.k + 1)) - - # running average of the gradient - self.chi = self.mu * self.chi + (1 - self.mu) * grad - chi = self.chi / (1 - self.mu ** (self.k + 1)) - - # determine the new optimum shots distribution for the next - # iteration of the optimizer - s = np.ceil( - (2 * self.lipschitz * self.lr * xi) - / ((2 - self.lipschitz * self.lr) * (chi ** 2 + self.b * (self.mu ** self.k))) - ) - - # apply an upper and lower bound on the new shot distributions, - # to avoid the number of shots reducing below min(2, min_shots), - # or growing too significantly. - gamma = ( - (self.lr - self.lipschitz * self.lr ** 2 / 2) * chi ** 2 - - xi * self.lipschitz * self.lr ** 2 / (2 * s) - ) / s - - argmax_gamma = np.unravel_index(np.argmax(gamma), gamma.shape) - smax = s[argmax_gamma] - self.s = np.clip(s, min(2, self.min_shots), max(2, smax)) - - self.k += 1 - return params - - -############################################################################## -# Rosalin optimization -# ~~~~~~~~~~~~~~~~~~~~ -# -# We are now ready to use our Rosalin optimizer to optimize the initial VQE problem. But first let's -# also create a separate cost function using an 'exact' quantum device, so that we can keep track of the -# *exact* cost function value at each iteration. - -@qml.qnode(analytic_dev, interface="autograd") -def cost_analytic(weights): - StronglyEntanglingLayers(weights, wires=analytic_dev.wires) - return qml.expval(qml.Hamiltonian(coeffs, obs)) - -############################################################################## -# Creating the optimizer and beginning the optimization: - - -opt = Rosalin(obs, coeffs, min_shots=10) -params = init_params - -cost_rosalin = [cost_analytic(params)] -shots_rosalin = [0] - -for i in range(60): - params = opt.step(params) - cost_rosalin.append(cost_analytic(params)) - shots_rosalin.append(opt.shots_used) - print(f"Step {i}: cost = {cost_rosalin[-1]}, shots_used = {shots_rosalin[-1]}") - - -############################################################################## -# Let's compare this to a standard Adam optimization. Using 100 shots per quantum -# evaluation, for each update step there are 2 quantum evaluations per parameter. - -adam_shots_per_eval = 100 -adam_shots_per_step = 2 * adam_shots_per_eval * len(params.flatten()) -print(adam_shots_per_step) - -############################################################################## -# Thus, Adam is using 2400 shots per update step. - -params = init_params -opt = qml.AdamOptimizer(0.07) - -adam_dev = qml.device('default.qubit', shots=adam_shots_per_eval, seed=595905) - -@qml.qnode(adam_dev, diff_method="parameter-shift", interface="autograd") -def cost(weights): - StronglyEntanglingLayers(weights, wires=non_analytic_dev.wires) - return qml.expval(qml.Hamiltonian(coeffs, obs)) - -cost_adam = [cost_analytic(params)] -shots_adam = [0] - -for i in range(100): - params = opt.step(cost, params) - cost_adam.append(cost_analytic(params)) - shots_adam.append(adam_shots_per_step * (i + 1)) - print("Step {}: cost = {} shots_used = {}".format(i, cost_adam[-1], shots_adam[-1])) - -############################################################################## -# Plotting both experiments: - -plt.style.use("seaborn-v0_8") -plt.plot(shots_rosalin, cost_rosalin, "b", label="Rosalin") -plt.plot(shots_adam, cost_adam, "g", label="Adam") - -plt.ylabel("Cost function value") -plt.xlabel("Number of shots") -plt.legend() -plt.xlim(0, 300000) -plt.show() - -############################################################################## -# The Rosalin optimizer performs significantly better than the Adam optimizer, -# approaching the ground state energy of the Hamiltonian with strikingly -# fewer shots. -# -# While beyond the scope of this demonstration, the Rosalin optimizer can be -# modified in various other ways; for instance, by incorporating *weighted hybrid -# sampling* (which distributes some shots deterministically, with the remainder -# done randomly), or by adapting the variant iCANS2 optimizer. Download -# this demonstration from the sidebar 👉 and give it a go! ⚛️ - - -############################################################################## -# References -# ---------- -# -# .. [#arrasmith2020] -# -# Andrew Arrasmith, Lukasz Cincio, Rolando D. Somma, and Patrick J. Coles. "Operator Sampling -# for Shot-frugal Optimization in Variational Algorithms." `arXiv:2004.06252 -# `__ (2020). -# -# .. [#stokes2019] -# -# James Stokes, Josh Izaac, Nathan Killoran, and Giuseppe Carleo. "Quantum Natural Gradient." -# `arXiv:1909.02108 `__ (2019). -# -# .. [#sweke2019] -# -# Ryan Sweke, Frederik Wilde, Johannes Jakob Meyer, Maria Schuld, Paul K. Fährmann, Barthélémy -# Meynard-Piganeau, and Jens Eisert. "Stochastic gradient descent for hybrid quantum-classical -# optimization." `arXiv:1910.01155 `__ (2019). -# -# .. [#kubler2020] -# -# Jonas M. Kübler, Andrew Arrasmith, Lukasz Cincio, and Patrick J. Coles. "An Adaptive Optimizer -# for Measurement-Frugal Variational Algorithms." `Quantum 4, 263 -# `__ (2020). -# -# -# About the author -# ---------------- +r""" +Frugal shot optimization with Rosalin +===================================== + +.. meta:: + :property="og:description": The Rosalin optimizer uses a measurement-frugal optimization strategy to minimize the + number of times a quantum computer is accessed. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/sphx_glr_tutorial_rosalin_002.png + +.. related:: + + tutorial_vqe A brief overview of VQE + tutorial_quantum_natural_gradient Quantum natural gradient + tutorial_doubly_stochastic Doubly stochastic gradient descent + tutorial_rotoselect Quantum circuit structure learning + +*Author: Josh Izaac — Posted: 19 May 2020. Last updated: 30 January 2023.* + +In this tutorial we investigate and implement the Rosalin (Random Operator Sampling for +Adaptive Learning with Individual Number of shots) from +Arrasmith et al. [#arrasmith2020]_. In this paper, a strategy +is introduced for reducing the number of shots required when optimizing variational quantum +algorithms, by both: + +* Frugally adapting the number of shots used per parameter update, and +* Performing a weighted sampling of operators from the cost Hamiltonian. + +.. note:: + + The Rosalin optimizer is available in PennyLane via the + :class:`~.pennylane.ShotAdaptiveOptimizer`. + +Background +---------- + +While a large number of papers in variational quantum algorithms focus on the +choice of circuit ansatz, cost function, gradient computation, or initialization method, +the optimization strategy—an important component affecting both convergence time and +quantum resource dependence—is not as frequently considered. Instead, common +'out-of-the-box' classical optimization techniques, such as gradient-free +methods (COBLYA, Nelder-Mead), gradient-descent, and Hessian-free methods (L-BFGS) tend to be used. + +However, for variational algorithms such as :doc:`VQE `, which involve evaluating +a large number of non-commuting operators in the cost function, decreasing the number of +quantum evaluations required for convergence, while still minimizing statistical noise, can +be a delicate balance. + +Recent work has highlighted that 'quantum-aware' optimization techniques +can lead to marked improvements when training variational quantum algorithms: + +* :doc:`/demos/tutorial_quantum_natural_gradient` descent by Stokes et al. [#stokes2019]_, which + takes into account the quantum geometry during the gradient-descent update step. + +* The work of Sweke et al. [#sweke2019]_, which shows + that quantum gradient descent with a finite number of shots is equivalent to + `stochastic gradient descent `_, + and has guaranteed convergence. Furthermore, combining a finite number of shots with + weighted sampling of the cost function terms leads to :doc:`/demos/tutorial_doubly_stochastic`. + +* The iCANS (individual Coupled Adaptive Number of Shots) optimization technique by + Jonas Kuebler et al. [#kubler2020]_ adapts the number + of shots measurements during training, by maximizing the expected gain per shot. + +In this latest result by Arrasmith et al. [#arrasmith2020]_, the +idea of doubly stochastic gradient descent has been used to extend the iCANS optimizer, +resulting in faster convergence. + +Over the course of this tutorial, we will explore their results; beginning first with a +demonstration of *weighted random sampling* of the cost Hamiltonian operators, before +combining this with the shot-frugal iCANS optimizer to perform doubly stochastic +Rosalin optimization. + +Weighted random sampling +------------------------ + +Consider a Hamiltonian :math:`H` expanded as a weighted sum of operators :math:`h_i` that can +be directly measured: + +.. math:: H = \sum_{i=1}^N c_i h_i. + +Due to the linearity of expectation values, the expectation value of this Hamiltonian +can be expressed as the weighted sum of each individual term: + +.. math:: \langle H\rangle = \sum_{i=1}^N c_i \langle h_i\rangle. + +In the :doc:`doubly stochastic gradient descent demonstration `, +we estimated this expectation value by **uniformly sampling** a subset of the terms +at each optimization step, and evaluating each term by using the same finite number of shots +:math:`N.` + +However, what happens if we use a weighted approach to determine how to distribute +our samples across the terms of the Hamiltonian? In **weighted random sampling** (WRS), +the number of shots used to determine the expectation value :math:`\langle h_i\rangle` +is a discrete random variable distributed according to a +`multinomial distribution `__, + +.. math:: S \sim \text{Multinomial}(p_i), + +with event probabilities + +.. math:: p_i = \frac{|c_i|}{\sum_i |c_i|}. + +That is, the number of shots assigned to the measurement of the expectation value of the +:math:`i\text{th}` term of the Hamiltonian is drawn from a probability distribution +*proportional to the magnitude of its coefficient* :math:`c_i.` + +To see this strategy in action, consider the Hamiltonian + +.. math:: H = 2I\otimes X + 4 I\otimes Z - X\otimes X + 5Y\otimes Y + 2 Z\otimes X. + +We can solve for the ground state energy using the variational quantum eigensolver (VQE) algorithm. + +First, let's import NumPy and PennyLane, and define our Hamiltonian. +""" +import pennylane as qml +from pennylane import numpy as np + +# set the random seed +np.random.seed(4) + +coeffs = [2, 4, -1, 5, 2] + +obs = [ + qml.PauliX(1), + qml.PauliZ(1), + qml.PauliX(0) @ qml.PauliX(1), + qml.PauliY(0) @ qml.PauliY(1), + qml.PauliZ(0) @ qml.PauliZ(1) +] + + +############################################################################## +# We can now create our quantum device (let's use the ``default.qubit`` simulator). + +num_layers = 2 +num_wires = 2 + +# create a device that estimates expectation values using a finite number of shots +non_analytic_dev = qml.device("default.qubit", wires=num_wires, shots=100, seed=432423) + +# create a device that calculates exact expectation values +analytic_dev = qml.device("default.qubit", wires=num_wires, shots=None) + +############################################################################## +# Now, let's set the total number of shots, and determine the probability +# for sampling each Hamiltonian term. + +total_shots = 8000 +prob_shots = np.abs(coeffs) / np.sum(np.abs(coeffs)) +print(prob_shots) + +############################################################################## +# We can now use SciPy to create our multinomial distributed random variable +# :math:`S,` using the number of trials (total shot number) and probability values: + +from scipy.stats import multinomial + +si = multinomial(n=total_shots, p=prob_shots) + +############################################################################## +# Sampling from this distribution will provide the number of shots used to +# sample each term in the Hamiltonian: + +samples = si.rvs()[0] +print(samples) +print(sum(samples)) + +############################################################################## +# As expected, if we sum the sampled shots per term, we recover the total number of shots. +# +# Let's now create our cost function. Recall that the cost function must do the +# following: +# +# 1. It must sample from the multinomial distribution we created above, +# to determine the number of shots :math:`s_i` to use to estimate the expectation +# value of the ith Hamiltonian term. +# +# 2. It then must estimate the expectation value :math:`\langle h_i\rangle` +# by creating the required QNode. For our ansatz, we'll use the +# :class:`~.pennylane.templates.layers.StronglyEntanglingLayers`. +# +# 3. And, last but not least, estimate the expectation value +# :math:`\langle H\rangle = \sum_i c_i\langle h_i\rangle.` +# + +from pennylane.templates.layers import StronglyEntanglingLayers + + +@qml.qnode(non_analytic_dev, diff_method="parameter-shift", interface="autograd") +def qnode(weights, observable): + StronglyEntanglingLayers(weights, wires=non_analytic_dev.wires) + return qml.expval(observable) + +def cost(params): + # sample from the multinomial distribution + shots_per_term = si.rvs()[0] + + result = 0 + + for o, c, s in zip(obs, coeffs, shots_per_term): + # evaluate the QNode corresponding to + # the Hamiltonian term, and add it on to our running sum + result += c * qnode(params, o, shots=int(s)) + + return result + + +############################################################################## +# Evaluating our cost function with some initial parameters, we can test out +# that our cost function evaluates correctly. + +param_shape = StronglyEntanglingLayers.shape(n_layers=num_layers, n_wires=num_wires) +init_params = np.random.uniform(low=0.0, high=2*np.pi, size=param_shape, requires_grad=True) +print(cost(init_params)) + + +############################################################################## +# Performing the optimization, with the number of shots randomly +# determined at each optimization step: + +opt = qml.AdamOptimizer(0.05) +params = init_params + +cost_wrs = [] +shots_wrs = [] + +for i in range(100): + params, _cost = opt.step_and_cost(cost, params) + cost_wrs.append(_cost) + shots_wrs.append(total_shots*i) + print("Step {}: cost = {} shots used = {}".format(i, cost_wrs[-1], shots_wrs[-1])) + +############################################################################## +# Let's compare this against an optimization not using weighted random sampling. +# Here, we will split the 8000 total shots evenly across all Hamiltonian terms, +# also known as *uniform deterministic sampling*. + +@qml.qnode(non_analytic_dev, diff_method="parameter-shift", interface="autograd") +def qnode(weights, obs): + StronglyEntanglingLayers(weights, wires=non_analytic_dev.wires) + return qml.expval(obs) + +def cost(params): + shots_per_term = int(total_shots / len(coeffs)) + + result = 0 + + for o, c in zip(obs, coeffs): + + # evaluate the QNode corresponding to + # the Hamiltonian term, and add it on to our running sum + result += c * qnode(params, o, shots=shots_per_term) + + return result + +opt = qml.AdamOptimizer(0.05) +params = init_params + +cost_adam = [] +shots_adam = [] + +for i in range(100): + params, _cost = opt.step_and_cost(cost, params) + cost_adam.append(_cost) + shots_adam.append(total_shots*i) + print("Step {}: cost = {} shots used = {}".format(i, cost_adam[-1], shots_adam[-1])) + +############################################################################## +# Comparing these two techniques: + +from matplotlib import pyplot as plt + +plt.style.use("seaborn-v0_8") +plt.plot(shots_wrs, cost_wrs, "b", label="Adam WRS") +plt.plot(shots_adam, cost_adam, "g", label="Adam") + +plt.ylabel("Cost function value") +plt.xlabel("Number of shots") +plt.legend() +plt.show() + +############################################################################## +# We can see that weighted random sampling performs just as well as the uniform +# deterministic sampling. However, weighted random sampling begins to show a +# non-negligible improvement over deterministic sampling for large Hamiltonians +# with highly non-uniform coefficients. For example, see Fig (3) and (4) of +# Arrasmith et al. [#arrasmith2020]_, comparing weighted random sampling VQE optimization +# for both :math:`\text{H}_2` and :math:`\text{LiH}` molecules. +# +# .. note:: +# +# While not covered here, another approach that could be taken is +# *weighted deterministic sampling*. Here, the number of shots is distributed +# across terms as per +# +# .. math:: s_i = \left\lfloor N \frac{|c_i|}{\sum_i |c_i|}\right\rfloor, +# +# where :math:`N` is the total number of shots. +# + +############################################################################## +# Rosalin: Frugal shot optimization +# --------------------------------- +# +# We can see above that both methods optimize fairly well; weighted random +# sampling converges just as well as evenly distributing the shots across +# all Hamiltonian terms. However, deterministic shot distribution approaches +# will always have a minimum shot value required per expectation value, as below +# this threshold they become biased estimators. This is not the case with random +# sampling; as we saw in the +# :doc:`doubly stochastic gradient descent demonstration `, +# the introduction of randomness allows for as little +# as a single shot per expectation term, while still remaining an unbiased estimator. +# +# Using this insight, Arrasmith et al. [#arrasmith2020]_ modified the iCANS frugal +# shot-optimization technique [#kubler2020]_ to include weighted random sampling, making it +# 'doubly stochastic'. +# +# iCANS optimizer +# ~~~~~~~~~~~~~~~ +# +# Two variants of the iCANS optimizer were introduced in Kübler et al., iCANS1 and iCANS2. +# The iCANS1 optimizer, on which Rosalin is based, frugally distributes a shot budget +# across the partial derivatives of each parameter, which are computed using the +# :doc:`parameter-shift rule `. It works roughly as follows: +# +# 1. The initial step of the optimizer is performed with some specified minimum +# number of shots, :math:`s_{min},` for all partial derivatives. +# +# 2. The parameter-shift rule is then used to estimate the gradient :math:`g_i` +# for each parameter :math:`\theta_i,` parameters, as well as the *variances* +# :math:`v_i` of the estimated gradients. +# +# 3. Gradient descent is performed for each parameter :math:`\theta_i,` using +# the pre-defined learning rate :math:`\alpha` and the gradient information :math:`g_i:` +# +# .. math:: \theta_i = \theta_i - \alpha g_i. +# +# 4. The improvement in the cost function per shot, for a specific parameter value, +# is then calculated via +# +# .. math:: +# +# \gamma_i = \frac{1}{s_i} \left[ \left(\alpha - \frac{1}{2} L\alpha^2\right) +# g_i^2 - \frac{L\alpha^2}{2s_i}v_i \right], +# +# where: +# +# * :math:`L \leq \sum_i|c_i|` is the bound on the `Lipschitz constant +# `__ of the variational quantum algorithm objective function, +# +# * :math:`c_i` are the coefficients of the Hamiltonian, and +# +# * :math:`\alpha` is the learning rate, and *must* be bound such that :math:`\alpha < 2/L` +# for the above expression to hold. +# +# 5. Finally, the new values of :math:`s_i` (shots for partial derivative of parameter +# :math:`\theta_i`) is given by: +# +# .. math:: +# +# s_i = \frac{2L\alpha}{2-L\alpha}\left(\frac{v_i}{g_i^2}\right)\propto +# \frac{v_i}{g_i^2}. +# +# In addition to the above, to counteract the presence of noise in the system, a +# running average of :math:`g_i` and :math:`s_i` (:math:`\chi_i` and :math:`\xi_i` respectively) +# are used when computing :math:`\gamma_i` and :math:`s_i.` +# +# .. note:: +# +# In classical machine learning, the Lipschitz constant of the cost function is generally +# unknown. However, for a variational quantum algorithm with cost of the form +# :math:`f(x) = \langle \psi(x) | \hat{H} |\psi(x)\rangle,` +# an upper bound on the Lipschitz constant is given by :math:`L < \sum_i|c_i|,` +# where :math:`c_i` are the coefficients of :math:`\hat{H}` when decomposed +# into a linear combination of Pauli-operator tensor products. +# +# Rosalin implementation +# ~~~~~~~~~~~~~~~~~~~~~~ +# +# Let's now modify iCANS above to incorporate weighted random sampling of Hamiltonian +# terms — the Rosalin frugal shot optimizer. +# +# Rosalin takes several hyper-parameters: +# +# * ``min_shots``: the minimum number of shots used to estimate the expectations +# of each term in the Hamiltonian. Note that this must be larger than 2 for the variance +# of the gradients to be computed. +# +# * ``mu``: The running average constant :math:`\mu\in[0, 1].` Used to control how quickly the +# number of shots recommended for each gradient component changes. +# +# * ``b``: Regularization bias. The bias should be kept small, but non-zero. +# +# * ``lr``: The learning rate. Recall from above that the learning rate *must* be such +# that :math:`\alpha < 2/L = 2/\sum_i|c_i|.` +# +# Since the Rosalin optimizer has a state that must be preserved between optimization steps, +# let's use a class to create our optimizer. +# + +class Rosalin: + + def __init__(self, obs, coeffs, min_shots, mu=0.99, b=1e-6, lr=0.07): + self.obs = obs + self.coeffs = coeffs + + self.lipschitz = np.sum(np.abs(coeffs)) + + if lr > 2 / self.lipschitz: + raise ValueError("The learning rate must be less than ", 2 / self.lipschitz) + + # hyperparameters + self.min_shots = min_shots + self.mu = mu # running average constant + self.b = b # regularization bias + self.lr = lr # learning rate + + # keep track of the total number of shots used + self.shots_used = 0 + # total number of iterations + self.k = 0 + # Number of shots per parameter + self.s = np.zeros_like(params, dtype=np.float64) + min_shots + + # Running average of the parameter gradients + self.chi = None + # Running average of the variance of the parameter gradients + self.xi = None + + def estimate_hamiltonian(self, params, shots): + """Returns an array containing length ``shots`` single-shot estimates + of the Hamiltonian. The shots are distributed randomly over + the terms in the Hamiltonian, as per a Multinomial distribution. + + Since we are performing single-shot estimates, the QNodes must be + set to 'sample' mode. + """ + # note that convergence depends on seed for random number generation + rosalin_device = qml.device("default.qubit", wires=num_wires, shots=100, seed=93754352) + + # determine the shot probability per term + prob_shots = np.abs(coeffs) / np.sum(np.abs(coeffs)) + + # construct the multinomial distribution, and sample + # from it to determine how many shots to apply per term + si = multinomial(n=shots, p=prob_shots) + shots_per_term = si.rvs()[0] + + results = [] + + @qml.qnode(rosalin_device, diff_method="parameter-shift", interface="autograd") + def qnode(weights, observable): + StronglyEntanglingLayers(weights, wires=rosalin_device.wires) + return qml.sample(observable) + + for o, c, p, s in zip(self.obs, self.coeffs, prob_shots, shots_per_term): + + # if the number of shots is 0, do nothing + if s == 0: + continue + + # evaluate the QNode corresponding to + # the Hamiltonian term + res = qnode(params, o, shots=int(s)) + + if s == 1: + res = np.array([res]) + + # Note that, unlike above, we divide each term by the + # probability per shot. This is because we are sampling one at a time. + results.append(c * res / p) + + return np.concatenate(results) + + def evaluate_grad_var(self, i, params, shots): + """Evaluate the gradient, as well as the variance in the gradient, + for the ith parameter in params, using the parameter-shift rule. + """ + shift = np.zeros_like(params) + shift[i] = np.pi / 2 + + shift_forward = self.estimate_hamiltonian(params + shift, shots) + shift_backward = self.estimate_hamiltonian(params - shift, shots) + + g = np.mean(shift_forward - shift_backward) / 2 + s = np.var((shift_forward - shift_backward) / 2, ddof=1) + + return g, s + + def step(self, params): + """Perform a single step of the Rosalin optimizer.""" + # keep track of the number of shots run + self.shots_used += int(2 * np.sum(self.s)) + + # compute the gradient, as well as the variance in the gradient, + # using the number of shots determined by the array s. + grad = [] + S = [] + + p_ind = list(np.ndindex(*params.shape)) + + for l in p_ind: + # loop through each parameter, performing + # the parameter-shift rule + g_, s_ = self.evaluate_grad_var(l, params, self.s[l]) + grad.append(g_) + S.append(s_) + + grad = np.reshape(np.stack(grad), params.shape) + S = np.reshape(np.stack(S), params.shape) + + # gradient descent update + params = params - self.lr * grad + + if self.xi is None: + self.chi = np.zeros_like(params, dtype=np.float64) + self.xi = np.zeros_like(params, dtype=np.float64) + + # running average of the gradient variance + self.xi = self.mu * self.xi + (1 - self.mu) * S + xi = self.xi / (1 - self.mu ** (self.k + 1)) + + # running average of the gradient + self.chi = self.mu * self.chi + (1 - self.mu) * grad + chi = self.chi / (1 - self.mu ** (self.k + 1)) + + # determine the new optimum shots distribution for the next + # iteration of the optimizer + s = np.ceil( + (2 * self.lipschitz * self.lr * xi) + / ((2 - self.lipschitz * self.lr) * (chi ** 2 + self.b * (self.mu ** self.k))) + ) + + # apply an upper and lower bound on the new shot distributions, + # to avoid the number of shots reducing below min(2, min_shots), + # or growing too significantly. + gamma = ( + (self.lr - self.lipschitz * self.lr ** 2 / 2) * chi ** 2 + - xi * self.lipschitz * self.lr ** 2 / (2 * s) + ) / s + + argmax_gamma = np.unravel_index(np.argmax(gamma), gamma.shape) + smax = s[argmax_gamma] + self.s = np.clip(s, min(2, self.min_shots), max(2, smax)) + + self.k += 1 + return params + + +############################################################################## +# Rosalin optimization +# ~~~~~~~~~~~~~~~~~~~~ +# +# We are now ready to use our Rosalin optimizer to optimize the initial VQE problem. But first let's +# also create a separate cost function using an 'exact' quantum device, so that we can keep track of the +# *exact* cost function value at each iteration. + +@qml.qnode(analytic_dev, interface="autograd") +def cost_analytic(weights): + StronglyEntanglingLayers(weights, wires=analytic_dev.wires) + return qml.expval(qml.Hamiltonian(coeffs, obs)) + +############################################################################## +# Creating the optimizer and beginning the optimization: + + +opt = Rosalin(obs, coeffs, min_shots=10) +params = init_params + +cost_rosalin = [cost_analytic(params)] +shots_rosalin = [0] + +for i in range(60): + params = opt.step(params) + cost_rosalin.append(cost_analytic(params)) + shots_rosalin.append(opt.shots_used) + print(f"Step {i}: cost = {cost_rosalin[-1]}, shots_used = {shots_rosalin[-1]}") + + +############################################################################## +# Let's compare this to a standard Adam optimization. Using 100 shots per quantum +# evaluation, for each update step there are 2 quantum evaluations per parameter. + +adam_shots_per_eval = 100 +adam_shots_per_step = 2 * adam_shots_per_eval * len(params.flatten()) +print(adam_shots_per_step) + +############################################################################## +# Thus, Adam is using 2400 shots per update step. + +params = init_params +opt = qml.AdamOptimizer(0.07) + +adam_dev = qml.device('default.qubit', shots=adam_shots_per_eval, seed=595905) + +@qml.qnode(adam_dev, diff_method="parameter-shift", interface="autograd") +def cost(weights): + StronglyEntanglingLayers(weights, wires=non_analytic_dev.wires) + return qml.expval(qml.Hamiltonian(coeffs, obs)) + +cost_adam = [cost_analytic(params)] +shots_adam = [0] + +for i in range(100): + params = opt.step(cost, params) + cost_adam.append(cost_analytic(params)) + shots_adam.append(adam_shots_per_step * (i + 1)) + print("Step {}: cost = {} shots_used = {}".format(i, cost_adam[-1], shots_adam[-1])) + +############################################################################## +# Plotting both experiments: + +plt.style.use("seaborn-v0_8") +plt.plot(shots_rosalin, cost_rosalin, "b", label="Rosalin") +plt.plot(shots_adam, cost_adam, "g", label="Adam") + +plt.ylabel("Cost function value") +plt.xlabel("Number of shots") +plt.legend() +plt.xlim(0, 300000) +plt.show() + +############################################################################## +# The Rosalin optimizer performs significantly better than the Adam optimizer, +# approaching the ground state energy of the Hamiltonian with strikingly +# fewer shots. +# +# While beyond the scope of this demonstration, the Rosalin optimizer can be +# modified in various other ways; for instance, by incorporating *weighted hybrid +# sampling* (which distributes some shots deterministically, with the remainder +# done randomly), or by adapting the variant iCANS2 optimizer. Download +# this demonstration from the sidebar 👉 and give it a go! ⚛️ + + +############################################################################## +# References +# ---------- +# +# .. [#arrasmith2020] +# +# Andrew Arrasmith, Lukasz Cincio, Rolando D. Somma, and Patrick J. Coles. "Operator Sampling +# for Shot-frugal Optimization in Variational Algorithms." `arXiv:2004.06252 +# `__ (2020). +# +# .. [#stokes2019] +# +# James Stokes, Josh Izaac, Nathan Killoran, and Giuseppe Carleo. "Quantum Natural Gradient." +# `arXiv:1909.02108 `__ (2019). +# +# .. [#sweke2019] +# +# Ryan Sweke, Frederik Wilde, Johannes Jakob Meyer, Maria Schuld, Paul K. Fährmann, Barthélémy +# Meynard-Piganeau, and Jens Eisert. "Stochastic gradient descent for hybrid quantum-classical +# optimization." `arXiv:1910.01155 `__ (2019). +# +# .. [#kubler2020] +# +# Jonas M. Kübler, Andrew Arrasmith, Lukasz Cincio, and Patrick J. Coles. "An Adaptive Optimizer +# for Measurement-Frugal Variational Algorithms." `Quantum 4, 263 +# `__ (2020). +# +# +# About the author +# ---------------- # \ No newline at end of file diff --git a/demonstrations_v2/vqe_parallel/demo.py b/demonstrations_v2/vqe_parallel/demo.py index 91c47d7bf1..7c298007e8 100644 --- a/demonstrations_v2/vqe_parallel/demo.py +++ b/demonstrations_v2/vqe_parallel/demo.py @@ -1,393 +1,393 @@ - -# coding=utf-8 -r""" -VQE with parallel QPUs with Rigetti -======================================== - -.. meta:: - :property="og:description": Using parallel QPUs to - speed up the calculation of the potential energy surface of molecular Hamiltonian. - :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/vqe_diagram.png - -.. related:: - - tutorial_vqe A brief overview of VQE - -*Author: Tom Bromley — Posted: 14 February 2020. Last updated: 29 August 2023.* - -.. warning:: - This demo requires Python <=3.10 and uses the PennyLane-Rigetti plugin, which is only compatible with PennyLane v0.40 or below. To run this demo with newer versions of PennyLane, you will need to use `a different simulator device `__. - -This tutorial showcases how using asynchronously-evaluated parallel QPUs can speed up the -calculation of the potential energy surface of molecular hydrogen (:math:`H_2`). - -Using a VQE setup, we task two devices from the -`PennyLane-Rigetti `__ plugin with evaluating -separate terms in the qubit Hamiltonian of :math:`H_2.` As these devices are allowed to operate -asynchronously, i.e., at the same time and without having to wait for each other, -the calculation can be performed in roughly half the time. - -We begin by importing the prerequisite libraries: -""" - -import time -import dask - -import matplotlib.pyplot as plt -from pennylane import numpy as np -import pennylane as qml -from pennylane import qchem - -############################################################################## -# -# This tutorial requires the ``pennylane-rigetti`` and ``dask`` -# packages, which are installed separately using: -# -# .. code-block:: bash -# -# pip install pennylane-rigetti -# pip install "dask[delayed]" -# -# Finding the qubit Hamiltonians of :math:`H_{2}` -# ----------------------------------------------- -# -# The objective of this tutorial is to evaluate the potential energy surface of molecular -# hydrogen. This is achieved by finding the ground state energy of :math:`H_{2}` as we increase -# the bond length between the hydrogen atoms. -# -# Each inter-atomic distance results in a different qubit Hamiltonian. Further -# details on the mapping from the electronic Hamiltonian of a molecule to a -# qubit Hamiltonian can be found in the -# :doc:`tutorial_quantum_chemistry` and :doc:`tutorial_vqe` -# tutorials. -# -# We begin by downloading a selection of datasets of :math:`H_2` molecule for -# various bond lengths using the -# `PennyLane Datasets library `__: - -bonds = [0.5, 0.58, 0.7, 0.9, 1.1, 1.3, 1.5, 1.7, 1.9, 2.1] -datasets = qml.data.load("qchem", molname="H2", bondlength=bonds, basis="STO-3G") - -############################################################################## -# We can now extract the qubit Hamiltonians from these datasets for each bond length: - -hamiltonians = [d.hamiltonian for d in datasets] - -############################################################################## -# Each Hamiltonian can be written as a linear combination of fifteen tensor products of Pauli -# matrices. Let's take a look more closely at one of the Hamiltonians: - -h = hamiltonians[0] -_, h_ops = h.terms() - -print("Number of terms: {}\n".format(len(h_ops))) -for op in h_ops: - print("Measurement {} on wires {}".format(str(op), op.wires)) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# Number of terms: 15 -# -# Measurement I(0) on wires Wires([0]) -# Measurement Z(0) on wires Wires([0]) -# Measurement Z(1) on wires Wires([1]) -# Measurement Z(0) @ Z(1) on wires Wires([0, 1]) -# Measurement Y(0) @ X(1) @ X(2) @ Y(3) on wires Wires([0, 1, 2, 3]) -# Measurement Y(0) @ Y(1) @ X(2) @ X(3) on wires Wires([0, 1, 2, 3]) -# Measurement X(0) @ X(1) @ Y(2) @ Y(3) on wires Wires([0, 1, 2, 3]) -# Measurement X(0) @ Y(1) @ Y(2) @ X(3) on wires Wires([0, 1, 2, 3]) -# Measurement Z(2) on wires Wires([2]) -# Measurement Z(0) @ Z(2) on wires Wires([0, 2]) -# Measurement Z(3) on wires Wires([3]) -# Measurement Z(0) @ Z(3) on wires Wires([0, 3]) -# Measurement Z(1) @ Z(2) on wires Wires([1, 2]) -# Measurement Z(1) @ Z(3) on wires Wires([1, 3]) -# Measurement Z(2) @ Z(3) on wires Wires([2, 3]) - -############################################################################## -# Defining the energy function -# ---------------------------- -# -# The fifteen Pauli terms comprising each Hamiltonian can conventionally be evaluated in a -# sequential manner: we evaluate one expectation value at a time before moving on to the next. -# However, this task is highly suited to parallelization. With access to multiple QPUs, -# we can split up evaluating the terms between the QPUs and gain an increase in processing speed. -# -# -# .. note:: -# Some of the Pauli terms commute, and so they can be evaluated in practice with fewer than -# fifteen quantum circuit runs. Nevertheless, these quantum circuit runs can still be -# parallelized to multiple QPUs. -# -# Let's suppose we have access to two quantum devices. In this tutorial we consider two -# simulators from Rigetti: ``4q-qvm`` and ``9q-square-qvm``, but we could also run on hardware -# devices from Rigetti or other providers. -# -# We can evaluate the expectation value of each Hamiltonian with eight terms run on -# one device and seven terms run on the other, as summarized by the diagram below: -# -# .. figure:: /_static/demonstration_assets/vqe_parallel/vqe_diagram.png -# :width: 65% -# :align: center -# -# To do this, start by instantiating a device for each term: - -dev1 = [qml.device("rigetti.qvm", device="4q-qvm") for _ in range(8)] -dev2 = [qml.device("rigetti.qvm", device="9q-square-qvm") for _ in range(7)] -devs = dev1 + dev2 - -############################################################################## -# .. note:: -# -# For the purposes of this demonstration, we are simulating the QPUs using the -# ``rigetti.qvm`` simulator. To run this demonstration on hardware, simply -# swap ``rigetti.qvm`` for ``rigetti.qpu`` and specify the hardware device to run on. -# -# Please refer to the `Rigetti website `__ for an up-to-date -# list on available QPUs. -# -# .. warning:: -# Rigetti's QVM and Quil Compiler services must be running for this tutorial to execute. They -# can be installed by consulting the `Rigetti documentation -# `__ or, for users with Docker, by running: -# -# .. code-block:: bash -# -# docker run -d -p 5555:5555 rigetti/quilc -R -p 5555 -# docker run -d -p 5000:5000 rigetti/qvm -S -p 5000 -# -# We must also define a circuit to prepare the ground state, which is a superposition of the -# Hartree-Fock (:math:`|1100\rangle`) and doubly-excited (:math:`|0011\rangle`) configurations. -# The simple circuit below is able to prepare states of the form :math:`\alpha |1100\rangle + -# \beta |0011\rangle` and hence encode the ground state wave function of the hydrogen molecule. The -# circuit has a single free parameter, which controls a Y-rotation on the third qubit. - - -def circuit(param, H): - qml.BasisState(np.array([1, 1, 0, 0], requires_grad=False), wires=[0, 1, 2, 3]) - qml.RY(param, wires=2) - qml.CNOT(wires=[2, 3]) - qml.CNOT(wires=[2, 0]) - qml.CNOT(wires=[3, 1]) - return qml.expval(H) - - -############################################################################## -# The ground state for each inter-atomic distance is characterized by a different Y-rotation angle. -# The values of these Y-rotations can be found by minimizing the ground state energy as outlined in -# :doc:`tutorial_vqe`. In this tutorial, we load pre-optimized rotations and focus on -# comparing the speed of evaluating the potential energy surface with sequential and parallel -# evaluation. These parameters can be downloaded by clicking :download:`here -# <../_static/demonstration_assets/vqe_parallel/RY_params.npy>`. - -params = np.load("vqe_parallel/RY_params.npy") - -############################################################################## -# Calculating the potential energy surface -# ---------------------------------------- -# The most vanilla execution of these 10 energy surfaces is using the standard PennyLane functionalities by executing the QNodes. -# Internally, this creates a measurement for each term in the Hamiltonian that are then sequentially computed. - -print("Evaluating the potential energy surface sequentially") -t0 = time.time() - -energies_seq = [] -for i, (h, param) in enumerate(zip(hamiltonians, params)): - print( - f"{i+1} / {len(bonds)}: Sequential execution; Running for inter-atomic distance {bonds[i]} Å" - ) - energies_seq.append(qml.QNode(circuit, devs[0])(param, h)) - -dt_seq = time.time() - t0 - -print(f"Evaluation time: {dt_seq:.2f} s") - -############################################################################## -# We can parallelize the individual evaluations using ``dask`` in the following way: We take the 15 terms of the Hamiltonian and -# distribute them to the 15 devices in ``devs``. This evaluation is delayed using ``dask.delayed`` and later computed -# in parallel using ``dask.compute``, which asynchronously executes the delayed objects in ``results``. - - -def compute_energy_parallel(H, devs, param): - H_coeffs, H_ops = H.terms() - assert len(H_ops) == len(devs) - results = [] - - for i in range(len(H_ops)): - qnode = qml.QNode(circuit, devs[i]) - results.append(dask.delayed(qnode)(param, H_ops[i])) - - results = dask.compute(*results, scheduler="threads") - result = sum(c * r for c, r in zip(H_coeffs, results)) - return result - - -############################################################################## -# We can now compute all 10 samples from the energy surface sequentially, where each execution is making use of -# parallel device execution. Curiously, in this example the overhead from doing so outweighs the speed-up -# and the execution is slower than standard execution using ``qml.expval``. For different circuits and -# different Hamiltonians, however, parallelization may provide significant speed-ups. - -print("Evaluating the potential energy surface in parallel") -t0 = time.time() - -energies_par = [] -for i, (h, param) in enumerate(zip(hamiltonians, params)): - print( - f"{i+1} / {len(bonds)}: Parallel execution; Running for inter-atomic distance {bonds[i]} Å" - ) - energies_par.append(compute_energy_parallel(h, devs, param)) - -dt_par = time.time() - t0 - -print(f"Evaluation time: {dt_par:.2f} s") - - -############################################################################## -# We can improve this procedure further by optimizing the measurement. Currently, we are measuring each term of the Hamiltonian -# in a separate measurement. This is not necessary as there are sub-groups of commuting terms in the Hamiltonian that can be measured -# simultaneously. We can utilize the grouping function :func:`~.pennylane.pauli.group_observables` to generate few measurements that -# are executed in parallel: - - -def compute_energy_parallel_optimized(H, devs, param): - H_coeffs, H_ops = H.terms() - assert len(H_ops) == len(devs) - results = [] - - obs_groupings, coeffs_groupings = qml.pauli.group_observables( - H_ops, H_coeffs, "qwc" - ) - - for i, (obs, coeffs) in enumerate(zip(obs_groupings, coeffs_groupings)): - H_part = qml.Hamiltonian(coeffs, obs) - qnode = qml.QNode(circuit, devs[i]) - results.append(dask.delayed(qnode)(param, H_part)) - - result = qml.math.sum(dask.compute(*results, scheduler="threads")) - return result - -print( - "Evaluating the potential energy surface in parallel with measurement optimization" -) -t0 = time.time() - -energies_par_opt = [] -for i, (h, param) in enumerate(zip(hamiltonians, params)): - print( - f"{i+1} / {len(bonds)}: Parallel execution and measurement optimization; Running for inter-atomic distance {bonds[i]} Å" - ) - energies_par_opt.append(compute_energy_parallel_optimized(h, devs, param)) - -dt_par_opt = time.time() - t0 - -print(f"Evaluation time: {dt_par_opt:.2f} s") - - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# Evaluating the potential energy surface sequentially -# 1 / 10: Sequential execution; Running for inter-atomic distance 0.3 Å -# 2 / 10: Sequential execution; Running for inter-atomic distance 0.5 Å -# 3 / 10: Sequential execution; Running for inter-atomic distance 0.7 Å -# 4 / 10: Sequential execution; Running for inter-atomic distance 0.9 Å -# 5 / 10: Sequential execution; Running for inter-atomic distance 1.1 Å -# 6 / 10: Sequential execution; Running for inter-atomic distance 1.3 Å -# 7 / 10: Sequential execution; Running for inter-atomic distance 1.5 Å -# 8 / 10: Sequential execution; Running for inter-atomic distance 1.7 Å -# 9 / 10: Sequential execution; Running for inter-atomic distance 1.9 Å -# 10 / 10: Sequential execution; Running for inter-atomic distance 2.1 Å -# Evaluation time: 39.33 s -# -# Evaluating the potential energy surface in parallel -# 1 / 10: Parallel execution; Running for inter-atomic distance 0.3 Å -# 2 / 10: Parallel execution; Running for inter-atomic distance 0.5 Å -# 3 / 10: Parallel execution; Running for inter-atomic distance 0.7 Å -# 4 / 10: Parallel execution; Running for inter-atomic distance 0.9 Å -# 5 / 10: Parallel execution; Running for inter-atomic distance 1.1 Å -# 6 / 10: Parallel execution; Running for inter-atomic distance 1.3 Å -# 7 / 10: Parallel execution; Running for inter-atomic distance 1.5 Å -# 8 / 10: Parallel execution; Running for inter-atomic distance 1.7 Å -# 9 / 10: Parallel execution; Running for inter-atomic distance 1.9 Å -# 10 / 10: Parallel execution; Running for inter-atomic distance 2.1 Å -# Evaluation time: 73.42 s -# -# Evaluating the potential energy surface in parallel with measurement optimization -# 1 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.3 Å -# 2 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.5 Å -# 3 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.7 Å -# 4 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.9 Å -# 5 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.1 Å -# 6 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.3 Å -# 7 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.5 Å -# 8 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.7 Å -# 9 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.9 Å -# 10 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 2.1 Å -# Evaluation time: 26.51 s - - -############################################################################## -# We have seen how Hamiltonian measurements can be parallelized and optimized at the same time. - -print("Speed up: {0:.2f}".format(dt_seq / dt_par_opt)) - -############################################################################## -# .. rst-class:: sphx-glr-script-out -# -# -# .. code-block:: none -# -# Speed up: 1.48 - -############################################################################## -# To conclude the tutorial, let's plot the calculated -# potential energy surfaces: - -np.savez( - "vqe_parallel", - energies_seq=energies_seq, - energies_par=energies_par, - energies_par_opt=energies_par_opt, -) - -plt.plot( - bonds, energies_seq, linewidth=2.2, marker="d", color="green", label="sequential" -) -plt.plot(bonds, energies_par, linewidth=2.2, marker="o", color="red", label="parallel") -plt.plot( - bonds, - energies_par_opt, - linewidth=2.2, - marker="d", - color="blue", - label="paralell and optimized", -) -plt.legend(fontsize=12) -plt.title("Potential energy surface for molecular hydrogen", fontsize=12) -plt.xlabel("Atomic separation (Å)", fontsize=16) -plt.ylabel("Ground state energy (Ha)", fontsize=16) -plt.grid(True) - -############################################################################## -# .. figure:: /_static/demonstration_assets/vqe_parallel/vqe_parallel_001.png -# :width: 80% -# :align: center -# - -############################################################################## -# These surfaces overlap, with any variation due to the limited number of shots used to evaluate the -# expectation values in the ``rigetti.qvm`` device (we are using the default value of -# ``shots=1024``). - -############################################################################## -# About the author -# ---------------- -# + +# coding=utf-8 +r""" +VQE with parallel QPUs with Rigetti +======================================== + +.. meta:: + :property="og:description": Using parallel QPUs to + speed up the calculation of the potential energy surface of molecular Hamiltonian. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/vqe_diagram.png + +.. related:: + + tutorial_vqe A brief overview of VQE + +*Author: Tom Bromley — Posted: 14 February 2020. Last updated: 29 August 2023.* + +.. warning:: + This demo requires Python <=3.10 and uses the PennyLane-Rigetti plugin, which is only compatible with PennyLane v0.40 or below. To run this demo with newer versions of PennyLane, you will need to use `a different simulator device `__. + +This tutorial showcases how using asynchronously-evaluated parallel QPUs can speed up the +calculation of the potential energy surface of molecular hydrogen (:math:`H_2`). + +Using a VQE setup, we task two devices from the +`PennyLane-Rigetti `__ plugin with evaluating +separate terms in the qubit Hamiltonian of :math:`H_2.` As these devices are allowed to operate +asynchronously, i.e., at the same time and without having to wait for each other, +the calculation can be performed in roughly half the time. + +We begin by importing the prerequisite libraries: +""" + +import time +import dask + +import matplotlib.pyplot as plt +from pennylane import numpy as np +import pennylane as qml +from pennylane import qchem + +############################################################################## +# +# This tutorial requires the ``pennylane-rigetti`` and ``dask`` +# packages, which are installed separately using: +# +# .. code-block:: bash +# +# pip install pennylane-rigetti +# pip install "dask[delayed]" +# +# Finding the qubit Hamiltonians of :math:`H_{2}` +# ----------------------------------------------- +# +# The objective of this tutorial is to evaluate the potential energy surface of molecular +# hydrogen. This is achieved by finding the ground state energy of :math:`H_{2}` as we increase +# the bond length between the hydrogen atoms. +# +# Each inter-atomic distance results in a different qubit Hamiltonian. Further +# details on the mapping from the electronic Hamiltonian of a molecule to a +# qubit Hamiltonian can be found in the +# :doc:`tutorial_quantum_chemistry` and :doc:`tutorial_vqe` +# tutorials. +# +# We begin by downloading a selection of datasets of :math:`H_2` molecule for +# various bond lengths using the +# `PennyLane Datasets library `__: + +bonds = [0.5, 0.58, 0.7, 0.9, 1.1, 1.3, 1.5, 1.7, 1.9, 2.1] +datasets = qml.data.load("qchem", molname="H2", bondlength=bonds, basis="STO-3G") + +############################################################################## +# We can now extract the qubit Hamiltonians from these datasets for each bond length: + +hamiltonians = [d.hamiltonian for d in datasets] + +############################################################################## +# Each Hamiltonian can be written as a linear combination of fifteen tensor products of Pauli +# matrices. Let's take a look more closely at one of the Hamiltonians: + +h = hamiltonians[0] +_, h_ops = h.terms() + +print("Number of terms: {}\n".format(len(h_ops))) +for op in h_ops: + print("Measurement {} on wires {}".format(str(op), op.wires)) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Number of terms: 15 +# +# Measurement I(0) on wires Wires([0]) +# Measurement Z(0) on wires Wires([0]) +# Measurement Z(1) on wires Wires([1]) +# Measurement Z(0) @ Z(1) on wires Wires([0, 1]) +# Measurement Y(0) @ X(1) @ X(2) @ Y(3) on wires Wires([0, 1, 2, 3]) +# Measurement Y(0) @ Y(1) @ X(2) @ X(3) on wires Wires([0, 1, 2, 3]) +# Measurement X(0) @ X(1) @ Y(2) @ Y(3) on wires Wires([0, 1, 2, 3]) +# Measurement X(0) @ Y(1) @ Y(2) @ X(3) on wires Wires([0, 1, 2, 3]) +# Measurement Z(2) on wires Wires([2]) +# Measurement Z(0) @ Z(2) on wires Wires([0, 2]) +# Measurement Z(3) on wires Wires([3]) +# Measurement Z(0) @ Z(3) on wires Wires([0, 3]) +# Measurement Z(1) @ Z(2) on wires Wires([1, 2]) +# Measurement Z(1) @ Z(3) on wires Wires([1, 3]) +# Measurement Z(2) @ Z(3) on wires Wires([2, 3]) + +############################################################################## +# Defining the energy function +# ---------------------------- +# +# The fifteen Pauli terms comprising each Hamiltonian can conventionally be evaluated in a +# sequential manner: we evaluate one expectation value at a time before moving on to the next. +# However, this task is highly suited to parallelization. With access to multiple QPUs, +# we can split up evaluating the terms between the QPUs and gain an increase in processing speed. +# +# +# .. note:: +# Some of the Pauli terms commute, and so they can be evaluated in practice with fewer than +# fifteen quantum circuit runs. Nevertheless, these quantum circuit runs can still be +# parallelized to multiple QPUs. +# +# Let's suppose we have access to two quantum devices. In this tutorial we consider two +# simulators from Rigetti: ``4q-qvm`` and ``9q-square-qvm``, but we could also run on hardware +# devices from Rigetti or other providers. +# +# We can evaluate the expectation value of each Hamiltonian with eight terms run on +# one device and seven terms run on the other, as summarized by the diagram below: +# +# .. figure:: /_static/demonstration_assets/vqe_parallel/vqe_diagram.png +# :width: 65% +# :align: center +# +# To do this, start by instantiating a device for each term: + +dev1 = [qml.device("rigetti.qvm", device="4q-qvm") for _ in range(8)] +dev2 = [qml.device("rigetti.qvm", device="9q-square-qvm") for _ in range(7)] +devs = dev1 + dev2 + +############################################################################## +# .. note:: +# +# For the purposes of this demonstration, we are simulating the QPUs using the +# ``rigetti.qvm`` simulator. To run this demonstration on hardware, simply +# swap ``rigetti.qvm`` for ``rigetti.qpu`` and specify the hardware device to run on. +# +# Please refer to the `Rigetti website `__ for an up-to-date +# list on available QPUs. +# +# .. warning:: +# Rigetti's QVM and Quil Compiler services must be running for this tutorial to execute. They +# can be installed by consulting the `Rigetti documentation +# `__ or, for users with Docker, by running: +# +# .. code-block:: bash +# +# docker run -d -p 5555:5555 rigetti/quilc -R -p 5555 +# docker run -d -p 5000:5000 rigetti/qvm -S -p 5000 +# +# We must also define a circuit to prepare the ground state, which is a superposition of the +# Hartree-Fock (:math:`|1100\rangle`) and doubly-excited (:math:`|0011\rangle`) configurations. +# The simple circuit below is able to prepare states of the form :math:`\alpha |1100\rangle + +# \beta |0011\rangle` and hence encode the ground state wave function of the hydrogen molecule. The +# circuit has a single free parameter, which controls a Y-rotation on the third qubit. + + +def circuit(param, H): + qml.BasisState(np.array([1, 1, 0, 0], requires_grad=False), wires=[0, 1, 2, 3]) + qml.RY(param, wires=2) + qml.CNOT(wires=[2, 3]) + qml.CNOT(wires=[2, 0]) + qml.CNOT(wires=[3, 1]) + return qml.expval(H) + + +############################################################################## +# The ground state for each inter-atomic distance is characterized by a different Y-rotation angle. +# The values of these Y-rotations can be found by minimizing the ground state energy as outlined in +# :doc:`tutorial_vqe`. In this tutorial, we load pre-optimized rotations and focus on +# comparing the speed of evaluating the potential energy surface with sequential and parallel +# evaluation. These parameters can be downloaded by clicking :download:`here +# <../_static/demonstration_assets/vqe_parallel/RY_params.npy>`. + +params = np.load("vqe_parallel/RY_params.npy") + +############################################################################## +# Calculating the potential energy surface +# ---------------------------------------- +# The most vanilla execution of these 10 energy surfaces is using the standard PennyLane functionalities by executing the QNodes. +# Internally, this creates a measurement for each term in the Hamiltonian that are then sequentially computed. + +print("Evaluating the potential energy surface sequentially") +t0 = time.time() + +energies_seq = [] +for i, (h, param) in enumerate(zip(hamiltonians, params)): + print( + f"{i+1} / {len(bonds)}: Sequential execution; Running for inter-atomic distance {bonds[i]} Å" + ) + energies_seq.append(qml.QNode(circuit, devs[0])(param, h)) + +dt_seq = time.time() - t0 + +print(f"Evaluation time: {dt_seq:.2f} s") + +############################################################################## +# We can parallelize the individual evaluations using ``dask`` in the following way: We take the 15 terms of the Hamiltonian and +# distribute them to the 15 devices in ``devs``. This evaluation is delayed using ``dask.delayed`` and later computed +# in parallel using ``dask.compute``, which asynchronously executes the delayed objects in ``results``. + + +def compute_energy_parallel(H, devs, param): + H_coeffs, H_ops = H.terms() + assert len(H_ops) == len(devs) + results = [] + + for i in range(len(H_ops)): + qnode = qml.QNode(circuit, devs[i]) + results.append(dask.delayed(qnode)(param, H_ops[i])) + + results = dask.compute(*results, scheduler="threads") + result = sum(c * r for c, r in zip(H_coeffs, results)) + return result + + +############################################################################## +# We can now compute all 10 samples from the energy surface sequentially, where each execution is making use of +# parallel device execution. Curiously, in this example the overhead from doing so outweighs the speed-up +# and the execution is slower than standard execution using ``qml.expval``. For different circuits and +# different Hamiltonians, however, parallelization may provide significant speed-ups. + +print("Evaluating the potential energy surface in parallel") +t0 = time.time() + +energies_par = [] +for i, (h, param) in enumerate(zip(hamiltonians, params)): + print( + f"{i+1} / {len(bonds)}: Parallel execution; Running for inter-atomic distance {bonds[i]} Å" + ) + energies_par.append(compute_energy_parallel(h, devs, param)) + +dt_par = time.time() - t0 + +print(f"Evaluation time: {dt_par:.2f} s") + + +############################################################################## +# We can improve this procedure further by optimizing the measurement. Currently, we are measuring each term of the Hamiltonian +# in a separate measurement. This is not necessary as there are sub-groups of commuting terms in the Hamiltonian that can be measured +# simultaneously. We can utilize the grouping function :func:`~.pennylane.pauli.group_observables` to generate few measurements that +# are executed in parallel: + + +def compute_energy_parallel_optimized(H, devs, param): + H_coeffs, H_ops = H.terms() + assert len(H_ops) == len(devs) + results = [] + + obs_groupings, coeffs_groupings = qml.pauli.group_observables( + H_ops, H_coeffs, "qwc" + ) + + for i, (obs, coeffs) in enumerate(zip(obs_groupings, coeffs_groupings)): + H_part = qml.Hamiltonian(coeffs, obs) + qnode = qml.QNode(circuit, devs[i]) + results.append(dask.delayed(qnode)(param, H_part)) + + result = qml.math.sum(dask.compute(*results, scheduler="threads")) + return result + +print( + "Evaluating the potential energy surface in parallel with measurement optimization" +) +t0 = time.time() + +energies_par_opt = [] +for i, (h, param) in enumerate(zip(hamiltonians, params)): + print( + f"{i+1} / {len(bonds)}: Parallel execution and measurement optimization; Running for inter-atomic distance {bonds[i]} Å" + ) + energies_par_opt.append(compute_energy_parallel_optimized(h, devs, param)) + +dt_par_opt = time.time() - t0 + +print(f"Evaluation time: {dt_par_opt:.2f} s") + + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Evaluating the potential energy surface sequentially +# 1 / 10: Sequential execution; Running for inter-atomic distance 0.3 Å +# 2 / 10: Sequential execution; Running for inter-atomic distance 0.5 Å +# 3 / 10: Sequential execution; Running for inter-atomic distance 0.7 Å +# 4 / 10: Sequential execution; Running for inter-atomic distance 0.9 Å +# 5 / 10: Sequential execution; Running for inter-atomic distance 1.1 Å +# 6 / 10: Sequential execution; Running for inter-atomic distance 1.3 Å +# 7 / 10: Sequential execution; Running for inter-atomic distance 1.5 Å +# 8 / 10: Sequential execution; Running for inter-atomic distance 1.7 Å +# 9 / 10: Sequential execution; Running for inter-atomic distance 1.9 Å +# 10 / 10: Sequential execution; Running for inter-atomic distance 2.1 Å +# Evaluation time: 39.33 s +# +# Evaluating the potential energy surface in parallel +# 1 / 10: Parallel execution; Running for inter-atomic distance 0.3 Å +# 2 / 10: Parallel execution; Running for inter-atomic distance 0.5 Å +# 3 / 10: Parallel execution; Running for inter-atomic distance 0.7 Å +# 4 / 10: Parallel execution; Running for inter-atomic distance 0.9 Å +# 5 / 10: Parallel execution; Running for inter-atomic distance 1.1 Å +# 6 / 10: Parallel execution; Running for inter-atomic distance 1.3 Å +# 7 / 10: Parallel execution; Running for inter-atomic distance 1.5 Å +# 8 / 10: Parallel execution; Running for inter-atomic distance 1.7 Å +# 9 / 10: Parallel execution; Running for inter-atomic distance 1.9 Å +# 10 / 10: Parallel execution; Running for inter-atomic distance 2.1 Å +# Evaluation time: 73.42 s +# +# Evaluating the potential energy surface in parallel with measurement optimization +# 1 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.3 Å +# 2 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.5 Å +# 3 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.7 Å +# 4 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.9 Å +# 5 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.1 Å +# 6 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.3 Å +# 7 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.5 Å +# 8 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.7 Å +# 9 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.9 Å +# 10 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 2.1 Å +# Evaluation time: 26.51 s + + +############################################################################## +# We have seen how Hamiltonian measurements can be parallelized and optimized at the same time. + +print("Speed up: {0:.2f}".format(dt_seq / dt_par_opt)) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Speed up: 1.48 + +############################################################################## +# To conclude the tutorial, let's plot the calculated +# potential energy surfaces: + +np.savez( + "vqe_parallel", + energies_seq=energies_seq, + energies_par=energies_par, + energies_par_opt=energies_par_opt, +) + +plt.plot( + bonds, energies_seq, linewidth=2.2, marker="d", color="green", label="sequential" +) +plt.plot(bonds, energies_par, linewidth=2.2, marker="o", color="red", label="parallel") +plt.plot( + bonds, + energies_par_opt, + linewidth=2.2, + marker="d", + color="blue", + label="paralell and optimized", +) +plt.legend(fontsize=12) +plt.title("Potential energy surface for molecular hydrogen", fontsize=12) +plt.xlabel("Atomic separation (Å)", fontsize=16) +plt.ylabel("Ground state energy (Ha)", fontsize=16) +plt.grid(True) + +############################################################################## +# .. figure:: /_static/demonstration_assets/vqe_parallel/vqe_parallel_001.png +# :width: 80% +# :align: center +# + +############################################################################## +# These surfaces overlap, with any variation due to the limited number of shots used to evaluate the +# expectation values in the ``rigetti.qvm`` device (we are using the default value of +# ``shots=1024``). + +############################################################################## +# About the author +# ---------------- +# diff --git a/notebook_converter/notebook_to_demo.py b/notebook_converter/notebook_to_demo.py old mode 100644 new mode 100755 diff --git a/sg_execution_times.rst b/sg_execution_times.rst deleted file mode 100644 index 3c1cd012f9..0000000000 --- a/sg_execution_times.rst +++ /dev/null @@ -1,568 +0,0 @@ - -:orphan: - -.. _sphx_glr_sg_execution_times: - - -Computation times -================= -**03:09.592** total execution time for 178 files **from all galleries**: - -.. container:: - - .. raw:: html - - - - - - - - .. list-table:: - :header-rows: 1 - :class: table table-striped sg-datatable - - * - Example - - Time - - Mem (MB) - * - :ref:`sphx_glr_demos_tutorial_quantum_phase_transitions.py` (``demonstrations\tutorial_quantum_phase_transitions.py``) - - 03:09.592 - - 0.0 - * - :ref:`sphx_glr_demos_adjoint_diff_benchmarking.py` (``demonstrations\adjoint_diff_benchmarking.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_ahs_aquila.py` (``demonstrations\ahs_aquila.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_braket-parallel-gradients.py` (``demonstrations\braket-parallel-gradients.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_circuits_as_fourier_series.py` (``demonstrations\circuits_as_fourier_series.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_covalent_cloud_gpu.py` (``demonstrations\covalent_cloud_gpu.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_ensemble_multi_qpu.py` (``demonstrations\ensemble_multi_qpu.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_function_fitting_qsp.py` (``demonstrations\function_fitting_qsp.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_gbs.py` (``demonstrations\gbs.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_getting_started_with_hybrid_jobs.py` (``demonstrations\getting_started_with_hybrid_jobs.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_gqe_training.py` (``demonstrations\gqe_training.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_how_to_catalyst_lightning_gpu.py` (``demonstrations\how_to_catalyst_lightning_gpu.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_how_to_use_qiskit1_with_pennylane.py` (``demonstrations\how_to_use_qiskit1_with_pennylane.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_ibm_pennylane.py` (``demonstrations\ibm_pennylane.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_learning2learn.py` (``demonstrations\learning2learn.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_linear_equations_hhl_qrisp_catalyst.py` (``demonstrations\linear_equations_hhl_qrisp_catalyst.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_ml_classical_shadows.py` (``demonstrations\ml_classical_shadows.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_oqc_pulse.py` (``demonstrations\oqc_pulse.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_plugins_hybrid.py` (``demonstrations\plugins_hybrid.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_pytorch_noise.py` (``demonstrations\pytorch_noise.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_qnn_module_tf.py` (``demonstrations\qnn_module_tf.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_qnspsa.py` (``demonstrations\qnspsa.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_qonn.py` (``demonstrations\qonn.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_qrack.py` (``demonstrations\qrack.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_qsim_beyond_classical.py` (``demonstrations\qsim_beyond_classical.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_quantum_neural_net.py` (``demonstrations\quantum_neural_net.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_quantum_volume.py` (``demonstrations\quantum_volume.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt.py` (``demonstrations\tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_How_to_optimize_QML_model_using_JAX_and_Optax.py` (``demonstrations\tutorial_How_to_optimize_QML_model_using_JAX_and_Optax.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax.py` (``demonstrations\tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_How_to_simulate_quantum_circuits_with_tensor_networks.py` (``demonstrations\tutorial_How_to_simulate_quantum_circuits_with_tensor_networks.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_QGAN.py` (``demonstrations\tutorial_QGAN.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_QUBO.py` (``demonstrations\tutorial_QUBO.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_adaptive_circuits.py` (``demonstrations\tutorial_adaptive_circuits.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_adjoint_diff.py` (``demonstrations\tutorial_adjoint_diff.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_adversarial_attacks_QML.py` (``demonstrations\tutorial_adversarial_attacks_QML.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_annni.py` (``demonstrations\tutorial_annni.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_apply_qsvt.py` (``demonstrations\tutorial_apply_qsvt.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_backprop.py` (``demonstrations\tutorial_backprop.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_barren_gadgets.py` (``demonstrations\tutorial_barren_gadgets.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_barren_plateaus.py` (``demonstrations\tutorial_barren_plateaus.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_block_encoding.py` (``demonstrations\tutorial_block_encoding.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_bluequbit.py` (``demonstrations\tutorial_bluequbit.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_chemical_reactions.py` (``demonstrations\tutorial_chemical_reactions.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_circuit_compilation.py` (``demonstrations\tutorial_circuit_compilation.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_classical_expval_estimation.py` (``demonstrations\tutorial_classical_expval_estimation.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_classical_kernels.py` (``demonstrations\tutorial_classical_kernels.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_classical_shadows.py` (``demonstrations\tutorial_classical_shadows.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_classically_boosted_vqe.py` (``demonstrations\tutorial_classically_boosted_vqe.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_clifford_circuit_simulations.py` (``demonstrations\tutorial_clifford_circuit_simulations.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_coherent_vqls.py` (``demonstrations\tutorial_coherent_vqls.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_constant_depth_mps_prep.py` (``demonstrations\tutorial_constant_depth_mps_prep.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_contextuality.py` (``demonstrations\tutorial_contextuality.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_data_reuploading_classifier.py` (``demonstrations\tutorial_data_reuploading_classifier.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_diffable-mitigation.py` (``demonstrations\tutorial_diffable-mitigation.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_diffable_shadows.py` (``demonstrations\tutorial_diffable_shadows.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_differentiable_HF.py` (``demonstrations\tutorial_differentiable_HF.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_doubly_stochastic.py` (``demonstrations\tutorial_doubly_stochastic.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_eqnn_force_field.py` (``demonstrations\tutorial_eqnn_force_field.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_equivariant_graph_embedding.py` (``demonstrations\tutorial_equivariant_graph_embedding.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_error_mitigation.py` (``demonstrations\tutorial_error_mitigation.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_error_prop.py` (``demonstrations\tutorial_error_prop.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_expressivity_fourier_series.py` (``demonstrations\tutorial_expressivity_fourier_series.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_falqon.py` (``demonstrations\tutorial_falqon.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_fermionic_operators.py` (``demonstrations\tutorial_fermionic_operators.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition.py` (``demonstrations\tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_game_of_surface_codes.py` (``demonstrations\tutorial_game_of_surface_codes.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_gaussian_transformation.py` (``demonstrations\tutorial_gaussian_transformation.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_general_parshift.py` (``demonstrations\tutorial_general_parshift.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_geometric_qml.py` (``demonstrations\tutorial_geometric_qml.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_givens_rotations.py` (``demonstrations\tutorial_givens_rotations.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_grovers_algorithm.py` (``demonstrations\tutorial_grovers_algorithm.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_guide_to_pennylane_knowing_qiskit.py` (``demonstrations\tutorial_guide_to_pennylane_knowing_qiskit.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_haar_measure.py` (``demonstrations\tutorial_haar_measure.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_here_comes_the_sun.py` (``demonstrations\tutorial_here_comes_the_sun.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_how_to_build_compressed_double_factorized_hamiltonians.py` (``demonstrations\tutorial_how_to_build_compressed_double_factorized_hamiltonians.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_how_to_build_spin_hamiltonians.py` (``demonstrations\tutorial_how_to_build_spin_hamiltonians.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_how_to_collect_mcm_stats.py` (``demonstrations\tutorial_how_to_collect_mcm_stats.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_how_to_create_dynamic_mcm_circuits.py` (``demonstrations\tutorial_how_to_create_dynamic_mcm_circuits.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_how_to_import_qiskit_noise_models.py` (``demonstrations\tutorial_how_to_import_qiskit_noise_models.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst.py` (``demonstrations\tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_how_to_use_noise_models.py` (``demonstrations\tutorial_how_to_use_noise_models.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_how_to_use_quantum_arithmetic_operators.py` (``demonstrations\tutorial_how_to_use_quantum_arithmetic_operators.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_how_to_use_registers.py` (``demonstrations\tutorial_how_to_use_registers.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_implicit_diff_susceptibility.py` (``demonstrations\tutorial_implicit_diff_susceptibility.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_initial_state_preparation.py` (``demonstrations\tutorial_initial_state_preparation.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_intro_amplitude_amplification.py` (``demonstrations\tutorial_intro_amplitude_amplification.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_intro_qrom.py` (``demonstrations\tutorial_intro_qrom.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_intro_qsvt.py` (``demonstrations\tutorial_intro_qsvt.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_iqp_circuit_optimization_jax.py` (``demonstrations\tutorial_iqp_circuit_optimization_jax.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_isingmodel_PyTorch.py` (``demonstrations\tutorial_isingmodel_PyTorch.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_jax_transformations.py` (``demonstrations\tutorial_jax_transformations.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_kak_decomposition.py` (``demonstrations\tutorial_kak_decomposition.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_kernel_based_training.py` (``demonstrations\tutorial_kernel_based_training.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_kernels_module.py` (``demonstrations\tutorial_kernels_module.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_lcu_blockencoding.py` (``demonstrations\tutorial_lcu_blockencoding.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_learning_dynamics_incoherently.py` (``demonstrations\tutorial_learning_dynamics_incoherently.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_learning_few_data.py` (``demonstrations\tutorial_learning_few_data.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_learning_from_experiments.py` (``demonstrations\tutorial_learning_from_experiments.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_learningshallow.py` (``demonstrations\tutorial_learningshallow.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_liealgebra.py` (``demonstrations\tutorial_liealgebra.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_liesim.py` (``demonstrations\tutorial_liesim.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_liesim_extension.py` (``demonstrations\tutorial_liesim_extension.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_local_cost_functions.py` (``demonstrations\tutorial_local_cost_functions.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_magic_state_distillation.py` (``demonstrations\tutorial_magic_state_distillation.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_mapping.py` (``demonstrations\tutorial_mapping.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_mbqc.py` (``demonstrations\tutorial_mbqc.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_mcm_introduction.py` (``demonstrations\tutorial_mcm_introduction.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_measurement_optimize.py` (``demonstrations\tutorial_measurement_optimize.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_mitigation_advantage.py` (``demonstrations\tutorial_mitigation_advantage.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_mol_geo_opt.py` (``demonstrations\tutorial_mol_geo_opt.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_mps.py` (``demonstrations\tutorial_mps.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_multiclass_classification.py` (``demonstrations\tutorial_multiclass_classification.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_neutral_atoms.py` (``demonstrations\tutorial_neutral_atoms.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_noisy_circuit_optimization.py` (``demonstrations\tutorial_noisy_circuit_optimization.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_noisy_circuits.py` (``demonstrations\tutorial_noisy_circuits.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_odegen.py` (``demonstrations\tutorial_odegen.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_optimal_control.py` (``demonstrations\tutorial_optimal_control.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_pasqal.py` (``demonstrations\tutorial_pasqal.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_period_finding.py` (``demonstrations\tutorial_period_finding.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_phase_kickback.py` (``demonstrations\tutorial_phase_kickback.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_photonics.py` (``demonstrations\tutorial_photonics.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_post-variational_quantum_neural_networks.py` (``demonstrations\tutorial_post-variational_quantum_neural_networks.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_pulse_programming101.py` (``demonstrations\tutorial_pulse_programming101.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_qaoa_intro.py` (``demonstrations\tutorial_qaoa_intro.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_qaoa_maxcut.py` (``demonstrations\tutorial_qaoa_maxcut.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_qcbm.py` (``demonstrations\tutorial_qcbm.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_qchem_external.py` (``demonstrations\tutorial_qchem_external.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_qft.py` (``demonstrations\tutorial_qft.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_qft_arithmetics.py` (``demonstrations\tutorial_qft_arithmetics.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_qgrnn.py` (``demonstrations\tutorial_qgrnn.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_qjit_compile_grovers_algorithm_with_catalyst.py` (``demonstrations\tutorial_qjit_compile_grovers_algorithm_with_catalyst.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_qnn_module_torch.py` (``demonstrations\tutorial_qnn_module_torch.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_qnn_multivariate_regression.py` (``demonstrations\tutorial_qnn_multivariate_regression.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_qpe.py` (``demonstrations\tutorial_qpe.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_qsvt_hardware.py` (``demonstrations\tutorial_qsvt_hardware.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_quantum_analytic_descent.py` (``demonstrations\tutorial_quantum_analytic_descent.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_quantum_chemistry.py` (``demonstrations\tutorial_quantum_chemistry.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_quantum_circuit_cutting.py` (``demonstrations\tutorial_quantum_circuit_cutting.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_quantum_dropout.py` (``demonstrations\tutorial_quantum_dropout.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_quantum_gans.py` (``demonstrations\tutorial_quantum_gans.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_quantum_metrology.py` (``demonstrations\tutorial_quantum_metrology.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_quantum_natural_gradient.py` (``demonstrations\tutorial_quantum_natural_gradient.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_quantum_transfer_learning.py` (``demonstrations\tutorial_quantum_transfer_learning.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_quanvolution.py` (``demonstrations\tutorial_quanvolution.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_qubit_rotation.py` (``demonstrations\tutorial_qubit_rotation.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_qubit_tapering.py` (``demonstrations\tutorial_qubit_tapering.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_qubitization.py` (``demonstrations\tutorial_qubitization.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_qutrits_bernstein_vazirani.py` (``demonstrations\tutorial_qutrits_bernstein_vazirani.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_resource_estimation.py` (``demonstrations\tutorial_resource_estimation.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_rl_pulse.py` (``demonstrations\tutorial_rl_pulse.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_rosalin.py` (``demonstrations\tutorial_rosalin.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_rotoselect.py` (``demonstrations\tutorial_rotoselect.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_sc_qubits.py` (``demonstrations\tutorial_sc_qubits.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_shadow_hamiltonian_simulation.py` (``demonstrations\tutorial_shadow_hamiltonian_simulation.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_shors_algorithm_catalyst.py` (``demonstrations\tutorial_shors_algorithm_catalyst.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_spsa.py` (``demonstrations\tutorial_spsa.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_state_preparation.py` (``demonstrations\tutorial_state_preparation.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_stochastic_parameter_shift.py` (``demonstrations\tutorial_stochastic_parameter_shift.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_teleportation.py` (``demonstrations\tutorial_teleportation.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_tensor_network_basics.py` (``demonstrations\tutorial_tensor_network_basics.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_testing_symmetry.py` (``demonstrations\tutorial_testing_symmetry.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_tn_circuits.py` (``demonstrations\tutorial_tn_circuits.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_toric_code.py` (``demonstrations\tutorial_toric_code.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_trapped_ions.py` (``demonstrations\tutorial_trapped_ions.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_unitary_designs.py` (``demonstrations\tutorial_unitary_designs.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_unitary_synthesis_kak.py` (``demonstrations\tutorial_unitary_synthesis_kak.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_univariate_qvr.py` (``demonstrations\tutorial_univariate_qvr.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_variational_classifier.py` (``demonstrations\tutorial_variational_classifier.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_vqe.py` (``demonstrations\tutorial_vqe.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_vqe_qng.py` (``demonstrations\tutorial_vqe_qng.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_vqe_spin_sectors.py` (``demonstrations\tutorial_vqe_spin_sectors.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_vqe_vqd.py` (``demonstrations\tutorial_vqe_vqd.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_vqls.py` (``demonstrations\tutorial_vqls.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_vqt.py` (``demonstrations\tutorial_vqt.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_zne_catalyst.py` (``demonstrations\tutorial_zne_catalyst.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_tutorial_zx_calculus.py` (``demonstrations\tutorial_zx_calculus.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_demos_vqe_parallel.py` (``demonstrations\vqe_parallel.py``) - - 00:00.000 - - 0.0 diff --git a/test-results/sphinx-gallery/junit.xml b/test-results/sphinx-gallery/junit.xml deleted file mode 100644 index 7fd1edd634..0000000000 --- a/test-results/sphinx-gallery/junit.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From 3bfb8b702057b48a1bb01d5143e658ca412604c6 Mon Sep 17 00:00:00 2001 From: Damian Pope Date: Thu, 10 Jul 2025 23:07:58 -0400 Subject: [PATCH 3/4] Added files for proposed new tutorial on quantum phase transitions. --- _static/authors/Damian_Pope.png | Bin 0 -> 69001 bytes _static/authors/Tirth_Shah.jpg | Bin 0 -> 180784 bytes _static/authors/damian_pope.txt | 4 + _static/authors/tirth_shah.txt | 4 + .../Fig_1_Ising_chain.png | Bin 0 -> 12045 bytes .../Fig_2_transverse_Ising.png | Bin 0 -> 18196 bytes .../Fig_3_ground_state_J_large.png | Bin 0 -> 32872 bytes .../Fig_4_ground_state_h_large.png | Bin 0 -> 36408 bytes .../Fig_5_2D_Ising_model.png | Bin 0 -> 24708 bytes ...al_quantum_phase_transitions.metadata.json | 138 ++ .../tutorial_quantum_phase_transitions.py | 1156 +++++++++++++++++ 11 files changed, 1302 insertions(+) create mode 100644 _static/authors/Damian_Pope.png create mode 100644 _static/authors/Tirth_Shah.jpg create mode 100644 _static/authors/damian_pope.txt create mode 100644 _static/authors/tirth_shah.txt create mode 100644 _static/demonstration_assets/quantum_phase_transitions/Fig_1_Ising_chain.png create mode 100644 _static/demonstration_assets/quantum_phase_transitions/Fig_2_transverse_Ising.png create mode 100644 _static/demonstration_assets/quantum_phase_transitions/Fig_3_ground_state_J_large.png create mode 100644 _static/demonstration_assets/quantum_phase_transitions/Fig_4_ground_state_h_large.png create mode 100644 _static/demonstration_assets/quantum_phase_transitions/Fig_5_2D_Ising_model.png create mode 100644 demonstrations/tutorial_quantum_phase_transitions.metadata.json create mode 100644 demonstrations/tutorial_quantum_phase_transitions.py diff --git a/_static/authors/Damian_Pope.png b/_static/authors/Damian_Pope.png new file mode 100644 index 0000000000000000000000000000000000000000..7b9e395e1e9471c4836bc65e44dc548966c8deb1 GIT binary patch literal 69001 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

*vV9&vLsh_IZvSmR3R&RPn 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

PUCV$3P1T~E59wr#L`*kQlh%m1N<<$LspoA!YH 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

3I@dXdTXRHcyY8rxGErD0Lvf81enN$d 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 z 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 LP9?&0Eu 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 DDz6v= literal 0 HcmV?d00001 diff --git a/_static/authors/damian_pope.txt b/_static/authors/damian_pope.txt new file mode 100644 index 0000000000..793dc4ee30 --- /dev/null +++ b/_static/authors/damian_pope.txt @@ -0,0 +1,4 @@ +.. bio:: Damian Pope + :photo: ../_static/authors/Damian_Pope.png + + Damian has a PhD in theoretical quantum information. He works at Perimeter Institute for Theoretical Physics, Canada in Educational Outreach. He’s interested in quantum computing education, quantum error correction, and using quantum computers in condensed matter physics, high-energy physics and cosmology. \ No newline at end of file diff --git a/_static/authors/tirth_shah.txt b/_static/authors/tirth_shah.txt new file mode 100644 index 0000000000..3139b4d7a1 --- /dev/null +++ b/_static/authors/tirth_shah.txt @@ -0,0 +1,4 @@ +.. bio:: Tirth Shah + :photo: ../_static/authors/Tirth_Shah.jpg + + Tirth is an undergraduate student at the University of Alberta studying Physics and Math. His interests include quantum computing, black hole physics, and the application of machine learning to space science. \ No newline at end of file diff --git a/_static/demonstration_assets/quantum_phase_transitions/Fig_1_Ising_chain.png b/_static/demonstration_assets/quantum_phase_transitions/Fig_1_Ising_chain.png new file mode 100644 index 0000000000000000000000000000000000000000..39abd30b8f827992d0af3f90b35bea799c563fb7 GIT binary patch literal 12045 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 rQ=i4D2Q3E2R8~OSeR+eUj=sM(soi + + + +.. raw:: html + + + +
  • + + + +the 1D quantum Ising model + + + +.. raw:: html + + + +
  • + + + +.. raw:: html + + + +
  • + + + +the 2D quantum Ising model + + + +.. raw:: html + + + +
  • + + + +.. raw:: html + + + +
  • + + + +`dynamical quantum phase transitions`_ (i.e., phase transitions in time evolution) + + + +.. raw:: html + + + +
  • + + + +.. raw:: html + + + + + + + + +A phase transition happens when there's an abrupt change in some property of a system. For example, when liquid water freezes and turns into ice. Phase transitions are important to many areas of physics including: + + + +.. raw:: html + + + +
      + + + +.. raw:: html + + + +
    • + + + +condensed matter physics, e.g., [#Vojta2002]_ + + + +.. raw:: html + + + +
    • + + + +.. raw:: html + + + +
    • + + + +cosmology, e.g., [#Mazumdar2019]_ + + + +.. raw:: html + + + +
    • + + + +.. raw:: html + + + +
    • + + + +high-energy physics, e.g., [#Mueller2023]_ + + + +.. raw:: html + + + +
    • + + + +.. raw:: html + + + +
    + + + +They're important as they can: + + + +.. raw:: html + + + +
      + + + +.. raw:: html + + + +
    • + + + +help us find new quantum states of matter + + + +.. raw:: html + + + +
    • + + + +.. raw:: html + + + +
    • + + + +help to shed light on entanglement and long-range correlations in quantum systems + + + +.. raw:: html + + + +
    • + + + +.. raw:: html + + + +
    • + + + +help us understand the behaviour of many different quantum systems at the same time (due to the + +property of universality) + + + +.. raw:: html + + + +
    • + + + +.. raw:: html + + + +
    + + + +Note: *Quantum* phase transitions are different from *classical* phase transitions. Classical phase + +transitions are caused by thermal fluctuations. Quantum phase transitions can occur at zero + +temperature and are caused by quantum fluctuations (i.e., Heisenberg's uncertainty principle). + +.. raw:: html + +
    + +Phase transitions can be hard to study analytically. Due to discontinuities, mathematical models can break + +down. Phase transitions have been widely studied numerically with classical computers. However, in + +some cases, the amount of computational resources needed is prohibitive. But there's another way + +to study phase transitions: using a quantum computer. Potentially, they can compute aspects of phase + +transitions more efficiently than any conventional technique. + + + +To date, quantum computers have been used to study quantum phase transitions related to: + + + +.. raw:: html + + + +
      + + + +.. raw:: html + + + +
    • + + + +the early universe and high-energy particle colliders [#Mueller2023]_ + + + +.. raw:: html + + + +
    • + + + +.. raw:: html + + + +
    • + + + +a topological transition in an Ising-like model [#Smith2019]_ + + + +.. raw:: html + + + +
    • + + + +.. raw:: html + + + +
    • + + + +the transverse Ising model [#Haghshenas2024]_ + + + +.. raw:: html + + + +
    • + + + +.. raw:: html + + + +
    • + + + +noisy quantum systems [#Chertkov2022]_ + + + +.. raw:: html + + + +
    • + + + +.. raw:: html + + + +
    • + + + +scalar quantum field theory [#Thompson2023]_ + + + +.. raw:: html + + + +
    • + + + +.. raw:: html + + + +
    • + + + +the evolution of the universe [#Vodeb2025]_ + + + +.. raw:: html + + + +
    • + + + +.. raw:: html + + + +
    + + + +Note: This tutorial focuses on the *quantum* Ising model. It complements existing content on this + +model: + +`3-qubit Ising model in PyTorch `_ + +`Transverse-field Ising model `_ + +`Ising Uprising Challenge `_ + +`How to Solve a QUBO problem `_ + +`Quadratic Unconstrained Binary Optimization (QUBO) `_ + +`Quantum Dataset How to build spin Hamiltonians `_ + + + +What is the Ising model? +------------------------ + + + +The simplest Ising model consists of :math:`N` qubits arranged along a line. + +""" + +############################################################################## +# .. figure:: ../_static/demonstration_assets/quantum_phase_transitions/Fig_1_Ising_chain.png +# :align: center +# :width: 50% + +###################################################################### +# Each qubit interacts with the qubits on either side of it. For example, the second qubit interacts with the first and third qubits. + +###################################################################### +# The system’s Hamiltonian is +# +# .. math:: +# \begin{equation} +# H = -J \,\, \Sigma_{i=1}^{N-1} \sigma_{z}^{(i)} \sigma^{(i+1)}_{z} +# \end{equation} +# +# where +# +# .. math:: +# \sigma_{z}^{(i)} = \left[ {\begin{array}{cc} +# 1 & 0 \\ +# 0 & -1 \\ +# \end{array} } \right] +# +# is the Pauli Z operator for the :math:`i^{th}` qubit and :math:`J` is the interaction strength +# between neighbouring qubits. +# +# The code below creates this Hamiltonian: +# +import pennylane as qml + +from pennylane import numpy as np + +N = 3 +J = 2 +wires = range(N) + +dev = qml.device("lightning.qubit", wires=N) + +coeffs = [-J] * (N - 1) + +obs = [] +for i in range(N - 1): + obs.append(qml.Z(i) @ qml.Z(i + 1)) +H = qml.Hamiltonian(coeffs, obs) + +print(f"H={H}") + +###################################################################### +# Why is the Ising Model Important? +# --------------------------------- +# At first glance, the Ising model looks like it's simple and unrealistic. However, it correctly +# models many properties of real-world magnets. Also, its simplicity allows us to actually solve it. +# You can think of the Ising model as a sandbox to play in and quickly learn about the essence of +# various complex real-world phenomena. +# +# The Ising model exhibits a wide range of interesting emergent properties, such as phase transitions. +# One calculation that gives us insight into their behaviour is finding the ground state of the Ising +# model and seeing how it changes as the interactions change. Often, we're looking to see if a phase +# transition happens. +# +# Let's look at an example. + + +###################################################################### +# Seeing Phase Transitions with Quantum Computers +# ----------------------------------------------- +# To do this, we'll use the well-known variational quantum eigensolver (VQE) algorithm to find the +# ground state. You can find an introduction to it `here `_. +# +# Let's start by finding the ground state of the Ising model for a fixed value of :math:`J`. +# We'll use the well-known Hardware Efficient Ansatz (HEA) [#Kandala2017]_ to do this. It's a +# general-purpose ansatz that efficiently represents a wide range of quantum states. It consists of: +# +# 1. Applying three single-qubit rotations to each qubit. Each one is parameterized by a different rotation angle. +# +# 2. Applying a CNOT gate to each neighbouring pair of qubits. +# +# 3. Applying three single-qubit rotations to each qubit. Again, each one is parameterized by a different rotation angle. + + + +import random + +random.seed(a=10) + +# params is an array that stores the parameter values of the statevector that we use in VQE. +# Generate some initial random angle values. +params = np.array([2 * np.pi * random.uniform(0, 1)] * (6 * N), requires_grad=True) + +# create an ansatz using the hardware efficiency ansatz (HEA) +def create_ansatz(params, N): + # STEP 1: perform single-qubit rotations on all the qubits + for i in range(N): + qml.RZ(phi=params[i], wires=i) + qml.RX(phi=params[N + i], wires=i) + qml.RZ(phi=params[2 * N + i], wires=i) + + # STEP 2: perform a CNOT gate on each pair of neighbouring qubits + for i in range(N - 1): + qml.CNOT(wires=[i, i + 1]) + + # STEP 3: perform single-qubit rotations on all the qubits + for i in range(N): + qml.RZ(phi=params[3 * N + i], wires=i) + qml.RX(phi=params[4 * N + i], wires=i) + qml.RZ(phi=params[5 * N + i], wires=i) + +@qml.qnode(dev) +def quantum_circuit(params): + # Create a quantum state using params + create_ansatz(params, N) + return qml.expval(H) + +max_iters = 200 +tolerance = 1e-04 + +# create an optimizer +opt = qml.GradientDescentOptimizer(stepsize=0.1) + +# energy is a list that stores all the estimates for the ground-state energy +energy = [] + +# execute the VQE optimization loop +for i in range(max_iters): + params, prev_energy = opt.step_and_cost(quantum_circuit, params) + energy.append(prev_energy) + + if i > 1: + if np.abs(energy[-2] - energy[-1]) < tolerance: + break + +# graph the energy as a function of the number of iterations +import matplotlib.pyplot as plt + +plt.plot(list(range(len(energy))), energy) +plt.xlabel("Iteration") +plt.ylabel("Energy") +plt.show() + +###################################################################### +# The graph above shows that the energy :math:`E` gradually decreases until it reaches :math:`E = - 4`. +# To check that this result makes sense, let's think about the Hamiltonian. Consider the first term, :math:`-2 * Z(0) @ Z(1)`. +# When the first and second qubits are in the computational basis state +# :math:`| 0 \rangle` , the product :math:`Z(0) @ Z(1)` is :math:`(+1)(+1) = +1`. Multiplying this by :math:`J = -2` +# gives an energy of -2. The second term :math:`-2 * Z(1) @ Z(2)` also gives :math:`E = -2`. Combining +# these results gives :math:`E = -2 -2 = -4`. When all the qubits are in the other basis state +# (:math:`| 1 \rangle`), we also get :math:`E = -4`. These two calculations agree with the numerical result from VQE. So far, so good. +# +# +# +# Let's now introduce an extra energy term that's proportional to the sum of all the Pauli X operators: +# +# .. math:: +# - h_{x}\Sigma_{i=1}^{N} \sigma_{x}^{(i)} +# +# If our qubits are actually spin-1/2 particles (e.g., electrons), :math:`h_{x}` is a horizontal +# magnetic field. Often, it's called a :math:`{\it transverse}` :math:`{\it field}`. +# + +############################################################################## +# .. figure:: ../_static/demonstration_assets/quantum_phase_transitions/Fig_2_transverse_Ising.png +# :align: center +# :width: 50% +# + +###################################################################### +# The system's Hamiltonian becomes +# +# .. math:: +# H = -J \,\, \Sigma_{i=1}^{N-1} \sigma_{z}^{(i)} \sigma^{(i+1)}_{z} - h_{x}\Sigma_{i=1}^{N} \sigma_{x}^{(i)} +# +# A quantum phase transitions happens when we change the ratio :math:`J/h_x`. Physically, this +# corresponds to changing the relative strengths of the coupling interaction and the horizontal +# magnetic field. When :math:`J` is much larger than :math:`h_{x}`, the ground state corresponds to +# all the spins (i.e., the qubits) being aligned vertically (parallel to the :math:`z` axis). +# + +############################################################################## +# .. figure:: ../_static/demonstration_assets/quantum_phase_transitions/Fig_3_ground_state_J_large.png +# :align: center +# :width: 75% +# + +###################################################################### +# But, when :math:`h_{x}` is much greater than :math:`J`, the ground state corresponds to +# all the spins being aligned along the :math:`x` axis parallel to the magnetic field: +# + +############################################################################## +# .. figure:: ../_static/demonstration_assets/quantum_phase_transitions/Fig_4_ground_state_h_large.png +# :align: center +# :width: 75% +# + +###################################################################### +# When :math:`J/h_{x} = 1`, the ground state suddenly switches from the first state (all +# vertical) to the second one (all horizontal). This is a quantum phase transition. The interplay +# between :math:`J` and :math:`h_{x}` is like a tug of war. The coupling constant :math:`J` tries to +# align all the qubits vertically, in the computational basis. The magnetic field :math:`h_{x}` tries +# to align them horizontally, in the Pauli X basis. Depending on value of :math:`J/h_{x}`, one of the two constants +# will dominate. +# +# To see the phase transition, let's introduce the total magnetization observable :math:`M` of all +# the qubits: +# +# .. math:: +# M =\frac{1}{N} \Sigma_{i} \sigma_{Z}^{(i)} +# +# It's just the sum of all the Pauli :math:`Z` operators, scaled by the number of qubits. For example, +# for the state :math:`| \psi \rangle = |0 \rangle |0\rangle`, +# :math:`M = \frac{1}{2} \left( 1 + 1 \right) = 1`. The total magnetization tracks the phase change as +# follows: +# +# - When :math:`h_{x} \gg J`, :math:`M = 0` as each qubit is in an equal superposition of :math:`|0 \rangle` and :math:`|1 \rangle`. +# +# - When :math:`J \gg h_{x}`, :math:`| M | = 1` as the qubits are either all in :math:`|0 \rangle` or all in :math:`|1 \rangle`. +# + +############################################################################## +# Let's now calculate :math:`M` for a range of :math:`J/h_{x}` values. +# +N = 5 +wires = range(N) + +# h_x is the strength of the transverse magnetic field +h_x = 1 + +# Vary the value of the coupling constant J in order to see a phase transition as we change J/h_x +J_list = [0.0, 0.25, 0.75, 0.9, 1.0, 1.1, 2.0, 5.0, 7.5] + +# This variable stores the values of the magnetization observable M for different values of J/h_x +magnetization_list = [] + +dev_2 = qml.device("lightning.qubit", wires=N) + +# This function prepares an estimate of the ground state & calculates its energy. +@qml.qnode(dev_2) +def quantum_circuit_2(params): + # Generate an estimate of the ground state + create_ansatz(params, N) + return qml.expval(H) + +# A function that returns the magnetization operator of N qubits. +def magnetization_op(N): + total_op = qml.PauliZ(0) + + if N > 1: + for i in range(1, N): + total_op = total_op + qml.PauliZ(i) + + return total_op / N + +#Prepare a parameterized state & return the value of the magnetization operator. +@qml.qnode(dev_2) +def calculate_magnetization(params): + create_ansatz(params, N) + return qml.expval(magnetization_op(N)) + +# Loop through all the different values of J +for i in range(len(J_list)): + + # Build the Hamiltonian + + # Add Pauli Z-Pauli Z interaction terms to the Hamiltonian + coeffs = [-J_list[i]] * (N - 1) + + obs = [] + for j in range(N - 1): + obs.append(qml.Z(j) @ qml.Z(j + 1)) + + # Add Pauli X terms to the Hamiltonian + for j in range(N): + obs.append(qml.X(j)) + coeffs.append(-h_x) + + H = qml.Hamiltonian(coeffs, obs) + + params = np.array([2 * np.pi * random.uniform(0, 1)] * (6 * N), requires_grad=True) + + max_iters = 200 + tolerance = 1e-04 + + # create an optimizer + opt = qml.MomentumOptimizer(stepsize=0.02, momentum=0.9) + + energy = [] + + # Run the VQE optimization loop + for j in range(max_iters): + params, prev_energy = opt.step_and_cost(quantum_circuit_2, params) + energy.append(prev_energy) + + if j > 1: + if np.abs(energy[-2] - energy[-1]) < tolerance: + break + + magnetization_list.append(calculate_magnetization(params)) + +############################################################################## +# Now that we've calculated :math:`M`, let's plot the results. +# + +# Plot |magnetization| versus J +plt.plot(J_list, np.abs(magnetization_list), marker="x") +plt.xlabel("J") +plt.ylabel("|Magnetization|") +plt.title("|Magnetization| vs. J for N=" + str(N)) +plt.show() + +###################################################################### +#Notice how the magnetization increases sharply around :math:`J/h_{x} = 1`. This suggests that a phase transition is happening. +#(It's also well known that a phase transition does happen at this value.) Why the graph doesn't have a sharp and discontinuous increase at +#exactly :math:`J/h_x=1`? There are two reasons: +# +#- Like all other numerical results, this result is just approximate. +#- The phase transition happens at :math:`J/h_x=1` in the asymptotic limit of large :math:`N`, i.e., as the number of qubits goes to infinity. You can see this by plotting how :math:`M` changes for three different values of :math:`N`, :math:`N = 4, 5, 6`. +# + +# magnetization values for N = 4 +magnetization_4 = [ + 0.01705303, + -0.05617393, + 0.34882499, + 0.38068118, + 0.74856645, + 0.90577316, + 0.9872206, +] +J_list_4 = [0.0, 0.25, 0.75, 0.9, 1.1, 2.0, 5.0] + +# magnetization values for N = 6 +magnetization_6 = [ + -0.11958867, + 0.00284093, + 0.01237123, + 0.00255386, + 0.81125517, + 0.92437233, + 0.99013448, +] +J_list_6 = J_list_4[:] + +# Plot |M| for multiple N values versus J +plt.plot(J_list_4, np.abs(magnetization_4), "xk-", label="N=4") +plt.plot(J_list[0:8], np.abs(magnetization_list[0:8]), "xb--", label="N=5") +plt.plot(J_list_6, np.abs(magnetization_6), "sg:", label="N=6") + +plt.xlabel("J") +plt.ylabel("|Magnetization|") +plt.title("|Magnetization| vs. J for N=4, 5, 6") +plt.legend(loc="lower right") +plt.show() + +###################################################################### +# Notice how the increase in :math:`|M|` gets steeper as we increase :math:`N`. You can +# think of this as showing that we're getting closer and closer to the asymptotic behaviour of a truly +# discontinuous phase transition. +# + +###################################################################### +# Two-dimensional Ising Model +# --------------------------- +# In the 2D quantum Ising model, the qubits are arranged in a 2D grid. +# + +###################################################################### +# .. figure:: ../_static/demonstration_assets/quantum_phase_transitions/Fig_5_2D_Ising_model.png +# :align: center +# :width: 25% + +############################################################################## +# Compared to the 1D model, it's richer, harder to solve mathematically, and harder to simulate on +# classical computers. It's also more realistic and is used by physicists to study +# low-dimensional quantum systems. In this section, we'll explore phase transitions +# in the 2D quantum Ising model. The Hamiltonian for the model is +# +# .. math:: +# H = -J \,\, \Sigma_{\langle i,j \rangle} \sigma_{z}^{(i)} \sigma^{(j)}_{z} - h_{x} \Sigma_{ i } \sigma_{x}^{(i)} +# +# The expression :math:`\langle i,j \rangle` includes every pair of neighbouring qubits in the lattice. The +# :math:`\Sigma_{i}` term sums over every qubit in the lattice. The code below creates the Hamiltonian +# using `PennyLane's spin module `_. +# + +N = 2 + +H = qml.spin.transverse_ising(lattice="square", n_cells=[N, N], h=1.0, boundary_condition=True) + +print(f"H={H}") + +###################################################################### +# Like we did for the 1D model, let's find the ground state using VQE. +# + +wires_2D = range(N**2) +dev_2D = qml.device("lightning.qubit", wires=wires_2D) + +random.seed(a=10) + +# generate random parameter values for the initial statevector +params = np.array([2 * np.pi * random.uniform(0, 1)] * (6 * N), requires_grad=True) + +@qml.qnode(dev_2D) +def quantum_circuit_2D(params): + create_ansatz(params, N) + return qml.expval(H) + +max_iters = 500 +tolerance = 3e-04 + +# create an optimizer +opt = qml.GradientDescentOptimizer(stepsize=0.015) + +energy = [] + +# execute the optimization loop +for i in range(max_iters): + params, prev_energy = opt.step_and_cost(quantum_circuit_2D, params) + energy.append(prev_energy) + + if i > 1: + if np.abs(energy[-2] - energy[-1]) < tolerance: + break + +# print out the results +plt.plot(list(range(len(energy))), energy) +plt.xlabel("Iteration") +plt.ylabel("Energy") +plt.show() + +############################################################################## +# The energy in the graph approaches -4.5, which makes sense. The Hamiltonian has four two-qubit interaction terms and the +# smallest that each one can be is -1. So, the ground-state energy must be less than -4. Let's vary the ratio :math:`J/h_{x}` again and calculate the +# magnetization :math:`M` each time. Finally, let's plot the results and see if there's a quantum +# phase transition. +# + +N = 3 +dev_2D_varying_J = qml.device("lightning.qubit", wires=N**2) + +# strength of transverse magnetic field +h_x = 1 + +# Vary J in order to see a phase transition in the magnetization as we change J/h_x +J_list = [0.035, 0.05, 0.1, 0.25, 0.375, 0.5, 0.75, 1.0, 5, 10] + +magnetization_list = [] + +# Prepare a parameterized state & calculate the value of the magnetization operator. +@qml.qnode(dev_2D_varying_J) +def calculate_magnetization_2D(params): + create_ansatz(params, N) + return qml.expval(magnetization_op(N)) + +@qml.qnode(dev_2D_varying_J) +def quantum_circuit_2D_varying_J(params): + create_ansatz(params, N) + return qml.expval(H) + +# Loop through all values of J +for i in range(len(J_list)): + H = qml.spin.transverse_ising( + lattice="square", coupling=J_list[i], n_cells=[N, N], boundary_condition=True + ) + #Set the initial values of the rotation angle parameters. + #The values below were chosen as, through trial and error, we discovered that they worked well. + params = np.zeros(6 * N, requires_grad=True) + for i in range(N): + params[i] = 0 + params[N + i] = np.pi / 2 + params[2 * N + i] = np.pi / 2 + + max_iters = 500 + + # create an optimizer + opt = qml.MomentumOptimizer(stepsize=0.03, momentum=0.9) + + energy = [] + + # execute the optimization loop + for j in range(max_iters): + params, prev_energy = opt.step_and_cost(quantum_circuit_2D_varying_J, params) + energy.append(prev_energy) + + if j > 1: + if np.abs(energy[-2] - energy[-1]) < tolerance: + break + + magnetization_list.append(calculate_magnetization_2D(params)) + +###################################################################### +# Let's plot the results. + +# Plot |magnetization| versus J +plt.plot(J_list, np.abs(magnetization_list), marker="x") +plt.xlabel("J") +plt.ylabel("|Magnetization|") +plt.title("|Magnetization| vs. J for N=" + str(N)) +plt.show() + +###################################################################### +# From the graph, it's unclear if there's a phase transition. Looking at it, multiple data points are +# bunched up on the left. To spread them out, let's change the scale by ignoring the last two +# points. +# + +plt.plot(J_list[0:8], np.abs(magnetization_list[0:8]), marker="x") +plt.xlabel("J") +plt.ylabel("|Magnetization|") +plt.title("|Magnetization| vs. J for N=" + str(N)) +plt.show() + +###################################################################### +# Like in the 1D case, the magnetization displays a rapid increase. This is consistent with a phase change +# but it's not conclusive as the increase is somewhat gradual. This is because :math:`N` is so small. +# Note that the result is consistent with where the phase change is known to occur [#Blote2002]_, [#Hashizume2022]_. +# + +############################################################################## +#.. _dynamical quantum phase transitions: +#Time Evolution & Dynamical Phase Transitions +#-------------------------------------------- +# +#Another important aspect of quantum systems is how they evolve over time. +#Sometimes, this evolution is hard to simulate on classical computers. So, researchers are +#interested in modelling it on quantum computers. Occasionally, +#some property of a quantum system changes abruptly. This is called a *dynamical quantum +#phase transition*: a phase transition that happens during the time evolution +#of a quantum system [#Heyl2013]_. +# +#To evolve the Ising model in time, we'll use the well-known Suzuki-Trotter product approximation. The code below does this. + +import math + +N = 5 +wires = range(N) + +# create the Hamiltonian for a 1D Ising model with transverse & longitudinal magnetic fields +# we do this to copy what was done in Reference 13: https://arxiv.org/abs/2008.04894 +obs = [] +for j in range(N - 1): + obs.append(qml.Z(j) @ qml.Z(j + 1)) + +# add Pauli X terms to Hamiltonian (transverse field) +for j in range(N): + obs.append(qml.X(j)) + +# add Pauli Z terms to Hamiltonian (longitudinal field) +for j in range(N): + obs.append(qml.Z(j)) + +dev = qml.device("lightning.qubit", wires=N) + +J = -0.1 + +# strength of transverse field interaction +h_x = 1 + +# strength of longitudinal field interaction +h_z = -0.15 + +J_coeffs = [-J] * (N - 1) + +X_coeffs = [h_x] * N + +Z_coeffs = [h_z] * N + +coeffs = J_coeffs + X_coeffs + Z_coeffs + +H = qml.Hamiltonian(coeffs, obs) + +# create the circuit that evolves the system in time +@qml.qnode(dev) +def time_evolution_circuit(H, T): + #Evolve the system via a sequence of short approximate Trotter time steps + #https://docs.pennylane.ai/en/stable/code/api/pennylane.TrotterProduct.html + qml.TrotterProduct(H, time=T, n=math.ceil(T / 0.1)+1, order=2) + + # return the final probabilities + return qml.probs(wires=range(N)) + + +############################################################################## +#To see if a dynamical phase transition happens, let's consider a observable called the :math:`\it{rate \; function}` +#:math:`\gamma`. It depends on the overlap between the quantum state that we start with and the final state at +#some time :math:`t`. More specifically, +# +# .. math:: +# \gamma = -\frac{1}{N} \log_{e} (|G|^{2}) +# +#where :math:`G = \langle \psi_{i} | \psi_{f}\rangle`, where :math:`| \psi_{i}\rangle` and :math:`| \psi_{f} \rangle` are the initial and final states +#respectively. As the system evolves, we'll keep calculating :math:`\gamma`. If it changes discontinuously, +#then a dynamical phase transition has happened. +# +# +#The function below calculates :math:`\gamma` at time :math:`T`. +def rate_function(H, T, N): + probability_list = time_evolution_circuit(H, T) + mag_G_squared = probability_list[0] + return -1 / N * np.log(mag_G_squared) + +###################################################################### +#Let's now calculate :math:`\gamma` at different times to see how it evolves. Finally, let's graph the value of :math:`\gamma` versus time to see if a dynamical phase transition happens. +# + +rate_function_list = [] + +# time step size for time evolution +deltaT = 0.05 + +num_time_steps = 50 + +for i in range(num_time_steps): + rate_function_list.append(rate_function(H, i * deltaT, N)) + +plt.plot(np.linspace(0, deltaT * (num_time_steps-1), num_time_steps), rate_function_list) +plt.xlabel("time") +plt.ylabel(r"Rate function, $\lambda$") +plt.title("Rate Function versus time") +plt.legend(["N=" + str(N)]) +plt.show() + +############################################################################## +# The sharp change in :math:`\Gamma` at :math:`t = 1.5` suggests that a dynamical phase transition has happened. This +# conclusion is supported by classical numerical simulations that show a phase +# transition at the same time [#Nicola2021]_. +# + +############################################################################## +#Summary +#------- +# In this demo, we have shown how you can use quantum computers to simulate quantum phase +# transitions in the 1D and 2D quantum Ising models. We've also shown how to use quantum computers to see dynamical quantum phase transitions. +# +#Acknowledgement +#--------------- +#Damian Pope would like to thank Associate Professor Matthew Johnson (Perimeter Institute for Theoretical Physics and +#York University) for insightful discussions on quantum phase transitions in cosmology and quantum computing. + +###################################################################### +#References +#------------ +# +# .. [#Vojta2002] +# T. Vojta, in K.H. Hoffmann and M. Schreiber (Eds): Computational Statistical Physics, Springer, Berlin (2002) +# +# .. [#Mazumdar2019] +# +# Anupam Mazumdar and Graham White. "Cosmic phase transitions: their applications and experimental signatures" Rep. Prog. Phys. 82, 076901, 2019 +# +# +# .. [#Mueller2023] +# +# Niklas Mueller et al. "Quantum Computation of Dynamical Quantum Phase Transitions and Entanglement Tomography in a Lattice Gauge Theory" PRX Quantum 4, 030323, 2023 +# +# +# .. [#Smith2019] +# +# Adam Smith, Bernhard Jobst, Andrew G. Green, and Frank Pollmann. "Crossing a topological phase transition with a quantum computer" `arXiv:1910.05351 [cond-mat.str-el] `__, 2019 +# +# +# .. [#Haghshenas2024] +# +# Reza Haghshenas et al. "Probing critical states of matter on a digital quantum computer" `arXiv:2305.01650 [quant-ph] `__, 2024 +# +# +# +# .. [#Chertkov2022] +# +# Eli Chertkov, et al., "Characterizing a non-equilibrium phase transition on a quantum computer", `arXiv:2209.12889 [quant-ph] `__, 2022 +# +# +# +# .. [#Thompson2023] +# +# Shane Thompson and George Siopsis. "Quantum Computation of Phase Transition in Interacting Scalar Quantum Field Theory" `arXiv:2303.02425 [quant-ph] `__, 2023 +# +# +# .. [#Vodeb2025] +# +# Jaka Vodeb et al., "Stirring the false vacuum via interacting quantized bubbles on a 5,564-qubit quantum annealer", Nature Physics, 21, 386, 2025 `https://www.nature.com/articles/s41567-024-02765-w `__ +# +# +# .. [#Kandala2017] +# +# Abhinav Kandala et al., "Hardware-efficient Variational Quantum Eigensolver for Small Molecules and Quantum Magnets", `arXiv:1704.05018 [quant-ph] `__ 2017 +# +# +# .. [#Blote2002] +# +# Henk W. J. Blöte and Youjin Deng. "Cluster Monte Carlo simulation of the transverse Ising model", Phys. Rev. E 66, 066110, 2002. (See Table II, row labelled 'square lattice'); +# +# +# .. [#Hashizume2022] +# +# Tomohiro Hashizume, Ian P. McCulloch, and Jad C. Halimeh. "Dynamical phase transitions in the two-dimensional transverse-field Ising model", Phys. Rev. Research 4, 013250, 2022 (See Figure 1 and Section II) +# +# .. [#Heyl2013] +# +# M. Heyl, A. Polkovnikov, S. Kehrein. "Dynamical Quantum Phase Transitions in the Transverse-Field Ising Model", Phys. Rev. Lett. 110 135704 (2013) +# +# .. [#Nicola2021] +# +# S. De Nicola , A. A. Michailidis , M. Serbyn. "Entanglement View of Dynamical Quantum Phase Transitions", Phys. Rev. Lett. 126 040602 (2021), Figure 1 (d) +# +############################################################################## +# About the author +# ---------------- +# + \ No newline at end of file From 51e3c25806c6a7f12b0e7e2ac0ca9b775900efa5 Mon Sep 17 00:00:00 2001 From: Damian Pope Date: Fri, 11 Jul 2025 23:19:47 -0400 Subject: [PATCH 4/4] Fixed invalid lines in metadata file. --- ...al_quantum_phase_transitions.metadata.json | 47 +++++++------------ 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/demonstrations/tutorial_quantum_phase_transitions.metadata.json b/demonstrations/tutorial_quantum_phase_transitions.metadata.json index 5dd1804b01..a4bcd44643 100644 --- a/demonstrations/tutorial_quantum_phase_transitions.metadata.json +++ b/demonstrations/tutorial_quantum_phase_transitions.metadata.json @@ -8,8 +8,8 @@ "username": "Tirth_Shah" } ], - "dateOfPublication": "", - "dateOfLastModification": "", + "dateOfPublication": "2025-07-11T00:00:00+00:00", + "dateOfLastModification": "2025-07-11T00:00:00+00:00", "categories": ["Optimization","Quantum Machine Learning"], "tags": ["quantum phase transition", "Ising model"], "previewImages": [ @@ -27,101 +27,90 @@ "references": [ { "id": "Vojta2002", - "type": "chapter-book", + "type": "book", "title": "Computational Statistical Physics", "authors": "Thomas Vojta", "year": "2002", - "doi": "", "url": "https://link.springer.com/book/10.1007/978-3-662-04804-7" }, { "id": "Mazumdar2019", - "type": "article-journal", + "type": "article", "title": "Cosmic phase transitions: their applications and experimental signatures", - "authors": ["Anupam Mazumdar","Graham White"], + "authors": "Anupam Mazumdar, Graham White", "year": "2019", - "doi": "", "url": "https://iopscience.iop.org/article/10.1088/1361-6633/ab1f55" }, { "id": "Mueller2023", - "type": "article-journal", + "type": "article", "title": "Quantum Computation of Dynamical Quantum Phase Transitions and Entanglement Tomography in a Lattice Gauge Theory", - "authors": ["Niklas Mueller","Joseph A. Carolan","Andrew Connelly","Zohreh Davoudi","Eugene F. Dumitrescu","Kübra Yeter-Aydeniz"], + "authors": "Niklas Mueller, Joseph A. Carolan, Andrew Connelly, Zohreh Davoudi, Eugene F. Dumitrescu, Kübra Yeter-Aydeniz", "year": "2023", - "doi": "", "url": "https://journals.aps.org/prxquantum/abstract/10.1103/PRXQuantum.4.030323" }, { "id": "Smith2019", "type": "preprint", "title": "Quantum Computation of Dynamical Quantum Phase Transitions and Entanglement Tomography in a Lattice Gauge Theory", - "authors": ["Adam Smith","Bernhard Jobst","Andrew G. Green","Frank Pollmann"], + "authors": "Adam Smith, Bernhard Jobst, Andrew G. Green, Frank Pollmann", "year": "2019", - "doi": "", "url": "https://arxiv.org/abs/1910.05351" }, { "id": "Haghshenas2024", "type": "preprint", "title": "Probing critical states of matter on a digital quantum computer", - "authors": ["Reza Haghshenas","Eli Chertkov","Matthew DeCross","Thomas M. Gatterman","Justin A. Gerber","Kevin Gilmore","Dan Gresh","Nathan Hewitt","Chandler V. Horst","Mitchell Matheny","Tanner Mengle","Brian Neyenhuis","David Hayes","Michael Foss-Feig"], + "authors": "Reza Haghshenas, Eli Chertkov, Matthew DeCross, Thomas M. Gatterman, Justin A. Gerber, Kevin Gilmore, Dan Gresh, Nathan Hewitt, Chandler V. Horst, Mitchell Matheny, Tanner Mengle, Brian Neyenhuis, David Hayes, Michael Foss-Feig", "year": "2024", - "doi": "", "url": "https://arxiv.org/abs/2305.01650" }, { "id": "Chertkov2022", "type": "preprint", "title": "Characterizing a non-equilibrium phase transition on a quantum computer", - "authors": ["Eli Chertkov","Zihan Cheng","Andrew C. Potter","Sarang Gopalakrishnan","Thomas M. Gatterman", "Justin A. Gerber","Kevin Gilmore","Dan Gresh","Alex Hall","Aaron Hankin","Mitchell Matheny","Tanner Mengle","David Hayes","Brian Neyenhuis","Russell Stutz","Michael Foss-Feig"], + "authors": "Eli Chertkov, Zihan Cheng, Andrew C. Potter, Sarang Gopalakrishnan, Thomas M. Gatterman, Justin A. Gerber, Kevin Gilmore, Dan Gresh, Alex Hall, Aaron Hankin, Mitchell Matheny, Tanner Mengle, David Hayes, Brian Neyenhuis, Russell Stutz, Michael Foss-Feig", "year": "2022", - "doi": "", "url": "https://arxiv.org/abs/2305.01650" }, { "id": "Thompson2023", "type": "preprint", "title": "Quantum Computation of Phase Transition in Interacting Scalar Quantum Field Theory", - "authors": ["Shane Thompson","George Siopsis"], + "authors": "Shane Thompson, George Siopsis", "year": "2023", - "doi": "", "url": "https://arxiv.org/abs/2303.02425" }, { "id": "Vodeb2025", - "type": "article-journal", + "type": "article", "title": "Stirring the false vacuum via interacting quantized bubbles on a 5,564-qubit quantum annealer", - "authors": ["Jaka Vodeb","Jean-Yves Desaules","Andrew Hallam","Andrea Rava","Gregor Humar","Dennis Willsch","Fengping Jin","Madita Willsch","Kristel Michielsen","Zlatko Papić"], + "authors": "Jaka Vodeb, Jean-Yves Desaules, Andrew Hallam, Andrea Rava, Gregor Humar, Dennis Willsch, Fengping Jin, Madita Willsch, Kristel Michielsen, Zlatko Papić", "year": "2024", - "doi": "", "url": "https://www.nature.com/articles/s41567-024-02765-w" }, { "id": "Kandala2017", "type": "preprint", "title": "Hardware-efficient Variational Quantum Eigensolver for Small Molecules and Quantum Magnets", - "authors": ["Abhinav Kandala","Antonio Mezzacapo","Kristan Temme","Maika Takita","Markus Brink","Jerry M. Chow","Jay M. Gambetta"], + "authors": "Abhinav Kandala, Antonio Mezzacapo, Kristan Temme, Maika Takita, Markus Brink, Jerry M. Chow, Jay M. Gambetta", "year": "2017", - "doi": "", "url": "https://arxiv.org/abs/1704.05018" }, { "id": "Blote2002", - "type": "article-journal", + "type": "article", "title": "Cluster Monte Carlo simulation of the transverse Ising model", - "authors": ["Henk W. J. Blote","Youjin Deng"], + "authors": "Henk W. J. Blote, Youjin Deng", "year": "2002", - "doi": "", "url": "https://journals.aps.org/pre/abstract/10.1103/PhysRevE.66.066110" }, { "id": "Hashizume2022", - "type": "article-journal", + "type": "article", "title": "Dynamical phase transitions in the two-dimensional transverse-field Ising model", - "authors": ["Tomohiro Hashizume","Ian P. McCulloch","Jad C. Halimeh"], + "authors": "Tomohiro Hashizume, Ian P. McCulloch, Jad C. Halimeh", "year": "2022", - "doi": "", "url": "https://journals.aps.org/prresearch/abstract/10.1103/PhysRevResearch.4.013250" } ],