From d32e1d4675bc33c0ccaae55b5df4cf8252928a6e Mon Sep 17 00:00:00 2001 From: larathi Date: Tue, 14 Feb 2023 17:14:45 +0530 Subject: [PATCH] Modified the requirements.txt file (as per Mac installation) and modified the templater to remove the backend choice. Added the kwargs argument for host.run_protocol Adding the bit_flip and phase_flip channel models Removed the commented libraries from requirements.txt Modified the template generation file Adding the channel model parameter Modified the test case for channel models and renamed channel_model file Added the function to send n-qubits and it's unit-test Added the method to initialize a qubit given angles theta and phi, tested only for the EQSN backend Added the classical storage in host Adding the channel_models documentation Made a new function for changing the host ID for the EPR qubit Changed the parameters for fibre channel in channel_models Changed the alignment Changed the alignment Made minor changes to the examples in the docs --- docs/source/components/channel_models.rst | 21 ++++ docs/source/components/network.rst | 6 - docs/source/examples/QKD_BB84.rst | 16 +-- docs/source/examples/entanglement_routing.rst | 1 + docs/source/examples/packet_sniffing.rst | 4 +- docs/source/examples/quantum_money.rst | 1 - docs/source/examples/send_epr.rst | 2 +- docs/source/examples/send_w.rst | 4 +- integration_tests/test_backend.py | 8 ++ integration_tests/test_host.py | 100 +++++++++++++++- qunetsim/components/host.py | 109 ++++++++++++++++-- .../connections/channel_models/__init__.py | 4 + .../channel_models/binary_erasure.py | 5 +- .../connections/channel_models/bit_flip.py | 58 ++++++++++ .../channel_models/channel_model.py | 8 ++ .../channel_models/classical_model.py | 5 +- .../connections/channel_models/fibre.py | 5 +- .../connections/channel_models/phase_flip.py | 57 +++++++++ .../connections/classical_connection.py | 5 +- qunetsim/objects/daemon_thread.py | 8 +- qunetsim/objects/qubit.py | 16 ++- requirements.txt | 11 +- templater.py | 12 +- 23 files changed, 411 insertions(+), 55 deletions(-) create mode 100644 docs/source/components/channel_models.rst create mode 100644 qunetsim/objects/connections/channel_models/bit_flip.py create mode 100644 qunetsim/objects/connections/channel_models/channel_model.py create mode 100644 qunetsim/objects/connections/channel_models/phase_flip.py diff --git a/docs/source/components/channel_models.rst b/docs/source/components/channel_models.rst new file mode 100644 index 00000000..9819e724 --- /dev/null +++ b/docs/source/components/channel_models.rst @@ -0,0 +1,21 @@ +Channel Models +======= + +Channels act as communication modes between the hosts in a network. Each host defines it's quantum and classical +connections with the other hosts in the network. QuNetSim introduces various channel models to mimic the real-world +quantum connections between the hosts. +The default channel model for quantum connections is fibre. + +QuNetSim implements the following channel models for quantum connections: + +* :code:`BitFlip(probability)` + * Flips the single-qubit (Pauli X) passing through the channel with certain probability +* :code:`PhaseFlip(probability)` + * Flips the phase of the single-qubit (Pauli Z) passing through the channel with certain probability +* :code:`BinaryErasure(probability)` + * Erases the single-qubit passing through the channel with certain probability +* :code:`Fibre(length, alpha)` + * Erases the single-qubit passing through the channel with certain probability determined from the length and absorption coefficient (alpha) of the channel + +.. automodule:: qunetsim.components.objects.connections.channel_models + :members: diff --git a/docs/source/components/network.rst b/docs/source/components/network.rst index 17b01d98..c2ca62c2 100644 --- a/docs/source/components/network.rst +++ b/docs/source/components/network.rst @@ -28,12 +28,6 @@ The most commonly used methods for Network are: * If the network should recalculate the route at each node in the route (set to True) or just once at the beginning (set to False) * :code:`(property) delay(float)` * the amount of delay the network should have. The network has the ability to throttle packet transmissions which is sometimes neccessary for different types of qubit / network backends. -* :code:`(property) packet_drop_rate(float)` - * The probability that a packet is dropped on transmission in the network -* :code:`(property) x_error_rate(float)` - * The probability that a qubit has an :math:`X` gate applied to in at each host in the route -* :code:`(property) z_error_rate(float)` - * The probability that a qubit has an :math:`Z` gate applied to in at each host in the route * :code:`draw_classical_network` * Generate a depiction of the classical network * :code:`draw_quantum_network` diff --git a/docs/source/examples/QKD_BB84.rst b/docs/source/examples/QKD_BB84.rst index 25f58566..6be8b815 100644 --- a/docs/source/examples/QKD_BB84.rst +++ b/docs/source/examples/QKD_BB84.rst @@ -94,10 +94,10 @@ the same bit again, until the transmission works. if base == 1: q_bit.H() - # Send Qubit to Bob + # Send Qubit to Eve alice.send_qubit(receiver, q_bit, await_ack=True) - # Get measured basis of Bob + # Get measured basis of Eve message = alice.get_next_classical_message(receiver, msg_buff, sequence_nr) # Compare to send basis, if same, answer with 0 and set ack True and go to next bit, @@ -130,7 +130,7 @@ the same bit again, until the transmission works. q_bit.H() bit = q_bit.measure() - # Send Alice the base in which Bob has measured + # Send Alice the base in which Eve has measured eve.send_classical(sender, "%d:%d" % (sequence_nr, measurement_base), await_ack=True) # get the return message from Alice, to know if the bases have matched @@ -211,7 +211,7 @@ We can now concatenate the two actions of Alice and Eve and let them each run in eve_key = eve_qkd(eve, msg_buff, key_size, host_alice.host_id) eve_receive_message(eve, msg_buff, eve_key, host_alice.host_id) - # Run Bob and Alice + # Run Eve and Alice t1 = host_alice.run_protocol(alice_func, ()) t2 = host_eve.run_protocol(eve_func, ()) @@ -275,10 +275,10 @@ The full example is below: if base == 1: q_bit.H() - # Send Qubit to Bob + # Send Qubit to Eve alice.send_qubit(receiver, q_bit, await_ack=True) - # Get measured basis of Bob + # Get measured basis of Eve message = alice.get_next_classical_message(receiver, msg_buff, sequence_nr) # Compare to send basis, if same, answer with 0 and set ack True and go to next bit, @@ -312,7 +312,7 @@ The full example is below: q_bit.H() bit = q_bit.measure() - # Send Alice the base in which Bob has measured + # Send Alice the base in which Eve has measured eve.send_classical(sender, "%d:%d" % (sequence_nr, measurement_base), await_ack=True) # get the return message from Alice, to know if the bases have matched @@ -404,7 +404,7 @@ The full example is below: eve_key = eve_qkd(eve, msg_buff, key_size, host_alice.host_id) eve_receive_message(eve, msg_buff, eve_key, host_alice.host_id) - # Run Bob and Alice + # Run Eve and Alice t1 = host_alice.run_protocol(alice_func, ()) t2 = host_eve.run_protocol(eve_func, ()) diff --git a/docs/source/examples/entanglement_routing.rst b/docs/source/examples/entanglement_routing.rst index 9b7edefa..0cdadcfe 100644 --- a/docs/source/examples/entanglement_routing.rst +++ b/docs/source/examples/entanglement_routing.rst @@ -139,6 +139,7 @@ The full example is below. from qunetsim.objects import Logger import networkx import time + import random def generate_entanglement(host): diff --git a/docs/source/examples/packet_sniffing.rst b/docs/source/examples/packet_sniffing.rst index 08b92786..ac37825c 100644 --- a/docs/source/examples/packet_sniffing.rst +++ b/docs/source/examples/packet_sniffing.rst @@ -85,7 +85,7 @@ We set these protocols to the hosts via the following code: :linenos: host_bob.q_relay_sniffing = True - host_bob.q_relay_sniffing_fn = eve_sniffing_quantum + host_bob.q_relay_sniffing_fn = bob_sniffing_quantum host_bob.c_relay_sniffing = True host_bob.c_relay_sniffing_fn = bob_sniffing_classical @@ -196,7 +196,7 @@ The full example is below. network.add_host(host_eve) host_bob.q_relay_sniffing = True - host_bob.q_relay_sniffing_fn = eve_sniffing_quantum + host_bob.q_relay_sniffing_fn = bob_sniffing_quantum host_bob.c_relay_sniffing = True host_bob.c_relay_sniffing_fn = bob_sniffing_classical diff --git a/docs/source/examples/quantum_money.rst b/docs/source/examples/quantum_money.rst index 8ccbfae1..33bb3ef5 100644 --- a/docs/source/examples/quantum_money.rst +++ b/docs/source/examples/quantum_money.rst @@ -189,7 +189,6 @@ The full example is below: from qunetsim.objects import Logger from qunetsim.objects import Qubit from random import randint, random - from qunetsim.backends import ProjectQBackend Logger.DISABLED = True diff --git a/docs/source/examples/send_epr.rst b/docs/source/examples/send_epr.rst index 1a75d95f..669b63f6 100644 --- a/docs/source/examples/send_epr.rst +++ b/docs/source/examples/send_epr.rst @@ -1,7 +1,7 @@ Send EPR Pairs -------------- -In this example, we'll see how to generate a network with 4 nodes as in the figure below. +In this example, we'll see how to generate a network with 3 nodes connected in a linear fashion. We'll then send an EPR pair from one end of the link to the other. The example shows how to build a network and a simple application. diff --git a/docs/source/examples/send_w.rst b/docs/source/examples/send_w.rst index 04cfbd62..00294d73 100644 --- a/docs/source/examples/send_w.rst +++ b/docs/source/examples/send_w.rst @@ -87,7 +87,7 @@ then the system density matrix is printed and a measurement on each participant q4 = host_dean.get_w('Alice', q_id1, wait=10) print("System density matrix:") - print(q1._qubit[0].data) + print(q1.density_operator()) m1 = q1.measure() m2 = q2.measure() @@ -154,7 +154,7 @@ The full example is below: q4 = host_dean.get_w('Alice', q_id1, wait=10) print("System density matrix:") - print(q1._qubit[0].data) + print(q1.density_operator()) m1 = q1.measure() m2 = q2.measure() diff --git a/integration_tests/test_backend.py b/integration_tests/test_backend.py index ac79b130..b84fba12 100644 --- a/integration_tests/test_backend.py +++ b/integration_tests/test_backend.py @@ -25,6 +25,14 @@ def setUpClass(cls): def tearDownClass(cls): pass + def test_qubit_initialization(self): + backend = EQSNBackend() + host_alice = Host('Alice', backend) + qubit_initialized = Qubit(host_alice, theta=np.pi/2, phi=np.pi/2) + + self.assertAlmostEqual(host_alice.backend.eqsn.give_statevector_for(qubit_initialized.qubit)[1][0], 0.5-0.5j) + self.assertAlmostEqual(host_alice.backend.eqsn.give_statevector_for(qubit_initialized.qubit)[1][1], 0.5+0.5j) + # @unittest.skip('') def test_epr_generation(self): for b in TestBackend.backends: diff --git a/integration_tests/test_host.py b/integration_tests/test_host.py index 991172cf..9c8019a7 100644 --- a/integration_tests/test_host.py +++ b/integration_tests/test_host.py @@ -1,9 +1,11 @@ import unittest +import numpy as np from qunetsim.objects import Qubit from qunetsim.components import Host from random import randint - +from qunetsim.components.network import Network +from qunetsim.objects.connections.channel_models import BitFlip, ClassicalModel, PhaseFlip, BinaryErasure, Fibre # @unittest.skip('') class TestHost(unittest.TestCase): @@ -180,6 +182,24 @@ def test_add_connections(self): self.assertEqual(len(a.classical_connections), 6) self.assertEqual(len(a.quantum_connections), 6) + def test_connection_models(self): + a = Host('A') + + a.add_c_connection('B', ClassicalModel()) + self.assertEqual(a.classical_connections['B'].model.type, "Classical") + with self.assertRaises(Exception): + a.add_c_connection('C', BitFlip()) + self.assertEqual(len(a.classical_connections), 1) + + a.add_q_connection('D', BitFlip(0.5)) + self.assertEqual(a.quantum_connections['D'].model.type, "Quantum") + a.add_q_connection('E', PhaseFlip(0.1)) + self.assertEqual(a.quantum_connections['E'].model.type, "Quantum") + a.add_q_connection('F', BinaryErasure(0.2)) + self.assertEqual(a.quantum_connections['F'].model.type, "Quantum") + a.add_q_connection('G', Fibre(alpha=0.3)) + self.assertEqual(a.quantum_connections['G'].model.type, "Quantum") + def test_remove_connections(self): a = Host('A') a.add_connections(['B', 'C']) @@ -197,3 +217,81 @@ def test_remove_connections(self): a.remove_connection('C') self.assertEqual(len(a.classical_connections), 0) self.assertEqual(len(a.quantum_connections), 0) + + def test_kwargs_runprotocol(self): + def bob_do(host, sender): + q = host.get_qubit(sender.host_id, wait=10) + self.assertNotEqual(q, None) + self.assertAlmostEqual(host.backend.eqsn.give_statevector_for(q.qubit)[1][0], 0.5-0.5j) + + def alice_do(host, receiver, theta, phi): + q = Qubit(host) + q.ry(theta) + q.rz(phi) + _, ack = host.send_qubit(receiver.host_id, q, await_ack=True) + + network = Network.get_instance() + nodes = ['Alice', 'Bob'] + network.start(nodes) + + host_alice = Host('Alice') + host_alice.add_connection('Bob') + host_alice.start() + + host_bob = Host('Bob') + host_bob.add_connection('Alice') + host_bob.start() + + network.add_host(host_alice) + network.add_host(host_bob) + network.start() + + t1 = host_alice.run_protocol(alice_do, kwargs={'theta':np.pi/2, 'receiver':host_bob, 'phi':np.pi/2}) + t2 = host_bob.run_protocol(bob_do, kwargs={'sender':host_alice}, blocking=True) + t1.join() + + s1 = host_alice.run_protocol(alice_do, (host_bob, np.pi/2, np.pi/2)) + s2 = host_bob.run_protocol(bob_do, (host_alice,), blocking=True) + s1.join() + + def test_send_n_qubits(self): + def sender_do(host, receiver, num_qubits, recv_ack=False): + global q_id_list + if recv_ack: + q_id_list, ack_arr = host.send_qubits(receiver.host_id, host.make_list_n_qubits(num_qubits), await_ack=recv_ack) + self.assertEqual(len(ack_arr), num_qubits) + else: + q_id_list = host.send_qubits(receiver.host_id, host.make_list_n_qubits(num_qubits), await_ack=recv_ack) + self.assertEqual(len(q_id_list), num_qubits) + + def receiver_do(host, sender, num_qubits): + global q_id_received + q_id_received = [] + while len(q_id_received) < num_qubits: + qubit_recv = host.get_qubit(sender.host_id, wait=10) + if qubit_recv is not None: + q_id_received.append(qubit_recv.id) + self.assertEqual(len(q_id_received), num_qubits) + + network = Network.get_instance() + nodes = ['sender', 'receiver'] + network.start(nodes) + + host_sender = Host('sender') + host_sender.add_connection('receiver') + host_sender.start() + + host_receiver = Host('receiver') + host_receiver.add_connection('sender') + host_receiver.start() + + network.add_host(host_sender) + network.add_host(host_receiver) + network.start() + + host_sender.run_protocol(sender_do, kwargs={'num_qubits':5, 'receiver': host_receiver, 'recv_ack': True}, blocking=True) + host_receiver.run_protocol(receiver_do, kwargs={'num_qubits':5, 'sender': host_sender}, blocking=True) + + network.stop(True) + self.assertEqual(set(q_id_list), set(q_id_received)) + \ No newline at end of file diff --git a/qunetsim/components/host.py b/qunetsim/components/host.py index fca2a862..4f4e5aca 100644 --- a/qunetsim/components/host.py +++ b/qunetsim/components/host.py @@ -34,6 +34,7 @@ def __init__(self, host_id, backend=None): self._classical_messages = ClassicalStorage() self._classical_connections = {} self._quantum_connections = {} + self._storage = {} if backend is None: self._backend = EQSNBackend() else: @@ -96,6 +97,34 @@ def classical(self): (list): Sorted list of classical messages. """ return sorted(self._classical_messages.get_all(), key=lambda x: x.seq_num, reverse=True) + + def add_in_storage(self, key, value): + """ + Add a key value pair in the classical storage of the host + + Args: + key (string): Description of data being stored + value (any): Value of data being stored + """ + self._storage[key] = value + + def retrieve_from_storage(self, key): + """ + Returns the value of the key from the classical storage + + Args: + key (string): Description of data to be retrieved + + Raises: + Exception: if key is not found in the dictionary + + Returns: + any: Returns value of the stored data + """ + if key in self._storage: + return self._storage[key] + else: + raise Exception("Given key not found in the data storage") def empty_classical(self, reset_seq_nums=False): """ @@ -562,16 +591,16 @@ def rec_packet(self, packet): """ self._packet_queue.put(packet) - def add_c_connection(self, receiver_id): + def add_c_connection(self, receiver_id, model=None): """ Adds the classical connection to host with ID *receiver_id*. Args: receiver_id (str): The ID of the host to connect with. """ - self.classical_connections[receiver_id] = ClassicalConnection(self.host_id, receiver_id) + self.classical_connections[receiver_id] = ClassicalConnection(self.host_id, receiver_id, model) - def add_c_connections(self, receiver_ids): + def add_c_connections(self, receiver_ids, model=None): """ Adds the classical connections to host with ID *receiver_id*. @@ -579,18 +608,18 @@ def add_c_connections(self, receiver_ids): receiver_ids (list): The IDs of the hosts to connect with. """ for receiver_id in receiver_ids: - self.classical_connections[receiver_id] = ClassicalConnection(self.host_id, receiver_id) + self.classical_connections[receiver_id] = ClassicalConnection(self.host_id, receiver_id, model) - def add_q_connection(self, receiver_id): + def add_q_connection(self, receiver_id, model=None): """ Adds the quantum connection to host with ID *receiver_id*. Args: receiver_id (str): The ID of the host to connect with. """ - self.quantum_connections[receiver_id] = QuantumConnection(self.host_id, receiver_id) + self.quantum_connections[receiver_id] = QuantumConnection(self.host_id, receiver_id, model) - def add_q_connections(self, receiver_ids): + def add_q_connections(self, receiver_ids, model=None): """ Adds the quantum connection to host with ID *receiver_id*. @@ -598,7 +627,7 @@ def add_q_connections(self, receiver_ids): receiver_ids (list): The IDs of the hosts to connect with. """ for receiver_id in receiver_ids: - self.quantum_connections[receiver_id] = QuantumConnection(self.host_id, receiver_id) + self.quantum_connections[receiver_id] = QuantumConnection(self.host_id, receiver_id, model) def add_connection(self, receiver_id): """ @@ -1167,7 +1196,48 @@ def send_qubit(self, receiver_id, q, await_ack=False, no_ack=False): self._log_ack('SEND QUBIT', receiver_id, packet.seq_num) return q_id, self.await_ack(packet.seq_num, receiver_id) return q_id + + def make_list_n_qubits(self, num_qubits): + """ + Makes a list of length *num_qubits*, all initialized to the zero state + + Args: + num_qubits (int): Number of qubits in the list + Returns: + list: List of all qubits initialized to the zero state + """ + list_qubits = [] + for _ in range(num_qubits): + q = Qubit(self) + list_qubits.append(q) + return list_qubits + + def send_qubits(self, receiver_id, q_list, await_ack=False, no_ack=False): + """ + Send the list of qubits *q_list* to the receiver with ID *receiver_id* + + Args: + receiver_id (str): The receiver ID to send the message to + q_list (list): List of qubits to be sent + await_ack (bool, optional): If sender should send wait for an ACK for each qubit. Defaults to False. + no_ack (bool, optional): If this message should not use any ACK and sequencing. Defaults to False. + """ + if await_ack: + list_ack = [] + q_ids_list = [] + for q in q_list: + if await_ack: + q_id, ack_recv = self.send_qubit(receiver_id, q, await_ack, no_ack) + q_ids_list.append(q_id) + list_ack.append(ack_recv) + else: + q_id = self.send_qubit(receiver_id, q, await_ack, no_ack) + q_ids_list.append(q_id) + if await_ack: + return q_ids_list, list_ack + return q_ids_list + def shares_epr(self, receiver_id): """ Returns boolean value dependent on if the host shares an EPR pair @@ -1195,6 +1265,17 @@ def change_epr_qubit_id(self, host_id, new_id, old_id=None): (str): Old if of the qubit which has been changed. """ return self._qubit_storage.change_qubit_id(host_id, new_id, old_id) + + def change_epr_host(self, host_id, new_host_id): + """ + Changes the host id of the EPR qubit + + Args: + host_id (str): ID of the current EPR host + new_host_id (str): ID of the new host for the EPR pair + """ + epr_qubit = self.get_epr(host_id) + self.add_epr(new_host_id, epr_qubit) def get_epr_pairs(self, host_id): """ @@ -1523,7 +1604,7 @@ def start(self): """ self._queue_processor_thread = DaemonThread(target=self._process_queue) - def run_protocol(self, protocol, arguments=(), blocking=False): + def run_protocol(self, protocol, args=None, kwargs=None, blocking=False): """ Run the protocol *protocol*. @@ -1535,11 +1616,15 @@ def run_protocol(self, protocol, arguments=(), blocking=False): Returns: (DaemonThread): The thread the protocol is running on """ - arguments = (self,) + arguments + if args is not None: + args = (self,) + args + else: + args = (self,) + if blocking: - DaemonThread(protocol, args=arguments).join() + DaemonThread(protocol, args=args, kwargs=kwargs).join() else: - return DaemonThread(protocol, args=arguments) + return DaemonThread(protocol, args=args, kwargs=kwargs) def get_qubit_by_id(self, q_id): """ diff --git a/qunetsim/objects/connections/channel_models/__init__.py b/qunetsim/objects/connections/channel_models/__init__.py index 348af7ae..6acb09d0 100644 --- a/qunetsim/objects/connections/channel_models/__init__.py +++ b/qunetsim/objects/connections/channel_models/__init__.py @@ -1,2 +1,6 @@ from .fibre import Fibre from .binary_erasure import BinaryErasure +from .bit_flip import BitFlip +from .phase_flip import PhaseFlip +from .channel_model import ChannelModel +from .classical_model import ClassicalModel diff --git a/qunetsim/objects/connections/channel_models/binary_erasure.py b/qunetsim/objects/connections/channel_models/binary_erasure.py index 8631cd1c..b685825a 100644 --- a/qunetsim/objects/connections/channel_models/binary_erasure.py +++ b/qunetsim/objects/connections/channel_models/binary_erasure.py @@ -1,12 +1,13 @@ import random +from qunetsim.objects.connections.channel_models.channel_model import ChannelModel - -class BinaryErasure(object): +class BinaryErasure(ChannelModel): """ The model for a binary erasure connection. """ def __init__(self, probability=0.0): + super().__init__(ChannelModel.QUANTUM) if not isinstance(probability, int) and not isinstance(probability, float): raise ValueError("Erasure probability must be float or int") elif probability < 0 or probability > 1: diff --git a/qunetsim/objects/connections/channel_models/bit_flip.py b/qunetsim/objects/connections/channel_models/bit_flip.py new file mode 100644 index 00000000..a207a14b --- /dev/null +++ b/qunetsim/objects/connections/channel_models/bit_flip.py @@ -0,0 +1,58 @@ +import random +from qunetsim.objects.connections.channel_models.channel_model import ChannelModel + +class BitFlip(ChannelModel): + """ + The model for a bit flip quantum connection. + """ + + def __init__(self, probability=0.0): + super().__init__(ChannelModel.QUANTUM) + if not isinstance(probability, int) and not isinstance(probability, float): + raise ValueError("Bit Flip probability must be float or int") + elif probability < 0 or probability > 1: + raise ValueError("Bit Flip probability must lie in the interval [0, 1]") + else: + self._p = probability + + @property + def bitflip_probability(self): + """ + Probability of bit flip of qubit + + Returns + (float) : Probability that a qubit is flipped during transmission + """ + return self._p + + @bitflip_probability.setter + def bitflip_probability(self, probability): + """ + Set the bit flip probability of the channel + + Args + probability (float) : Probability that a qubit is flipped during transmission + """ + if not isinstance(probability, int) and not isinstance(probability, float): + raise ValueError("Bit flip probability must be float or int") + elif probability < 0 or probability > 1: + raise ValueError("Bit flip probability must lie in the interval [0, 1]") + else: + self._p = probability + + def qubit_func(self, qubit): + """ + Function to modify the qubit based on channel properties + In this case - Returns flipped qubit, otherwise returns the original qubit + Required in all channel models + + Returns + (object) : Modified qubit + """ + if random.random() > (1.0 - self.bitflip_probability): + if qubit is not None: + qubit.X() + return qubit + else: + return qubit + \ No newline at end of file diff --git a/qunetsim/objects/connections/channel_models/channel_model.py b/qunetsim/objects/connections/channel_models/channel_model.py new file mode 100644 index 00000000..aea44eca --- /dev/null +++ b/qunetsim/objects/connections/channel_models/channel_model.py @@ -0,0 +1,8 @@ +class ChannelModel(object): + """ + A parent class for all the channel models + """ + QUANTUM = "Quantum" + CLASSICAL = "Classical" + def __init__(self, type) -> None: + self.type = type \ No newline at end of file diff --git a/qunetsim/objects/connections/channel_models/classical_model.py b/qunetsim/objects/connections/channel_models/classical_model.py index 63ee0a43..5f6b4f66 100644 --- a/qunetsim/objects/connections/channel_models/classical_model.py +++ b/qunetsim/objects/connections/channel_models/classical_model.py @@ -1,9 +1,12 @@ -class ClassicalModel(object): +from qunetsim.objects.connections.channel_models.channel_model import ChannelModel + +class ClassicalModel(ChannelModel): """ The model for a classical erasure channel. """ def __init__(self): + super().__init__(ChannelModel.CLASSICAL) self._length = 0 self._transmission_p = 1.0 diff --git a/qunetsim/objects/connections/channel_models/fibre.py b/qunetsim/objects/connections/channel_models/fibre.py index 956801bb..7b7dcd00 100644 --- a/qunetsim/objects/connections/channel_models/fibre.py +++ b/qunetsim/objects/connections/channel_models/fibre.py @@ -1,12 +1,13 @@ import random +from qunetsim.objects.connections.channel_models.channel_model import ChannelModel - -class Fibre(object): +class Fibre(ChannelModel): """ The model for a fibre optic connection. """ def __init__(self, length=0.0, alpha=0.0): + super().__init__(ChannelModel.QUANTUM) if not isinstance(length, int) and not isinstance(length, float): raise ValueError("Length must be float or int") elif length < 0: diff --git a/qunetsim/objects/connections/channel_models/phase_flip.py b/qunetsim/objects/connections/channel_models/phase_flip.py new file mode 100644 index 00000000..5d029b13 --- /dev/null +++ b/qunetsim/objects/connections/channel_models/phase_flip.py @@ -0,0 +1,57 @@ +import random +from qunetsim.objects.connections.channel_models.channel_model import ChannelModel + +class PhaseFlip(ChannelModel): + """ + The model for a phase flip quantum connection. + """ + + def __init__(self, probability=0.0): + super().__init__(ChannelModel.QUANTUM) + if not isinstance(probability, int) and not isinstance(probability, float): + raise ValueError("Phase Flip probability must be float or int") + elif probability < 0 or probability > 1: + raise ValueError("Phase Flip probability must lie in the interval [0, 1]") + else: + self._p = probability + + @property + def phaseflip_probability(self): + """ + Probability of phase flip of qubit + + Returns + (float) : Probability that a qubit's phase is flipped during transmission + """ + return self._p + + @phaseflip_probability.setter + def phaseflip_probability(self, probability): + """ + Set the phase flip probability of the channel + + Args + probability (float) : Probability that a qubit's phase is flipped during transmission + """ + if not isinstance(probability, int) and not isinstance(probability, float): + raise ValueError("Phase flip probability must be float or int") + elif probability < 0 or probability > 1: + raise ValueError("Phase flip probability must lie in the interval [0, 1]") + else: + self._p = probability + + def qubit_func(self, qubit): + """ + Function to modify the qubit based on channel properties + In this case - Returns qubit with phase flipped, otherwise returns the original qubit + Required in all channel models + + Returns + (object) : Modified qubit + """ + if random.random() > (1.0 - self.phaseflip_probability): + if qubit is not None: + qubit.Z() + return qubit + else: + return qubit diff --git a/qunetsim/objects/connections/classical_connection.py b/qunetsim/objects/connections/classical_connection.py index 70aaa7e0..26f21a7b 100644 --- a/qunetsim/objects/connections/classical_connection.py +++ b/qunetsim/objects/connections/classical_connection.py @@ -1,6 +1,7 @@ +import unittest from qunetsim.objects.connections.channel_models.classical_model import ClassicalModel from qunetsim.objects.connections.connection import Connection - +from .channel_models.channel_model import ChannelModel class ClassicalConnection(Connection): """ @@ -11,4 +12,6 @@ def __init__(self, sender_id, receiver_id, model=None): if model is None: super().__init__(sender_id, receiver_id, ClassicalModel()) else: + if model.type != ChannelModel.CLASSICAL: + raise Exception("Classical connection should have the model type - classical") super().__init__(sender_id, receiver_id, model) diff --git a/qunetsim/objects/daemon_thread.py b/qunetsim/objects/daemon_thread.py index e1f67df4..3d3c8138 100644 --- a/qunetsim/objects/daemon_thread.py +++ b/qunetsim/objects/daemon_thread.py @@ -4,9 +4,11 @@ class DaemonThread(threading.Thread): """ A Daemon thread that runs a task until completion and then exits. """ - def __init__(self, target, args=None): - if args is not None: - super().__init__(target=target, daemon=True, args=args) + def __init__(self, target, args=None, kwargs=None): + if kwargs is not None: + super().__init__(target=target, daemon=True, args=args, kwargs=kwargs) + elif args is not None: + super().__init__(target=target, daemon=True, args=args) else: super().__init__(target=target, daemon=True) self.start() diff --git a/qunetsim/objects/qubit.py b/qunetsim/objects/qubit.py index 2b0b8186..ec6b140b 100644 --- a/qunetsim/objects/qubit.py +++ b/qunetsim/objects/qubit.py @@ -14,7 +14,7 @@ class Qubit(object): GHZ_QUBIT = "GHZ" W_QUBIT = "W" - def __init__(self, host, qubit=None, q_id=None, blocked=False): + def __init__(self, host, qubit=None, q_id=None, blocked=False, theta=None, phi=None): self._blocked = blocked self._host = host if q_id is not None: @@ -25,6 +25,8 @@ def __init__(self, host, qubit=None, q_id=None, blocked=False): self._qubit = qubit else: self._qubit = self._host.backend.create_qubit(self._host.host_id) + if theta is not None and phi is not None: + self.initialize_qubit(theta, phi) @property def host(self): @@ -105,6 +107,18 @@ def blocked(self, state): state (bool): True for blocked, False if not. """ self._blocked = state + + def initialize_qubit(self, theta, phi): + """ + Initializes the qubit according to the angles + + Args: + theta (float): Rotation in radians around Y axis + phi (float): Rotation in radians around Z axis + """ + self._host.backend.ry(self, theta) + self._host.backend.rz(self, phi) + def send_to(self, receiver_id): """ diff --git a/requirements.txt b/requirements.txt index d1bbb560..3280230b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,7 @@ -matplotlib==3.1.2 -networkx==2.4 +matplotlib==3.7.0 +networkx<3.0 nose2==0.9.1 -numpy==1.22.0 -cqc==3.2.2 -simulaqron==3.0.15 +numpy==1.24.1 eqsn==0.0.8 -# qutip==4.5.2 -scipy==1.5.4 +scipy==1.10.0 pathvalidate==2.4.1 \ No newline at end of file diff --git a/templater.py b/templater.py index 738f0716..dd1cff8f 100644 --- a/templater.py +++ b/templater.py @@ -195,12 +195,14 @@ def gen_import_statements(backend_num: int) -> StringIO: imports.write("from qunetsim.components import Host, Network\n") imports.write("from qunetsim.objects import Message, Qubit, Logger\n") - imports.write("from qunetsim.backends import " + - backends[backend_num]['import'] + '\n') + if backend_num != 1: + imports.write("from qunetsim.backends import " + + backends[backend_num]['import'] + '\n') imports.write("Logger.DISABLED = True\n\n") - imports.write("# create the " + backends[backend_num]['name'] + - " backend object\n") - imports.write("backend = " + backends[backend_num]['import'] + "()\n\n\n") + if backend_num != 1: + imports.write("# create the " + backends[backend_num]['name'] + + " backend object\n") + imports.write("backend = " + backends[backend_num]['import'] + "()\n\n\n") imports.seek(0) return imports