Skip to content

Commit cbd044d

Browse files
author
Axel Dahlberg
committed
Finalised tests for WFQ, all pass
1 parent 0c32341 commit cbd044d

File tree

9 files changed

+358
-59
lines changed

9 files changed

+358
-59
lines changed

.flake8

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[flake8]
22
count = True
33
exclude = bin, docs, .git, .idea, __pycache__
4-
ignore = W291 W293 E402
4+
ignore = W291 W293 E402 W503 W504
55
max-line-length = 120
66
statistics = True

qlinklayer/datacollection.py

-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import abc
22
import numpy as np
3-
import bitstring
43
from easysquid.puppetMaster import PM_SQLDataSequence
54
from easysquid.toolbox import logger
65
from netsquid.pydynaa import Entity, EventHandler

qlinklayer/egp.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1003,7 +1003,7 @@ def _create_ok(self, creq, mhp_seq):
10031003
"""
10041004
# Get the fidelity estimate from FEU
10051005
logger.debug("Estimating fidelity")
1006-
fidelity_estimate = self.feu.estimated_fidelity
1006+
fidelity_estimate = self.feu.estimate_fidelity_of_request(creq)
10071007

10081008
# Create entanglement identifier
10091009
logical_id = self.scheduler.curr_storage_id()

qlinklayer/feu.py

+32-12
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ def get_max_fidelity(self):
9999

100100
return max([f[1] for f in self.achievable_fidelities])
101101

102-
def select_bright_state(self, min_fidelity):
102+
def select_bright_state(self, min_fidelity, return_fidelity=False):
103103
"""
104104
Given a minimum desired fidelity returns the largest bright state population that achieves this fidelity
105105
:param min_fidelity: float
@@ -109,10 +109,23 @@ def select_bright_state(self, min_fidelity):
109109
"""
110110
for bright_state, fidelity in reversed(self.achievable_fidelities):
111111
if fidelity >= min_fidelity:
112-
return bright_state
112+
if return_fidelity:
113+
return bright_state, fidelity
114+
else:
115+
return bright_state
113116

114117
return None
115118

119+
def estimate_fidelity_of_request(self, request):
120+
"""
121+
Estimates the fidelity of a given request
122+
The request object must have an attribute 'min_fidelity'.
123+
:param request:
124+
:return: float
125+
"""
126+
_, fidelity = self.select_bright_state(request.min_fidelity, return_fidelity=True)
127+
return fidelity
128+
116129
def _estimate_fidelity(self, alphaA=None, alphaB=None):
117130
"""
118131
Calculates the fidelity by extracting parameters from the mhp components, calculating an estimated state of
@@ -141,8 +154,13 @@ def _compute_total_detection_probability_of_node(self, node):
141154
what is the probability that a detector clicks at the midpoint. (assuming no dark counts)
142155
:return: float
143156
"""
144-
p_zero_phonon = node.qmem.photon_emission_noise.p_zero_phonon
145-
collection_eff = node.qmem.photon_emission_noise.collection_eff
157+
photon_emission_noise = node.qmem.photon_emission_noise
158+
if photon_emission_noise is None:
159+
p_zero_phonon = 1
160+
collection_eff = 1
161+
else:
162+
p_zero_phonon = node.qmem.photon_emission_noise.p_zero_phonon
163+
collection_eff = node.qmem.photon_emission_noise.collection_eff
146164
mhp_conn = self.mhp_service.get_mhp_conn(node)
147165
if node == mhp_conn.nodeA:
148166
p_no_loss = self.mhp_service.get_fibre_transmissivities(node)[0]
@@ -156,7 +174,8 @@ def _compute_total_detection_probability_of_node(self, node):
156174

157175
def _compute_conditional_detection_probabilities(self, alphaA=None, alphaB=None):
158176
"""
159-
Computes the probabilites p_uu, p_ud, pdu, pdd as given in eq (10) in https://arxiv.org/src/1712.07567v2/anc/SupplementaryInformation.pdf
177+
Computes the probabilites p_uu, p_ud, pdu, pdd as given in eq (10) in
178+
https://arxiv.org/src/1712.07567v2/anc/SupplementaryInformation.pdf
160179
Note that this assumed low detection efficiencies
161180
If either alphaA or alphaB is None, then alpha is assumed to be the same for the two nodes.
162181
@@ -174,17 +193,18 @@ def _compute_conditional_detection_probabilities(self, alphaA=None, alphaB=None)
174193
mhp_conn = self.mhp_service.get_mhp_conn(self.node)
175194
p_dc = mhp_conn.midPoint.pdark
176195
# Dark count probabilities for both detectors
177-
p_no_dc = (1 - p_dc)**2
196+
p_no_dc = (1 - p_dc) ** 2
178197
p_at_least_one_dc = 1 - p_no_dc
179198

