Vai al contenuto principale
Questo progetto è in fase beta. Hai trovato un bug? Apri una issue su GitHub. Hai una domanda o un'idea? Chiedi su GitHub Discussions — apprezziamo il tuo feedback!
Com'è la qualità della traduzione su questa pagina?

Usa diverse norme Lp per il troncamento dei termini di Pauli

Nota: Prima di leggere questa guida, dovresti leggere la guida Truncate Pauli terms, che descrive il troncamento dei termini di Pauli a basso peso integrato nel metodo backpropagate sulla base di un TruncationErrorBudget specificato. In questa guida imparerai a conoscere l'argomento keyword di backpropagate, p_norm, che può essere usato per cambiare la norma Lp utilizzata per stimare l'errore introdotto dai termini di Pauli troncati.

Costruire un circuito di esempio​

Questa guida usa lo stesso circuito di esempio della guida Truncate Pauli terms:

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"matplotlib": "matplotlib", "qiskit": "qiskit", "qiskit_addon_obp": "qiskit-addon-obp", "qiskit_addon_utils": "qiskit-addon-utils", "rustworkx": "rustworkx"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
import rustworkx.generators
from qiskit.synthesis import LieTrotter
from qiskit_addon_utils.problem_generators import (
PauliOrderStrategy,
generate_time_evolution_circuit,
generate_xyz_hamiltonian,
)
from qiskit_addon_utils.slicing import combine_slices, slice_by_gate_types

# Generate a linear chain of 10 qubits
num_qubits = 10
linear_chain = rustworkx.generators.path_graph(num_qubits)

# Use an arbitrary XY model
hamiltonian = generate_xyz_hamiltonian(
linear_chain,
coupling_constants=(0.05, 0.02, 0.0),
ext_magnetic_field=(0.02, 0.08, 0.0),
pauli_order_strategy=PauliOrderStrategy.InteractionThenColor,
)
# Evolve for some time
circuit = generate_time_evolution_circuit(
hamiltonian, synthesis=LieTrotter(reps=3), time=2.0
)
# slice the circuit by gate type
slices = slice_by_gate_types(circuit)

# For visualization purposes only, recombine the slices with barriers between them and draw the resulting circuit
combine_slices(slices, include_barriers=True).draw("mpl", fold=50, scale=0.6)

Output of the previous code cell

Definisci un'osservabile per la magnetizzazione totale:

from qiskit.quantum_info import SparsePauliOp

obs = SparsePauliOp.from_sparse_list(
[("Z", [i], 1.0) for i in range(num_qubits)], num_qubits=num_qubits
)

Come riferimento, calcola il valore di aspettazione esatto:

from qiskit.primitives import StatevectorEstimator

estimator = StatevectorEstimator()
job = estimator.run([(circuit, obs)])
res = job.result()
exact_exp = res[0].data.evs
print(exact_exp)
9.318197859862146

Usare la norma L1​

Per impostazione predefinita, e come hai già visto nella guida Truncate Pauli terms, p_norm=1, il che significa che l'errore viene stimato come:

∣⟨ψ∣Δ∣ψ⟩∣≤∑P∈T∣cP∣|\langle\psi|\Delta|\psi\rangle| \leq \sum_{P\in\mathcal{T}} |c_P|

dove ψ\psi è lo stato quantistico, Δ\Delta è la differenza reale tra l'osservabile esatta e quella troncata (che è sconosciuta), T\mathcal{T} è l'insieme dei termini di Pauli che sono stati troncati e cPc_P è il coefficiente del termine di Pauli. Questa disuguaglianza è un limite superiore rigoroso ma molto debole nella maggior parte degli scenari. In questa guida, si effettua la retropropagazione di sei slice del circuito di esempio, usando un errore costante per slice pari a 0.001. Questo valore va inteso come il budget all'interno della p_norm data.

from qiskit_addon_obp.utils.truncating import setup_budget

l1_truncation_error_budget = setup_budget(max_error_per_slice=0.001, p_norm=1)
print(l1_truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.001], max_error_total=inf, p_norm=1)
from qiskit_addon_obp import backpropagate

