انتقل إلى المحتوى الرئيسي

دوال التكلفة

خلال هذا الدرس، سنتعلم كيفية تقييم دالة التكلفة:

  • أولاً، سنتعرف على بدائيات Qiskit Runtime
  • تعريف دالة التكلفة C(θ)C(\vec\theta). وهي دالة خاصة بالمشكلة تحدد هدف المشكلة ليقوم المُحسِّن بتصغيره (أو تكبيره)
  • تحديد استراتيجية القياس باستخدام بدائيات Qiskit Runtime لتحقيق التوازن بين السرعة والدقة

 

A diagram showing key components of a cost function including using primitives like estimator and sampler.

البدائيات

جميع الأنظمة الفيزيائية، سواء كانت كلاسيكية أو كمية، يمكن أن توجد في حالات مختلفة. على سبيل المثال، يمكن أن يكون للسيارة على الطريق كتلة معينة، وموضع، وسرعة، أو تسارع يميز حالتها. وبالمثل، يمكن أن تكون للأنظمة الكمية أيضاً تكوينات أو حالات مختلفة، لكنها تختلف عن الأنظمة الكلاسيكية في كيفية تعاملنا مع القياسات وتطور الحالة. وهذا يؤدي إلى خصائص فريدة مثل التراكب والتشابك التي هي حصرية لميكانيكا الكم. تماماً كما يمكننا وصف حالة السيارة باستخدام خصائص فيزيائية مثل السرعة أو التسارع، يمكننا أيضاً وصف حالة نظام كمي باستخدام المرصودات، وهي كائنات رياضية.

في ميكانيكا الكم، تُمثَّل الحالات بمتجهات عمودية مركبة مُعيَّرة، أو كيتات (ψ|\psi\rangle)، والمرصودات هي مؤثرات خطية هرمتية (H^=H^\hat{H}=\hat{H}^{\dagger}) تعمل على الكيتات. المتجه الذاتي (λ|\lambda\rangle) لمرصود يُعرف بـالحالة الذاتية. قياس مرصود لإحدى حالاته الذاتية (λ|\lambda\rangle) سيعطينا القيمة الذاتية المقابلة (λ\lambda) كقراءة.

إذا كنت تتساءل عن كيفية قياس نظام كمي وما يمكنك قياسه، يقدم Qiskit بدائيتين يمكنهما المساعدة:

  • Sampler: بالنظر إلى حالة كمية ψ|\psi\rangle، تحصل هذه البدائية على احتمال كل حالة أساسية حسابية ممكنة.
  • Estimator: بالنظر إلى مرصود كمي H^\hat{H} وحالة ψ|\psi\rangle، تحسب هذه البدائية القيمة المتوقعة لـ H^\hat{H}.

بدائية Sampler

تحسب بدائية Sampler احتمال الحصول على كل حالة ممكنة k|k\rangle من الأساس الحسابي، بالنظر إلى دائرة كمية تحضّر الحالة ψ|\psi\rangle. تحسب

pk=kψ2kZ2n{0,1,,2n1},p_k = |\langle k | \psi \rangle|^2 \quad \forall k \in \mathbb{Z}_2^n \equiv \{0,1,\cdots,2^n-1\},

حيث nn هو عدد الكيوبتات، و kk هو التمثيل الصحيح لأي سلسلة ثنائية ناتجة ممكنة {0,1}n\{0,1\}^n (أي، أعداد صحيحة في الأساس 22).

يقوم Sampler في Qiskit Runtime بتشغيل الدائرة عدة مرات على جهاز كمي، مع إجراء قياسات في كل تشغيل، وإعادة بناء التوزيع الاحتمالي من سلاسل البتات المسترجعة. كلما زاد عدد التشغيلات (أو اللقطات)، كانت النتائج أكثر دقة، لكن هذا يتطلب مزيداً من الوقت والموارد الكمية.

ومع ذلك، بما أن عدد المخرجات الممكنة ينمو أسياً مع عدد الكيوبتات nn (أي 2n2^n)، فإن عدد اللقطات سيحتاج إلى النمو أسياً أيضاً من أجل التقاط توزيع احتمالي كثيف. لذلك، Sampler فعّال فقط للتوزيعات الاحتمالية المتناثرة؛ حيث يجب أن تكون الحالة المستهدفة ψ|\psi\rangle قابلة للتعبير عنها كتركيبة خطية لحالات الأساس الحسابي، مع نمو عدد الحدود على الأكثر بشكل متعدد الحدود مع عدد الكيوبتات:

ψ=kPoly(n)wkk.|\psi\rangle = \sum^{\text{Poly}(n)}_k w_k |k\rangle.

يمكن أيضاً تكوين Sampler لاسترجاع الاحتمالات من جزء من الدائرة، يمثل مجموعة فرعية من إجمالي الحالات الممكنة.

بدائية Estimator

تحسب بدائية Estimator القيمة المتوقعة لمرصود H^\hat{H} لحالة كمية ψ|\psi\rangle؛ حيث يمكن التعبير عن احتمالات المرصود كـ pλ=λψ2p_\lambda = |\langle\lambda|\psi\rangle|^2، حيث λ|\lambda\rangle هي الحالات الذاتية للمرصود H^\hat{H}. تُعرَّف القيمة المتوقعة بأنها متوسط جميع النتائج الممكنة λ\lambda (أي القيم الذاتية للمرصود) لقياس الحالة ψ|\psi\rangle، مرجحة بالاحتمالات المقابلة:

H^ψ:=λpλλ=ψH^ψ\langle\hat{H}\rangle_\psi := \sum_\lambda p_\lambda \lambda = \langle \psi | \hat{H} | \psi \rangle

ومع ذلك، حساب القيمة المتوقعة لمرصود ليس ممكناً دائماً، لأننا غالباً لا نعرف أساسه الذاتي. يستخدم Estimator في Qiskit Runtime عملية جبرية معقدة لتقدير القيمة المتوقعة على جهاز كمي حقيقي عن طريق تفكيك المرصود إلى تركيبة من مرصودات أخرى نعرف أساسها الذاتي.

بعبارات أبسط، يفكك Estimator أي مرصود لا يعرف كيفية قياسه إلى مرصودات أبسط قابلة للقياس تسمى مؤثرات باولي. يمكن التعبير عن أي مؤثر كتركيبة من 4n4^n مؤثر باولي.

P^k:=σkn1σk0kZ4n{0,1,,4n1},\hat{P}_k := \sigma_{k_{n-1}}\otimes \cdots \otimes \sigma_{k_0} \quad \forall k \in \mathbb{Z}_4^n \equiv \{0,1,\cdots,4^n-1\}, \\

بحيث

H^=k=04n1wkP^k\hat{H} = \sum^{4^n-1}_{k=0} w_k \hat{P}_k

حيث nn هو عدد الكيوبتات، kkn1k0k \equiv k_{n-1} \cdots k_0 لـ klZ4{0,1,2,3}k_l \in \mathbb{Z}_4 \equiv \{0, 1, 2, 3\} (أي أعداد صحيحة في الأساس 44)، و (σ0,σ1,σ2,σ3):=(I,X,Y,Z)(\sigma_0, \sigma_1, \sigma_2, \sigma_3) := (I, X, Y, Z).

