تمرين -- تنفيذ Oracle الكم لمشكلة تلوين الرسم البياني

مكتمل

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

في هذا الجزء، عليك تنفيذ Oracle الكم لمشكلة التلوين الرسم البياني.

ملاحظة

في هذه الوحدة سنركز على سلوك عالي المستوى من Oracle الكم و(في الوحدات التالية) خوارزمية البحث Grover. نحن نشجعكم على التعمق في التعليمات البرمجية بنفسك، خاصة، للبحث عن أي عمليات غير مألوفة وبنى اللغة في ⁧⁩وثائق Q#⁧⁩.

إنشاء المشروع

ابدأ بإنشاء مشروع Q# كما هو موضح في الوحدة النمطية ⁧⁩Create your first Q# program by using the Quantum Development Kit⁧⁩. للقيام بذلك:

  1. في القائمة ⁧⁩View⁧⁩، اختر ⁧⁩Command Palette⁧⁩.
  2. أدخل ⁧⁩Q#: قم بإنشاء مشروع جديد⁧⁩.
  3. حدد ⁧⁩Standalone console application⁧⁩.
  4. حدد دليلاً للاحتفاظ بالمشروع، مثل الدليل الرئيسي. أدخل ⁧⁩ExploringGroversSearch⁧⁩ كاسم المشروع، ثم اختر ⁧⁩Create Project⁧⁩.
  5. من النافذة التي تظهر في الأسفل، اختر ⁧⁩Open new project⁧⁩.

يجب أن ترى ملفين: ملف المشروع و⁧⁩Program.qs⁧⁩، الذي يحتوي على تعليمات برمجية للبدء.

بالنسبة إلى كل قصاصة برمجية في هذه الوحدة النمطية، يجب نسخ القصاصة بالكامل لاستبدال محتويات الملف ⁧⁩Program.qs⁧⁩. بعد ذلك، افتح terminal المتكاملة (من قائمة ⁧⁩Terminal⁧⁩ اختر ⁧⁩New Terminal⁧⁩) وقم بتشغيل ⁧dotnet run⁩:

dotnet run

تمثيل الرسم البياني

نحن بحاجة إلى معلمتين لتمثيل الرسم البياني: عدد الذرى وقائمة الحواف.

في Q#، سنقوم بتخزين عدد الذرى ⁧nVertices⁩ كأعداد صحيحة، وقائمة الحواف ⁧edges⁩ كصفيفة من المجموعات. تصف كل مجموعة حافة واحدة من الرسم البياني كزوج من مؤشرات الذرى المتصلة بهذه الحافة؛ سوف نستخدم مؤشرات تستند إلى الصفر بحيث يمكن أن تكون قيمة المؤشر بين 0 و⁧nVertices⁩ - 1.

الشكل الذي يظهر رسمًا بيانيًا.

يمكن تمثيل هيكل الرسم البياني المثال كما يلي:

namespace ExploringGroversSearchAlgorithm {
    @EntryPoint()
    operation SolveGraphColoringProblem() : Unit {
        // Graph description: hardcoded from the example
        // The number of vertices is an integer
        let nVertices = 5;
        // The list of edges is an array of tuples, and each tuple is a pair of integers
        let edges = [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3), (3, 4)];
    }
}

تمثل تلوين الذروة

سنقوم بوصف تلوين الرسم البياني من خلال مجموعة من ⁧nVertices⁩ الألوان. على سبيل المثال، سوف نبحث عن أربعة ألوان للرسم البياني -- التلوين الذي يستخدم على الأكثر أربعة ألوان، والترميز مع أعداد صحيحة من 0 إلى 3.

نحن بحاجة لتمثيل تلويننا في سلسلة بت، لذلك سوف نستخدم سلسلة بت بطول 2 * ⁧nVertices⁩، مع أول زوج من البت لترميز لون الذروة 0، الزوج الثاني -- لون الذروة 1، وهلم جرًا. سنقوم بتخزين بت لدينا كقيم منطقية، مع 0 و1 بت مشفرة كـ ⁧false⁩ و⁧true⁩ على التوالي. سيقوم زوج البتات بترميز فهرس ألوان عدد صحيح باستخدام little-endian notation، حيث يتم ترميز العدد الصحيح 1 كـ 10، مع تخزين أقل بت أهمية أولاً.