max_slices = 6
l1_bp_obs, l1_remaining_slices, l1_metadata = backpropagate(
obs,
slices[-max_slices:],
truncation_error_budget=l1_truncation_error_budget,
)
l1_reduced_circuit = combine_slices(
slices[:-max_slices] + l1_remaining_slices
)
print(
f"Backpropagated {max_slices - len(l1_remaining_slices)} circuit slices."
)
print(
f"New observable contains {len(l1_bp_obs)} terms and {len(l1_bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 6 circuit slices.
New observable contains 116 terms and 10 commuting groups.

Ora possiamo calcolare il valore di aspettazione dell'osservabile retropropagata, così come l'errore rispetto al riferimento esatto:

estimator = StatevectorEstimator()
job = estimator.run([(l1_reduced_circuit, l1_bp_obs)])
res = job.result()
l1_exp = res[0].data.evs
l1_error = exact_exp - l1_exp
print(l1_exp, l1_error)
9.317869899338842 0.00032796052330397174

Infine, possiamo tracciare il grafico dell'errore introdotto durante la retropropagazione di ciascuna slice, così come dell'errore accumulato. L'errore accumulato è la somma degli errori delle singole slice. Possiamo vedere che l'errore accumulato è un limite superiore molto debole dell'errore effettivo.

from matplotlib import pyplot as plt
from qiskit_addon_obp.utils.visualization import (
plot_accumulated_error,
plot_slice_errors,
)

fig, axes = plt.subplots(1, 2, figsize=(14, 5))
axes[1].plot([6], [l1_error], "x", color="red", label="actual error")
plot_slice_errors(l1_metadata, axes[0])
plot_accumulated_error(l1_metadata, axes[1])

Output of the previous code cell

Usare la norma L2​

Si può sostenere che la norma L2 sia un'approssimazione migliore dell'errore introdotto rispetto alla norma L1. Il motivo è che possiamo assumere che lo stato quantistico ∣ψ⟩|\psi\rangle sia estratto da un ensemble casuale di Haar, nel qual caso l'errore introdotto segue una distribuzione con media nulla e una varianza che può essere approssimativamente limitata dalla norma L2:

∣⟨ψ∣Δ∣ψ⟩∣≲(∑P∈T∣cP∣2)1/2|\langle\psi|\Delta|\psi\rangle| \lesssim \left( \sum_{P\in\mathcal{T}} |c_P|^2 \right)^{1/2}

Sebbene il limite non sia rigoroso, verrà violato solo in casi patologici. Effettuiamo di nuovo la retropropagazione di sei slice del circuito di esempio, usando un errore massimo per slice pari a 0.001 (questa volta interpretato nella norma L2).

l2_truncation_error_budget = setup_budget(max_error_per_slice=0.001, p_norm=2)
print(l2_truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.001], max_error_total=inf, p_norm=2)
max_slices = 6
l2_bp_obs, l2_remaining_slices, l2_metadata = backpropagate(
obs,
slices[-max_slices:],
truncation_error_budget=l2_truncation_error_budget,
)
l2_reduced_circuit = combine_slices(
slices[:-max_slices] + l2_remaining_slices
)
print(
f"Backpropagated {max_slices - len(l2_remaining_slices)} circuit slices."
)
print(
f"New observable contains {len(l2_bp_obs)} terms and {len(l2_bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 6 circuit slices.
New observable contains 84 terms and 6 commuting groups.

Di nuovo, calcoliamo il valore di aspettazione dell'osservabile retropropagata e l'errore rispetto al riferimento esatto:

estimator = StatevectorEstimator()
job = estimator.run([(l2_reduced_circuit, l2_bp_obs)])
res = job.result()
l2_exp = res[0].data.evs
l2_error = exact_exp - l2_exp
print(l2_exp, l2_error)
9.317829770853422 0.00036808900872387085

Tracciare il grafico degli errori introdotti per slice e dell'errore accumulato produce un quadro simile al precedente.

fig, axes = plt.subplots(1, 2, figsize=(14, 5))
axes[1].plot([6], [l2_error], "x", color="red", label="actual error")
plot_slice_errors(l2_metadata, axes[0])
plot_accumulated_error(l2_metadata, axes[1])

Output of the previous code cell

Nota che l'errore accumulato è di nuovo la somma degli errori delle singole slice. Questo è un altro limite debole dovuto alla disuguaglianza di Minkowski, poiché dobbiamo calcolare questo limite in modo ricorsivo:

∣⟨ψ∣Δi∣ψ⟩∣≤∣⟨ψ∣Δ~i−1∣ψ⟩∣+(∑P∈Ti∣cP∣2)1/2=∣⟨ψ∣Δ~i∣ψ⟩∣|\langle\psi|\Delta_{i}|\psi\rangle| \leq |\langle\psi|\tilde{\Delta}_{i-1}|\psi\rangle| + \left( \sum_{P\in\mathcal{T_i}} |c_P|^2 \right)^{1/2} = |\langle\psi|\tilde{\Delta}_{i}|\psi\rangle|

dove il nuovo pedice ii indica l'iterazione corrente sulle slice, rendendo Δi\Delta_i l'errore effettivo all'iterazione di retropropagazione ii, Δ~i−1\tilde{\Delta}_{i-1} l'errore di troncamento approssimato dell'iterazione i−1i-1 e Ti\mathcal{T}_i l'insieme dei termini di Pauli troncati all'iterazione ii.