Usage

Usage

Using Maestro

Maestro can be used in two ways:

  1. Python bindings — Direct access to the C++ simulation engine
  2. Cloud API — Via Composer Gateway

Python Bindings

QASM-based Execution

The simplest way to use Maestro is through the simple_execute function with an OpenQASM circuit:

import maestro

qasm_bell = """
OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
creg c[2];
h q[0];
cx q[0], q[1];
measure q[0] -> c[0];
measure q[1] -> c[1];
"""

result = maestro.simple_execute(qasm_bell, shots=1000)
print(result["counts"])     # e.g. {'00': 498, '11': 502}
print(result["simulator"])  # e.g. 'QCSim'
print(result["method"])     # e.g. 'Statevector'
print(f"Time: {result['time_taken']:.4f}s")

QuantumCircuit Model

Maestro provides a native QuantumCircuit class for building circuits programmatically in Python, without needing QASM strings:

import maestro

QuantumCircuit = maestro.circuits.QuantumCircuit

# Create a Bell state
qc = QuantumCircuit()
qc.h(0)
qc.cx(0, 1)
qc.measure([(0, 0), (1, 1)])  # Map qubit 0 → classical bit 0, qubit 1 → bit 1

result = qc.execute(shots=1000)
print(result["counts"])  # {'00': ~500, '11': ~500}

Available Gates

Single-qubit (non-parametric):

qc.x(0)    # Pauli-X
qc.y(0)    # Pauli-Y
qc.z(0)    # Pauli-Z
qc.h(0)    # Hadamard
qc.s(0)    # S gate
qc.sdg(0)  # S†
qc.t(0)    # T gate
qc.tdg(0)  # T†
qc.sx(0)   # √X

Single-qubit (parametric):

qc.rx(0, 0.5)          # Rx(θ)
qc.ry(0, 0.5)          # Ry(θ)
qc.rz(0, 0.5)          # Rz(θ)
qc.p(0, 0.5)           # Phase gate
qc.u(0, 0.1, 0.2, 0.3) # U(θ, φ, λ)

Two-qubit gates:

qc.cx(0, 1)       # CNOT
qc.cy(0, 1)       # Controlled-Y
qc.cz(0, 1)       # Controlled-Z
qc.swap(0, 1)     # SWAP
qc.cp(0, 1, 0.5)  # Controlled-Phase
qc.crx(0, 1, 0.5) # Controlled-Rx
qc.cry(0, 1, 0.5) # Controlled-Ry
qc.crz(0, 1, 0.5) # Controlled-Rz

Execution with Backend Selection

# Automode — Maestro selects the best backend
result = qc.execute(shots=1000)

# Explicit backend selection
result = qc.execute(
    simulator_type=maestro.SimulatorType.QCSim,
    simulation_type=maestro.SimulationType.Statevector,
    shots=1000,
)

# MPS simulation with tunable bond dimension
result = qc.execute(
    simulator_type=maestro.SimulatorType.QCSim,
    simulation_type=maestro.SimulationType.MatrixProductState,
    max_bond_dimension=64,
    singular_value_threshold=1e-6,
    shots=1000,
)

Expectation Value Estimation

Estimate expectation values without sampling, using exact state vector computation:

qc = QuantumCircuit()
qc.x(0)

# Single observable
result = qc.estimate(observables="Z")
print(result["expectation_values"])  # [-1.0]

# Multiple observables (semicolon-separated or list)
result = qc.estimate(observables="Z;I")
print(result["expectation_values"])  # [-1.0, 1.0]

# Or as a list
result = qc.estimate(observables=["X", "Z"])
print(result["expectation_values"])  # [0.0, -1.0]

Multi-qubit observables on entangled states:

qc = QuantumCircuit()
qc.h(0)
qc.cx(0, 1)
# Bell state: (|00⟩ + |11⟩) / √2

result = qc.estimate(observables=["XX", "ZZ", "IZ"])
# XX → 1.0, ZZ → 1.0, IZ → 0.0

Estimate with MPS backend:

result = qc.estimate(
    observables="Z",
    simulator_type=maestro.SimulatorType.QCSim,
    simulation_type=maestro.SimulationType.MatrixProductState,
    max_bond_dimension=4,
)

Simulation Types

Type Enum Value Best For
State Vector SimulationType.Statevector Exact simulation, ≤30 qubits
MPS SimulationType.MatrixProductState Large, low-entanglement circuits
Stabilizer SimulationType.Stabilizer Clifford-only circuits (any size)
Pauli Propagator SimulationType.PauliPropagator Pauli-heavy circuits
Extended Stabilizer SimulationType.ExtendedStabilizer Near-Clifford circuits
Auto SimulationType.Auto Let Maestro choose the optimal backend

Low-Level API

For fine-grained control, you can manage simulators directly:

import maestro

m = maestro.Maestro()

# Create a simulator
handle = m.create_simulator(
    maestro.SimulatorType.QCSim,
    maestro.SimulationType.Statevector,
)
simulator = m.get_simulator(handle)

# Allocate and initialize
simulator.AllocateQubits(2)
simulator.Initialize()

# Apply gates directly
simulator.ApplyH(0)
simulator.ApplyCX(0, 1)

# Sample
results = simulator.SampleCounts([0, 1], 1000)
print(results)  # {'00': ~500, '11': ~500}

# Clean up
m.destroy_simulator(handle)