예제 #1
0
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
예제 #2
0
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
예제 #3
0
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}")
예제 #4
0
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