180-
prob_click_given_two_photons = (p_no_dc * (p_det_A*(1-p_det_B) + p_det_B*(1-p_det_A) + p_det_A*p_det_B) +
181-
p_at_least_one_dc * (1-p_det_A) * (1-p_det_B))
199+
prob_click_given_two_photons = (
200+
p_no_dc * (p_det_A * (1 - p_det_B) + p_det_B * (1 - p_det_A) + p_det_A * p_det_B)
201+
+ p_at_least_one_dc * (1 - p_det_A) * (1 - p_det_B))
182202
p_uu = alphaA * alphaB * prob_click_given_two_photons
183203

184-
prob_click_given_photon_A = p_no_dc * p_det_A + p_at_least_one_dc * (1-p_det_A)
185-
prob_click_given_photon_B = p_no_dc * p_det_B + p_at_least_one_dc * (1-p_det_B)
186-
p_ud = alphaA * (1-alphaB) * prob_click_given_photon_A
187-
p_du = alphaB * (1-alphaA) * prob_click_given_photon_B
204+
prob_click_given_photon_A = p_no_dc * p_det_A + p_at_least_one_dc * (1 - p_det_A)
205+
prob_click_given_photon_B = p_no_dc * p_det_B + p_at_least_one_dc * (1 - p_det_B)
206+
p_ud = alphaA * (1 - alphaB) * prob_click_given_photon_A
207+
p_du = alphaB * (1 - alphaA) * prob_click_given_photon_B
188208

189209
prob_click_given_no_photons = p_at_least_one_dc
190210
p_dd = (1 - alphaA) * (1 - alphaB) * prob_click_given_no_photons

qlinklayer/localQueue.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -425,15 +425,15 @@ def update_mhp_cycle_number(self, current_cycle, max_cycle):
425425
The max MHP cycle
426426
"""
427427
logger.debug("Updating to MHP cycle {}".format(current_cycle))
428-
if self.timeout_cycle is not None and self.timeout_cycle > 0:
428+
if self.timeout_cycle is not None:
429429
if check_schedule_cycle_bounds(current_cycle, max_cycle, self.timeout_cycle):
430430
logger.debug("Item timed out, calling callback")
431431
if not self.acked:
432432
logger.warning("Item timed out before being acknowledged.")
433433
self.timeout_callback(self)
434434
if self.acked:
435435
if not self.ready:
436-
if self.schedule_cycle > 0:
436+
if self.schedule_cycle is not None:
437437
if check_schedule_cycle_bounds(current_cycle, max_cycle, self.schedule_cycle):
438438
self.ready = True
439439
logger.debug("Item is ready to be scheduled")
@@ -456,7 +456,8 @@ def update_virtual_finish(self):
456456
:return: None
457457
"""
458458
if not self.request.atomic:
459-
self.virtual_finish += self.cycles_per_pair
459+
if self.virtual_finish is not None:
460+
self.virtual_finish += self.cycles_per_pair
460461

461462

462463
class _TimeoutLocalQueueItem(_LocalQueueItem, Entity):

qlinklayer/scheduler.py

+41-21
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@
1313
SchedulerRequest = namedtuple("Scheduler_request",
1414
["sched_cycle", "timeout_cycle", "min_fidelity", "purpose_id", "create_id", "num_pairs",
1515
"priority", "store", "atomic", "measure_directly", "master_request"])
16-
SchedulerRequest.__new__.__defaults__ = (0,) * 7 + (True, False, False, True)
16+
SchedulerRequest.__new__.__defaults__ = (0,) * 5 + (1,) + (0,) + (True, False, False, True)
1717

1818
WFQSchedulerRequest = namedtuple("WFQ_Scheduler_request",
19-
["sched_cycle", "timeout_cycle", "min_fidelity", "purpose_id", "create_id", "num_pairs",
20-
"priority", "init_virtual_finish", "est_cycles_per_pair", "store", "atomic", "measure_directly", "master_request"])
21-
WFQSchedulerRequest.__new__.__defaults__ = (0,) * 9 + (True, False, False, True)
19+
["sched_cycle", "timeout_cycle", "min_fidelity", "purpose_id", "create_id",
20+
"num_pairs",
21+
"priority", "init_virtual_finish", "est_cycles_per_pair", "store", "atomic",
22+
"measure_directly", "master_request"])
23+
WFQSchedulerRequest.__new__.__defaults__ = (0,) * 5 + (1,) + (0,) * 3 + (True, False, False, True)
2224

