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

# Fourier Loading

#### Haiqu.fourier\_loading(fourier\_coefficients, num\_qubits=None, min\_freqs=None, long\_range=False, num\_layers=2, truncation\_cutoff=1e-06, fine\_tuning\_iterations=20, max\_time=900, name=None, job\_description=None)

Generate a quantum circuit that prepares a band-limited multidimensional state from its Fourier coefficients.

Given the Fourier coefficients of a state, this method creates a Data Loading job that runs in the Haiqu cloud. The
coefficients are synthesized on the smallest register that fits them, and an optimized quantum Fourier transform per
dimension expands them onto the output grid. The result of this job is a circuit which can be used to supply the
state to a quantum algorithm for processing. It pays off only for spectra concentrated in few frequencies, so the
state is assumed to represent a periodic function; symmetrizing a dimension can make a non-periodic target more
periodic.

The coefficients are normally obtained with [`compute_fourier_coefficients()`](#haiqu.sdk.utils.compute_fourier_coefficients), which takes an array of
values on a uniform grid and returns `fourier_coefficients` and `min_freqs` in exactly the form this method expects.

The complexity and quality of the generated circuit can be controlled by the `num_layers`,
`truncation_cutoff`, and `fine_tuning_iterations` parameters.

#### NOTE

**Frequency layout.** `fourier_coefficients[i0, i1, ...]` is the weight of the harmonic
`exp(2j*pi*((min_freqs[0] + i0)*x0 + (min_freqs[1] + i1)*x1 + ...))`, so coefficients ascend in
frequency along every axis — the layout [`compute_fourier_coefficients()`](#haiqu.sdk.utils.compute_fourier_coefficients) returns.

`np.fft.fftn` does not produce this order: it wraps the negative frequencies onto the end, returning a
length-4 axis as `[0, 1, -2, -1]` rather than `[-2, -1, 0, 1]`. Pass a raw FFT result through
`np.fft.fftshift` first, after which index 0 carries `-(n // 2)` — the `min_freqs` default.

`min_freqs` is applied as a frequency-shift layer, not just as bookkeeping. A value that does not match
how the coefficients were obtained encodes the wrong complex exponential, whose amplitude magnitudes may
still look plausible. Slicing a narrower band out of a shifted spectrum moves the first frequency, so pass
the `min_freqs` that [`compute_fourier_coefficients()`](#haiqu.sdk.utils.compute_fourier_coefficients) returns.

#### NOTE

**Grid indexing.** The encoded array is recovered from the statevector by reshaping it onto the per-dimension grid,
`Statevector(circuit).data.reshape([2**n for n in job.info["num_qubits_per_dimension"]])`, **up to a
global phase** left behind by the frequency-shift layer. The first array dimension occupies the
highest-index qubits, and grid point `m` along dimension `d` corresponds to
`x_d = m / 2**num_qubits_per_dimension[d]`.

Unlike [`vector_loading()`](../index.md#haiqu.sdk.quantum_haiqu.Haiqu.vector_loading), where qubits beyond the data size are dead zero padding, every output
qubit above the per-dimension minimum performs band-limited Fourier interpolation onto a finer grid, at
the cost of a Fourier layer rather than a wider state preparation. Resolution is therefore almost free:
the same 9x9 block of coefficients reaches the same fidelity on a 32x32 grid (10 qubits) as on a 256x256
grid (16 qubits). A dimension holding a single frequency needs no qubit to embed it,
so its entry in `num_qubits_per_dimension` may be `0` when `num_qubits` is `None`; such a
dimension carries a constant, and giving it output qubits spreads that constant over its grid.

#### NOTE

**\`\`job.quality\`\` excludes the truncation error.** It is the fidelity of synthesizing the coefficients you
supplied, which are taken as the exact target, so how they were obtained does not enter it. Discarding
frequencies is a separate, purely classical approximation, measured by the fidelity that
[`compute_fourier_coefficients()`](#haiqu.sdk.utils.compute_fourier_coefficients) returns. The fidelity of the final state against your original
array is approximately the product of the two.

* **Parameters:**
  * **fourier\_coefficients** (*Sequence* \*\[\**Number* *]*  *|* *np.ndarray*) -- The multidimensional array of Fourier coefficients,
    ascending in frequency along every axis, which is not the order `np.fft.fftn`
    returns (see the frequency-layout note above). May be real or complex, and is
    normalized in the process. This is the layout that
    [`compute_fourier_coefficients()`](#haiqu.sdk.utils.compute_fourier_coefficients) returns, which is the recommended way
    to obtain it; an analytically known spectrum can equally be written down directly.
  * **num\_qubits** (*int* *|* *Sequence* \*\[\**int* *]*  *|* *None*) -- The number of output qubits per dimension. An integer is shared by all
    dimensions, a sequence sets them individually in the order of the coefficient array's
    axes. Each entry must be at least `ceil(log2(n))` for a dimension holding `n`
    frequencies; every qubit above that minimum interpolates the state onto a finer grid.
    If `None` (default), the minimum that fits the coefficients is used, which performs
    no interpolation.
  * **min\_freqs** (*int* *|* *Sequence* \*\[\**int* *]*  *|* *None*) -- The integer frequency that the first coefficient of each dimension
    carries. An integer is shared by all dimensions, a sequence sets them individually
    in the order of the coefficient array's axes. If `None` (default), each dimension
    is assumed to hold a band centred on zero and takes `-(n // 2)` for its length
    `n`. It is applied as a frequency-shift layer, so a value that disagrees with how
    the coefficients were obtained silently encodes the wrong state — see the
    frequency-layout note above.
  * **long\_range** (*bool*) -- Whether to allow long-range gates in the Fourier part of the circuit. The prepared state is
    the same either way, while `False` (default) keeps the gates on a linear chain.
  * **num\_layers** (*int*) -- The number of layers used to synthesize the coefficients (from 1 to 100 layers).
    More layers can improve the quality of the output at the cost of a deeper circuit.
    Defaults to 2.
  * **truncation\_cutoff** (*Real*) -- The entanglement cutoff for later layers. Increasing this threshold may result in a
    smaller (but more approximate) circuit. Defaults to `1e-6`.
  * **fine\_tuning\_iterations** (*int*) -- The maximum number of fine-tuning iterations to perform after each layer is added.
    Increasing this limit may improve the quality of the circuit by using more
    classical resources. Defaults to 20, maximal is 500.
  * **max\_time** (*int* *|* *float*) -- Soft time limit for the job (in seconds).
    The data loading job will first always produce the initial result and then limit the fine-tuning
    stage by the remaining time left. If time limit exceeds during the fine-tuning - the best
    current result will be returned. Defaults to 900
    (15 min). Max allowed job time is 15 min.
    The job can take more wall clock time than user specified max\_time due to latency,
    initialization overheads or if the initial result already takes more time.
  * **name** (*str* *|* *None*) -- The name for the job and the produced circuit. If `None` (default), a name will be
    automatically generated.
  * **job\_description** (*str* *|* *None*) -- The description for the job.
* **Returns:**
  The Data Loading job that will generate the circuit for the band-limited state.
  : Call `job.result()` to retrieve a Qiskit-compatible gate (`HaiquCircuitGate`) that prepares the state.
  `job.quality` is the achieved state fidelity vs. the supplied coefficients; `job.info` exposes loader
  metadata (`fidelity`, `num_qubits_per_dimension`).
  Run `help(job.result)` for the full description of result and `info` contents.
* **Return type:**
  DataLoadingJobModel

#### Examples

An exact finite Fourier series. `cos(2*pi*3*x)` has only the -3 and +3 harmonics, so retaining the band
`(-3, 3)` discards nothing and both fidelities are 1:

```python theme={null}
>>> import numpy as np
>>> from haiqu.sdk.utils import compute_fourier_coefficients
>>> grid = np.arange(64) / 64
>>> cosine = np.cos(2 * np.pi * 3 * grid)
>>> coefficients, min_freqs, truncation_fidelity = compute_fourier_coefficients(
...     cosine, freq_ranges=[(-3, 3)], return_fidelity=True
... )
>>> print(coefficients.shape, min_freqs, f"{truncation_fidelity:.6f}")
(7,) [-3] 1.000000
>>> job = haiqu.fourier_loading(coefficients, num_qubits=6, min_freqs=min_freqs)
>>> fl_gate = job.result()  # fl_gate is a Qiskit-compatible gate
>>> print(f"Cosine was loaded with fidelity {job.quality:.6f}")
Cosine was loaded with fidelity 1.000000
```

A 2D Gaussian sampled on a 32x32 grid, encoded onto a 256x256 grid. Only 81 coefficients are sent, and the
eight output qubits per dimension interpolate the rest:

```python theme={null}
>>> axis = np.arange(32) / 32
>>> x, y = np.meshgrid(axis, axis, indexing="ij")
>>> gaussian = np.exp(-((x - 0.4) ** 2 + (y - 0.5) ** 2) / (4 * 0.15 ** 2))
>>> coefficients, min_freqs, truncation_fidelity = compute_fourier_coefficients(
...     gaussian, freq_ranges=[(-4, 4), (-4, 4)], return_fidelity=True
... )
>>> print(coefficients.shape, min_freqs, f"{truncation_fidelity:.6f}")
(9, 9) [-4, -4] 0.999535
>>> job = haiqu.fourier_loading(coefficients, num_qubits=8, min_freqs=min_freqs)
>>> fl_gate = job.result()
>>> print(f"{job.num_qubits} qubits, synthesis fidelity {job.quality:.6f}")
16 qubits, synthesis fidelity 0.999998
>>> print(f"Fidelity against the original array is about {truncation_fidelity * job.quality:.6f}")
Fidelity against the original array is about 0.999533
```

<a id="haiqu.sdk.utils.compute_fourier_coefficients" />

### haiqu.sdk.utils.compute\_fourier\_coefficients(state, freq\_ranges, return\_fidelity=False)

Compute truncated, normalized Fourier coefficients of an n-dimensional array.

The returned values are the input that
[`haiqu.sdk.quantum_haiqu.Haiqu.fourier_loading()`](../index.md#haiqu.sdk.quantum_haiqu.Haiqu.fourier_loading) expects: the coefficients ascend in
frequency along every axis, which is not the order `np.fft.fftn` returns, and `min_freqs`
records which frequency the first coefficient of each axis carries.

Keeping only a few frequencies per axis is what makes Fourier loading cheap, and it is also
the only lossy step performed here. The optional fidelity quantifies exactly that loss. It is
a purely classical quantity computed locally and for free, and it is *not* part of the loading
job's `quality`, which measures how well the cloud reproduces whatever coefficients it is
given. The fidelity of the final state against the original `state` is approximately the
product of the two.

* **Parameters:**
  * **state** (*np.ndarray* *|* *Sequence*) -- Values of the target on a (possibly coarse) uniform grid.
    The array is treated as periodic on that grid, so a non-periodic target may need many
    frequencies; symmetrizing it along a dimension can help.
  * **freq\_ranges** (*Sequence* \*\[\**Sequence* \*\[\**int* *]* *]*) -- One `(min_freq, max_freq)` pair per dimension,
    giving the inclusive frequency band to retain. Each band must lie inside the range the
    grid resolves, `[-(n // 2), n - n // 2 - 1]` for an axis of length `n`.
  * **return\_fidelity** (*bool*) -- Whether to also return the retained fraction of the spectral
    energy. Defaults to `False`.
* **Returns:**
  The complex
  coefficient array of shape `max_freq - min_freq + 1` per dimension, normalized to unit
  norm, and the per-dimension `min_freq` values. With `return_fidelity` a third value is
  appended: the squared overlap between the reconstruction from the retained frequencies and
  `state`. It is `1.0` when the bands cover the whole resolved spectrum, and also when
  they cover the entire support of a finite Fourier series, however few frequencies that is.
* **Return type:**
  tuple\[np.ndarray, list\[int]] | tuple\[np.ndarray, list\[int], float]

- **Raises:**
  **ValueError** -- If the number of bands does not match the number of dimensions, a band is not
  a pair, `min_freq` exceeds `max_freq`, or a band falls outside the resolved range.

#### Examples

```python theme={null}
>>> import numpy as np
>>> from haiqu.sdk.utils import compute_fourier_coefficients
>>> grid = np.arange(64) / 64
>>> cosine = np.cos(2 * np.pi * 3 * grid)  # only the -3 and +3 harmonics are non-zero
>>> coefficients, min_freqs, fidelity = compute_fourier_coefficients(
...     cosine, freq_ranges=[(-3, 3)], return_fidelity=True
... )
>>> print(coefficients.shape, min_freqs, f"{fidelity:.6f}")
(7,) [-3] 1.000000
```
