def _RBS_DIG(self, contaminated_set): """ Binary splitting algorithm. Input: contaminated set Output: - single contaminated item in input set - list of items declared healthy """ healthy_set = [[], []] while len(contaminated_set[0]) >> 1: test_group = aux._split_groups(contaminated_set) if aux._make_test(test_group[0][1], self.success_rate_test, self.false_posivite_rate_test, self.prob_sick, self.tests_repetitions, self.test_result_decision_strategy) == 1: contaminated_set = test_group[0] else: contaminated_set = test_group[1] for i in range(2): for item in test_group[0][i]: healthy_set[i] += [item] self.number_of_tests += self.tests_repetitions # single sick individual returned if contaminated_set == [[], []]: return None, healthy_set else: return [contaminated_set[0][0], contaminated_set[1][0]], healthy_set
def two_stage_testing_time_dependent(self, num_simultaneous_tests, test_duration, group_size): # number of tests which can be performed at once self.num_simultaneous_tests = num_simultaneous_tests # duration of one test [h] self.test_duration = test_duration self.number_groupwise_tests = np.ones( int(np.ceil(self.sample_size / group_size))) # initialize active groups self.active_groups = [] self.confirmed_sick_individuals = [] # this is for indexing the individuals self.continuousIndex = 0 for i in range(self.num_simultaneous_tests): self.get_next_group_from_data(group_size) while (len(self.active_groups) > 0): # Caution: new groups are added to active_groups every time a group is positively tested # However, we can only process the ones that existed at the beginning of this loop iteration for i in range( min(len(self.active_groups), self.num_simultaneous_tests)): testgroup = self.active_groups[0] self.active_groups = self.active_groups[1:] testresult = aux._make_test(testgroup[1], self.success_rate_test, self.false_posivite_rate_test, self.prob_sick, self.tests_repetitions, self.test_result_decision_strategy) self.number_of_tests += self.tests_repetitions if testresult == 1: if len(testgroup[1]) == 1: self.sick_individuals.append(testgroup[0][0]) else: # If a group is tested positive all its individuals are tested positive. # We consider this two consecutive tests. self.number_groupwise_tests[int( np.floor(testgroup[0][0] / group_size))] = 2 for j in range(len(testgroup[1])): self.active_groups += [[[testgroup[0][j]], [testgroup[1][j]]]] if len(self.active_groups) < self.num_simultaneous_tests: # groups have been fully processed. Add next group from data for i in range(self.num_simultaneous_tests - len(self.active_groups)): self.get_next_group_from_data(group_size) #self.total_time += self.test_duration self.total_time = self.number_of_tests * self.test_duration * self.tests_repetitions / self.num_simultaneous_tests self.update_sick_lists_and_success_rate()
def individual_testing_time_dependent(self, num_simultaneous_tests, test_duration, group_size=1): self.num_simultaneous_tests = num_simultaneous_tests self.test_duration = test_duration if group_size == 1: self.number_groupwise_tests = np.ones(self.sample_size) # initialize active groups self.active_groups = [] self.confirmed_sick_individuals = [] # this is for indexing the individuals self.continuousIndex = 0 for i in range(self.num_simultaneous_tests): self.get_next_group_from_data(group_size) while (len(self.active_groups) > 0): # Caution: binary_splitting_step adds new groups to active_groups every time it is # called. However, we can only process the ones that existed at the beginning of this # loop iteration self.number_of_rounds += 1 for i in range( min(len(self.active_groups), self.num_simultaneous_tests)): testgroup = self.active_groups[0] self.active_groups = self.active_groups[1:] testresult = aux._make_test(testgroup[1], self.success_rate_test, self.false_posivite_rate_test, self.prob_sick, self.tests_repetitions, self.test_result_decision_strategy) self.number_of_tests += self.tests_repetitions if testresult == 1: for individual in testgroup[0]: self.sick_individuals.append(individual) if len(self.active_groups) < self.num_simultaneous_tests: # groups have been fully processed. Add next group from data for i in range(self.num_simultaneous_tests - len(self.active_groups)): self.get_next_group_from_data(group_size) #self.total_time += self.test_duration * self.tests_repetitions self.total_time = self.number_of_tests * self.test_duration * self.tests_repetitions / self.num_simultaneous_tests self.update_sick_lists_and_success_rate()
def binary_splitting_step(self, testgroup): self.number_of_rounds += 1 # instantiate test results result_test = [0, 0] # split in left and right branch for i in range(2): if len(testgroup[i][0]) != 0: result_test[i] += aux._make_test( testgroup[i][1], self.success_rate_test, self.false_posivite_rate_test, self.prob_sick, self.tests_repetitions, self.test_result_decision_strategy) # Adjust the counter for the number of tests self.number_of_tests += self.tests_repetitions # Determine the outcome of the finding if result_test[i] == 1: if len(testgroup[i][1]) == 1: self.sick_individuals.append(testgroup[i][0][0]) else: self.active_groups += [testgroup[i]]
def purim_time_dependent(self, num_simultaneous_tests, test_duration, group_size): ''' Purim matrix based testing algorithm as in Fargion, Benjamin Isac, et al. "Purim: a rapid method with reduced cost for massive detection of CoVid-19." arXiv preprint arXiv:2003.11975 (2020). ''' self.num_simultaneous_tests = num_simultaneous_tests self.test_duration = test_duration # initialize active groups self.active_groups = [] self.confirmed_sick_individuals = [] # this is for indexing the individuals self.continuousIndex = 0 # by definition the sequential depth of purim is always two (right?) self.number_groupwise_tests = np.ones( int(np.ceil(self.sample_size / (group_size**2)))) * 2 for i in range(self.num_simultaneous_tests): self.get_next_group_from_data(group_size**2) while (len(self.active_groups) > 0): self.number_of_rounds += 1 for i in range( min(len(self.active_groups), self.num_simultaneous_tests)): testgroup = self.active_groups[0] self.active_groups = self.active_groups[1:] nearest_square = round(np.sqrt(len(testgroup[1])))**2 diff = nearest_square - len(testgroup[1]) if len(testgroup[1]) <= 4: # if the number of people in a group is 2 or less, do individual testing for j in range(len(testgroup[1])): result = aux._make_test( [testgroup[1][j]], self.success_rate_test, self.false_posivite_rate_test, self.prob_sick, self.tests_repetitions, self.test_result_decision_strategy) self.number_of_tests += self.tests_repetitions if result == 1: self.sick_individuals += [testgroup[0][j]] elif int(diff) < 0: # write into nearest_square+1 x nearest_square+1 array testarray = np.ones( int(nearest_square + 2 * np.sqrt(nearest_square) + 1)) for l in range(len(testgroup[1])): testarray[l] = testgroup[1][l] testarray = testarray.reshape( (int(np.sqrt(nearest_square) + 1), int(np.sqrt(nearest_square) + 1))) testarray_index = np.ones( int(nearest_square + 2 * np.sqrt(nearest_square) + 1)) for l in range(len(testgroup[0])): testarray_index[l] = testgroup[0][l] # testarray_index = np.append(np.asarray(testgroup[0]),np.ones(int(-diff)+1)) testarray_index = testarray_index.reshape( (int(np.sqrt(nearest_square) + 1), int(np.sqrt(nearest_square) + 1))) columns = [] rows = [] for k in range(int(np.sqrt(nearest_square) + 1)): result = aux._make_test( testarray[k, :], self.success_rate_test, self.false_posivite_rate_test, self.prob_sick, self.tests_repetitions, self.test_result_decision_strategy) self.number_of_tests += self.tests_repetitions if result == 1: columns += [k] for j in range(int(np.sqrt(nearest_square) + 1)): result = aux._make_test( testarray[:, j], self.success_rate_test, self.false_posivite_rate_test, self.prob_sick, self.tests_repetitions, self.test_result_decision_strategy) self.number_of_tests += self.tests_repetitions if result == 1: rows += [j] for k in columns: for j in rows: result = aux._make_test( [testarray[k, j]], self.success_rate_test, self.false_posivite_rate_test, self.prob_sick, self.tests_repetitions, self.test_result_decision_strategy) self.number_of_tests += self.tests_repetitions if result == 1: self.sick_individuals += [ int(testarray_index[k, j]) ] else: testarray = np.ones(int(nearest_square)) for l in range(len(testgroup[1])): testarray[l] = testgroup[1][l] testarray = testarray.reshape( (int(np.sqrt(nearest_square)), int(np.sqrt(nearest_square)))) testarray_index = np.ones(int(nearest_square)) for l in range(len(testgroup[0])): testarray_index[l] = testgroup[0][l] # testarray_index = np.append(np.asarray(testgroup[0]),np.ones(int(diff))) testarray_index = testarray_index.reshape( (int(np.sqrt(nearest_square)), int(np.sqrt(nearest_square)))) columns = [] rows = [] for k in range(int(np.sqrt(nearest_square))): result = aux._make_test( testarray[k, :], self.success_rate_test, self.false_posivite_rate_test, self.prob_sick, self.tests_repetitions, self.test_result_decision_strategy) self.number_of_tests += self.tests_repetitions if result == 1: columns += [k] for j in range(int(np.sqrt(nearest_square))): result = aux._make_test( testarray[:, j], self.success_rate_test, self.false_posivite_rate_test, self.prob_sick, self.tests_repetitions, self.test_result_decision_strategy) self.number_of_tests += self.tests_repetitions if result == 1: rows += [j] for k in columns: for j in rows: result = aux._make_test( [testarray[k, j]], self.success_rate_test, self.false_posivite_rate_test, self.prob_sick, self.tests_repetitions, self.test_result_decision_strategy) self.number_of_tests += self.tests_repetitions if result == 1: self.sick_individuals += [ int(testarray_index[k, j]) ] if len(self.active_groups) < self.num_simultaneous_tests: # groups have been fully processed. Add next group from data for i in range(self.num_simultaneous_tests - len(self.active_groups)): self.get_next_group_from_data(group_size**2) # simplified time measure, because individual time tracking is a bit complicated for RBS self.total_time = self.number_of_tests * self.test_duration / self.num_simultaneous_tests self.update_sick_lists_and_success_rate()
def binary_splitting_time_dependent(self, num_simultaneous_tests, test_duration, group_size): # in contrast to 'binary_splitting' this does not start with one huge group of all individuals # but with as many groups as tests are available, each group a reasonable size # when there are less groups than simultaneously processable tests new groups are added to # active_groups # number of tests which can be performed at once self.num_simultaneous_tests = num_simultaneous_tests # duration of one test [h] self.test_duration = test_duration self.number_groupwise_tests = np.ones( int(np.ceil(self.sample_size / group_size))) # initialize active groups self.active_groups = [] self.confirmed_sick_individuals = [] # this is for indexing the individuals self.continuousIndex = 0 for i in range(self.num_simultaneous_tests): self.get_next_group_from_data(group_size) while (len(self.active_groups) > 0): # Caution: binary_splitting_step adds new groups to active_groups every time it is # called. However, we can only process the ones that existed at the beginning of this # loop iteration self.number_of_rounds += 1 # print('---') for i in range( min(len(self.active_groups), self.num_simultaneous_tests)): testgroup = self.active_groups[0] self.active_groups = self.active_groups[1:] testresult = aux._make_test(testgroup[1], self.success_rate_test, self.false_posivite_rate_test, self.prob_sick, self.tests_repetitions, self.test_result_decision_strategy) self.number_of_tests += self.tests_repetitions if testresult == 1: # when a group of initial size is tested positive a tree of depth # log2(groupsize)+1 will be created. Otherwise only default depth 1 if len(testgroup[1]) == group_size: self.number_groupwise_tests[int( np.floor(testgroup[0][0] / group_size))] = np.ceil( np.log2(group_size)) + 1 # if only one individual in group if len(testgroup[1]) == 1: self.sick_individuals.append(testgroup[0][0]) else: new_groups = aux._split_groups(testgroup) self.active_groups.append(new_groups[0]) self.active_groups.append(new_groups[1]) if len(self.active_groups) < self.num_simultaneous_tests: # groups have been fully processed. Add next group from data for i in range(self.num_simultaneous_tests - len(self.active_groups)): self.get_next_group_from_data(group_size) #self.total_time += self.test_duration * self.tests_repetitions self.total_time = self.number_of_tests * self.test_duration * self.tests_repetitions / self.num_simultaneous_tests self.update_sick_lists_and_success_rate()
def sobel_main(self, num_simultaneous_tests, test_duration, maxGroupsize): ''' The algorithm R1 from (Sobel, Groll 1959) Sobel, Milton, and Phyllis A. Groll. "Group testing to eliminate efficiently all defectives in a binomial sample." Bell System Technical Journal 38.5 (1959): 1179-1252. ''' self.num_simultaneous_tests = num_simultaneous_tests self.test_duration = test_duration self.continuousIndex = 0 self.maxGroupsize = maxGroupsize # counter for the number of tests which are performed on one initial group of # maxGroupsize in total self.number_groupwise_tests = np.zeros( int(np.ceil(self.sample_size / maxGroupsize))) self.G, self.x = self.sobel_computeGx(1 - self.prob_sick, maxGroupsize) # initialize active groups self.active_groups = [] self.confirmed_sick_individuals = [] # this is for indexing the individuals while (len(self.rawdata) > 0): # initial groups have maximal size # TODO: inefficient way of getting groups self.get_next_group_from_data(maxGroupsize) binomial_set = self.active_groups[0] self.active_groups = self.active_groups[1:] # size of current defective group sobel_m = 0 # size of current other group sobel_n = maxGroupsize testgroup = [[], []] defective_set = [[], []] while (sobel_m != 0 or sobel_n != 0): # TODO This was causing errors. Is it ok to break if both are empty (because there # is nothing else to do), or is this a symptomatic error and the groups should never # be empty !? if len(binomial_set[0]) == 0 and len(defective_set[0]) == 0: break k = self.x[(sobel_m, sobel_n)] (mSuccess, nSuccess), (mFailure, nFailure) = self.sobel_step(sobel_m, sobel_n) if sobel_m == 0: # H-situation # take k individuals from binomial set into testgroup testgroup = [binomial_set[0][:k], binomial_set[1][:k]] binomial_set = [binomial_set[0][k:], binomial_set[1][k:]] testresult = aux._make_test( testgroup[1], self.success_rate_test, self.false_posivite_rate_test, self.prob_sick, self.tests_repetitions, self.test_result_decision_strategy) self.number_of_tests += self.tests_repetitions self.number_groupwise_tests[int( np.floor(testgroup[0][0] / self.maxGroupsize))] += 1 if testresult == 1: # infected if len(testgroup[0]) == 1: # print("single infected index {} identified".format(testgroup[0][0])) self.sick_individuals.append(testgroup[0][0]) testgroup = [[], []] else: defective_set = testgroup # TODO make a copy here!? elif testresult == 0: # clean testgroup = [[], []] if len(defective_set[0]) == 1: # print("identified index {} by conlusion".format(defective_set[0][0])) self.sick_individuals.append(defective_set[0][0]) defective_set = [[], []] elif sobel_m > 0: # G situation # take k individuals from defective set into testgroup testgroup = [defective_set[0][:k], defective_set[1][:k]] defective_set = [ defective_set[0][k:], defective_set[1][k:] ] testresult = aux._make_test( testgroup[1], self.success_rate_test, self.false_posivite_rate_test, self.prob_sick, self.tests_repetitions, self.test_result_decision_strategy) self.number_of_tests += self.tests_repetitions self.number_groupwise_tests[int( np.floor(testgroup[0][0] / self.maxGroupsize))] += 1 if testresult == 1: # pinfected if len(testgroup[0]) == 1: # print("single infected index {} identified".format(testgroup[0][0])) self.sick_individuals.append(testgroup[0][0]) testgroup = [[], []] binomial_set = [ binomial_set[0] + defective_set[0], binomial_set[1] + defective_set[1] ] defective_set = testgroup elif testresult == 0: # clean testgroup = [[], []] if len(defective_set[0]) == 1: # print("identified index {} by conlusion".format(defective_set[0][0])) self.sick_individuals.append(defective_set[0][0]) defective_set = [[], []] if testresult == 1: sobel_m = mFailure sobel_n = nFailure elif testresult == 0: sobel_m = mSuccess sobel_n = nSuccess # simplified time measure, because individual time tracking is a bit complicated self.total_time = self.number_of_tests * self.test_duration * self.tests_repetitions / self.num_simultaneous_tests self.update_sick_lists_and_success_rate()
def RBS_time_step(self): for i in range( min(len(self.active_groups), self.num_simultaneous_tests)): # remove empty lists self.active_groups = [x for x in self.active_groups if any(x)] if not any(self.active_groups): # if self.active_groups is empty return testgroup = self.active_groups[0] self.active_groups = self.active_groups[1:] if len(testgroup[0]) == 1: if not self.indicator: self.number_of_tests += self.tests_repetitions self.number_groupwise_tests[int( np.floor(testgroup[0][0] / self.group_size))] += 1 if aux._make_test(testgroup[1], self.success_rate_test, self.false_posivite_rate_test, self.prob_sick, self.tests_repetitions, self.test_result_decision_strategy) == 1: self.sick_individuals.append(testgroup[0][0]) return else: self.sick_individuals.append(testgroup[0][0]) return # else: # fix for empty groups ? elif len(testgroup[0]) != 0: if not self.indicator: testresult = aux._make_test( testgroup[1], self.success_rate_test, self.false_posivite_rate_test, self.prob_sick, self.tests_repetitions, self.test_result_decision_strategy) self.number_of_tests += self.tests_repetitions # TODO ACTIVATE #self.number_groupwise_tests[int(np.floor(testgroup[0][0] / self.group_size))] += 1 else: testresult = 1 if testresult == 1: testgroup = aux._split_groups(testgroup) # instantiate test results result_test = [0, 0] # split in left and right branch for i in range(2): result_test[i] += aux._make_test( testgroup[i][1], self.success_rate_test, self.false_posivite_rate_test, self.prob_sick, self.tests_repetitions, self.test_result_decision_strategy) self.number_of_tests += self.tests_repetitions # TODO ACTIVATE #self.number_groupwise_tests[int(np.floor(testgroup[i][0][0] / self.group_size))] += 1 # Determine the outcome of the finding if result_test[0] == 0 and result_test[1] == 1: sick_ind, healthy_ind = self._RBS_DIG(testgroup[1]) # remove healthy individuals found by DIG from testgroup for i in range(2): for item in healthy_ind[i]: testgroup[1][i].remove(item) if sick_ind is not None: # add found infected individual self.sick_individuals += [sick_ind[0]] # remove found sick individual from testgroup testgroup[1][0].remove(sick_ind[0]) testgroup[1][1].remove(sick_ind[1]) # update active groups self.active_groups += [testgroup[1]] else: # update active groups self.active_groups += [testgroup[1]] self.indicator = False return elif result_test[0] == 1 and result_test[1] == 0: sick_ind, healthy_ind = self._RBS_DIG(testgroup[0]) # remove healthy individuals found by DIG from testgroup for i in range(2): for item in healthy_ind[i]: testgroup[0][i].remove(item) if sick_ind is not None: # add found sick individual self.sick_individuals += [sick_ind[0]] # remove found sick individual from testgroup testgroup[0][0].remove(sick_ind[0]) testgroup[0][1].remove(sick_ind[1]) # update active groups self.active_groups += [testgroup[0]] else: # update active groups self.active_groups += [testgroup[0]] self.indicator = False return elif result_test[0] == 1 and result_test[1] == 1: self.active_groups += testgroup self.indicator = True return else: return