def decrement_num_pairs(self, aid):
        """
        Decrements the remaining number of pairs of the request with the given absolute queue ID
        :param aid: tuple(int, int)
            The absolute queue ID
        :return: bool
        """
        try:
            queue_item = self.distQueue.local_peek(aid)
        except LinkLayerException as err:
            logger.warning(
                "Could not find queue item with aid = {}, when trying to decrement number of remaining pairs."
                .format(aid))
            raise err

        if queue_item.num_pairs_left > 1:
            logger.debug("Decrementing number of remaining pairs")
            queue_item.num_pairs_left -= 1

        elif queue_item.num_pairs_left == 1:
            logger.debug("Generated final pair, removing request")
            self.clear_request(aid=aid)
        else:
            raise LinkLayerException(
                "Current request with aid = {} has invalid number of remaining pairs."
                .format(aid))
    def _has_resources_for_gen(self, request):
        """
        Checks if we have the resources to service a generation request.
        :return: bool
            True/False whether we have resources
        """
        other_free_comm, other_free_storage = self.other_mem
        my_free_comm, my_free_storage = self.my_free_memory

        # Verify whether we have the resources to satisfy this request
        logger.debug("Checking if we can satisfy next gen")
        if not other_free_comm:
            logger.debug("Peer memory has no available communication qubits!")
            return False

        elif not my_free_comm > 0:
            logger.debug("Local memory has no available communication qubits!")
            return False

        if request.store and not request.measure_directly:
            if not other_free_storage:
                logger.debug(
                    "Requested storage but peer memory has no available storage qubits!"
                )
                return False

            elif not my_free_storage:
                logger.debug(
                    "Requested storage but local memory has no available storage qubits!"
                )
                return False

        return True
Example #3
0
    def produce_entanglement(self, sender):
        """
        Handler for a message requesting the production of entangled pairs. Only performs a swap
        if the heralding station has received a qubit from both peers.
        :param classical: obj any
            Classical data sent by the sender
        :param qubit: list `~netsquid.qubits.qubit.Qubit`
            The qubits sent by the sender
        :param sender: int
            NodeID of the sender of the information
        :return: None
        """
        logger.debug("Producing entanglement")
        qubit = self.node_requests[sender].quantum_data
        # Check if we have a qubit from other end of connection
        if self._has_both_qubits():
            # There is a qubit available from Bob already to swap with
            logger.debug(
                "Have qubits from both A and B with request for production")

            # Check the absolute queue id's from both ends of the connection
            if not self._has_same_aid():
                logger.warning("Absolute queue IDs don't match!")
                self._send_notification_to_both(self.ERR_QUEUE_MISMATCH)
                self._drop_qubit(qubit)
                self._reset_incoming()
                return
Example #4
0
    def _handle_cq(self, classical, qubit, sender):
        """
        Handles the Classical/Quantum messages from either end of the connection
        :param classical: obj any
            Classical data sent by sender
        :param qubit: list `~netsquid.qubits.qubit.Qubit`
            The qubits sent by the sender
        :param sender: int
            NodeID of the sender of the information
        :return: None
        """
        logger.debug(
            "Handling CQ from {}, got classical: {} and qubit {}".format(
                sender, classical, qubit))

        # Check whether we are in time window
        if not self._in_window and qubit:
            logger.warning("Received CQ out of detection time window")
            # Outside window, drop qubit
            self._drop_qubit(qubit)

            # Notify out of window error
            self._send_notification_to_one(self.ERR_OUT_OF_WINDOW, sender)
            self._reset_incoming()
            return

        if classical is not None:
            incoming_request = self._construct_request(sender, classical,
                                                       qubit)
            self._process_incoming_request(sender, incoming_request)

        else:
            if qubit is not None:
                self._drop_qubit(qubit)
    def pop(self):
        """
        Get item off the top of the queue, if it is ready to be scheduled. 
        If no item is available, return None
        """
        if len(self.queue) == 0:
            # No items on queue
            return None

        # Get item off queue
        q = self.queue[0]

        if q.ready:
            # Item ready

            # Remove from queue
            self.remove_item(q.seq)

            logger.debug("Removing item with seq={} to local queue".format(
                q.seq))

            if self.throw_events:
                logger.debug("Scheduling item added event now.")
                self._schedule_now(self._EVT_ITEM_REMOVED)
                self._seqs_removed.append(q.seq)

            # Return item
            return q
    def remove_item(self, seq):
        """
        Removes the queue item corresponding to the provided sequence number from the queue
        :param seq: int
            Identifier of the queue item we wish to remove
        :return: obj `~qlinklayer.localQueue.LocalQueueItem`
            The queue item that we removed if any, else None
        """
        if seq in self.sequence_to_item:
            item = self.sequence_to_item.pop(seq)
            self.queue.remove(item)
            logger.debug(
                "Removing item with seq={} from local queue".format(seq))

            if self.throw_events:
                logger.debug("Scheduling item removed event now.")
                self._schedule_now(self._EVT_ITEM_REMOVED)
                self._seqs_removed.append(seq)

            return item

        else:
            logger.warning(
                "Sequence number {} not found in local queue".format(seq))
            return None
