Skip to content

qiu_quantum_computing.phase_propagator.sample_based

Sample-based phase propagators, applying e^(i f(x)) for a signal f of one sign.

The signal is split into its sum alpha and the state |phi> = sqrt(f / alpha) by sample_based_decomposition, so that f = alpha |phi|^2. Each cycle of the protocol prepares |phi> in a second register, applies the phase e^(i delta) where both registers are in the same basis state (partial_phase_circuit), un-prepares |phi> and measures the second register. On success, i.e. measuring |0...0>, the amplitudes psi_j of the first register are mapped to psi_j (1 + (e^(i delta) - 1) |phi_j|^2), which is e^(i delta |phi_j|^2) psi_j up to O(delta^2). Slicing alpha into small deltas thus applies e^(i alpha |phi|^2) = e^(i f).

Classes:

Functions:

GenericIterativeSampleBasedPhasePropagator

GenericIterativeSampleBasedPhasePropagator(deltas: NDArray | list[float], U_phi: QuantumCircuit, U_phi_dagger: QuantumCircuit, take_snapshot: bool = False)

Bases: QuantumCircuit

A sample-based phase propagator with one cycle per delta.

Each cycle only runs if all previous ones succeeded, i.e. measured the phi register in |0...0>. The phi register is reset after each cycle.

Parameters:

  • deltas (NDArray | list[float]) –

    The phase of each cycle.

  • U_phi (QuantumCircuit) –

    The circuit preparing |phi> from |0...0>.

  • U_phi_dagger (QuantumCircuit) –

    The inverse of U_phi.

  • take_snapshot (bool, default: False ) –

    If True, save the statevector after each cycle, labeled by the cycle index (Aer simulators only).

Methods:

  • from_state –

    Create the propagator for the preparable state |phi>.

Attributes:

Source code in packages/qiu-quantum-computing/src/qiu_quantum_computing/phase_propagator/sample_based.py
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
def __init__(
    self,
    deltas: npt.NDArray | list[float],
    U_phi: QuantumCircuit,
    U_phi_dagger: QuantumCircuit,
    take_snapshot: bool = False,
) -> None:
    """Initialize the propagator.

    Args:
        deltas: The phase of each cycle.
        U_phi: The circuit preparing `|phi>` from `|0...0>`.
        U_phi_dagger: The inverse of `U_phi`.
        take_snapshot: If True, save the statevector after each cycle, labeled
            by the cycle index (Aer simulators only).
    """
    psi_reg, phi_reg, success_flag = propagator_registers(U_phi.num_qubits)
    super().__init__(
        psi_reg, phi_reg, success_flag, name="Iterative phase propagator"
    )
    self.num_of_cycles = len(deltas)

    for cycle, delta in enumerate(deltas):
        with self.if_test((success_flag, 0)):
            _append_cycle(self, delta, U_phi, U_phi_dagger, psi_reg, phi_reg)
            self.measure(phi_reg, success_flag)
            # on success, phi is already |0...0>; the reset keeps the corrupted
            # output inspectable after a failure
            self.reset(phi_reg)
            if take_snapshot:
                self.save_statevector(f"{cycle}")  # type: ignore[attr-defined]

num_of_cycles instance-attribute

num_of_cycles: int = len(deltas)

The number of cycles.

from_state classmethod

Create the propagator for the preparable state |phi>.

Source code in packages/qiu-quantum-computing/src/qiu_quantum_computing/phase_propagator/sample_based.py
197
198
199
200
201
202
203
204
@classmethod
def from_state(
    cls, state: PreparableState, deltas: npt.NDArray | list[float]
) -> "GenericIterativeSampleBasedPhasePropagator":
    """Create the propagator for the preparable state `|phi>`."""
    return cls(
        deltas=deltas, U_phi=state.circuit, U_phi_dagger=state.inverse_circuit
    )

GenericIterativeSampleBasedPhasePropagatorWithConstantDelta