وإليك كيف سيتم ترميز تلوين الرسم البياني المثال لدينا وتفسيره:

namespace ExploringGroversSearchAlgorithm {
    open Microsoft.Quantum.Arrays;
    open Microsoft.Quantum.Convert;
    open Microsoft.Quantum.Intrinsic;


    @EntryPoint()
    operation SolveGraphColoringProblem() : Unit {
        // Graph description: hardcoded from the example
        let nVertices = 5;
        let edges = [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3), (3, 4)];

        // Graph coloring: hardcoded from the example
        let coloring = [false, false, true, false, false, true, true, true, true, false];
        let colors = ["red", "green", "blue", "yellow"];

        // Interpret the coloring: split the bit string into 2-bit fragments and convert them to colors.
        let colorBits = Chunks(2, coloring);
        for i in 0 .. nVertices - 1 {
            let colorIndex = BoolArrayAsInt(colorBits[i]);
            Message($"Vertex {i} - color #{colorIndex} ({colors[colorIndex]})");
        }
    }
}

هذا التعليمة البرمجية سوف تنتج المخرجات التالية:

Vertex 0 - color #0 (red)
Vertex 1 - color #1 (green)
Vertex 2 - color #2 (blue)
Vertex 3 - color #3 (yellow)
Vertex 4 - color #1 (green)

عندما نعمل مع تلوين الرسم البياني في برنامج الكم، نستخدم نفس الترميز، ولكن مع الحالات الأساس $|0\rangle $ و$ |1\rangle $ بدلاً من البت الكلاسيكية ⁧false⁩ و⁧true⁩. وسيتم تمثيل نفس التلوين كحالة 10-qubit $|0010011110\rangle$

تنفيذ Oracle

أحد النهج النموذجية لتنفيذ Oracle الكم لوظيفة معينة هو كما يلي:

  1. تكسير الدالة الكلاسيكية إلى كتل البناء الصغيرة التي يسهُل تنفيذها.
    يمكنك استخدام بوابات منطقية بدائية لتنفيذ أي دالة منطقية. يمكنك إما استخدام البوابات المنطقية البدائية مباشرة للحصول على تمثيل منخفض المستوى للدالة أو الاستفادة من كتل الإنشاء ذات المستوى الأعلى التي يتم تنفيذها بواسطة عمليات مكتبة Q#.

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

    • بوابة NOT الكلاسيكية تعادل بوابة X.
    • ويمكن تنفيذ بوابة XOR الكلاسيكية باستخدام بوابة CNOT (بوابة X الخاضعة للتحكم).
    • ويمكن تحقيق البوابة الكلاسيكية باستخدام بوابة Toffoli (بوابة X مزدوجة التحكم) وكيوبيت إضافية.
  3. إذا كانت الخوارزمية تحتاج إلى Oracle مرحلة، فقم بتحويل Oracle وضع العلامات إلى Oracle مرحلة.
    هذه الخطوة تستخدم خدعة قياسية تسمى ⁧⁩phase kickback⁧⁩: وهي، أن تطبيق Oracle وضع العلامات على مجموعة المدخلات من كيوبيت وكيوبيت الهدف في حالة معينة سيكون لها نفس التأثير على مجموعة المدخلات كتطبيق Oracle مرحلة عليها.

دعونا نرى كيف يعمل هذا النهج بالنسبة لمشكلة تلوين الذروة!

الخطوة 1 تحقق مما إذا كانت ألوان اثنتين من الذرى متشابهة

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

