def test_initialization(self): parameters = [3, 3, 3, 3] constraints = {((0, 0), (2, 0)), ((0, 1), (1, 1), (2, 0)), ((0, 2), (3, 2))} t_value = 2 solver = Solver(parameters, constraints) combination_matrix = CombinationMatrix(parameters, t_value) solver.clean_hash_table(combination_matrix, t_value) cit = Cit(parameters, t_value, constraints) self.assertEqual(combination_matrix, cit.combination_matrix, "The initialization of cit algorithm is wrong")
def __init__(self, input_data, t_value, constraints): """ Creation of CombinationMatrix from user input :param input_data: parameters from user :param t_value: size of one combination :param constraints: constraints of combinations """ self.data = input_data self.t_value = t_value # CombinationMatrix creation self.combination_matrix = CombinationMatrix(input_data, t_value) # Creation of solver and simplification of constraints self.solver = Solver(input_data, constraints) # Combinations which do not match to the constraints are disabled self.solver.clean_hash_table(self.combination_matrix, t_value) self.final_matrix = [] self.__throbber = Throbber()
def test_solver_constraints_without_simplification(self): """ Test that, solver do not delete important constraints """ parameters = [3, 3, 3, 3] constraints = {((0, 0), (2, 0)), ((0, 1), (1, 1), (2, 0)), ((0, 2), (3, 2))} solver = Solver(parameters, constraints) self.assertEqual(solver.constraints, constraints, "solver deleted some important constraints")
def test_solver_constraints_with_simplification_and_secrete(self): """ Test that, Minimum forbidden tuple algorithm can find and simplify constraints """ parameters = [3, 3, 3, 3] constraints = {((0, 0), (1, 0)), ((0, 0), (1, 2)), ((0, 1), (3, 0)), ((0, 2), (3, 0)), ((1, 1), (3, 0))} solver = Solver(parameters, constraints) expectation = {((0, 0), (1, 0)), ((0, 0), (1, 2)), ((3, 0), )} self.assertEqual(solver.constraints, expectation, "solver can not compute and simplify constraints")
def test_solver_without_secrete_constraints(self): """ Test that, solver didn't change constraints if there isn't any secret constraint """ parameters = [3, 3, 3] constraints = {((0, 0), (2, 0)), ((0, 1), (1, 1)), ((1, 0), (2, 0))} solver = Solver(parameters, constraints) self.assertEqual( solver.constraints, constraints, "solver change constraints without secret " "constraint")
def test_simplify_constraints_constraints_without_simplification(self): """ Test that, function do not delete important constraints """ parameters = [3, 3, 3, 3] constraints = {((0, 0), (2, 0)), ((0, 1), (1, 1), (2, 0)), ((0, 2), (3, 2))} solver = Solver([], []) solver.data = parameters solver.constraints = constraints solver.read_constraints() solver.simplify_constraints() self.assertEqual( solver.constraints, constraints, "simplify_constraints deleted some important constraints")
def test_compute_constraints_without_secret_constraint(self): """ Test that, function shouldn't change constraints if there isn't any secret constraint """ parameters = [3, 3, 3] constraints = {((0, 0), (2, 0)), ((0, 1), (1, 1)), ((1, 0), (2, 0))} solver = Solver([], []) solver.data = parameters solver.constraints = constraints solver.read_constraints() solver.compute_constraints() self.assertEqual( solver.constraints, constraints, "compute_constraints change constraints without secret " "constraint")
def test_compute_constraints_with_one_secrete_constraint(self): """ Test that, function should find new constraint """ parameters = [3, 3, 3, 3] constraints = {((0, 0), (2, 0)), ((0, 1), (1, 1), (2, 0)), ((0, 2), (3, 2))} solver = Solver([], []) solver.data = parameters solver.constraints = constraints solver.read_constraints() solver.compute_constraints() expectation = {((0, 0), (2, 0)), ((0, 1), (1, 1), (2, 0)), ((0, 2), (3, 2)), ((1, 1), (2, 0), (3, 2))} self.assertEqual(solver.constraints, expectation, "compute_constraints didn't find secret constraints")
def test_compute_constraints_new_constraint_with_more_than_one_value_for_one_parameter( self): """ Test that, function shouldn't change constraints. It founds new constraint, but the constraint has one parameter with two values. This means that it isn't secreted constraint. """ parameters = [3, 3, 3] constraints = {((0, 0), (1, 0)), ((0, 1), (1, 1)), ((1, 2), (2, 0))} solver = Solver([], []) solver.data = parameters solver.constraints = constraints solver.read_constraints() solver.compute_constraints() self.assertEqual( solver.constraints, constraints, "compute_constraints change constraints without secret " "constraint")
def test_compute_constraints_detect_invalid_constraints(self): """ Test that, function should raise an exception, if there is invalid_constraints """ parameters = [3, 3, 3] constraints = {((0, 0), ), ((0, 1), ), ((0, 2), )} solver = Solver([], []) solver.data = parameters solver.constraints = constraints solver.read_constraints() self.assertRaises(ValueError, solver.compute_constraints)
class Cit: def __init__(self, input_data, t_value, constraints): """ Creation of CombinationMatrix from user input :param input_data: parameters from user :param t_value: size of one combination :param constraints: constraints of combinations """ self.data = input_data self.t_value = t_value # CombinationMatrix creation self.combination_matrix = CombinationMatrix(input_data, t_value) # Creation of solver and simplification of constraints self.solver = Solver(input_data, constraints) # Combinations which do not match to the constraints are disabled self.solver.clean_hash_table(self.combination_matrix, t_value) self.final_matrix = [] self.__throbber = Throbber() def final_matrix_init(self): """ Creation of the first solution. This solution is the start of searching for the best solution :return: solution matrix (list(list)) """ self.final_matrix = [self.create_random_row_with_constraints()] self.combination_matrix.cover_solution_row(self.final_matrix[0]) while self.combination_matrix.total_uncovered != 0: if self.combination_matrix.total_uncovered < self.combination_matrix.total_covered_more_than_ones: new_row = self.compute_row() else: new_row = self.compute_row_using_hamming_distance() self.combination_matrix.cover_solution_row(new_row) self.final_matrix.append(new_row) return self.final_matrix def compute(self): """ Searching for the best solution. It creates one solution and from that, it tries to create smaller solution. This searching process is limited by ITERATIONS_SIZE. When ITERATIONS_SIZE is 0 the last found solution is the best solution. :return: The best solution """ self.final_matrix = self.final_matrix_init() matrix = [x[:] for x in self.final_matrix] iterations = ITERATIONS_SIZE step_size = 1 deleted_rows = [] while step_size != 0: for i in range(step_size): delete_row = matrix.pop(random.randint(0, len(matrix) - 1)) self.combination_matrix.uncover_solution_row(delete_row) deleted_rows.append(delete_row) LOG.debug("I'm trying solution with size %s and %s iterations", len(matrix), iterations) matrix, is_better_solution = self.find_better_solution(iterations, matrix) if is_better_solution: self.final_matrix = matrix[:] deleted_rows = [] step_size *= 2 LOG.debug("-----solution with size %s was found-----\n", len(matrix)) iterations = ITERATIONS_SIZE else: LOG.debug("-----solution with size %s was not found-----\n", len(matrix)) for i in range(step_size): self.combination_matrix.cover_solution_row(deleted_rows[i]) matrix.append(deleted_rows[i]) if step_size > 1: step_size = 1 else: step_size = 0 return self.final_matrix def find_better_solution(self, counter, matrix): """ Changing the matrix to cover all combinations :param counter: maximum number of changes in the matrix :param matrix: matrix to be changed :return: new matrix and is changes have been successful? """ while self.combination_matrix.total_uncovered != 0: LOG.debug(self.__throbber.render(), extra={"skip_newline": True}) solution, row_index, _ = self.use_random_algorithm(matrix) if len(solution) != 0: self.combination_matrix.uncover_solution_row(matrix[row_index]) self.combination_matrix.cover_solution_row(solution) matrix[row_index] = solution if counter == 0: return matrix, False counter -= 1 return matrix, True def use_random_algorithm(self, matrix): """ Applies one of these algorithms to the matrix. It chooses algorithm by random in proportion 1:1:8 :param matrix: matrix to be changed :return: new row of matrix, index of row inside matrix and parameters which has been changed """ switch = random.randint(0, 9) if switch == 0: solution, row_index, parameters = self.change_one_value(matrix) elif switch == 1: solution, row_index, parameters = self.change_one_column(matrix) else: solution, row_index, parameters = self.cover_missing_combination(matrix) return solution, row_index, parameters def compute_row(self): """ Computation of one row which covers most of combinations :return: new solution row """ is_valid_row = False while not is_valid_row: possible_parameters = list(self.combination_matrix.uncovered_rows) row = [-1] * len(self.data) while len(possible_parameters) != 0: # finding uncovered combination combination_parameters_index = random.randint(0, len(possible_parameters) - 1) combination_parameters = possible_parameters[combination_parameters_index] del possible_parameters[combination_parameters_index] combination_row = self.combination_matrix.get_row(combination_parameters) possible_combinations = list(combination_row.get_all_uncovered_combinations()) combination_index = random.randint(0, len(possible_combinations) - 1) combination = possible_combinations[combination_index] is_parameter_used = False # Are parameters already used in row? for i in combination_parameters: if row[i] != -1: is_parameter_used = True break if is_parameter_used: continue row_copy = row.copy() # Is combination matches the constraints? for index, parameter in enumerate(combination_parameters): row_copy[parameter] = combination[index] if self.combination_matrix.is_valid_solution(row_copy): row = row_copy # Filling in of free spaces inside the row for index, r in enumerate(row): if r == -1: is_valid = False while not is_valid: row[index] = random.randint(0, self.data[index] - 1) is_valid = self.combination_matrix.is_valid_solution(row) is_valid_row = self.combination_matrix.is_valid_solution(row) return row def cover_missing_combination(self, matrix): """ Randomly finds one missing combination. This combination puts into each row of the matrix. The row with the best coverage is the solution :param matrix: matrix to be changed :return: solution, index of solution inside matrix and parameters which has been changed """ parameters, combination = self.get_missing_combination_random() best_uncover = float("inf") best_solution = [] best_row_index = 0 for row_index in range(len(matrix)): solution = [x for x in matrix[row_index]] for index, item in enumerate(parameters): solution[item] = combination[index] if self.combination_matrix.is_valid_combination(solution, parameters): self.combination_matrix.uncover_combination(matrix[row_index], parameters) self.combination_matrix.cover_combination(solution, parameters) if self.combination_matrix.total_uncovered < best_uncover: best_uncover = self.combination_matrix.total_uncovered best_solution = solution best_row_index = row_index self.combination_matrix.uncover_combination(solution, parameters) self.combination_matrix.cover_combination(matrix[row_index], parameters) if best_uncover == 0: break return best_solution, best_row_index, parameters def get_missing_combination_random(self): """ Randomly finds one missing combination. :return: parameter of combination and values of combination """ possible_parameters = list(self.combination_matrix.uncovered_rows) combination_parameters_index = random.randint(0, len(possible_parameters) - 1) combination_parameters = possible_parameters[combination_parameters_index] combination_row = self.combination_matrix.get_row(combination_parameters) possible_combinations = list(combination_row.get_all_uncovered_combinations()) combination_index = random.randint(0, len(possible_combinations) - 1) combination = possible_combinations[combination_index] return combination_parameters, combination def change_one_column(self, matrix): """ Randomly choose one column of the matrix. In each cell of this column changes value. The row with the best coverage is the solution. :param matrix: matrix to be changed :return: solution, index of solution inside matrix and parameters which has been changed """ column_index = random.randint(0, len(self.data) - 1) best_uncover = float("inf") best_solution = [] best_row_index = 0 for row_index in range(len(matrix)): try: solution, row_index, parameters = self.change_one_value(matrix, row_index, column_index) except ValueError: continue self.combination_matrix.uncover_combination(matrix[row_index], parameters) self.combination_matrix.cover_combination(solution, parameters) if self.combination_matrix.total_uncovered < best_uncover: best_uncover = self.combination_matrix.total_uncovered best_solution = solution best_row_index = row_index self.combination_matrix.uncover_combination(solution, parameters) self.combination_matrix.cover_combination(matrix[row_index], parameters) if best_uncover == 0: break return best_solution, best_row_index, [column_index] def change_one_value(self, matrix, row_index=None, column_index=None): """ Change one cell inside the matrix :param matrix: matrix to be changed :param row_index: row inside matrix. If it's None it is chosen randomly :param column_index: column inside matrix. If it's None it is chosen randomly :return: solution, index of solution inside matrix and parameters which has been changed """ is_cell_chosen = True if row_index is None: is_cell_chosen = False row_index = random.randint(0, len(matrix) - 1) row = [x for x in matrix[row_index]] if column_index is None: is_cell_chosen = False column_index = random.randint(0, len(row) - 1) possible_numbers = list(range(0, row[column_index])) + list( range(row[column_index] + 1, self.data[column_index])) row[column_index] = random.choice(possible_numbers) while not self.combination_matrix.is_valid_combination(row, [column_index]): possible_numbers.remove(row[column_index]) if len(possible_numbers) == 0: if is_cell_chosen: raise ValueError("Selected cell can't be changed") column_index = random.randint(0, len(row) - 1) row_index = random.randint(0, len(matrix) - 1) row = [x for x in matrix[row_index]] possible_numbers = list(range(0, row[column_index])) + list( range(row[column_index] + 1, self.data[column_index])) row[column_index] = random.choice(possible_numbers) return row, row_index, [column_index] def compute_row_using_hamming_distance(self): """ :return: row with the biggest hamming distance from final matrix """ row_1 = self.create_random_row_with_constraints() row_2 = self.create_random_row_with_constraints() if self.compute_hamming_distance(row_1) >= self.compute_hamming_distance(row_2): return row_1 else: return row_2 def compute_hamming_distance(self, row): """ :return: hamming distance of row from final matrix """ distance = 0 for final_row in self.final_matrix: for index, cell in enumerate(final_row): if row[index] != cell: distance += 1 return distance def create_random_row_with_constraints(self): """ Create a new test-case random row, and the row meets the constraints. :return: new random row :rtype: list """ data_size = len(self.data) row = [-1]*data_size for parameter in random.sample(range(data_size), data_size): possible_values = self.solver.get_possible_values(row, parameter) value_choice = random.choice(possible_values) row[parameter] = value_choice return row