GenericIterativeSampleBasedPhasePropagatorWithConstantDelta(delta: float, number_of_cycles: int, U_phi: QuantumCircuit, U_phi_dagger: QuantumCircuit)

Bases: QuantumCircuit

A sample-based phase propagator repeating one delta in a loop.

The loop breaks at the first failed cycle, i.e. when the phi register is not measured in |0...0>.

Parameters:

  • delta (float) –

    The phase of each cycle.

  • number_of_cycles (int) –

    The number of cycles.

  • U_phi (QuantumCircuit) –

    The circuit preparing |phi> from |0...0>.

  • U_phi_dagger (QuantumCircuit) –

    The inverse of U_phi.

Methods:

  • from_state –

    Create the propagator for the preparable state |phi>.

Attributes:

Source code in packages/qiu-quantum-computing/src/qiu_quantum_computing/phase_propagator/sample_based.py
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
def __init__(
    self,
    delta: float,
    number_of_cycles: int,
    U_phi: QuantumCircuit,
    U_phi_dagger: QuantumCircuit,
) -> None:
    """Initialize the propagator.

    Args:
        delta: The phase of each cycle.
        number_of_cycles: The number of cycles.
        U_phi: The circuit preparing `|phi>` from `|0...0>`.
        U_phi_dagger: The inverse of `U_phi`.
    """
    psi_reg, phi_reg, success_flag = propagator_registers(U_phi.num_qubits)
    super().__init__(
        psi_reg, phi_reg, success_flag, name="Iterative phase propagator"
    )
    self.num_of_cycles = number_of_cycles

    # Qiskit annotates the context manager form of for_loop too narrowly
    with self.for_loop(range(number_of_cycles)):  # pyright: ignore[reportCallIssue]
        _append_cycle(self, delta, U_phi, U_phi_dagger, psi_reg, phi_reg)
        self.measure(phi_reg, success_flag)
        self.reset(phi_reg)

        with self.if_test((success_flag, 0)) as else_:
            self.continue_loop()
        with else_:
            self.break_loop()

num_of_cycles instance-attribute

num_of_cycles: int = number_of_cycles

The number of cycles.

from_state classmethod

Create the propagator for the preparable state |phi>.

Source code in packages/qiu-quantum-computing/src/qiu_quantum_computing/phase_propagator/sample_based.py
249
250
251
252
253
254
255
256
257
258
259
@classmethod
def from_state(
    cls, state: PreparableState, delta: float, number_of_cycles: int
) -> "GenericIterativeSampleBasedPhasePropagatorWithConstantDelta":
    """Create the propagator for the preparable state `|phi>`."""
    return cls(
        delta=delta,
        number_of_cycles=number_of_cycles,
        U_phi=state.circuit,
        U_phi_dagger=state.inverse_circuit,
    )

QuadraticSignalSampleBasedPhasePropagator

QuadraticSignalSampleBasedPhasePropagator(signal: SampledSignal, max_delta: float, method: SynthesisMethod = GATE)

Bases: QuantumCircuit

The sample-based phase propagator applying e^(i f(x)) for a signal f.

The signal is decomposed as f = alpha |phi|^2 by sample_based_decomposition, and alpha is sliced into equal deltas of magnitude at most max_delta.

Parameters:

  • signal (SampledSignal) –

    The real signal f of one sign, on an axis of 2**n samples.

  • max_delta (float) –

    The maximum phase per cycle.

  • method (SynthesisMethod, default: GATE ) –

    How the preparation of |phi> is represented in the circuit.

Attributes:

