Skip to content

Prepare partial exponential state

[View on GitHub :material-github:](https://github.com/Classiq/classiq-library/tree/main/applications/finance/autocallable_options/partial_exponential_state_preparation.ipynb){ .md-button .md-button--primary target='_blank'}

The notebook shows how to construct the following state:

\[|\psi\rangle = \sum_{x_0}^{x_1}\sqrt{\frac{e^{-ar}}{Z}}|r\rangle\]
\[Z = \sum_{x_0}^{x_1}\sqrt{e^{-ar}}\]

The methodology is to load the state on the full range of states, then use exact amplitude amplification to leave only the wanted part.

image.png

Exponential state preparation on the full interval

import matplotlib.pyplot as plt
import numpy as np

from classiq import *
from classiq.execution import *

EXP_RATE = 0.5
NUM_QUBITS = 5


@qfunc
def main(x: Output[QNum]):
    allocate(NUM_QUBITS, x)
    prepare_exponential_state(-EXP_RATE, x)


execution_preferences = ExecutionPreferences(
    num_shots=1,
    backend_preferences=ClassiqBackendPreferences(
        backend_name=ClassiqSimulatorBackendNames.SIMULATOR_STATEVECTOR
    ),
)
qprog = synthesize(create_model(main, execution_preferences=execution_preferences))
res = execute(qprog).get_sample_result()
def parse_res(r, plot=True):
    x = []
    amps = []
    r = res
    for s in r.parsed_state_vector:
        if s.state["x"] in x:
            amps[x.index(s.state["x"])] += np.abs(s.amplitude)
        else:
            x.append(s.state["x"])
            amps.append(np.abs(s.amplitude))
    if plot:
        plt.scatter(x, amps)
        plt.xlabel("x")
        plt.ylabel("amplitude")
    return x, amps


x, amps = parse_res(res)

png

Exp State on a specific interval with Exact Amplitude Amplification

X0 = 15
X1 = 29
X_MIN = 0
X_MAX = 2**NUM_QUBITS - 1


def get_good_states_amplitude(x0, x1, exp_rate, num_qubits):
    x_min = 0
    x_max = 2**NUM_QUBITS - 1
    """for the range[x0, x1] including x0 and x1"""
    return np.sqrt(
        (np.exp(exp_rate * x1) - np.exp(exp_rate * x0))
        / (np.exp(exp_rate * x_max) - np.exp(exp_rate * x_min))
    )


AMPLITUDE = get_good_states_amplitude(X0, X1, EXP_RATE, NUM_QUBITS)
print(AMPLITUDE)
0.6062541106972759
from classiq.qmod.symbolic import logical_and


@qfunc
def oracle_comp(x: QNum, res: QBit):
    res ^= logical_and(x >= X0, x <= X1)


@qfunc
def main(x: Output[QNum]):
    allocate(NUM_QUBITS, x)
    exact_amplitude_amplification(
        amplitude=AMPLITUDE,
        oracle=lambda _x: phase_oracle(oracle_comp, _x),
        space_transform=lambda _x: prepare_exponential_state(-EXP_RATE, _x),
        packed_qvars=x,
    )


qprog = synthesize(create_model(main, execution_preferences=execution_preferences))
show(qprog)

res = execute(qprog).get_sample_result()
x, measured_amps = parse_res(res, plot=False)

# compare to expected amplitudes
grid = np.arange(X0, X1 + 1)
expected_amps = np.sqrt(np.exp(EXP_RATE * grid))
expected_amps /= np.linalg.norm(expected_amps)

plt.scatter(grid, expected_amps, marker="+", s=100, label="expected")
plt.scatter(x, measured_amps, label="measured")
plt.xlabel("x")
plt.ylabel("amplitude")
plt.legend()
plt.show()
Opening: https://platform.classiq.io/circuit/2vipzHDRUsWlhsEXuViIOrANEOf?login=True&version=0.74.0

png

Adjust to the case that a single grover is not enough

If the wanted range does not hold enough amplitude, it is enough to load the end of the range (for positive EXP_RATE) or the beginning of the range (for negative EXP_RATE), then finish with a modular adder.

from classiq.qmod.symbolic import logical_and

X0 = 3
X1 = 13
X_MIN = 0
X_MAX = 2**NUM_QUBITS - 1

AMPLITUDE = get_good_states_amplitude(X0, X1, EXP_RATE, NUM_QUBITS)

print(AMPLITUDE)
0.011071508393649327

This fraction of good states is not enough for a single grover iteration to amplify to 1. So we first load the same sized interval at the end of the range:

X_MAX = 2**NUM_QUBITS - 1
if EXP_RATE > 0:
    AMPLITUDE = get_good_states_amplitude(
        X_MAX - (X1 - X0), X_MAX, EXP_RATE, NUM_QUBITS
    )
else:
    AMPLITUDE = get_good_states_amplitude(0, X1 - X0, EXP_RATE, NUM_QUBITS)

print(AMPLITUDE)


@qfunc
def oracle_comp(x: QNum, res: QBit):
    if EXP_RATE > 0:
        res ^= x >= X_MAX - (X1 - X0)
    else:
        res ^= x <= (X1 - X0)


@qfunc
def main(x: Output[QNum]):
    allocate(NUM_QUBITS, x)
    exact_amplitude_amplification(
        amplitude=AMPLITUDE,
        oracle=lambda _x: phase_oracle(oracle_comp, _x),
        space_transform=lambda _x: prepare_exponential_state(-EXP_RATE, _x),
        packed_qvars=x,
    )
    # shift to the wanted domain
    if EXP_RATE > 0:
        x += -(X_MAX - X1)
    else:
        x += X0


qmod = create_model(main, execution_preferences=execution_preferences)
qprog = synthesize(qmod)
show(qprog)

res = execute(qprog).get_sample_result()
x, measured_amps = parse_res(res, plot=False)

# compare to expected amplitudes
grid = np.arange(X0, X1 + 1)
expected_amps = np.sqrt(np.exp(EXP_RATE * grid))
expected_amps /= np.linalg.norm(expected_amps)

plt.scatter(grid, expected_amps, marker="+", s=100, label="expected")
plt.scatter(x, measured_amps, label="measured")
plt.xlabel("x")
plt.ylabel("amplitude")
plt.legend()
plt.show()
0.996625424765961
Opening: https://platform.classiq.io/circuit/2vix8eeN70DpxQCHGqp7PQyEOaP?login=True&version=0.74.0

png

Verify the results:

x, measured_amps = parse_res(res, plot=False)
for i, amp in zip(x, measured_amps):
    if i >= X0 and i <= X1:
        assert np.isclose(amp, expected_amps[i - X0], atol=0.01)