بعد إجراء هذا التفكيك، يشتق Estimator دائرة جديدة VkψV_k|\psi\rangle لكل مرصود P^k\hat{P}_k (من الدائرة الأصلية)، لـتقطير مرصود باولي بشكل فعال في الأساس الحسابي وقياسه. يمكننا قياس مرصودات باولي بسهولة لأننا نعرف VkV_k مسبقاً، وهو ما لا ينطبق عموماً على المرصودات الأخرى.

لكل P^k\hat{P}_{k}، يقوم Estimator بتشغيل الدائرة المقابلة على جهاز كمي عدة مرات، ويقيس حالة الخرج في الأساس الحسابي، ويحسب الاحتمال pkjp_{kj} للحصول على كل خرج ممكن jj. ثم يبحث عن القيمة الذاتية λkj\lambda_{kj} لـ PkP_k المقابلة لكل خرج jj، ويضرب في wkw_k، ويجمع كل النتائج معاً للحصول على القيمة المتوقعة للمرصود H^\hat{H} للحالة المعطاة ψ|\psi\rangle.

H^ψ=k=04n1wkj=02n1pkjλkj,\langle\hat{H}\rangle_\psi = \sum_{k=0}^{4^n-1} w_k \sum_{j=0}^{2^n-1}p_{kj} \lambda_{kj},

بما أن حساب القيمة المتوقعة لـ 4n4^n من مؤثرات باولي غير عملي (أي ينمو أسياً)، لا يمكن أن يكون Estimator فعالاً إلا عندما يكون عدد كبير من wkw_k صفراً (أي تفكيك باولي متناثر بدلاً من كثيف). نقول رسمياً أنه لكي يكون هذا الحساب قابلاً للحل بكفاءة، يجب أن ينمو عدد الحدود غير الصفرية على الأكثر بشكل متعدد الحدود مع عدد الكيوبتات nn: H^=kPoly(n)wkP^k.\hat{H} = \sum^{\text{Poly}(n)}_k w_k \hat{P}_k.

قد يلاحظ القارئ الافتراض الضمني بأن أخذ العينات الاحتمالي يحتاج أيضاً إلى أن يكون فعالاً كما شُرح لـ Sampler، مما يعني

H^ψ=kPoly(n)wkjPoly(n)pkjλkj.\langle\hat{H}\rangle_\psi = \sum_{k}^{\text{Poly}(n)} w_k \sum_{j}^{\text{Poly}(n)}p_{kj} \lambda_{kj}.

مثال موجّه لحساب القيم المتوقعة

لنفترض حالة الكيوبت الواحد +:=H0=12(0+1)|+\rangle := H|0\rangle = \frac{1}{\sqrt{2}}(|0\rangle + |1\rangle)، والمرصود

H^=(1221)=2XZ\begin{aligned} \hat{H} & = \begin{pmatrix} -1 & 2 \\ 2 & 1 \\ \end{pmatrix}\\[1mm] & = 2X - Z \end{aligned}

مع القيمة المتوقعة النظرية التالية H^+=+H^+=2.\langle\hat{H}\rangle_+ = \langle+|\hat{H}|+\rangle = 2.

بما أننا لا نعرف كيفية قياس هذا المرصود، لا يمكننا حساب قيمته المتوقعة مباشرة، ونحتاج إلى إعادة التعبير عنه كـ H^+=2X+Z+\langle\hat{H}\rangle_+ = 2\langle X \rangle_+ - \langle Z \rangle_+ . يمكن إثبات أن هذا يعطي نفس النتيجة بملاحظة أن +X+=1\langle+|X|+\rangle = 1، و +Z+=0\langle+|Z|+\rangle = 0.

دعنا نرى كيفية حساب X+\langle X \rangle_+ و Z+\langle Z \rangle_+ مباشرة. بما أن XX و ZZ لا يتبادلان (أي لا يشتركان في نفس الأساس الذاتي)، لا يمكن قياسهما في وقت واحد، لذلك نحتاج إلى الدوائر المساعدة:

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-aer qiskit-ibm-runtime rustworkx
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp

# The following code will work for any other initial single-qubit state and observable
original_circuit = QuantumCircuit(1)
original_circuit.h(0)

H = SparsePauliOp(["X", "Z"], [2, -1])

aux_circuits = []
for pauli in H.paulis:
aux_circ = original_circuit.copy()
aux_circ.barrier()
if str(pauli) == "X":
aux_circ.h(0)
elif str(pauli) == "Y":
aux_circ.sdg(0)
aux_circ.h(0)
else:
aux_circ.id(0)
aux_circ.measure_all()
aux_circuits.append(aux_circ)

original_circuit.draw("mpl")

Output of the previous code cell

# Auxiliary circuit for X
aux_circuits[0].draw("mpl")

Output of the previous code cell

# Auxiliary circuit for Z
aux_circuits[1].draw("mpl")

Output of the previous code cell

يمكننا الآن إجراء الحساب يدوياً باستخدام Sampler والتحقق من النتائج على Estimator:

from qiskit.primitives import StatevectorSampler, StatevectorEstimator
from qiskit.result import QuasiDistribution
import numpy as np

## SAMPLER
shots = 10000
sampler = StatevectorSampler()
job = sampler.run(aux_circuits, shots=shots)

# Run the sampler job and step through results
expvals = []
for index, pauli in enumerate(H.paulis):
data_pub = job.result()[index].data
bitstrings = data_pub.meas.get_bitstrings()
counts = data_pub.meas.get_counts()
quasi_dist = QuasiDistribution(
{outcome: freq / shots for outcome, freq in counts.items()}
)

# Use the probabilities and known eigenvalues of Pauli operators to estimate the expectation value.
val = 0

if str(pauli) == "X":
val += -1 * quasi_dist.get(1, 0)
val += 1 * quasi_dist.get(0, 0)

if str(pauli) == "Y":
val += -1 * quasi_dist.get(1, 0)
val += 1 * quasi_dist.get(0, 0)

if str(pauli) == "Z":
val += 1 * quasi_dist.get(0, 0)
val += -1 * quasi_dist.get(1, 0)

expvals.append(val)

# Print expectation values

print("Sampler results:")
for pauli, expval in zip(H.paulis, expvals):
print(f" >> Expected value of {str(pauli)}: {expval:.5f}")

total_expval = np.sum(H.coeffs * expvals).real
print(f" >> Total expected value: {total_expval:.5f}")

# Use estimator for comparison
observables = [
*H.paulis,
H,
] # Note: run for individual Paulis as well as full observable H

estimator = StatevectorEstimator()
job = estimator.run([(original_circuit, observables)])
estimator_expvals = job.result()[0].data.evs

# Print results
print("Estimator results:")
for obs, expval in zip(observables, estimator_expvals):
if obs is not H:
print(f" >> Expected value of {str(obs)}: {expval:.5f}")
else:
print(f" >> Total expected value: {expval:.5f}")
Sampler results:
>> Expected value of X: 1.00000
>> Expected value of Z: 0.00420
>> Total expected value: 1.99580
Estimator results:
>> Expected value of X: 1.00000
>> Expected value of Z: 0.00000
>> Total expected value: 2.00000

