Skip to content

State Preparation

Most quantum applications start with preparing a state in a quantum register. For example, in finance the state may represent price distribution of some assets. In chemistry, it may be an initial guess for the ground state of a molecule and in a quantum machine learning, feature vector to be analyzed.

The state preparation function creates a quantum circuit which outputs either a probability distribution \(p_{i}\) or a real amplitudes vector \(a_{i}\) in the computational basis, with \(i\) denoting the corresponding basis state. The amplitudes should be given in the form of list of float numbers. The probabilities can be described as a list or positive numbers or a mixture of a gaussian distributions. The resulting wave function is given by

\[ \left|\psi\right\rangle = \sum_{i}\sqrt{p_{i}} \left|i\right\rangle, \]

in the case of probability and by

\[ \left|\psi\right\rangle = \sum_{i}a_{i} \left|i\right\rangle, \]

in the case of amplitude

In general, state preparation is hard. Only a very small portion of the Hilbert space can be prepared efficiently (in \(O(poly(n))\) steps) on a quantum circuit. Therefore, in practice, an approximation is often used to lower the complexity. The approximation is specified by an error metric and error range.

For a circuit consisting of error-less gates, there are several options for the error metric:

For the amplitude preparation only The \(L_p\) norms are available.

The higher the specified error tolerance, the smaller the output circuit will be. Not specifying an error will result in an attempt to build the exact circuit.

Syntax

Function: StatePreparation

Parameters:

  • probabilities: [pmf, GaussianMixture, List, Tuple, Ndarray]

    or amplitudes: [ List, Tuple, Ndarray]

  • error_metric: Optional[[KL, L1, L2, MAX_PROBABILITY, LOSS_OF_FIDELITY, TOTAL_VARIATION, HELLINGER, BHATTACHARYYA], NonNegativeFloatRange]]
  • is_uniform_start: Optional[bool] = true
{
  "function": "StatePreparation",
  "function_params": {
    "probabilities": [0.05, 0.11, 0.13, 0.23, 0.27, 0.12, 0.03, 0.06],
    "error_metric": { "KL": { "upper_bound": 0.01 } }
  }
}

Example 1: Loading Point Mass Function(PMF)

{
    "constraints": {
        "max_depth": 91
    },
    "logic_flow": [{
        "function": "StatePreparation",
        "function_params": {
            "probabilities": [0.05, 0.11, 0.13, 0.23, 0.27, 0.12, 0.03, 0.06],
            "error_metric": {"KL": {"upper_bound": 0.01}}
        }
    }]
}
from classiq import Model
from classiq.model import Constraints
from classiq.builtin_functions import StatePreparation

probabilities = (0.05, 0.11, 0.13, 0.23, 0.27, 0.12, 0.03, 0.06)
params = StatePreparation(
    probabilities=probabilities,
    error_metric={"KL": {"upper_bound": 0.01}},
)

constraints = Constraints(max_depth=91)
model = Model(constraints=constraints)
model.StatePreparation(params)
circuit = model.synthesize()

This example generates a circuit whose output state probabilities are an approximation to the pmf given. That is, the probability to measure the state \(\ket{000}\) is 0.05, \(\ket{001}\) is 0.11,... ,and the probability to measure \(\ket{111}\) is 0.06. The error metric used is Kullback–Leibler.

example_1.png

To execute the circuit, you may run the following code:

Create the following file: execution_preferences.exct

{
    "preferences": {
        "num_shots": 4000,
        "backend_preferences": {
            "backend_service_provider": "IBM Quantum",
            "backend_name": "aer_simulator"
        }
    }
}
Run Classiq execute command (Classiq: Execute Quantum Program), choose a file containing the program and pick it's instruction set (e.g. QASM or ionq) and the outptut path.

from classiq import Executor
from classiq import execution

res = Executor(
    num_shots=4000,
    backend_preferences=execution.IBMBackendPreferences(backend_name="aer_simulator"),
).execute(circuit)

To print the result on the SDK, use the code below:

counts = sorted(res.counts.items())
print(
    f"probabilities are:\n{dict([(bit_string, count/NUM_SHOT) for bit_string, count in counts])}"
)

which results in the following values:

probabilities are:
{'0000': 0.048, '0001': 0.133, '0010': 0.129, '0011': 0.228, '0100': 0.255, '0101': 0.114, '0110': 0.047, '0111': 0.046}

