FoundryProductsTechnologyCompanyInvestor relationsResource libraryNews
Contact us
Resource library
    Resource library home
    Developer resources
    Applications
    Lessons
    Research and publications
    Support
      Software packages
        eqc-direct software package
        qci-client software package
        uqrng-direct software package
        emucore-direct software package
        eqc-models software package
          Getting Started
          Basic Usage
          eqc_models
          Dependencies
      Spec sheets
      User guides

Couldn’t find what you are looking for? Reach out to technical support.

Contact support
Privacy PolicyCookie PolicyTerms of UseForward Looking StatementsAccessibility Statement
Terms and Conditions of SaleEnd User License Agreement

© 2018-2026 Quantum Computing Inc.

Download

Default

Source code for eqc_models.base.quadratic

  • # (C) Quantum Computing Inc., 2024.
  • from typing import Tuple
  • import warnings
  • import numpy as np
  • from eqc_models.base.base import EqcModel
  • from eqc_models.base.constraints import ConstraintsMixIn
  • from eqc_models.base.operators import QUBO, Polynomial
  • class QuadraticMixIn:
  • """
  • This class provides an instance method and property that
  • manage quadratic models.
  • """
  • @property
  • def sparse(self) -> Tuple[np.ndarray, np.ndarray]:
  • """
  • Put the linear and quadratic terms in a sparse (poly) format
  • Returns
  • ----------
  • coefficients: List
  • indices: List
  • """
  • C, J = self.H
  • C = np.squeeze(C)
  • n = self.n
  • indices = []
  • coefficients = []
  • # build a key (ordered tuple of indices) of length 2 for each element
  • for i in range(n):
  • if C[i] != 0:
  • key = (0, i+1)
  • indices.append(key)
  • coefficients.append(C[i])
  • # make J upper triangular
  • J = np.triu(J) + np.tril(J, -1).T
  • for i in range(n):
  • for j in range(i, n):
  • val = J[i, j]
  • if val != 0:
  • key = (i+1, j+1)
  • indices.append(key)
  • coefficients.append(val)
  • return np.array(coefficients, dtype=np.float32), np.array(indices, dtype=np.int32)
  • @property
  • def polynomial(self) -> Polynomial:
  • coefficients, indices = self.sparse
  • return Polynomial(coefficients=coefficients, indices=indices)
  • def evaluate(self, solution: np.ndarray) -> float:
  • """
  • Evaluate the solution using the original operator.
  • """
  • sol = np.array(solution)
  • C, J = self.H
  • return np.squeeze(sol.T @ J @ sol + C.T @ sol)
  • @property
  • def dynamic_range(self) -> float:
  • """
  • Dynamic range is a measure in decibels of the ratio of the largest
  • magnitude coefficient in a problem to the smallest non-zero magnitude
  • coefficient.
  • The possible range of values are all greater than or equal to 0. The
  • calculation is performed by finding the lowest non-zero of the
  • absolute value of all the coefficients, which could be empty. The
  • values are in two arrays, so the minimum and maximum values of these
  • arrays are compared to each other. When there is no non-zero in either
  • of the arrays, then an exception is raised indicating that the dynamic
  • range of an operator of all zeros is undefined. If the lowest value is
  • positive, then the maximum of the absolute values is divided by the
  • lowest. The base-10 logarithm of that value is taken and multiplied by
  • 10. This is the dynamic range.
  • Returns
  • ----------
  • float
  • """
  • C, J = self.H
  • # if either C or J are all 0, then set min to very large value
  • try:
  • min_c = np.min(np.abs(C[C!=0]))
  • except IndexError:
  • min_c = 1e308
  • try:
  • min_j = np.min(np.abs(J[J!=0]))
  • except IndexError:
  • min_j = 1e308
  • max_c = np.max(np.abs(C))
  • max_j = np.max(np.abs(J))
  • lowest = min_c if min_c < min_j else min_j
  • highest = max_c if max_c > max_j else max_j
  • if lowest > highest:
  • raise ValueError("Dynamic range of a Hamiltonian of all 0 is undefined")
  • return 10*np.log10(highest / lowest)
  • @property
  • def qubo(self) -> QUBO:
  • """
  • Transform the model into QUBO form. Use `upper_bound` to determine
  • a log-encoding of the variables.
  • """
  • bin_n = 0
  • bits = []
  • #
  • C, J = self.H
  • # upper_bound is an array of the maximum values each variable can take
  • upper_bound = self.upper_bound
  • if np.sum(upper_bound)!=upper_bound.shape[0]:
  • for i in range(upper_bound.shape[0]):
  • bits.append(1+np.floor(np.log2(upper_bound[i])))
  • bin_n += bits[-1]
  • bin_n = int(bin_n)
  • Q = np.zeros((bin_n, bin_n), dtype=np.float32)
  • Q.shape
  • powers = [2**np.arange(bit_count) for bit_count in bits]
  • blocks = []
  • linear_blocks = []
  • for i in range(len(powers)):
  • # add the linear terms to the diagonal
  • linear_blocks.append(C[i]*powers[i])
  • row = []
  • for j in range(len(powers)):
  • mult = J[i,j]
  • block = np.outer(powers[i], powers[j])
  • block *= mult
  • row.append(block)
  • blocks.append(row)
  • Q[:, :] = np.block(blocks)
  • linear_operator = np.hstack(linear_blocks)
  • Q += np.diag(linear_operator)
  • else:
  • # in this case, the fomulation already has only binary variables
  • Q = np.zeros_like(J)
  • Q[:, :] = J
  • Q += np.diag(np.squeeze(C))
  • return QUBO(Q)
  • [docs]
  • class QuadraticModel(QuadraticMixIn, EqcModel):
  • """
  • Provides a quadratic operator and device sum constraint support.
  • Parameters
  • -----------
  • J: Quadratic hamiltonian array.
  • C: Linear hamiltonian array.
  • Examples
  • ---------
  • >>> C = np.array([1, 2])
  • >>> J = np.array([[2, 1], [1, 2]])
  • >>> from eqc_models.base.quadratic import QuadraticModel
  • >>> model = QuadraticModel(C, J)
  • >>> model.upper_bound = np.array([1, 1])
  • >>> qubo = model.qubo
  • >>> qubo.Q # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
  • array([[3., 1.],
  • [1., 4.]])
  • """
  • def __init__(self, C: np.ndarray, J: np.ndarray):
  • self._H = C, J
  • [docs]
  • class ConstrainedQuadraticModel(ConstraintsMixIn, QuadraticModel):
  • """
  • Provides a constrained quadratic operator and device sum constraint support.
  • Parameters
  • -----------
  • J: Quadratic hamiltonian array.
  • C: Linear hamiltonian array.
  • lhs: Left hand side of the linear constraints.
  • rhs: Right hand side of the linear constraints.
  • Examples
  • -------------------
  • >>> C = np.array([1, 2])
  • >>> J = np.array([[2, 1], [1, 2]])
  • >>> lhs = np.array([[1, 1], [1, 1]])
  • >>> rhs = np.array([1, 1])
  • >>> from eqc_models.base.quadratic import ConstrainedQuadraticModel
  • >>> model = ConstrainedQuadraticModel(C, J, lhs, rhs)
  • >>> model.penalty_multiplier = 1
  • >>> model.upper_bound = np.array([1, 1])
  • >>> qubo = model.qubo
  • >>> qubo.Q # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
  • array([[1., 3.],
  • [3., 2.]])
  • """
  • def __init__(self, C_obj: np.array, J_obj: np.array, lhs: np.array, rhs: np.array):
  • self.quad_objective = J_obj
  • self.linear_objective = C_obj
  • self.lhs = lhs
  • self.rhs = rhs
  • self._penalty_multiplier = None
  • @property
  • def H(self) -> Tuple[np.ndarray, np.ndarray]:
  • """
  • Return a pair of arrays, the linear and quadratic portions of a quadratic
  • operator that has the objective plus penalty functions multiplied by the
  • penalty multiplier. The linear terms are the first array and the quadratic
  • are the second.
  • Returns
  • -----------
  • `np.ndarray, np.nedarray`
  • """
  • C = self.linear_objective
  • J = self.quad_objective
  • pC, pJ = self.penalties
  • alpha = self.penalty_multiplier
  • return C + alpha * pC, J + alpha * pJ
  • @ConstraintsMixIn.penalty_multiplier.setter
  • def penalty_multiplier(self, value):
  • ConstraintsMixIn.penalty_multiplier.fset(self, value)
  • @property
  • def constraints(self):
  • return self.lhs, self.rhs
  • [docs]
  • def evaluateObjective(self, solution: np.ndarray) -> float:
  • J = self.quad_objective
  • C = self.linear_objective
  • return np.squeeze(C.T @ solution + solution.T@J@solution)
Previous page
Next page

Content

  • Source code for eqc_models.base.quadratic