الدقة الرياضية (اختياري)

بالتعبير عن ψ|\psi\rangle بالنسبة لأساس الحالات الذاتية لـ H^\hat{H}، ψ=λaλλ|\psi\rangle = \sum_\lambda a_\lambda |\lambda\rangle، يترتب على ذلك:

ψH^ψ=(λaλλ)H^(λaλλ)=λλaλaλλH^λ=λλaλaλλλλ=λλaλaλλδλ,λ=λaλ2λ=λpλλ\begin{aligned} \langle \psi | \hat{H} | \psi \rangle & = \bigg(\sum_{\lambda'}a^*_{\lambda'} \langle \lambda'|\bigg) \hat{H} \bigg(\sum_{\lambda} a_\lambda | \lambda\rangle\bigg)\\[1mm] & = \sum_{\lambda}\sum_{\lambda'} a^*_{\lambda'}a_{\lambda} \langle \lambda'|\hat{H}| \lambda\rangle\\[1mm] & = \sum_{\lambda}\sum_{\lambda'} a^*_{\lambda'}a_{\lambda} \lambda \langle \lambda'| \lambda\rangle\\[1mm] & = \sum_{\lambda}\sum_{\lambda'} a^*_{\lambda'}a_{\lambda} \lambda \cdot \delta_{\lambda, \lambda'}\\[1mm] & = \sum_\lambda |a_\lambda|^2 \lambda\\[1mm] & = \sum_\lambda p_\lambda \lambda\\[1mm] \end{aligned}

بما أننا لا نعرف القيم الذاتية أو الحالات الذاتية للمرصود المستهدف H^\hat{H}، نحتاج أولاً إلى النظر في تقطيره. بالنظر إلى أن H^\hat{H} هرمتي، يوجد تحويل وحدوي VV بحيث H^=VΛV,\hat{H}=V^\dagger \Lambda V, حيث Λ\Lambda هي المصفوفة القطرية للقيم الذاتية، بحيث jΛk=0\langle j | \Lambda | k \rangle = 0 إذا jkj\neq k، و jΛj=λj\langle j | \Lambda | j \rangle = \lambda_j.

هذا يعني أن القيمة المتوقعة يمكن إعادة كتابتها كالتالي:

ψH^ψ=ψVΛVψ=ψV(j=02n1jj)Λ(k=02n1kk)Vψ=j=02n1k=02n1ψVjjΛkkVψ=j=02n1ψVjjΛjjVψ=j=02n1jVψ2λj\begin{aligned} \langle\psi|\hat{H}|\psi\rangle & = \langle\psi|V^\dagger \Lambda V|\psi\rangle\\[1mm] & = \langle\psi|V^\dagger \bigg(\sum_{j=0}^{2^n-1} |j\rangle \langle j|\bigg) \Lambda \bigg(\sum_{k=0}^{2^n-1} |k\rangle \langle k|\bigg) V|\psi\rangle\\[1mm] & = \sum_{j=0}^{2^n-1} \sum_{k=0}^{2^n-1}\langle\psi|V^\dagger |j\rangle \langle j| \Lambda |k\rangle \langle k| V|\psi\rangle\\[1mm] & = \sum_{j=0}^{2^n-1}\langle\psi|V^\dagger |j\rangle \langle j| \Lambda |j\rangle \langle j| V|\psi\rangle\\[1mm] & = \sum_{j=0}^{2^n-1}|\langle j| V|\psi\rangle|^2 \lambda_j\\[1mm] \end{aligned}

بالنظر إلى أنه إذا كان النظام في الحالة ϕ=Vψ|\phi\rangle = V |\psi\rangle فإن احتمال قياس j| j\rangle هو pj=jϕ2p_j = |\langle j|\phi \rangle|^2، يمكن التعبير عن القيمة المتوقعة أعلاه كالتالي:

ψH^ψ=j=02n1pjλj.\langle\psi|\hat{H}|\psi\rangle = \sum_{j=0}^{2^n-1} p_j \lambda_j.

من المهم جداً ملاحظة أن الاحتمالات مأخوذة من الحالة VψV |\psi\rangle بدلاً من ψ|\psi\rangle. هذا هو السبب في أن المصفوفة VV ضرورية تماماً. قد تتساءل عن كيفية الحصول على المصفوفة VV والقيم الذاتية Λ\Lambda. إذا كنت تملك القيم الذاتية بالفعل، فلن تكون هناك حاجة لاستخدام حاسوب كمي لأن هدف الخوارزميات التباينية هو إيجاد هذه القيم الذاتية لـ H^\hat{H}.

لحسن الحظ، هناك طريقة للتغلب على ذلك: يمكن كتابة أي مصفوفة 2n×2n2^n \times 2^n كتركيبة خطية من 4n4^n جداء موتري لـ nn من مصفوفات باولي ومصفوفات الوحدة، وجميعها هرمتية ووحدوية مع VV و Λ\Lambda معروفين. هذا ما يفعله Estimator في Runtime داخلياً عن طريق تفكيك أي كائن Operator إلى SparsePauliOp.

إليك المؤثرات التي يمكن استخدامها:

OperatorσVΛIσ0=(1001)V0=IΛ0=I=(1001)Xσ1=(0110)V1=H=12(1111)Λ1=σ3=(1001)Yσ2=(0ii0)V2=HS=12(1111)(100i)=12(1i1i)Λ2=σ3=(1001)Zσ3=(1001)V3=IΛ3=σ3=(1001)\begin{array}{c|c|c|c} \text{Operator} & \sigma & V & \Lambda \\[1mm] \hline I & \sigma_0 = \begin{pmatrix} 1 & 0 \\ 0 & 1 \end{pmatrix} & V_0 = I & \Lambda_0 = I = \begin{pmatrix} 1 & 0 \\ 0 & 1 \end{pmatrix} \\[4mm] X & \sigma_1 = \begin{pmatrix} 0 & 1 \\ 1 & 0 \end{pmatrix} & V_1 = H =\frac{1}{\sqrt{2}} \begin{pmatrix} 1 & 1 \\ 1 & -1 \end{pmatrix} & \Lambda_1 = \sigma_3 = \begin{pmatrix} 1 & 0 \\ 0 & -1 \end{pmatrix} \\[4mm] Y & \sigma_2 = \begin{pmatrix} 0 & -i \\ i & 0 \end{pmatrix} & V_2 = HS^\dagger =\frac{1}{\sqrt{2}} \begin{pmatrix} 1 & 1 \\ 1 & -1 \end{pmatrix}\cdot \begin{pmatrix} 1 & 0 \\ 0 & -i \end{pmatrix} = \frac{1}{\sqrt{2}} \begin{pmatrix} 1 & -i \\ 1 & i \end{pmatrix}\quad & \Lambda_2 = \sigma_3 = \begin{pmatrix} 1 & 0 \\ 0 & -1 \end{pmatrix} \\[4mm] Z & \sigma_3 = \begin{pmatrix} 1 & 0 \\ 0 & -1 \end{pmatrix} & V_3 = I & \Lambda_3 = \sigma_3 = \begin{pmatrix} 1 & 0 \\ 0 & -1 \end{pmatrix} \end{array}