Source code in packages/qiu-quantum-computing/src/qiu_quantum_computing/phase_propagator/sample_based.py
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
def __init__(
    self,
    signal: SampledSignal,
    max_delta: float,
    method: SynthesisMethod = SynthesisMethod.GATE,
) -> None:
    """Initialize the propagator.

    Args:
        signal: The real signal `f` of one sign, on an axis of `2**n` samples.
        max_delta: The maximum phase per cycle.
        method: How the preparation of `|phi>` is represented in the circuit.
    """
    alpha, state = sample_based_decomposition(signal)
    deltas = slice_alpha_to_deltas_evenly(alpha, max_delta)

    psi_reg, phi_reg, success_flag = propagator_registers(
        num_qubits_of(signal.axis)
    )
    super().__init__(
        psi_reg, phi_reg, success_flag, name="Quadratic signal phase propagator"
    )

    propagator = (
        GenericIterativeSampleBasedPhasePropagatorWithConstantDelta.from_state(
            state=PreparableState(state, method=method),
            delta=deltas[0],
            number_of_cycles=len(deltas),
        )
    )
    self.num_of_cycles = propagator.num_of_cycles
    self.compose(propagator, inplace=True)

num_of_cycles instance-attribute

num_of_cycles: int = propagator.num_of_cycles

The number of cycles.

sample_based_decomposition

sample_based_decomposition(signal: SampledSignal) -> tuple[float, Statevector]

Split a real signal of one sign into its sum and a normalized state.

Parameters:

  • signal (SampledSignal) –

    The signal f, real and either non-negative or non-positive, on an axis of 2**n samples.

Returns:

  • float –

    The sum alpha of the samples and the state sqrt(f / alpha), such that

  • Statevector –

    f = alpha |state|^2.

Raises:

  • ValueError –

    If the axis does not have 2**n samples with n >= 1, or if the signal is not real, has samples of both signs, or vanishes.

Source code in packages/qiu-quantum-computing/src/qiu_quantum_computing/phase_propagator/sample_based.py
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
def sample_based_decomposition(signal: SampledSignal) -> tuple[float, Statevector]:
    """Split a real signal of one sign into its sum and a normalized state.

    Args:
        signal: The signal `f`, real and either non-negative or non-positive, on an
            axis of `2**n` samples.

    Returns:
        The sum `alpha` of the samples and the state `sqrt(f / alpha)`, such that
        `f = alpha |state|^2`.

    Raises:
        ValueError: If the axis does not have `2**n` samples with `n >= 1`, or if the
            signal is not real, has samples of both signs, or vanishes.
    """
    num_qubits_of(signal.axis)
    data = np.asarray(signal.data)

    if np.iscomplexobj(data):
        if np.any(data.imag != 0):
            raise ValueError("The signal must be real.")
        data = data.real
    if np.any(data > 0) and np.any(data < 0):
        raise ValueError("The samples of the signal must all have the same sign.")

    alpha = float(np.sum(data))
    if alpha == 0:
        raise ValueError("The signal must not vanish.")

    return alpha, Statevector(np.sqrt(data / alpha))

slice_alpha_to_deltas_evenly

slice_alpha_to_deltas_evenly(alpha: float, max_delta: float) -> NDArray

Slice alpha into the fewest equal deltas of magnitude at most max_delta.

Parameters:

  • alpha (float) –

    The total phase coefficient to slice.

  • max_delta (float) –

    The positive maximum magnitude of each delta.

Returns:

  • NDArray –

    The equal deltas summing up to alpha, none if alpha is 0.

Raises:

Source code in packages/qiu-quantum-computing/src/qiu_quantum_computing/phase_propagator/sample_based.py
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
def slice_alpha_to_deltas_evenly(alpha: float, max_delta: float) -> npt.NDArray:
    """Slice `alpha` into the fewest equal deltas of magnitude at most `max_delta`.

    Args:
        alpha: The total phase coefficient to slice.
        max_delta: The positive maximum magnitude of each delta.

    Returns:
        The equal deltas summing up to `alpha`, none if `alpha` is 0.

    Raises:
        ValueError: If `max_delta` is not positive.
    """
    if max_delta <= 0:
        raise ValueError(f"The max_delta must be positive, got {max_delta}.")

    number_of_deltas = int(np.ceil(abs(alpha) / max_delta))
    if number_of_deltas == 0:
        return np.zeros(0)
    return np.full(number_of_deltas, alpha / number_of_deltas)

