def test_SCPInstances(data_dir='../data', scp_instances_dir='scp_instances'): # Get file list scp_instances_list = os.listdir(os.path.join(data_dir, scp_instances_dir)) scp_instances_list = [ i for i in scp_instances_list if not i.startswith('.') ] # Go trough list for i, scp_instance in enumerate(scp_instances_list): scp_instance = SCPInstance( os.path.join(data_dir, scp_instances_dir, scp_instance)) if scp_instance.scp_number_of_columns != scp_instance.scp_instance_column_costs.shape[ 0]: print("Error in number of attributes in SCPInstance {}.".format( scp_instance.scp_instance_filename)) return 1 elif len(scp_instance.scp_instance_all_rows ) != scp_instance.scp_number_of_rows: print("Error in number of subsets in SCPInstance {}.".format( scp_instance.scp_instance_filename)) return 2 return 0
for ch_idx, ch in enumerate([ch1, ch2, ch3, ch4, ch5]): print("Current: CH{}".format(ch_idx + 1)) # We create a results array to append all the results results_array = np.zeros(shape=(len(scp_instances_list) + 1, 5), dtype='object') # We add the columns meaning to the 1st line results_array[0, 0] = 'SCP Instance Filename' results_array[0, 1] = 'Solution' results_array[0, 2] = 'Cost' results_array[0, 3] = 'Processed Solution' results_array[0, 4] = 'Processed Cost' # We go through all the SCP Instances available for scp_idx, scp_instance_filename in enumerate(scp_instances_list): scp_instance = SCPInstance( os.path.join(scp_instances_dir, scp_instance_filename)) # We add the filename to the results array results_array[scp_idx + 1, 0] = scp_instance_filename # We obtain the results final_solution, final_cost, final_processed_solution, final_processed_cost = ch( set_covering_problem_instance=scp_instance, post_processing=True, random_seed=42) # We add the results to the array results_array[scp_idx + 1, 1] = final_solution results_array[scp_idx + 1, 2] = final_cost results_array[scp_idx + 1, 3] = final_processed_solution results_array[scp_idx + 1, 4] = final_processed_cost
def lsh2(ih_results_array, scp_instances_dir, random_seed=42, k_max=10, l_max=10): # Set Numpy random seed np.random.seed(seed=random_seed) # Read the array information # SCP Instance Filename is at index 0 scp_instance_filename = ih_results_array[0] # Processed Solution is at index 3 initial_solution = ih_results_array[3] # Processed Cost is at index 4 initial_cost = ih_results_array[4] # Get the SCP Instance scp_instance_path = os.path.join(scp_instances_dir, scp_instance_filename) # Load the SCP Instance Object scp_instance = SCPInstance(scp_instance_filename=scp_instance_path) # Build Row X Column Matrix problem_matrix = np.zeros( (scp_instance.scp_number_of_rows, scp_instance.scp_number_of_columns), dtype=int) # Fill Problem Matrix for row_idx, row in enumerate(scp_instance.scp_instance_all_rows): for column in row: problem_matrix[row_idx, column - 1] = 1 # Variables in Memory: We create several memory variables that will be useful through the algorithm # Columns Availability: Variable To Use in Flips/Swaps, 1 if available, 0 if not available columns_availability = [1 for i in range(problem_matrix.shape[1])] for col in initial_solution: columns_availability[col] = 0 # Rows Availability: Variable to check for rows that are covered/not covered 1 if covered, 0 if not covered rows_availability = [1 for i in range(problem_matrix.shape[0])] for row_idx, _ in enumerate(rows_availability): if np.sum(problem_matrix[row_idx, :]) == 0: rows_availability[row_idx] = 0 # Rows Frequency Problem: Number of Times Each Row Appears in the Problem Matrix rows_freq_problem = [0 for i in range(problem_matrix.shape[0])] for row_idx, _ in enumerate(rows_freq_problem): rows_freq_problem[row_idx] = np.sum(problem_matrix[row_idx, :]) # Rows Frequency Solution: Number of Times Each Row Appears in the Solution rows_freq_solution = [0 for i in range(problem_matrix.shape[0])] for col in initial_solution: for row_idx, _ in enumerate(rows_freq_solution): rows_freq_solution[row_idx] += problem_matrix[row_idx, col] # Column Frequency: Number of Times Each Column Appears in the Problem Matrix column_freq_problem = [0 for i in range(problem_matrix.shape[1])] for col_idx, _ in enumerate(column_freq_problem): column_freq_problem[col_idx] = np.sum(problem_matrix[:, col_idx]) # Initialise variables # Current solution current_solution = initial_solution.copy() # Current cost current_cost = 0 for col in current_solution: current_cost += scp_instance.scp_instance_column_costs[col] # If current cost is different from the processed cost, we will take this last into account if current_cost != initial_cost: initial_cost = current_cost # Initialise best solution and best cost variables best_solution = initial_solution.copy() best_cost = initial_cost.copy() # Initialise number of iterations iteration = 1 # History: Save the Iteration and the Cost Value in that iteration to obtain good plots history = list() history.append([iteration, initial_cost]) # Begin algorithm # Iterate through Nk, k in [1, ..., k_max] k = 0 while k < k_max: print("Current k: {} | k_max: {}".format(k, k_max)) # Generate Nk neighbourhood Nk_neighbourhood = list() while len(Nk_neighbourhood) < (3 * problem_matrix.shape[1]): # We generate a possible candidate neighbour based on a swap swap_column = np.random.choice(a=current_solution) # Neighbours # Should we "swap or remove" to find a new neighbour? swap_or_remove_or_insert = np.random.choice(a=[0, 1, 2]) # Option 1: All rows are covered and our previous redundancy routines had bugs or were not effective if swap_or_remove_or_insert == 0: # If all the rows are covered this column is redundant! candidate_neighbour = current_solution.copy() candidate_neighbour.remove(swap_column) # Therefore, we found a valid neighbour solution # print("removed column") Nk_neighbourhood.append(candidate_neighbour) # Option 2: It's a swap! elif swap_or_remove_or_insert == 1: # Check availability candidate_neighbour_columns = list() for col, col_avail in enumerate(columns_availability): if col_avail == 1: candidate_neighbour_columns.append(col) # Create a procedure to find a proper candidate column candidate_column = np.random.choice( a=candidate_neighbour_columns) # Generate candidate neighbour candidate_neighbour = current_solution.copy() candidate_neighbour.remove(swap_column) candidate_neighbour.append(candidate_column) Nk_neighbourhood.append(candidate_neighbour) # Option 3: It's an insert! elif swap_or_remove_or_insert == 2: # Check availability candidate_neighbour_columns = list() for col, col_avail in enumerate(columns_availability): if col_avail == 1: candidate_neighbour_columns.append(col) # Create a procedure to find a proper candidate column candidate_column = np.random.choice( a=candidate_neighbour_columns) # Generate candidate neighbour candidate_neighbour = current_solution.copy() # candidate_neighbour.remove(swap_column) candidate_neighbour.append(candidate_column) Nk_neighbourhood.append(candidate_neighbour) # First check if all neighbours keep universitality Nk_valid_neighbourhood = list() for _, neigh in enumerate(Nk_neighbourhood): # print(neigh) rows_covered_by_neighbour = [ 0 for i in range(problem_matrix.shape[0]) ] for row, _ in enumerate(rows_covered_by_neighbour): for col in neigh: if problem_matrix[row, col] == 1: rows_covered_by_neighbour[row] = 1 if int(np.sum(rows_covered_by_neighbour)) == int( problem_matrix.shape[0]): Nk_valid_neighbourhood.append(list(neigh)) # Choose a random neighbour Nk_indices = [i for i in range(len(Nk_valid_neighbourhood))] Nk_index = np.random.choice(a=Nk_indices) # Assign variables solution and costs Nk_solution = Nk_valid_neighbourhood[Nk_index] Nk_cost = np.sum( [scp_instance.scp_instance_column_costs[c] for c in Nk_solution]) # Generate Nl neighbourhood l = 0 while l < l_max: print("Current l: {} | l_max: {}".format(l, l_max)) Nl_neighbourhood = list() # Let's create the Nl_neighbourhood while len(Nl_neighbourhood) < (3 * problem_matrix.shape[1]): # We generate a possible candidate neighbour based on a swap (it's the neighbourhood of Nk_solution!) swap_column = np.random.choice(a=Nk_solution) # Neighbours # Should we "swap or remove" to find a new neighbour? swap_or_remove_or_insert = np.random.choice(a=[0, 1, 2]) # Option 1: All rows are covered and our previous redundancy routines had bugs or were not effective if swap_or_remove_or_insert == 0: # If all the rows are covered this column is redundant! candidate_neighbour = Nk_solution.copy() candidate_neighbour.remove(swap_column) # Therefore, we found a valid neighbour solution # print("removed column") Nl_neighbourhood.append(candidate_neighbour) # Option 2: It's a swap! elif swap_or_remove_or_insert == 1: # Check availability candidate_neighbour_columns = list() for col in range(problem_matrix.shape[1]): if col not in Nk_solution: candidate_neighbour_columns.append(col) # Create a procedure to find a proper candidate column candidate_column = np.random.choice( a=candidate_neighbour_columns) # Generate candidate neighbour candidate_neighbour = Nk_solution.copy() candidate_neighbour.remove(swap_column) candidate_neighbour.append(candidate_column) Nl_neighbourhood.append(candidate_neighbour) # Option 3: It's an insert! elif swap_or_remove_or_insert == 2: # Check availability candidate_neighbour_columns = list() for col in range(problem_matrix.shape[1]): if col not in Nk_solution: candidate_neighbour_columns.append(col) # Create a procedure to find a proper candidate column candidate_column = np.random.choice( a=candidate_neighbour_columns) # Generate candidate neighbour candidate_neighbour = Nk_solution.copy() # candidate_neighbour.remove(swap_column) candidate_neighbour.append(candidate_column) Nl_neighbourhood.append(candidate_neighbour) # First check if all neighbours keep universitality Nl_valid_neighbourhood = list() for _, neigh in enumerate(Nl_neighbourhood): # print(neigh) rows_covered_by_neighbour = [ 0 for i in range(problem_matrix.shape[0]) ] for row, _ in enumerate(rows_covered_by_neighbour): for col in neigh: if problem_matrix[row, col] == 1: rows_covered_by_neighbour[row] = 1 if int(np.sum(rows_covered_by_neighbour)) == int( problem_matrix.shape[0]): Nl_valid_neighbourhood.append(list(neigh)) # Choose a the best neighbour Nl_valid_neighbourhood_costs = list() for Nl in Nl_valid_neighbourhood: Nl_valid_neighbourhood_costs.append( np.sum([ scp_instance.scp_instance_column_costs[c] for c in Nl ])) Nl_cost = Nl_valid_neighbourhood_costs[np.argmin( Nl_valid_neighbourhood_costs)] Nl_solution = Nl_valid_neighbourhood[np.argmin( Nl_valid_neighbourhood_costs)] # Compare Nk_solution vs Nl_solution if Nl_cost < Nk_cost: Nk_solution = Nl_solution.copy() Nk_cost = Nl_cost.copy() l = 0 else: l += 1 # Now compare the current Nk_solution against the current solution if Nl_cost < current_cost: current_solution = Nl_solution.copy() current_cost = Nl_cost.copy() if current_cost < best_cost: best_solution = current_solution.copy() best_cost = current_cost.copy() else: k += 1 # Updates history.append([iteration, current_cost]) iteration += 1 print("Initial Cost: {} | Current Cost: {} | Best Cost: {}".format( initial_cost, current_cost, best_cost)) # Final Solutions final_solution = best_solution.copy() final_cost = best_cost.copy() return initial_solution, initial_cost, final_solution, final_cost, history
def lsh1(ih_results_array, scp_instances_dir, random_seed=42, initial_temperature=10, final_temperature=0.01, cooling_ratio_alpha=0.99, tabu_thr=10, patience=30): # Set Numpy random seed np.random.seed(seed=random_seed) # Read the array information # SCP Instance Filename is at index 0 scp_instance_filename = ih_results_array[0] # Processed Solution is at index 3 initial_solution = ih_results_array[3] # Processed Cost is at index 4 initial_cost = ih_results_array[4] # Get the SCP Instance scp_instance_path = os.path.join(scp_instances_dir, scp_instance_filename) # Load the SCP Instance Object scp_instance = SCPInstance(scp_instance_filename=scp_instance_path) # Build Row X Column Matrix problem_matrix = np.zeros( (scp_instance.scp_number_of_rows, scp_instance.scp_number_of_columns), dtype=int) # Fill Problem Matrix for row_idx, row in enumerate(scp_instance.scp_instance_all_rows): for column in row: problem_matrix[row_idx, column - 1] = 1 # Variables in Memory: We create several memory variables that will be useful through the algorithm # Columns Availability: Variable To Use in Flips/Swaps, 1 if available, 0 if not available columns_availability = [1 for i in range(problem_matrix.shape[1])] for col in initial_solution: columns_availability[col] = 0 # Rows Availability: Variable to check for rows that are covered/not covered 1 if covered, 0 if not covered rows_availability = [1 for i in range(problem_matrix.shape[0])] for row_idx, _ in enumerate(rows_availability): if np.sum(problem_matrix[row_idx, :]) == 0: rows_availability[row_idx] = 0 # Rows Frequency Problem: Number of Times Each Row Appears in the Problem Matrix rows_freq_problem = [0 for i in range(problem_matrix.shape[0])] for row_idx, _ in enumerate(rows_freq_problem): rows_freq_problem[row_idx] = np.sum(problem_matrix[row_idx, :]) # Rows Frequency Solution: Number of Times Each Row Appears in the Solution rows_freq_solution = [0 for i in range(problem_matrix.shape[0])] for col in initial_solution: for row_idx, _ in enumerate(rows_freq_solution): rows_freq_solution[row_idx] += problem_matrix[row_idx, col] # Column Frequency: Number of Times Each Column Appears in the Problem Matrix column_freq_problem = [0 for i in range(problem_matrix.shape[1])] for col_idx, _ in enumerate(column_freq_problem): column_freq_problem[col_idx] = np.sum(problem_matrix[:, col_idx]) # Tabu Search for Columnns: Columns in the solution begin with value -1, the rest with 0; until the value of tabu_thr the column is usable tabu_columns = [0 for i in range(problem_matrix.shape[1])] for col_idx, _ in enumerate(tabu_columns): if col_idx in initial_solution: tabu_columns[col_idx] = -1 # Initialise variables # Current solution current_solution = initial_solution.copy() # Current cost current_cost = 0 for col in current_solution: current_cost += scp_instance.scp_instance_column_costs[col] # If current cost is different from the processed cost, we will take this last into account if current_cost != initial_cost: initial_cost = current_cost # Initialise best solution and best cost best_solution = initial_solution.copy() best_cost = initial_cost.copy() # Initialise number of iterations iteration = 1 # History: Save the Iteration and the Cost Value in that iteration to obtain good plots history = list() history.append([iteration, initial_cost, initial_temperature]) # Current temperature current_temperature = initial_temperature current_patience = 0 # Begin algorithm while (current_temperature > final_temperature) and (current_patience < patience): # Select a random neighbour # Valid neighbour finding success variable valid_neighbour = list() # Let's generate more neighbours at a time while len(valid_neighbour) < (3 * problem_matrix.shape[1]): # We generate a possible candidate neighbour based on a swap swap_column = np.random.choice(a=current_solution) # Neighbours # Should we "swap or remove" to find a new neighbour? swap_or_remove_or_insert = np.random.choice(a=[0, 1, 2]) # Option 1: All rows are covered and our previous redundancy routines had bugs or were not effective if swap_or_remove_or_insert == 0: # If all the rows are covered this column is redundant! candidate_neighbour = current_solution.copy() candidate_neighbour.remove(swap_column) # Therefore, we found a valid neighbour solution # print("removed column") valid_neighbour.append(candidate_neighbour) # Option 2: It's a swap! elif swap_or_remove_or_insert == 1: # Check availability candidate_neighbour_columns = list() for col, col_avail in enumerate(columns_availability): if col_avail == 1: candidate_neighbour_columns.append(col) # Create a procedure to find a proper candidate column candidate_column_found = False while candidate_column_found != True: candidate_column = np.random.choice( a=candidate_neighbour_columns) if (tabu_columns[candidate_column] >= 0) and (tabu_columns[candidate_column] <= 10): candidate_column_found = True # Generate candidate neighbour candidate_neighbour = current_solution.copy() candidate_neighbour.remove(swap_column) candidate_neighbour.append(candidate_column) valid_neighbour.append(candidate_neighbour) # Option 3: It's an insert! elif swap_or_remove_or_insert == 2: # Check availability candidate_neighbour_columns = list() for col, col_avail in enumerate(columns_availability): if col_avail == 1: candidate_neighbour_columns.append(col) # Create a procedure to find a proper candidate column candidate_column_found = False while candidate_column_found != True: candidate_column = np.random.choice( a=candidate_neighbour_columns) if (tabu_columns[candidate_column] >= 0) and (tabu_columns[candidate_column] <= 10): candidate_column_found = True # Generate candidate neighbour candidate_neighbour = current_solution.copy() # candidate_neighbour.remove(swap_column) candidate_neighbour.append(candidate_column) valid_neighbour.append(candidate_neighbour) # First check if all neighbours keep universitality possible_neighbours = list() for _, neigh in enumerate(valid_neighbour): # print(neigh) rows_covered_by_neighbour = [ 0 for i in range(problem_matrix.shape[0]) ] for row, _ in enumerate(rows_covered_by_neighbour): for col in neigh: if problem_matrix[row, col] == 1: rows_covered_by_neighbour[row] = 1 if int(np.sum(rows_covered_by_neighbour)) == int( problem_matrix.shape[0]): possible_neighbours.append(list(neigh)) # We have possible neighbours! (check the universitality of the solution) if len(possible_neighbours) > 0: # print(possible_neighbours, len(possible_neighbours)) possible_neighbours_costs = list() for _, neighbour in enumerate(possible_neighbours): # print(len(neigh)) neigh_cost = list() # print(n) if isinstance(neighbour, list): for c in neighbour: # print(c) neigh_cost.append( scp_instance.scp_instance_column_costs[c]) neigh_cost = np.sum(neigh_cost) # print(neigh_cost) possible_neighbours_costs.append(neigh_cost) # We choose the best neighbour best_neighbour = possible_neighbours[np.argmin( possible_neighbours_costs)] best_neighbour_cost = possible_neighbours_costs[np.argmin( possible_neighbours_costs)] cost_difference = current_cost - best_neighbour_cost # Now, evaluate costs if cost_difference > 0: current_solution = best_neighbour.copy() current_cost = best_neighbour_cost else: # Probability threshold if random.uniform(0, 1) < math.exp( cost_difference / current_temperature): current_solution = best_neighbour.copy() current_cost = best_neighbour_cost # Update Best Solution Found if current_cost < best_cost: best_cost = current_cost.copy() best_solution = current_solution.copy() current_patience = 0 else: current_patience += 1 # Temperature update print("Temperature decreased from {} to {}.".format( current_temperature, current_temperature * cooling_ratio_alpha)) current_temperature *= cooling_ratio_alpha print("Initial Cost: {} | Current Cost: {} | Best Cost: {}".format( initial_cost, current_cost, best_cost)) print("Current Patience: {} | Max Patience: {}".format( current_patience, patience)) # Updates # Columns Availability for col, _ in enumerate(columns_availability): if col in current_solution: columns_availability[col] = 0 else: columns_availability[col] = 1 # Tabu Search for col, col_tabu in enumerate(tabu_columns): if col in current_solution: tabu_columns[col] = -1 elif col in best_neighbour and col not in current_solution: tabu_columns[col] += 1 if tabu_columns[col] == 2 * tabu_thr: tabu_columns[col] = 0 # Rows Frequency Solution rows_freq_solution = [0 for i in range(problem_matrix.shape[0])] for col in current_solution: for row_idx, _ in enumerate(rows_freq_solution): rows_freq_solution[row_idx] += problem_matrix[row_idx, col] # Iterations iteration += 1 # History history.append([iteration, current_cost, current_temperature]) # Final final_solution = best_solution.copy() final_cost = best_cost.copy() print("Initial Cost: {} | Final Cost: {}".format(initial_cost, final_cost)) return initial_solution, initial_cost, final_solution, final_cost, history
def ih4(ch_results_array, scp_instances_dir, use_processed_solution=True, random_seed=42, set_minimization_repetition_factor=5000, hill_climbing_repetition_factor=1000): # Set Numpy random seed np.random.seed(seed=random_seed) # Read the array information # SCP Instance Filename is at index 0 scp_instance_filename = ch_results_array[0] if use_processed_solution: # Processed Solution is at index 3 initial_solution = ch_results_array[3] # Processed Cost is at index 4 initial_cost = ch_results_array[4] else: # Initial Solution is at index 1 initial_solution = ch_results_array[1] # Initial Cost is at index 2 initial_cost = ch_results_array[2] # Get the SCP Instance scp_instance_path = os.path.join(scp_instances_dir, scp_instance_filename) # Load the SCP Instance Object scp_instance = SCPInstance(scp_instance_filename=scp_instance_path) # Build Row X Column Matrix problem_matrix = np.zeros( (scp_instance.scp_number_of_rows, scp_instance.scp_number_of_columns), dtype=int) # Fill Problem Matrix for row_idx, row in enumerate(scp_instance.scp_instance_all_rows): for column in row: problem_matrix[row_idx, column - 1] = 1 # Variables in Memory: We create several memory variables that will be useful through the algorithm # Columns Availability: Variable To Use in Flips/Swaps, 1 if available, 0 if not available columns_availability = [1 for i in range(problem_matrix.shape[1])] for col in initial_solution: columns_availability[col] = 0 # Rows Availability: Variable to check for rows that are covered/not covered 1 if covered, 0 if not covered rows_availability = [1 for i in range(problem_matrix.shape[0])] for row_idx, _ in enumerate(rows_availability): if np.sum(problem_matrix[row_idx, :]) == 0: rows_availability[row_idx] = 0 # Rows Frequency Problem: Number of Times Each Row Appears in the Problem Matrix rows_freq_problem = [0 for i in range(problem_matrix.shape[0])] for row_idx, _ in enumerate(rows_freq_problem): rows_freq_problem[row_idx] = np.sum(problem_matrix[row_idx, :]) # Rows Frequency Solution: Number of Times Each Row Appears in the Solution rows_freq_solution = [0 for i in range(problem_matrix.shape[0])] for col in initial_solution: for row_idx, _ in enumerate(rows_freq_solution): rows_freq_solution[row_idx] += problem_matrix[row_idx, col] # Column Frequency: Number of Times Each Column Appears in the Problem Matrix column_freq_problem = [0 for i in range(problem_matrix.shape[1])] for col_idx, _ in enumerate(column_freq_problem): column_freq_problem[col_idx] = np.sum(problem_matrix[:, col_idx]) # Tabu Search for Columnns: Columns in the solution begin with value -1, the rest with 0; until the value of tabu_thr the column is usable tabu_columns = [0 for i in range(problem_matrix.shape[1])] for col_idx, _ in enumerate(tabu_columns): if col_idx in initial_solution: tabu_columns[col_idx] = -1 # Initialise variables # Current solution current_solution = initial_solution.copy() # Current cost current_cost = 0 for col in current_solution: current_cost += scp_instance.scp_instance_column_costs[col] # If current cost is different from the processed cost, we will take this last into account if current_cost != initial_cost: initial_cost = current_cost # Compute the R value from the paper R = problem_matrix.shape[0] * problem_matrix.shape[1] # History history = list() history.append(initial_cost) # Begin algorithm # First Part: Set Redundancy Elimination for _ in range(int(set_minimization_repetition_factor)): # Randomly select a set X* from the selected sets. candidate_redundant_set = np.random.choice(a=current_solution) # Mark this set X as Unselected Set. new_solution = current_solution.copy() new_solution.remove(candidate_redundant_set) # Check whether the universality constraint holds temp_row_availability = [0 for i in range(problem_matrix.shape[0])] for col in new_solution: for row, row_value in enumerate(problem_matrix[:, col]): if row_value == 1: if temp_row_availability[row] == 0: temp_row_availability[row] = 1 else: temp_row_availability[row] = 1 # The total_availability must be equal to the number of rows to cover, then we have a new solution if int(np.sum(temp_row_availability)) == (problem_matrix.shape[0]): new_cost = [ scp_instance.scp_instance_column_costs[c] for c in new_solution ] # Stay with this state and find the cost, Cnew. new_cost = np.sum(new_cost) # Replace the best found cost C, with the current cost, Cnew. current_cost = new_cost # Remove set X from the selected sets, X. current_solution = new_solution.copy() # Update column availability columns_availability[candidate_redundant_set] = 1 # History history.append(new_cost) # Second Part: Hill Climbing Algorithm for _ in range(int(hill_climbing_repetition_factor)): # Randomly select a set Y from the unselected sets, S-X available_sets = [ c for c, c_avail in enumerate(columns_availability) if c_avail == 1 ] # Mark this set as Selected. candidate_added_set = np.random.choice(a=available_sets) new_solution = current_solution.copy() # Check whether the universality constraint holds new_solution.append(candidate_added_set) # Check whether the universality constraint holds temp_row_availability = [0 for i in range(problem_matrix.shape[0])] for col in new_solution: for row, row_value in enumerate(problem_matrix[:, col]): if row_value == 1: if temp_row_availability[row] == 0: temp_row_availability[row] = 1 else: temp_row_availability[row] = 1 # The total_availability must be equal to the number of rows to cover, then we have a new solution if int(np.sum(temp_row_availability)) == (problem_matrix.shape[0]): # Find cost Cnew of c((X - X) U ( Y ) new_cost = np.sum([ scp_instance.scp_instance_column_costs[c] for c in new_solution ]) if new_cost <= current_cost: # Replace the best found cost C, with the current cost, Cnew. current_cost = new_cost current_solution = new_solution.copy() # Update column availability columns_availability[candidate_added_set] = 0 # History history.append(new_cost) # End algorithm final_solution = current_solution.copy() final_cost = current_cost.copy() # History history.append(final_cost) return initial_solution, initial_cost, final_solution, final_cost, history
def ih3(ch_results_array, scp_instances_dir, use_processed_solution=True, random_seed=42, max_iterations=5000, patience=100, tabu_thr=10): """ IH3: A (tentative) hybrid approach. """ # Set Numpy random seed np.random.seed(seed=random_seed) # Read the array information # SCP Instance Filename is at index 0 scp_instance_filename = ch_results_array[0] if use_processed_solution: # Processed Solution is at index 3 initial_solution = ch_results_array[3] # Processed Cost is at index 4 initial_cost = ch_results_array[4] else: # Initial Solution is at index 1 initial_solution = ch_results_array[1] # Initial Cost is at index 2 initial_cost = ch_results_array[2] # Get the SCP Instance scp_instance_path = os.path.join(scp_instances_dir, scp_instance_filename) # Load the SCP Instance Object scp_instance = SCPInstance(scp_instance_filename=scp_instance_path) # Build Row X Column Matrix problem_matrix = np.zeros( (scp_instance.scp_number_of_rows, scp_instance.scp_number_of_columns), dtype=int) # Fill Problem Matrix for row_idx, row in enumerate(scp_instance.scp_instance_all_rows): for column in row: problem_matrix[row_idx, column - 1] = 1 # Variables in Memory: We create several memory variables that will be useful through the algorithm # Columns Availability: Variable To Use in Flips/Swaps, 1 if available, 0 if not available columns_availability = [1 for i in range(problem_matrix.shape[1])] for col in initial_solution: columns_availability[col] = 0 # Rows Availability: Variable to check for rows that are covered/not covered 1 if covered, 0 if not covered rows_availability = [1 for i in range(problem_matrix.shape[0])] for row_idx, _ in enumerate(rows_availability): if np.sum(problem_matrix[row_idx, :]) == 0: rows_availability[row_idx] = 0 # Rows Frequency Problem: Number of Times Each Row Appears in the Problem Matrix rows_freq_problem = [0 for i in range(problem_matrix.shape[0])] for row_idx, _ in enumerate(rows_freq_problem): rows_freq_problem[row_idx] = np.sum(problem_matrix[row_idx, :]) # Rows Frequency Solution: Number of Times Each Row Appears in the Solution rows_freq_solution = [0 for i in range(problem_matrix.shape[0])] for col in initial_solution: for row_idx, _ in enumerate(rows_freq_solution): rows_freq_solution[row_idx] += problem_matrix[row_idx, col] # Column Frequency: Number of Times Each Column Appears in the Problem Matrix column_freq_problem = [0 for i in range(problem_matrix.shape[1])] for col_idx, _ in enumerate(column_freq_problem): column_freq_problem[col_idx] = np.sum(problem_matrix[:, col_idx]) # Tabu Search for Columnns: Columns in the solution begin with value -1, the rest with 0; until the value of tabu_thr the column is usable tabu_columns = [0 for i in range(problem_matrix.shape[1])] for col_idx, _ in enumerate(tabu_columns): if col_idx in initial_solution: tabu_columns[col_idx] = -1 # Initialise variables # Current solution current_solution = initial_solution.copy() # Current cost current_cost = 0 for col in current_solution: current_cost += scp_instance.scp_instance_column_costs[col] # If current cost is different from the processed cost, we will take this last into account if current_cost != initial_cost: initial_cost = current_cost # Initialise number of iterations nr_iteration = 1 # Initialise number of iterations in patience nr_patience = 1 # History history = list() history.append(initial_cost) # Begin algorithm # We create a history list first so we do not repeat in each iteration swap_column_history = list() while (nr_iteration <= max_iterations) and (nr_patience <= patience): # print("Iteration: {} | Patience: {}".format(nr_iteration, nr_patience)) # Generate a neighbour-solution # Create a condition that decides that we have found a proper neighbour valid_neighbours = False while valid_neighbours != True: # Always choose the most expensive column # Sort the solution: last columns will be the expensive ones current_solution.sort() # To perform the while loop swap_column = -1 # Will help us to reach the last col, then the second to last, and so on... aux_idx = 1 # Get the swap column while swap_column < 0: if len(current_solution) - aux_idx >= 0: col_candidate = current_solution[len(current_solution) - aux_idx] if col_candidate not in swap_column_history: swap_column = col_candidate swap_column_history.append(swap_column) else: aux_idx += 1 else: swap_column_history = list() aux_idx = 1 # Neighbours # First we verify the rows that this column covers rows_covered_by_swap_column = list() for row, row_value in enumerate(problem_matrix[:, swap_column]): if row_value == 1: rows_covered_by_swap_column.append(row) # Then we subtract 1 from the row freq column to check if there are columns that suddenly become unavailable rows_freq_solution_after_swap = rows_freq_solution.copy() for row in rows_covered_by_swap_column: rows_freq_solution_after_swap[row] -= 1 # Now we have to verify if there is some row that is unavailable uncovered_rows_after_swap = list() for row, row_freq in enumerate(rows_freq_solution_after_swap): if row_freq <= 0: uncovered_rows_after_swap.append(row) # We have two options: neighbours = list() # Option 1: All rows are covered if len(uncovered_rows_after_swap) == 0: # If all the rows are covered this column is redundant! new_solution = current_solution.copy() new_solution.remove(swap_column) # Therefore, we found a valid neighbour solution valid_neighbours = True # Option 2: We have uncovered rows else: # This way, the neighbours must contain at least the uncovered rows after swap # We check the available columns first for col, col_avail in enumerate(columns_availability): # print("Column: ", col) # It must respect the constraints related with the availability and tabu search if (col != swap_column) and (col_avail == 1) and ( tabu_columns[col] >= 0) and (tabu_columns[col] <= 10): # Check the rows that this col covers temp_rows_col_covers = list() for row, row_value in enumerate(problem_matrix[:, col]): if row_value == 1: temp_rows_col_covers.append(row) # Now let's check this column covers all the uncovered rows try: for row in uncovered_rows_after_swap: temp_rows_col_covers.remove(row) if len(temp_rows_col_covers) == 0: neighbours.append(col) except: neighbours = neighbours.copy() # Now we should have neighbours (or not) if len(neighbours) >= 0: # print("more than one neighbour", len(neighbours)) # Small tweak to avoid loopholes new_solution = current_solution.copy() valid_neighbours = True # If we just removed a column if len(neighbours) == 0: # Let's compute the cost of the new solution new_cost = np.sum([ scp_instance.scp_instance_column_costs[col] for col in new_solution ]) # It is better, our current cost is the new cost if new_cost < current_cost: current_cost = new_cost # And our current solution is the new_solution current_solution = new_solution # Updates # Columns Availability for col in current_solution: columns_availability[col] = 0 # Rows Frequency Solution rows_freq_solution = [ 0 for i in range(problem_matrix.shape[0]) ] for col in current_solution: for row_idx, _ in enumerate(rows_freq_solution): rows_freq_solution[row_idx] += problem_matrix[row_idx, col] # History history.append(new_cost) # Iterations nr_iteration += 1 nr_patience = 0 else: # History history.append(new_cost) nr_iteration += 1 nr_patience += 1 # Otherwise, we have neighbours else: # Here, we choose the BEST neighbour we find # First compute the costs of the possible neighbours chosen_neighbour_costs = [ scp_instance.scp_instance_column_costs[c] for c in neighbours ] # We add the one which grants less cost chosen_neighbour = neighbours[np.argmin(chosen_neighbour_costs)] # Let's perform the swap new_solution = current_solution.copy() new_solution.remove(swap_column) if chosen_neighbour not in new_solution: new_solution.append(chosen_neighbour) # Compute the new cost new_cost = np.sum([ scp_instance.scp_instance_column_costs[col] for col in new_solution ]) # It is better, our current cost is the new cost if new_cost < current_cost: current_cost = new_cost # And our current solution is the new_solution current_solution = new_solution # Updates # Columns Availability for col in current_solution: columns_availability[col] = 0 # Do not forget the swap column! columns_availability[swap_column] = 1 # Tabu Search # The neighbour tabu_columns[chosen_neighbour] += 1 # The swap column tabu_columns[swap_column] += 1 # Check if we have to reset Tabu Search for col, tabu_value in enumerate(tabu_columns): if tabu_value >= 20: tabu_columns[col] = 0 # Rows Frequency Solution rows_freq_solution = [ 0 for i in range(problem_matrix.shape[0]) ] for col in current_solution: for row_idx, _ in enumerate(rows_freq_solution): rows_freq_solution[row_idx] += problem_matrix[row_idx, col] # History history.append(new_cost) # Iterations nr_iteration += 1 nr_patience = 0 else: # History history.append(new_cost) nr_iteration += 1 nr_patience += 1 # Final solutions final_cost = current_cost final_solution = current_solution.copy() # History history.append(final_cost) return initial_solution, initial_cost, final_solution, final_cost, history