العملية التي تنفذ هذا الاختيار ( ⁧MarkColorEquality⁩ ) يجب أن تأخذ صفائف 2-كيوبيت كمدخلات، تمثل ألوان الذرى، وكيوبيت التي سنستخدمها لوضع علامة على نتيجة المقارنة عن طريق قلب حالتها إذا كانت الألوان هي نفسها. لمقارنة صفائف كيوبيت، قارنا البت المقابلة لبعضها البعض؛ إذا كانت كافة أزواج البتات هي نفسها، تكون الصفائف هي نفسها، وألوان الذرى المخزنة في تلك الصفائف هي نفسها. للمقارنة بين زوج من البتات، يمكننا حساب XOR الخاصة بها: إذا كانت النتيجة 0، تكون البتات هي نفسها؛ وإلا فهي مختلفة.

وهنا التعليمة البرمجية من Q# التي تنفذ هذا التفقد وتُستخدم لمقارنة اثنتين من صفائف كيوبيت: الأولى في حالة $|00\rangle $ والثانية في تراكب متساوٍ من جميع حالات الأساس.

namespace ExploringGroversSearchAlgorithm {
    open Microsoft.Quantum.Arrays;
    open Microsoft.Quantum.Canon;
    open Microsoft.Quantum.Convert;
    open Microsoft.Quantum.Diagnostics;
    open Microsoft.Quantum.Intrinsic;


    operation MarkColorEquality(c0 : Qubit[], c1 : Qubit[], target : Qubit) : Unit is Adj+Ctl {
        within {
            // Iterate over pairs of qubits in matching positions in c0 and c1.
            for (q0, q1) in Zipped(c0, c1) {
                // Compute XOR of bits q0 and q1 in place (storing it in q1).
                CNOT(q0, q1);
            }
        } apply {
            // If all computed XORs are 0, the bit strings are equal - flip the state of the target.
            (ControlledOnInt(0, X))(c1, target);
        }
    }


    @EntryPoint()
    operation ShowColorEqualityCheck() : Unit {
        use (c0, c1, target) = (Qubit[2], Qubit[2], Qubit());
        // Leave register c0 in the |00⟩ state.

        // Prepare a quantum state that is a superposition of all possible colors on register c1.
        ApplyToEach(H, c1);

        // Output the initial state of qubits c1 and target. 
        // We do not include the state of qubits in the register c0 for brevity, 
        // since they will remain |00⟩ throughout the program.
        Message("The starting state of qubits c1 and target:");
        DumpRegister((), c1 + [target]);

        // Compare registers and mark the result in target qubit.
        MarkColorEquality(c0, c1, target);

        Message("");
        Message("The state of qubits c1 and target after the equality check:");
        DumpRegister((), c1 + [target]);

        // Return the qubits to |0⟩ state before releasing them.
        ResetAll(c1 + [target]);
    }
}

within ... apply⁩تنفذ العبارة نمطًا شائعًا في الحوسبة الكمّية: تطبيق العبارات ⁧within⁩ وكتل ⁧apply⁩، ومن ثم "التراجع" عن كتلة ⁧within⁩. نستخدمها للتأكد من أن تطبيق التفقد لدينا ليس له تأثير غير متوقع على كيوبيت الإدخال.

ملاحظة

الدالة ⁧DumpRegister⁩ مشابهة للدالة ⁧DumpMachine⁩ التي تم استخدامها في الوحدات النمطية السابقة. إلا أنها تطبع المعلومات حول حالة ⁧⁩مجموعة فرعية⁧⁩ من كيوبيت بدلاً من كافة كيوبيت المستخدمة من قبل البرنامج. في التنفيذ الحالي لمحاكاة الحالة الكاملة، يمكن استخدام ⁧DumpRegister⁩ فقط إذا لم يكن هذا السجل متشابكًا مع بقية كيوبيت.

وهنا المخرجات من هذا الرمز :