Example #7
0
    def _transmit_photon(self, photon):
        """
        Handler for the photon emission that occurs in the underlying MHP protocol.  Sends the photon along
        with the corresponding absolute queue id for this generation attempt and free memory advertisement
        :param photon: obj `~netsquid.qubits.qubit.Qubit`
            The photon to send to the heralding station
        """
        try:
            # Construct the information to pass to our peer
            pass_info = self.aid
            self._previous_aid = self.aid
            logger.debug("Node {} : Sending pass info: {}".format(
                self.node.name, pass_info))

            # Send info to the heralding station
            self.send_msg(self.node.nodeID, self.conn.CMD_PRODUCE, pass_info,
                          photon)
            logger.debug("Scheduling entanglement event now.")
            self._schedule_now(self._EVT_ENTANGLE_ATTEMPT)

        except Exception:
            logger.exception("Error occurred while handling photon emission")
            result = self._construct_error_result(err_code=self.ERR_LOCAL,
                                                  err_data=self.aid)
            self._handle_error(None, result)

        finally:
            self.reset_protocol()
 def update_mhp_cycle_number(self, current_cycle, max_cycle):
     """
     Goes over the elements in the queue and checks if they are ready to be scheduled or have timed out.
     :return: None
     """
     logger.debug("Updating to MHP cycle {}".format(current_cycle))
     for q_item in self.queue:
         q_item.update_mhp_cycle_number(current_cycle, max_cycle)
Example #9
0
 def reset_protocol(self):
     """
     Resets the protocol to an unitialized state so that we don't produce entanglement
     :return:
     """
     logger.debug("{} Resetting MHP".format(self.node.nodeID))
     self.electron_physical_ID = 0
     self.storage_physical_ID = 0
     self.aid = None
Example #10
0
 def err_callback(self, result):
     """
     Handler for errors thrown by the EGP during the simulation.  Schedules an event for data collection
     :param result: tuple
         Result information containing the error
     """
     self._err_callback(result)
     logger.debug("Scheduling error event now.")
     self._schedule_now(self._EVT_ERR)
Example #11
0
 def ok_callback(self, result):
     """
     Handler for oks issued by the EGP containing generation result information.  Schedules an event for data
     collection
     :param result: tuple
         The result of our create request
     """
     self._ok_callback(result)
     logger.debug("Scheduling OK event now.")
     self._schedule_now(self._EVT_OK)
Example #12
0
 def _has_same_aid(self):
     """
     Checks if the last requests sent by each node contains the same absolute queue ID
     :return: bool
         Indicates whether (or not) the absolute queue IDs match
     """
     aid_A, aid_B = self.get_current_aids()
     logger.debug("Comparing absolute queue IDs {} and {}".format(
         aid_A, aid_B))
     return aid_A == aid_B
Example #13
0
 def free_qubits(self, id_list):
     """
     Releases the provided qubits from the memory device and marks their locations as not reserved in the
     memory manager
     :param id_list: list of int
         List of qubit ids in memory to free
     """
     logger.debug("Freeing qubits {}".format(id_list))
     for q in id_list:
         self.free_qubit(q)
 def _schedule_handler(self, evt):
     """
     Handler that is triggered when an item is ready to be scheduled, bubbles up to the distributed queue
     :param evt: obj `~netsquid,pydynaa.Event`
         The event that triggered the handler
     """
     logger.debug("Schedule handler triggered in local queue")
     logger.debug("Scheduling schedule event now.")
     queue_item = evt.source
     self.ready_items.append(queue_item)
     self._schedule_now(self._EVT_SCHEDULE)
 def prepare(self):
     """
     Sets up the timeout event into the future
     """
     if self.lifetime:
         if self.request.create_time is not None:
             start = self.request.create_time
         else:
             start = sim_time()
         deadline = start + self.lifetime
         logger.debug("Scheduling timeout event at {}.".format(deadline))
         self._schedule_at(deadline, self._EVT_TIMEOUT)
 def ack(self, seq):
     """
     Mark the queue item with queue sequence number seq as acknowledged by remote node
     """
     try:
         self.sequence_to_item[seq].acked = True
         logger.debug("Item with seq {} is acknowledged".format(seq))
     except KeyError:
         logger.warning(
             "Sequence number {} not found in local queue".format(seq))
         # Not in queue
         return
 def _handle_item_timeout(self, queue_item):
     """
     Timeout handler that is triggered when a queue item times out.  If the item has not been serviced yet
     then the stored request and it's information is removed.
     :param queue_item: obj `~qlinklayer.localQueue._LocalQueueItem`
         The local queue item
     """
     logger.debug("Handling item timeout")
     request = queue_item.request
     aid = queue_item.qid, queue_item.seq
     self.clear_request(aid)
     self.timeout_callback(aid, request)
