def exact_cover_bqm(problem_set, subsets): """Returns a BQM for an exact cover. An exact cover is a collection of subsets of `problem_set` that contains every element in `problem_set` exactly once. Args: problem_set : iterable An iterable of unique numbers. subsets : list(iterable(numeric)) A list of subsets of `problem_set` used to find an exact cover. """ bqm = BinaryQuadraticModel({}, {}, 0, 'BINARY') for element in problem_set: bqm.offset += 1 for i in range(len(subsets)): if element in subsets[i]: bqm.add_variable(i, -1) for j in range(i): if element in subsets[j]: bqm.add_interaction(i, j, 2) return bqm
def anti_crossing_clique(num_variables): """Anti crossing problems with a single clique. Given the number of variables, the code will generate a clique of size num_variables/2, each variable ferromagnetically interacting with a partner variable with opposite bias. A single variable in the cluster will have no bias applied. Args: num_variables (int): Number of variables used to generate the problem. Must be an even number greater than 6. Returns: :obj:`.BinaryQuadraticModel`. """ if num_variables % 2 != 0 or num_variables < 6: raise ValueError('num_variables must be an even number > 6') bqm = BinaryQuadraticModel({}, {}, 0, 'SPIN') hf = int(num_variables / 2) for n in range(hf): for m in range(n + 1, hf): bqm.add_interaction(n, m, -1) bqm.add_interaction(n, n + hf, -1) bqm.add_variable(n, 1) bqm.add_variable(n + hf, -1) bqm.set_linear(1, 0) return bqm
class QbsolvSMP: def __init__(self, matching, mode="bqm"): self.matching = matching self.mode = mode self.p = None self.p1 = None self.p2 = None self.qubo = None self.encoding = {} self.__create_encoding() def create_qubo(self): self.p, self.p1, self.p2 = self.get_default_penalties() assert self.p is not None and self.p1 is not None and self.p2 is not None length = self.matching.size ** 2 if self.mode == "np": self.qubo = np.zeros((length, length), dtype=np.object) elif self.mode == "bqm": self.qubo = BinaryQuadraticModel({}, {}, 0.0, dimod.BINARY) else: raise Exception(f"unknown mode: {self.mode}") for j in range(length): for i in range(j + 1): if i == j: self.__assign_qubo(j, i, - 2 * (self.matching.size - 1) * self.p1) else: self.__assign_qubo(j, i, 1) # match of current line == current "selected" matched pair w_j = self.encoding[j][1] m_j = self.encoding[j][0] # match of current column == current match under review w_i = self.encoding[i][1] m_i = self.encoding[i][0] if m_j == m_i or w_j == w_i: self.__assign_qubo(j, i, self.p) continue prefs_m_j = self.matching.prefers(m_j, w_i, w_j) and self.matching.prefers(w_i, m_j, m_i) prefs_w_j = self.matching.prefers(w_j, m_i, m_j) and self.matching.prefers(m_i, w_j, w_i) if prefs_m_j and prefs_w_j: self.__assign_qubo(j, i, 2 * self.p2) elif prefs_m_j: self.__assign_qubo(j, i, self.p2) elif prefs_w_j: self.__assign_qubo(j, i, self.p2) return self def solve(self, verbose=False, num_repeats=100, target=None): if self.qubo is None: self.create_qubo() if self.mode == "np": # more memory intensive response = QBSolv().sample(BinaryQuadraticModel.from_numpy_matrix(self.qubo), target=target) elif self.mode == "bqm": response = QBSolv().sample(self.qubo, num_repeats=num_repeats, target=target) else: raise Exception(f"mode: {self.mode} cannot be solved yet") if verbose: print(response) energies = list(response.data_vectors['energy']) min_en = min(energies) ret_match = self.encode(list(response.samples())[energies.index(min_en)]) return Solution(self.matching, ret_match) def solve_multi(self, verbose=True, num_repeats=100, target=None): if self.qubo is None: self.create_qubo() if self.mode == "np": # more memory intensive response = QBSolv().sample(BinaryQuadraticModel.from_numpy_matrix(self.qubo), target=target) elif self.mode == "bqm": response = QBSolv().sample(self.qubo, num_repeats=num_repeats, target=target) else: raise Exception(f"mode: {self.mode} cannot be solved yet") if verbose: print(response) n = self.matching.size stable_energy = -3 / 2 * self.p1 * (n - 1) * n energies = list(response.data_vectors['energy']) samples = enumerate(list(response.samples())) allowed_samples = [sample for idx, sample in samples if energies[idx] == stable_energy] return [Solution(self.matching, self.encode(sample)) for sample in allowed_samples] def __create_encoding(self): qubo_size = 0 for male in self.matching.males: for female in self.matching.females: self.encoding[qubo_size] = (male, female) qubo_size += 1 def encode(self, sample): match = {} for index, element in enumerate(sample.keys()): if index >= len(self.encoding): return match if sample[element] == 1: match[self.encoding[index][0]] = self.encoding[index][1] return match def get_default_penalties(self): p1 = 1 p2 = p1 + 1 p = 2 * (p1 * self.matching.size + p2) + 1 return p, p1, p2 def __assign_qubo(self, i, j, val): if self.mode == "np": self.qubo[j][i] += val elif self.mode == "bqm": if i == j: self.qubo.add_variable(j, val) else: self.qubo.add_interaction(j, i, val) else: raise Exception(f"Unknown mode: {self.mode}")
class QUBO_SMTI: def __init__(self, matching, mode="bqm"): self.p = None self.p1 = None self.p2 = None self.qubo = None self.encoding = None self.rev_encoding = None self.qubo_size = 0 self.matching = matching self.pre_evaluated_solution = None self.mode = mode self.token = os.getenv('TOKEN', "") def create_encoding(self): encoding = {} rev_encoding = {} qubo_size = 0 for male in self.matching.males: for female in self.matching.females: if self.matching.is_acceptable(male, female): encoding[qubo_size] = (male, female) rev_encoding[(male, female)] = qubo_size qubo_size += 1 if self.qubo_size == 0 or self.qubo_size == 1: self.pre_evaluated_solution = {} for m, w in encoding.values(): self.pre_evaluated_solution[m] = w self.encoding = encoding self.rev_encoding = rev_encoding self.qubo_size = qubo_size def encode_qa(self, sample): valid = True match = {} for index, element in enumerate(sample): if element == 1: if self.encoding[index][0] in match.keys(): valid = False match[self.encoding[index][0]] = self.encoding[index][1] return match, valid def encode(self, sample): match = {} valid = True for index, element in enumerate(sample.keys()): if index >= len(self.encoding): return match if sample[element] == 1: if self.encoding[index][0] in match.keys(): valid = False match[self.encoding[index][0]] = self.encoding[index][1] return match, valid def __create_qubo_matrix(self, length): if self.mode == "np": self.qubo = np.zeros((length, length)) elif self.mode == "bqm": self.qubo = BinaryQuadraticModel({}, {}, 0.0, dimod.BINARY) elif self.mode == "qa": self.qubo = {} else: raise Exception(f"unknown mode: {self.mode}") def __assign_qubo(self, j, i, val): """ helper function to assign values to the qubo :param j: :param i: :param val: :return: """ if self.mode == "np": self.qubo[j][i] += val elif self.mode == "bqm": if i == j: self.qubo.add_variable(j, val) else: self.qubo.add_interaction(j, i, val) elif self.mode == "qa": if (i, j) not in self.qubo.keys(): self.qubo[(j, i)] = val else: self.qubo[(j, i)] = self.qubo[(j, i)] + val else: raise Exception(f"Unknown mode: {self.mode}") def pre_process(self): self.create_encoding() assert self.encoding is not None self.p, self.p1, self.p2 = self.get_default_penalties() assert self.p is not None and self.p2 is not None length = self.qubo_size is_acceptable = self.matching.is_acceptable prefers = self.matching.prefers self.__create_qubo_matrix(length) for i in range(self.qubo_size): m = self.encoding[i][0] w = self.encoding[i][1] self.__assign_qubo(i, i, -self.p1) for j in range(i): m_j = self.encoding[j][0] w_j = self.encoding[j][1] if m == m_j or w == w_j: self.__assign_qubo(j, i, self.p) for w_i in self.matching.females: if not prefers(m, w, w_i) and is_acceptable(m, w_i): m_w_i = self.rev_encoding[(m, w_i)] self.__assign_qubo(m_w_i, m_w_i, -self.p2) for m_i in self.matching.males: if not prefers(w, m, m_i) and is_acceptable(m_i, w): m_i_w = self.rev_encoding[(m_i, w)] self.__assign_qubo(m_i_w, m_i_w, -self.p2) for m_i in self.matching.males: for w_i in self.matching.females: if is_acceptable(m_i, w) and is_acceptable(w_i, m): m_w_i = self.rev_encoding[(m, w_i)] # note that in the equation i and j were used w_m_i = self.rev_encoding[(m_i, w)] if not (prefers(m, w, w_i) or prefers(w, m, m_i)): if m_w_i <= w_m_i: self.__assign_qubo(m_w_i, w_m_i, self.p2) else: self.__assign_qubo(w_m_i, m_w_i, self.p2) return self def get_chain_stength(self): assert self.mode == "bqm" qubo_np = self.qubo.to_numpy_matrix() return np.amax(qubo_np) def solve_qa(self, verbose=True, num_reads=100): assert self.token is not None assert self.mode == "bqm" if self.qubo is None: self.pre_process() if verbose: print(f"Solving SMTI with: {SOLVER}") print(f"Optimal Solution: {-(len(self.encoding) * self.p2 + self.matching.size * self.p1)}") chain_strength = self.get_chain_stength() + 1 # max element in qubo matrix + epsilon solver_limit = len(self.encoding) # solver_limit => size of qubo matrix G = nx.complete_graph(solver_limit) dw_solver = DWaveSampler(solver=SOLVER, token=self.token, endpoint=ENDPOINT) embedding = minorminer.find_embedding(G.edges, dw_solver.edgelist) fixed_embedding = FixedEmbeddingComposite(dw_solver, embedding) result = fixed_embedding.sample(self.qubo, num_reads=num_reads, chain_strength=chain_strength) dw_solver.client.close() # clean up all the thread mess the client creates so it does not block my code if verbose: print(result) for index, (sample, energy, occ, chain) in enumerate(result.record): match_, _ = self.encode_qa(sample.tolist()) stable_, size_ = Solution(self.matching, match_).is_stable() print(f"{index}: ", match_, size_, stable_) samples = pd.DataFrame() for sample, energy, occ, chain in result.record: match, valid = self.encode_qa(sample.tolist()) stable, size = Solution(self.matching, match).is_stable() samples = samples.append({"match": match, "sample": sample.tolist(), "energy": energy, "occ": occ, "chain": chain, "valid": valid, "stable": stable, "size": size}, ignore_index=True) return samples def compute_energy(self, vector): assert len(vector) == len(self.qubo) assert self.mode == "np" vector = np.array(vector) return vector.dot(self.qubo).dot(vector.T) def solve(self, verbose=False, num_repeats=50, target=None, debug=False): if self.qubo is None: self.pre_process() if verbose: print("Solving MAX-SMTI with Qbsolv") if self.qubo_size == 0 or self.qubo_size == 1: if debug: return None return Solution(self.matching, self.pre_evaluated_solution) if self.mode == "np": # more memory intensive response = QBSolv().sample(BinaryQuadraticModel.from_numpy_matrix(self.qubo), num_repeats=num_repeats, target=target) elif self.mode == "bqm": response = QBSolv().sample(self.qubo, num_repeats=num_repeats, target=target) else: raise Exception(f"mode: {self.mode} cannot be solved yet") if debug: return response if verbose: print(response) for index, sample in enumerate(list(response.samples())): match, valid = self.encode(sample) print(index, ":", Solution(self.matching, match).is_stable(), match, valid) energies = list(response.data_vectors['energy']) min_en = min(energies) ret_match, valid = self.encode(list(response.samples())[energies.index(min_en)]) return Solution(self.matching, ret_match, energy=min_en) def get_optimal_energy(self, size): return -(len(self.encoding) * self.p2 + size * self.p1) def solve_multi_data(self, verbose=False, target=None, num_repeats=200): if self.qubo is None: self.pre_process() if verbose: print("Solving multiple solutions of MAX-SMTI with Qbsolv") if self.qubo_size == 0 or self.qubo_size == 1: return [Solution(self.matching, self.pre_evaluated_solution)] if self.mode == "np": # more memory intensive response = QBSolv().sample(BinaryQuadraticModel.from_numpy_matrix(self.qubo), num_repeats=num_repeats, target=target, algorithm=SOLUTION_DIVERSITY) elif self.mode == "bqm": response = QBSolv().sample(self.qubo, num_repeats=num_repeats, target=target) else: raise Exception(f"mode: {self.mode} cannot be solved yet") if verbose: print(response) for index, sample in enumerate(list(response.samples())): match, valid = self.encode(sample) print(index, ":", Solution(self.matching, match).is_stable(), match, valid) samples = pd.DataFrame() for sample, energy, occ in response.record: match, valid = self.encode_qa(sample.tolist()) stable, size = Solution(self.matching, match).is_stable() samples = samples.append({"match": match, "sample": sample.tolist(), "energy": energy, "occ": occ, "valid": valid, "stable": stable, "size": size}, ignore_index=True) return samples def solve_multi(self, verbose=False, num_repeats=200): if self.qubo is None: self.pre_process() if verbose: print("Solving multiple solutions of MAX-SMTI with Qbsolv") if self.qubo_size == 0 or self.qubo_size == 1: return [Solution(self.matching, self.pre_evaluated_solution)] if self.mode == "np": # more memory intensive response = QBSolv().sample(BinaryQuadraticModel.from_numpy_matrix(self.qubo), num_repeats=num_repeats) elif self.mode == "bqm": response = QBSolv().sample(self.qubo, num_repeats=num_repeats) else: raise Exception(f"mode: {self.mode} cannot be solved yet") if verbose: print(response) for index, sample in enumerate(list(response.samples())): match, valid = self.encode(sample) print(index, ":", Solution(self.matching, match).is_stable(), match, valid) opt_en = self.get_optimal_energy(self.matching.size) solutions = [] for sample, energy, occ in response.record: if energy == opt_en: match, valid = self.encode_qa(sample.tolist()) if verbose and not valid: print("Invalid encoding and valid energy!") solutions.append(Solution(self.matching, match)) return solutions def get_default_penalties(self): p1 = 1 p2 = self.matching.size p = self.matching.size * p2 + p1 return p, p1, p2