# Copyright 2023 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.
"""| This module defines the BinaryQuadraticModel with the Hamiltonian,
.. math::
H = \\sum_{i\\neq j} J_{ij}\\sigma_i \\sigma_j + \\sum_{i} h_{i}\\sigma_i,
| in an Ising form and
.. math::
H = \\sum_{ij} Q_{ij}x_i x_j + \\sum_{i} H_{i}x_i,
| in a QUBO form.
| The methods and usage are basically the same as `dimod <https://github.com/dwavesystems/dimod>`_.
"""
from __future__ import annotations
import cimod
import cimod.cxxcimod as cxxcimod
import dimod
import openjij.cxxjij as cxxjij
[docs]
def make_BinaryQuadraticModel(linear: dict, quadratic: dict, sparse):
"""BinaryQuadraticModel factory.
Returns:
generated BinaryQuadraticModel class
"""
Base = cimod.make_BinaryQuadraticModel(linear, quadratic, sparse)
class BinaryQuadraticModel(Base):
"""Represents Binary quadratic model.
Indices are listed in self.indices.
Attributes:
vartype (dimod.Vartype): variable type SPIN or BINARY
linear (dict): represents linear term
quadratic (dict): represents quadratic term
offset (float): represents constant energy term when convert to SPIN from BINARY
num_variables (int): represents number of variables in the model
variables (list): represents variables of the binary quadratic model
"""
def __init__(self, *args, **kwargs):
"""BinaryQuadraticModel constructor.
Args:
linear (dict): linear biases.
quadratic (dict): quadratic biases
offset (float): offset
vartype (openjij.variable_type.Vartype): vartype
kwargs:
"""
super().__init__(*args, **kwargs)
self.model_type = "openjij.BinaryQuadraticModel"
def get_cxxjij_ising_graph(self):
"""Generate cxxjij Ising graph from the interactions.
Returns:
cxxjij.graph.Dense or cxxjij.graph.Sparse:
offset (float): offset of the energy due to qubo->ising transformation
"""
# if sparse is true, select `cxxjij.graph.CSRSparse` graph type,
# else, select `cxxjij.graph.Dense` graph type.
if sparse:
old_vartype = self.vartype
self.change_vartype("SPIN")
GraphClass = (
cxxjij.graph.CSRSparse
)
offset = self.offset
sparse_mat = self.interaction_matrix()
self.change_vartype(old_vartype)
return GraphClass(sparse_mat), offset
else:
old_vartype = self.vartype
self.change_vartype("SPIN")
GraphClass = (
cxxjij.graph.Dense
)
# initialize with interaction matrix.
mat = self.interaction_matrix()
num_variables = mat.shape[0] - 1
dense = GraphClass(num_variables)
dense.set_interaction_matrix(mat)
offset = self.offset
self.change_vartype(old_vartype)
return dense, offset
# compatible with the previous version
def calc_energy(self, sample, **kwargs):
return self.energy(sample, **kwargs)
# compatible with the previous version
@property
def indices(self):
return self.variables
return BinaryQuadraticModel
[docs]
def make_BinaryQuadraticModel_from_JSON(obj: dict):
"""Make BinaryQuadraticModel from JSON.
Returns:
corresponding BinaryQuadraticModel type
"""
label = obj["variable_labels"][0]
if isinstance(label, list):
# convert to tuple
label = tuple(label)
mock_linear = {label: 1.0}
if obj["version"]["bqm_schema"] == "3.0.0-dense":
sparse = False
elif obj["version"]["bqm_schema"] == "3.0.0":
sparse = True
else:
raise TypeError("Invalid bqm_schema")
return make_BinaryQuadraticModel(mock_linear, {}, sparse)
[docs]
def BinaryQuadraticModel(linear, quadratic, *args, **kwargs):
"""Generate BinaryQuadraticModel object.
Attributes:
vartype (dimod.Vartype): variable type SPIN or BINARY
linear (dict): represents linear term
quadratic (dict): represents quadratic term
offset (float): represents constant energy term when convert to SPIN from BINARY
num_variables (int): represents number of variables in the model
variables (list): represents variables of the binary quadratic model
Args:
linear (dict): linear biases
quadratic (dict): quadratic biases
offset (float): offset
vartype (openjij.variable_type.Vartype): vartype ('SPIN' or 'BINARY')
kwargs:
Returns:
generated BinaryQuadraticModel
Examples:
BinaryQuadraticModel can be initialized by specifing h and J::
>>> h = {0: 1, 1: -2}
>>> J = {(0, 1): -1, (1, 2): -3, (2, 3): 0.5}
>>> bqm = oj.BinaryQuadraticModel(self.h, self.J)
You can also use strings and tuples of integers (up to 4 elements) as indices::
>>> h = {'a': 1, 'b': -2}
>>> J = {('a', 'b'): -1, ('b', 'c'): -3, ('c', 'd'): 0.5}
>>> bqm = oj.BinaryQuadraticModel(self.h, self.J)
"""
sparse_option = kwargs.pop("sparse", False)
Model = make_BinaryQuadraticModel(linear, quadratic, sparse_option)
def __extract_offset_and_vartype(*args, **kwargs):
if kwargs == {}:
if len(args) == 0:
raise TypeError(
f"Offset or vartype is configured incorrectly. Vartype must be set."
)
elif len(args) == 1:
offset = 0.0
[vartype] = args
elif len(args) == 2:
[offset, vartype] = args
else:
raise TypeError(
f"Offset or vartype is configured incorrectly. Vartype must be set."
)
else:
if "offset" in kwargs and "vartype" in kwargs:
offset = kwargs["offset"]
vartype = kwargs["vartype"]
elif "offset" in kwargs:
if len(args) != 1:
raise TypeError(
f"Offset or vartype is configured incorrectly. Vartype must be set."
)
offset = kwargs["offset"]
[vartype] = args
elif "vartype" in kwargs:
if len(args) >= 2:
raise TypeError(
f"Offset or vartype is configured incorrectly. Vartype must be set."
)
elif len(args) == 0:
offset = 0.0
elif len(args) == 1:
[offset] = args
vartype = kwargs["vartype"]
else:
raise TypeError(
f"Offset or vartype is configured incorrectly. Vartype must be set."
)
return offset, vartype
offset, vartype = __extract_offset_and_vartype(*args, **kwargs)
return Model(linear, quadratic, offset, vartype)
# classmethods
[docs]
def bqm_from_numpy_matrix(
mat, variables: list = None, offset=0.0, vartype="BINARY", **kwargs
):
if variables is None:
# generate array
num_variables = mat.shape[0]
variables = list(range(num_variables))
sparse_option = kwargs.pop("sparse", False)
return make_BinaryQuadraticModel(
{variables[0]: 1.0}, {}, sparse_option
).from_numpy_matrix(mat, variables, offset, vartype, True, **kwargs)
BinaryQuadraticModel.from_numpy_matrix = bqm_from_numpy_matrix
[docs]
def bqm_from_qubo(Q, offset=0.0, **kwargs):
sparse_option = kwargs.pop("sparse", False)
return make_BinaryQuadraticModel(
{}, Q, sparse_option
).from_qubo(Q, offset, **kwargs)
BinaryQuadraticModel.from_qubo = bqm_from_qubo
[docs]
def bqm_from_ising(linear, quadratic, offset=0.0, **kwargs):
sparse_option = kwargs.pop("sparse", False)
return make_BinaryQuadraticModel(
linear, quadratic, sparse_option
).from_ising(linear, quadratic, offset, **kwargs)
BinaryQuadraticModel.from_ising = bqm_from_ising
BinaryQuadraticModel.from_serializable = (
lambda obj, **kwargs: make_BinaryQuadraticModel_from_JSON(obj).from_serializable(
obj, **kwargs
)
)
[docs]
def make_BinaryPolynomialModel(polynomial, index_type=None, tuple_size=0):
class BinaryPolynomialModel(
cimod.make_BinaryPolynomialModel(polynomial, index_type, tuple_size)
):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.model_type = "openjij.BinaryPolynomialModel"
def get_cxxjij_ising_graph(self):
return cxxjij.graph.Polynomial(self.to_serializable())
def calc_energy(self, sample, omp_flag=True):
return self.energy(sample, omp_flag)
return BinaryPolynomialModel
[docs]
def make_BinaryPolynomialModel_from_JSON(obj):
if obj["type"] != "BinaryPolynomialModel":
raise Exception('Type must be "BinaryPolynomialModel"')
mock_polynomial = {}
if obj["index_type"] == "IndexType.INT":
mock_polynomial = {(0, 1): 1}
elif obj["index_type"] == "IndexType.STRING":
mock_polynomial = {("a", "b"): 1}
elif obj["index_type"] == "IndexType.INT_TUPLE_2":
mock_polynomial = {((0, 1), (1, 2)): 1}
elif obj["index_type"] == "IndexType.INT_TUPLE_3":
mock_polynomial = {((0, 1, 2), (1, 2, 3)): 1}
elif obj["index_type"] == "IndexType.INT_TUPLE_4":
mock_polynomial = {((0, 1, 2, 3), (1, 2, 3, 4)): 1}
else:
raise TypeError("Invalid types of polynomial")
return make_BinaryPolynomialModel(mock_polynomial)
[docs]
def BinaryPolynomialModel(*args, **kwargs):
if kwargs == {}:
if len(args) <= 1:
raise TypeError("Invalid argument for this function")
elif len(args) == 2:
if isinstance(args[0], dict):
return _BinaryPolynomialModel_from_dict(args[0], _to_cxxcimod(args[1]))
else:
raise TypeError("Invalid argument for this function")
elif len(args) == 3:
key_condition = isinstance(args[0], list) or isinstance(args[0], tuple)
val_condition = isinstance(args[1], list) or isinstance(args[1], tuple)
if key_condition and val_condition:
return _BinaryPolynomialModel_from_list(
args[0], args[1], _to_cxxcimod(args[2])
)
else:
raise TypeError("Invalid argument for this function")
else:
raise TypeError("Invalid argument for this function")
else:
if "keys" in kwargs and "values" in kwargs and "vartype" in kwargs:
key_condition = isinstance(kwargs["keys"], list) or isinstance(
kwargs["keys"], tuple
)
val_condition = isinstance(kwargs["values"], list) or isinstance(
kwargs["values"], tuple
)
if key_condition and val_condition:
return _BinaryPolynomialModel_from_list(
kwargs["keys"], kwargs["values"], _to_cxxcimod(kwargs["vartype"])
)
else:
raise TypeError("Invalid argument for this function")
elif "polynomial" in kwargs and "vartype" in kwargs:
if isinstance(kwargs["polynomial"], dict):
return _BinaryPolynomialModel_from_dict(
kwargs["polynomial"], _to_cxxcimod(kwargs["vartype"])
)
else:
raise TypeError("Invalid argument for this function")
elif "values" in kwargs and "vartype" in kwargs:
if len(args) != 1:
raise TypeError("Invalid argument for this function")
key_condition = isinstance(args[0], list) or isinstance(args[0], tuple)
val_condition = isinstance(kwargs["values"], list) or isinstance(
kwargs["values"], tuple
)
if key_condition and val_condition:
return _BinaryPolynomialModel_from_list(
args[0], kwargs["values"], _to_cxxcimod(kwargs["vartype"])
)
else:
raise TypeError("Invalid argument for this function")
elif "vartype" in kwargs:
if len(args) == 1:
if isinstance(args[0], dict):
return _BinaryPolynomialModel_from_dict(
args[0], _to_cxxcimod(kwargs["vartype"])
)
else:
raise TypeError("Invalid argument for this function")
elif len(args) == 2:
key_condition = isinstance(args[0], list) or isinstance(args[0], tuple)
val_condition = isinstance(args[1], list) or isinstance(args[1], tuple)
if key_condition and val_condition:
return _BinaryPolynomialModel_from_list(
args[0], args[1], _to_cxxcimod(kwargs["vartype"])
)
else:
raise TypeError("Invalid argument for this function")
else:
raise TypeError("Invalid argument for this function")
else:
raise TypeError("Invalid argument for this function")
def _BinaryPolynomialModel_from_dict(polynomial: dict, vartype):
Model = make_BinaryPolynomialModel(polynomial)
return Model(polynomial, _to_cxxcimod(vartype))
def _BinaryPolynomialModel_from_list(keys: list, values: list, vartype):
if len(keys) == 0:
Model = make_BinaryPolynomialModel({})
return Model(keys, values, _to_cxxcimod(vartype))
i = 0
label = None
while i < len(keys):
if len(keys[i]) > 0:
label = keys[i][0]
break
i += 1
if label is None:
Model = make_BinaryPolynomialModel({(): 1.0})
return Model(keys, values, _to_cxxcimod(vartype))
else:
if isinstance(label, list):
label = tuple(label)
mock_polynomial = {(label,): 1.0}
Model = make_BinaryPolynomialModel(mock_polynomial)
return Model(keys, values, _to_cxxcimod(vartype))
[docs]
def make_BinaryPolynomialModel_from_hising(*args, **kwargs):
if kwargs == {}:
if len(args) == 0:
raise TypeError("Invalid argument for this function")
elif len(args) == 1:
if isinstance(args[0], dict):
return _make_BinaryPolynomialModel_from_hising_from_dict(args[0])
else:
raise TypeError("Invalid argument for this function")
elif len(args) == 2:
key_condition = isinstance(args[0], list) or isinstance(args[0], tuple)
val_condition = isinstance(args[1], list) or isinstance(args[1], tuple)
if key_condition and val_condition:
return _make_BinaryPolynomialModel_from_hising_from_list(
args[0], args[1]
)
else:
raise TypeError("Invalid argument for this function")
else:
raise TypeError("Invalid argument for this function")
else:
if "keys" in kwargs and "values" in kwargs:
key_condition = isinstance(kwargs["keys"], list) or isinstance(
kwargs["keys"], tuple
)
val_condition = isinstance(kwargs["values"], list) or isinstance(
kwargs["values"], tuple
)
if key_condition and val_condition:
return _make_BinaryPolynomialModel_from_hising_from_list(
kwargs["keys"], kwargs["values"]
)
else:
raise TypeError("Invalid argument for this function")
elif "values" in kwargs:
if len(args) != 1:
raise TypeError("Invalid argument for this function")
key_condition = isinstance(args[0], list) or isinstance(args[0], tuple)
val_condition = isinstance(kwargs["values"], list) or isinstance(
kwargs["values"], tuple
)
if key_condition and val_condition:
return _make_BinaryPolynomialModel_from_hising_from_list(
args[0], kwargs["values"]
)
else:
raise TypeError("Invalid argument for this function")
elif "polynomial" in kwargs:
if len(args) != 0:
raise TypeError("Invalid argument for this function")
if isinstance(kwargs["polynomial"], dict):
_make_BinaryPolynomialModel_from_hising_from_dict(kwargs["polynomial"])
else:
raise TypeError("Invalid argument for this function")
else:
raise TypeError("Invalid argument for this function")
def _make_BinaryPolynomialModel_from_hising_from_dict(polynomial: dict):
return make_BinaryPolynomialModel(polynomial).from_hising(polynomial)
def _make_BinaryPolynomialModel_from_hising_from_list(keys: list, values: list):
if len(keys) == 0:
return make_BinaryPolynomialModel({}).from_hising(keys, values)
i = 0
label = None
while i < len(keys):
if len(keys[i]) > 0:
label = keys[i][0]
break
i += 1
if label is None:
return make_BinaryPolynomialModel({(): 1.0}).from_hising(keys, values)
else:
if isinstance(label, list):
label = tuple(label)
mock_polynomial = {(label,): 1.0}
return make_BinaryPolynomialModel(mock_polynomial).from_hising(keys, values)
[docs]
def make_BinaryPolynomialModel_from_hubo(*args, **kwargs):
if kwargs == {}:
if len(args) == 0:
raise TypeError("Invalid argument for this function")
elif len(args) == 1:
if isinstance(args[0], dict):
return _make_BinaryPolynomialModel_from_hubo_from_dict(args[0])
else:
raise TypeError("Invalid argument for this function")
elif len(args) == 2:
key_condition = isinstance(args[0], list) or isinstance(args[0], tuple)
val_condition = isinstance(args[1], list) or isinstance(args[1], tuple)
if key_condition and val_condition:
return _make_BinaryPolynomialModel_from_hubo_from_list(args[0], args[1])
else:
raise TypeError("Invalid argument for this function")
else:
raise TypeError("Invalid argument for this function")
else:
if "keys" in kwargs and "values" in kwargs:
key_condition = isinstance(kwargs["keys"], list) or isinstance(
kwargs["keys"], tuple
)
val_condition = isinstance(kwargs["values"], list) or isinstance(
kwargs["values"], tuple
)
if key_condition and val_condition:
return _make_BinaryPolynomialModel_from_hubo_from_list(
kwargs["keys"], kwargs["values"]
)
else:
raise TypeError("Invalid argument for this function")
elif "values" in kwargs:
if len(args) != 1:
raise TypeError("Invalid argument for this function")
key_condition = isinstance(args[0], list) or isinstance(args[0], tuple)
val_condition = isinstance(kwargs["values"], list) or isinstance(
kwargs["values"], tuple
)
if key_condition and val_condition:
return _make_BinaryPolynomialModel_from_hubo_from_list(
args[0], kwargs["values"]
)
else:
raise TypeError("Invalid argument for this function")
elif "polynomial" in kwargs:
if len(args) != 0:
raise TypeError("Invalid argument for this function")
if isinstance(kwargs["polynomial"], dict):
_make_BinaryPolynomialModel_from_hubo_from_dict(kwargs["polynomial"])
else:
raise TypeError("Invalid argument for this function")
else:
raise TypeError("Invalid argument for this function")
def _make_BinaryPolynomialModel_from_hubo_from_dict(polynomial: dict):
return make_BinaryPolynomialModel(polynomial).from_hubo(polynomial)
def _make_BinaryPolynomialModel_from_hubo_from_list(keys: list, values: list):
if len(keys) == 0:
return make_BinaryPolynomialModel({}).from_hubo(keys, values)
i = 0
label = None
while i < len(keys):
if len(keys[i]) > 0:
label = keys[i][0]
break
i += 1
if label is None:
return make_BinaryPolynomialModel({(): 1.0}).from_hubo(keys, values)
else:
if isinstance(label, list):
label = tuple(label)
mock_polynomial = {(label,): 1.0}
return make_BinaryPolynomialModel(mock_polynomial).from_hubo(keys, values)
def _to_cxxcimod(vartype):
# convert to cxxcimod type
if isinstance(vartype, cxxcimod.Vartype):
return vartype
vartype = dimod.as_vartype(vartype)
if vartype == dimod.SPIN:
return cxxcimod.Vartype.SPIN
if vartype == dimod.BINARY:
return cxxcimod.Vartype.BINARY
# classmethods
BinaryPolynomialModel.from_serializable = (
lambda obj: make_BinaryPolynomialModel_from_JSON(obj).from_serializable(obj)
)
BinaryPolynomialModel.from_hising = (
lambda *args, **kwargs: make_BinaryPolynomialModel_from_hising(*args, **kwargs)
)
BinaryPolynomialModel.from_hubo = (
lambda *args, **kwargs: make_BinaryPolynomialModel_from_hubo(*args, **kwargs)
)