Source code for eqc_models.base.base
- import os
- import logging
- from typing import (Dict, List, Tuple, Union)
- from warnings import warn
- import numpy as np
- from eqc_models.base.operators import Polynomial, QUBO, OperatorNotAvailableError
- log = logging.getLogger(name=__name__)
- [docs]
- class EqcModel:
- """
- EqcModel subclasses must provide these properties/methods.
-
- :decode: takes a raw solution and translates it into the original problem
- formulation
- :H: property which returns a Hamiltonian operator
- :upper_bound: Let D be an array of length n which contains the largest possible value
- allowed for x[i], which is the variable at index i, 0<=i<n. This means that a x[i]
- is in the domain [0,D[i]]. If the solution type of x[i] is integer, then x[i] is
- in the set of integers, Z, and also 0<=x[i]<=floor(D[i]).
- :qudit_limits: maximum value permitted for each qudit
- >>> model = EqcModel()
- >>> ub = np.array([1, 1.5, 2])
- >>> model.upper_bound = ub # doctest: +ELLIPSIS
- Traceback (most recent call last):
- ...
- ValueError: ...
- >>> model.upper_bound = np.ones((3,))
- >>> (model.upper_bound==np.ones((3,))).all()
- True
- >>> model.upper_bound = 2*np.ones((3,))
- >>> (model.upper_bound==2).all()
- True
- """
- _upper_bound = None
- _H = None
- _machine_slacks = 0
- _is_discrete = None
- [docs]
- def decode(self, solution : np.ndarray, from_encoding : str=None) -> np.ndarray:
- """
- Manipulate the solution to match the variable count. Optionally,
- a log-encoded solution vector can be translated into a vector
- with integral values.
- Parameters
- -----------
- solution : np.ndarray
- 1d solution vector
- from_encoding : str
- string indicating if the vector is an encoding of particular type. The
- text 'qubo' is the only option that does anything for now.
- >>> model = EqcModel()
- >>> ub = np.array([1, 2, 5])
- >>> model.upper_bound = ub
- >>> model.machine_slacks = 1
- >>> solution = np.array([1, 1, 1, 1, 0, 1, 0]) # this solution has a 3, which violates the upper bound, but decode doesn't care
- >>> model.decode(solution, "qubo")
- array([1, 3, 5])
- """
-
- solution = np.array(solution)
-
- if self.machine_slacks > 0:
- solution = solution[:-self.machine_slacks]
- if from_encoding == "qubo":
- ub = self.upper_bound
- en = solution.shape[0]
- assert en > 0
- n = ub.shape[0]
- linear_operator = np.zeros((n,en))
- j = 0
- for i in range(self.n):
- m = int(np.floor(np.log2(ub[i]))+1)
- bits = 2**np.arange(m)
- assert j+m <= linear_operator.shape[1]+2, f"Invalid slice for i={i} {j}:{j+m}"
- linear_operator[i,j:j+m] = bits
- j += m
- solution = (linear_operator@solution).astype(np.int64)
- return solution
- @property
- def upper_bound(self) -> np.array:
- """
- An array of upper bound values for every variable in the model. Must be integer.
- """
- return self._upper_bound
- @upper_bound.setter
- def upper_bound(self, value : np.array):
- value = np.array(value)
- if (value != value.astype(np.int64)).any():
- raise ValueError("Upper bound values must be integer")
- if (value==0).any():
- raise ValueError("Zero values are not allowed as an upper_bound.")
- self._upper_bound = value.astype(np.int64)
- @property
- def domains(self) -> np.array:
- if self._upper_bound is None:
- raise ValueError("Variable domains are required for model definition")
- return self._upper_bound
- @domains.setter
- def domains(self, value):
- warn("The domains property is deprecated in favor of naming it upper_bound", DeprecationWarning)
- self._upper_bound = value
- @property
- def is_discrete(self) -> List:
- """ An array of boolean values indicating if variable should be restricted to a discrete domain """
- return self._is_discrete
- @is_discrete.setter
- def is_discrete(self, value : List):
- self._is_discrete = value
- @property
- def n(self) -> int:
- """ Return the number of variables """
- return int(max(self.upper_bound.shape))
-
- @property
- def H(self):
- """ Hamiltonian operator of unknown type """
- return self._H
- @H.setter
- def H(self, value):
- """ The H setter ensures that the Hamiltonian is properly formatted. """
- raise NotImplementedError("H property setter not implemented in subclass, can't be set directly")
- @property
- def sparse(self) -> Tuple[np.ndarray, np.ndarray]:
-
- raise NotImplementedError("sparse must be implemented in a subclass")
-
- @property
- def machine_slacks(self):
- """ Number of slack qudits to add to the model """
- return self._machine_slacks
- @machine_slacks.setter
- def machine_slacks(self, value:int):
- assert int(value) == value, "value not integer"
- self._machine_slacks = value
- [docs]
- def evaluateObjective(self, solution : np.ndarray) -> float:
- raise NotImplementedError("evaluateObjective must be implemented in a subclass")
- [docs]
- def createConfigElements(self) -> Dict:
- obj = {"number_of_nonzero": None}
- return obj
- [docs]
- def createBenchmarkConfig(self, fname : str) -> None:
- obj = self.createConfigElements()
- @property
- def dynamic_range(self) -> float:
- raise NotImplementedError("EqcModel does not implement dynamic_range")
-
- @property
- def polynomial(self) -> Polynomial:
- raise OperatorNotAvailableError("Polynomial operator not available")
-
- @property
- def qubo(self) -> QUBO:
- raise OperatorNotAvailableError("QUBO operator not available")
- class SumConstraintMixin:
- _sum_constraint = None
- @property
- def sum_constraint(self):
- return self._sum_constraint
-
- @sum_constraint.setter
- def sum_constraint(self, value : Union[float, int]):
- assert value >= 0, "sum_constraint must be greater than or equal to one"
- self._sum_constraint = value
- [docs]
- class ModelSolver:
- """ Provide a common interface for solver implementations.
- Store a model, implement a solve method."""
- [docs]
- def solve(self, model:EqcModel, *args, **kwargs) -> Dict:
- raise NotImplementedError()