partial_phase_diagonal

partial_phase_diagonal(delta: float, num_qubits: int) -> NDArray[complex128]

Return the diagonal of the unitary of partial_phase_circuit.

The entry of |j>|l>, at index l * 2**n + j, is e^(i delta [j == l]).

Parameters:

  • delta (float) –

    The phase.

  • num_qubits (int) –

    The number of qubits n of each register.

Returns:

Source code in packages/qiu-quantum-computing/src/qiu_quantum_computing/phase_propagator/sample_based.py
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def partial_phase_diagonal(delta: float, num_qubits: int) -> npt.NDArray[np.complex128]:
    """Return the diagonal of the unitary of `partial_phase_circuit`.

    The entry of `|j>|l>`, at index `l * 2**n + j`, is `e^(i delta [j == l])`.

    Args:
        delta: The phase.
        num_qubits: The number of qubits `n` of each register.

    Returns:
        The `4**n` diagonal entries.
    """
    dimension = 2**num_qubits
    diagonal = np.ones(dimension**2, dtype=np.complex128)
    diagonal[:: dimension + 1] = np.exp(1j * delta)
    return diagonal

partial_phase_circuit

partial_phase_circuit(delta: float, num_qubits: int) -> QuantumCircuit

Return the phase e^(i delta) on the basis states where both registers agree.

The circuit acts on the registers psi (qubits 0, ..., n-1) and phi (qubits n, ..., 2n-1) and maps |j>|l> to e^(i delta [j == l]) |j>|l>.

Parameters:

  • delta (float) –

    The phase.

  • num_qubits (int) –

    The number of qubits n of each register.

Returns:

Source code in packages/qiu-quantum-computing/src/qiu_quantum_computing/phase_propagator/sample_based.py
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
def partial_phase_circuit(delta: float, num_qubits: int) -> QuantumCircuit:
    """Return the phase `e^(i delta)` on the basis states where both registers agree.

    The circuit acts on the registers `psi` (qubits `0, ..., n-1`) and `phi` (qubits
    `n, ..., 2n-1`) and maps `|j>|l>` to `e^(i delta [j == l]) |j>|l>`.

    Args:
        delta: The phase.
        num_qubits: The number of qubits `n` of each register.

    Returns:
        The circuit on `2n` qubits.
    """
    psi_reg = QuantumRegister(num_qubits, name=r"\psi")
    phi_reg = QuantumRegister(num_qubits, name=r"\phi")
    circuit = QuantumCircuit(psi_reg, phi_reg, name="partial_phase")

    # flag the bits of psi that agree with phi, in place ...
    for psi_bit, phi_bit in zip(psi_reg, phi_reg, strict=True):
        circuit.cx(phi_bit, psi_bit, ctrl_state=0)
    # ... apply the phase if all of them agree ...
    if num_qubits == 1:
        circuit.p(delta, psi_reg[0])
    else:
        circuit.mcp(delta, psi_reg[:-1], psi_reg[-1])
    # ... and restore psi
    for psi_bit, phi_bit in zip(psi_reg, phi_reg, strict=True):
        circuit.cx(phi_bit, psi_bit, ctrl_state=0)

    return circuit

propagator_registers

propagator_registers(num_qubits: int) -> tuple[QuantumRegister, QuantumRegister, ClassicalRegister]

Return the registers of a propagator: psi, phi and the success flags.

Source code in packages/qiu-quantum-computing/src/qiu_quantum_computing/phase_propagator/sample_based.py
144
145
146
147
148
149
150
151
152
def propagator_registers(
    num_qubits: int,
) -> tuple[QuantumRegister, QuantumRegister, ClassicalRegister]:
    """Return the registers of a propagator: `psi`, `phi` and the success flags."""
    return (
        QuantumRegister(num_qubits, name=r"\psi"),
        QuantumRegister(num_qubits, name=r"\phi"),
        ClassicalRegister(num_qubits, name="success_flag"),
    )