The starting state of qubits c1 and target:
# wave function for qubits with ids (least to most significant): 2;3;4
∣0❭:     0.500000 +  0.000000 i  ==     *****                [ 0.250000 ]     --- [  0.00000 rad ]
∣1❭:     0.500000 +  0.000000 i  ==     *****                [ 0.250000 ]     --- [  0.00000 rad ]
∣2❭:     0.500000 +  0.000000 i  ==     *****                [ 0.250000 ]     --- [  0.00000 rad ]
∣3❭:     0.500000 +  0.000000 i  ==     *****                [ 0.250000 ]     --- [  0.00000 rad ]
∣4❭:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
∣5❭:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
∣6❭:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
∣7❭:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]

The state of qubits c1 and target after the equality check:
# wave function for qubits with ids (least to most significant): 2;3;4
∣0❭:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
∣1❭:     0.500000 +  0.000000 i  ==     *****                [ 0.250000 ]     --- [  0.00000 rad ]
∣2❭:     0.500000 +  0.000000 i  ==     *****                [ 0.250000 ]     --- [  0.00000 rad ]
∣3❭:     0.500000 +  0.000000 i  ==     *****                [ 0.250000 ]     --- [  0.00000 rad ]
∣4❭:     0.500000 +  0.000000 i  ==     *****                [ 0.250000 ]     --- [  0.00000 rad ]
∣5❭:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
∣6❭:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
∣7❭:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]

ملاحظة

للتذكير، يتم ترميز المؤشرات في مخرجات ⁧DumpMachine⁩ ⁧/⁩ ⁧DumpRegister⁩ في little-endian ومن ثمَّ، يتوافق الفهرس ⁧|1❭⁩ مع سلسلة بت ⁧100⁩، مع أقل بت أهمية المخزنة أولاً.

نرى أنه في البداية تكون حالة النظام

$$|00\rangle⁧_⁩\textrm{c0} \otimes \frac12\big(|00\rangle + |10\rangle + |01\rangle + |11\rangle\big)⁧_⁩\textrm{c1} \otimes |0\rangle⁧_⁩\textrm{target}.$$

بعد أن تطبيق التحقق من المساواة، لا تتغير حالة السجل ⁧c0⁩ (يمكنك التحقق من ذلك عن طريق إضافة مكالمة ⁧DumpRegister⁩ أخرى)، ولكن تتغير سعات الحالة المجتمعة من السجل ⁧c1⁩ و⁧target⁩ كيوبيت. تصبح سعة الحالة $|00\rangle⁧_⁩\textrm{c1} \otimes |0\rangle⁧_⁩\textrm{target}$ تساوي 0، وتصبح سعة حالة $|00\rangle⁧_⁩\textrm{c1} \otimes |1\rangle⁧_⁩\textrm{target}$ تساوي $0.25$$. لاحظ أن قيم السعتين لا تتغير فقط بل يتم تبديلها كنتيجة لتطبيق هذا التفقد.

في الواقع، منذ الألوان المشفرة في الدولة $ |00 \ rangle ⁧_⁩ \textrm {c0} \ otimes |00 \ rangle ⁧_⁩ \textrm {c1} \otimes |0\rangle ⁧_⁩ \textrm{الهدف}$ متساوية، تكون ⁧target⁩ حالة qubit لهذه الحالة الأساس قد انقلبت، ما يعطينا الحالة الناتجة.

$$|00\rangle_\textrm{c0} \otimes \frac12\big(|00\rangle_\textrm{c1} \otimes |1\rangle_\textrm{target} + |10\rangle_\textrm{c1} \otimes |0\rangle_\textrm{target} + |01\rangle_\textrm{c1} \otimes |0\rangle_\textrm{target} + |11\rangle_\textrm{c1} \otimes |0\rangle_\textrm{target} \big).$$

ملاحظة

لاحظ أن كيوبيت ⁧target⁩ تصبح متشابكة مع السجل ⁧c1⁩ : لم يعد بإمكانك فصل حالاتها! إذا كانت قيمة الدالة التي نقوم بتقييمها هي نفسها بالنسبة إلى جميع المدخلات، فإن كيوبيت الهدف سوف تبقى غير متشابكة من سجل الإدخال، وسيتم تخزين هذه القيمة بدلاً من ذلك. في حالتنا، بعض المدخلات تنتج $f(x) = 0 دولار وبعضها الآخر ينتج $f(x) = 1$، لذلك لا يمكن فصل المعلومات حول المدخلات من المعلومات حول المخرجات بعد ذلك.

