Source code for cimod.model.legacy.binary_quadratic_model

# Copyright 2022 Jij Inc.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import annotations

import cimod.cxxcimod as cxxcimod
import dimod
import numpy as np

from cimod.utils.decolator import recalc
from cimod.vartype import to_cxxcimod


[docs] def make_BinaryQuadraticModel(linear, quadratic): """BinaryQuadraticModel factory. Generate BinaryQuadraticModel class with the base class specified by the arguments linear and quadratic Args: linear (dict): linear bias quadratic (dict): quadratic bias Returns: generated BinaryQuadraticModel class """ # select base class index = set() base = None if linear != {}: index.add(next(iter(linear))) if quadratic != {}: index.add(next(iter(quadratic))[0]) index.add(next(iter(quadratic))[1]) if len(set(type(i) for i in index)) != 1: raise TypeError("invalid types of linear and quadratic") else: ind = next(iter(index)) if isinstance(ind, int): base = cxxcimod.BinaryQuadraticModel_Dict elif isinstance(ind, str): base = cxxcimod.BinaryQuadraticModel_str_Dict elif isinstance(ind, tuple): if len(ind) == 2: base = cxxcimod.BinaryQuadraticModel_tuple2_Dict elif len(ind) == 3: base = cxxcimod.BinaryQuadraticModel_tuple3_Dict elif len(ind) == 4: base = cxxcimod.BinaryQuadraticModel_tuple4_Dict else: raise TypeError("invalid length of tuple") else: raise TypeError("invalid types of linear and quadratic") # now define class class BinaryQuadraticModel(base): """Represents Binary quadratic model. Note that the indices are converted to the integers internally. The dictionaries between indices and integers are self.ind_to_num (indices -> integers) and self.num_to_ind (integers -> indices). Indices are listed in self._indices. Attributes: vartype (cimod.VariableType): variable type SPIN or BINARY linear (dict): represents linear term quadratic (dict): represents quadratic term adj (dict): represents adjacency indices (list): labels of each variables sorted by results variables ind_to_num (list): map which specifies where the index is in self._indices offset (float): represents constant energy term when convert to SPIN from BINARY """ def __init__(self, linear, quadratic, offset, vartype): super().__init__(linear, quadratic, offset, to_cxxcimod(vartype)) self._init_process() def _init_process(self): # set recalculate flag True # Be sure to enable this flag when variables are changed. self._re_calculate = True self._re_calculate_indices = True # interaction_matrix self._interaction_matrix = None # indices self._indices = None # ind to num self._ind_to_num = None @property def linear(self): return self.get_linear() @property def quadratic(self): return self.get_quadratic() @property def num_variables(self): return self.get_num_variables() @property def variables(self): return self.get_variables() @property def vartype(self): vartype = super().get_vartype() if vartype == cxxcimod.Vartype.SPIN: return dimod.SPIN else: # BINARY return dimod.BINARY @property def offset(self): return self.get_offset() @property def indices(self): ind, _ = self.update_indices() return ind def update_indices(self): """calculate self._indices and self.ind_to_num Returns: self._indices and self._ind_to_num """ if self._re_calculate_indices is True: self._indices = self._generate_indices() self._ind_to_num = {ind: num for num, ind in enumerate(self._indices)} self._re_calculate_indices = False return self._indices, self._ind_to_num def interaction_matrix(self): """make Dense-type interaction matrix The Ising model: E = ΣJ_ij σiσj + Σhiσi Interaction matrix -> H_ij = J_ij + J_ji, H_ii = hi QUBO: E = Σ1/2Q_ij q_iq_j + ΣQ_ii q_i Returns: numpy.ndarray: interactioin matrix H_{ij} or Q_{ij}, energy_bias (float) """ if self._re_calculate is True: # calculate interaction matrix indices, ind_to_num = self.update_indices() self._interaction_matrix = super().interaction_matrix(indices) self._re_calculate = False return self._interaction_matrix def energy(self, sample, sparse=False, convert_sample=False): """Determine the energy of the specified sample of a binary quadratic model. Args: sample (dict): single sample. sparse (bool): if true calculate energy by using adjacency matrix convert_sample (bool): if true the sample is automatically converted to self.vartype type. Returns: energy (float) """ indices, ind_to_num = self.update_indices() # convert sample to dict if isinstance(sample, list) or isinstance(sample, np.ndarray): sample = {indices[i]: elem for i, elem in enumerate(sample)} # convert samples to SPIN or BINARY if convert_sample: for k in sample.keys(): if sample[k] == -1 and self.vartype == dimod.BINARY: sample[k] = 0 if sample[k] == 0 and self.vartype == dimod.SPIN: sample[k] = -1 if sparse: return super().energy(sample) else: # convert to array if isinstance(sample, dict): state = [0] * len(sample) for k, v in sample.items(): state[ind_to_num[k]] = v sample = state sample = np.array(sample) int_mat = self.interaction_matrix() # calculate if self.vartype == dimod.BINARY: return ( np.dot(sample, np.dot(np.triu(int_mat), sample)) + self.get_offset() ) elif self.vartype == dimod.SPIN: linear_term = np.diag(int_mat) energy = ( np.dot(sample, np.dot(int_mat, sample)) - np.sum(linear_term) ) / 2 energy += np.dot(linear_term, sample) energy += self.get_offset() return energy def energies(self, samples_like, **kwargs): en_vec = [] for elem in samples_like: en_vec.append(self.energy(elem, **kwargs)) return en_vec @recalc def empty(self, *args, **kwargs): return super().empty(*args, **kwargs) @recalc def add_variable(self, *args, **kwargs): return super().add_variable(*args, **kwargs) @recalc def add_variables_from(self, *args, **kwargs): return super().add_variables_from(*args, **kwargs) @recalc def add_interaction(self, *args, **kwargs): return super().add_interaction(*args, **kwargs) @recalc def add_interactions_from(self, *args, **kwargs): return super().add_interactions_from(*args, **kwargs) @recalc def remove_variable(self, *args, **kwargs): return super().remove_variable(*args, **kwargs) @recalc def remove_variables_from(self, *args, **kwargs): return super().remove_variables_from(*args, **kwargs) @recalc def remove_interaction(self, *args, **kwargs): return super().remove_interaction(*args, **kwargs) @recalc def remove_interactions_from(self, *args, **kwargs): return super().remove_interactions_from(*args, **kwargs) @recalc def add_offset(self, *args, **kwargs): return super().add_offset(*args, **kwargs) @recalc def remove_offset(self, *args, **kwargs): return super().remove_offset(*args, **kwargs) @recalc def scale(self, *args, **kwargs): return super().scale(*args, **kwargs) @recalc def normalize(self, *args, **kwargs): return super().normalize(*args, **kwargs) @recalc def fix_variable(self, *args, **kwargs): return super().fix_variable(*args, **kwargs) @recalc def fix_variables(self, *args, **kwargs): return super().fix_variables(*args, **kwargs) @recalc def flip_variable(self, *args, **kwargs): return super().flip_variable(*args, **kwargs) @recalc def update(self, *args, **kwargs): return super().update(*args, **kwargs) @recalc def contract_variables(self, *args, **kwargs): return super().contract_variables(*args, **kwargs) def change_vartype(self, vartype, inplace=None): """ Create a binary quadratic model with the specified vartype Args: vartype (cimod.Vartype): SPIN or BINARY Returns: A new instance of the BinaryQuadraticModel class. """ cxxvartype = to_cxxcimod(vartype) if inplace is None: super().change_vartype(cxxvartype) return # in the case inplace is not None bqm = super().change_vartype(cxxvartype, inplace) self._re_calculate = True return BinaryQuadraticModel( bqm.get_linear(), bqm.get_quadratic(), bqm.get_offset(), vartype ) @classmethod def from_qubo(cls, Q, offset=0.0, **kwargs): linear = {} quadratic = {} for (u, v), bias in Q.items(): if u == v: linear[u] = bias else: quadratic[(u, v)] = bias return cls(linear, quadratic, offset, vartype=dimod.BINARY, **kwargs) @classmethod def from_ising(cls, linear, quadratic, offset=0.0, **kwargs): return cls(linear, quadratic, offset, vartype=dimod.SPIN, **kwargs) @classmethod def from_serializable(cls, obj): variable_labels = [ tuple(elem) if isinstance(elem, list) else elem for elem in obj["variable_labels"] ] # convert to linear biases linear = { elem: obj["linear_biases"][k] for k, elem in enumerate(variable_labels) } # convert to quadratic biases zipped_obj = zip( obj["quadratic_head"], obj["quadratic_tail"], obj["quadratic_biases"] ) quadratic = { (variable_labels[elem[0]], variable_labels[elem[1]]): elem[2] for elem in zipped_obj } # set offset offset = obj["offset"] # set vartype vartype = dimod.SPIN if obj["variable_type"] == "SPIN" else dimod.BINARY return cls(linear, quadratic, offset, vartype) return BinaryQuadraticModel
# for JSON
[docs] def make_BinaryQuadraticModel_from_JSON(obj): label = obj["variable_labels"][0] if isinstance(label, list): # convert to tuple label = tuple(label) mock_linear = {label: 1.0} return make_BinaryQuadraticModel(mock_linear, {})
[docs] def BinaryQuadraticModel(linear, quadratic, *args, **kwargs): Model = make_BinaryQuadraticModel(linear, quadratic) if len(args) == 2: [offset, vartype] = args return Model(linear, quadratic, offset, to_cxxcimod(vartype)) elif len(args) == 1 and "vartype" in kwargs: [offset] = args vartype = kwargs["vartype"] return Model(linear, quadratic, offset, to_cxxcimod(vartype)) elif len(args) == 1: [vartype] = args return Model(linear, quadratic, 0.0, to_cxxcimod(vartype)) elif len(args) == 0 and "vartype" in kwargs: vartype = kwargs["vartype"] return Model(linear, quadratic, 0.0, to_cxxcimod(vartype)) else: raise TypeError("invalid args for BinaryQuadraticModel")
# classmethods BinaryQuadraticModel.from_qubo = ( lambda Q, offset=0.0, **kwargs: make_BinaryQuadraticModel({}, Q).from_qubo( Q, offset, **kwargs ) ) BinaryQuadraticModel.from_ising = ( lambda linear, quadratic, offset=0.0, **kwargs: make_BinaryQuadraticModel( linear, quadratic ).from_ising(linear, quadratic, offset, **kwargs) ) BinaryQuadraticModel.from_serializable = ( lambda obj: make_BinaryQuadraticModel_from_JSON(obj).from_serializable(obj) )