def cmp_value_fct(inp): coeff, exp, x = inp x = np.array(x).T poly = MultivarPolynomial(coeff, exp, rectify_input=True, validate_input=True, compute_representation=True) res1 = poly.eval(x, rectify_input=True, validate_input=False) print('MultivarPolynomial', poly) horner_poly = HornerMultivarPolynomial(coeff, exp, rectify_input=True, validate_input=True, compute_representation=True, find_optimal=False) res2 = poly.eval(x, rectify_input=True, validate_input=False) print('HornerMultivarPolynomial', horner_poly) # print('x=',x.tolist()) horner_poly_opt = HornerMultivarPolynomial(coeff, exp, rectify_input=True, validate_input=True, compute_representation=True, find_optimal=True) res3 = poly.eval(x, rectify_input=True, validate_input=False) print('HornerMultivarPolynomial (optimal)', horner_poly_opt) # print('x=',x.tolist()) if res1 != res2 or res2 != res3: print(f'x = {x}') print(f'results differ:\n{res1} (canonical)\n{res2} (horner)\n{res3} (horner optimal)\n') assert False assert horner_poly.num_ops >= horner_poly_opt.num_ops return res1
def cmp_value_fct(inp): print() coeff, exp, x = inp x = np.array(x).T poly = MultivarPolynomial(coeff, exp, rectify_input=True, validate_input=True) res1 = poly.eval(x, validate_input=True) print(str(poly)) horner_poly = HornerMultivarPolynomial(coeff, exp, rectify_input=True, validate_input=True, compute_representation=True, find_optimal=False) res2 = horner_poly.eval(x, validate_input=True) print(str(horner_poly)) # print('x=',x.tolist()) horner_poly_opt = HornerMultivarPolynomial( coeff, exp, rectify_input=True, validate_input=True, compute_representation=True, find_optimal=True) res3 = horner_poly_opt.eval(x, validate_input=True) print(str(horner_poly_opt)) # print('x=',x.tolist()) if res1 != res2 or res2 != res3: print('results differ:', res1, res2, res3) assert False assert horner_poly.num_ops >= horner_poly_opt.num_ops return poly.eval(x, validate_input=True)
def change_coeffs_fct(inp): print('\n') # changing the coefficients to the same coefficients should not alter the evaluation results # (reuse test data) coeffs1, exp, x, coeffs2 = inp # x = np.array(x).T print(x) poly = MultivarPolynomial(coeffs1, exp, rectify_input=True, validate_input=True, compute_representation=True) print(poly) poly = poly.change_coefficients(coeffs2, rectify_input=True, validate_input=True, compute_representation=True, ) print(poly) res1 = poly.eval(x, rectify_input=True, validate_input=True) print('\n') horner_poly = HornerMultivarPolynomial(coeffs1, exp, rectify_input=True, validate_input=True, compute_representation=True, keep_tree=True, find_optimal=False) print(horner_poly) horner_poly = horner_poly.change_coefficients(coeffs2, rectify_input=True, validate_input=True, compute_representation=True, ) res2 = horner_poly.eval(x, rectify_input=True, validate_input=True) print(horner_poly) # print('x=',x.tolist()) print('\n') horner_poly_opt = HornerMultivarPolynomial(coeffs1, exp, rectify_input=True, validate_input=True, compute_representation=True, keep_tree=True, find_optimal=True) print(horner_poly_opt) horner_poly_opt = horner_poly_opt.change_coefficients(coeffs2, rectify_input=True, validate_input=True, compute_representation=True, ) res3 = horner_poly_opt.eval(x, rectify_input=True, validate_input=True) print(horner_poly_opt) # print('x=',x.tolist()) if res1 != res2 or res2 != res3: print(f'x = {x}') print(f'results differ:\n{res1} (canonical)\n{res2} (horner)\n{res3} (horner optimal)\n') assert horner_poly.num_ops >= horner_poly_opt.num_ops return res1
# print(np.sum(exponents, axis=0).max()) print(np.sum(exponents, axis=0)) print(np.max(np.sum(exponents, axis=0))) polynomial = MultivarPolynomial(coefficients, exponents) # visualising the used polynomial representation print(polynomial) # [#ops=27] p(x) = 5.0 x_1^0 x_2^0 x_3^0 + 1.0 x_1^3 x_2^1 x_3^0 + 2.0 x_1^2 x_2^0 x_3^1 + 3.0 x_1^1 x_2^1 x_3^1 # NOTE: the number in square brackets indicates the number of operations required # to evaluate the polynomial (ADD, MUL, POW). # NOTE: in the case of unfactorised polynomials many unnecessary operations are being done # (internally uses numpy matrix operations) # define a query point and evaluate the polynomial x = np.array([-2.0, 3.0, 1.0], dtype=np.float64) # numpy row vector (1,N) p_x = polynomial.eval(x) print(p_x) # -29.0 # represent the polynomial in factorised form: # uses the heuristic proposed in [1]: iteratively factors out the factor with the highest usage # pass compute_representation=True in order to compile a string representation of the factorisation # pass keep_tree=True when the factorisation tree should be kept after the factorisation process horner_polynomial = HornerMultivarPolynomial(coefficients, exponents, compute_representation=True) print(horner_polynomial ) # [#ops=10] p(x) = x_1 (x_1 (x_1 (c x_2) + c x_3) + c x_2 x_3) + c # pass rectify_input=True to automatically try converting the input to the required numpy data structures # pass validate_input=True to check if input data is valid (e.g. only non negative exponents) # NOTE: the default for both options is false (increased speed)
class polynomial(): def __init__(self, repr: polynomialRepr, coeffs: np.ndarray = None, alwaysFull: bool = True): self.repr = repr self._isUpdate = False if coeffs is None: self._coeffs = nzeros((self.repr.nMonoms, ), dtype=nfloat) else: # Must be convertible self._coeffs = narray(coeffs).astype(nfloat).reshape( (self.repr.nMonoms, )) self._coeffs.setflags(write=False) self._alwaysFull = alwaysFull self._evalPoly = None self.maxDeg = self.getMaxDegree() if __debug__: assert self.maxDeg <= self.repr.maxDeg def __copy__(self): return polynomial(self.repr, np.copy(self._coeffs), self._alwaysFull) def __deepcopy__(self, memodict={}): return polynomial(dp(self.repr, memodict), np.copy(self._coeffs), cp(self._alwaysFull)) def unlock(self): self._coeffs.setflags(write=True) def lock(self): self._coeffs.setflags(write=False) @property def coeffs(self): return self._coeffs @coeffs.setter def coeffs(self, newCoeffs): try: self._coeffs = narray(newCoeffs).astype(nfloat).reshape( (self.repr.nMonoms, )) except: print("Could not be converted -> skip") self._coeffs.setflags(write=False) # Update degree self.maxDeg = self.getMaxDegree( ) #monomial are of ascending degree -> last nonzero coeff determines degree self._isUpdate = False def __add__(self, other): if __debug__: assert isinstance(other, polynomial) assert self.repr == other.repr return polynomial(self.repr, self._coeffs + other._coeffs, alwaysFull=self._alwaysFull) def __iadd__(self, other): if __debug__: assert isinstance(other, polynomial) assert self.repr == other.repr self._coeffs += other._coeffs self.maxDeg = self.getMaxDegree() return self def __sub__(self, other): if __debug__: assert isinstance(other, polynomial) assert self.repr == other.repr return polynomial(self.repr, self._coeffs - other._coeffs, alwaysFull=self._alwaysFull) def __isub__(self, other): if __debug__: assert isinstance(other, polynomial) assert self.repr == other.repr self._coeffs -= other._coeffs self.maxDeg = self.getMaxDegree() return self def __neg__(self): return polynomial(self.repr, -self._coeffs, alwaysFull=self._alwaysFull) def __mul__(self, other): if isinstance(other, (float, int)): return polynomial(self.repr, float(other) * self._coeffs, alwaysFull=self._alwaysFull) else: if __debug__: assert isinstance(other, polynomial) assert self.repr == other.repr assert self.maxDeg + other.maxDeg <= self.repr.maxDeg c = polyMul(self._coeffs, other._coeffs, self.repr.idxMat) return polynomial(self.repr, c, alwaysFull=self._alwaysFull) def __rmul__(self, other): # Commute return self.__mul__(other) def __imul__(self, other): self._coeffs.setflags(write=True) if isinstance(other, (float, int)): self._coeffs *= float(other) else: if __debug__: assert isinstance(other, polynomial) assert self.repr == other.repr assert self.maxDeg + other.maxDeg <= self.repr.maxDeg self._coeffs = polyMul(self._coeffs, other._coeffs, self.repr.idxMat) self.maxDeg = self.maxDeg + other.maxDeg self._coeffs.setflags(write=False) return self def __pow__(self, power: int, modulo=None): if __debug__: assert self.maxDeg * power <= self.repr.maxDeg #very simplistic newPoly = self * self for _ in range(power - 2): newPoly *= self return newPoly def round(self, atol=1e-12): self._coeffs[np.abs(self._coeffs) <= atol] = 0. self.maxDeg = self.getMaxDegree() def getMaxDegree(self): try: return self.repr.listOfMonomials[int( np.argwhere(self._coeffs != 0.)[-1])].sum() except IndexError: #all zero return 0 def computeInternal(self, full: bool = False): # This computes and stores the horner scheme # If not told otherwise it will try to seek a sparse layout, but only takes into account explicit zeros #shortcut of always full if self._alwaysFull: if self._evalPoly is None: self._evalPoly = MultivarPolynomial( self._coeffs.reshape((-1, 1)), np.require(narray(self.repr.listOfMonomials), dtype=nint)) else: self._evalPoly.coefficients = self.coeffs.reshape((-1, 1)) self._isUpdate = True return if full: thisExp = np.require(narray(self.repr.listOfMonomials), dtype=nint) thisCoeffs = self._coeffs else: thisExp = [] thisCoeffs = [] for aExp, aCoeff in zip(self.repr.listOfMonomials, self._coeffs): if aCoeff != 0: thisExp.append(aExp) thisCoeffs.append(aCoeff) thisExp = np.require(narray(thisExp), dtype=nint) thisCoeffs = np.require(narray(thisCoeffs), dtype=nfloat) thisCoeffs.resize((thisCoeffs.size, 1)) self._evalPoly = MultivarPolynomial(thisCoeffs, thisExp) self._isUpdate = True def setQuadraticForm(self, Q: np.ndarray, qMonoms: np.ndarray = None, h=None, hMonoms=None): if __debug__: assert Q.shape[0] == Q.shape[1] qMonoms = self.repr.varNumsPerDeg[1] if qMonoms is None else qMonoms hMonoms = self.repr.varNumsUpToDeg[1] if ( (h is not None) and (hMonoms is None)) else hMonoms if ((h is None) and (hMonoms is None)): h = nzeros((1, ), dtype=nfloat) hMonoms = self.repr.varNumsPerDeg[0] self._coeffs = quadraticForm_Numba( Q, qMonoms, h, hMonoms, self.repr.idxMat, np.zeros((self.repr.nMonoms, ), dtype=nfloat)) self.maxDeg = self.getMaxDegree() return None def setEllipsoidalConstraint(self, center: np.ndarray, radius: float, P=None): """ # get the ellipsoid # Polynomial constrained are defined as being valid of larger then zero : p(x)>=0. # (x-center).T.P.(x-center) <= radius**2 # center.T.P.center - 2*center.T.P.x + x.T.P.x - radius**2 <= 0 # center.T.(-P).center + 2*center.T.P.x - x.T.P.x + radius**2 >= 0 :param center: :param radius: :param P: :return: """ self._coeffs.flags['WRITEABLE'] = True self._coeffs[:] = 0. P = nidentity(center.size, dtype=nfloat) if P is None else P self.setQuadraticForm(-P, self.repr.varNumsPerDeg[1], 2. * ndot(center.T, P).squeeze(), self.repr.varNumsPerDeg[1]) self._coeffs[0] += (mndot([center.T, -P, center]) + radius**2) self._coeffs.flags['WRITEABLE'] = False return None def eval(self, x: np.array): if not self._isUpdate: self.computeInternal() return self._evalPoly.eval(x) def eval2(self, x: np.array, deg: int = None): assert self._coeffs.size == self.repr.nMonoms if deg is None: nMonoms_ = self.repr.varNumsUpToDeg[self.maxDeg].size else: assert deg <= self.repr.maxDeg nMonoms_ = len(self.repr.varNumsUpToDeg[deg]) coeffsEval = np.reshape(self._coeffs, (1, self.repr.nMonoms))[[0], :nMonoms_] if x.shape[0] >= nMonoms_: z = x[:nMonoms_, :] else: z = evalMonomsNumba(x, self.repr.varNum2varNumParents[:nMonoms_, :]) return ndot(coeffsEval, z)