لذا دعنا نعيد كتابة H^\hat{H} بالنسبة لمؤثرات باولي ومصفوفات الوحدة:

H^=kn1=03...k0=03wkn1...k0σkn1...σk0=k=04n1wkP^k,\hat{H} = \sum_{k_{n-1}=0}^3... \sum_{k_0=0}^3 w_{k_{n-1}...k_0} \sigma_{k_{n-1}}\otimes ... \otimes \sigma_{k_0} = \sum_{k=0}^{4^n-1} w_k \hat{P}_k,

حيث k=l=0n14lklkn1...k0k = \sum_{l=0}^{n-1} 4^l k_l \equiv k_{n-1}...k_0 لـ kn1,...,k0{0,1,2,3}k_{n-1},...,k_0\in \{0,1,2,3\} (أي في الأساس 44)، و P^k:=σkn1...σk0\hat{P}_{k} := \sigma_{k_{n-1}}\otimes ... \otimes \sigma_{k_0}:

ψH^ψ=k=04n1wkj=02n1jVkψ2jΛkj=k=04n1wkj=02n1pkjλkj,\begin{aligned} \langle\psi|\hat{H}|\psi\rangle & = \sum_{k=0}^{4^n-1} w_k \sum_{j=0}^{2^n-1}|\langle j| V_k|\psi\rangle|^2 \langle j| \Lambda_k |j\rangle \\[1mm] & = \sum_{k=0}^{4^n-1} w_k \sum_{j=0}^{2^n-1}p_{kj} \lambda_{kj}, \\[1mm] \end{aligned}

حيث Vk:=Vkn1...Vk0V_k := V_{k_{n-1}}\otimes ... \otimes V_{k_0} و Λk:=Λkn1...Λk0\Lambda_k := \Lambda_{k_{n-1}}\otimes ... \otimes \Lambda_{k_0}، بحيث: Pk^=VkΛkVk.\hat{P_k}=V_k^\dagger \Lambda_k V_k.

دوال التكلفة

بشكل عام، تُستخدم دوال التكلفة لوصف هدف المشكلة ومدى جودة أداء حالة التجربة بالنسبة لذلك الهدف. يمكن تطبيق هذا التعريف على أمثلة متنوعة في الكيمياء، والتعلم الآلي، والتمويل، والتحسين، وما إلى ذلك.

لنأخذ مثالاً بسيطاً لإيجاد الحالة الأرضية لنظام ما. هدفنا هو تصغير القيمة المتوقعة للمرصود الذي يمثل الطاقة (الهاملتوني H^\hat{\mathcal{H}}):

minθψ(θ)H^ψ(θ)\min_{\vec\theta} \langle\psi(\vec\theta)|\hat{\mathcal{H}}|\psi(\vec\theta)\rangle

يمكننا استخدام Estimator لتقييم القيمة المتوقعة وتمرير هذه القيمة إلى مُحسِّن لتصغيرها. إذا نجح التحسين، سيعيد مجموعة من قيم المعلمات المثلى θ\vec\theta^*، والتي سنتمكن من خلالها من بناء حالة الحل المقترحة ψ(θ)|\psi(\vec\theta^*)\rangle وحساب القيمة المتوقعة المرصودة كـ C(θ)C(\vec\theta^*).

لاحظ أننا سنكون قادرين فقط على تصغير دالة التكلفة لمجموعة محدودة من الحالات التي نأخذها بعين الاعتبار. هذا يقودنا إلى احتمالين منفصلين:

  • الأنساتز الخاص بنا لا يحدد حالة الحل عبر فضاء البحث: إذا كان هذا هو الحال، فلن يجد المُحسِّن الحل أبداً، ونحتاج إلى تجربة أنساتزات أخرى قد تكون قادرة على تمثيل فضاء البحث بدقة أكبر.
  • المُحسِّن غير قادر على إيجاد هذا الحل الصالح: يمكن تعريف التحسين عالمياً ومحلياً. سنستكشف ما يعنيه هذا في القسم اللاحق.

بشكل عام، سنقوم بتنفيذ حلقة تحسين كلاسيكية لكن مع الاعتماد على تقييم دالة التكلفة بواسطة حاسوب كمي. من هذا المنظور، يمكن للمرء أن يفكر في التحسين كمسعى كلاسيكي بحت حيث نستدعي أوراكل كمي صندوق أسود في كل مرة يحتاج فيها المُحسِّن إلى تقييم دالة التكلفة.

def cost_func_vqe(params, circuit, hamiltonian, estimator):
"""Return estimate of energy from estimator

Parameters:
params (ndarray): Array of ansatz parameters
ansatz (QuantumCircuit): Parameterized ansatz circuit
hamiltonian (SparsePauliOp): Operator representation of Hamiltonian
estimator (Estimator): Estimator primitive instance

Returns:
float: Energy estimate
"""
pub = (circuit, hamiltonian, params)
cost = estimator.run([pub]).result()[0].data.evs
return cost
from qiskit.circuit.library import TwoLocal

observable = SparsePauliOp.from_list([("XX", 1), ("YY", -3)])

reference_circuit = QuantumCircuit(2)
reference_circuit.x(0)

variational_form = TwoLocal(
2,
rotation_blocks=["rz", "ry"],
entanglement_blocks="cx",
entanglement="linear",
reps=1,
)
ansatz = reference_circuit.compose(variational_form)

theta_list = (2 * np.pi * np.random.rand(1, 8)).tolist()
ansatz.decompose().draw("mpl")

Output of the previous code cell

سنقوم أولاً بتنفيذ هذا باستخدام محاكي: StatevectorEstimator. عادة ما يُنصح بهذا لأغراض تصحيح الأخطاء، لكننا سنتبع تشغيل التصحيح فوراً بحساب على عتاد كمي حقيقي. بشكل متزايد، المشكلات ذات الاهتمام لم تعد قابلة للمحاكاة كلاسيكياً بدون مرافق حوسبة فائقة متطورة.

estimator = StatevectorEstimator()
cost = cost_func_vqe(theta_list, ansatz, observable, estimator)
print(cost)
[-0.58744589]

سننتقل الآن إلى التشغيل على حاسوب كمي حقيقي. لاحظ تغييرات الصياغة. سيتم مناقشة الخطوات المتعلقة بـ pass_manager بمزيد من التفصيل في المثال التالي. خطوة ذات أهمية خاصة في الخوارزميات التباينية هي استخدام جلسة Qiskit Runtime. بدء جلسة يسمح لك بتشغيل تكرارات متعددة لخوارزمية تباينية دون الانتظار في طابور جديد في كل مرة يتم فيها تحديث المعلمات. هذا مهم إذا كانت أوقات الطابور طويلة و/أو كانت هناك حاجة إلى العديد من التكرارات. فقط الشركاء في شبكة IBM Quantum® يمكنهم استخدام جلسات Runtime. إذا لم يكن لديك وصول إلى الجلسات، يمكنك تقليل عدد التكرارات التي ترسلها في وقت معين، وحفظ أحدث المعلمات لاستخدامها في عمليات التشغيل المستقبلية. إذا أرسلت عدداً كبيراً جداً من التكرارات أو واجهت أوقات طابور طويلة جداً، فقد تواجه رمز الخطأ 1217، الذي يشير إلى تأخيرات طويلة بين إرسالات المهام.

