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

simulation_type

"Statevector" (default), "MatrixProductState", "Stabilizer", "TensorNetwork", "PauliPropagator", "DensityMatrix", "MatrixProductOperator", …

simulator_type

"QCSim" (default, CPU), "QiskitAer", or "Gpu", "DistributedGpu" and "DistributedMpiGpu" with GPU access

max_bond_dimension, singular_value_threshold

MPS truncation

pp_max_pauli_weight, pp_coefficient_threshold

Pauli propagation truncation

shots, seed

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,
)