2325
SchedulerGen = namedtuple("Scheduler_gen", ["flag", "aid", "comm_q", "storage_q", "param"])
2426
SchedulerGen.__new__.__defaults__ = (False,) + (None,) * 4
@@ -48,11 +50,10 @@ def next(self):
4850

4951

5052
class StrictPriorityRequestScheduler(Scheduler):
51-
5253
# The scheduler request named tuple to use
5354
_scheduler_request_named_tuple = SchedulerRequest
5455

55-
def __init__(self, distQueue, qmm, feu):
56+
def __init__(self, distQueue, qmm, feu=None):
5657
"""
5758
Stub for a scheduler to decide how we assign and consume elements of the queue.
5859
This scheduler puts requests in queues depending on only their specified priority
@@ -199,11 +200,13 @@ def _get_scheduler_request(cls, egp_request, create_id, sched_cycle, timeout_cyc
199200
:return: :obj:`~qlinklayer.scheduler.SchedulerRequest`
200201
"""
201202
scheduler_request = cls._scheduler_request_named_tuple(sched_cycle=sched_cycle, timeout_cycle=timeout_cycle,
202-
min_fidelity=egp_request.min_fidelity, purpose_id=egp_request.purpose_id,
203-
create_id=create_id, num_pairs=egp_request.num_pairs,
204-
priority=egp_request.priority, store=egp_request.store,
205-
atomic=egp_request.atomic, measure_directly=egp_request.measure_directly,
206-
master_request=master_request)
203+
min_fidelity=egp_request.min_fidelity,
204+
purpose_id=egp_request.purpose_id,
205+
create_id=create_id, num_pairs=egp_request.num_pairs,
206+
priority=egp_request.priority, store=egp_request.store,
207+
atomic=egp_request.atomic,
208+
measure_directly=egp_request.measure_directly,
209+
master_request=master_request)
207210

208211
return scheduler_request
209212

@@ -793,11 +796,10 @@ def _reset_outstanding_req_data(self):
793796

794797

795798
class WFQRequestScheduler(StrictPriorityRequestScheduler):
796-
797799
# The scheduler request named tuple to use
798800
_scheduler_request_named_tuple = WFQSchedulerRequest
799801