# Estimated usage: < 1 min. Benchmarked at 7 seconds on an Eagle processor
# Load necessary packages:

from qiskit_ibm_runtime import (
QiskitRuntimeService,
Session,
EstimatorOptions,
EstimatorV2 as Estimator,
)
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

# Select the least busy backend:

service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, min_num_qubits=ansatz.num_qubits, simulator=False
)
# Or get a specific backend:
# backend = service.backend("ibm_brisbane")

# Use a pass manager to transpile the circuit and observable for the specific backend being used:

pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_ansatz = pm.run(ansatz)
isa_observable = observable.apply_layout(layout=isa_ansatz.layout)

# Set estimator options
estimator_options = EstimatorOptions(resilience_level=1, default_shots=10_000)

# Open a Runtime session:

with Session(backend=backend) as session:
estimator = Estimator(mode=session, options=estimator_options)
cost = cost_func_vqe(theta_list, isa_ansatz, isa_observable, estimator)

session.close()
print(cost)

لاحظ أن القيم المحصلة من الحسابين أعلاه متشابهة جداً. سيتم مناقشة تقنيات تحسين النتائج بمزيد من التفصيل أدناه.

مثال على التعيين لأنظمة غير فيزيائية

مشكلة القطع الأقصى (Max-Cut) هي مشكلة تحسين توافقية تتضمن تقسيم رؤوس رسم بياني إلى مجموعتين منفصلتين بحيث يكون عدد الحواف بين المجموعتين أقصى ما يمكن. بشكل أكثر رسمية، بالنظر إلى رسم بياني غير موجه G=(V,E)G=(V,E)، حيث VV هي مجموعة الرؤوس و EE هي مجموعة الحواف، تطلب مشكلة القطع الأقصى تقسيم الرؤوس إلى مجموعتين فرعيتين منفصلتين، SS و TT، بحيث يكون عدد الحواف التي لها نقطة نهاية واحدة في SS والأخرى في TT أقصى ما يمكن.

يمكننا تطبيق القطع الأقصى لحل مشاكل متنوعة بما في ذلك: التجميع، وتصميم الشبكات، والتحولات الطورية، وما إلى ذلك. سنبدأ بإنشاء رسم بياني للمشكلة:

import rustworkx as rx
from rustworkx.visualization import mpl_draw

n = 4
G = rx.PyGraph()
G.add_nodes_from(range(n))
# The edge syntax is (start, end, weight)
edges = [(0, 1, 1.0), (0, 2, 1.0), (0, 3, 1.0), (1, 2, 1.0), (2, 3, 1.0)]
G.add_edges_from(edges)

mpl_draw(
G, pos=rx.shell_layout(G), with_labels=True, edge_labels=str, node_color="#1192E8"
)

Output of the previous code cell

يمكن التعبير عن هذه المشكلة كمشكلة تحسين ثنائية. لكل عقدة 0i<n0 \leq i < n، حيث nn هو عدد عقد الرسم البياني (في هذه الحالة n=4n=4)، سنأخذ بعين الاعتبار المتغير الثنائي xix_i. سيكون لهذا المتغير القيمة 11 إذا كانت العقدة ii في إحدى المجموعات التي سنسميها 11 و 00 إذا كانت في المجموعة الأخرى التي سنسميها 00. سنرمز أيضاً بـ wijw_{ij} (العنصر (i,j)(i,j) من مصفوفة التجاور ww) لوزن الحافة التي تربط العقدة ii بالعقدة jj. لأن الرسم البياني غير موجه، wij=wjiw_{ij}=w_{ji}. يمكننا حينئذ صياغة مشكلتنا كتكبير دالة التكلفة التالية:

C(x)=i,j=0nwijxi(1xj)=i,j=0nwijxii,j=0nwijxixj=i,j=0nwijxii=0nj=0i2wijxixj\begin{aligned} C(\vec{x}) & =\sum_{i,j=0}^n w_{ij} x_i(1-x_j)\\[1mm] & = \sum_{i,j=0}^n w_{ij} x_i - \sum_{i,j=0}^n w_{ij} x_ix_j\\[1mm] & = \sum_{i,j=0}^n w_{ij} x_i - \sum_{i=0}^n \sum_{j=0}^i 2w_{ij} x_ix_j \end{aligned}

لحل هذه المشكلة بحاسوب كمي، سنعبّر عن دالة التكلفة كقيمة متوقعة لمرصود. ومع ذلك، فإن المرصودات التي يقبلها Qiskit أصلاً تتكون من مؤثرات باولي، التي لها قيم ذاتية 11 و 1-1 بدلاً من 00 و 11. لهذا السبب سنجري تغيير المتغير التالي:

حيث x=(x0,x1,,xn1)\vec{x}=(x_0,x_1,\cdots ,x_{n-1}). يمكننا استخدام مصفوفة التجاور ww للوصول بسهولة إلى أوزان جميع الحواف. سيُستخدم هذا للحصول على دالة التكلفة:

zi=12xixi=1zi2z_i = 1-2x_i \rightarrow x_i = \frac{1-z_i}{2}

هذا يعني أن:

xi=0zi=1xi=1zi=1.\begin{array}{lcl} x_i=0 & \rightarrow & z_i=1 \\ x_i=1 & \rightarrow & z_i=-1.\end{array}

إذن دالة التكلفة الجديدة التي نريد تكبيرها هي:

C(z)=i,j=0nwij(1zi2)(11zj2)=i,j=0nwij4i,j=0nwij4zizj=i=0nj=0iwij2i=0nj=0iwij2zizj\begin{aligned} C(\vec{z}) & = \sum_{i,j=0}^n w_{ij} \bigg(\frac{1-z_i}{2}\bigg)\bigg(1-\frac{1-z_j}{2}\bigg)\\[1mm] & = \sum_{i,j=0}^n \frac{w_{ij}}{4} - \sum_{i,j=0}^n \frac{w_{ij}}{4} z_iz_j\\[1mm] & = \sum_{i=0}^n \sum_{j=0}^i \frac{w_{ij}}{2} - \sum_{i=0}^n \sum_{j=0}^i \frac{w_{ij}}{2} z_iz_j \end{aligned}

علاوة على ذلك، الميل الطبيعي للحاسوب الكمي هو إيجاد الحدود الدنيا (عادة أقل طاقة) بدلاً من القصوى، لذا بدلاً من تكبير C(z)C(\vec{z}) سنقوم بتصغير:

C(z)=i=0nj=0iwij2zizji=0nj=0iwij2-C(\vec{z}) = \sum_{i=0}^n \sum_{j=0}^i \frac{w_{ij}}{2} z_iz_j - \sum_{i=0}^n \sum_{j=0}^i \frac{w_{ij}}{2}

الآن بما أن لدينا دالة تكلفة لتصغيرها ومتغيراتها يمكن أن تأخذ القيم 1-1 و 11، يمكننا إجراء التناظر التالي مع مؤثر باولي ZZ:

