From a79d7dbfbcb24767f416e43eec565c060319de7f Mon Sep 17 00:00:00 2001 From: poig <43441314+poig@users.noreply.github.com> Date: Wed, 16 Nov 2022 11:18:13 +0800 Subject: [PATCH 1/6] startup --- requirements.txt | 0 src/backend/metal_backend.py | 2 ++ src/channels/classical_channel.py | 7 ++++ src/channels/model/binary.py | 1 + src/channels/model/optical.py | 1 + src/channels/model/wireless.py | 1 + src/channels/quantum_channel.py | 4 +++ src/components/network.py | 2 ++ src/components/node.py | 53 +++++++++++++++++++++++++++++++ src/components/protocols.py | 1 + src/components/storage.py | 1 + src/kernel/event.py | 1 + src/kernel/eventList.py | 1 + src/kernel/timeline.py | 1 + src/utils/cascade.py | 0 src/utils/entanglements.py | 1 + src/utils/logging.py | 0 src/visual/plot.py | 2 ++ 18 files changed, 79 insertions(+) create mode 100644 requirements.txt create mode 100644 src/backend/metal_backend.py create mode 100644 src/channels/model/binary.py create mode 100644 src/channels/model/optical.py create mode 100644 src/channels/model/wireless.py create mode 100644 src/components/network.py create mode 100644 src/components/protocols.py create mode 100644 src/components/storage.py create mode 100644 src/kernel/event.py create mode 100644 src/kernel/eventList.py create mode 100644 src/kernel/timeline.py create mode 100644 src/utils/cascade.py create mode 100644 src/utils/entanglements.py create mode 100644 src/utils/logging.py create mode 100644 src/visual/plot.py diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/backend/metal_backend.py b/src/backend/metal_backend.py new file mode 100644 index 0000000..70b06d2 --- /dev/null +++ b/src/backend/metal_backend.py @@ -0,0 +1,2 @@ +#where qiskit metal fake backend import and setup will include here. + diff --git a/src/channels/classical_channel.py b/src/channels/classical_channel.py index 8b13789..5df03a9 100644 --- a/src/channels/classical_channel.py +++ b/src/channels/classical_channel.py @@ -1 +1,8 @@ + + +class ClassicalChannel(object): + + def __init__(self, sender_id, receiver_id, model): + pass + diff --git a/src/channels/model/binary.py b/src/channels/model/binary.py new file mode 100644 index 0000000..858c6e5 --- /dev/null +++ b/src/channels/model/binary.py @@ -0,0 +1 @@ +# where package drop probability, which mimic the real situation \ No newline at end of file diff --git a/src/channels/model/optical.py b/src/channels/model/optical.py new file mode 100644 index 0000000..7aea744 --- /dev/null +++ b/src/channels/model/optical.py @@ -0,0 +1 @@ +#setting up optical fiber connection, adding condition \ No newline at end of file diff --git a/src/channels/model/wireless.py b/src/channels/model/wireless.py new file mode 100644 index 0000000..2fb3865 --- /dev/null +++ b/src/channels/model/wireless.py @@ -0,0 +1 @@ +# maybe it is setalite connection \ No newline at end of file diff --git a/src/channels/quantum_channel.py b/src/channels/quantum_channel.py index 8b13789..efd978f 100644 --- a/src/channels/quantum_channel.py +++ b/src/channels/quantum_channel.py @@ -1 +1,5 @@ +class QuantumChannel(object): + + def __init__(self, sender_id, receiver_id, model): + pass \ No newline at end of file diff --git a/src/components/network.py b/src/components/network.py new file mode 100644 index 0000000..f04fd1a --- /dev/null +++ b/src/components/network.py @@ -0,0 +1,2 @@ +# here will include and where the node will be build up using rustworkx, reference QuNetSim/component/network + diff --git a/src/components/node.py b/src/components/node.py index ce55856..f96ee5e 100644 --- a/src/components/node.py +++ b/src/components/node.py @@ -1 +1,54 @@ #Placeholder for qiskit implementation of a node architecture +import itertools + +class Node(object): + id_obj = itertools.count() + def __init__(self,node_name, timeline): + #reference will be sequence/topology/node & QuNetSim/Component/host + #logger + #time_line, what time the node will be create + + #node_id + self.node_id = Node.id_obj # will replace to uuid4().int for future use in large network + #node_name + self.node_name = node_name #this is like + #node_classical_channel, we will use rustworkx, to speed up, also it may apply qiskit optimization in the future, for applying other related algorithm in qiskit ecosystem. + self.classical_channel = {} + #node_quantum_channel + self.quantum_channel = {} + #classical storage + self.classical_storage = None + #quantum storage + self.quantum_storage = None + #component, for future use for create other type of node + self.components = {} + + def add_connection(self): + """ + adding quantum_channel or classical_channel to the specific node + """ + pass + def delete_connection(self): + pass + + def send_message(self): + """ + how it receive classical message, will include in protocols.py + """ + pass + def receive_message(self): + """ + how it receive classical message, will include in protocols.py + """ + pass + +class CustomNode(): + """ + this will be custom circuit input as startup + """ + +class QKDNode(): + """ + Quantum Key Distribution Node + """ + diff --git a/src/components/protocols.py b/src/components/protocols.py new file mode 100644 index 0000000..ec51abf --- /dev/null +++ b/src/components/protocols.py @@ -0,0 +1 @@ +#this file will include all the tools for sending keys, reference QuNetSim \ No newline at end of file diff --git a/src/components/storage.py b/src/components/storage.py new file mode 100644 index 0000000..c7d579b --- /dev/null +++ b/src/components/storage.py @@ -0,0 +1 @@ +#here will include quantum storage and classical storage, reference QuNetSim \ No newline at end of file diff --git a/src/kernel/event.py b/src/kernel/event.py new file mode 100644 index 0000000..3efca6b --- /dev/null +++ b/src/kernel/event.py @@ -0,0 +1 @@ +# where saving what node will do \ No newline at end of file diff --git a/src/kernel/eventList.py b/src/kernel/eventList.py new file mode 100644 index 0000000..caea6a0 --- /dev/null +++ b/src/kernel/eventList.py @@ -0,0 +1 @@ +# class for packing event \ No newline at end of file diff --git a/src/kernel/timeline.py b/src/kernel/timeline.py new file mode 100644 index 0000000..807f116 --- /dev/null +++ b/src/kernel/timeline.py @@ -0,0 +1 @@ +# where everything will save and happen here \ No newline at end of file diff --git a/src/utils/cascade.py b/src/utils/cascade.py new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/entanglements.py b/src/utils/entanglements.py new file mode 100644 index 0000000..16e68f6 --- /dev/null +++ b/src/utils/entanglements.py @@ -0,0 +1 @@ +#this file will include all type of entanglements, reference Sequences/entanglement_management \ No newline at end of file diff --git a/src/utils/logging.py b/src/utils/logging.py new file mode 100644 index 0000000..e69de29 diff --git a/src/visual/plot.py b/src/visual/plot.py new file mode 100644 index 0000000..b18ecbe --- /dev/null +++ b/src/visual/plot.py @@ -0,0 +1,2 @@ +#maybe make a thing that can plot something cool, like timeline of all the action, or simple node connection prinout. + From 80c6e6de77a1cd7b5cc60f1ae118921d02801f3a Mon Sep 17 00:00:00 2001 From: poig <43441314+poig@users.noreply.github.com> Date: Tue, 13 Dec 2022 18:51:03 +0800 Subject: [PATCH 2/6] demo sample --- docs/demo/Readme.md | 10 + docs/demo/demo_bb84.ipynb | 292 +++++++++++++++++++++++++++++ docs/demo/discrete_bb84/alice.py | 0 docs/demo/discrete_bb84/bob.py | 0 docs/demo/discrete_bb84/network.py | 0 5 files changed, 302 insertions(+) create mode 100644 docs/demo/Readme.md create mode 100644 docs/demo/demo_bb84.ipynb create mode 100644 docs/demo/discrete_bb84/alice.py create mode 100644 docs/demo/discrete_bb84/bob.py create mode 100644 docs/demo/discrete_bb84/network.py diff --git a/docs/demo/Readme.md b/docs/demo/Readme.md new file mode 100644 index 0000000..20dd71a --- /dev/null +++ b/docs/demo/Readme.md @@ -0,0 +1,10 @@ + +# Demo +## This file consist two type of simulation way: +### Continuous application all in one python file: +This able to simulate a large number of node easier, also is easier for development and analysis. +more example and explaination... + +### Descrete application, which each node and settings will have its own seperate file: +Which can be use for complex simulation as well as use in real application +more example and explaination... diff --git a/docs/demo/demo_bb84.ipynb b/docs/demo/demo_bb84.ipynb new file mode 100644 index 0000000..4162bb5 --- /dev/null +++ b/docs/demo/demo_bb84.ipynb @@ -0,0 +1,292 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# BB84_Demo" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import random\n", + "\n", + "from qiskit_network.components import Network\n", + "from qiskit_network.components.storage import QuantumStorage, ClassicalStorage\n", + "from qiskit_network.channels import Channels, QuantumChannels, ClassicalChannels\n", + "from qiskit_network import logger\n", + "\n", + "from qiskit.utils import QuantumInstance\n", + "from qiskit import Aer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# the backend to run the circuit\n", + "qi = QuantumInstance(Aer.get_backend('aer_simulator_statevector'), shots=1)\n", + "\n", + "# pennylane-qiskit plugin may use here, but not sure if plugin will include pennylane-pulse.\n", + "#pennylane = qml.device('default.qubit', wires=1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#alice will create its own network, it also can interact with other network, we should auto determind backend.\n", + "network = Network.Environment(name='alice_net', nodes=['Alice', 'Bob','Eve'], backend=qi) \n", + "\n", + "Alice = network.get_node('Alice')\n", + "Bob = network.get_node('Bob')\n", + "# so this will be seperate out from normal setup.\n", + "# todo: set eve\n", + "network.get_node('eve').eve_interception()\n", + "\n", + "# set all network with idendical setup\n", + "# this will use for storing packet message\n", + "Cstorage = ClassicalStorage()\n", + "# Not sure how quantum storage will works out with real device yet, but we want to simulate the photon components in pulse, \n", + "# there should be some research paper for references in #14 or pennylane photon.\n", + "# todo: need more inventigation to how to tranform it into pulse level\n", + "Qstorage = QuantumStorage(name=\"\", timeline=None, num_memories=10,fidelity=0.85, frequency=80e6, efficiency=1, coherence_time=-1, wavelength=500)\n", + "\n", + "# if any settings require qiskit pulse, will automatically turn into qiskit-pulse scheduler, but it only work for clean simulator.\n", + "QKD_QC = QuantumChannels(name=\"\", timeline=None,distance = 1000,attenuation=0, polarization_fidelity = 1.0, light_speed=2e-4, frequency=8e7) \n", + "ethernet_CC = ClassicalChannels(name=\"\",timeline=None, distance = 1000,delay=1e9)\n", + "#other type of channels, like service channels for other protocols\n", + "# todo: how to setup custom encryption, will need to look into https://github.com/OpenKMIP/PyKMIP/tree/master/kmip/demos\n", + "encrypted_CC = ClassicalChannels(name=\"encrypted data over fiber\",timeline= None, distance = 1000,delay=1e9, ) \n", + "\n", + "# todo: what else need to set here, need to learn more about channels from different quantum internet Standardization and latest research\n", + "channels = Channels(Classical=[ethernet_CC,encrypted_CC], Quantum=QKD_QC, ) \n", + "# connection can be any known type of ethernet connection or custom coupling map\n", + "# (which can be useful for vqe optimize quantum network connection, link: https://pennylane.ai/blog/2022/10/the-quantum-internet-and-variational-quantum-optimization/)\n", + "network.setup_all(storage=[Qstorage,Cstorage], channels=channels, connection='mesh') " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#cascade, reference: https://github.com/upsideon/qkd-qchack-2022/blob/main/qkd/src/cascade.py\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this will be action of the node whether its a receiver or a sender\n", + "class all_func:\n", + " def __init__(self, sender, receiver, n=16):\n", + " self.sender = sender\n", + " self.receiver = receiver\n", + " self.n = n\n", + "\n", + " self.bit_flips = [None for _ in range(n)]\n", + " self.basis_flips = [random.randint(0, 1) for _ in range(n)]\n", + " self.num_test_bits = max(n // 4, 1)\n", + " \n", + " def distribute_bb84_states(self, conn, epr_socket, receiver=False):\n", + " for i in range(self.n):\n", + " # Note that we will need to inlcude other things like bsm node, so this is where everything is differnet\n", + " if receiver == False:\n", + " qc = epr_socket.create_epr(num_qubit=1, post_routine= None, sequential=False, time_unit=time.SECONDS, Max_time = 0, min_fidelity = None, max_tries = None)\n", + " else:\n", + " qc = epr_socket.receive_epr(num_qubit=1, post_routine= None, sequential=False, min_fidelity = None, max_tries = None)\n", + " \n", + " if self.basis_flips[i]:\n", + " qc.h(0)\n", + " # todo: maybe include different measurement or settings\n", + " m = qc.run_measure(0,0,)\n", + "\n", + " conn.flush()\n", + " self.bit_flips[i] = int(m)\n", + " return self.bit_flips, self.basis_flips\n", + " \n", + " def estimate_error_rate(self,socket,key,start = None, end = None, receiver = False):\n", + " if receiver:\n", + " test_indices = socket.recv_structured().payload\n", + " start,end = test_indices\n", + " test_outcomes = key[start:end]\n", + "\n", + " #logger.info(f\"bob test indices: {test_indices}\")\n", + " #logger.info(f\"bob test outcomes: {test_outcomes}\")\n", + "\n", + " socket.send_structured(StructuredMessage(\"Test outcomes\", test_outcomes))\n", + " target_test_outcomes = socket.recv_structured().payload\n", + " else:\n", + " test_outcomes = key[start:end]\n", + " test_indices = start,end\n", + "\n", + " socket.send_structured(StructuredMessage(\"Test indices\", test_indices))\n", + " target_test_outcomes = socket.recv_structured().payload\n", + " socket.send_structured(StructuredMessage(\"Test outcomes\", test_outcomes))\n", + "\n", + " num_error = 0\n", + " for (i1, i2) in zip(test_outcomes, target_test_outcomes):\n", + " #assert i1 == i2\n", + " if i1 != i2:\n", + " num_error += 1\n", + "\n", + " return (num_error / (end - start))*100\n", + " \n", + " def start_sender(self,start, end):\n", + " self.start, self.end = start, end\n", + " bit_flips, basis_flips = self.distribute_bb84_states(\n", + " self.sender, channels.quantum(receiver = self.receiver)\n", + " )\n", + " #logger.info(f\"sender outcomes: {bit_flips}\")\n", + " #logger.info(f\"sender theta: {basis_flips}\")\n", + " socket = channels.classical(sender = self.sender, receiver = self.receiver)\n", + " error_rate = self.estimate_error_rate(socket,bit_flips, 0, 6)\n", + " socket.send('1' if error_rate<=0.0 else '0')\n", + " return {\n", + " \"error_rate\" : error_rate,\n", + " \"secret_key\" : self.basis_flips,\n", + " }\n", + "\n", + " def start_receiver(self):\n", + " bit_flips, basis_flips = self.distribute_bb84_states(\n", + " self.receiver, channels.quantum(sender = self.receiver), receriver = True\n", + " )\n", + " #logger.info(f\"receiver outcomes: {bit_flips}\")\n", + " #logger.info(f\"receiver theta: {basis_flips}\")\n", + " socket = channels.classical(sender = self.sender, receiver = self.receiver)\n", + " error_rate = self.estimate_error_rate(socket,bit_flips, receiver = True)\n", + " accept_string = socket.recv()\n", + " accept_key = True if accept_string == '1' else False\n", + " return {\n", + " \"error_rate\" : error_rate,\n", + " \"secret_key\" : self.basis_flips,\n", + " \"accept\" : accept_key\n", + " }\n", + "\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this will insert in the middle of circuit\n", + "def eve(qc):\n", + " key_string = random.randint(0, 1)\n", + " if key_string == 0:\n", + " qc.x(0)\n", + " elif key_string == 1:\n", + " qc.h(0)\n", + " # qiskit dynamic circuit here\n", + " qc.run_measure()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# which may include more function settings, all_func will always include sender and receiver variable to run analysis or any thing.\n", + "network.set_function(all_func, interception=eve)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# how the overall circuit look like\n", + "display(network.construct_circuit().draw())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "result = network.run()\n", + "# sample output:\n", + "\"\"\"\n", + "{\"Alice\": {\"error_rate\": 6.2, \"secret_key\": xxxxxx}, \"Bob\": {\"error_rate\": 6.2, \"secret_key\": xxxxxx, \"accept\" : 0} }\n", + "\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# visualization or maybe \n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# or setup for individually\n", + "#alice = network.get_node('Alice')\n", + "\n", + "\n", + "# seperate for the node\n", + "#def alice_func(Alice):\n", + "# msg_buff = []\n", + "# distribute_bb84_states(alice, msg_buff, secret_key, network.get_node('eve'))\n", + "# estimate_error_rate(alice,key, )\n", + "#\n", + "#def bob_func(bob):\n", + "# msg_buff = []\n", + "# distribute_bb84_states(bob, msg_buff, secret_key, network.get_node('eve'))\n", + "# estimate_error_rate(bob,key, )" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.8.5 ('base')", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.5" + }, + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "cfc6018f50c06ad9d7e17a7fe21ab56b98ad21c1ebae78e6fadfee463f6ace79" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/demo/discrete_bb84/alice.py b/docs/demo/discrete_bb84/alice.py new file mode 100644 index 0000000..e69de29 diff --git a/docs/demo/discrete_bb84/bob.py b/docs/demo/discrete_bb84/bob.py new file mode 100644 index 0000000..e69de29 diff --git a/docs/demo/discrete_bb84/network.py b/docs/demo/discrete_bb84/network.py new file mode 100644 index 0000000..e69de29 From 5974fd930467821f980f8e331f6a69054d9b90e7 Mon Sep 17 00:00:00 2001 From: poig <43441314+poig@users.noreply.github.com> Date: Tue, 13 Dec 2022 18:57:47 +0800 Subject: [PATCH 3/6] Update demo_bb84.ipynb --- docs/demo/demo_bb84.ipynb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/demo/demo_bb84.ipynb b/docs/demo/demo_bb84.ipynb index 4162bb5..a6b753b 100644 --- a/docs/demo/demo_bb84.ipynb +++ b/docs/demo/demo_bb84.ipynb @@ -45,13 +45,13 @@ "outputs": [], "source": [ "#alice will create its own network, it also can interact with other network, we should auto determind backend.\n", - "network = Network.Environment(name='alice_net', nodes=['Alice', 'Bob','Eve'], backend=qi) \n", + "network = Network.Environment(name='alice_net', user_nodes=['Alice', 'Bob','Eve'], backend=qi) \n", "\n", - "Alice = network.get_node('Alice')\n", - "Bob = network.get_node('Bob')\n", + "Alice = network.get_user_node('Alice')\n", + "Bob = network.get_user_node('Bob')\n", "# so this will be seperate out from normal setup.\n", "# todo: set eve\n", - "network.get_node('eve').eve_interception()\n", + "network.get_user_node('eve').eve_interception()\n", "\n", "# set all network with idendical setup\n", "# this will use for storing packet message\n", @@ -59,7 +59,7 @@ "# Not sure how quantum storage will works out with real device yet, but we want to simulate the photon components in pulse, \n", "# there should be some research paper for references in #14 or pennylane photon.\n", "# todo: need more inventigation to how to tranform it into pulse level\n", - "Qstorage = QuantumStorage(name=\"\", timeline=None, num_memories=10,fidelity=0.85, frequency=80e6, efficiency=1, coherence_time=-1, wavelength=500)\n", + "Qstorage = QuantumStorage(name=\"\", timeline=None, num_memories=16,fidelity=0.85, frequency=80e6, efficiency=1, coherence_time=-1, wavelength=500)\n", "\n", "# if any settings require qiskit pulse, will automatically turn into qiskit-pulse scheduler, but it only work for clean simulator.\n", "QKD_QC = QuantumChannels(name=\"\", timeline=None,distance = 1000,attenuation=0, polarization_fidelity = 1.0, light_speed=2e-4, frequency=8e7) \n", @@ -105,9 +105,9 @@ " for i in range(self.n):\n", " # Note that we will need to inlcude other things like bsm node, so this is where everything is differnet\n", " if receiver == False:\n", - " qc = epr_socket.create_epr(num_qubit=1, post_routine= None, sequential=False, time_unit=time.SECONDS, Max_time = 0, min_fidelity = None, max_tries = None)\n", + " qc = epr_socket.create_epr(num_qubit=1)\n", " else:\n", - " qc = epr_socket.receive_epr(num_qubit=1, post_routine= None, sequential=False, min_fidelity = None, max_tries = None)\n", + " qc = epr_socket.receive_epr(num_qubit=1)\n", " \n", " if self.basis_flips[i]:\n", " qc.h(0)\n", From 6dc9b817453788c5e03c91ee5adf544b95650055 Mon Sep 17 00:00:00 2001 From: poig <43441314+poig@users.noreply.github.com> Date: Fri, 16 Dec 2022 17:22:49 +0800 Subject: [PATCH 4/6] Update demo_bb84.ipynb --- docs/demo/demo_bb84.ipynb | 337 ++++++++++++++++++++++++++++++++++---- 1 file changed, 303 insertions(+), 34 deletions(-) diff --git a/docs/demo/demo_bb84.ipynb b/docs/demo/demo_bb84.ipynb index a6b753b..4e85070 100644 --- a/docs/demo/demo_bb84.ipynb +++ b/docs/demo/demo_bb84.ipynb @@ -1,10 +1,12 @@ { "cells": [ { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ - "# BB84_Demo" + "# BB84_Demo\n", + "## import and tools setup" ] }, { @@ -20,6 +22,7 @@ "from qiskit_network.components.storage import QuantumStorage, ClassicalStorage\n", "from qiskit_network.channels import Channels, QuantumChannels, ClassicalChannels\n", "from qiskit_network import logger\n", + "from qiskit_network.kernel import Timeline\n", "\n", "from qiskit.utils import QuantumInstance\n", "from qiskit import Aer" @@ -38,6 +41,14 @@ "#pennylane = qml.device('default.qubit', wires=1)" ] }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Node" + ] + }, { "cell_type": "code", "execution_count": null, @@ -45,28 +56,53 @@ "outputs": [], "source": [ "#alice will create its own network, it also can interact with other network, we should auto determind backend.\n", - "network = Network.Environment(name='alice_net', user_nodes=['Alice', 'Bob','Eve'], backend=qi) \n", + "network = Network.Environment(name='alice_net', user_nodes=['Alice', 'Bob','Eve'], backend=qi, hardware_node = None) \n", "\n", "Alice = network.get_user_node('Alice')\n", "Bob = network.get_user_node('Bob')\n", "# so this will be seperate out from normal setup.\n", "# todo: set eve\n", - "network.get_user_node('eve').eve_interception()\n", - "\n", + "Eve = network.get_user_node('Eve').eve_interception()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create Network" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# auto, which it will depend on the action and hardware to determine the timeline length\n", + "timeline = Timeline('auto')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "# set all network with idendical setup\n", "# this will use for storing packet message\n", "Cstorage = ClassicalStorage()\n", "# Not sure how quantum storage will works out with real device yet, but we want to simulate the photon components in pulse, \n", "# there should be some research paper for references in #14 or pennylane photon.\n", "# todo: need more inventigation to how to tranform it into pulse level\n", - "Qstorage = QuantumStorage(name=\"\", timeline=None, num_memories=16,fidelity=0.85, frequency=80e6, efficiency=1, coherence_time=-1, wavelength=500)\n", + "Qstorage = QuantumStorage(name=\"\", timeline=timeline, num_memories=16,fidelity=0.85, frequency=80e6, efficiency=1, coherence_time=-1, wavelength=500)\n", "\n", "# if any settings require qiskit pulse, will automatically turn into qiskit-pulse scheduler, but it only work for clean simulator.\n", - "QKD_QC = QuantumChannels(name=\"\", timeline=None,distance = 1000,attenuation=0, polarization_fidelity = 1.0, light_speed=2e-4, frequency=8e7) \n", - "ethernet_CC = ClassicalChannels(name=\"\",timeline=None, distance = 1000,delay=1e9)\n", + "QKD_QC = QuantumChannels(name=\"\", timeline=timeline,distance = 1000,attenuation=0, polarization_fidelity = 1.0, light_speed=2e-4, frequency=8e7) \n", + "ethernet_CC = ClassicalChannels(name=\"\",timeline=timeline, distance = 1000,delay=1e9)\n", "#other type of channels, like service channels for other protocols\n", "# todo: how to setup custom encryption, will need to look into https://github.com/OpenKMIP/PyKMIP/tree/master/kmip/demos\n", - "encrypted_CC = ClassicalChannels(name=\"encrypted data over fiber\",timeline= None, distance = 1000,delay=1e9, ) \n", + "encrypted_CC = ClassicalChannels(name=\"encrypted data over fiber\",timeline= timeline, distance = 1000,delay=1e9, ) \n", "\n", "# todo: what else need to set here, need to learn more about channels from different quantum internet Standardization and latest research\n", "channels = Channels(Classical=[ethernet_CC,encrypted_CC], Quantum=QKD_QC, ) \n", @@ -75,13 +111,199 @@ "network.setup_all(storage=[Qstorage,Cstorage], channels=channels, connection='mesh') " ] }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## setup Action function" + ] + }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "#cascade, reference: https://github.com/upsideon/qkd-qchack-2022/blob/main/qkd/src/cascade.py\n" + "#cascade, reference: https://github.com/upsideon/qkd-qchack-2022/blob/main/qkd/src/cascade.py\n", + "import numpy as np\n", + "\n", + "def quantum_bit_error_rate(local_set, remote_set):\n", + " \"\"\"\n", + " Estimates the quantum bit error rate based on two sets.\n", + " \"\"\"\n", + " num_incorrect = 0\n", + " for i in range(len(local_set)):\n", + " if remote_set[i] != local_set[i]:\n", + " num_incorrect += 1\n", + " return num_incorrect / len(local_set)\n", + "\n", + "def binary_algorithm(block, block_indices, ask_parity_fn):\n", + " \"\"\"\n", + " Recursively splits a block with odd error parity into\n", + " left and right sub-blocks to find and correct one-bit\n", + " errors.\n", + " \"\"\"\n", + "\n", + " # If we have a block of size one, we correct the bit as\n", + " # it must have an odd number of errors per the input\n", + " # assumptions of the binary algorithm.\n", + " if len(block) == 1:\n", + " if block[0] == 0:\n", + " block[0] = 1\n", + " else:\n", + " block[0] = 0\n", + " return block\n", + "\n", + " # The block split index selection ensures that the left\n", + " # block has one more bit than the right when the block\n", + " # size is odd.\n", + " block_split_index = (len(block) + 1) // 2\n", + "\n", + " left_block = block[:block_split_index]\n", + " right_block = block[block_split_index:]\n", + "\n", + " left_block_indices = block_indices[:block_split_index]\n", + " right_block_indices = block_indices[block_split_index:]\n", + "\n", + " # Computing the current parity of the left block.\n", + " current_left_block_parity = np.sum(left_block) % 2\n", + "\n", + " # Asking for the correct parity of the left block. The\n", + " # parity of the right block can be inferred from the left\n", + " # block's parity.\n", + " correct_left_block_parity = ask_parity_fn(left_block_indices)\n", + "\n", + " # Determining the error parity for the left block.\n", + " left_block_error_parity = current_left_block_parity ^ correct_left_block_parity\n", + "\n", + " # Recursing on the block with odd error parity.\n", + " if left_block_error_parity == 1:\n", + " left_block = binary_algorithm(left_block, left_block_indices, ask_parity_fn)\n", + " else:\n", + " right_block = binary_algorithm(right_block, right_block_indices, ask_parity_fn)\n", + "\n", + " return np.concatenate((left_block, right_block))\n", + "\n", + "def client_cascade(noisy_key, qber, ask_parity_fn):\n", + " \"\"\"\n", + " An implementation of the Cascade information reconciliation algorithm\n", + " used for post-processing of keys exchanged via quantum key distribution.\n", + " \"\"\"\n", + "\n", + " # Representing the noisy key as a NumPy array, if it isn't already.\n", + " noisy_key = np.array(noisy_key)\n", + "\n", + " key_length = len(noisy_key)\n", + "\n", + " # If the estimated quantum bit error rate is 0%, assume that a reasonable\n", + " # amount of errors were present outside of the sampling set.\n", + " if qber == 0.0:\n", + " qber = 0.1\n", + "\n", + " # The top level block size is determined by the quantum bit error rate.\n", + " block_size = int(np.round(0.73 / qber))\n", + "\n", + " iteration = 0\n", + "\n", + " while block_size <= key_length:\n", + " # The identity permutation is used for the first iteration.\n", + " permutation = np.arange(key_length)\n", + "\n", + " if iteration > 0:\n", + " # Randomly shuffle Bob's key.\n", + " rng = np.random.default_rng()\n", + " permutation = rng.permutation(key_length)\n", + " shuffled_key = noisy_key[permutation]\n", + "\n", + " # Increasing block size for current iteration.\n", + " block_size *= 2\n", + " else:\n", + " # The key is not shuffled during the first iteration.\n", + " shuffled_key = noisy_key.copy()\n", + "\n", + " num_blocks = int(np.ceil(key_length / block_size))\n", + "\n", + " for block_index in range(num_blocks):\n", + " block = None\n", + " block_indices = None\n", + "\n", + " block_start = block_size * block_index\n", + "\n", + " if block_index < num_blocks - 1:\n", + " block_end = block_size * (block_index + 1)\n", + "\n", + " block = shuffled_key[block_start:block_end]\n", + " block_indices = permutation[block_start:block_end]\n", + " else:\n", + " # The final block is not guaranteed to have the exact block size.\n", + " block = shuffled_key[block_start:]\n", + " block_indices = permutation[block_start:]\n", + "\n", + " # Computing current block parity.\n", + " current_block_parity = np.sum(block) % 2\n", + "\n", + " # Requesting correct block parity.\n", + " correct_block_parity = ask_parity_fn(block_indices)\n", + "\n", + " # Determining error parity.\n", + " error_parity = current_block_parity ^ correct_block_parity\n", + "\n", + " # Correcting one-bit errors for blocks with odd error parity.\n", + " if error_parity == 1:\n", + " updated_block = binary_algorithm(block, block_indices, ask_parity_fn)\n", + " noisy_key[block_indices] = updated_block\n", + "\n", + " iteration += 1\n", + "\n", + " return noisy_key\n", + "\n", + "def get_ask_block_parity_fn(secret_key, socket):\n", + " \"\"\"\n", + " Returns a function for requesting block parities that is compatible\n", + " with the signature expected by client_cascade, but which communicates\n", + " over a NetQasm socket.\n", + " \"\"\"\n", + "\n", + " secret_key = np.array(secret_key)\n", + "\n", + " def ask_block_parity(block_indices):\n", + " request = \",\".join([str(b) for b in list(block_indices)])\n", + "\n", + " socket.send(request)\n", + "\n", + " response = socket.recv()\n", + "\n", + " return int(response)\n", + "\n", + " return ask_block_parity\n", + "\n", + "def get_block_parity_from_indices(full_key, indices):\n", + " \"\"\"\n", + " Returns the parity of a subset of a key using indices.\n", + " \"\"\"\n", + " element_sum = 0\n", + " for i in indices:\n", + " element_sum += full_key[i]\n", + " return element_sum % 2\n", + "\n", + "def send_cascade_stop(socket):\n", + " socket.send(\"STOP\")\n", + "\n", + "def listen_and_respond_block_parity(correct_key, socket):\n", + " \"\"\"\n", + " Listens for block parity questions and responds.\n", + " \"\"\"\n", + " question = socket.recv()\n", + "\n", + " while question != \"STOP\":\n", + " block_indices = [int(s) for s in question.split(\",\")]\n", + " correct_parity = get_block_parity_from_indices(\n", + " correct_key,\n", + " block_indices,\n", + " )\n", + " socket.send(str(correct_parity))\n", + " question = socket.recv()" ] }, { @@ -92,23 +314,23 @@ "source": [ "# this will be action of the node whether its a receiver or a sender\n", "class all_func:\n", - " def __init__(self, sender, receiver, n=16):\n", + " def __init__(self, sender, receiver, n=10):\n", " self.sender = sender\n", " self.receiver = receiver\n", " self.n = n\n", "\n", - " self.bit_flips = [None for _ in range(n)]\n", - " self.basis_flips = [random.randint(0, 1) for _ in range(n)]\n", - " self.num_test_bits = max(n // 4, 1)\n", - " \n", - " def distribute_bb84_states(self, conn, epr_socket, receiver=False):\n", + " self.bit_flips = [None for _ in range(self.n)]\n", + " self.basis_flips = [random.randint(0, 1) for _ in range(self.n)]\n", + " self.num_test_bits = max(self.n // 4, 1)\n", + "\n", + " def distribute_bb84_states(self, conn, epr_socket, sender=False):\n", " for i in range(self.n):\n", " # Note that we will need to inlcude other things like bsm node, so this is where everything is differnet\n", - " if receiver == False:\n", + " if sender:\n", " qc = epr_socket.create_epr(num_qubit=1)\n", " else:\n", " qc = epr_socket.receive_epr(num_qubit=1)\n", - " \n", + "\n", " if self.basis_flips[i]:\n", " qc.h(0)\n", " # todo: maybe include different measurement or settings\n", @@ -118,8 +340,15 @@ " self.bit_flips[i] = int(m)\n", " return self.bit_flips, self.basis_flips\n", " \n", - " def estimate_error_rate(self,socket,key,start = None, end = None, receiver = False):\n", - " if receiver:\n", + " def estimate_error_rate(self,socket,key,start = None, end = None, sender = True):\n", + " if sender:\n", + " test_outcomes = key[start:end]\n", + " test_indices = start,end\n", + "\n", + " socket.send_structured(StructuredMessage(\"Test indices\", test_indices))\n", + " target_test_outcomes = socket.recv_structured().payload\n", + " socket.send_structured(StructuredMessage(\"Test outcomes\", test_outcomes))\n", + " else:\n", " test_indices = socket.recv_structured().payload\n", " start,end = test_indices\n", " test_outcomes = key[start:end]\n", @@ -129,13 +358,6 @@ "\n", " socket.send_structured(StructuredMessage(\"Test outcomes\", test_outcomes))\n", " target_test_outcomes = socket.recv_structured().payload\n", - " else:\n", - " test_outcomes = key[start:end]\n", - " test_indices = start,end\n", - "\n", - " socket.send_structured(StructuredMessage(\"Test indices\", test_indices))\n", - " target_test_outcomes = socket.recv_structured().payload\n", - " socket.send_structured(StructuredMessage(\"Test outcomes\", test_outcomes))\n", "\n", " num_error = 0\n", " for (i1, i2) in zip(test_outcomes, target_test_outcomes):\n", @@ -150,10 +372,13 @@ " bit_flips, basis_flips = self.distribute_bb84_states(\n", " self.sender, channels.quantum(receiver = self.receiver)\n", " )\n", + "\n", " #logger.info(f\"sender outcomes: {bit_flips}\")\n", " #logger.info(f\"sender theta: {basis_flips}\")\n", + " \n", " socket = channels.classical(sender = self.sender, receiver = self.receiver)\n", - " error_rate = self.estimate_error_rate(socket,bit_flips, 0, 6)\n", + " error_rate = self.estimate_error_rate(socket,bit_flips, start, end)\n", + "\n", " socket.send('1' if error_rate<=0.0 else '0')\n", " return {\n", " \"error_rate\" : error_rate,\n", @@ -162,12 +387,15 @@ "\n", " def start_receiver(self):\n", " bit_flips, basis_flips = self.distribute_bb84_states(\n", - " self.receiver, channels.quantum(sender = self.receiver), receriver = True\n", + " self.receiver, channels.quantum(sender = self.receiver), sender = False\n", " )\n", + "\n", " #logger.info(f\"receiver outcomes: {bit_flips}\")\n", " #logger.info(f\"receiver theta: {basis_flips}\")\n", + "\n", " socket = channels.classical(sender = self.sender, receiver = self.receiver)\n", - " error_rate = self.estimate_error_rate(socket,bit_flips, receiver = True)\n", + " error_rate = self.estimate_error_rate(socket,bit_flips, sender = False)\n", + " \n", " accept_string = socket.recv()\n", " accept_key = True if accept_string == '1' else False\n", " return {\n", @@ -187,13 +415,18 @@ "source": [ "# this will insert in the middle of circuit\n", "def eve(qc):\n", + " result = []\n", " key_string = random.randint(0, 1)\n", " if key_string == 0:\n", " qc.x(0)\n", " elif key_string == 1:\n", " qc.h(0)\n", " # qiskit dynamic circuit here\n", - " qc.run_measure()" + " result.append(qc.run_measure(0,0))\n", + " Eve.get_classical(Alice, )\n", + " \n", + " \n", + " # todo: eve tracking classical channels" ] }, { @@ -206,6 +439,24 @@ "network.set_function(all_func, interception=eve)" ] }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Action timeline" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Alice.start_sender(timeline=timeline, receiver=Bob)\n", + "Bob.start_receiver(timeline=timeline, sender=Alice)" + ] + }, { "cell_type": "code", "execution_count": null, @@ -213,7 +464,15 @@ "outputs": [], "source": [ "# how the overall circuit look like\n", - "display(network.construct_circuit().draw())" + "network.construct_circuit().draw('mpl')" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### start timeline" ] }, { @@ -222,7 +481,8 @@ "metadata": {}, "outputs": [], "source": [ - "result = network.run()\n", + "result = network.run(run_time=False)\n", + "print(result)\n", "# sample output:\n", "\"\"\"\n", "{\"Alice\": {\"error_rate\": 6.2, \"secret_key\": xxxxxx}, \"Bob\": {\"error_rate\": 6.2, \"secret_key\": xxxxxx, \"accept\" : 0} }\n", @@ -235,8 +495,17 @@ "metadata": {}, "outputs": [], "source": [ - "# visualization or maybe \n", - "result" + "#result.logger" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# visualization the timeline and result with widget, like\n", + "#result" ] }, { From 037fbff76d15f8c79ab9558684395a42fdf48732 Mon Sep 17 00:00:00 2001 From: poig <43441314+poig@users.noreply.github.com> Date: Sat, 17 Dec 2022 02:46:24 +0800 Subject: [PATCH 5/6] change conflic --- docs/demo/demo_bb84.ipynb | 2 +- docs/tutorial/1 different Node.ipynb | 30 ++++++++ src/channels/channel.py | 46 +++++++++++ src/channels/classical_channel.py | 8 -- src/channels/quantum_channel.py | 5 -- src/components/node.py | 111 +++++++++++++++------------ 6 files changed, 138 insertions(+), 64 deletions(-) create mode 100644 docs/tutorial/1 different Node.ipynb create mode 100644 src/channels/channel.py delete mode 100644 src/channels/classical_channel.py delete mode 100644 src/channels/quantum_channel.py diff --git a/docs/demo/demo_bb84.ipynb b/docs/demo/demo_bb84.ipynb index 4e85070..8346792 100644 --- a/docs/demo/demo_bb84.ipynb +++ b/docs/demo/demo_bb84.ipynb @@ -547,7 +547,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.5" + "version": "3.8.5 (default, Sep 3 2020, 21:29:08) [MSC v.1916 64 bit (AMD64)]" }, "orig_nbformat": 4, "vscode": { diff --git a/docs/tutorial/1 different Node.ipynb b/docs/tutorial/1 different Node.ipynb new file mode 100644 index 0000000..a5e12ba --- /dev/null +++ b/docs/tutorial/1 different Node.ipynb @@ -0,0 +1,30 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.10.0 64-bit", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.10.0" + }, + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "0cc6fef2abb65beb60a121de5f4f89280da74c656e642b572c90966ff10b4cc0" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/src/channels/channel.py b/src/channels/channel.py new file mode 100644 index 0000000..8a200c6 --- /dev/null +++ b/src/channels/channel.py @@ -0,0 +1,46 @@ +#TODO: channel noise &/or distance dependecy + +class Channel: + """ + Object class for the empty Nodes. + """ + + def __init__(self, name: str, nodes: list, nature: str): + """ + Constructor of the channel. + :param name: the name of the channel. + :param connected nodes: name of nodes connected to the topology. + """ + + self.name = name + """ + Set the name of the Node. + """ + + self.nodes = nodes + """ + Set the nodes connected to the channel. + """ + + + def get_name(self) -> str: + + """ + Return the name of the Node. + """ + return self.name + + def get_nodes(self) -> list: + + """ + Return the name of all nodes connected to the channel. + """ + + return self.nodes + + def get_nature(self) -> str: + + """ + Return the nature of the channel: Quantum/Classic. + """ + return self.get_nature \ No newline at end of file diff --git a/src/channels/classical_channel.py b/src/channels/classical_channel.py deleted file mode 100644 index 5df03a9..0000000 --- a/src/channels/classical_channel.py +++ /dev/null @@ -1,8 +0,0 @@ - - - -class ClassicalChannel(object): - - def __init__(self, sender_id, receiver_id, model): - pass - diff --git a/src/channels/quantum_channel.py b/src/channels/quantum_channel.py deleted file mode 100644 index efd978f..0000000 --- a/src/channels/quantum_channel.py +++ /dev/null @@ -1,5 +0,0 @@ - -class QuantumChannel(object): - - def __init__(self, sender_id, receiver_id, model): - pass \ No newline at end of file diff --git a/src/components/node.py b/src/components/node.py index f96ee5e..e1e9f90 100644 --- a/src/components/node.py +++ b/src/components/node.py @@ -1,54 +1,65 @@ -#Placeholder for qiskit implementation of a node architecture -import itertools - -class Node(object): - id_obj = itertools.count() - def __init__(self,node_name, timeline): - #reference will be sequence/topology/node & QuNetSim/Component/host - #logger - #time_line, what time the node will be create - - #node_id - self.node_id = Node.id_obj # will replace to uuid4().int for future use in large network - #node_name - self.node_name = node_name #this is like - #node_classical_channel, we will use rustworkx, to speed up, also it may apply qiskit optimization in the future, for applying other related algorithm in qiskit ecosystem. - self.classical_channel = {} - #node_quantum_channel - self.quantum_channel = {} - #classical storage - self.classical_storage = None - #quantum storage - self.quantum_storage = None - #component, for future use for create other type of node - self.components = {} - - def add_connection(self): - """ - adding quantum_channel or classical_channel to the specific node - """ - pass - def delete_connection(self): - pass - - def send_message(self): - """ - how it receive classical message, will include in protocols.py - """ - pass - def receive_message(self): - """ - how it receive classical message, will include in protocols.py - """ - pass - -class CustomNode(): - """ - this will be custom circuit input as startup - """ +import numpy as np +import qiskit -class QKDNode(): +class Node: """ - Quantum Key Distribution Node + Object class for the empty Nodes. """ + def __init__(self, name: str, location: list, elements: list, ports: list): + """ + Constructor of the Node. + :param name: the name of the Node. + :param location: defined location of the Node within a topology. + :param elements: list of network elements within the Node. + :param ports: list of ports within the Node. + """ + + self.name = name + """ + Set the name of the Node. + """ + + self.location = location + """ + Set the location address of the Node. + """ + + + def get_name(self) -> str: + + """ + Return the name of the Node. + """ + return self.name + + def get_location(self) -> list: + + """ + Return the location address of the Node. + Should be described as a 2D or 3D array. + """ + + if len(self.location) == 2: + self.dimension_topology = 2 + elif len(self.location) == 3: + self.dimension_topology = 3 + else: + self.dimension_topology = np.nan + print("Node.location has an incorrect dimensionality. Please define the location through the use of a 2D or a 3D grid system.") + + return self.location + + def elements(self) -> list: + + """ + Return the list of element within the Node. + """ + return self.elements + + def ports(self) -> list: + + """ + Return ports within the Node. + """ + return self.ports \ No newline at end of file From e439afe4280cb6ee2a32ce98cf89c2acc1b9e09e Mon Sep 17 00:00:00 2001 From: poig <43441314+poig@users.noreply.github.com> Date: Sat, 17 Dec 2022 03:30:18 +0800 Subject: [PATCH 6/6] Update demo_bb84.ipynb --- docs/demo/demo_bb84.ipynb | 180 +------------------------------------- 1 file changed, 1 insertion(+), 179 deletions(-) diff --git a/docs/demo/demo_bb84.ipynb b/docs/demo/demo_bb84.ipynb index 8346792..dc0b818 100644 --- a/docs/demo/demo_bb84.ipynb +++ b/docs/demo/demo_bb84.ipynb @@ -125,185 +125,7 @@ "metadata": {}, "outputs": [], "source": [ - "#cascade, reference: https://github.com/upsideon/qkd-qchack-2022/blob/main/qkd/src/cascade.py\n", - "import numpy as np\n", - "\n", - "def quantum_bit_error_rate(local_set, remote_set):\n", - " \"\"\"\n", - " Estimates the quantum bit error rate based on two sets.\n", - " \"\"\"\n", - " num_incorrect = 0\n", - " for i in range(len(local_set)):\n", - " if remote_set[i] != local_set[i]:\n", - " num_incorrect += 1\n", - " return num_incorrect / len(local_set)\n", - "\n", - "def binary_algorithm(block, block_indices, ask_parity_fn):\n", - " \"\"\"\n", - " Recursively splits a block with odd error parity into\n", - " left and right sub-blocks to find and correct one-bit\n", - " errors.\n", - " \"\"\"\n", - "\n", - " # If we have a block of size one, we correct the bit as\n", - " # it must have an odd number of errors per the input\n", - " # assumptions of the binary algorithm.\n", - " if len(block) == 1:\n", - " if block[0] == 0:\n", - " block[0] = 1\n", - " else:\n", - " block[0] = 0\n", - " return block\n", - "\n", - " # The block split index selection ensures that the left\n", - " # block has one more bit than the right when the block\n", - " # size is odd.\n", - " block_split_index = (len(block) + 1) // 2\n", - "\n", - " left_block = block[:block_split_index]\n", - " right_block = block[block_split_index:]\n", - "\n", - " left_block_indices = block_indices[:block_split_index]\n", - " right_block_indices = block_indices[block_split_index:]\n", - "\n", - " # Computing the current parity of the left block.\n", - " current_left_block_parity = np.sum(left_block) % 2\n", - "\n", - " # Asking for the correct parity of the left block. The\n", - " # parity of the right block can be inferred from the left\n", - " # block's parity.\n", - " correct_left_block_parity = ask_parity_fn(left_block_indices)\n", - "\n", - " # Determining the error parity for the left block.\n", - " left_block_error_parity = current_left_block_parity ^ correct_left_block_parity\n", - "\n", - " # Recursing on the block with odd error parity.\n", - " if left_block_error_parity == 1:\n", - " left_block = binary_algorithm(left_block, left_block_indices, ask_parity_fn)\n", - " else:\n", - " right_block = binary_algorithm(right_block, right_block_indices, ask_parity_fn)\n", - "\n", - " return np.concatenate((left_block, right_block))\n", - "\n", - "def client_cascade(noisy_key, qber, ask_parity_fn):\n", - " \"\"\"\n", - " An implementation of the Cascade information reconciliation algorithm\n", - " used for post-processing of keys exchanged via quantum key distribution.\n", - " \"\"\"\n", - "\n", - " # Representing the noisy key as a NumPy array, if it isn't already.\n", - " noisy_key = np.array(noisy_key)\n", - "\n", - " key_length = len(noisy_key)\n", - "\n", - " # If the estimated quantum bit error rate is 0%, assume that a reasonable\n", - " # amount of errors were present outside of the sampling set.\n", - " if qber == 0.0:\n", - " qber = 0.1\n", - "\n", - " # The top level block size is determined by the quantum bit error rate.\n", - " block_size = int(np.round(0.73 / qber))\n", - "\n", - " iteration = 0\n", - "\n", - " while block_size <= key_length:\n", - " # The identity permutation is used for the first iteration.\n", - " permutation = np.arange(key_length)\n", - "\n", - " if iteration > 0:\n", - " # Randomly shuffle Bob's key.\n", - " rng = np.random.default_rng()\n", - " permutation = rng.permutation(key_length)\n", - " shuffled_key = noisy_key[permutation]\n", - "\n", - " # Increasing block size for current iteration.\n", - " block_size *= 2\n", - " else:\n", - " # The key is not shuffled during the first iteration.\n", - " shuffled_key = noisy_key.copy()\n", - "\n", - " num_blocks = int(np.ceil(key_length / block_size))\n", - "\n", - " for block_index in range(num_blocks):\n", - " block = None\n", - " block_indices = None\n", - "\n", - " block_start = block_size * block_index\n", - "\n", - " if block_index < num_blocks - 1:\n", - " block_end = block_size * (block_index + 1)\n", - "\n", - " block = shuffled_key[block_start:block_end]\n", - " block_indices = permutation[block_start:block_end]\n", - " else:\n", - " # The final block is not guaranteed to have the exact block size.\n", - " block = shuffled_key[block_start:]\n", - " block_indices = permutation[block_start:]\n", - "\n", - " # Computing current block parity.\n", - " current_block_parity = np.sum(block) % 2\n", - "\n", - " # Requesting correct block parity.\n", - " correct_block_parity = ask_parity_fn(block_indices)\n", - "\n", - " # Determining error parity.\n", - " error_parity = current_block_parity ^ correct_block_parity\n", - "\n", - " # Correcting one-bit errors for blocks with odd error parity.\n", - " if error_parity == 1:\n", - " updated_block = binary_algorithm(block, block_indices, ask_parity_fn)\n", - " noisy_key[block_indices] = updated_block\n", - "\n", - " iteration += 1\n", - "\n", - " return noisy_key\n", - "\n", - "def get_ask_block_parity_fn(secret_key, socket):\n", - " \"\"\"\n", - " Returns a function for requesting block parities that is compatible\n", - " with the signature expected by client_cascade, but which communicates\n", - " over a NetQasm socket.\n", - " \"\"\"\n", - "\n", - " secret_key = np.array(secret_key)\n", - "\n", - " def ask_block_parity(block_indices):\n", - " request = \",\".join([str(b) for b in list(block_indices)])\n", - "\n", - " socket.send(request)\n", - "\n", - " response = socket.recv()\n", - "\n", - " return int(response)\n", - "\n", - " return ask_block_parity\n", - "\n", - "def get_block_parity_from_indices(full_key, indices):\n", - " \"\"\"\n", - " Returns the parity of a subset of a key using indices.\n", - " \"\"\"\n", - " element_sum = 0\n", - " for i in indices:\n", - " element_sum += full_key[i]\n", - " return element_sum % 2\n", - "\n", - "def send_cascade_stop(socket):\n", - " socket.send(\"STOP\")\n", - "\n", - "def listen_and_respond_block_parity(correct_key, socket):\n", - " \"\"\"\n", - " Listens for block parity questions and responds.\n", - " \"\"\"\n", - " question = socket.recv()\n", - "\n", - " while question != \"STOP\":\n", - " block_indices = [int(s) for s in question.split(\",\")]\n", - " correct_parity = get_block_parity_from_indices(\n", - " correct_key,\n", - " block_indices,\n", - " )\n", - " socket.send(str(correct_parity))\n", - " question = socket.recv()" + "#cascade, reference: https://github.com/upsideon/qkd-qchack-2022/blob/main/qkd/src/cascade.py" ] }, {