Skip to main content

Haiqu.variational_optimization(problem, shots=1000, device=None, device_id=None, options=None, initial_parameters=None, seed=None, optimizer_options=None, use_mitigation=False, use_packing=False, pack_size=None, use_session=False, use_compression=False, compression_options=None, job_name=None, dry_run=False)

Optimize a variational quantum circuit to minimize the expectation value of input observable. Defaults to the NFT (Nakanishi-Fujii-Todo) optimizer, a gradient-free optimizer designed for variational quantum algorithms (https://arxiv.org/abs/1903.12166). Pass a ScipyOptimizerOptions instance as optimizer_options to dispatch to any derivative-free scipy.optimize.minimize method instead (cobyla, nelder-mead, powell, cobyqa).
  • Parameters:
    • problem (VariationalProblem) — problem instance containing the ansatz circuit and observable.
    • shots (int) — Number of shots per circuit evaluation. Defaults to 1000.
    • device (DeviceModel | None) — Device to execute on. If specified, device_id is ignored.
    • device_id (str | None) — ID of the device to execute on. Defaults to None.
    • options (dict | None) — Additional device options.
    • initial_parameters (list *[*float ] | None) — Initial parameter values. Cannot be used together with seed. If neither is provided, random parameters in [-0.1π, 0.1π] are generated.
    • seed (int | None) — Random seed for reproducible generation of initial parameters from a uniform distribution in [-0.1π, 0.1π]. Cannot be used together with initial_parameters.
    • optimizer_options (OptimizerOptions | None) — Configuration for the optimizer. If None, defaults to NFTOptimizerOptions(). Pass a ScipyOptimizerOptions instance to use any derivative-free scipy method (cobyla, nelder-mead, powell, cobyqa) instead.
    • use_mitigation (bool) — Whether to use error mitigation techniques. Defaults to False.
    • use_packing (bool) — Whether to use circuit packing for efficient device utilization. Defaults to False. Warning: Experimental — packing replicates circuits on unused device qubits to run multiple copies in parallel, which may increase errors for deeper input circuits. For example, a 4-qubit circuit with pack_size=2 and 1000 shots runs two copies in parallel with 500 shots each, yielding 1000 shots of results while only paying for 500 shot executions on the QPU — a 2x cost saving.
    • pack_size (int | None) — Number of circuit copies to pack onto the device. Must be >= 2. Only valid when use_packing=True. If None (default), the backend will pack into at most 2/3 of the device qubits.
    • use_session (bool) — Whether to use IBM Qiskit Runtime Session for execution. Defaults to False.
    • use_compression (bool) — Whether to apply circuit compression at each training step. Binds parameters and compresses the circuit before each QPU evaluation, reducing 2-qubit gate count and thus QPU noise. Defaults to False.
    • compression_options (CompressionOptions | None) — Configuration for compression-in-training. Only used when use_compression=True. If None, default compression settings are applied.
    • job_name (str | None) — The name for the job. If None (default), a name will be automatically generated.
    • dry_run (bool) — Whether to stop just prior to backend execution for QPU cost estimation. Defaults to False. When True, the job’s optimization result will be empty since execution on the device is skipped. The estimated QPU cost is then available via job.estimated_qpu_cost. When use_session=True, the estimate excludes classical optimization and parameter-update time, which session mode also bills; job.estimated_qpu_cost["warning"] carries this notice. Only supported for the NFT optimizer; passing a ScipyOptimizerOptions with dry_run=True raises NotImplementedError.
  • Returns: Job handle to track optimization progress and retrieve results. : Call job.result() to retrieve a VariationalResult exposing optimal_parameters (list[float]), min_loss (float), and loss_history (list[float]). job.info exposes auxiliary metadata (loss_history, qpu_cost, session_cost). When dry_run=True, result() is empty; use job.estimated_qpu_cost instead. Use job.progress() for live status updates and help(job.result) for the full description of result and info contents.
  • Return type: VariationalJobModel

Examples

Default optimizer settings:
>>> from qiskit import QuantumCircuit
>>> from qiskit.circuit import Parameter
>>> from qiskit.quantum_info import SparsePauliOp
>>> from haiqu.sdk.qml import VariationalProblem
>>> theta = Parameter('θ')
>>> ansatz = QuantumCircuit(2)
>>> ansatz.ry(theta, 0)
>>> ansatz.cx(0, 1)
>>> obs = SparsePauliOp.from_list([("ZZ", 1.0), ("XI", 0.5)])
>>> problem = VariationalProblem(ansatz, obs)
>>> job = haiqu.variational_optimization(problem, shots=1000, device_id="aer_simulator")
>>> result = job.result()
>>> print(result.min_loss)
Custom optimizer settings:
>>> from haiqu.sdk.qml import NFTOptimizerOptions
>>> optimizer = NFTOptimizerOptions(maxfev=2048, maxiter=100)
>>> job = haiqu.variational_optimization(problem, shots=1000, device_id="aer_simulator", optimizer_options=optimizer)
Scipy COBYLA instead of NFT:
>>> from haiqu.sdk.qml import ScipyOptimizerOptions
>>> optimizer = ScipyOptimizerOptions(method="cobyla", maxfev=200, options={"rhobeg": 0.5})
>>> job = haiqu.variational_optimization(problem, shots=1000, device_id="aer_simulator", optimizer_options=optimizer)

class haiqu.sdk.qml.problem.VariationalProblem(ansatz, observable)

A variational quantum optimization problem definition. Bundles a parameterized ansatz circuit with an observable to minimize.
  • Parameters:
    • ansatz (QuantumCircuit) — Parameterized quantum circuit.
    • observable (SparsePauliOp) — The observable as a SparsePauliOp.
  • Raises:
    • TypeError — If inputs are wrong types.
    • ValueError — If ansatz is not parameterized.

Example

>>> from qiskit import QuantumCircuit
>>> from qiskit.circuit import Parameter
>>> from qiskit.quantum_info import SparsePauliOp
>>> from haiqu.sdk.qml import VariationalProblem
>>> theta = Parameter('θ')
>>> ansatz = QuantumCircuit(2)
>>> ansatz.ry(theta, 0)
>>> ansatz.cx(0, 1)
>>> obs = SparsePauliOp.from_list([("ZZ", 1.0), ("XI", 0.5)])
>>> problem = VariationalProblem(ansatz, obs)

SEE ALSO

haiqu.sdk.quantum_haiqu.Haiqu.variational_optimization(): Submit problem to Haiqu cloud.

class haiqu.sdk.qml.optimizer.NFTOptimizerOptions(*, type=‘nft’, randomized_order=False, reset_interval=32, maxfev=200, maxiter=100, eps=1e-32)

Configuration options for the NFT (Nakanishi-Fujii-Todo) optimizer. The NFT algorithm is a gradient-free optimizer designed for variational quantum algorithms. For detailed information about the algorithm, see the paper: https://arxiv.org/abs/1903.12166 Preconditions: : NFT requires the following conditions on the parameterized quantum circuit:
  1. Parameters must be independent: each parameter must appear in exactly one gate (no reusing the same parameter across multiple gates).
  2. Parameterized gates must be rotations of the form R_j(θ_j) = exp(-i*θ_j*A_j/2) where A_j² = I (e.g., RX, RY, RZ gates satisfy this).
  3. The cost function must be a sum of expectation values of Hermitian operators: L(θ) = Σ_k w_k ⟨ψ_k|U†(θ) H_k U(θ)|ψ_k⟩.
Scaling: : NFT updates one parameter at a time. Each full sweep through N parameters requires ≥2N function evaluations (depending on reset_interval).
  • Parameters:
    • randomized_order (bool) — If True, shuffles the order of parameters to update each lap (full sweep through all parameters). Default: False.
    • reset_interval (int) — How often to reset the recycled loss value. Set to 0 to disable resets. Default: 32.
    • maxfev (int) — Maximum number of function evaluations (circuit executions). Optimization stops when this limit is reached. Default: 200.
    • maxiter (int) — Maximum number of iterations (parameter updates). Default: 100.
    • eps (float) — Small epsilon value to avoid division by zero in the analytic solution. Default: 1e-32.
    • type (Literal [ ‘nft’ ])

Notes

Stopping criterion: Optimization stops when either maxfev or maxiter is reached, whichever comes first. Function evaluations per iteration: Each iteration uses 2-3 function evaluations. The very first iteration and the first iteration of each reset interval use 3 evaluations. Subsequent iterations reuse the previous optimal value, requiring only 2 evaluations.

Example

>>> from haiqu.sdk.qml import NFTOptimizerOptions
>>> optimizer = NFTOptimizerOptions(maxfev=500, maxiter=200)

class haiqu.sdk.qml.optimizer.ScipyOptimizerOptions(*, type=‘scipy’, method, maxfev=200, options)

Configuration for any derivative-free scipy.optimize.minimize method. The Haiqu backend wraps scipy.optimize.minimize for the four supported derivative-free methods. maxfev is the only option that is universal across all of them, so it gets a typed slot; everything else goes in the free-form options dict and is validated against a per-method whitelist at construction time, mirroring what scipy.optimize.minimize accepts.

- “cobyla“

Constrained Optimization BY Linear Approximation. Trust-region method with linear surrogates; robust default on noisy expectation values.

- “nelder-mead“

Downhill simplex. No surrogate model; forgiving on noisy or non-smooth objectives but tends to need more evaluations.

- “powell“

Direction-set method that minimizes along conjugate directions; often fast on well-conditioned problems.

- “cobyqa“

COBYLA’s quadratic-approximation successor; typically higher quality per evaluation than COBYLA at modest extra cost.
  • Parameters:
    • method (Literal [ ‘cobyla’ , ‘nelder-mead’ , ‘powell’ , ‘cobyqa’ ]) — scipy method name. One of cobyla, nelder-mead, powell, cobyqa.
    • maxfev (int) — Maximum number of function evaluations (circuit executions). Default: 200. The Haiqu backend enforces this cap uniformly across methods even when scipy’s native option name differs.
    • options (Dict *[*str , Any ]) — Per-method options forwarded to scipy.optimize.minimize. Allowed keys are validated at construction time; an unknown key raises ValueError. Per-method allowed keys:
      • cobyla: rhobeg, catol, disp
      • nelder-mead: xatol, fatol, adaptive, disp
      • powell: xtol, ftol, direc, disp
      • cobyqa: rhobeg, final_tr_radius, disp
      maxfev is intentionally excluded; pass it via the top-level field.
    • type (Literal [ ‘scipy’ ])

Notes

Final result selection: scipy methods can wander after they have found a good point. Haiqu therefore returns the best-so-far parameters tracked across the optimization, not the final scipy iterate.

Example

>>> from haiqu.sdk.qml import ScipyOptimizerOptions
>>> ScipyOptimizerOptions(method="cobyla", maxfev=200, options={"rhobeg": 0.5})
>>> ScipyOptimizerOptions(method="powell", maxfev=500, options={"xtol": 1e-6})
>>> ScipyOptimizerOptions(method="nelder-mead", options={"adaptive": True})