ziZi=In1...Zi...I0z_i \equiv Z_i = \overbrace{I}^{n-1}\otimes ... \otimes \overbrace{Z}^{i} \otimes ... \otimes \overbrace{I}^{0}

بعبارة أخرى، المتغير ziz_i سيكون مكافئاً لبوابة ZZ تعمل على الكيوبت ii. علاوة على ذلك:

Zixn1x0=zixn1x0xn1x0Zixn1x0=ziZ_i|x_{n-1}\cdots x_0\rangle = z_i|x_{n-1}\cdots x_0\rangle \rightarrow \langle x_{n-1}\cdots x_0 |Z_i|x_{n-1}\cdots x_0\rangle = z_i

إذن المرصود الذي سنأخذه بعين الاعتبار هو:

H^=i=0nj=0iwij2ZiZj\hat{H} = \sum_{i=0}^n \sum_{j=0}^i \frac{w_{ij}}{2} Z_iZ_j

والذي سنضيف إليه الحد المستقل بعد ذلك:

offset=i=0nj=0iwij2\texttt{offset} = - \sum_{i=0}^n \sum_{j=0}^i \frac{w_{ij}}{2}

المؤثر هو تركيبة خطية لحدود تحتوي على مؤثرات Z على العقد المتصلة بحافة (تذكر أن الكيوبت رقم 0 هو الأبعد يميناً): IIZZ+IZIZ+IZZI+ZIIZ+ZZIIIIZZ + IZIZ + IZZI + ZIIZ + ZZII. بمجرد بناء المؤثر، يمكن بناء الأنساتز لخوارزمية QAOA بسهولة باستخدام دائرة QAOAAnsatz من مكتبة دوائر Qiskit.

from qiskit.circuit.library import QAOAAnsatz
from qiskit.quantum_info import SparsePauliOp

hamiltonian = SparsePauliOp.from_list(
[("IIZZ", 1), ("IZIZ", 1), ("IZZI", 1), ("ZIIZ", 1), ("ZZII", 1)]
)

ansatz = QAOAAnsatz(hamiltonian, reps=2)
# Draw
ansatz.decompose(reps=3).draw("mpl")

Output of the previous code cell

# Sum the weights, and divide by 2

offset = -sum(edge[2] for edge in edges) / 2
print(f"""Offset: {offset}""")
Offset: -2.5

مع أخذ Runtime Estimator مباشرة للهاملتوني والأنساتز المعلمي، وإرجاع الطاقة اللازمة، فإن دالة التكلفة لمثال QAOA بسيطة للغاية:

def cost_func(params, ansatz, hamiltonian, estimator):
"""Return estimate of energy from estimator

Parameters:
params (ndarray): Array of ansatz parameters
ansatz (QuantumCircuit): Parameterized ansatz circuit
hamiltonian (SparsePauliOp): Operator representation of Hamiltonian
estimator (Estimator): Estimator primitive instance

Returns:
float: Energy estimate
"""
pub = (ansatz, hamiltonian, params)
cost = estimator.run([pub]).result()[0].data.evs
# cost = estimator.run(ansatz, hamiltonian, parameter_values=params).result().values[0]
return cost
import numpy as np

x0 = 2 * np.pi * np.random.rand(ansatz.num_parameters)

estimator = StatevectorEstimator()
cost = cost_func_vqe(x0, ansatz, hamiltonian, estimator)
print(cost)
1.473098768180865
# Estimated usage: < 1 min, benchmarked at 6 seconds on ibm_osaka, 5-23-24
# Load some necessary packages:

from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import Session, EstimatorV2 as Estimator

# Select the least busy backend:

backend = service.least_busy(
operational=True, min_num_qubits=ansatz.num_qubits, simulator=False
)

# Or get a specific backend:
# backend = service.backend("ibm_brisbane")

# Use a pass manager to transpile the circuit and observable for the specific backend being used:

pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_ansatz = pm.run(ansatz)
isa_hamiltonian = hamiltonian.apply_layout(layout=isa_ansatz.layout)

# Set estimator options
estimator_options = EstimatorOptions(resilience_level=1, default_shots=10_000)

# Open a Runtime session:

with Session(backend=backend) as session:
estimator = Estimator(mode=session, options=estimator_options)
cost = cost_func_vqe(x0, isa_ansatz, isa_hamiltonian, estimator)

# Close session after done
session.close()
print(cost)
1.1120776913677988

سنعود إلى هذا المثال في التطبيقات لاستكشاف كيفية الاستفادة من مُحسِّن للتكرار عبر فضاء البحث. بشكل عام، يتضمن هذا:

  • الاستفادة من مُحسِّن لإيجاد المعلمات المثلى
  • ربط المعلمات المثلى بالأنساتز لإيجاد القيم الذاتية
  • ترجمة القيم الذاتية إلى تعريف مشكلتنا

استراتيجية القياس: السرعة مقابل الدقة

كما ذُكر، نحن نستخدم حاسوباً كمياً مشوشاً كـأوراكل صندوق أسود، حيث يمكن أن تجعل الضوضاء القيم المسترجعة غير حتمية، مما يؤدي إلى تقلبات عشوائية والتي بدورها ستضر — أو حتى تمنع تماماً — تقارب بعض المُحسِّنات نحو حل مقترح. هذه مشكلة عامة يجب أن نعالجها بينما نستكشف تدريجياً الفائدة الكمية ونتقدم نحو التفوق الكمي:

A graph showing how simulation cost varies with circuit complexity. Using a classical computer it grows exponentially. With quantum error mitigation, there should be a crossover at which that becomes advantageous. Quantum error correction allows for linear growth of the simulation cost and will certainly lead to advantage.

يمكننا استخدام خيارات قمع الأخطاء وتخفيف الأخطاء في بدائيات Qiskit Runtime لمعالجة الضوضاء وتعظيم فائدة الحواسيب الكمية الحالية.

قمع الأخطاء

يشير قمع الأخطاء إلى التقنيات المستخدمة لتحسين وتحويل الدائرة أثناء التجميع من أجل تقليل الأخطاء. هذه تقنية أساسية لمعالجة الأخطاء تؤدي عادة إلى بعض التكلفة الإضافية الكلاسيكية في المعالجة المسبقة لوقت التشغيل الكلي. تتضمن التكلفة الإضافية تحويل الدوائر للتشغيل على العتاد الكمي عن طريق:

  • التعبير عن الدائرة باستخدام البوابات الأصلية المتاحة على النظام الكمي
  • تعيين الكيوبتات الافتراضية على الكيوبتات الفيزيائية
  • إضافة عمليات SWAP بناءً على متطلبات الاتصال
  • تحسين بوابات 1Q و 2Q
  • إضافة الفصل الديناميكي للكيوبتات الخاملة لمنع تأثيرات فقدان الترابط.

تسمح البدائيات باستخدام تقنيات قمع الأخطاء من خلال ضبط خيار optimization_level واختيار خيارات التحويل المتقدمة. في دورة لاحقة، سنتعمق في طرق بناء الدوائر المختلفة لتحسين النتائج، لكن في معظم الحالات، نوصي بضبط optimization_level=3.

سنقوم بتوضيح قيمة زيادة التحسين في عملية التحويل من خلال النظر إلى دائرة مثال ذات سلوك مثالي بسيط.