الخطوة 2 التحقق مما إذا كان تلوين الذروة صالحًا

الآن بعد أن عرفنا كيف نتحقق من أن ألوان أي من الذروتين مختلفة، يمكننا أن نمثل التحقق من صحة تلوين الذروة كما يلي:

  1. كرر على جميع أزواج الذرى المتصلة بالحواف.
  2. لكل زوج، تحقق من أن ألوان هذه الذرى مختلفة.
  3. إذا كانت جميع أزواج الذرى تلبي هذا الشرط، يكون التلوين صالحًا.

لتنفيذ هذه الخطوات كعملية الكم، سنحتاج إلى تخصيص كيوبيت إضافية لتخزين نتائج مقارنات الألوان الزوجية، وكيوبيت واحدة لكل حافة. نبدأ مع تلك الكيوبيت في حالة $|0\rangle$ ومقارنة ألوان الذرى في كل زوج باستخدام عملية ⁧MarkColorEquality⁩ التي رأيناها أعلاه؛ تقوم بقلب حالة كيوبيت إلى $|1\rangle$ إذا كانت ألوان زوج الذرى المقابل هي نفسها.

وأخيرًا، نحسب النتيجة النهائية. إذا كانت كل الكيوبيت الإضافية المخصصة في حالة $|0\rangle $، نقوم بقلب حالة كيوبيت الهدف لدينا للإشارة إلى أن تلوين الذروة صالح.

إليك رمز Q# الذي يتحقق من صحة تلوين الذروة.

namespace ExploringGroversSearchAlgorithm {
    open Microsoft.Quantum.Arrays;
    open Microsoft.Quantum.Canon;
    open Microsoft.Quantum.Convert;
    open Microsoft.Quantum.Diagnostics;
    open Microsoft.Quantum.Intrinsic;


    operation MarkColorEquality(c0 : Qubit[], c1 : Qubit[], target : Qubit) : Unit is Adj+Ctl {
        within {
            // Iterate over pairs of qubits in matching positions in c0 and c1.
            for (q0, q1) in Zipped(c0, c1) {
                // Compute XOR of bits q0 and q1 in place (storing it in q1).
                CNOT(q0, q1);
            }
        } apply {
            // If all computed XORs are 0, the bit strings are equal - flip the state of the target.
            (ControlledOnInt(0, X))(c1, target);
        }
    }


    operation MarkValidVertexColoring(
        edges : (Int, Int)[], 
        colorsRegister : Qubit[], 
        target : Qubit
    ) : Unit is Adj+Ctl {
        let nEdges = Length(edges);
        // Split the register that encodes the colors into an array of two-qubit registers, one per color.
        let colors = Chunks(2, colorsRegister);
        // Allocate one extra qubit per edge to mark the edges that connect vertices with the same color.
        use conflictQubits = Qubit[nEdges];
        within {
            for ((start, end), conflictQubit) in Zipped(edges, conflictQubits) {
                // Check that the endpoints have different colors: apply MarkColorEquality operation; 
                // if the colors are the same, the result will be 1, indicating a conflict.
                MarkColorEquality(colors[start], colors[end], conflictQubit);
            }
        } apply {
            // If there are no conflicts (all qubits are in 0 state), the vertex coloring is valid.
            (ControlledOnInt(0, X))(conflictQubits, target);
        }
    }


    @EntryPoint()
    operation ShowColoringValidationCheck() : Unit {
        // Graph description: hardcoded from the example
        let nVertices = 5;
        let edges = [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3), (3, 4)];

        // Graph coloring: hardcoded from the example
        let coloring = [false, false, true, false, false, true, true, true, false, true];

        use (coloringRegister, target) = (Qubit[2 * nVertices], Qubit());
        // Encode the coloring in the quantum register:
        // apply an X gate to each qubit that corresponds to "true" bit in the bit string.
        ApplyPauliFromBitString(PauliX, true, coloring, coloringRegister);