Example 2: Loading Gaussian Mixture

{
 "constraints": {
        "max_depth": 91
  },
 "logic_flow": [
  {
   "function": "StatePreparation",
   "function_params": {
    "probabilities": {
     "gaussian_moment_list": [
      {
       "mu": 1,
       "sigma": 1
      },
      {
       "mu": 3,
       "sigma": 1
      },
      {
       "mu": -3,
       "sigma": 1
      }
     ],
     "num_qubits": 8
    },
    "error_metric": {
     "L2": {
      "upper_bound": 0.023
     }
    }
   }
  }
 ]
}
from classiq import Model
from classiq.model import Constraints
from classiq.builtin_functions import StatePreparation
from classiq.builtin_functions.state_preparation import (
    GaussianMixture,
    GaussianMoments,
)

params = StatePreparation(
    probabilities=GaussianMixture(
        gaussian_moment_list=(
            GaussianMoments(mu=1, sigma=1),
            GaussianMoments(mu=3, sigma=1),
            GaussianMoments(mu=-3, sigma=1),
        ),
        num_qubits=8,
    ),
    error_metric={"L2": {"upper_bound": 0.023}},
)

constraints = Constraints(max_depth=91)
model = Model(constraints=constraints)
model.StatePreparation(params)
circuit = model.synthesize()

This example generates a circuit whose output state probabilities correspond to a gaussian mixture. GaussianMixture includes a list of gaussian functions to describe the total distribution, and num_qubits field to determine the sampling. Each gaussian function is described by mean (mu) and standard deviation (sigma). The underlying pmf is calculated in the following way: first, the support of the gaussian mixture cdf is truncated at 5 sigma from the gaussian at each edge, second, the support is divided into an equal size grid containing \(2^8+ 1\) points and lastly the pmf is calculated by taking the difference between the cdf values of consecutive grid points. The error metric used is L2. Note that 4 qubits do not undergo any operation. This is due to the selected error bound (these qubits correspond to the least significant bits). A tighter error bound would result in a circuit operating on more qubits.

example_2.png

To execute the circuit, you may run the following code:

Create the following file: execution_preferences.exct

{
    "preferences": {
        "num_shots": 4000,
        "backend_preferences": {
            "backend_service_provider": "IBM Quantum",
            "backend_name": "aer_simulator"
        }
    }
}
Run Classiq execute command (Classiq: Execute Quantum Program), choose a file containing the program and pick it's instruction set (e.g. QASM or ionq) and the outptut path.

from classiq import Executor
from classiq import execution

res = Executor(
    num_shots=4000,
    backend_preferences=execution.IBMBackendPreferences(backend_name="aer_simulator"),
).execute(circuit)

The resulting plot and the code to generate it using the SDK are shown below.

from matplotlib import pyplot as plt

sorted_counts = dict(sorted(res.counts.items()))
bit_strings, counts = sorted_counts.keys(), sorted_counts.values()
plt.title("Gaussian Mixtures graph")
plt.xlabel("State")
plt.ylabel("Measurement Probability [%]")
plt.plot(
    [int(bit_str, 2) for bit_str in bit_strings], [count / NUM_SHOT for count in counts]
)
plt.show()

example_2_graph.png

Example 3 - Amplitudes Preparation

{
    "constraints": {
        "max_depth": 120
    },
    "logic_flow": [{
        "function": "StatePreparation",
        "function_params": {
            "amplitudes": [-0.540061731, -0.38575837, -0.23145502, -0.07715167,  0.07715167,
    0.23145502,  0.385758371,  0.540061731],
            "error_metric": {"L2": {"upper_bound": 0.1}}
        }
    }]
}
from classiq import Model
from classiq.model import Constraints
from classiq.builtin_functions import StatePreparation
import numpy as np

amp = np.linspace(-1, 1, 8)
amp = amp / np.linalg.norm(amp)

params = StatePreparation(
    amplitudes=amp,
    error_metric={"L2": {"upper_bound": 0.1}},
)

constraints = Constraints(max_depth=120)
model = Model(constraints=constraints)
model.StatePreparation(params)
circuit = model.synthesize()

In this example, we load a normalized linear space between -1 to 1. The load state has an accuracy of 90 present under the L2 norm.

Note

When amplitudes are loaded, probabilities shouldn't pass as an argument. StatePreparation must get either probabilities or amplitudes but not both of them.

example_4.png