from qiskit.circuit import Parameter, QuantumCircuit
from qiskit.quantum_info import SparsePauliOp

theta = Parameter("theta")

qc = QuantumCircuit(2)
qc.x(1)
qc.h(0)
qc.cp(theta, 0, 1)
qc.h(0)
observables = SparsePauliOp.from_list([("ZZ", 1)])

qc.draw("mpl")

Output of the previous code cell

يمكن للدائرة أعلاه أن تنتج قيم متوقعة جيبية للمرصود المعطى، شريطة أن ندخل أطواراً تمتد على فترة مناسبة، مثل [0,2π][0,2\pi].

## Setup phases
import numpy as np

phases = np.linspace(0, 2 * np.pi, 50)

# phases need to be expressed as a list of lists in order to work
individual_phases = [[phase] for phase in phases]

يمكننا استخدام محاكي لإظهار فائدة التحويل المحسّن. سنعود أدناه لاستخدام العتاد الحقيقي لتوضيح فائدة تخفيف الأخطاء. سنستخدم QiskitRuntimeService للحصول على واجهة خلفية حقيقية (في هذه الحالة، ibm_brisbane)، ونستخدم AerSimulator لمحاكاة تلك الواجهة الخلفية، بما في ذلك سلوك الضوضاء.

from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_aer import AerSimulator

# get a real backend from the runtime service
service = QiskitRuntimeService()
backend = service.backend("ibm_brisbane")

# generate a simulator that mimics the real quantum system with the latest calibration results
backend_sim = AerSimulator.from_backend(backend)

يمكننا الآن استخدام مدير التمريرات لتحويل الدائرة إلى "بنية مجموعة التعليمات" أو ISA للواجهة الخلفية. هذا متطلب جديد في Qiskit Runtime: يجب أن تتوافق جميع الدوائر المقدمة إلى واجهة خلفية مع قيود هدف الواجهة الخلفية، مما يعني أنها يجب أن تكون مكتوبة بمصطلحات ISA للواجهة الخلفية — أي مجموعة التعليمات التي يمكن للجهاز فهمها وتنفيذها. يتم تحديد قيود الهدف هذه بعوامل مثل بوابات الأساس الأصلية للجهاز، واتصال الكيوبتات، و — عند الاقتضاء — مواصفات النبض والتوقيت الأخرى للتعليمات.

لاحظ أنه في الحالة الحالية، سنقوم بهذا مرتين: مرة مع optimization_level = 0، ومرة مع ضبطه على 3. في كل مرة سنستخدم بدائية Estimator لتقدير القيم المتوقعة للمرصود عند قيم مختلفة للطور.

# Import estimator and specify that we are using the simulated backend:

from qiskit_ibm_runtime import EstimatorV2 as Estimator

estimator = Estimator(mode=backend_sim)

circuit = qc
# Use a pass manager to transpile the circuit and observable for the backend being simulated.
# Start with no optimization:

from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

pm = generate_preset_pass_manager(backend=backend_sim, optimization_level=0)
isa_circuit = pm.run(circuit)
isa_observables = observables.apply_layout(layout=isa_circuit.layout)

noisy_exp_values = []
pub = (isa_circuit, isa_observables, [individual_phases])
cost = estimator.run([pub]).result()[0].data.evs
noisy_exp_values = cost[0]

# Repeat above steps, but now with optimization = 3:

exp_values_with_opt_es = []
pm = generate_preset_pass_manager(backend=backend_sim, optimization_level=3)
isa_circuit = pm.run(circuit)
isa_observables = observables.apply_layout(layout=isa_circuit.layout)

pub = (isa_circuit, isa_observables, [individual_phases])
cost = estimator.run([pub]).result()[0].data.evs
exp_values_with_opt_es = cost[0]

أخيراً، يمكننا رسم النتائج، ونرى أن دقة الحساب كانت جيدة إلى حد ما حتى بدون تحسين، لكنها تحسنت بالتأكيد بزيادة التحسين إلى المستوى 3. لاحظ أنه في الدوائر الأعمق والأكثر تعقيداً، من المرجح أن يكون الفرق بين مستويات التحسين 0 و 3 أكثر أهمية. هذه دائرة بسيطة جداً تُستخدم كنموذج تجريبي.

import matplotlib.pyplot as plt

plt.plot(phases, noisy_exp_values, "o", label="opt=0")
plt.plot(phases, exp_values_with_opt_es, "o", label="opt=3")
plt.plot(phases, 2 * np.sin(phases / 2) ** 2 - 1, label="ideal")
plt.ylabel("Expectation")
plt.legend()
plt.show()

Output of the previous code cell

تخفيف الأخطاء

يشير تخفيف الأخطاء إلى التقنيات التي تسمح للمستخدمين بتقليل أخطاء الدائرة عن طريق نمذجة ضوضاء الجهاز في وقت التنفيذ. عادة ما يؤدي هذا إلى تكلفة إضافية في المعالجة المسبقة الكمية المتعلقة بتدريب النموذج وتكلفة إضافية في المعالجة اللاحقة الكلاسيكية لتخفيف الأخطاء في النتائج الخام باستخدام النموذج المُنشأ.

يحدد خيار resilience_level لبدائيات Qiskit Runtime مقدار المرونة المبنية ضد الأخطاء. تولّد المستويات الأعلى نتائج أكثر دقة على حساب أوقات معالجة أطول بسبب تكلفة أخذ العينات الكمية الإضافية. يمكن استخدام مستويات المرونة لتكوين المقايضة بين التكلفة والدقة عند تطبيق تخفيف الأخطاء على استعلام البدائية.

عند تنفيذ أي تقنية لتخفيف الأخطاء، نتوقع أن يتم تقليل التحيز في نتائجنا بالنسبة للتحيز السابق غير المخفف. في بعض الحالات، قد يختفي التحيز حتى. ومع ذلك، يأتي هذا بتكلفة. عندما نقلل التحيز في كمياتنا المقدرة، سيزداد التباين الإحصائي (أي التباين)، والذي يمكننا تعويضه بزيادة عدد اللقطات لكل دائرة في عملية أخذ العينات. سيقدم هذا تكلفة إضافية تتجاوز تلك اللازمة لتقليل التحيز، لذا لا يتم بشكل افتراضي. يمكننا بسهولة الاشتراك في هذا السلوك من خلال ضبط عدد اللقطات لكل دائرة في options.executions.shots، كما هو موضح في المثال أدناه.

A diagram showing broader or narrowing distributions as in the bias/variance tradeoff.

في هذه الدورة، سنستكشف نماذج تخفيف الأخطاء هذه على مستوى عالٍ لتوضيح تخفيف الأخطاء الذي يمكن أن تنفذه بدائيات Qiskit Runtime دون الحاجة إلى تفاصيل التنفيذ الكاملة.

إخماد خطأ القراءة الملتوي (T-REx)

يستخدم إخماد خطأ القراءة الملتوي (T-REx) تقنية تعرف بتلوية باولي لتقليل الضوضاء المدخلة أثناء عملية القياس الكمي. تفترض هذه التقنية عدم وجود شكل محدد للضوضاء، مما يجعلها عامة جداً وفعالة.