Example #18
0
    def run_entanglement_protocol(self):
        """
        Executes the primary entanglement protocol, calls back to return an error if one occurs
        :return: None
        """
        try:
            logger.debug("{} Beginning entanglement attempt".format(
                self.node.name))
            NodeCentricMHP.run_protocol(self)

        except Exception:
            logger.exception("Error occured attempting entanglement")
            self._handle_error(None, self.ERR_LOCAL)
Example #19
0
    def get_free_mem_ad(self):
        """
        Returns the amount of free memory (storage qubit locations) that we have in the
        quantum memory device
        :return:
        """
        free_comms = self.get_free_communication_ids()
        free_storage = self.get_free_storage_ids()
        free_mem = (len(free_comms), len(free_storage))

        logger.debug(
            "Quantum Memory Device has free memory {}".format(free_mem))
        return free_mem
Example #20
0
 def _create(self, cqc_request_raw):
     """
     Internal method for calling the EGP's create method and storing the creation id and timestamp info for
     data collection
     :param cqc_request_raw: bytes
         The raw CQC request consisting of a CQCHeader + CQCCmdHeader and CQCEPRRequestHeader
     """
     # Only extract result information if the create was successfully submitted
     create_id = self.egp.create(cqc_request_raw=cqc_request_raw)
     create_time = sim_time()
     if create_id is not None:
         self.create_storage.append((self.egp.node.nodeID, cqc_request_raw, create_id, create_time))
         logger.debug("Scheduling create event now.")
         self._schedule_now(self._EVT_CREATE)
    def next(self):
        """
        Returns the next request (if any) to be processed.  Defaults to an information pass request.
        :return: tuple
            Tuple containing the request information for the MHP or None if no request
        """
        logger.debug("Getting next item from scheduler")
        # Get our available memory
        self.my_free_memory = self.qmm.get_free_mem_ad()

        next_gen = self.get_default_gen()

        if self.qmm.is_busy():
            logger.debug("QMM is currently busy")

        elif self.suspended():
            logger.debug("Generation is currently suspended")

        else:
            if self.curr_gen:
                self.free_gen_resources(self.curr_gen.aid)

            next_gen = self.get_next_gen_template()
            if next_gen.flag:
                logger.debug("Node {} : Scheduler has next gen {}".format(
                    self.distQueue.node.name, next_gen))

        return next_gen
Example #22
0
 def pass_information(self, sender):
     """
     Handler for a message requesting the passthrough of information to the other end of the connection
     :param classical: obj any
         Classical data sent by the sender
     :param qubit: list `~netsquid.qubits.qubit.Qubit`
         The qubits sent by the sender
     :param sender: int
         NodeID of the sender of the information
     :return: None
     """
     # Send info message to other end of connection
     receiver = self.nodeB.nodeID if sender == self.nodeA.nodeID else self.nodeA.nodeID
     logger.debug("Passing {}'s information to {}".format(sender, receiver))
     self._send_notification_to_one(self.CMD_INFO, receiver)
    def suspend_generation(self, t):
        """
        Instructs the scheduler to suspend generation for some specified time.
        :param t: float
            The amount of simulation time to suspend entanglement generation for
        """

        num_suspended_cycles = self._get_num_suspend_cycles(t)

        # Update the number of suspended cycles if it exceeds the amount we are currently suspended for
        if num_suspended_cycles > self.num_suspended_cycles:
            logger.debug(
                "Node {} : Suspending generation for {} cycles".format(
                    self.distQueue.node.name, num_suspended_cycles))
            self.num_suspended_cycles = num_suspended_cycles