        // Apply the operation that will check whether the coloring is valid.
        MarkValidVertexColoring(edges, coloringRegister, target);

        // Print validation result.
        let isColoringValid = M(target) == One;
        Message($"The coloring is {isColoringValid ? "valid" | "invalid"}");

        // Return the qubits to |0⟩ state before releasing them.
        ResetAll(coloringRegister);
}
}

ملاحظة

لا يتم تشغيل هذه المقتطفات البرمجية حاليًا على أي من أهداف أجهزة Azure Quantum المتوفرة، وذلك لأن مقارنات نتيجة القياس العامة (let isColoringValid = M(target) == One;) تتطلب توفر QPU تحتوي على ملف تعريف حساب كامل.

يمكن تنفيذ تعليمة وحدة Learn البرمجية التي لا تعرض هذه الملاحظة على أهداف الجهاز الحالية.

وهنا المخرجات من هذا الرمز :

The coloring is valid

تمارين المكافأة

  • جرب مع تلوينات وهياكل الرسم البياني لمعرفة تلك التي تعتبر صالحة وغير صالحة. مثال على التلوين غير الصالح لهذا الرسم البياني هو ⁧[false, false, true, false, false, true, true, true, true, true]⁩، الذي يصف رسمًا بيانيًا مع تعيين الذرى 3 و4 بنفس اللون.
  • قم بتعديل التعليمات البرمجية للتشغيل على تراكبات الإدخالات ومعرفة ما سيحدث.

الخطوة 3 قم بتحويل Oracle وضع العلامات إلى Oracle مرحلة

الآن، لدينا Oracle وضع العلامات، وهي، عملية تميز حالات كيوبيت التي تمثل التلوينات صالحة في حالة كيوبيت اضافية. كيف يمكننا استخدامها لتنفيذ Oracle مرحلة، أي عملية أخرى من شأنها أن تميز مثل هذه الحالات باستخدام مراحلها؟

يمكننا أن نفعل ذلك باستخدام ما يسمى "خدعة kickback مرحلة":

  1. قم بتخصيص كيوبيت إضافية في حالة $\frac⁧{1}⁩{\sqrt2}(|0\rangle - |1\rangle)$
  2. قم بتطبيق Oracle وضع العلامات $U_\textrm{mark}$ مع الكيوبيت الإضافية هذه كهدف.
    ماذا يحدث للسجل الذي يقوم بترميز التلوين في هذه الخطوة؟
    • إذا كانت الحالة الأساس $ |x\rangle $ تقوم بترميز ⁧⁩التلوين غير صالح⁧⁩، لا تتغير الحالة.
    • إذا كانت الحالة الأساس $|x\rangle $ تقوم بترميز التلوين ⁧⁩صالح⁧⁩، تقلب العملية $U_\textrm{mark}$ حالة كيوبيت الاضافية، وتحولها إلى$\frac⁧{1}⁩{\sqrt2}(|1\rangle - |0\rangle)$، وهو ما يعادل ضرب الحالة بأكملها بـ $-1$.

إذا قمت بتطبيق هذه الخطوات على حالة أساس، فلن تتمكن من معرفة الفرق - لن تكون المرحلة العالمية قابلة للملاحظة. ولكن إذا قمت بتطبيق هذه الخطوات على حالة تراكب، فسترى أن الحالات الأساس التي تقوم بترميز التلوين بأنه صالح تحصل على المرحلة النسبية $-1 $ - وهو بالضبط التأثير الذي نحتاج أن تكون عملية المرحلة فيه!

إليك ما تبدو عليه خدعة kickback المرحلة في Q#. سنستخدم العملية التي تنفذ فحص اللون، ما يجعل من السهل رؤية الآثار في المخرجات، ولكن يمكنك استخدام نفس الخدعة على أي عملية تنفذ Oracle وضع العلامات.

