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
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
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
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)
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
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)
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)
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
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)
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)
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
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
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
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()
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)
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)
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
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")