예제 #1
0
        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
예제 #2
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
예제 #3
0
 def construction_should_raise(data, expected_error):
     for inp, expected_output in data:
         coeff, exp, x = inp
         with pytest.raises(expected_error):
             MultivarPolynomial(coeff, exp, rectify_input=False, validate_input=True, )
         with pytest.raises(expected_error):
             HornerMultivarPolynomial(coeff, exp, rectify_input=False, validate_input=True, find_optimal=False)
         with pytest.raises(expected_error):
             HornerMultivarPolynomial(coeff, exp, rectify_input=False, validate_input=True, find_optimal=True)
예제 #4
0
        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
예제 #5
0
        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)
예제 #6
0
 def query_should_raise(data, expected_error):
     for inp, expected_output in data:
         coeff, exp, x = inp
         # NOTE: construction must not raise an error:
         p = MultivarPolynomial(coeff, exp, rectify_input=False, validate_input=True)
         with pytest.raises(expected_error):
             p(x, rectify_input=False, validate_input=True)
         p = HornerMultivarPolynomial(coeff, exp, rectify_input=False, validate_input=True)
         with pytest.raises(expected_error):
             p(x, rectify_input=False, validate_input=True)
         p = HornerMultivarPolynomial(coeff, exp, rectify_input=False, validate_input=True, find_optimal=True)
         with pytest.raises(expected_error):
             p(x, rectify_input=False, validate_input=True)
예제 #7
0
    def test_construction_basic(self):
        """
        test the basic construction API functionalities
        :return:
        """
        print('\nTESTING COSNTRUCTION API...')
        coefficients = np.array([[5.0], [1.0], [2.0], [3.0]], dtype=FLOAT_DTYPE)
        exponents = np.array([[0, 0, 0], [3, 1, 0], [2, 0, 1], [1, 1, 1]], dtype=UINT_DTYPE)

        polynomial1 = MultivarPolynomial(coefficients, exponents, compute_representation=False)
        polynomial2 = MultivarPolynomial(coefficients, exponents, compute_representation=True)
        # both must have a string representation
        # [#ops=27] p(x)
        # [#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
        assert len(str(polynomial1)) < len(str(polynomial2))
        assert str(polynomial1) == polynomial1.representation
        assert polynomial1.num_ops == 27

        return_str_repr = polynomial1.compute_string_representation(coeff_fmt_str='{:1.1e}',
                                                                    factor_fmt_str='(x{dim} ** {exp})')
        # the representation should get updated
        assert return_str_repr == polynomial1.representation

        polynomial1 = HornerMultivarPolynomial(coefficients, exponents, compute_representation=False)
        polynomial2 = HornerMultivarPolynomial(coefficients, exponents, compute_representation=True)
        # [#ops=10]
        # [#ops=10] p(x) = x_1^1 (x_1^1 (x_1^1 (1.0 x_2^1) + 2.0 x_3^1) + 3.0 x_2^1 x_3^1) + 5.0
        assert len(str(polynomial1)) < len(str(polynomial2))
        assert str(polynomial1) == polynomial1.representation
        assert polynomial1.num_ops == 10

        return_str_repr = polynomial1.compute_string_representation(coeff_fmt_str='{:1.1e}',
                                                                    factor_fmt_str='(x{dim} ** {exp})')
        # the representation should get updated
        assert return_str_repr == polynomial1.representation

        # converting the input to the required numpy data structures
        coefficients = [5.0, 1.0, 2.0, 3.0]
        exponents = [[0, 0, 0], [3, 1, 0], [2, 0, 1], [1, 1, 1]]
        horner_polynomial = HornerMultivarPolynomial(coefficients, exponents, rectify_input=True, validate_input=True,
                                                     compute_representation=True, keep_tree=True)

        # search for an optimal factorisation
        horner_polynomial_optimal = HornerMultivarPolynomial(coefficients, exponents, find_optimal=True,
                                                             compute_representation=True, rectify_input=True,
                                                             validate_input=True)
        assert horner_polynomial_optimal.num_ops <= horner_polynomial.num_ops

        # partial derivative:
        deriv_2 = horner_polynomial.get_partial_derivative(2, compute_representation=True)

        # NOTE: partial derivatives themselves will be instances of the same parent class
        assert deriv_2.__class__ is horner_polynomial.__class__

        grad = horner_polynomial.get_gradient(compute_representation=True)
        # partial derivative for every dimension
        assert len(grad) == horner_polynomial.dim
        print('OK.\n')
예제 #8
0
    def test_invalid_input_detection(self):

        print('\n\nTEST INVALID INPUT DETECTION...')

        def construction_should_raise(data, expected_error):
            for inp, expected_output in data:
                coeff, exp, x = inp
                with pytest.raises(expected_error):
                    MultivarPolynomial(coeff, exp, rectify_input=False, validate_input=True, )
                with pytest.raises(expected_error):
                    HornerMultivarPolynomial(coeff, exp, rectify_input=False, validate_input=True, find_optimal=False)
                with pytest.raises(expected_error):
                    HornerMultivarPolynomial(coeff, exp, rectify_input=False, validate_input=True, find_optimal=True)

        construction_should_raise(INPUT_DATA_INVALID_TYPES_CONSTRUCTION, TypeError)
        construction_should_raise(INPUT_DATA_INVALID_VALUES_CONSTRUCTION, ValueError)

        # input rectification with negative exponents should raise a ValueError:
        coeff = [3.2]
        exp = [[0, 3, -4]]
        with pytest.raises(ValueError):
            HornerMultivarPolynomial(coeff, exp, rectify_input=True, validate_input=False, find_optimal=True)
        with pytest.raises(ValueError):
            MultivarPolynomial(coeff, exp, rectify_input=True, validate_input=False)
        with pytest.raises(ValueError):
            HornerMultivarPolynomial(coeff, exp, rectify_input=True, validate_input=False, find_optimal=False)

        def query_should_raise(data, expected_error):
            for inp, expected_output in data:
                coeff, exp, x = inp
                # NOTE: construction must not raise an error:
                p = MultivarPolynomial(coeff, exp, rectify_input=False, validate_input=True)
                with pytest.raises(expected_error):
                    p(x, rectify_input=False, validate_input=True)
                p = HornerMultivarPolynomial(coeff, exp, rectify_input=False, validate_input=True)
                with pytest.raises(expected_error):
                    p(x, rectify_input=False, validate_input=True)
                p = HornerMultivarPolynomial(coeff, exp, rectify_input=False, validate_input=True, find_optimal=True)
                with pytest.raises(expected_error):
                    p(x, rectify_input=False, validate_input=True)

        query_should_raise(INPUT_DATA_INVALID_TYPES_QUERY, TypeError)
        query_should_raise(INPUT_DATA_INVALID_VALUES_QUERY, ValueError)
        print('OK.')
예제 #9
0
#   p(x) = 5.0 + 1.0 x_1^3 x_2^1 + 2.0 x_1^2 x_3^1 + 3.0 x_1^1 x_2^1 x_3^1
#   with...
#       dimension N = 3
#       amount of monomials M = 4
#       max_degree D = 3
# NOTE: these data types and shapes are required by the precompiled functions in helper_fcts_numba.py
coefficients = np.array([[5.0], [1.0], [2.0], [3.0]],
                        dtype=np.float64)  # column numpy vector = (M,1)-matrix
exponents = np.array([[0, 0, 0], [3, 1, 0], [2, 0, 1], [1, 1, 1]],
                     dtype=np.uint32)  # numpy (M,N)-matrix

# represent the polynomial in the regular, naive form without any factorisation (simply stores the matrices)
# 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:
예제 #10
0
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)