800-
def __init__(self, distQueue, qmm, feu, weights=None):
802+
def __init__(self, distQueue, qmm, feu=None, weights=None):
801803
"""
802804
Stub for a scheduler to decide how we assign and consume elements of the queue.
803805
This weighted fair queue (WFQ) scheduler implements a weighted fair queue where queue i
@@ -851,6 +853,17 @@ def _compare_mhp_cycle(self, cycle1, cycle2):
851853
else:
852854
return 1
853855

856+
def _get_largest_mhp_cycle(self):
857+
"""
858+
Return the MHP which is considered largest, which comparison of MHP cycles is defined
859+
in self._compare_mhp_cycle
860+
:return: int
861+
"""
862+
# Compute opposite of current
863+
opposite = self.mhp_cycle_number - int(self.max_mhp_cycle_number / 2)
864+
865+
return (opposite - 1) % self.max_mhp_cycle_number
866+
854867
def _get_relative_weights(self, weights):
855868
if weights is None:
856869
return [1] * len(self.distQueue.queueList)
@@ -863,7 +876,8 @@ def _get_relative_weights(self, weights):
863876
if weight < 0:
864877
raise ValueError("All weights need to be non-zero")
865878
except TypeError:
866-
raise ValueError("Weights need to be comparable, got TypeError when comparing weight={} to 0".format(weight))
879+
raise ValueError(
880+
"Weights need to be comparable, got TypeError when comparing weight={} to 0".format(weight))
867881

868882
# Compute relative weights
869883
total_sum = sum(weights)
@@ -883,16 +897,18 @@ def select_queue(self):
883897
# Virt start is the virtual start of the service (see https://en.wikipedia.org/wiki/Fair_queuing#Pseudo_code)
884898
# Look through the head of each queue and pick the one with the smallest virtual_finish
885899
# unless a queue has infinite (0) weight.
886-
min_virtual_finish = float('inf')
900+
min_virtual_finish = self._get_largest_mhp_cycle()
887901
queue_item_to_use = None
888-
for local_queue in self.distQueue.queueList:
902+
903+
# Go in reverse such that if two items have the same virt finish, take the one with the lowest qid
904+
for local_queue in reversed(self.distQueue.queueList):
889905
queue_item = local_queue.peek()
890906
if queue_item is not None and queue_item.ready:
891-
if queue_item.request.virtual_finish is None:
907+
if queue_item.request.init_virtual_finish is None:
892908
# This queue has infinite weight so just take this queue_item directly
893909
queue_item_to_use = queue_item
894910
break
895-
if self._compare_mhp_cycle(queue_item.virtual_finish, min_virtual_finish) == -1:
911+
if self._compare_mhp_cycle(queue_item.virtual_finish, min_virtual_finish) < 1:
896912
min_virtual_finish = queue_item.virtual_finish
897913
queue_item_to_use = queue_item
898914
if queue_item_to_use is None:
@@ -912,7 +928,8 @@ def set_virtual_finish(self, scheduler_request, qid):
912928
# If queue has weight zero (seen as infinite) we put virtual finish to None
913929
if self.relative_weights[qid] == 0:
914930
init_virt_finish = None
915-
wfq_scheduler_request = WFQSchedulerRequest(**scheduler_request._asdict(), init_virtual_finish=init_virt_finish)
931+
wfq_scheduler_request = WFQSchedulerRequest(**scheduler_request._asdict(),
932+
init_virtual_finish=init_virt_finish)
916933
else:
917934
virt_start = max(self.mhp_cycle_number, self.last_virt_finish[qid])
918935
est_nr_cycles_per_pair = self._estimate_nr_of_cycles_per_pair(scheduler_request)
@@ -924,11 +941,14 @@ def set_virtual_finish(self, scheduler_request, qid):
924941
self.last_virt_finish[qid] = virt_start + virt_duration / self.relative_weights[qid]
925942
else:
926943
virt_duration = est_nr_cycles_per_pair
927-
self.last_virt_finish[qid] = virt_start + virt_duration * scheduler_request.num_pairs /self.relative_weights[qid]
944+
self.last_virt_finish[qid] = (
945+
virt_start + virt_duration * scheduler_request.num_pairs / self.relative_weights[qid])
928946

929947
# Compute initial virt finish (if non-atomic this will be updated per success)
930948
init_virt_finish = virt_start + virt_duration / self.relative_weights[qid]
931-
wfq_scheduler_request = WFQSchedulerRequest(**scheduler_request._asdict(), init_virtual_finish=init_virt_finish, est_cycles_per_pair=est_nr_cycles_per_pair)
949+
wfq_scheduler_request = WFQSchedulerRequest(**scheduler_request._asdict(),
950+
init_virtual_finish=init_virt_finish,
951+
est_cycles_per_pair=est_nr_cycles_per_pair)
932952

933953
return wfq_scheduler_request
934954

tests/test_feu.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
import unittest
2-
from easysquid.toolbox import EasySquidException
32
from qlinklayer.feu import SingleClickFidelityEstimationUnit
43
from qlinklayer.mhp import SimulatedNodeCentricMHPService
54
from qlinklayer.toolbox import LinkLayerException
6-
from easysquid.qnode import QuantumNode
7-
from easysquid.quantumMemoryDevice import NVCommunicationDevice
85
from easysquid.easynetwork import setup_physical_network
96
from util.config_paths import ConfigPathStorage
107
from netsquid.qubits import dm_fidelity
@@ -65,7 +62,7 @@ def test_success_prob_est(self):
6562

6663
# Test linearity in alpha
6764
for i in range(11):
68-
alpha = i/20
65+
alpha = i / 20
6966
p_succ = self.feuA._estimate_success_probability(alpha)
7067
self.assertAlmostEqual(p_succ, 2 * alpha * (4e-4), places=4)
7168

@@ -78,7 +75,7 @@ def test_fidelity_estimation(self):
7875

7976
# Check linearity in alpha (for high enough alpha due to dark counts)
8077
for i in range(40, 100):
81-
alpha = i/200
78+
alpha = i / 200
8279
F = self.feuA._estimate_fidelity(alpha)
8380
self.assertAlmostEqual(F, 1 - alpha, places=1)
8481

0 commit comments

Comments
 (0)