> ## Documentation Index
> Fetch the complete documentation index at: https://docs.haiqu.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Variational Quantum Optimization

> Variational quantum optimization using the NFT optimizer.

#### 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.

This method uses the NFT (Nakanishi-Fujii-Todo) optimizer internally, a gradient-free
optimizer designed for variational quantum algorithms. For detailed information about
the algorithm, see the paper: [https://arxiv.org/abs/1903.12166](https://arxiv.org/abs/1903.12166)

* **Parameters:**
  * **problem** ([*VariationalProblem*](#haiqu.sdk.qml.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 NFT optimizer. If None, uses NFTOptimizerOptions
    with default values. See NFTOptimizerOptions for available settings.
  * **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:

```python theme={null}
>>> 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:

```python theme={null}
>>> 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)
```

### *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

```python theme={null}
>>> 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()`](../index.md#haiqu.sdk.quantum_haiqu.Haiqu.variational_optimization): Submit problem to Haiqu cloud.

### *class* haiqu.sdk.qml.optimizer.NFTOptimizerOptions(\*, 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](https://arxiv.org/abs/1903.12166)

Preconditions:
: NFT requires the following conditions on the parameterized quantum circuit:

<br />

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 (in iterations) to refresh the recycled
    loss sample. To save one circuit evaluation per iteration, NFT reuses the predicted
    minimum from the previous iteration's sinusoid fit as the next iteration's `z₀`
    sample, rather than re-measuring it. Because that value is a *prediction* from
    a single-frequency fit and not a measurement, it can drift from the true loss
    when the preconditions above are violated, when Trotter error is present, or
    when shot noise is high. Every `reset_interval` iterations, the recycled `z₀`
    is discarded and freshly measured (that iteration uses 3 evaluations instead
    of 2). Set to 0 to disable resets entirely — only safe when the preconditions
    hold exactly and shot noise is negligible. 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.

### 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.

Diagnosing the loss curve: If the loss history shows synchronous step
changes at iteration multiples of `reset_interval` (32, 64, 96, …), the
recycled `z₀` is drifting from the true loss between resets. The most
common cause is a precondition violation — typically a parameter that
appears in more than one gate (for example, UCCSD-style ansatzes share
an excitation amplitude across multiple Pauli-rotation gates after
Trotterization, breaking precondition 1). The fix is to switch ansatz
(e.g. to a hardware-efficient one like `EfficientSU2` where each
parameter sits on a single rotation gate) or to switch optimizer. Tuning
`reset_interval` controls how visible the symptom is, not the underlying
convergence quality.

### Example

```python theme={null}
>>> from haiqu.sdk.qml import NFTOptimizerOptions
>>> optimizer = NFTOptimizerOptions(maxfev=2048, maxiter=100)
```
