Use Maestro from PennyLane¶
Run existing PennyLane code on Maestro by changing one line: the device name.
pip install pennylane-maestro
import pennylane as qp
dev = qp.device("maestro.qubit", wires=2) # instead of "default.qubit"
@qp.qnode(dev)
def circuit(theta):
qp.RX(theta, wires=0)
qp.CNOT(wires=[0, 1])
return qp.expval(qp.Z(1))
print(circuit(0.5))
0.8775825618903726
The plugin is a separate package,
pennylane-maestro, which
installs PennyLane and qoro-maestro for you. Like Maestro, PennyLane writes
wire 0 first in bitstrings, so counts read the same in both.
VQE with exact gradients¶
On the CPU statevector, the default, the plugin computes gradients with the
adjoint method, so qp.grad and PennyLane’s optimizers work unchanged:
from pennylane import numpy as np
n = 4
hamiltonian = qp.Hamiltonian(
[-1.0] * (n - 1) + [-0.5] * n,
[qp.Z(i) @ qp.Z(i + 1) for i in range(n - 1)] + [qp.X(i) for i in range(n)],
)
dev = qp.device("maestro.qubit", wires=n)
@qp.qnode(dev)
def energy(params):
for i in range(n):
qp.RY(params[i], wires=i)
for i in range(n - 1):
qp.CNOT(wires=[i, i + 1])
for i in range(n):
qp.RY(params[n + i], wires=i)
return qp.expval(hamiltonian)
params = np.array([0.1] * (2 * n), requires_grad=True)
print("gradient method:", qp.workflow.get_best_diff_method(energy)(params))
optimizer = qp.GradientDescentOptimizer(stepsize=0.2)
for step in range(41):
params, value = optimizer.step_and_cost(energy, params)
if step % 10 == 0:
print(f"step {step:2d}: energy {value:.4f}")
print(f"exact ground energy: {min(qp.eigvals(hamiltonian)):.4f}")
gradient method: adjoint
step 0: energy -3.2064
step 10: energy -3.3753
step 20: energy -3.3762
step 30: energy -3.3763
step 40: energy -3.3765
exact ground energy: -3.4270
The plugin evaluates every term of the Hamiltonian in one Maestro call. Finite shots, mid-circuit measurements and methods other than the statevector fall back to parameter-shift gradients.
Choose the simulation method¶
Device keyword arguments select the method and map onto Maestro’s
SimulatorConfig. An MPS handles many qubits when entanglement is limited:
mps = qp.device("maestro.qubit", wires=60, simulation_type="MatrixProductState", max_bond_dimension=16)
@qp.qnode(mps)
def ghz_correlation():
qp.Hadamard(0)
for i in range(59):
qp.CNOT(wires=[i, i + 1])
return qp.expval(qp.Z(0) @ qp.Z(59))
print(ghz_correlation())
1.0000000000000002
Finite shots with a seed:
sampler = qp.device("maestro.qubit", wires=2, shots=1000, seed=42)
@qp.qnode(sampler)
def bell_counts():
qp.Hadamard(0)
qp.CNOT(wires=[0, 1])
return qp.counts()
print(bell_counts())
{np.str_('00'): np.int64(481), np.str_('11'): np.int64(519)}
Argument |
Values |
|---|---|
|
|
|
|
|
MPS truncation |
|
Pauli propagation truncation |
|
Finite-shot sampling and reproducibility |
Other SimulatorConfig options, such as gate_fusion or precision, are
accepted as device arguments too; see the
plugin README
for the full list.
Run on a GPU¶
Requires GPU access
simulator_type="Gpu" needs the GPU plugin and a licence. See
Get GPU access.
# Not executed in the docs build: requires GPU access.
dev = qp.device("maestro.qubit", wires=28, simulator_type="Gpu")
gpu_mps = qp.device(
"maestro.qubit", wires=200, simulator_type="Gpu",
simulation_type="MatrixProductState", max_bond_dimension=256,
)