namespace ExploringGroversSearchAlgorithm {
    open Microsoft.Quantum.Arrays;
    open Microsoft.Quantum.Canon;
    open Microsoft.Quantum.Convert;
    open Microsoft.Quantum.Diagnostics;
    open Microsoft.Quantum.Intrinsic;


    operation MarkColorEquality(c0 : Qubit[], c1 : Qubit[], target : Qubit) : Unit is Adj+Ctl {
        within {
            // Iterate over pairs of qubits in matching positions in c0 and c1.
            for (q0, q1) in Zipped(c0, c1) {
                // Compute XOR of bits q0 and q1 in place (storing it in q1).
                CNOT(q0, q1);
            }
        } apply {
            // If all computed XORs are 0, the bit strings are equal - flip the state of the target.
            (ControlledOnInt(0, X))(c1, target);
        }
    }


    operation ApplyMarkingOracleAsPhaseOracle(
        markingOracle : ((Qubit[], Qubit[], Qubit) => Unit is Adj), 
        c0 : Qubit[],
        c1 : Qubit[]
    ) : Unit is Adj {
        use target = Qubit();
        within {
            // Put the target qubit into the |-⟩ state.
            X(target);
            H(target);
        } apply {
            // Apply the marking oracle; since the target is in the |-⟩ state,
            // flipping the target if the register state satisfies the condition 
            // will apply a -1 relative phase to the register state.
            markingOracle(c0, c1, target);
        }
    }


    @EntryPoint()
    operation ShowPhaseKickbackTrick() : Unit {
        use (c0, c1) = (Qubit[2], Qubit[2]);
        // Leave register c0 in the |00⟩ state.

        // Prepare a quantum state that is a superposition of all possible colors on register c1.
        ApplyToEach(H, c1);

        // Output the initial state of qubits c1. 
        // We do not include the state of qubits in the register c0 for brevity, 
        // since they will remain |00⟩ throughout the program.
        Message("The starting state of qubits c1:");
        DumpRegister((), c1);

        // Compare registers and mark the result in their phase.
        ApplyMarkingOracleAsPhaseOracle(MarkColorEquality, c0, c1);

        Message("");
        Message("The state of qubits c1 after the equality check:");
        DumpRegister((), c1);

        // Return the qubits to |0⟩ state before releasing them.
        ResetAll(c1);
    }
}
The starting state of qubits c1:
# wave function for qubits with ids (least to most significant): 2;3
∣0❭:     0.500000 +  0.000000 i  ==     *****                [ 0.250000 ]     --- [  0.00000 rad ]
∣1❭:     0.500000 +  0.000000 i  ==     *****                [ 0.250000 ]     --- [  0.00000 rad ]
∣2❭:     0.500000 +  0.000000 i  ==     *****                [ 0.250000 ]     --- [  0.00000 rad ]
∣3❭:     0.500000 +  0.000000 i  ==     *****                [ 0.250000 ]     --- [  0.00000 rad ]

The state of qubits c1 after the equality check:
# wave function for qubits with ids (least to most significant): 2;3
∣0❭:    -0.500000 +  0.000000 i  ==     *****                [ 0.250000 ] ---     [  3.14159 rad ]
∣1❭:     0.500000 +  0.000000 i  ==     *****                [ 0.250000 ]     --- [  0.00000 rad ]
∣2❭:     0.500000 +  0.000000 i  ==     *****                [ 0.250000 ]     --- [  0.00000 rad ]
∣3❭:     0.500000 +  0.000000 i  ==     *****                [ 0.250000 ]     --- [  0.00000 rad ]

يمكنك أن ترى أنه حقًا، تغيرت سعة حالة $|00\rangle $ تغيرت إلى $ - 0.5 $، ومن ثمَّ فإن المرحلة النسبية الخاصة بها مقارنة مع الحالات الأساس الأخرى أصبحت الآن $-1 $.

مبروك، يا مستكشف الفضاء! الآن أصبحت تعرف كيفية بناء Oracle الكم الكامل لمشكلة تلوين الرسم البياني!

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