سير العمل العام:

  1. الحصول على بيانات للحالة الصفرية مع قلبات بتات عشوائية (باولي X قبل القياس)
  2. الحصول على بيانات للحالة المطلوبة (المشوشة) مع قلبات بتات عشوائية (باولي X قبل القياس)
  3. حساب الدالة الخاصة لكل مجموعة بيانات، والقسمة.

 

A diagram showing measurement and calibration circuits for T-REX.

يمكننا ضبط هذا مع options.resilience_level = 1، كما هو موضح في المثال أدناه.

استقراء الضوضاء الصفرية

يعمل استقراء الضوضاء الصفرية (ZNE) عن طريق تضخيم الضوضاء أولاً في الدائرة التي تحضّر الحالة الكمية المطلوبة، والحصول على قياسات لعدة مستويات مختلفة من الضوضاء، واستخدام تلك القياسات لاستنتاج النتيجة الخالية من الضوضاء.

سير العمل العام:

  1. تضخيم ضوضاء الدائرة لعدة عوامل ضوضاء
  2. تشغيل كل دائرة مضخمة الضوضاء
  3. الاستقراء إلى حد الضوضاء الصفرية

 

A diagram showing steps in ZNE. Noise is artificially amplified by different factors. Then the values are extrapolated to what they should be at zero noise.

يمكننا ضبط هذا مع options.resilience_level = 2. يمكننا تحسين هذا أكثر من خلال استكشاف مجموعة متنوعة من noise_factors و noise_amplifiers و extrapolators، لكن هذا خارج نطاق هذه الدورة. نشجعك على تجربة هذه الخيارات كما هو موصوف هنا.

كل طريقة تأتي مع تكلفتها الإضافية الخاصة: مقايضة بين عدد الحسابات الكمية اللازمة (الوقت) ودقة نتائجنا:

MethodsR=1, T-RExR=2, ZNEAssumptionsNoneAbility to scale noiseQubit overhead11Sampling overhead2Nnoise-factorsBias0O(λNnoise-factors)\begin{array}{c|c|c|c} \text{Methods} & R=1 \text{, T-REx} & R=2 \text{, ZNE} \\[1mm] \hline \text{Assumptions} & \text{None} & \text{Ability to scale noise} \\[1mm] \text{Qubit overhead} & 1 & 1 \\[1mm] \text{Sampling overhead} & 2 & N_{\text{noise-factors}} \\[1mm] \text{Bias} & 0 & \mathcal{O}(\lambda^{N_{\text{noise-factors}}}) \\[1mm] \end{array}

استخدام خيارات التخفيف والقمع في Qiskit Runtime

إليك كيفية حساب قيمة متوقعة مع استخدام تخفيف وقمع الأخطاء في Qiskit Runtime. يمكننا الاستفادة من نفس الدائرة والمرصود بالضبط كما سبق، لكن هذه المرة مع الحفاظ على مستوى التحسين ثابتاً عند المستوى 2، والآن ضبط المرونة أو تقنية(تقنيات) تخفيف الأخطاء المستخدمة. تحدث عملية تخفيف الأخطاء هذه عدة مرات خلال حلقة التحسين.

ننفذ هذا الجزء على عتاد حقيقي، حيث أن تخفيف الأخطاء غير متاح على المحاكيات.

# Estimated usage: 8 minutes, benchmarked on an Eagle processor, 5-23-24

from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import (
Session,
EstimatorOptions,
EstimatorV2 as Estimator,
)

# We select the least busy backend

# Select the least busy backend
# backend = service.least_busy(
# operational=True, min_num_qubits=ansatz.num_qubits, simulator=False
# )

# Or use a specific backend
backend = service.backend("ibm_brisbane")

# Initialize some variables to save the results from different runs:

exp_values_with_em0_es = []
exp_values_with_em1_es = []
exp_values_with_em2_es = []

# Use a pass manager to optimize the circuit and observables for the backend chosen:

pm = generate_preset_pass_manager(backend=backend, optimization_level=2)
isa_circuit = pm.run(circuit)
isa_observables = observables.apply_layout(layout=isa_circuit.layout)

# Open a session and run with no error mitigation:

estimator_options = EstimatorOptions(resilience_level=0, default_shots=10_000)

with Session(backend=backend) as session:
estimator = Estimator(mode=session, options=estimator_options)

pub = (isa_circuit, isa_observables, [individual_phases])
cost = estimator.run([pub]).result()[0].data.evs

session.close()

exp_values_with_em0_es = cost[0]

# Open a session and run with resilience = 1:

estimator_options = EstimatorOptions(resilience_level=1, default_shots=10_000)

with Session(backend=backend) as session:
estimator = Estimator(mode=session, options=estimator_options)

pub = (isa_circuit, isa_observables, [individual_phases])
cost = estimator.run([pub]).result()[0].data.evs

session.close()

exp_values_with_em1_es = cost[0]

# Open a session and run with resilience = 2:

estimator_options = EstimatorOptions(resilience_level=2, default_shots=10_000)

with Session(backend=backend) as session:
estimator = Estimator(mode=session, options=estimator_options)

pub = (isa_circuit, isa_observables, [individual_phases])
cost = estimator.run([pub]).result()[0].data.evs

session.close()

exp_values_with_em2_es = cost[0]

كما سبق، يمكننا رسم القيم المتوقعة الناتجة كدالة لزاوية الطور لمستويات تخفيف الأخطاء الثلاثة المستخدمة. بصعوبة كبيرة، يمكن ملاحظة أن تخفيف الأخطاء يحسّن النتائج قليلاً. مرة أخرى، هذا التأثير أكثر وضوحاً بكثير في الدوائر الأعمق والأكثر تعقيداً.

import matplotlib.pyplot as plt

plt.plot(phases, exp_values_with_em0_es, "o", label="unmitigated")
plt.plot(phases, exp_values_with_em1_es, "o", label="resil = 1")
plt.plot(phases, exp_values_with_em2_es, "o", label="resil = 2")
plt.plot(phases, 2 * np.sin(phases / 2) ** 2 - 1, label="ideal")
plt.ylabel("Expectation")
plt.legend()
plt.show()

Output of the previous code cell

الملخص

من خلال هذا الدرس، تعلمت كيفية إنشاء دالة تكلفة:

  • إنشاء دالة تكلفة
  • كيفية الاستفادة من بدائيات Qiskit Runtime لتخفيف وقمع الضوضاء
  • كيفية تحديد استراتيجية قياس لتحقيق التوازن بين السرعة والدقة

إليك حمل العمل التبايُني عالي المستوى:

A diagram showing the quantum circuit with unitaries preparing the reference state and variational state, followed by measurements. These are used to evaluate the cost function.

تعمل دالة التكلفة أثناء كل تكرار من حلقة التحسين. سيستكشف الدرس التالي كيف يستخدم المُحسِّن الكلاسيكي تقييم دالة التكلفة لاختيار معلمات جديدة.

import qiskit
import qiskit_ibm_runtime

print(qiskit.version.get_version_info())
print(qiskit_ibm_runtime.version.get_version_info())
1.1.0
0.23.0