diff --git a/conf.py b/conf.py index 68e7bf060e..61b3d032a5 100644 --- a/conf.py +++ b/conf.py @@ -110,9 +110,6 @@ # Raise PennyLane deprecation warnings as errors warnings.filterwarnings("error", category=PennyLaneDeprecationWarning) -warnings.filterwarnings( - "ignore", message="Device will no longer be accessible", category=PennyLaneDeprecationWarning -) # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] diff --git a/demonstrations/ensemble_multi_qpu.metadata.json b/demonstrations/ensemble_multi_qpu.metadata.json index 2163c50b80..2e1918c1d5 100644 --- a/demonstrations/ensemble_multi_qpu.metadata.json +++ b/demonstrations/ensemble_multi_qpu.metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2020-02-14T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2025-01-14T00:00:00+00:00", "categories": [ "Quantum Machine Learning" ], diff --git a/demonstrations/ensemble_multi_qpu.py b/demonstrations/ensemble_multi_qpu.py index 55b83eebd5..50709c2cbd 100644 --- a/demonstrations/ensemble_multi_qpu.py +++ b/demonstrations/ensemble_multi_qpu.py @@ -18,10 +18,10 @@ classification problem. .. warning:: - This demo does not work with the latest version of Qiskit or the Pennylane-Qiskit plugin. - 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. + 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 @@ -198,9 +198,9 @@ def plot_points(x_train, y_train, x_test, y_test): ############################################################################## # .. 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 Q Experience can -# swap ``qiskit.aer`` for ``qiskit.ibmq`` and specify their chosen backend (see `here -# `__). +# 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: @@ -578,4 +578,4 @@ def plot_points_prediction(x, y, p, title): ############################################################################## # About the author # ---------------- -# .. include:: ../_static/authors/thomas_bromley.txt \ No newline at end of file +# .. include:: ../_static/authors/thomas_bromley.txt diff --git a/demonstrations/getting_started_with_hybrid_jobs.metadata.json b/demonstrations/getting_started_with_hybrid_jobs.metadata.json index 3000431cf0..b7b67ddfe9 100644 --- a/demonstrations/getting_started_with_hybrid_jobs.metadata.json +++ b/demonstrations/getting_started_with_hybrid_jobs.metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2023-10-16T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "dateOfLastModification": "2025-01-14T00:00:00+00:00", "categories": [ "Devices and Performance" ], diff --git a/demonstrations/getting_started_with_hybrid_jobs.py b/demonstrations/getting_started_with_hybrid_jobs.py index 9dc028badc..aaf2a22cbe 100644 --- a/demonstrations/getting_started_with_hybrid_jobs.py +++ b/demonstrations/getting_started_with_hybrid_jobs.py @@ -304,6 +304,10 @@ def circuit(params): # .. note:: # AWS devices must be declared within the body of the decorated function. # +# .. warning:: +# The Rigetti device used in this demo, AspenM3, has been retired. For an updated list of available hardware through +# Amazon Braket, please consult the `supported regions and devices documentation `__. The general steps outlined below still hold regardless of the choice of device, though. +# from braket.devices import Devices diff --git a/demonstrations/how_to_use_qiskit1_with_pennylane.metadata.json b/demonstrations/how_to_use_qiskit1_with_pennylane.metadata.json index 3c9d27a9ba..c5a0361540 100644 --- a/demonstrations/how_to_use_qiskit1_with_pennylane.metadata.json +++ b/demonstrations/how_to_use_qiskit1_with_pennylane.metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2024-07-02T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2025-01-08T00:00:00+00:00", "categories": [ "Quantum Computing", "How-to" diff --git a/demonstrations/how_to_use_qiskit1_with_pennylane.py b/demonstrations/how_to_use_qiskit1_with_pennylane.py index 1a6e0ca61a..83d39d546f 100644 --- a/demonstrations/how_to_use_qiskit1_with_pennylane.py +++ b/demonstrations/how_to_use_qiskit1_with_pennylane.py @@ -242,7 +242,7 @@ def qiskit_GHZ_circuit(n): # pl_circuit = qml.QNode(pl_qfunc, device=qml.device("lightning.qubit", wires=n)) -pl_circuit() +print(pl_circuit()) ###################################################################### # .. rst-class:: sphx-glr-script-out @@ -263,7 +263,7 @@ def qiskit_GHZ_circuit(n): pl_qfunc = qml.from_qiskit(qc, measurements=measurements) pl_circuit = qml.QNode(pl_qfunc, device=qml.device("default.qubit", wires=n)) -pl_circuit(shots=5) +print(pl_circuit(shots=5)) ###################################################################### # .. rst-class:: sphx-glr-script-out @@ -304,6 +304,7 @@ def qiskit_GHZ_circuit(n): # from qiskit.circuit import ParameterVector, Parameter +from matplotlib import pyplot as plt n = 3 @@ -316,6 +317,8 @@ def qiskit_GHZ_circuit(n): qc.ry(angle2, [2]) qc.draw("mpl") +plt.show() + ###################################################################### # .. rst-class:: image-no-text-wrap @@ -349,7 +352,8 @@ def differentiable_circuit(phis, theta): theta = np.array([0.19]) print(differentiable_circuit(phis, theta)) -print(qml.draw_mpl(differentiable_circuit)(phis, theta)) +qml.draw_mpl(differentiable_circuit, style="pennylane")(phis, theta) +plt.show() ###################################################################### # .. rst-class:: sphx-glr-script-out diff --git a/demonstrations/ibm_pennylane.metadata.json b/demonstrations/ibm_pennylane.metadata.json index 351c3d6d25..95825f1de7 100644 --- a/demonstrations/ibm_pennylane.metadata.json +++ b/demonstrations/ibm_pennylane.metadata.json @@ -12,7 +12,7 @@ } ], "dateOfPublication": "2023-06-20T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "dateOfLastModification": "2025-01-10T00:00:00+00:00", "categories": [ "Devices and Performance" ], diff --git a/demonstrations/ibm_pennylane.py b/demonstrations/ibm_pennylane.py index eb1101d4bc..90b3fa769e 100644 --- a/demonstrations/ibm_pennylane.py +++ b/demonstrations/ibm_pennylane.py @@ -124,6 +124,16 @@ # # First, we set up our problem as usual, and then retrieve a program ID from IBM, which gives us a # place to upload our job: +# +# .. warning:: +# +# By default, this demo uses the online simulator (`ibmq_qasm_simulator`), which is free at the +# time of writing. Please note that IBM Quantum's policies may change, and simulators could become paid services. +# Always verify current pricing and access policies on the IBM Quantum platform. +# +# This demo can also run on quantum hardware by updating the backend variable accordingly. Be aware, that access +# to IBM Quantum hardware is not free and may result in substantial costs. Ensure you are aware of these costs +# and comfortable with them before proceeding. from pennylane import numpy as pnp from qiskit_ibm_runtime import QiskitRuntimeService @@ -135,13 +145,16 @@ H = dataset.hamiltonian qubits = 4 +# Initialize QiskitRuntimeService service = QiskitRuntimeService() -# Gets a 127 qubit device from IBM -backend = service.least_busy(n_qubits=127, simulator=False, operational=True) + +# Use the `ibmq_qasm_simulator` available on IBM Cloud +backend = service.backend("ibmq_qasm_simulator") try: - # Although we only need 4 qubits, our device has 127 qubits, therefore we initialize with wires=127 - dev = qml.device("qiskit.remote", wires=127, backend=backend) + # Our device supports a maximum of 31 qubits + NUM_QUBITS_SUPPORTED = 31 + dev = qml.device("qiskit.remote", wires=NUM_QUBITS_SUPPORTED, backend=backend) except Exception as e: print(e) diff --git a/demonstrations/learning2learn.metadata.json b/demonstrations/learning2learn.metadata.json index f6b067357f..c77a2eb0f2 100644 --- a/demonstrations/learning2learn.metadata.json +++ b/demonstrations/learning2learn.metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2021-03-02T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "dateOfLastModification": "2025-01-07T00:00:00+00:00", "categories": [ "Quantum Machine Learning" ], diff --git a/demonstrations/learning2learn.py b/demonstrations/learning2learn.py index effa725a2e..7e3ff9de66 100644 --- a/demonstrations/learning2learn.py +++ b/demonstrations/learning2learn.py @@ -13,6 +13,9 @@ *Author: Stefano Mangini — Posted: 02 March 2021. Last updated: 15 September 2021.* +.. warning:: + This demo is only compatible with TensorFlow version ``2.9`` or below. + Otherwise, the output of some cells and plots may differ. In this demo we recreate the architecture proposed in *Learning to learn with quantum neural networks via diff --git a/demonstrations/ml_classical_shadows.metadata.json b/demonstrations/ml_classical_shadows.metadata.json index 3b515b5fc0..5bcf2a0116 100644 --- a/demonstrations/ml_classical_shadows.metadata.json +++ b/demonstrations/ml_classical_shadows.metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2022-05-02T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2025-01-10T00:00:00+00:00", "categories": [ "Quantum Machine Learning" ], diff --git a/demonstrations/ml_classical_shadows.py b/demonstrations/ml_classical_shadows.py index 4d290982a4..66bca15783 100644 --- a/demonstrations/ml_classical_shadows.py +++ b/demonstrations/ml_classical_shadows.py @@ -410,8 +410,8 @@ def estimate_shadow_obs(shadow, observable, k=10): target_obs = np.array([map_name_to_int[observable.name]]) target_locs = np.array([observable.wires[0]]) else: - target_obs = np.array([map_name_to_int[o.name] for o in observable.obs]) - target_locs = np.array([o.wires[0] for o in observable.obs]) + target_obs = np.array([map_name_to_int[o.name] for o in observable.operands]) + target_locs = np.array([o.wires[0] for o in observable.operands]) # perform median of means to return the result means = [] @@ -859,4 +859,3 @@ def fit_predict_data(cij, kernel, opt="linear"): # # About the author # ---------------- - diff --git a/demonstrations/oqc_pulse.metadata.json b/demonstrations/oqc_pulse.metadata.json index 1c964a11be..bd26d757a4 100644 --- a/demonstrations/oqc_pulse.metadata.json +++ b/demonstrations/oqc_pulse.metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2023-10-30T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2025-01-14T00:00:00+00:00", "categories": [ "Quantum Computing" ], diff --git a/demonstrations/oqc_pulse.py b/demonstrations/oqc_pulse.py index 5c3bd0ab50..d5e8d68bf0 100644 --- a/demonstrations/oqc_pulse.py +++ b/demonstrations/oqc_pulse.py @@ -11,6 +11,10 @@ *Author: Korbinian Kottmann — Posted: 30 October 2023.* +.. warning:: + The OQC Lucy device is no longer available on Amazon Braket. As there is no alternative at this time, this + demo is now intended for educational purposes only. + Pulse-level access to quantum computers offers many interesting new avenues in quantum optimal control, variational quantum algorithms and device-aware algorithm design. We now have the possibility to run hardware-level circuits combined with standard gates on a diff --git a/demonstrations/pytorch_noise.metadata.json b/demonstrations/pytorch_noise.metadata.json index 3bb21e9b33..97f6fc7129 100644 --- a/demonstrations/pytorch_noise.metadata.json +++ b/demonstrations/pytorch_noise.metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2019-10-11T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2025-01-14T00:00:00+00:00", "categories": [ "Devices and Performance" ], diff --git a/demonstrations/pytorch_noise.py b/demonstrations/pytorch_noise.py index 1593bc3f90..6951abc037 100644 --- a/demonstrations/pytorch_noise.py +++ b/demonstrations/pytorch_noise.py @@ -15,6 +15,10 @@ *Author: Josh Izaac — Posted: 11 October 2019. Last updated: 9 November 2022.* +.. warning:: + This demo is only compatible with PennyLane v0.40 or below and Braket v1.31.0. To use Rigetti hardware with newer versions of PennyLane please use the `PennyLane-Braket plugin `__ instead. + + Let's revisit the original :ref:`qubit rotation ` tutorial, but instead of using the default NumPy/autograd QNode interface, we'll use the :doc:`introduction/interfaces/torch`. We'll also replace the ``default.qubit`` device with a noisy ``rigetti.qvm`` @@ -251,4 +255,4 @@ def cost(phi, theta, step): # # About the author # ---------------- -# .. include:: ../_static/authors/josh_izaac.txt \ No newline at end of file +# .. include:: ../_static/authors/josh_izaac.txt diff --git a/demonstrations/qnspsa.metadata.json b/demonstrations/qnspsa.metadata.json index 2679e5668d..c854871045 100644 --- a/demonstrations/qnspsa.metadata.json +++ b/demonstrations/qnspsa.metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2022-07-18T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "dateOfLastModification": "2025-01-09T00:00:00+00:00", "categories": [ "Optimization" ], diff --git a/demonstrations/qnspsa.py b/demonstrations/qnspsa.py index d859f176e0..0ab0624564 100644 --- a/demonstrations/qnspsa.py +++ b/demonstrations/qnspsa.py @@ -356,26 +356,12 @@ def get_grad(params_curr): # 0 (minimum overlap) and 1 (perfect overlap). # -from copy import copy - - -def get_operations(qnode, params): - qnode.construct([params], {}) - return qnode.tape.operations - - def get_overlap_tape(qnode, params1, params2): - op_forward = get_operations(qnode, params1) - op_inv = get_operations(qnode, params2) - - with qml.tape.QuantumTape() as tape: - for op in op_forward: - qml.apply(op) - for op in reversed(op_inv): - qml.adjoint(copy(op)) - qml.probs(wires=qnode.tape.wires.labels) - return tape + tape_forward = qml.workflow.construct_tape(qnode)(*params1) + tape_inv = qml.workflow.construct_tape(qnode)(*params2) + ops = tape_forward.operations + list(qml.adjoint(op) for op in reversed(tape_inv.operations)) + return qml.tape.QuantumTape(ops, [qml.probs(wires=tape_forward.wires)]) def get_state_overlap(tape): return qml.execute([tape], dev, None)[0][0] @@ -833,10 +819,8 @@ def __get_spsa_grad_tapes(self, cost, params): # used to estimate the gradient per optimization step. The sampled # direction is of the shape of the input parameter. direction = self.__get_perturbation_direction(params) - cost.construct([params + self.finite_diff_step * direction], {}) - tape_forward = cost.tape.copy(copy_operations=True) - cost.construct([params - self.finite_diff_step * direction], {}) - tape_backward = cost.tape.copy(copy_operations=True) + tape_forward = qml.workflow.construct_tape(cost)(*[params + self.finite_diff_step * direction]) + tape_backward = qml.workflow.construct_tape(cost)(*[params - self.finite_diff_step * direction]) return [tape_forward, tape_backward], direction def __update_tensor(self, tensor_raw): @@ -864,21 +848,7 @@ def __get_tensor_tapes(self, cost, params): return tapes, dir_vecs def __get_overlap_tape(self, cost, params1, params2): - op_forward = self.__get_operations(cost, params1) - op_inv = self.__get_operations(cost, params2) - - with qml.tape.QuantumTape() as tape: - for op in op_forward: - qml.apply(op) - for op in reversed(op_inv): - qml.adjoint(copy(op)) - qml.probs(wires=cost.tape.wires.labels) - return tape - - def __get_operations(self, cost, params): - # Given a QNode, returns the list of operations before the measurement. - cost.construct([params], {}) - return cost.tape.operations + return get_overlap_tape(cost, params1, params2) def __get_tensor_moving_avg(self, metric_tensor): # For numerical stability: averaging on the Fubini-Study metric tensor. @@ -893,10 +863,8 @@ def __regularize_tensor(self, metric_tensor): def __apply_blocking(self, cost, params_curr, params_next): # For numerical stability: apply the blocking condition on the parameter update. - cost.construct([params_curr], {}) - tape_loss_curr = cost.tape.copy(copy_operations=True) - cost.construct([params_next], {}) - tape_loss_next = cost.tape.copy(copy_operations=True) + tape_loss_curr = qml.workflow.construct_tape(cost)(*[params_curr]) + tape_loss_next = qml.workflow.construct_tape(cost)(*[params_next]) loss_curr, loss_next = qml.execute([tape_loss_curr, tape_loss_next], cost.device, None) # self.k has been updated earlier. @@ -945,11 +913,14 @@ def __apply_blocking(self, cost, params_curr, params_next): # The optimizer performs reasonably well: the loss drops over optimization # steps and converges finally. We then reproduce the benchmarking test # between the gradient descent, quantum natural gradient descent, SPSA and -# QN-SPSA in Fig. 1(b) of reference [#Gacon2021]_ with the following job. You -# can find a more detailed version of the example in this -# `notebook `__, -# with its dependencies in the `source_scripts` -# `folder `__. +# QN-SPSA in Fig. 1(b) of reference [#Gacon2021]_ with the following job. +# +# .. warning:: +# The code required to plot the results of the example below is not provided, but a similar +# example can be found in this +# `notebook `__, +# with all its dependencies included in the `source_scripts` +# `folder `__. # # .. note:: # In order for the remainder of this demo to work, you will need to have done 3 things: diff --git a/demonstrations/qrack.metadata.json b/demonstrations/qrack.metadata.json index 9afc67ab59..d53d40ed5c 100644 --- a/demonstrations/qrack.metadata.json +++ b/demonstrations/qrack.metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2024-07-10T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "dateOfLastModification": "2025-01-10T00:00:00+00:00", "categories": [ "Devices and Performance" ], diff --git a/demonstrations/qrack.py b/demonstrations/qrack.py index c5e7968332..38812e8497 100644 --- a/demonstrations/qrack.py +++ b/demonstrations/qrack.py @@ -276,6 +276,11 @@ def circuit(): # # Comparing performance # --------------------- +# .. note:: +# These results were obtained using Ubuntu 22.04 LTS, Linux kernel version 5.19.0-35-generic, +# using one Intel(R) Core(TM) i9-10980HK CPU @ 2.40GHz, one NVIDIA GeForce RTX 3080 Laptop GPU, +# and 32 GB of SK hynix 3200 MT/s DDR4 in 2x16 GB row configuration. +# Results may differ if run on other system configurations. # We've already seen that the Qrack device back end can do some tasks that most other simulators, or # basically any other simulator, simply can't do, like 60-qubit-wide special cases of the QFT or GHZ state # preparation with a Clifford or universal (QBDD) simulation algorithm, for example. However, @@ -438,7 +443,7 @@ def circuit(): # choice of devices or device initialization, to handle a mixture of wide and narrow qubit registers in your subroutines. ############################################################################## -# +# # References # ---------- # diff --git a/demonstrations/quantum_volume.metadata.json b/demonstrations/quantum_volume.metadata.json index 9e45b3ab88..4a626d10ed 100644 --- a/demonstrations/quantum_volume.metadata.json +++ b/demonstrations/quantum_volume.metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2020-12-15T00:00:00+00:00", - "dateOfLastModification": "2024-10-11T00:00:00+00:00", + "dateOfLastModification": "2025-01-08T00:00:00+00:00", "categories": [ "Quantum Hardware", "Quantum Computing" diff --git a/demonstrations/quantum_volume.py b/demonstrations/quantum_volume.py index 7dc9e43f48..08d3783992 100644 --- a/demonstrations/quantum_volume.py +++ b/demonstrations/quantum_volume.py @@ -343,19 +343,17 @@ def permute_qubits(num_qubits): ############################################################################## # -# Next, we need to apply SU(4) gates to pairs of qubits. PennyLane doesn't have -# built-in functionality to generate these random matrices, however its cousin -# `Strawberry Fields `_ does! We will use the -# ``random_interferometer`` method, which can generate unitary matrices uniformly +# Next, we need to apply SU(4) gates to pairs of qubits. +# We can use scipy to generate unitary matrices uniformly # at random. This function actually generates elements of U(4), but they are # essentially equivalent up to a global phase. -from strawberryfields.utils import random_interferometer +from scipy.stats import unitary_group def apply_random_su4_layer(num_qubits): for qubit_idx in range(0, num_qubits, 2): if qubit_idx < num_qubits - 1: - rand_haar_su4 = random_interferometer(N=4) + rand_haar_su4 = unitary_group.rvs(4) qml.QubitUnitary(rand_haar_su4, wires=[qubit_idx, qubit_idx + 1]) @@ -386,9 +384,10 @@ def qv_circuit_layer(num_qubits): m = 3 # number of qubits with qml.tape.QuantumTape() as tape: - qml.layer(qv_circuit_layer, m, num_qubits=m) + for _ in range(m): + qv_circuit_layer(m) -expanded_tape = tape.expand(stop_at=lambda op: isinstance(op, qml.QubitUnitary)) +(expanded_tape, ), _ = qml.transforms.decompose(tape, gate_set={qml.QubitUnitary, qml.SWAP}) print(qml.drawer.tape_text(expanded_tape, wire_order=dev_ideal.wires, show_all_wires=True, show_matrices=True)) @@ -487,12 +486,11 @@ def heavy_output_set(m, probs): # # Adds a measurement of the first m qubits to the previous circuit -with tape: - qml.probs(wires=range(m)) +tape = tape.copy(measurements=[qml.probs(wires=range(m))]) # Run the circuit, compute heavy outputs, and print results -output_probs = qml.execute([tape], dev_ideal, None) # returns a list of result ! -output_probs = output_probs[0].reshape(2 ** m, ) +[output_probs] = qml.execute([tape], dev_ideal) # returns a list of result ! +output_probs = output_probs.reshape(2 ** m, ) heavy_outputs, prob_heavy_output = heavy_output_set(m, output_probs) print("State\tProbability") @@ -583,10 +581,6 @@ def heavy_output_set(m, probs): # Lima noise model. Again, we won't be running on Lima directly --- # we'll set up a local device to simulate its behaviour. # - -dev_noisy = qml.device("qiskit.remote", wires=5, shots=1000, backend=FakeLimaV2()) - -############################################################################## # # As a final point, since we are allowed to do as much optimization as we like, # let's put the compiler to work. The compiler will perform a number of @@ -594,14 +588,15 @@ def heavy_output_set(m, probs): # qubit placement and routing techniques [#sabre]_ in order to fit the circuits # on the hardware graph in the best way possible. -dev_noisy.set_transpile_args( - **{ + +transpile_args = { "optimization_level": 3, "coupling_map": FakeLimaV2().coupling_map, "layout_method": "sabre", "routing_method": "sabre", } -) + +dev_noisy = qml.device("qiskit.remote", wires=5, shots=1000, backend=FakeLimaV2(), **transpile_args) ############################################################################## @@ -625,28 +620,28 @@ def heavy_output_set(m, probs): for trial in range(num_trials): # Simulate the circuit analytically - with qml.tape.QuantumTape() as tape: - qml.layer(qv_circuit_layer, m, num_qubits=m) + with qml.tape.QuantumTape() as tape_probs: + for _ in range(m): + qv_circuit_layer(m) qml.probs(wires=range(m)) + + # when using qml.execute, shots must be on the tape + tape_counts = tape_probs.copy(measurements=[qml.counts()], shots=1000) - output_probs = qml.execute([tape], dev_ideal, None) + output_probs = qml.execute([tape_probs], dev_ideal) output_probs = output_probs[0].reshape(2 ** m, ) heavy_outputs, prob_heavy_output = heavy_output_set(m, output_probs) # Execute circuit on the noisy device - qml.execute([tape], dev_noisy, None) - - # Get the output bit strings; flip ordering of qubits to match PennyLane - counts = dev_noisy._current_job.result().get_counts() - reordered_counts = {x[::-1]: counts[x] for x in counts.keys()} + [counts] = qml.execute([tape_counts], dev_noisy) device_heavy_outputs = np.sum( [ - reordered_counts[x] if x[:m] in heavy_outputs else 0 - for x in reordered_counts.keys() + counts[x] if x[:m] in heavy_outputs else 0 + for x in counts.keys() ] ) - fraction_device_heavy_output = device_heavy_outputs / dev_noisy.shots + fraction_device_heavy_output = device_heavy_outputs / dev_noisy.shots.total_shots probs_ideal[m - min_m, trial] = prob_heavy_output probs_noisy[m - min_m, trial] = fraction_device_heavy_output diff --git a/demonstrations/tutorial_adaptive_circuits.metadata.json b/demonstrations/tutorial_adaptive_circuits.metadata.json index 676fc101da..c65158915f 100644 --- a/demonstrations/tutorial_adaptive_circuits.metadata.json +++ b/demonstrations/tutorial_adaptive_circuits.metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2021-09-13T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-12-13T00:00:00+00:00", "categories": [ "Quantum Chemistry" ], diff --git a/demonstrations/tutorial_adaptive_circuits.py b/demonstrations/tutorial_adaptive_circuits.py index bd55318b10..29bd8a4b14 100644 --- a/demonstrations/tutorial_adaptive_circuits.py +++ b/demonstrations/tutorial_adaptive_circuits.py @@ -61,10 +61,15 @@ """ import pennylane as qml -from pennylane import qchem -from pennylane import numpy as np +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) @@ -172,8 +177,17 @@ def circuit(): # # 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(hf_state, wires=range(qubits)) + qml.BasisState(jnp.array(hf_state), wires=range(qubits)) for i, excitation in enumerate(excitations): if len(excitation) == 4: @@ -189,10 +203,10 @@ def circuit_1(params, excitations): # with respect to the Hartree-Fock state. -dev = qml.device("default.qubit", wires=qubits) -cost_fn = qml.QNode(circuit_1, dev, interface="autograd") +dev = qml.device("lightning.qubit", wires=qubits) +cost_fn = qml.QNode(circuit_1, dev, interface="jax") -circuit_gradient = qml.grad(cost_fn, argnum=0) +circuit_gradient = jax.grad(cost_fn, argnums=0) params = [0.0] * len(doubles) grads = circuit_gradient(params, excitations=doubles) @@ -214,12 +228,17 @@ def circuit_1(params, excitations): # 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. -opt = qml.GradientDescentOptimizer(stepsize=0.5) +import optax + +params_doubles = jnp.zeros(len(doubles_select)) -params_doubles = np.zeros(len(doubles_select), requires_grad=True) +opt = optax.sgd(learning_rate=0.5) # sgd stands for StochasticGradientDescent +opt_state = opt.init(params_doubles) -for n in range(20): - params_doubles = opt.step(cost_fn, params_doubles, excitations=doubles_select) +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 @@ -248,8 +267,8 @@ def circuit_2(params, excitations, gates_select, params_select): ############################################################################## # We now compute the gradients for the single excitation gates. -cost_fn = qml.QNode(circuit_2, dev, interface="autograd") -circuit_gradient = qml.grad(cost_fn, argnum=0) +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( @@ -280,15 +299,19 @@ def circuit_2(params, excitations, gates_select, params_select): # 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="autograd") +cost_fn = qml.QNode(circuit_1, dev, interface="jax") -params = np.zeros(len(doubles_select + singles_select), requires_grad=True) +params = jnp.zeros(len(doubles_select + singles_select)) gates_select = doubles_select + singles_select +opt_state = opt.init(params) -for n in range(20): +for n in range(10): t1 = time.time() - params, energy = opt.step_and_cost(cost_fn, params, excitations=gates_select) + 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)) @@ -327,9 +350,9 @@ def circuit_2(params, excitations, gates_select, params_select): excitations = doubles_select + singles_select -params = np.zeros(len(excitations), requires_grad=True) +params = jnp.zeros(len(excitations)) -@qml.qnode(dev, diff_method="parameter-shift", interface="autograd") +@qml.qnode(dev, diff_method="parameter-shift", interface="jax") def circuit(params): qml.BasisState(hf_state, wires=range(qubits)) @@ -342,9 +365,12 @@ def circuit(params): return qml.expval(qml.SparseHamiltonian(H_sparse, wires=range(qubits))) -for n in range(20): +for n in range(10): t1 = time.time() - params, energy = opt.step_and_cost(circuit, params) + 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)) @@ -397,4 +423,3 @@ def circuit(params): # # About the author # ---------------- -# .. include:: ../_static/authors/soran_jahangiri.txt diff --git a/demonstrations/tutorial_apply_qsvt.metadata.json b/demonstrations/tutorial_apply_qsvt.metadata.json index 41298d7dfe..ac0d7e56c5 100644 --- a/demonstrations/tutorial_apply_qsvt.metadata.json +++ b/demonstrations/tutorial_apply_qsvt.metadata.json @@ -9,7 +9,7 @@ } ], "dateOfPublication": "2023-08-22T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "dateOfLastModification": "2024-12-05T00:00:00+00:00", "categories": [ "Quantum Computing", "Algorithms", diff --git a/demonstrations/tutorial_apply_qsvt.py b/demonstrations/tutorial_apply_qsvt.py index b441ba0c8f..de2931eecc 100644 --- a/demonstrations/tutorial_apply_qsvt.py +++ b/demonstrations/tutorial_apply_qsvt.py @@ -32,7 +32,7 @@ ------------- For a refresher on the basics of QSVT check out our :doc:`Intro to QSVT ` tutorial. Let's recall how to apply QSVT in a circuit. This requires two pieces of information -as input: the matrix to be transformed and a set of phase angles which determine the polynomial +as input: the block encoding of the matrix to be transformed and a set of projectors which determine the polynomial transformation. For now, we use placeholder values for the phase angles; we'll later describe how to obtain them. The code below shows how to construct a basic QSVT circuit on two qubits: """ @@ -40,30 +40,33 @@ import pennylane as qml from pennylane import numpy as np -dev = qml.device("default.qubit", wires=[0, 1]) +dev = qml.device("lightning.qubit", wires=[0, 1]) A = np.array([[0.1, 0.2], [0.3, 0.4]]) phase_angles = np.array([0.0, 1.0, 2.0, 3.0]) +block_encoding = qml.BlockEncode(A, wires=[0, 1]) +projectors = [qml.PCPhase(angle, dim=2, wires=[0, 1]) for angle in phase_angles] + @qml.qnode(dev) -def my_circuit(phase_angles): - qml.qsvt(A, phase_angles, wires=[0, 1]) +def my_circuit(): + qml.QSVT(block_encoding, projectors) return qml.state() ############################################################################### # We can now execute the circuit and visualize it. -my_circuit(phase_angles) -print(qml.draw(my_circuit)(phase_angles)) +my_circuit() +print(qml.draw(my_circuit, level = "top")()) ############################################################################### # We can inspect details by drawing the expanded circuit. The :class:`~.pennylane.QSVT` # operation is composed of repeated applications of the :class:`~.pennylane.BlockEncode` and # :class:`~.pennylane.PCPhase` (:math:`\Pi_{\phi}`) operations. -print(my_circuit.tape.expand().draw()) +print(qml.draw(my_circuit)()) ############################################################################### # Now let's look at an application of QSVT --- solving a linear system of equations. @@ -120,6 +123,7 @@ def my_circuit(phase_angles): kappa = 4 s = 0.10145775 phi_pyqsp = [-2.287, 2.776, -1.163, 0.408, -0.16, -0.387, 0.385, -0.726, 0.456, 0.062, -0.468, 0.393, 0.028, -0.567, 0.76, -0.432, -0.011, 0.323, -0.573, 0.82, -1.096, 1.407, -1.735, 2.046, -2.321, 2.569, -2.819, -0.011, 2.71, -2.382, 2.574, 0.028, -2.749, 2.673, 0.062, -2.685, 2.416, 0.385, -0.387, -0.16, 0.408, -1.163, -0.365, 2.426] +phi_qsvt = qml.transform_angles(phi_pyqsp, "QSP", "QSVT") # convert pyqsp angles to be compatible with QSVT ############################################################################### # .. note:: @@ -144,9 +148,11 @@ def my_circuit(phase_angles): qsvt_y_vals = [] for x in x_vals: - poly_x = qml.matrix(qml.qsvt, wire_order=[0])( - x, phi_pyqsp, wires=[0], convention="Wx" # specify angles using convention=`Wx` - ) + + block_encoding = qml.BlockEncode(x, wires=[0]) + projectors = [qml.PCPhase(angle, dim=1, wires=[0]) for angle in phi_qsvt] + + poly_x = qml.matrix(qml.QSVT, wire_order=[0])(block_encoding, projectors) qsvt_y_vals.append(np.real(poly_x[0, 0])) ############################################################################### @@ -184,15 +190,20 @@ def my_circuit(phase_angles): # We apply each QSVT operation, even or odd, conditioned on the ancilla. Finally, the ancilla # qubit is reset. - def sum_even_odd_circ(x, phi, ancilla_wire, wires): - phi1, phi2 = phi[: len(phi) // 2], phi[len(phi) // 2:] + phi1, phi2 = phi[: len(phi) // 2], phi[len(phi) // 2 :] + block_encode = qml.BlockEncode(x, wires=wires) qml.Hadamard(wires=ancilla_wire) # equal superposition # apply even and odd polynomial approx - qml.ctrl(qml.qsvt, control=(ancilla_wire,), control_values=(0,))(x, phi1, wires=wires) - qml.ctrl(qml.qsvt, control=(ancilla_wire,), control_values=(1,))(x, phi2, wires=wires) + + dim = x.shape[0] if x.ndim > 0 else 1 + projectors_even = [qml.PCPhase(angle, dim= dim, wires=wires) for angle in phi1] + qml.ctrl(qml.QSVT, control=(ancilla_wire,), control_values=(0,))(block_encode, projectors_even) + + projectors_odd = [qml.PCPhase(angle, dim= dim, wires=wires) for angle in phi2] + qml.ctrl(qml.QSVT, control=(ancilla_wire,), control_values=(0,))(block_encode, projectors_odd) qml.Hadamard(wires=ancilla_wire) # un-prepare superposition diff --git a/demonstrations/tutorial_bluequbit.metadata.json b/demonstrations/tutorial_bluequbit.metadata.json index 5103d7af75..d30aba3426 100644 --- a/demonstrations/tutorial_bluequbit.metadata.json +++ b/demonstrations/tutorial_bluequbit.metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2024-09-24T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-11-12T00:00:00+00:00", "categories": [ "Devices and Performance", "Quantum Computing" diff --git a/demonstrations/tutorial_bluequbit.py b/demonstrations/tutorial_bluequbit.py index 574356d8ac..95f74963e4 100644 --- a/demonstrations/tutorial_bluequbit.py +++ b/demonstrations/tutorial_bluequbit.py @@ -41,10 +41,6 @@ import matplotlib.pyplot as plt import numpy as np -# This filter will suppress deprecation warnings for viewability -import warnings -warnings.filterwarnings("ignore", "QubitDevice", qml.PennyLaneDeprecationWarning) - def bell_pair(): qml.Hadamard(0) diff --git a/demonstrations/tutorial_chemical_reactions.metadata.json b/demonstrations/tutorial_chemical_reactions.metadata.json index c68aea4c8d..7c69271a89 100644 --- a/demonstrations/tutorial_chemical_reactions.metadata.json +++ b/demonstrations/tutorial_chemical_reactions.metadata.json @@ -9,7 +9,7 @@ } ], "dateOfPublication": "2021-07-23T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "dateOfLastModification": "2024-12-13T00:00:00+00:00", "categories": [ "Quantum Chemistry" ], diff --git a/demonstrations/tutorial_chemical_reactions.py b/demonstrations/tutorial_chemical_reactions.py index 541a01d159..719beb31f6 100644 --- a/demonstrations/tutorial_chemical_reactions.py +++ b/demonstrations/tutorial_chemical_reactions.py @@ -106,7 +106,7 @@ # the equilibrium bond length, and the point where the bond is broken, which occurs when the atoms # are far away from each other. -from pennylane import numpy as np +from jax import numpy as jnp # atomic symbols defining the molecule symbols = ['H', 'H'] @@ -115,7 +115,7 @@ energies = [] # set up a loop to change bond length -r_range = np.arange(0.5, 5.0, 0.25) +r_range = jnp.arange(0.5, 5.0, 0.25) # keeps track of points in the potential energy surface pes_point = 0 @@ -124,9 +124,11 @@ # We build the Hamiltonian using the :func:`~.pennylane.qchem.molecular_hamiltonian` # function, and use standard Pennylane techniques to optimize the circuit. +import optax + for r in r_range: # Change only the z coordinate of one atom - coordinates = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, r]]) + coordinates = jnp.array([[0.0, 0.0, 0.0], [0.0, 0.0, r]]) # Construct the Molecule object molecule = qchem.Molecule(symbols, coordinates) @@ -135,13 +137,13 @@ H, qubits = qchem.molecular_hamiltonian(molecule, method='openfermion') # define the device, optimizer and circuit - dev = qml.device("default.qubit", wires=qubits) - opt = qml.GradientDescentOptimizer(stepsize=0.4) + dev = qml.device("lightning.qubit", wires=qubits) + opt = optax.sgd(learning_rate=0.4) # sgd stands for StochasticGradientDescent - @qml.qnode(dev, interface='autograd') + @qml.qnode(dev, interface='jax') def circuit(parameters): # Prepare the HF state: |1100> - qml.BasisState(hf, wires=range(qubits)) + qml.BasisState.compute_decomposition(hf, wires=range(qubits)) qml.DoubleExcitation(parameters[0], wires=[0, 1, 2, 3]) qml.SingleExcitation(parameters[1], wires=[0, 2]) qml.SingleExcitation(parameters[2], wires=[1, 3]) @@ -149,19 +151,33 @@ def circuit(parameters): return qml.expval(H) # we are interested in minimizing this expectation value # initialize the gate parameters - params = np.zeros(3, requires_grad=True) + init_params = jnp.zeros(3) # initialize with converged parameters from previous point if pes_point > 0: - params = params_old + init_params = params_old prev_energy = 0.0 - for n in range(50): - # perform optimization step - params, energy = opt.step_and_cost(circuit, params) + @qml.qjit + def update_step(i, params, opt_state): + """Perform a single gradient update step""" + grads = qml.grad(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 - if np.abs(energy - prev_energy) < 1e-6: + for i in range(50): + params, opt_state = update_step(i, params, opt_state) + energy = circuit(params) + + if jnp.abs(energy - prev_energy) < 1e-6: break + prev_energy = energy # store the converged parameters @@ -280,35 +296,52 @@ def circuit(parameters): # loop to change reaction coordinate -r_range = np.arange(1.0, 3.0, 0.1) +r_range = jnp.arange(1.0, 3.0, 0.1) for r in r_range: - coordinates = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, r], [0.0, 0.0, 4.0]]) + coordinates = jnp.array([[0.0, 0.0, 0.0], [0.0, 0.0, r], [0.0, 0.0, 4.0]]) # We now specify the multiplicity molecule = qchem.Molecule(symbols, coordinates, mult=multiplicity) H, qubits = qchem.molecular_hamiltonian(molecule, method='openfermion') - dev = qml.device("default.qubit", wires=qubits) - opt = qml.GradientDescentOptimizer(stepsize=1.5) + dev = qml.device("lightning.qubit", wires=qubits) + opt = optax.sgd(learning_rate=1.5) # sgd stands for StochasticGradientDescent - @qml.qnode(dev, interface='autograd') + @qml.qjit + @qml.qnode(dev, interface='jax') def circuit(parameters): AllSinglesDoubles(parameters, range(qubits), hf, singles, doubles) return qml.expval(H) # we are interested in minimizing this expectation value - params = np.zeros(len(singles) + len(doubles), requires_grad=True) + init_params = jnp.zeros(len(singles) + len(doubles)) + # initialize with converged parameters from previous point if pes_point > 0: - params = params_old + init_params = params_old prev_energy = 0.0 + @qml.qjit + def update_step(i, params, opt_state): + """Perform a single gradient update step""" + grads = qml.grad(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 n in range(60): - params, energy = opt.step_and_cost(circuit, params) - if np.abs(energy - prev_energy) < 1e-6: + for i in range(60): + params, opt_state = update_step(i, params, opt_state) + energy = circuit(params) + + if jnp.abs(energy - prev_energy) < 1e-6: break + prev_energy = energy # store the converged parameters @@ -393,7 +426,7 @@ def circuit(parameters): # Temperature T = 300 -ratio = np.exp(activation_energy / (2 * k_B * T)) +ratio = jnp.exp(activation_energy / (2 * k_B * T)) print(f"Ratio of reaction rates is {ratio:.0f}") @@ -436,4 +469,4 @@ def circuit(parameters): # ----------------- # .. include:: ../_static/authors/varun_rishi.txt # -# .. include:: ../_static/authors/juan_miguel_arrazola.txt \ No newline at end of file +# .. include:: ../_static/authors/juan_miguel_arrazola.txt diff --git a/demonstrations/tutorial_classically_boosted_vqe.metadata.json b/demonstrations/tutorial_classically_boosted_vqe.metadata.json index 7e3e268228..797d86cf63 100644 --- a/demonstrations/tutorial_classically_boosted_vqe.metadata.json +++ b/demonstrations/tutorial_classically_boosted_vqe.metadata.json @@ -9,7 +9,7 @@ } ], "dateOfPublication": "2022-10-31T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "dateOfLastModification": "2024-12-13T00:00:00+00:00", "categories": [ "Quantum Chemistry" ], diff --git a/demonstrations/tutorial_classically_boosted_vqe.py b/demonstrations/tutorial_classically_boosted_vqe.py index 21f4b06529..d3e3f24e36 100644 --- a/demonstrations/tutorial_classically_boosted_vqe.py +++ b/demonstrations/tutorial_classically_boosted_vqe.py @@ -104,10 +104,11 @@ import pennylane as qml from pennylane import qchem -from pennylane import numpy as np +import numpy as np +from jax import numpy as jnp symbols = ["H", "H"] -coordinates = np.array([[0.0, 0.0, -0.6614], [0.0, 0.0, 0.6614]]) +coordinates = jnp.array([[0.0, 0.0, -0.6614], [0.0, 0.0, 0.6614]]) basis_set = "sto-3g" electrons = 2 @@ -139,7 +140,9 @@ def circuit_VQE(theta, wires): - qml.AllSinglesDoubles(weights=theta, wires=wires, hf_state=hf, singles=singles, doubles=doubles) + qml.AllSinglesDoubles( + weights=theta, wires=wires, hf_state=hf, singles=singles, doubles=doubles + ) ###################################################################### @@ -147,11 +150,17 @@ def circuit_VQE(theta, wires): # define a circuit for the cost function. # +import optax +import jax + +jax.config.update("jax_enable_x64", True) # use double-precision numbers + dev = qml.device("lightning.qubit", wires=qubits) -@qml.qnode(dev, interface="autograd") -def cost_fn(theta): +@qml.qjit +@qml.qnode(dev, interface="jax") +def cost(theta): circuit_VQE(theta, range(qubits)) return qml.expval(H) @@ -163,20 +172,35 @@ def cost_fn(theta): stepsize = 0.4 max_iterations = 30 -opt = qml.GradientDescentOptimizer(stepsize=stepsize) -theta = np.zeros(num_theta, requires_grad=True) +opt = optax.sgd(learning_rate=stepsize) # sgd stands for StochasticGradientDescent +init_params = jnp.zeros(num_theta) ###################################################################### # Finally, we run the algorithm. # -for n in range(max_iterations): - theta, prev_energy = opt.step_and_cost(cost_fn, theta) - samples = cost_fn(theta) -energy_VQE = cost_fn(theta) -theta_opt = theta +@qml.qjit +def update_step(i, params, opt_state): + """Perform a single gradient update step""" + grads = qml.grad(cost)(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(30): + params, opt_state = update_step(i, params, opt_state) + energy = cost(params) + +energy_VQE = cost(params) +theta_opt = params print("VQE energy: %.4f" % (energy_VQE)) print("Optimal parameters:", theta_opt) @@ -411,20 +435,22 @@ def cost_fn(theta): # :math:`U_i \vert 0^n \rangle = \vert \phi_i \rangle.` In this case, this # is just a mapping of a classical basis state into the circuit consisting # of :math:`X` gates and can be easily implemented using PennyLane’s -# function ``qml.BasisState(i, n))``. +# function ``qml.BasisState(i, n)``. # wires = range(qubits + 1) dev = qml.device("lightning.qubit", wires=wires) -@qml.qnode(dev, interface="autograd") +@qml.qnode(dev, interface="jax") def hadamard_test(Uq, Ucl, component="real"): if component == "imag": qml.RX(math.pi / 2, wires=wires[1:]) qml.Hadamard(wires=[0]) - qml.ControlledQubitUnitary(Uq.conjugate().T @ Ucl, control_wires=[0], wires=wires[1:]) + qml.ControlledQubitUnitary( + Uq.conjugate().T @ Ucl, control_wires=[0], wires=wires[1:] + ) qml.Hadamard(wires=[0]) return qml.probs(wires=[0]) @@ -445,7 +471,7 @@ def circuit_product_state(state): H12 = 0 relevant_basis_states = np.array( - [[1, 1, 0, 0], [0, 1, 1, 0], [1, 0, 0, 1], [0, 0, 1, 1]], requires_grad=True + [[1, 1, 0, 0], [0, 1, 1, 0], [1, 0, 0, 1], [0, 0, 1, 1]] ) for j, basis_state in enumerate(relevant_basis_states): Ucl = qml.matrix(circuit_product_state, wire_order=wire_order)(basis_state) diff --git a/demonstrations/tutorial_clifford_circuit_simulations.metadata.json b/demonstrations/tutorial_clifford_circuit_simulations.metadata.json index 2dc1876ec4..f46c5c790d 100644 --- a/demonstrations/tutorial_clifford_circuit_simulations.metadata.json +++ b/demonstrations/tutorial_clifford_circuit_simulations.metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2024-04-12T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-11-14T00:00:00+00:00", "categories": [ "Devices and Performance" ], diff --git a/demonstrations/tutorial_clifford_circuit_simulations.py b/demonstrations/tutorial_clifford_circuit_simulations.py index 60eef7a1e4..acc2402425 100644 --- a/demonstrations/tutorial_clifford_circuit_simulations.py +++ b/demonstrations/tutorial_clifford_circuit_simulations.py @@ -381,7 +381,8 @@ def state_at_each_step(tape): # Let's examine the remaining operations to confirm this. # -circuit_ops = circuit.tape.operations +tape = qml.workflow.construct_tape(circuit)() +circuit_ops = tape.operations print("Circ. Ops: ", circuit_ops) for step in range(1, len(circuit_ops)): diff --git a/demonstrations/tutorial_differentiable_HF.metadata.json b/demonstrations/tutorial_differentiable_HF.metadata.json index 2389de40b2..8f76d217f4 100644 --- a/demonstrations/tutorial_differentiable_HF.metadata.json +++ b/demonstrations/tutorial_differentiable_HF.metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2022-05-09T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-12-13T00:00:00+00:00", "categories": [ "Quantum Chemistry" ], diff --git a/demonstrations/tutorial_differentiable_HF.py b/demonstrations/tutorial_differentiable_HF.py index d157c3dd35..78c148e093 100644 --- a/demonstrations/tutorial_differentiable_HF.py +++ b/demonstrations/tutorial_differentiable_HF.py @@ -101,20 +101,21 @@ For the hydrogen molecule we have """ -from autograd import grad import pennylane as qml -from pennylane import numpy as np +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 = np.array([[-0.672943567415407, 0.0, 0.0], - [ 0.672943567415407, 0.0, 0.0]], requires_grad=True) +geometry = jnp.array([[-0.672943567415407, 0.0, 0.0], + [ 0.672943567415407, 0.0, 0.0]]) ############################################################################## -# The use of ``requires_grad=True`` specifies that the nuclear coordinates are differentiable -# parameters. We can now compute the Hartree-Fock energy and its gradient with respect to the +# 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. @@ -124,12 +125,12 @@ # 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)(geometry) +qml.qchem.hf_energy(mol)() ############################################################################## # We now compute the gradient of the energy with respect to the nuclear coordinates -grad(qml.qchem.hf_energy(mol))(geometry) +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 @@ -181,13 +182,13 @@ # are those of the hydrogen atoms by default and are therefore treated as differentiable parameters # by PennyLane. -qml.qchem.overlap_integral(S1, S2)([geometry[0], geometry[1]]) +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 -grad(qml.qchem.overlap_integral(S1, S2))([geometry[0], geometry[1]]) +jax.grad(qml.qchem.overlap_integral(S1, S2))(geometry, mol.coeff, mol.alpha) ############################################################################## # Can you explain why some of the computed gradients are zero? @@ -228,13 +229,13 @@ # 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]._value for i in range(n) for j in range(n)]).reshape(n, n) +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) @@ -252,7 +253,7 @@ # 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, args=[mol.coordinates]) +hamiltonian, qubits = qml.qchem.molecular_hamiltonian(mol) print(hamiltonian) ############################################################################## @@ -265,11 +266,12 @@ # hydrogen atoms. dev = qml.device("default.qubit", wires=4) -def energy(mol): - @qml.qnode(dev, interface="autograd") +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][0], wires=[0, 1, 2, 3]) + 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 @@ -282,23 +284,24 @@ def circuit(*args): # coordinate gradients are simply the forces on the atomic nuclei. # initial value of the circuit parameter -circuit_param = [np.array([0.0], requires_grad=True)] +circuit_param = jnp.array([0.0]) -for n in range(36): +geometry = jnp.array([[0.0, 0.02, -0.672943567415407], + [0.1, 0.0, 0.672943567415407]]) - args = [circuit_param, geometry] +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 = grad(energy(mol), argnum = 0)(*args) + g_param = jax.grad(energy(), argnums = 0)(*args) circuit_param = circuit_param - 0.25 * g_param[0] # gradient for nuclear coordinates - forces = -grad(energy(mol), argnum = 1)(*args) - geometry = geometry + 0.5 * forces + forces = jax.grad(energy(), argnums = 1)(*args) + geometry = geometry - 0.5 * forces if n % 5 == 0: - print(f'n: {n}, E: {energy(mol)(*args):.8f}, Force-max: {abs(forces).max():.8f}') + 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 @@ -313,44 +316,41 @@ def circuit(*args): # 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 = np.array([[0.0, 0.0, -0.672943567415407], - [0.0, 0.0, 0.672943567415407]], requires_grad=True) - -# initial values of the basis set exponents -alpha = np.array([[3.42525091, 0.62391373, 0.1688554], - [3.42525091, 0.62391373, 0.1688554]], requires_grad=True) +geometry = jnp.array([[0.0, 0.0, -0.672943567415407], + [0.0, 0.0, 0.672943567415407]]) # initial values of the basis set contraction coefficients -coeff = np.array([[0.1543289673, 0.5353281423, 0.4446345422], - [0.1543289673, 0.5353281423, 0.4446345422]], requires_grad=True) +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 = [np.array([0.0], requires_grad=True)] +circuit_param = jnp.array([0.0]) -for n in range(36): +mol = qml.qchem.Molecule(symbols, geometry, coeff=coeff, alpha=alpha) +args = [circuit_param, geometry, coeff, alpha] - args = [circuit_param, geometry, alpha, coeff] +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 = grad(energy(mol), argnum=0)(*args) + 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 - forces = -grad(energy(mol), argnum=1)(*args) - geometry = geometry + 0.5 * forces - - # gradient for basis set exponents - g_alpha = grad(energy(mol), argnum=2)(*args) - alpha = alpha - 0.25 * g_alpha - - # gradient for basis set contraction coefficients - g_coeff = grad(energy(mol), argnum=3)(*args) - coeff = coeff - 0.25 * g_coeff + 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: {energy(mol)(*args):.8f}, Force-max: {abs(forces).max():.8f}') + 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 @@ -390,4 +390,4 @@ def circuit(*args): # # About the author # ---------------- -# .. include:: ../_static/authors/soran_jahangiri.txt \ No newline at end of file +# .. include:: ../_static/authors/soran_jahangiri.txt diff --git a/demonstrations/tutorial_eqnn_force_field.metadata.json b/demonstrations/tutorial_eqnn_force_field.metadata.json index 99dd1bcc61..b2d5af1d64 100644 --- a/demonstrations/tutorial_eqnn_force_field.metadata.json +++ b/demonstrations/tutorial_eqnn_force_field.metadata.json @@ -9,7 +9,7 @@ } ], "dateOfPublication": "2024-03-12T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "dateOfLastModification": "2024-12-13T00:00:00+00:00", "categories": [ "Quantum Machine Learning", "Quantum Chemistry" diff --git a/demonstrations/tutorial_eqnn_force_field.py b/demonstrations/tutorial_eqnn_force_field.py index 45373bf0b4..a0bf8d0093 100644 --- a/demonstrations/tutorial_eqnn_force_field.py +++ b/demonstrations/tutorial_eqnn_force_field.py @@ -134,7 +134,7 @@ import jax -jax.config.update("jax_platform_name", "cpu") +jax.config.update('jax_platform_name', 'cpu') jax.config.update("jax_enable_x64", True) from jax import numpy as jnp @@ -155,7 +155,7 @@ [ np.kron(X, X), np.kron(Y, Y), - np.kron(Z, Z), + np.kron(Z, Z) ] # Vector of tensor products of Pauli matrices ) ) diff --git a/demonstrations/tutorial_equivariant_graph_embedding.metadata.json b/demonstrations/tutorial_equivariant_graph_embedding.metadata.json index 22cbcd9936..e299020b98 100644 --- a/demonstrations/tutorial_equivariant_graph_embedding.metadata.json +++ b/demonstrations/tutorial_equivariant_graph_embedding.metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2023-07-13T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2025-01-10T00:00:00+00:00", "categories": [ "Quantum Machine Learning" ], diff --git a/demonstrations/tutorial_equivariant_graph_embedding.py b/demonstrations/tutorial_equivariant_graph_embedding.py index 060ff6953e..5e8c697abe 100644 --- a/demonstrations/tutorial_equivariant_graph_embedding.py +++ b/demonstrations/tutorial_equivariant_graph_embedding.py @@ -12,57 +12,57 @@ ###################################################################### # A notorious problem when data comes in the form of graphs -- think of molecules or social media -# networks -- is that the numerical representation of a graph in a computer is not unique. +# networks -- is that the numerical representation of a graph in a computer is not unique. # For example, if we describe a graph via an `adjacency matrix `_ whose -# entries contain the edge weights as off-diagonals and node weights on the diagonal, -# any simultaneous permutation of rows and columns of this matrix refer to the same graph. -# +# entries contain the edge weights as off-diagonals and node weights on the diagonal, +# any simultaneous permutation of rows and columns of this matrix refer to the same graph. +# # .. figure:: ../_static/demonstration_assets/equivariant_graph_embedding/adjacency-matrices.png # :width: 60% # :align: center # :alt: adjacency-matrices # -# For example, the graph in the image above is represented by each of the two equivalent adjacency matrices. -# The top matrix can be transformed into the bottom matrix -# by swapping the first row with the third row, then swapping the third column with the first column, then the +# For example, the graph in the image above is represented by each of the two equivalent adjacency matrices. +# The top matrix can be transformed into the bottom matrix +# by swapping the first row with the third row, then swapping the third column with the first column, then the # new first row with the second, and finally the first colum with the second. # -# But the number of such permutations grows factorially with the number of nodes in the graph, which +# But the number of such permutations grows factorially with the number of nodes in the graph, which # is even worse than an exponential growth! -# +# # If we want computers to learn from graph data, we usually want our models to "know" that all these -# permuted adjacency matrices refer to the same object, so we do not waste resources on learning -# this property. In mathematical terms, this means that the model should be in- or -# equivariant (more about this distinction below) with respect to permutations. -# This is the basic motivation of `Geometric Deep Learning `_, -# ideas of which have found their way into quantum machine learning. -# -# This tutorial shows how to implement an example of a trainable permutation equivariant graph embedding -# as proposed in `Skolik et al. (2022) `_. The embedding -# maps the adjacency matrix of an undirected graph with edge and node weights to a quantum state, such that -# permutations of an adjacency matrix get mapped to the same states *if only we also +# permuted adjacency matrices refer to the same object, so we do not waste resources on learning +# this property. In mathematical terms, this means that the model should be in- or +# equivariant (more about this distinction below) with respect to permutations. +# This is the basic motivation of `Geometric Deep Learning `_, +# ideas of which have found their way into quantum machine learning. +# +# This tutorial shows how to implement an example of a trainable permutation equivariant graph embedding +# as proposed in `Skolik et al. (2022) `_. The embedding +# maps the adjacency matrix of an undirected graph with edge and node weights to a quantum state, such that +# permutations of an adjacency matrix get mapped to the same states *if only we also # permute the qubit registers in the same fashion*. -# -# .. note:: -# The tutorial is meant for beginners and does not contain the mathematical details of the +# +# .. note:: +# The tutorial is meant for beginners and does not contain the mathematical details of the # rich theory of equivariance. Have a look -# `at this demo `_ if you want to know more. -# -# +# `at this demo `_ if you want to know more. +# +# # Permuted adjacency matrices describe the same graph # --------------------------------------------------- -# -# Let us first verify that permuted adjacency matrices really describe one and the same graph. +# +# Let us first verify that permuted adjacency matrices really describe one and the same graph. # We also gain some useful data generation functions for later. # -# First we create random adjacency matrices. -# The entry :math:`a_{ij}` of this matrix corresponds to the weight of the edge between nodes -# :math:`i` and :math:`j` in the graph. We assume that graphs have no self-loops; instead, -# the diagonal elements of the adjacency matrix are interpreted as node weights (or -# "node attributes"). -# -# Taking the example of a Twitter user retweet network, the nodes would be users, -# edge weights indicate how often two users retweet each other and node attributes +# First we create random adjacency matrices. +# The entry :math:`a_{ij}` of this matrix corresponds to the weight of the edge between nodes +# :math:`i` and :math:`j` in the graph. We assume that graphs have no self-loops; instead, +# the diagonal elements of the adjacency matrix are interpreted as node weights (or +# "node attributes"). +# +# Taking the example of a Twitter user retweet network, the nodes would be users, +# edge weights indicate how often two users retweet each other and node attributes # could indicate the follower count of a user. # @@ -104,7 +104,7 @@ def permute(A, permutation): print(A_perm) ###################################################################### -# If we create `networkx` graphs from both adjacency matrices and plot them, +# If we create `networkx` graphs from both adjacency matrices and plot them, # we see that they are identical as claimed. # @@ -115,7 +115,7 @@ def permute(A, permutation): np.fill_diagonal(A, np.zeros(len(A))) G1 = nx.Graph(A) -pos1=nx.spring_layout(G1) +pos1 = nx.spring_layout(G1, seed=1) nx.draw(G1, pos1, labels=node_labels, ax=ax1, node_size = 800, node_color = "#ACE3FF") edge_labels = nx.get_edge_attributes(G1,'weight') nx.draw_networkx_edge_labels(G1,pos1,edge_labels=edge_labels, ax=ax1) @@ -125,7 +125,7 @@ def permute(A, permutation): np.fill_diagonal(A_perm, np.zeros(len(A))) G2 = nx.Graph(A_perm) -pos2=nx.spring_layout(G2) +pos2 = nx.spring_layout(G2, seed=1) nx.draw(G2, pos2, labels=node_labels, ax=ax2, node_size = 800, node_color = "#ACE3FF") edge_labels = nx.get_edge_attributes(G2,'weight') nx.draw_networkx_edge_labels(G2,pos2,edge_labels=edge_labels, ax=ax2) @@ -136,73 +136,73 @@ def permute(A, permutation): plt.show() ###################################################################### -# .. note:: -# -# The issue of non-unique numerical representations of graphs ultimately stems -# from the fact that the nodes in a graph +# .. note:: +# +# The issue of non-unique numerical representations of graphs ultimately stems +# from the fact that the nodes in a graph # do not have an intrinsic order, and by labelling them in a numerical data structure like a matrix # we therefore impose an arbitrary order. # # Permutation equivariant embeddings # ---------------------------------- -# -# When we design a machine learning model that takes graph data, the first step is to encode -# the adjacency matrix into a quantum state using an embedding or +# +# When we design a machine learning model that takes graph data, the first step is to encode +# the adjacency matrix into a quantum state using an embedding or # `quantum feature map `_ # :math:`\phi:` -# -# .. math:: -# +# +# .. math:: +# # A \rightarrow |\phi(A)\rangle . -# -# We may want the resulting quantum state to be the same for all adjacency matrices describing -# the same graph. In mathematical terms, this means that :math:`\phi` is an *invariant* embedding with respect to +# +# We may want the resulting quantum state to be the same for all adjacency matrices describing +# the same graph. In mathematical terms, this means that :math:`\phi` is an *invariant* embedding with respect to # simultaneous row and column permutations :math:`\pi(A)` of the adjacency matrix: -# -# .. math:: -# +# +# .. math:: +# # |\phi(A) \rangle = |\phi(\pi(A))\rangle \;\; \text{ for all } \pi . -# -# However, invariance is often too strong a constraint. Think for example of an encoding that -# associates each node in the graph with a qubit. We might want permutations of the adjacency +# +# However, invariance is often too strong a constraint. Think for example of an encoding that +# associates each node in the graph with a qubit. We might want permutations of the adjacency # matrix to lead to the same state *up to an equivalent permutation of the qubits* :math:`P_{\pi},` # where # -# .. math:: -# +# .. math:: +# # P_{\pi} |q_1,...,q_n \rangle = |q_{\textit{perm}_{\pi}(1)}, ... q_{\textit{perm}_{\pi}(n)} \rangle . -# -# The function :math:`\text{perm}_{\pi}` maps each index to the permuted index according to :math:`\pi.` -# # -# .. note:: +# The function :math:`\text{perm}_{\pi}` maps each index to the permuted index according to :math:`\pi.` +# +# +# .. note:: # # The operator :math:`P_{\pi}` is implemented by PennyLane's :class:`~pennylane.Permute.` -# +# # This results in an *equivariant* embedding with respect to permutations of the adjacency matrix: -# -# .. math:: -# -# |\phi(A) \rangle = P_{\pi}|\phi(\pi(A))\rangle \;\; \text{ for all } \pi . -# -# +# +# .. math:: +# +# |\phi(A) \rangle = P_{\pi}|\phi(\pi(A))\rangle \;\; \text{ for all } \pi . +# +# # This is exactly what the following quantum embedding is aiming to do! The mathematical details -# behind these concepts use group theory and are beautiful, but can be a bit daunting. +# behind these concepts use group theory and are beautiful, but can be a bit daunting. # Have a look at `this paper `_ if you want to learn more. -# +# # # Implementation in PennyLane # --------------------------- -# -# Let's get our hands dirty with an example. As mentioned, we will implement the permutation-equivariant +# +# Let's get our hands dirty with an example. As mentioned, we will implement the permutation-equivariant # embedding suggested in `Skolik et al. (2022) `_ which has this structure: -# +# # .. figure:: ../_static/demonstration_assets/equivariant_graph_embedding/circuit.png # :width: 70% # :align: center # :alt: Equivariant embedding -# -# The image can be found in `Skolik et al. (2022) `_ and shows one layer of the circuit. +# +# The image can be found in `Skolik et al. (2022) `_ and shows one layer of the circuit. # The :math:`\epsilon` are our edge weights while :math:`\alpha` describe the node weights, and the :math:`\beta,` :math:`\gamma` are variational parameters. # # In PennyLane this looks as follows: @@ -222,23 +222,23 @@ def perm_equivariant_embedding(A, betas, gammas): """ n_nodes = len(A) n_layers = len(betas) # infer the number of layers from the parameters - + # initialise in the plus state for i in range(n_nodes): qml.Hadamard(i) - + for l in range(n_layers): for i in range(n_nodes): for j in range(i): - # factor of 2 due to definition of gate + # factor of 2 due to definition of gate qml.IsingZZ(2*gammas[l]*A[i,j], wires=[i,j]) for i in range(n_nodes): qml.RX(A[i,i]*betas[l], wires=i) ###################################################################### -# We can use this ansatz in a circuit. +# We can use this ansatz in a circuit. n_qubits = 5 n_layers = 2 @@ -265,9 +265,9 @@ def eqc(adjacency_matrix, observable, trainable_betas, trainable_gammas): ###################################################################### # Validating the equivariance # --------------------------- -# +# # Let's now check if the circuit is really equivariant! -# +# # This is the expectation value we get using the original adjacency matrix as an input: # @@ -286,15 +286,15 @@ def eqc(adjacency_matrix, observable, trainable_betas, trainable_gammas): ###################################################################### -# Why are the two values different? Well, we constructed an *equivariant* ansatz, -# not an *invariant* one! Remember, an *invariant* ansatz means that embedding a permutation of -# the adjacency matrix leads to the same state as an embedding of the original matrix. -# An *equivariant* ansatz embeds the permuted adjacency matrix into a state where the qubits -# are permuted as well. -# -# As a result, the final state before measurement is only the same if we -# permute the qubits in the same manner that we permute the input adjacency matrix. We could insert a -# permutation operator ``qml.Permute(perm)`` to achieve this, or we simply permute the wires +# Why are the two values different? Well, we constructed an *equivariant* ansatz, +# not an *invariant* one! Remember, an *invariant* ansatz means that embedding a permutation of +# the adjacency matrix leads to the same state as an embedding of the original matrix. +# An *equivariant* ansatz embeds the permuted adjacency matrix into a state where the qubits +# are permuted as well. +# +# As a result, the final state before measurement is only the same if we +# permute the qubits in the same manner that we permute the input adjacency matrix. We could insert a +# permutation operator ``qml.Permute(perm)`` to achieve this, or we simply permute the wires # of the observables! # @@ -309,34 +309,30 @@ def eqc(adjacency_matrix, observable, trainable_betas, trainable_gammas): ###################################################################### # Et voilà! -# -# +# +# # Conclusion # ---------- -# -# Equivariant graph embeddings can be combined with other equivariant parts of a quantum machine learning pipeline -# (like measurements and the cost function). `Skolik et al. (2022) `_, -# for example, use such a pipeline as part of a reinforcement learning scheme that finds heuristic solutions for the -# traveling salesman problem. Their simulations compare a fully equivariant model to circuits that break -# permutation equivariance and show that it performs better, confirming that if we know +# +# Equivariant graph embeddings can be combined with other equivariant parts of a quantum machine learning pipeline +# (like measurements and the cost function). `Skolik et al. (2022) `_, +# for example, use such a pipeline as part of a reinforcement learning scheme that finds heuristic solutions for the +# traveling salesman problem. Their simulations compare a fully equivariant model to circuits that break +# permutation equivariance and show that it performs better, confirming that if we know # about structure in our data, we should try to use this knowledge in machine learning. # # References # ---------- # -# 1. Andrea Skolik, Michele Cattelan, Sheir Yarkoni,Thomas Baeck and Vedran Dunjko (2022). -# Equivariant quantum circuits for learning on weighted graphs. +# 1. Andrea Skolik, Michele Cattelan, Sheir Yarkoni,Thomas Baeck and Vedran Dunjko (2022). +# Equivariant quantum circuits for learning on weighted graphs. # `arXiv:2205.06109 `__ # # 2. Quynh T. Nguyen, Louis Schatzki, Paolo Braccia, Michael Ragone, # Patrick J. Coles, Frédéric Sauvage, Martín Larocca and Marco Cerezo (2022). # Theory for Equivariant Quantum Neural Networks. # `arXiv:2210.08566 `__ -# -# About the author +# +# About the author # ------------------------- # .. include:: ../_static/authors/maria_schuld.txt - - - - diff --git a/demonstrations/tutorial_error_mitigation.metadata.json b/demonstrations/tutorial_error_mitigation.metadata.json index 0dfc8c3ad3..3d93261a10 100644 --- a/demonstrations/tutorial_error_mitigation.metadata.json +++ b/demonstrations/tutorial_error_mitigation.metadata.json @@ -9,7 +9,7 @@ } ], "dateOfPublication": "2021-11-29T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "dateOfLastModification": "2024-11-12T00:00:00+00:00", "categories": [ "Algorithms", "Quantum Computing" diff --git a/demonstrations/tutorial_error_mitigation.py b/demonstrations/tutorial_error_mitigation.py index e75f6f452f..133f63a469 100644 --- a/demonstrations/tutorial_error_mitigation.py +++ b/demonstrations/tutorial_error_mitigation.py @@ -273,7 +273,7 @@ def executor(circuits, dev=dev_noisy): ) circuits_with_meas.append(circuit_with_meas) - return qml.execute(circuits_with_meas, dev, gradient_fn=None) + return qml.execute(circuits_with_meas, dev, diff_method=None) ############################################################################## @@ -540,7 +540,7 @@ def executor(circuit): circuits, postproc = qml.transforms.split_non_commuting( circuit_with_meas, grouping_strategy=None ) - circuits_executed = qml.execute(circuits, dev_noisy, gradient_fn=None) + circuits_executed = qml.execute(circuits, dev_noisy, diff_method=None) return postproc(circuits_executed) mitig_energy = execute_with_zne(circuit, executor, scale_noise=fold_global) diff --git a/demonstrations/tutorial_falqon.metadata.json b/demonstrations/tutorial_falqon.metadata.json index 1df5f4ec09..ff99d8f9f3 100644 --- a/demonstrations/tutorial_falqon.metadata.json +++ b/demonstrations/tutorial_falqon.metadata.json @@ -9,7 +9,7 @@ } ], "dateOfPublication": "2021-05-21T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "dateOfLastModification": "2025-01-10T00:00:00+00:00", "categories": [ "Optimization" ], diff --git a/demonstrations/tutorial_falqon.py b/demonstrations/tutorial_falqon.py index 98a2e1bbdc..2e0cc30de7 100644 --- a/demonstrations/tutorial_falqon.py +++ b/demonstrations/tutorial_falqon.py @@ -130,7 +130,9 @@ edges = [(0, 1), (1, 2), (2, 0), (2, 3), (1, 4)] graph = nx.Graph(edges) -nx.draw(graph, with_labels=True, node_color="#e377c2") +positions = nx.spring_layout(graph, seed=1) +nx.draw(graph, with_labels=True, node_color="#e377c2", pos=positions) +plt.show() ###################################################################### # We must first encode this combinatorial problem into a cost Hamiltonian :math:`H_c.` This ends up being @@ -320,7 +322,9 @@ def prob_circuit(): graph = nx.Graph(edges) cmap = ["#00b4d9"]*3 + ["#e377c2"]*2 -nx.draw(graph, with_labels=True, node_color=cmap) +positions = nx.spring_layout(graph, seed=1) +nx.draw(graph, with_labels=True, node_color=cmap, pos=positions) +plt.show() ###################################################################### # Benchmarking FALQON @@ -394,7 +398,9 @@ def prob_circuit(): new_edges = [(0, 1), (1, 2), (2, 0), (2, 3), (1, 4), (4, 5), (5, 2), (0, 6)] new_graph = nx.Graph(new_edges) -nx.draw(new_graph, with_labels=True, node_color="#e377c2") +positions = nx.spring_layout(new_graph, seed=1) +nx.draw(new_graph, with_labels=True, node_color="#e377c2", pos=positions) +plt.show() ###################################################################### # We can now use the PennyLane QAOA module to create a QAOA circuit corresponding to the MaxClique problem. For this diff --git a/demonstrations/tutorial_fermionic_operators.metadata.json b/demonstrations/tutorial_fermionic_operators.metadata.json index 6ac634f0ab..46fc8ef4a2 100644 --- a/demonstrations/tutorial_fermionic_operators.metadata.json +++ b/demonstrations/tutorial_fermionic_operators.metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2023-06-27T00:00:00+00:00", - "dateOfLastModification": "2024-10-30T00:00:00+00:00", + "dateOfLastModification": "2024-12-13T00:00:00+00:00", "categories": [ "Quantum Chemistry" ], diff --git a/demonstrations/tutorial_fermionic_operators.py b/demonstrations/tutorial_fermionic_operators.py index b41b286cd1..eae85e2a51 100644 --- a/demonstrations/tutorial_fermionic_operators.py +++ b/demonstrations/tutorial_fermionic_operators.py @@ -108,7 +108,7 @@ # The matrix representation of the qubit Hamiltonian in the computational basis can be diagonalized # to get its eigenpairs. -from pennylane import numpy as np +import numpy as np val, vec = np.linalg.eigh(h.sparse_matrix().toarray()) print(f"eigenvalues:\n{val}") @@ -136,9 +136,10 @@ # 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 = np.array([[-0.67294, 0.0, 0.0], [0.67294, 0.0, 0.0]], requires_grad=False) +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 diff --git a/demonstrations/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition.metadata.json b/demonstrations/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition.metadata.json index f3fd505a54..1efc93770b 100644 --- a/demonstrations/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition.metadata.json +++ b/demonstrations/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition.metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2024-12-19T00:00:00+00:00", - "dateOfLastModification": "2024-01-07T09:00:00+00:00", + "dateOfLastModification": "2025-01-10T00:00:00+00:00", "categories": [ "Quantum Computing", "Algorithms" diff --git a/demonstrations/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition.py b/demonstrations/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition.py index 4172e17604..a4f5439a83 100644 --- a/demonstrations/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition.py +++ b/demonstrations/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition.py @@ -91,21 +91,21 @@ g = [op.pauli_rep for op in g] ############################################################################## -# +# # Cartan decomposition # -------------------- -# +# # A Cartan decomposition is a bipartition :math:`\mathfrak{g} = \mathfrak{k} \oplus \mathfrak{m}` into a vertical subspace # :math:`\mathfrak{k}` and an orthogonal horizontal subspace :math:`\mathfrak{m}.` In practice, it can be induced by an -# involution function :math:`\Theta` that fulfills :math:`\Theta(\Theta(g)) = g \ \forall g \in \mathfrak{g}.` Different -# involutions lead to different types of Cartan decompositions, which have been fully classified by Cartan +# involution function :math:`\Theta` that fulfills :math:`\Theta(\Theta(g)) = g \ \forall g \in \mathfrak{g}.` Different +# involutions lead to different types of Cartan decompositions, which have been fully classified by Cartan # (see `the classification of symmetric spaces by Cartan `__). -# +# # .. admonition:: Notation # :class: note # # Note that :math:`\mathfrak{k}` is the small letter k in -# `Fraktur `__ and a +# `Fraktur `__ and a # common — not our — choice for the vertical subspace in a Cartan decomposition. # # One common choice of involution is the so-called even-odd involution for Pauli words, @@ -119,7 +119,7 @@ def even_odd_involution(op): even_odd_involution(X(0)), even_odd_involution(X(0) @ Y(3)) ############################################################################## -# +# # The vertical and horizontal subspaces are the two eigenspaces of the involution, corresponding to the :math:`\pm 1` eigenvalues. # In particular, we have :math:`\Theta(\mathfrak{k}) = \mathfrak{k}` and :math:`\Theta(\mathfrak{m}) = - \mathfrak{m}.` # So in order to perform the Cartan decomposition :math:`\mathfrak{g} = \mathfrak{k} \oplus \mathfrak{m},` we simply @@ -151,7 +151,7 @@ def cartan_decomposition(g, involution): ############################################################################## -# We have successfully decomposed the 60-dimensional Lie algebra +# We have successfully decomposed the 60-dimensional Lie algebra # into a 24-dimensional vertical subspace and a 36-dimensional subspace. # # Note that not every bipartition of a Lie algebra constitutes a Cartan decomposition. @@ -167,7 +167,7 @@ def cartan_decomposition(g, involution): # In particular, :math:`\mathfrak{k}` is closed under commutation and is therefore a subalgebra, whereas :math:`\mathfrak{m}` is not. # This also has the consequence that the associated Lie group :math:`\mathcal{K} := e^{i \mathfrak{k}}` is a subgroup # of the associated Lie group :math:`\mathcal{G} := e^{i \mathfrak{g}}.` -# +# # We mentioned earlier that we are aiming to do a special case of KAK decomposition where the second unitary :math:`K_2 = K_1^\dagger.` # This is possible whenever the operator that we want to decompose is in the horizontal subspace :math:`\mathfrak{m}`, i.e. we want :math:`\Theta(H) = -H`. # The chosen :math:`H` and :math:`\Theta` fulfill this property, as can be easily verified. @@ -180,7 +180,7 @@ def cartan_decomposition(g, involution): # # Cartan subalgebra # ----------------- -# +# # With this we have identified the first subgroup, :math:`\mathcal{K},` of the KAK decomposition. The other subgroup # is induced by the so-called (horizontal) Cartan subalgebra :math:`\mathfrak{h}.` This is a maximal Abelian subalgebra of :math:`\mathfrak{m}` and it is not unique. # For the case of Pauli words, we can simply pick any element in :math:`\mathfrak{m}` and collect all other operators in :math:`\mathfrak{m}` @@ -237,19 +237,19 @@ def cartan_subalgebra(m, which=0): ############################################################################## # We now have the Cartan decomposition :math:`\mathfrak{g} = \mathfrak{k} \oplus \tilde{\mathfrak{m}} \oplus \mathfrak{h}` # and with that all the necessary ingredients for the KAK decomposition. -# +# # Variational KhK decomposition # ----------------------------- # # The KAK theorem is not constructive in the sense that it proves that there exists such a decomposition, but there is no general way of obtaining # it. In particular, there are no linear algebra subroutines implemented in ``numpy`` or ``scipy`` that just compute it for us. -# Here, we follow the construction of [#Kökcü]_ for the special case of :math:`H` being in the horizontal space and the decomposition +# Here, we follow the construction of [#Kökcü]_ for the special case of :math:`H` being in the horizontal space and the decomposition # simplifying to :math:`H = K^\dagger h K`. # # The authors propose to find a local extremum of the cost function -# +# # .. math:: f(\theta) = \langle K(\theta) v K(\theta)^\dagger, H\rangle -# +# # where :math:`\langle \cdot, \cdot \rangle` is some inner product (in our case, the trace inner product :math:`\langle A, B \rangle = \text{tr}(A^\dagger B)).` # This construction uses the operator :math:`v = \sum_j \pi^j h_j \in \mathfrak{h}` # that is such that :math:`e^{i t v}` is dense in :math:`e^{i \mathcal{h}}.` @@ -264,20 +264,20 @@ def cartan_subalgebra(m, which=0): ############################################################################## -# +# # This procedure has the advantage that we can use an already decomposed ansatz -# +# # .. math:: K(\theta) = \prod_j e^{-i \theta_j k_j} -# +# # for the vertical unitary. -# +# # Now we just have to define the cost function and find an extremum. # In this case, we are going to use gradient descent to minimize the cost function to a minimum. # We are going to use ``jax`` and ``optax`` and write some boilerplate for the optimization procedure. # -# .. note:: -# You can check our demos on parameter optimization in JAX with -# :doc:`Optax ` or +# .. note:: +# You can check our demos on parameter optimization in JAX with +# :doc:`Optax ` or # :doc:`JAXOpt `. # @@ -338,7 +338,7 @@ def loss(theta): theta0 = jnp.ones(len(k), dtype=float) -thetas, energy, _ = run_opt(loss, theta0, n_epochs=600, lr=0.05) +thetas, energy, _ = run_opt(loss, theta0, n_epochs=1000, lr=0.05) plt.plot(energy - np.min(energy)) plt.xlabel("epochs") plt.ylabel("cost") @@ -355,7 +355,7 @@ def loss(theta): ############################################################################## # The special element :math:`h_0` from the Cartan subalgebra :math:`\mathfrak{h}` is given by # rotating the Hamiltonian by the critical :math:`K_c,` -# +# # .. math:: h_0 = K_c^\dagger H K_c. h_0_m = Kc_m.conj().T @ H_m @ Kc_m @@ -368,18 +368,18 @@ def loss(theta): not h_vspace.is_independent(h_0.pauli_rep) ############################################################################## -# +# # The fact that :math:`K_c^\dagger H K_c \in \mathfrak{h}` is crucial for this decomposition to be valid and meaningful. # Otherwise, :math:`h_0` could be anything and we arrive back at the original problem of decomposing :math:`e^{-i t h_0}`. # Here we know that :math:`h_0` is composed of elements of an Abelian Lie algebra :math:`\mathfrak{h}`, such that we can -# trivially decompose its unitary as -# +# trivially decompose its unitary as +# # .. math:: e^{-i t h_0} = e^{-i t \sum_{j=1}^{|\mathfrak{h}|} c_j h_j} = \prod_{j=1}^{|\mathfrak{h}|} e^{-i t c_j h_j}. # # Overall, this gives us the KhK decomposition of :math:`H,` -# +# # .. math:: H = K_c h_0 K_c^\dagger. -# +# # This trivially reproduces the original Hamiltonian. # @@ -407,9 +407,6 @@ def trace_distance(A, B): trace_distance(U_exact_m, U_kak_m) - - - ############################################################################## # Indeed we find that the KAK decomposition that we found reproduces the unitary evolution operator. # Note that this is valid for arbitrary :math:`t,` such that the Hamiltonian simulation operator has a fixed depth. @@ -431,7 +428,7 @@ def trace_distance(A, B): ############################################################################## # Time evolutions # --------------- -# +# # We compute multiple time evolutions for different times and compare Suzuki–Trotter products with the KAK decomposition circuit. # @@ -477,28 +474,27 @@ def compute_res(Us): ############################################################################## -# +# # Conclusion # ---------- -# +# # The KAK theorem is a very general mathematical result with far-reaching consequences. # While there is no canonical way of obtaining an actual decomposition in practice, we followed # the approach of [#Kökcü]_ which uses a specifically designed loss function and variational # optimization to find the decomposition. # This approach has the advantage that the resulting decomposition is itself already decomposed in terms of rotation gates in the original Lie algebra, # as opposed to other methods such as [#Chu]_ that find :math:`K` as a whole. -# We provided a flexible pipeline that lets users find KAK decompositions in PennyLane for systems with small +# We provided a flexible pipeline that lets users find KAK decompositions in PennyLane for systems with small # DLA (:doc:`Dynamical Lie Algebras `) and specifically decomposed the Heisenberg model Hamiltonian with :math:`n=4` qubits that has a DLA of dimension :math:`60` (:math:`\left(\mathfrak{s u}(2^{n-2})\right)^{\oplus 4}`). # -# As most DLAs scale exponentially in the number of qubits, KAK decompositions are limited to small system sizes +# As most DLAs scale exponentially in the number of qubits, KAK decompositions are limited to small system sizes # or special cases of systems with small DLAs. # This is in line with the notion that fast-forwarding is generally not possible and limited to special systems. # In particular, a KAK decomposition is ultimately always a fast-forwarding of a Hamiltonian. - ############################################################################## -# +# # References # ---------- # diff --git a/demonstrations/tutorial_givens_rotations.metadata.json b/demonstrations/tutorial_givens_rotations.metadata.json index 7ce03efc41..b7e2d7694b 100644 --- a/demonstrations/tutorial_givens_rotations.metadata.json +++ b/demonstrations/tutorial_givens_rotations.metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2021-06-30T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-12-13T00:00:00+00:00", "categories": [ "Quantum Chemistry" ], diff --git a/demonstrations/tutorial_givens_rotations.py b/demonstrations/tutorial_givens_rotations.py index ddbd9ab254..01ce63c636 100644 --- a/demonstrations/tutorial_givens_rotations.py +++ b/demonstrations/tutorial_givens_rotations.py @@ -185,21 +185,23 @@ """ import pennylane as qml -import numpy as np +from jax import numpy as jnp +import jax -dev = qml.device('default.qubit', wires=3) +jax.config.update("jax_enable_x64", True) +dev = qml.device('lightning.qubit', wires=3) -@qml.qnode(dev, interface="autograd") +@qml.qnode(dev, interface="jax") def circuit(x, y): # prepares the reference state |100> - qml.BasisState(np.array([1, 0, 0]), wires=[0, 1, 2]) + 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 * np.arcsin(np.sqrt(1/3)) -y = -2 * np.arcsin(np.sqrt(1/2)) +x = -2 * jnp.arcsin(jnp.sqrt(1/3)) +y = -2 * jnp.arcsin(jnp.sqrt(1/2)) print(circuit(x, y)) ############################################################################## @@ -258,12 +260,14 @@ def circuit(x, y): ############################################################################## # Now we continue to build the circuit: -dev2 = qml.device('default.qubit', wires=6) +from jax import random + +dev2 = qml.device('lightning.qubit', wires=6) -@qml.qnode(dev2, interface="autograd") +@qml.qnode(dev2, interface="jax") def circuit2(x, y): # prepares reference state - qml.BasisState(np.array([1, 1, 1, 0, 0, 0]), wires=[0, 1, 2, 3, 4, 5]) + 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) @@ -273,8 +277,10 @@ def circuit2(x, y): return qml.state() # random angles of rotation -x = np.random.normal(0, 1, len(singles)) -y = np.random.normal(0, 1, len(doubles)) +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) @@ -283,6 +289,8 @@ def circuit2(x, y): # 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) @@ -356,17 +364,17 @@ def circuit2(x, y): dev = qml.device('default.qubit', wires=6) -@qml.qnode(dev, interface="autograd") +@qml.qnode(dev, interface="jax") def circuit3(x, y, z): - qml.BasisState(np.array([1, 1, 0, 0, 0, 0]), wires=[i for i in range(6)]) + 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 * np.arcsin(np.sqrt(1/4)) -y = -2 * np.arcsin(np.sqrt(1/3)) -z = -2 * np.arcsin(np.sqrt(1/2)) +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] @@ -380,9 +388,9 @@ def circuit3(x, y, z): # 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="autograd") +@qml.qnode(dev, interface="jax") def circuit4(x, y, z): - qml.BasisState(np.array([1, 1, 0, 0, 0, 0]), wires=[i for i in range(6)]) + 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 @@ -438,9 +446,9 @@ def circuit4(x, y, z): dev = qml.device('default.qubit', wires=4) -@qml.qnode(dev, interface="autograd") +@qml.qnode(dev, interface="jax") def state_preparation(params): - qml.BasisState(np.array([1, 1, 0, 0]), wires=[0, 1, 2, 3]) + 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 @@ -450,11 +458,11 @@ def state_preparation(params): return qml.state() n = 6 -params = np.array([-2 * np.arcsin(1/np.sqrt(n-i)) for i in range(n-1)]) +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[np.abs(output) < 1e-10] = 0 +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) @@ -495,4 +503,3 @@ def state_preparation(params): # # About the author # ---------------- -# .. include:: ../_static/authors/juan_miguel_arrazola.txt \ No newline at end of file diff --git a/demonstrations/tutorial_initial_state_preparation.metadata.json b/demonstrations/tutorial_initial_state_preparation.metadata.json index 9ee85aaabf..fc1cf3178f 100644 --- a/demonstrations/tutorial_initial_state_preparation.metadata.json +++ b/demonstrations/tutorial_initial_state_preparation.metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2023-10-20T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-12-13T00:00:00+00:00", "categories": [ "Quantum Chemistry" ], diff --git a/demonstrations/tutorial_initial_state_preparation.py b/demonstrations/tutorial_initial_state_preparation.py index 589546603a..02d7374442 100644 --- a/demonstrations/tutorial_initial_state_preparation.py +++ b/demonstrations/tutorial_initial_state_preparation.py @@ -49,13 +49,11 @@ from pyscf import gto, scf, ci from pennylane.qchem import import_state -from pennylane import numpy as np +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) +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() @@ -76,7 +74,7 @@ ############################################################################## # CCSD states # ~~~~~~~~~~~ -# The function :func:`~.pennylane.qchem.import_state` is general and works similarly for CCSD. It can +# 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 @@ -159,8 +157,6 @@ # Let's take this opportunity to create the Hartree-Fock initial state, to compare the # other states against it later on. -from pennylane import numpy as np - hf_primer = ([[3, 0, 0]], np.array([1.0])) wf_hf = import_state(hf_primer) @@ -224,10 +220,11 @@ 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 = np.array([[0, 0, 0], [0, 0, R/0.529], [0, 0, 2*R/0.529]]) +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) @@ -252,19 +249,32 @@ def circuit_VQE(theta, initial_state): 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 = qml.GradientDescentOptimizer(stepsize=0.4) -theta = np.array(np.zeros(len(excitations)), requires_grad=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: - theta, prev_energy = opt.step_and_cost(circuit_VQE, theta, initial_state=wf_hf) - new_energy = circuit_VQE(theta, initial_state=wf_hf) + 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") @@ -273,18 +283,30 @@ def circuit_VQE(theta, initial_state): ############################################################################## # And compare with how things go when you run it with the CISD initial state: -theta = np.array(np.zeros(len(excitations)), requires_grad=True) + +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: - theta, prev_energy = opt.step_and_cost(circuit_VQE, theta, initial_state=wf_cisd) - new_energy = circuit_VQE(theta, initial_state=wf_cisd) + 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.") +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 @@ -339,4 +361,3 @@ def circuit_VQE(theta, initial_state): # # About the author # ---------------- -# .. include:: ../_static/authors/stepan_fomichev.txt diff --git a/demonstrations/tutorial_intro_qrom.metadata.json b/demonstrations/tutorial_intro_qrom.metadata.json index 9fe232c5f7..54ec124cd5 100644 --- a/demonstrations/tutorial_intro_qrom.metadata.json +++ b/demonstrations/tutorial_intro_qrom.metadata.json @@ -9,7 +9,7 @@ } ], "dateOfPublication": "2024-09-18T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-11-18T00:00:00+00:00", "categories": [ "Algorithms", "Quantum Computing" diff --git a/demonstrations/tutorial_intro_qrom.py b/demonstrations/tutorial_intro_qrom.py index efceb77449..687f26da88 100644 --- a/demonstrations/tutorial_intro_qrom.py +++ b/demonstrations/tutorial_intro_qrom.py @@ -75,7 +75,7 @@ # This line is included for drawing purposes only. -@partial(qml.devices.preprocess.decompose, stopping_condition=lambda obj: False, max_expansion=1) +@partial(qml.transforms.decompose, max_expansion=1) @qml.qnode(dev) def circuit(index): @@ -225,7 +225,7 @@ def circuit(index): # Line added for drawing purposes only -@partial(qml.devices.preprocess.decompose, stopping_condition=lambda obj: False, max_expansion=2) +@partial(qml.transforms.decompose, max_expansion=2) @qml.qnode(qml.device("default.qubit", shots=1)) def circuit(index): qml.BasisState(index, wires=control_wires) diff --git a/demonstrations/tutorial_intro_qsvt.metadata.json b/demonstrations/tutorial_intro_qsvt.metadata.json index 2a2d3d128e..87108cd2d8 100644 --- a/demonstrations/tutorial_intro_qsvt.metadata.json +++ b/demonstrations/tutorial_intro_qsvt.metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2023-05-23T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-12-05T00:00:00+00:00", "categories": [ "Algorithms", "Quantum Computing" diff --git a/demonstrations/tutorial_intro_qsvt.py b/demonstrations/tutorial_intro_qsvt.py index b54bc0b88d..ca1cd7cc06 100644 --- a/demonstrations/tutorial_intro_qsvt.py +++ b/demonstrations/tutorial_intro_qsvt.py @@ -86,12 +86,11 @@ find :math:`d+1` angles that implement any real polynomial of parity :math:`d \mod 2` and maximum degree :math:`d.` Multiple QSP sequences can then be used to implement real polynomials of indefinite parity. Finding the desired angles can be done efficiently in practice, but identifying the best -methods is an active area of research. You can learn more in our `QSP demo `_ -and in Ref. [#unification]_. +methods is an active area of research. The :class:`~pennylane.qsvt` function in PennyLane computes the angles internally. -For now, let's look at a simple example of how quantum signal processing can be implemented using +Let's look at a simple example of how quantum signal processing can be implemented using PennyLane. We aim to perform a transformation by the Legendre polynomial -:math:`(5 x^3 - 3x)/2,` for which we use pre-computed optimal angles. +:math:`(5 x^3 - 3x)/2`. As you will soon learn, QSP can be viewed as a special case of QSVT. We thus use the :func:`~.pennylane.qsvt` operation to construct the output matrix and compare the resulting transformation to the target polynomial. @@ -103,24 +102,18 @@ import matplotlib.pyplot as plt -def target_poly(a): - return 0.5 * (5 * a**3 - 3 * a) - - -# pre-optimized angles -angles = [-0.20409113, -0.91173829, 0.91173829, 0.20409113] +target_poly = [0, -3 * 0.5, 0, 5 * 0.5] def qsvt_output(a): # output matrix - out = qml.matrix(qml.qsvt(a, angles, wires=[0])) + out = qml.matrix(qml.qsvt(a, target_poly, encoding_wires=[0], block_encoding="embedding")) return out[0, 0] # top-left entry a_vals = np.linspace(-1, 1, 50) qsvt = [np.real(qsvt_output(a)) for a in a_vals] # neglect small imaginary part -target = [target_poly(a) for a in a_vals] - +target = [np.polyval(target_poly[::-1], a) for a in a_vals] # evaluate polynomial plt.plot(a_vals, target, label="target") plt.plot(a_vals, qsvt, "*", label="qsvt") @@ -258,7 +251,9 @@ def qsvt_output(a): eigvals = np.linspace(-1, 1, 16) A = np.diag(eigvals) # 16-dim matrix wire_order = list(range(5)) -U_A = qml.matrix(qml.qsvt, wire_order=wire_order)(A, angles, wires=wire_order) # block-encoded in 5-qubit system +U_A = qml.matrix(qml.qsvt, wire_order=wire_order)( + A, target_poly, encoding_wires=wire_order, block_encoding="embedding" +) # block-encoded in 5-qubit system qsvt_A = np.real(np.diagonal(U_A))[:16] # retrieve transformed eigenvalues diff --git a/demonstrations/tutorial_liesim_extension.metadata.json b/demonstrations/tutorial_liesim_extension.metadata.json index c91f1393be..7da2c15c82 100644 --- a/demonstrations/tutorial_liesim_extension.metadata.json +++ b/demonstrations/tutorial_liesim_extension.metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2024-06-18T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2025-01-13T00:00:00+00:00", "categories": [ "Quantum Computing", "Quantum Machine Learning" diff --git a/demonstrations/tutorial_liesim_extension.py b/demonstrations/tutorial_liesim_extension.py index b0f3226e98..b11a640b6e 100644 --- a/demonstrations/tutorial_liesim_extension.py +++ b/demonstrations/tutorial_liesim_extension.py @@ -133,7 +133,7 @@ def TFIM(n): # # For that, let us construct a concrete example. First we pick two elements from :math:`\mathfrak{g}` such that their product is not in :math:`\mathfrak{g}.` -p = dla[-5] @ dla[-2] +p = dla[-5] @ dla[-1] p = next(iter(p)) # strip any scalar coefficients dla_vspace = qml.pauli.PauliVSpace(dla, dtype=complex) dla_vspace.is_independent(p.pauli_rep) @@ -414,9 +414,11 @@ def Moment_step(ops, dla): # # We can now choose arbitrary DLA gates and a maximum of `one` :math:`P` gate to evolve the expectation value vector. +P_index = Moment[comp_moment].index(1.*p) # pick the gate in the Moment space that p corresponds to + e_t = e_in e_t = expm(0.5 * adjoint_repr[dim_g-1]) @ e_t # the same U gate -e_t = expm(0.5 * adjoint_repr[74]) @ e_t # the same P gate +e_t = expm(0.5 * adjoint_repr[P_index]) @ e_t # the same P gate e_t = expm(0.5 * adjoint_repr[dim_g-2]) @ e_t # the same V gate ############################################################################## diff --git a/demonstrations/tutorial_mapping.metadata.json b/demonstrations/tutorial_mapping.metadata.json index f7be98076a..fe0ee177a4 100644 --- a/demonstrations/tutorial_mapping.metadata.json +++ b/demonstrations/tutorial_mapping.metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2024-05-06T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "dateOfLastModification": "2024-12-13T00:00:00+00:00", "categories": [ "Algorithms", "Quantum Computing", diff --git a/demonstrations/tutorial_mapping.py b/demonstrations/tutorial_mapping.py index e1f1911189..96a757a506 100644 --- a/demonstrations/tutorial_mapping.py +++ b/demonstrations/tutorial_mapping.py @@ -202,11 +202,14 @@ # coordinates. from pennylane import qchem -from pennylane import numpy as np +from jax import numpy as jnp +import jax + +jax.config.update("jax_enable_x64", True) symbols = ['H', 'H'] -geometry = np.array([[0.0, 0.0, -0.69434785], - [0.0, 0.0, 0.69434785]], requires_grad = False) +geometry = jnp.array([[0.0, 0.0, -0.69434785], + [0.0, 0.0, 0.69434785]]) mol = qchem.Molecule(symbols, geometry) @@ -289,7 +292,7 @@ # 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 = np.array([0.22347661, 0.0, 0.0]) +params = jnp.array([0.22347661, 0.0, 0.0]) dev = qml.device("default.qubit", wires=qubits) diff --git a/demonstrations/tutorial_measurement_optimize.metadata.json b/demonstrations/tutorial_measurement_optimize.metadata.json index 597ab1dfff..c8e5d19f8c 100644 --- a/demonstrations/tutorial_measurement_optimize.metadata.json +++ b/demonstrations/tutorial_measurement_optimize.metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2021-01-18T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2025-01-10T00:00:00+00:00", "categories": [ "Quantum Chemistry" ], diff --git a/demonstrations/tutorial_measurement_optimize.py b/demonstrations/tutorial_measurement_optimize.py index 860a8700e6..6722bf4338 100644 --- a/demonstrations/tutorial_measurement_optimize.py +++ b/demonstrations/tutorial_measurement_optimize.py @@ -110,11 +110,13 @@ import functools import warnings -from pennylane import numpy as np +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] +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) @@ -141,7 +143,7 @@ ) # generate the cost function -@qml.qnode(dev, interface="autograd") +@qml.qnode(dev, interface="jax") def cost_circuit(params): ansatz(params, wires=range(num_qubits)) return qml.expval(H) @@ -150,7 +152,9 @@ def cost_circuit(params): # If we evaluate this cost function, we can see that it corresponds to 15 different # executions under the hood—one per expectation value: -params = np.random.normal(0, np.pi, len(singles) + len(doubles)) +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)) @@ -387,20 +391,20 @@ def cost_circuit(params): dev = qml.device("default.qubit", wires=3) -@qml.qnode(dev, interface="autograd") +@qml.qnode(dev, interface="jax") def circuit1(weights): qml.StronglyEntanglingLayers(weights, wires=range(3)) return qml.expval(obs[0]) -@qml.qnode(dev, interface="autograd") +@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) -rng = np.random.default_rng(192933) -weights = rng.normal(scale=0.1, size=param_shape) +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)) @@ -409,15 +413,15 @@ def 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="autograd") +@qml.qnode(dev, interface="jax") def circuit_qwc(weights): qml.StronglyEntanglingLayers(weights, wires=range(3)) # rotate wire 0 into the shared eigenbasis - qml.RY(-np.pi / 2, wires=0) + qml.RY(-jnp.pi / 2, wires=0) # rotate wire 1 into the shared eigenbasis - qml.RX(np.pi / 2, wires=1) + qml.RX(jnp.pi / 2, wires=1) # wire 2 does not require a rotation @@ -428,7 +432,6 @@ def circuit_qwc(weights): 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 @@ -440,12 +443,12 @@ def circuit_qwc(weights): # 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 = np.kron(np.kron([1, -1], [1, -1]), [1, 1]) -eigenvalues_XIZ = np.kron(np.kron([1, -1], [1, 1]), [1, -1]) +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 = ", np.dot(eigenvalues_XYI, rotated_probs)) -print("Expectation value of XIZ = ", np.dot(eigenvalues_XIZ, rotated_probs)) +print("Expectation value of XYI = ", jnp.dot(eigenvalues_XYI, rotated_probs)) +print("Expectation value of XIZ = ", jnp.dot(eigenvalues_XIZ, rotated_probs)) ############################################################################## @@ -455,7 +458,7 @@ def circuit_qwc(weights): # 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="autograd") +@qml.qnode(dev, interface="jax") def circuit(weights): qml.StronglyEntanglingLayers(weights, wires=range(3)) return [ @@ -606,14 +609,16 @@ def format_pauli_word(term): ]) 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" + edge_color="#c1c1c1", ) ############################################################################## @@ -621,7 +626,7 @@ def format_pauli_word(term): # version above!): C = nx.complement(G) - coords = nx.spring_layout(C) + coords = nx.spring_layout(C, seed=1) nx.draw( C, @@ -634,7 +639,6 @@ def format_pauli_word(term): edge_color="#c1c1c1" ) - ############################################################################## # Now that we have the complement graph, we can perform a greedy coloring to # determine the minimum number of QWC groups: @@ -711,16 +715,17 @@ def format_pauli_word(term): # However, this isn't strictly necessary—recall previously that the QNode # has the capability to *automatically* measure qubit-wise commuting observables! -dev = qml.device("default.qubit", wires=4) +dev = qml.device("lightning.qubit", wires=4) -@qml.qnode(dev, interface="autograd") +@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) -weights = np.random.normal(scale=0.1, size=param_shape) -result = [circuit(weights, group=g) for g in obs_groupings] +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): @@ -728,7 +733,7 @@ def circuit(weights, group=None, **kwargs): # Since all the coefficients of the Hamiltonian are unity, # we can simply sum the expectation values. -print(" = ", np.sum(np.hstack(result))) +print(" = ", jnp.sum(jnp.hstack(result))) ############################################################################## @@ -737,9 +742,9 @@ def circuit(weights, group=None, **kwargs): # problems), we can use the option ``grouping_type="qwc"`` in :class:`~.pennylane.Hamiltonian` to # automatically optimize the measurements. -H = qml.Hamiltonian(coeffs=np.ones(len(terms)), observables=terms, grouping_type="qwc") +H = qml.Hamiltonian(coeffs=jnp.ones(len(terms)), observables=terms, grouping_type="qwc") _, H_ops = H.terms() -@qml.qnode(dev, interface="autograd") +@qml.qnode(dev, interface="jax") def cost_fn(weights): qml.StronglyEntanglingLayers(weights, wires=range(4)) return qml.expval(H) @@ -790,7 +795,7 @@ def cost_fn(weights): # # 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 `__. +# in the `PennyLane Datasets library `__. ############################################################################## # References @@ -836,4 +841,3 @@ def cost_fn(weights): # # About the author # ---------------- -# .. include:: ../_static/authors/josh_izaac.txt \ No newline at end of file diff --git a/demonstrations/tutorial_mol_geo_opt.metadata.json b/demonstrations/tutorial_mol_geo_opt.metadata.json index c876927c3d..13b4eb8be7 100644 --- a/demonstrations/tutorial_mol_geo_opt.metadata.json +++ b/demonstrations/tutorial_mol_geo_opt.metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2021-06-30T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-12-13T00:00:00+00:00", "categories": [ "Quantum Chemistry" ], diff --git a/demonstrations/tutorial_mol_geo_opt.py b/demonstrations/tutorial_mol_geo_opt.py index 50f092ddb6..ef7944ab1e 100644 --- a/demonstrations/tutorial_mol_geo_opt.py +++ b/demonstrations/tutorial_mol_geo_opt.py @@ -100,10 +100,13 @@ """ -from pennylane import numpy as np +import jax +from jax import numpy as jnp + +jax.config.update("jax_enable_x64", True) symbols = ["H", "H", "H"] -x = np.array([0.028, 0.054, 0.0, 0.986, 1.610, 0.0, 1.855, 0.002, 0.0], requires_grad=True) +x = jnp.array([[0.028, 0.054, 0.0], [0.986, 1.610, 0.0], [1.855, 0.002, 0.0]]) ############################################################################## # Next, we need to build the parametrized electronic Hamiltonian :math:`H(x).` @@ -191,12 +194,12 @@ def H(x): # The ``hf`` array is used by the :class:`~.pennylane.BasisState` operation to initialize # the qubit register. Then, the :class:`~.pennylane.DoubleExcitation` operations are applied # First, we define the quantum device used to compute the expectation value. -# In this example, we use the ``lightning.qubit`` simulator: +# In this example, we use the ``default.qubit`` simulator: num_wires = 6 -dev = qml.device("lightning.qubit", wires=num_wires) +dev = qml.device("default.qubit", wires=num_wires) -@qml.qnode(dev, interface="autograd") +@qml.qnode(dev, interface="jax") def circuit(params, obs, wires): qml.BasisState(hf, wires=wires) qml.DoubleExcitation(params[0], wires=[0, 1, 2, 3]) @@ -259,10 +262,11 @@ def cost(params, x): def finite_diff(f, x, delta=0.01): """Compute the central-difference finite difference of a function""" gradient = [] + x = jnp.ravel(x) for i in range(len(x)): - shift = np.zeros_like(x) - shift[i] += 0.5 * delta + shift = jnp.zeros_like(x) + shift = shift.at[i].set(0.5*delta) res = (f(x + shift) - f(x - shift)) * delta**-1 gradient.append(res) @@ -272,7 +276,7 @@ def finite_diff(f, x, delta=0.01): def grad_x(params, x): grad_h = finite_diff(H, x) grad = [circuit(params, obs=obs, wires=range(num_wires)) for obs in grad_h] - return np.array(grad) + return jnp.array(grad).reshape(x.shape) ############################################################################## @@ -285,18 +289,12 @@ def grad_x(params, x): # each optimization step. This approach does not require nested VQE # optimization of the circuit parameters for each set of nuclear coordinates. # -# We start by defining the classical optimizers: - -opt_theta = qml.GradientDescentOptimizer(stepsize=0.4) -opt_x = qml.GradientDescentOptimizer(stepsize=0.8) - -############################################################################## -# Next, we initialize the circuit parameters :math:`\theta.` The angles +# First, we initialize the circuit parameters :math:`\theta.` The angles # :math:`\theta_1` and :math:`\theta_2` are set to zero so that the # initial state :math:`\vert\Psi(\theta_1, \theta_2)\rangle` # is the Hartree-Fock state. -theta = np.array([0.0, 0.0], requires_grad=True) +theta = jnp.array([0.0, 0.0]) ############################################################################## # The initial set of nuclear coordinates :math:`x,` defined at @@ -305,17 +303,15 @@ def grad_x(params, x): # for the starting geometry that we are aiming to improve due to the electronic # correlation effects included in the trial state :math:`\vert\Psi(\theta)\rangle.` # -# We carry out the optimization over a maximum of 100 steps. +# We carry out the optimization over a maximum of 36 steps. # The circuit parameters and the nuclear coordinates are optimized until the # maximum component of the nuclear gradient :math:`\nabla_x g(\theta,x)` is # less than or equal to :math:`10^{-5}` Hartree/Bohr. Typically, this is the # convergence criterion used for optimizing molecular geometries in # quantum chemistry simulations. -from functools import partial - # store the values of the cost function -energy = [] +energies = [] # store the values of the bond length bond_length = [] @@ -323,33 +319,30 @@ def grad_x(params, x): # Factor to convert from Bohrs to Angstroms bohr_angs = 0.529177210903 -for n in range(100): - - # Optimize the circuit parameters - theta.requires_grad = True - x.requires_grad = False - theta, _ = opt_theta.step(cost, theta, x) - - # Optimize the nuclear coordinates - x.requires_grad = True - theta.requires_grad = False - _, x = opt_x.step(cost, theta, x, grad_fn=grad_x) +for n in range(36): + # gradient for params + g_param = jax.grad(cost, argnums=[0])(theta, x)[0] + theta = theta - 0.8 * g_param - energy.append(cost(theta, x)) - bond_length.append(np.linalg.norm(x[0:3] - x[3:6]) * bohr_angs) + # gradient for coordinates + value, _ = jax.value_and_grad(cost, argnums=1)(theta, x) + grad = grad_x(theta, x) + x = x - 0.8 * grad + energies.append(value) + bond_length.append(jnp.linalg.norm(x[0] - x[1]) * bohr_angs) if n % 4 == 0: - print(f"Step = {n}, E = {energy[-1]:.8f} Ha, bond length = {bond_length[-1]:.5f} A") + print(f"Step = {n}, E = {energies[-1]:.8f} Ha, bond length = {bond_length[-1]:.5f} A") # Check maximum component of the nuclear gradient - if np.max(grad_x(theta, x)) <= 1e-05: + if jnp.max(grad_x(theta, x)) <= 1e-04: break -print("\n" f"Final value of the ground-state energy = {energy[-1]:.8f} Ha") +print("\n" f"Final value of the ground-state energy = {energies[-1]:.8f} Ha") print("\n" "Ground-state equilibrium geometry") print("%s %4s %8s %8s" % ("symbol", "x", "y", "z")) for i, atom in enumerate(symbols): - print(f" {atom} {x[3 * i]:.4f} {x[3 * i + 1]:.4f} {x[3 * i + 2]:.4f}") + print(f" {atom} {x[0][i]:.4f} {x[1][i]:.4f} {x[2][i]:.4f}") ############################################################################## # Next, we plot the values of the ground state energy of the molecule @@ -363,10 +356,10 @@ def grad_x(params, x): # Add energy plot on column 1 E_fci = -1.27443765658 -E_vqe = np.array(energy) +E_vqe = jnp.array(energies) ax1 = fig.add_subplot(121) ax1.plot(range(n + 1), E_vqe - E_fci, "go", ls="dashed") -ax1.plot(range(n + 1), np.full(n + 1, 0.001), color="red") +ax1.plot(range(n + 1), jnp.full(n + 1, 0.001), color="red") ax1.set_xlabel("Optimization step", fontsize=13) ax1.set_ylabel("$E_{VQE} - E_{FCI}$ (Hartree)", fontsize=13) ax1.text(5, 0.0013, r"Chemical accuracy", fontsize=13) @@ -378,7 +371,7 @@ def grad_x(params, x): d_fci = 0.986 ax2 = fig.add_subplot(122) ax2.plot(range(n + 1), bond_length, "go", ls="dashed") -ax2.plot(range(n + 1), np.full(n + 1, d_fci), color="red") +ax2.plot(range(n + 1), jnp.full(n + 1, d_fci), color="red") ax2.set_ylim([0.965, 0.99]) ax2.set_xlabel("Optimization step", fontsize=13) ax2.set_ylabel("bond length ($\AA$)", fontsize=13) @@ -392,7 +385,7 @@ def grad_x(params, x): ############################################################################## # | # Notice that despite the fact that the ground-state energy is already converged -# within chemical accuracy (:math:`0.0016` Ha) after the fourth iteration, more +# within chemical accuracy (:math:`0.0016` Ha) after the first iteration, more # optimization steps are required to find the equilibrium bond length of the # molecule. # diff --git a/demonstrations/tutorial_qaoa_intro.metadata.json b/demonstrations/tutorial_qaoa_intro.metadata.json index 89b69fa6d9..2fc2f5eb95 100644 --- a/demonstrations/tutorial_qaoa_intro.metadata.json +++ b/demonstrations/tutorial_qaoa_intro.metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2020-11-18T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "dateOfLastModification": "2025-01-10T00:00:00+00:00", "categories": [ "Optimization" ], diff --git a/demonstrations/tutorial_qaoa_intro.py b/demonstrations/tutorial_qaoa_intro.py index 36dc01a337..7caed2f250 100644 --- a/demonstrations/tutorial_qaoa_intro.py +++ b/demonstrations/tutorial_qaoa_intro.py @@ -246,8 +246,9 @@ def circuit(params, **kwargs): edges = [(0, 1), (1, 2), (2, 0), (2, 3)] graph = nx.Graph(edges) +positions = nx.spring_layout(graph, seed=1) -nx.draw(graph, with_labels=True) +nx.draw(graph, with_labels=True, pos=positions) plt.show() diff --git a/demonstrations/tutorial_qchem_external.metadata.json b/demonstrations/tutorial_qchem_external.metadata.json index eb5d12152d..63e5c1b469 100644 --- a/demonstrations/tutorial_qchem_external.metadata.json +++ b/demonstrations/tutorial_qchem_external.metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2023-01-03T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-12-13T00:00:00+00:00", "categories": [ "Quantum Chemistry", "Devices and Performance" ], diff --git a/demonstrations/tutorial_qchem_external.py b/demonstrations/tutorial_qchem_external.py index 77dff37479..5d08033a0b 100644 --- a/demonstrations/tutorial_qchem_external.py +++ b/demonstrations/tutorial_qchem_external.py @@ -48,12 +48,12 @@ """ import pennylane as qml -from pennylane import numpy as np +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]], requires_grad = False) + [ 2.7909, -0.5159, 0.0000]]) molecule = qml.qchem.Molecule(symbols, geometry) H, qubits = qml.qchem.molecular_hamiltonian(molecule, method="pyscf") @@ -227,4 +227,3 @@ # # About the author # ---------------- -# .. include:: ../_static/authors/soran_jahangiri.txt diff --git a/demonstrations/tutorial_qft.metadata.json b/demonstrations/tutorial_qft.metadata.json index d99c9dd532..99d3b07ae4 100644 --- a/demonstrations/tutorial_qft.metadata.json +++ b/demonstrations/tutorial_qft.metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2024-04-16T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-11-18T00:00:00+00:00", "categories": [ "Algorithms", "Quantum Computing" diff --git a/demonstrations/tutorial_qft.py b/demonstrations/tutorial_qft.py index f682bcdbd7..487e1a68c6 100644 --- a/demonstrations/tutorial_qft.py +++ b/demonstrations/tutorial_qft.py @@ -97,7 +97,7 @@ plt.style.use('pennylane.drawer.plot') # This line is to expand the circuit to see the operators -@partial(qml.devices.preprocess.decompose, stopping_condition = lambda obj: False, max_expansion=1) +@partial(qml.transforms.decompose, max_expansion=1) def circuit(): qml.QFT(wires=range(4)) diff --git a/demonstrations/tutorial_qgrnn.metadata.json b/demonstrations/tutorial_qgrnn.metadata.json index 9e0c81ffb2..5bcafc2c23 100644 --- a/demonstrations/tutorial_qgrnn.metadata.json +++ b/demonstrations/tutorial_qgrnn.metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2020-07-27T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "dateOfLastModification": "2025-01-10T00:00:00+00:00", "categories": [ "Quantum Machine Learning" ], diff --git a/demonstrations/tutorial_qgrnn.py b/demonstrations/tutorial_qgrnn.py index 913fc9aea4..2056b89bc0 100644 --- a/demonstrations/tutorial_qgrnn.py +++ b/demonstrations/tutorial_qgrnn.py @@ -224,10 +224,10 @@ ising_graph = nx.cycle_graph(qubit_number) +positions = nx.spring_layout(ising_graph, seed=1) print(f"Edges: {ising_graph.edges}") -nx.draw(ising_graph) - +nx.draw(ising_graph, pos=positions) ###################################################################### @@ -303,8 +303,6 @@ def create_hamiltonian_matrix(n_qubits, graph, weights, bias): plt.show() - - ###################################################################### # Preparing Quantum Data # ^^^^^^^^^^^^^^^^^^^^^^ @@ -472,9 +470,10 @@ def swap_test(control, register1, register2): # Defines the interaction graph for the new qubit system new_ising_graph = nx.complete_graph(reg2) +positions = nx.spring_layout(new_ising_graph, seed=1) print(f"Edges: {new_ising_graph.edges}") -nx.draw(new_ising_graph) +nx.draw(new_ising_graph, pos=positions) ###################################################################### @@ -611,8 +610,6 @@ def cost_function(weight_params, bias_params): plt.show() - - ###################################################################### # These images look very similar, indicating that the QGRNN has done a good job # learning the target Hamiltonian. diff --git a/demonstrations/tutorial_qsvt_hardware.metadata.json b/demonstrations/tutorial_qsvt_hardware.metadata.json index acc4370c21..ed09c58b37 100644 --- a/demonstrations/tutorial_qsvt_hardware.metadata.json +++ b/demonstrations/tutorial_qsvt_hardware.metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2024-09-18T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-12-06T00:00:00+00:00", "categories": [ "Algorithms", "Quantum Computing", diff --git a/demonstrations/tutorial_qsvt_hardware.py b/demonstrations/tutorial_qsvt_hardware.py index 9314e3f87f..e5c3324473 100644 --- a/demonstrations/tutorial_qsvt_hardware.py +++ b/demonstrations/tutorial_qsvt_hardware.py @@ -26,22 +26,31 @@ - **Projection angles**: A list of angles that will determine the coefficients of the polynomial to be applied. - **Block encoding**: The strategy used to encode the Hamiltonian. We will use the :doc:`linear combinations of unitaries ` approach via the PennyLane :class:`~.qml.PrepSelPrep` operation. -Calculating angles is not a trivial task, but there are tools such as `pyqsp `_ that do the job for us. -For instance, to find the angles to apply the polynomial :math:`p(x) = -x + \frac{x^3}{2}+ \frac{x^5}{2},` we can run this code: - -.. code-block:: python - - from pyqsp.angle_sequence import QuantumSignalProcessingPhases - import numpy as np - - # Define the polynomial, the coefficients are in the order of the polynomial degree. - poly = np.array([0,-1, 0, 0.5, 0 , 0.5]) - - ang_seq = QuantumSignalProcessingPhases(poly, signal_operator="Wx") - -The angles obtained after execution are as follows: - +Calculating angles is not a trivial task, but we can use the PennyLane function :func:`~.pennylane.poly_to_angles` +to obtain them. There are also tools such as `pyqsp `_ that can do the job for us. +Let's try both tools to calculate the angles for applying the +polynomial :math:`p(x) = -x + \frac{x^3}{2}+ \frac{x^5}{2}`. + +The :func:`~.pennylane.poly_to_angles` function in PennyLane accepts the coefficients of the +polynomial, ordered from lowest to highest power, as input. We also need to define the routine for +which the angles are computed, which is ``'QSVT'`` here. """ +import pennylane as qml +poly = [0, -1.0, 0, 1/2, 0, 1/2] +angles_pl = qml.poly_to_angles(poly, "QSVT") +print(angles_pl) + +###################################################################### +# To find the angles with ``pyqsp`` we can run this code: +# +# .. code-block:: python +# +# from pyqsp.angle_sequence import QuantumSignalProcessingPhases +# import numpy as np +# +# ang_seq = QuantumSignalProcessingPhases(np.array(poly), signal_operator="Wx") +# +# The angles obtained after execution are as follows: ang_seq = [ -1.5115007723754004, @@ -53,31 +62,18 @@ ] ###################################################################### -# We use these angles to apply the polynomial transformation. -# However, we are not finished yet: these angles have been calculated following the "Wx" -# convention [#unification]_, while :class:`~.qml.PrepSelPrep` follows a different one. Moreover, the angles obtained in the -# context of QSP (the ones given by ``pyqsp``) are not the same as the ones we have to use in QSVT. That is why -# we must transform the angles: - -import numpy as np - +# The ``pyqsp`` angles are obtained in the +# context of QSP and are not the same as the ones we have to use in QSVT. +# We can use the :func:`~.pennylane.transform_angles` function to transform the angles: -def convert_angles(angles): - num_angles = len(angles) - update_vals = np.zeros(num_angles) - - update_vals[0] = 3 * np.pi / 4 - (3 + len(angles) % 4) * np.pi / 2 - update_vals[1:-1] = np.pi / 2 - update_vals[-1] = -np.pi / 4 - - return angles + update_vals - - -angles = convert_angles(ang_seq) -print(angles) +angles_pyqsp = qml.transform_angles(ang_seq, "QSP", "QSVT") +print(angles_pyqsp) ###################################################################### -# Using these angles, we can now start working with the template. +# Note that these angles are not exactly the same as those obtained with +# :func:`~.pennylane.poly_to_angles`, but they will both produce the same polynomial transformation. +# Using the angles computed with :func:`~.pennylane.poly_to_angles` or ``pyqsp``, we can now start +# working with the template. # # QSVT on hardware # ----------------- @@ -88,6 +84,7 @@ def convert_angles(angles): # a Hamiltonian and manually apply the polynomial of interest: import pennylane as qml +import numpy as np from numpy.linalg import matrix_power as mpow coeffs = np.array([0.2, -0.7, -0.6]) @@ -113,8 +110,8 @@ def convert_angles(angles): block_encode = qml.PrepSelPrep(H, control=control_wires) projectors = [ - qml.PCPhase(angles[i], dim=2 ** len(H.wires), wires=control_wires + H.wires) - for i in range(len(angles)) + qml.PCPhase(angles_pl[i], dim=2 ** len(H.wires), wires=control_wires + H.wires) + for i in range(len(angles_pl)) ] diff --git a/demonstrations/tutorial_quantum_circuit_cutting.metadata.json b/demonstrations/tutorial_quantum_circuit_cutting.metadata.json index ff928c43f3..af6563fe52 100644 --- a/demonstrations/tutorial_quantum_circuit_cutting.metadata.json +++ b/demonstrations/tutorial_quantum_circuit_cutting.metadata.json @@ -12,7 +12,7 @@ } ], "dateOfPublication": "2022-09-02T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "dateOfLastModification": "2024-11-12T00:00:00+00:00", "categories": [ "Algorithms", "Quantum Computing" ], diff --git a/demonstrations/tutorial_quantum_circuit_cutting.py b/demonstrations/tutorial_quantum_circuit_cutting.py index 45083d90fe..ea3ed96f5b 100644 --- a/demonstrations/tutorial_quantum_circuit_cutting.py +++ b/demonstrations/tutorial_quantum_circuit_cutting.py @@ -741,10 +741,10 @@ def make_kraus_ops(num_wires: int): tape0 = QuantumTape(ops=ops_0, measurements=tape.measurements, shots=channel_shots[0].item()) tape1 = QuantumTape(ops=ops_1, measurements=tape.measurements, shots=channel_shots[1].item()) -(shots0,) = qml.execute([tape0], device=device, cache=False, gradient_fn=None) +(shots0,) = qml.execute([tape0], device=device, cache=False, diff_method=None) samples[choices == 0] = shots0 -(shots1,) = qml.execute([tape1], device=device, cache=False, gradient_fn=None) +(shots1,) = qml.execute([tape1], device=device, cache=False, diff_method=None) samples[choices == 1] = shots1 ###################################################################### diff --git a/demonstrations/tutorial_qubit_tapering.metadata.json b/demonstrations/tutorial_qubit_tapering.metadata.json index d65cec6324..fc85d10815 100644 --- a/demonstrations/tutorial_qubit_tapering.metadata.json +++ b/demonstrations/tutorial_qubit_tapering.metadata.json @@ -9,7 +9,7 @@ } ], "dateOfPublication": "2022-05-16T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-12-13T00:00:00+00:00", "categories": [ "Quantum Chemistry" ], diff --git a/demonstrations/tutorial_qubit_tapering.py b/demonstrations/tutorial_qubit_tapering.py index f2af09bfa4..ac7845d993 100644 --- a/demonstrations/tutorial_qubit_tapering.py +++ b/demonstrations/tutorial_qubit_tapering.py @@ -122,10 +122,12 @@ coordinates. """ import pennylane as qml -from pennylane import numpy as np +from jax import numpy as jnp +import jax +jax.config.update("jax_enable_x64", True) symbols = ["He", "H"] -geometry = np.array([[0.00000000, 0.00000000, -0.87818361], +geometry = jnp.array([[0.00000000, 0.00000000, -0.87818361], [0.00000000, 0.00000000, 0.87818362]]) molecule = qml.qchem.Molecule(symbols, geometry, charge=1) @@ -169,7 +171,7 @@ H_tapered = qml.taper(H, generators, paulixops, paulix_sector) H_tapered_coeffs, H_tapered_ops = H_tapered.terms() -H_tapered = qml.Hamiltonian(np.real(H_tapered_coeffs), H_tapered_ops) +H_tapered = qml.Hamiltonian(jnp.real(jnp.array(H_tapered_coeffs)), H_tapered_ops) print(H_tapered) ############################################################################## @@ -215,24 +217,24 @@ # Hartree-Fock energies for each Hamiltonian. dev = qml.device("default.qubit", wires=H.wires) -@qml.qnode(dev, interface="autograd") +@qml.qnode(dev, interface="jax") def circuit(): - qml.BasisState(np.array([1, 1, 0, 0]), wires=H.wires) + 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: {np.real(HF_energy):.8f} Ha") +print(f"HF energy: {jnp.real(HF_energy):.8f} Ha") -dev = qml.device("default.qubit", wires=H_tapered.wires) -@qml.qnode(dev, interface="autograd") +dev = qml.device("lightning.qubit", wires=H_tapered.wires) +@qml.qnode(dev, interface="jax") def circuit(): - qml.BasisState(np.array([1, 1]), wires=H_tapered.wires) + 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): {np.real(HF_energy):.8f} Ha") +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. @@ -257,9 +259,9 @@ def circuit(): wire_order=H.wires, op_wires=single) for single in singles ] -dev = qml.device("default.qubit", wires=H_tapered.wires) +dev = qml.device("lightning.qubit", wires=H_tapered.wires) -@qml.qnode(dev, interface="autograd") +@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): @@ -270,13 +272,29 @@ def tapered_circuit(params): # We define an optimizer and the initial values of the circuit parameters and optimize the circuit # parameters with respect to the ground state energy. -optimizer = qml.GradientDescentOptimizer(stepsize=0.5) -params = np.zeros(len(doubles) + len(singles), requires_grad=True) +import optax +import catalyst -for n in range(1, 41): - params, energy = optimizer.step_and_cost(tapered_circuit, params) - if not n % 5: - print(f"n: {n}, E: {energy:.8f} Ha, Params: {params}") +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 @@ -309,6 +327,3 @@ def tapered_circuit(params): # # About the author # ---------------- -# .. include:: ../_static/authors/utkarsh_azad.txt -# -# .. include:: ../_static/authors/soran_jahangiri.txt diff --git a/demonstrations/tutorial_resource_estimation.metadata.json b/demonstrations/tutorial_resource_estimation.metadata.json index 9bc9fc0efe..17f82f2766 100644 --- a/demonstrations/tutorial_resource_estimation.metadata.json +++ b/demonstrations/tutorial_resource_estimation.metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2022-11-21T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-12-13T00:00:00+00:00", "categories": [ "Quantum Chemistry" ], diff --git a/demonstrations/tutorial_resource_estimation.py b/demonstrations/tutorial_resource_estimation.py index c171a76578..f2033e6d41 100644 --- a/demonstrations/tutorial_resource_estimation.py +++ b/demonstrations/tutorial_resource_estimation.py @@ -65,12 +65,15 @@ `6-31g basis set `_ as an example. """ import pennylane as qml -from pennylane import numpy as np +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]], requires_grad=False) + [0.00000000, -1.45278171, -1.00662237]]) ############################################################################## # Then we construct a molecule object and compute the one- and two-electron @@ -315,4 +318,4 @@ # `__ # About the author # ---------------- -# .. include:: ../_static/authors/soran_jahangiri.txt \ No newline at end of file +# .. include:: ../_static/authors/soran_jahangiri.txt diff --git a/demonstrations/tutorial_vqe_spin_sectors.metadata.json b/demonstrations/tutorial_vqe_spin_sectors.metadata.json index e8afdd4075..e656601199 100644 --- a/demonstrations/tutorial_vqe_spin_sectors.metadata.json +++ b/demonstrations/tutorial_vqe_spin_sectors.metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2020-10-13T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-12-13T00:00:00+00:00", "categories": [ "Quantum Chemistry" ], diff --git a/demonstrations/tutorial_vqe_spin_sectors.py b/demonstrations/tutorial_vqe_spin_sectors.py index a65b93352c..ce8664df63 100644 --- a/demonstrations/tutorial_vqe_spin_sectors.py +++ b/demonstrations/tutorial_vqe_spin_sectors.py @@ -49,7 +49,7 @@ nuclear coordinates in `atomic units `_. """ -from pennylane import numpy as np +import numpy as np symbols = ["H", "H"] coordinates = np.array([0.0, 0.0, -0.6614, 0.0, 0.0, 0.6614]) @@ -191,18 +191,17 @@ def circuit(params, wires): # a cost function that can be evaluated with the circuit parameters: -@qml.qnode(dev, interface="autograd") +@qml.qnode(dev, interface="jax") def cost_fn(params): circuit(params, wires=range(qubits)) return qml.expval(H) - ############################################################################## # As a reminder, we also built the total spin operator :math:`\hat{S}^2` for which # we can now define a function to compute its expectation value: -@qml.qnode(dev, interface="autograd") +@qml.qnode(dev, interface="jax") def S2_exp_value(params): circuit(params, wires=range(qubits)) return qml.expval(S2) @@ -227,35 +226,40 @@ def total_spin(params): # Now, we proceed to minimize the cost function to find the ground state. We define # the classical optimizer and initialize the circuit parameters. -opt = qml.GradientDescentOptimizer(stepsize=0.8) -np.random.seed(0) # for reproducibility -theta = np.random.normal(0, np.pi, len(singles) + len(doubles), requires_grad=True) -print(theta) +from jax import random +import optax + +opt = optax.sgd(learning_rate=0.8) # sgd stands for StochasticGradientDescent +key = random.PRNGKey(0) +init_params = random.normal(key, shape=(len(singles) + len(doubles),)) * np.pi ############################################################################## # We carry out the optimization over a maximum of 100 steps, aiming to reach a # convergence tolerance of :math:`10^{-6}` for the value of the cost function. +import catalyst max_iterations = 100 -conv_tol = 1e-06 - -for n in range(max_iterations): - theta, prev_energy = opt.step_and_cost(cost_fn, theta) +prev_energy = 0.0 +@qml.qjit +def update_step(i, params, opt_state): + """Perform a single gradient update step""" + grads = qml.grad(cost_fn)(params) + updates, opt_state = opt.update(grads, opt_state) + params = optax.apply_updates(params, updates) + return (params, opt_state) - energy = cost_fn(theta) - spin = total_spin(theta) +loss_history = [] - conv = np.abs(energy - prev_energy) +opt_state = opt.init(init_params) +params = init_params - if n % 4 == 0: - print(f"Step = {n}, Energy = {energy:.8f} Ha, S = {spin:.4f}") - - if conv <= conv_tol: - break +for i in range(max_iterations): + params, opt_state = update_step(i, params, opt_state) + energy = cost_fn(params) print("\n" f"Final value of the ground-state energy = {energy:.8f} Ha") -print("\n" f"Optimal value of the circuit parameters = {theta}") +print("\n" f"Optimal value of the circuit parameters = {params}") ############################################################################## # As a result, we have estimated the lowest-energy state of the hydrogen molecule @@ -304,13 +308,13 @@ def circuit(params, wires): # and the total spin operator for the new circuit. -@qml.qnode(dev, interface="autograd") +@qml.qnode(dev, interface="jax") def cost_fn(params): circuit(params, wires=range(qubits)) return qml.expval(H) -@qml.qnode(dev, interface="autograd") +@qml.qnode(dev, interface="jax") def S2_exp_value(params): circuit(params, wires=range(qubits)) return qml.expval(S2) @@ -320,29 +324,31 @@ def S2_exp_value(params): # Finally, we generate the new set of initial parameters, and proceed with the VQE algorithm to # optimize the variational circuit. -np.random.seed(0) -theta = np.random.normal(0, np.pi, len(singles) + len(doubles), requires_grad=True) +opt = optax.sgd(learning_rate=0.8) # sgd stands for StochasticGradientDescent +key = random.PRNGKey(0) +init_params = random.normal(key, shape=(len(singles) + len(doubles),)) * np.pi max_iterations = 100 -conv_tol = 1e-06 - -for n in range(max_iterations): - - theta, prev_energy = opt.step_and_cost(cost_fn, theta) - energy = cost_fn(theta) - spin = total_spin(theta) +@qml.qjit +def update_step(i, params, opt_state): + """Perform a single gradient update step""" + grads = qml.grad(cost_fn)(params) + updates, opt_state = opt.update(grads, opt_state) + params = optax.apply_updates(params, updates) + return (params, opt_state) - conv = np.abs(energy - prev_energy) +loss_history = [] - if n % 4 == 0: - print(f"Step = {n}, Energy = {energy:.8f} Ha, S = {spin:.4f}") +opt_state = opt.init(init_params) +params = init_params - if conv <= conv_tol: - break +for i in range(max_iterations): + params, opt_state = update_step(i, params, opt_state) + energy = cost_fn(params) print("\n" f"Final value of the energy = {energy:.8f} Ha") -print("\n" f"Optimal value of the circuit parameters = {theta}") +print("\n" f"Optimal value of the circuit parameters = {params}") ############################################################################## # As expected, the VQE algorithms has found the lowest-energy state with total spin @@ -376,4 +382,3 @@ def S2_exp_value(params): # # About the author # ---------------- -# .. include:: ../_static/authors/alain_delgado.txt diff --git a/demonstrations/tutorial_vqe_vqd.metadata.json b/demonstrations/tutorial_vqe_vqd.metadata.json index cfd303ebac..3b497a13a3 100644 --- a/demonstrations/tutorial_vqe_vqd.metadata.json +++ b/demonstrations/tutorial_vqe_vqd.metadata.json @@ -9,7 +9,7 @@ } ], "dateOfPublication": "2024-08-26T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-11-18T00:00:00+00:00", "categories": [ "Quantum Chemistry", "How-to" diff --git a/demonstrations/tutorial_vqe_vqd.py b/demonstrations/tutorial_vqe_vqd.py index cb618a7887..f894077297 100644 --- a/demonstrations/tutorial_vqe_vqd.py +++ b/demonstrations/tutorial_vqe_vqd.py @@ -100,7 +100,7 @@ def circuit(): from functools import partial # This line is added to better visualise the circuit -@partial(qml.devices.preprocess.decompose, stopping_condition = lambda obj:False, max_expansion=1) +@partial(qml.transforms.decompose, max_expansion=1) def ansatz(theta, wires): singles, doubles = qml.qchem.excitations(2, n_qubits) diff --git a/demonstrations/vqe_parallel.metadata.json b/demonstrations/vqe_parallel.metadata.json index d27d1dd51b..e05599b7a2 100644 --- a/demonstrations/vqe_parallel.metadata.json +++ b/demonstrations/vqe_parallel.metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2020-02-14T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2025-01-14T00:00:00+00:00", "categories": [ "Quantum Chemistry" ], diff --git a/demonstrations/vqe_parallel.py b/demonstrations/vqe_parallel.py index 89a64e91f7..113896a6d4 100644 --- a/demonstrations/vqe_parallel.py +++ b/demonstrations/vqe_parallel.py @@ -15,6 +15,9 @@ *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`). @@ -79,7 +82,7 @@ print("Number of terms: {}\n".format(len(h_ops))) for op in h_ops: - print("Measurement {} on wires {}".format(op.name, op.wires)) + print("Measurement {} on wires {}".format(str(op), op.wires)) ############################################################################## # .. rst-class:: sphx-glr-script-out @@ -89,21 +92,21 @@ # # Number of terms: 15 # -# Measurement Identity on wires -# Measurement PauliZ on wires -# Measurement PauliZ on wires -# Measurement ['PauliZ', 'PauliZ'] on wires -# Measurement ['PauliY', 'PauliX', 'PauliX', 'PauliY'] on wires -# Measurement ['PauliY', 'PauliY', 'PauliX', 'PauliX'] on wires -# Measurement ['PauliX', 'PauliX', 'PauliY', 'PauliY'] on wires -# Measurement ['PauliX', 'PauliY', 'PauliY', 'PauliX'] on wires -# Measurement PauliZ on wires -# Measurement ['PauliZ', 'PauliZ'] on wires -# Measurement PauliZ on wires -# Measurement ['PauliZ', 'PauliZ'] on wires -# Measurement ['PauliZ', 'PauliZ'] on wires -# Measurement ['PauliZ', 'PauliZ'] on wires -# Measurement ['PauliZ', 'PauliZ'] on wires +# 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 diff --git a/poetry.lock b/poetry.lock index 1a93345c53..30230d281a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. [[package]] name = "absl-py" @@ -361,17 +361,17 @@ files = [ [[package]] name = "bluequbit" -version = "0.8.1b1" +version = "0.9.3b1" description = "Python SDK to BlueQubit app" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "bluequbit-0.8.1b1-py3-none-any.whl", hash = "sha256:0c6bd231f46db2a336fe1de0498f9faeafe2a3d13e8efda5e9568615c127e2a7"}, - {file = "bluequbit-0.8.1b1.tar.gz", hash = "sha256:832f9c5b3f50c594659e470af5fda3fd33b0ebd5a3c5af40922db94eba314b86"}, + {file = "bluequbit-0.9.3b1-py3-none-any.whl", hash = "sha256:2b9130ccced25b81b81fbef40247294c3804b14026aafbf0bac1ee795a545977"}, + {file = "bluequbit-0.9.3b1.tar.gz", hash = "sha256:d695862ae350040ceadb2dc6dd78c0b10626b1b00b6d061fb1a0ed2ec568f788"}, ] [package.dependencies] -numpy = ">=1.21,<2.0" +numpy = ">=1.21,<2.1" packaging = "*" python-dateutil = ">=2.8,<3.0" requests = ">=2.28,<3.0" @@ -4148,12 +4148,12 @@ scipy = ">=1.6.0" [[package]] name = "pennylane" -version = "0.39.0" +version = "0.40.0" description = "PennyLane is a cross-platform Python library for quantum computing, quantum machine learning, and quantum chemistry. Train a quantum computer the same way as a neural network." optional = false python-versions = "*" files = [ - {file = "PennyLane-0.39.0-py3-none-any.whl", hash = "sha256:e11928a8ffd652b9c1b4f11955b50210c3b637f36ee3d8cea64a3a9a6a830977"}, + {file = "PennyLane-0.40.0-py3-none-any.whl", hash = "sha256:c93bb48ff80833edaf424b3b91e01a56cd25b22170bc99b7e53e7168a1ad4dd1"}, ] [package.dependencies] @@ -4161,14 +4161,15 @@ appdirs = "*" autograd = "*" autoray = ">=0.6.11" cachetools = "*" +diastatic-malt = "*" networkx = "*" numpy = "<2.1" packaging = "*" -pennylane-lightning = ">=0.39" +pennylane-lightning = ">=0.40" requests = "*" rustworkx = ">=0.14.0" scipy = "*" -toml = "*" +tomlkit = "*" typing-extensions = "*" [package.extras] @@ -4176,23 +4177,23 @@ kernels = ["cvxopt", "cvxpy"] [[package]] name = "pennylane-catalyst" -version = "0.9.0" +version = "0.10.0" description = "A JIT compiler for hybrid quantum programs in PennyLane" optional = false python-versions = ">=3.10" files = [ - {file = "PennyLane_Catalyst-0.9.0-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:d75986c75fcd7ac8762d83822dac998f6afa8d90131096d3855354f5593c97f7"}, - {file = "PennyLane_Catalyst-0.9.0-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:2954e3666556434ab87c1fca1b73bf68bbf1997bbe49f64cd74c6703e8c14a9d"}, - {file = "PennyLane_Catalyst-0.9.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:0f21b9779882a92d189689f9e82b0b4ccff2f6a449bb6d642ffba4fe4c33dc9f"}, - {file = "PennyLane_Catalyst-0.9.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:768e4b976a79ac9bcd8e96526dab0fdcffdf7a293b60c92f0326a4b0004a6c7b"}, - {file = "PennyLane_Catalyst-0.9.0-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:ab3788a7e1579bfca13e1a483f49dbd4631e4fe1a42ab58d38c1da5f801ce55c"}, - {file = "PennyLane_Catalyst-0.9.0-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:2a3ae980d47f7ca70cdef95c7f24033b22cd442a6ebaa046fbbb3cfc75ae7695"}, - {file = "PennyLane_Catalyst-0.9.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:3a9bd87ce684cc7e9229260839df46566b8e458725537f9ec9bdce2d383963f9"}, - {file = "PennyLane_Catalyst-0.9.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:2ec0c811bf65509430da190fbfb7bdb6ccdbad18de9cd45f4cd77aa567edc48b"}, - {file = "PennyLane_Catalyst-0.9.0-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:86d87b4a01ce2f64d0c6e449dd87bd07b9eb060047a8a49f3191bff02a68c0a0"}, - {file = "PennyLane_Catalyst-0.9.0-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:56cabe79df7c97d7f2df7c981bd84ff52a18d3ebc02cc328847b092082d889f5"}, - {file = "PennyLane_Catalyst-0.9.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:5171a9ea08d1690a1124fc1a87c56c1e77a033c596c41a6fbbfaa864f6ef2aee"}, - {file = "PennyLane_Catalyst-0.9.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:60cb9af071b340dbe7270cccb54d9222600a740e792206b161dc2b0798c5f8a8"}, + {file = "PennyLane_Catalyst-0.10.0-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:fb4e2c8f32cdbb0f327b0912ee5a14dc16a35d76156f1c99967129978a2bcd70"}, + {file = "PennyLane_Catalyst-0.10.0-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:ee6bf5cbce1bc8b3f01d97d5cd99901e820cfa4a89131ec9707f282cc74c5179"}, + {file = "PennyLane_Catalyst-0.10.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:76e6a0609893bb58aa8a77123de23dde1883014fd7c0e552c3706def11e71c17"}, + {file = "PennyLane_Catalyst-0.10.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:744be1cb016b15c9a51bbc1b8ccb7fe4d11526d55b7782b8fc1533aef6c62998"}, + {file = "PennyLane_Catalyst-0.10.0-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:54da27797abd94e54599f0fdcc61e28dac47fef6a2b09b9bc643b1d4b2f43e8a"}, + {file = "PennyLane_Catalyst-0.10.0-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:4ee6d691a00da586e3b6cd845e05063284e01e9db724e36cb79cb787435f44a5"}, + {file = "PennyLane_Catalyst-0.10.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:041a22b5668340bdaf1cad479ba6c9495d1947abaecb471dbc0d433a4ad7a066"}, + {file = "PennyLane_Catalyst-0.10.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:49090111e111d5a126f82158b47c8b2ef1de4640efd8c86a18dbadbbe6b9e7f8"}, + {file = "PennyLane_Catalyst-0.10.0-cp312-abi3-macosx_13_0_arm64.whl", hash = "sha256:98e4d88403c907169bde62e03ac71c30779366dfbed821ab55392223330cba95"}, + {file = "PennyLane_Catalyst-0.10.0-cp312-abi3-macosx_13_0_x86_64.whl", hash = "sha256:5cd40fe9177895acd7ce6533e09ec4591d95450f619c580d4da9041ea66290eb"}, + {file = "PennyLane_Catalyst-0.10.0-cp312-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9a1b34b1b922cb91a5da0671b911e3a4c33e03c1269d4c0f36b9a511e19c2d96"}, + {file = "PennyLane_Catalyst-0.10.0-cp312-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:00f3f255e205a95854c0a83895379f33528aedaaf3b386afd8ac65f300730a03"}, ] [package.dependencies] @@ -4200,19 +4201,18 @@ diastatic-malt = ">=2.15.2" jax = "0.4.28" jaxlib = "0.4.28" numpy = "!=2.0.0" -pennylane = ">=0.39" -pennylane-lightning = ">=0.39" -scipy = "<=1.13" -tomlkit = {version = "*", markers = "python_version < \"3.11\""} +pennylane = ">=0.4" +pennylane-lightning = ">=0.4" +scipy-openblas32 = ">=0.3.26" [[package]] name = "pennylane-cirq" -version = "0.39.0" +version = "0.40.0" description = "PennyLane plugin for Cirq" optional = false python-versions = "*" files = [ - {file = "PennyLane_Cirq-0.39.0-py3-none-any.whl", hash = "sha256:a66c201dca56a66cf55fbe17dccf9fb495b20973ba402df2a990b693f4bca211"}, + {file = "PennyLane_Cirq-0.40.0-py3-none-any.whl", hash = "sha256:fecd7e1ac9864efaa5ba4399597418646f71941a513ce311812412a66e450171"}, ] [package.dependencies] @@ -4222,32 +4222,33 @@ pennylane = ">=0.38.0" [[package]] name = "pennylane-lightning" -version = "0.39.0" +version = "0.40.0" description = "PennyLane-Lightning plugin" optional = false python-versions = ">=3.10" files = [ - {file = "PennyLane_Lightning-0.39.0-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:dfdf6071337f743519d7bcbe4d1bf35f1822bf14020dbda3c734b0471c4dbb12"}, - {file = "PennyLane_Lightning-0.39.0-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:0d9d7a865cf81923a05247e811a1534f3395fc9f27ea51a0472837b2dbe88f8d"}, - {file = "PennyLane_Lightning-0.39.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:1d4c22f28afdb816bca37617d9d508e3a73b0bd50df4581bafd1de7f8ab028b2"}, - {file = "PennyLane_Lightning-0.39.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:1878dbf59c779face32ee8b9291e2cf18a601c491a132afe89f9b1e477026455"}, - {file = "PennyLane_Lightning-0.39.0-cp310-cp310-win_amd64.whl", hash = "sha256:8e5e68f467f68216d2b5f19639b4b3d621743c389a4d207282c52557e20ff10c"}, - {file = "PennyLane_Lightning-0.39.0-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:8b5dfb5ee187a98925130965b5cbfb9ecfc650ec23b2cfdbf16040db25d79f73"}, - {file = "PennyLane_Lightning-0.39.0-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:80debc3b1c7a5923509f4662b95ca1703ac8a29eaaa78a169240b23c4c1c7875"}, - {file = "PennyLane_Lightning-0.39.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:ac2bcafd8b10b58e99cd050bb89a67e97b53e66ab8636e37bafd67a32905f935"}, - {file = "PennyLane_Lightning-0.39.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:de45c1c2e97572e59081878dc80bbebe17b938a783005ce0ad6632ba8dd46956"}, - {file = "PennyLane_Lightning-0.39.0-cp311-cp311-win_amd64.whl", hash = "sha256:dcbec946148018a8aebae6cf45bb0d9d6f9a418bb0655ed4b3ae2f7e948e320d"}, - {file = "PennyLane_Lightning-0.39.0-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:6c7553d206f185ab6f7688fe086d80a33f382beb56e9ad579090bdba041adfb1"}, - {file = "PennyLane_Lightning-0.39.0-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:30a08ed59fd2c11257bc989303547b8125e7453c8165f8c75ec7d8090451ec54"}, - {file = "PennyLane_Lightning-0.39.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:c0f52da46791605e3f8f0644b45a97de799df1f69cd3659a58fc04f7288f948e"}, - {file = "PennyLane_Lightning-0.39.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:f0a9ab74b0cf2cbc9556a0c85a750a5d6063721405f76cacf8b17f9e6774dd9b"}, - {file = "PennyLane_Lightning-0.39.0-cp312-cp312-win_amd64.whl", hash = "sha256:f568120eac4ed4b5d498889cb9359040dd7070ef711e98cdbace4efde794c866"}, - {file = "PennyLane_Lightning-0.39.0-py3-none-any.whl", hash = "sha256:55de99da8e513f89b416087d1665257498be244006968b472b82a56b972d0f0d"}, - {file = "PennyLane_Lightning-0.39.0.tar.gz", hash = "sha256:a1db6069c7d97eea50846d5b51a3d9ae97539bf68ac480b5c9bea1eaa5e3f1d1"}, + {file = "PennyLane_Lightning-0.40.0-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:3a6915cc17ce99cd4f3d04dcf7d579f59cecdc2866cd4e8e1c33478d86101437"}, + {file = "PennyLane_Lightning-0.40.0-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:4a66e8c644f634f9d1fda6b9223f5cc106acf4146334a25a23f948ec59b075c0"}, + {file = "PennyLane_Lightning-0.40.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:a58ab1489fee0424840bc8f91e8f7e32ecc3338a714b2fe1bf68a9026a5a295a"}, + {file = "PennyLane_Lightning-0.40.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:7fe9262c602b6c7f0ab893468867e24c63e9cacc7d77e19b448ac1361bb86a47"}, + {file = "PennyLane_Lightning-0.40.0-cp310-cp310-win_amd64.whl", hash = "sha256:3f75b7d39d63880e9d59562d78ae5ec63d2aed936b2feee3c937dfbcd080b834"}, + {file = "PennyLane_Lightning-0.40.0-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:99cf7bcfb3a2a29838cc12f0001e3e7e02d35c5c1d696ce2e5ba0c3f2995c4d9"}, + {file = "PennyLane_Lightning-0.40.0-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:6b89ad785e16cc3b3d155b1abd27e42fcb5854a0e2d90452f6445fc0e80b1cf4"}, + {file = "PennyLane_Lightning-0.40.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:20052041abc417d74d1794506d4340a8a2298b858e2b94591704e73670d913f9"}, + {file = "PennyLane_Lightning-0.40.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a3db3cf2c3caed80ce561b66bb4bf6b5712ecf0b08db986f24c4ff9e187e82b3"}, + {file = "PennyLane_Lightning-0.40.0-cp311-cp311-win_amd64.whl", hash = "sha256:fed88008b7d468cb1d0e5b3ef92d87f9b600d0896d21eff43655a521cc841d7b"}, + {file = "PennyLane_Lightning-0.40.0-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:e3152f6b53390281334257554ecad90566cd4d200187971d238d2c7691000466"}, + {file = "PennyLane_Lightning-0.40.0-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:8a4ff3f1d82f664e6d608d155cb019b189aac676c6c7cb40c4f92b15e0d2d485"}, + {file = "PennyLane_Lightning-0.40.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:b2209727b8c914c8101087da188cbbc7b332bb77ceab4b6791c7ed7b3c5a942c"}, + {file = "PennyLane_Lightning-0.40.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:2ba97b4d6aa0a6eb774413ea3272d418959b792de6df8e4196171cad450f25af"}, + {file = "PennyLane_Lightning-0.40.0-cp312-cp312-win_amd64.whl", hash = "sha256:3d7715c84290fc1291e315d2c0d2bf5719cbe86e3e92fb538393ab130b3f5ba2"}, + {file = "PennyLane_Lightning-0.40.0-py3-none-any.whl", hash = "sha256:96390ce82767b3a66c4f8e0b1bcec2f4f15fb317652cffa9815e371e74458197"}, + {file = "pennylane_lightning-0.40.0.tar.gz", hash = "sha256:edc95e75ef1b4e6dba96e914893175f9448d4d1b53a6e78614cb7587a8a22f2f"}, ] [package.dependencies] pennylane = ">=0.37" +scipy-openblas32 = ">=0.3.26" [package.extras] gpu = ["pennylane-lightning-gpu"] @@ -4256,19 +4257,19 @@ tensor = ["pennylane-lightning-tensor"] [[package]] name = "pennylane-qiskit" -version = "0.39.0" +version = "0.40.0" description = "PennyLane plugin for Qiskit" optional = false python-versions = "*" files = [ - {file = "PennyLane_qiskit-0.39.0-py3-none-any.whl", hash = "sha256:0551be4022cd72de7ebde7c916778aff119f8e153427c633660250cc131ec38b"}, + {file = "PennyLane_qiskit-0.40.0-py3-none-any.whl", hash = "sha256:6295926b5acd2e96515459fb7c9dddeadf2c70e2a5f2807c6685fb025c7c4408"}, ] [package.dependencies] networkx = ">=2.2" numpy = "*" pennylane = ">=0.38" -qiskit = ">=0.32" +qiskit = ">=0.32,<1.3" qiskit-aer = "*" qiskit-ibm-provider = "*" qiskit-ibm-runtime = "<=0.29" @@ -4276,35 +4277,34 @@ sympy = "<1.13" [[package]] name = "pennylane-qrack" -version = "0.10.6" +version = "0.11.1" description = "PennyLane plugin for Qrack." optional = false python-versions = "*" files = [ - {file = "pennylane_qrack-0.10.6-py3-none-macosx_12_0_x86_64.whl", hash = "sha256:8ff4fee278a57d7898a345cc5cf9b999de5cc43e4983216fb31fcbcfb97e83a0"}, - {file = "pennylane_qrack-0.10.6-py3-none-macosx_13_0_x86_64.whl", hash = "sha256:454fd1f2e20ed82b1871b23eefe7ff4ef04f294dfcb29cf751c8cbad8c388bdb"}, - {file = "pennylane_qrack-0.10.6-py3-none-macosx_14_0_arm64.whl", hash = "sha256:0722c931ed4541e5768791457d168c9be380cbf867c2c0133da32d0a3cd841fa"}, - {file = "pennylane_qrack-0.10.6-py3-none-macosx_15_0_arm64.whl", hash = "sha256:818d3d17857b4ad16abf1c41d1ead868906b251ed87b46a309a4d343e7510ef7"}, - {file = "pennylane_qrack-0.10.6-py3-none-manylinux_2_31_x86_64.whl", hash = "sha256:470a29c67d186d15784181a3120d426980425c0a8d86d181a4fed067f6cdbb7a"}, - {file = "pennylane_qrack-0.10.6-py3-none-manylinux_2_35_x86_64.whl", hash = "sha256:a0a7333b2cdfd68e631aefb093b242d2c4674161578131e1e40663baac0f1b8b"}, - {file = "pennylane_qrack-0.10.6-py3-none-manylinux_2_39_x86_64.whl", hash = "sha256:dfd59d968594b21635817f7b0fa71925b573fa2f72c2024a88c8368d37cc3277"}, - {file = "pennylane_qrack-0.10.6-py3-none-win_amd64.whl", hash = "sha256:24a600217d7c5d13f1e17506717309dfa57e00df3256851e3956ad0f62999b90"}, - {file = "pennylane_qrack-0.10.6.tar.gz", hash = "sha256:3ee51483977dbdbf47943bf4be3f85f2a6aa9d556c62eae40e9c8d8cabe5c294"}, + {file = "pennylane_qrack-0.11.1-py3-none-macosx_13_0_x86_64.whl", hash = "sha256:06d9255430ea5af0081bcb06a2ea9b8d6bb6f9534131b275092e10296f03c2a2"}, + {file = "pennylane_qrack-0.11.1-py3-none-macosx_14_0_arm64.whl", hash = "sha256:43c2aa4f2819207e93ca0c29f39bcea1720086f3c5eaa02c57668f3e8f0595fa"}, + {file = "pennylane_qrack-0.11.1-py3-none-macosx_15_0_arm64.whl", hash = "sha256:9e251a85565af5a77538997d94f412f6f86a455ff33f5d308ecf05139e3f2e41"}, + {file = "pennylane_qrack-0.11.1-py3-none-manylinux_2_31_x86_64.whl", hash = "sha256:ac0640c4c59877f1f5a9c1be47ef07e949da1d9d1e34547e3d90a6afc01863b2"}, + {file = "pennylane_qrack-0.11.1-py3-none-manylinux_2_35_x86_64.whl", hash = "sha256:24835ba3287c57f87185a717d3c66a228350f1de24ed44f5bbac0c12d102fdcd"}, + {file = "pennylane_qrack-0.11.1-py3-none-manylinux_2_39_x86_64.whl", hash = "sha256:4f857ce52add9dc1997b4f0a59357057d5ca8bc21f9e18a1c1e4d1d21edb352f"}, + {file = "pennylane_qrack-0.11.1-py3-none-win_amd64.whl", hash = "sha256:73fe5dbde0f2acaea768cd9680e68e2752e0b474a7a9982d2b9b6539b651be58"}, + {file = "pennylane_qrack-0.11.1.tar.gz", hash = "sha256:d8a6124545f1772517c060ffe31e17c3f933e5be0a1006a45a4610daad46c21b"}, ] [package.dependencies] numpy = ">=1.16" -pennylane = ">=0.32" +pennylane = ">=0.39.0" pyqrack = ">=1.30.0" [[package]] name = "pennylane-qulacs" -version = "0.39.0" +version = "0.40.0" description = "PennyLane plugin for Qulacs." optional = false python-versions = "*" files = [ - {file = "pennylane_qulacs-0.39.0-py3-none-any.whl", hash = "sha256:94aee328291f716efe475694137c9055070640c03c9cc9878b5e767f3e4ef982"}, + {file = "pennylane_qulacs-0.40.0-py3-none-any.whl", hash = "sha256:056341c8877034a32939657ca0b3d746fb8fce5813c61b5377a86d661d0e3959"}, ] [package.dependencies] @@ -6069,6 +6069,26 @@ dev = ["click", "cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyl doc = ["jupytext", "matplotlib (>2)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-design (>=0.2.0)"] test = ["asv", "gmpy2", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +[[package]] +name = "scipy-openblas32" +version = "0.3.28.0.2" +description = "Provides OpenBLAS for python packaging" +optional = false +python-versions = ">=3.7" +files = [ + {file = "scipy_openblas32-0.3.28.0.2-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:233f449a842a754a5154af399a6b4ea6ffb27457883339a6b324b1fc3e55cad8"}, + {file = "scipy_openblas32-0.3.28.0.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cb97169552004960ad6758d0c65998dbcd23bb4908e321381d288ed6c019280e"}, + {file = "scipy_openblas32-0.3.28.0.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b10b433d4c48c74ea1a3b88dae31447a1bae5597d04577a531fbc16f2bdcf84b"}, + {file = "scipy_openblas32-0.3.28.0.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:875f981d5024833775322ef2aae4aa0545283e856503326812e9dc1aeafaef14"}, + {file = "scipy_openblas32-0.3.28.0.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f0ecb748ce7dc7996220614100d7bf87fa0aab9499bbbdabbdcb6f5487a9e65"}, + {file = "scipy_openblas32-0.3.28.0.2-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:c4b6cf0067a6c62ef4b998a4cf59694cd1e13a7bf994346bc5dcddbf3e7a2a80"}, + {file = "scipy_openblas32-0.3.28.0.2-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:a0597896ac1e0973b3da6ea15e03f2189ded49780d6571773698842072e66eb2"}, + {file = "scipy_openblas32-0.3.28.0.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:35051f9ee933ec642caafc1101b79a760bd536597e486e0168a7700afec256b0"}, + {file = "scipy_openblas32-0.3.28.0.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ea226ce2ffd8a5bfc020aa8d80e1013c758a8032e87588681b04cd25d1b1fd48"}, + {file = "scipy_openblas32-0.3.28.0.2-py3-none-win32.whl", hash = "sha256:e6595e950df5584fe9fdf5f483b321432e14c7792d61f0cf3d6bf2bc4d2c39be"}, + {file = "scipy_openblas32-0.3.28.0.2-py3-none-win_amd64.whl", hash = "sha256:7a6c0618760084278eeda0ea0d1d7ca77eebbc4fce9f0a0b4fef82000e5ec01b"}, +] + [[package]] name = "seaborn" version = "0.13.2" @@ -8103,4 +8123,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "~3.10.0" -content-hash = "18a7d6691c3e680b9b60a91d5401bf69ffaa028b1fd8a6aa9684149b6d7ab45b" +content-hash = "f7e22996e133b32706e6af4b43ae9d90a57faa8ccc0f4254c6f126eb3c545a20" diff --git a/pyproject.toml b/pyproject.toml index 9bed5d9256..4ad0b09a8f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,11 +38,11 @@ pypandoc = "1.5" # These pinned versions of PL are NOT used for Dev builds # # The latest commit from GitHub is used instead # ########################################################### -pennylane = "0.39.0" -pennylane-cirq = "0.39.0" -pennylane-qiskit = "0.39.0" -pennylane-qulacs = "0.39.0" -pennylane-catalyst = "0.9.0" +pennylane = "0.40.0" +pennylane-cirq = "0.40.0" +pennylane-qiskit = "0.40.0" +pennylane-qulacs = "0.40.0" +pennylane-catalyst = "0.10.0" ########################################################## @@ -65,12 +65,12 @@ optax = "0.2.3" flax = "0.9.0" qutip = "4.7.3" mitiq = "0.32.0" -pennylane-qrack = "0.10.6" +pennylane-qrack = "0.11.1" pyqrack = "1.32.12" zstd = "*" dill = "*" stim = "*" -bluequbit = "0.8.1b1" +bluequbit = "0.9.3b1" quimb = "1.8.2" aiohttp = "3.9.5" fsspec = "2024.6.1"