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, job_name=None)

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.
    • job_name (str | None) — The name for the job. If None (default), a name will be automatically generated.
  • 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). 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", maxiter=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=1024, maxiter=500, 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: 1024.
    • maxiter (int) — Maximum number of iterations (parameter updates). Default: 500.
    • 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=2048, maxiter=100)

class haiqu.sdk.qml.optimizer.ScipyOptimizerOptions(*, type=‘scipy’, method, maxiter=500, options=<factory>)

Configuration for any derivative-free scipy.optimize.minimize method. The Haiqu backend wraps scipy.optimize.minimize for the four supported derivative-free methods. maxiter 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.
    • maxiter (int) — Maximum number of iterations. Default: 500. Scipy interprets this slightly differently per method; see the per-method scipy docs for whether it caps iterations or function evaluations.
    • 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, maxfev, disp
      • powell: xtol, ftol, maxfev, direc, disp
      • cobyqa: rhobeg, final_tr_radius, disp
      maxiter 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. Function-evaluation cap: nelder-mead and powell accept maxfev in their options dict in addition to maxiter. cobyla and cobyqa do not expose a separate function-evaluation cap.

Example

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