Example #24
0
    def _do_swap(self):
        # Performs entanglement swapping, if two qubits are available
        node_reqs = [
            r.request_data for r in self.node_requests.values()
            if r is not None
        ]
        num_production_requests = node_reqs.count(self.CMD_PRODUCE)

        logger.debug(
            "Have {} production requests".format(num_production_requests))

        # Don't bother swapping because neither party requested entanglement
        if num_production_requests == 0:
            pass

        # Error if we only received on qubit during this cycle
        elif num_production_requests != 2:
            for _, qubit in self.qubits.items():
                if qubit:
                    self._drop_qubit(qubit)

            dataA, dataB = self._get_error_data(self.ERR_NO_CLASSICAL_OTHER)
            if self.node_requests[self.nodeA.nodeID] is not None:
                self._send_to_node(self.nodeA, dataA)
            elif self.node_requests[self.nodeB.nodeID] is not None:
                self._send_to_node(self.nodeB, dataB)

            logger.warning(
                "Midpoint only received entanglement generation data from one node"
            )

        else:
            for (id, q) in self.qubits.items():
                if q is None:
                    q = create_qubits(1)[0]
                    q.is_number_state = True
                    self.qubits[id] = q

            outcome = self.midPoint.measure(self.qubits[self.idA],
                                            self.qubits[self.idB])
            self.last_outcome = outcome
            logger.debug("Scheduling entanglement event now.")
            self._schedule_now(self._EVT_ENTANGLE_ATTEMPT)

            self._send_notification_to_both(outcome)

        # Reset incoming data
        self._reset_incoming()
Example #25
0
    def run_protocol(self):
        """
        Generic protocol, either accepts incoming requests (if any)
        """
        try:
            logger.debug("{} Node {} running protocol".format(
                sim_time(), self.node.nodeID))
            if self._has_resources() and self.stateProvider():
                request_data = self.service.get_as(self.node.nodeID)
                self._handle_request(request_data)

        except Exception as err_data:
            logger.exception("Exception occurred while running protocol")
            result = self._construct_error_result(err_code=self.ERR_LOCAL,
                                                  err_data=err_data)
            self.callback(result=result)
Example #26
0
    def _get_notification_data(self, notification_type, receiver):
        """
        Given a notification type to be sent to the receiver, constructs the corresponding data
        :param notification_type: int
            ID of the notification type we are sending
        :param receiver: int
            NodeID of the node receiving the notification
        :return: tuple of (resp, pass)
        """
        peer = self.nodeB.nodeID if receiver == self.nodeA.nodeID else self.nodeA.nodeID
        logger.debug(
            "Getting notification data to send to {}, has peer {}".format(
                receiver, peer))
        notification_data = {self.CMD_INFO: self.node_requests[peer].pass_data}

        return [[notification_type, notification_data.get(notification_type)]]
    def add_scheduling_event(self, qseq):
        """
        Configures a handler to catch the scheduling event of the queue item
        :param qseq: int
            Sequence number of the item we want to add a handler to
        """
        queue_item = self.sequence_to_item[qseq]

        # Only attach a handler if the item supports the timeout event
        if isinstance(queue_item, _TimeoutLocalQueueItem):
            logger.debug("TimedLocalQueue has queue item {}".format(
                vars(queue_item)))

            schedule_evt_handler = EventHandler(self._schedule_handler)
            self._wait_once(schedule_evt_handler,
                            entity=queue_item,
                            event_type=queue_item._EVT_SCHEDULE)
Example #28
0
    def process_data(self):
        """
        Receives incoming messages on the connection and constructs a reply object to pass into
        the reply processing method.
        :return: None
        """
        try:
            [msg, deltaT] = self.conn.get_as(self.node.nodeID)
            logger.debug("Received message {}".format(msg))
            respM, passM = msg[0]
            reply_message = MHPReply(response_data=respM, pass_data=passM)
            self._process_reply(reply_message)

        except Exception as err_data:
            logger.exception("Exception occurred processing data")
            result = self._construct_error_result(err_code=self.ERR_LOCAL,
                                                  err_data=err_data)
            self.callback(result=result)
    def add(self, originID, request):
        """
        Add item to the Queue with a new sequence number, used by master node only.
        """

        # Check how many items are on the queue right now
        if self.is_full():
            raise LinkLayerException("Local queue full: {}".format(
                len(self.queue)))

        # There is space, create a new queue item
        seq = self.get_next_sequence_number()

        logger.debug("Adding item with seq={} to local queue".format(seq))

        self.add_with_id(originID, seq, request)

        return seq
Example #30
0
    def _send_to_node(self, node, data):
        """
        Sends data out from the heralding station to a connected end node
        :param node: obj `~easysquid.qnode.QuantumNode`
            The node we want to send the data to
        :param data: obj any
            The data to place on the channel to the node
        """
        logger.debug("Midpoint sending data={} to node {}".format(
            data, node.name))
        if node.nodeID == self.nodeA.nodeID:
            self.channel_M_to_A.send(data)

        elif node.nodeID == self.nodeB.nodeID:
            self.channel_M_to_B.send(data)

        else:
            raise EasySquidException("Tried to send to unconnected node")