def create_key(self, base_ring, arg1=None, arg2=None, sparse=None, order='degrevlex', names=None, name=None, implementation=None, degrees=None): """ Create the key under which a free algebra is stored. TESTS:: sage: FreeAlgebra.create_key(GF(5),['x','y','z']) (Finite Field of size 5, ('x', 'y', 'z')) sage: FreeAlgebra.create_key(GF(5),['x','y','z'],3) (Finite Field of size 5, ('x', 'y', 'z')) sage: FreeAlgebra.create_key(GF(5),3,'xyz') (Finite Field of size 5, ('x', 'y', 'z')) sage: FreeAlgebra.create_key(GF(5),['x','y','z'], implementation='letterplace') (Multivariate Polynomial Ring in x, y, z over Finite Field of size 5,) sage: FreeAlgebra.create_key(GF(5),['x','y','z'],3, implementation='letterplace') (Multivariate Polynomial Ring in x, y, z over Finite Field of size 5,) sage: FreeAlgebra.create_key(GF(5),3,'xyz', implementation='letterplace') (Multivariate Polynomial Ring in x, y, z over Finite Field of size 5,) sage: FreeAlgebra.create_key(GF(5),3,'xyz', implementation='letterplace', degrees=[1,2,3]) ((1, 2, 3), Multivariate Polynomial Ring in x, y, z, x_ over Finite Field of size 5) """ if arg1 is None and arg2 is None and names is None: # this is used for pickling if degrees is None: return (base_ring, ) return tuple(degrees), base_ring # test if we can use libSingular/letterplace if implementation == "letterplace": args = [arg for arg in (arg1, arg2) if arg is not None] kwds = dict(sparse=sparse, order=order, implementation="singular") if name is not None: kwds["name"] = name if names is not None: kwds["names"] = names PolRing = PolynomialRing(base_ring, *args, **kwds) if degrees is None: return (PolRing, ) from sage.all import TermOrder T = PolRing.term_order() + TermOrder('lex', 1) varnames = list(PolRing.variable_names()) newname = 'x' while newname in varnames: newname += '_' varnames.append(newname) R = PolynomialRing(PolRing.base(), varnames, sparse=sparse, order=T) return tuple(degrees), R # normalise the generator names from sage.all import Integer if isinstance(arg1, (Integer, int)): arg1, arg2 = arg2, arg1 if not names is None: arg1 = names elif not name is None: arg1 = name if arg2 is None: arg2 = len(arg1) names = normalize_names(arg2, arg1) return base_ring, names
def create_key(self,base_ring, arg1=None, arg2=None, sparse=False, order='degrevlex', names=None, name=None, implementation=None, degrees=None): """ Create the key under which a free algebra is stored. TESTS:: sage: FreeAlgebra.create_key(GF(5),['x','y','z']) (Finite Field of size 5, ('x', 'y', 'z')) sage: FreeAlgebra.create_key(GF(5),['x','y','z'],3) (Finite Field of size 5, ('x', 'y', 'z')) sage: FreeAlgebra.create_key(GF(5),3,'xyz') (Finite Field of size 5, ('x', 'y', 'z')) sage: FreeAlgebra.create_key(GF(5),['x','y','z'], implementation='letterplace') (Multivariate Polynomial Ring in x, y, z over Finite Field of size 5,) sage: FreeAlgebra.create_key(GF(5),['x','y','z'],3, implementation='letterplace') (Multivariate Polynomial Ring in x, y, z over Finite Field of size 5,) sage: FreeAlgebra.create_key(GF(5),3,'xyz', implementation='letterplace') (Multivariate Polynomial Ring in x, y, z over Finite Field of size 5,) sage: FreeAlgebra.create_key(GF(5),3,'xyz', implementation='letterplace', degrees=[1,2,3]) ((1, 2, 3), Multivariate Polynomial Ring in x, y, z, x_ over Finite Field of size 5) """ if arg1 is None and arg2 is None and names is None: # this is used for pickling if degrees is None: return (base_ring,) return tuple(degrees),base_ring PolRing = None # test if we can use libSingular/letterplace if implementation is not None and implementation != 'generic': try: PolRing = PolynomialRing(base_ring, arg1, arg2, sparse=sparse, order=order, names=names, name=name, implementation=implementation if implementation != 'letterplace' else None) if not isinstance(PolRing, MPolynomialRing_libsingular): if PolRing.ngens() == 1: PolRing = PolynomialRing(base_ring, 1, PolRing.variable_names()) if not isinstance(PolRing, MPolynomialRing_libsingular): raise TypeError else: raise TypeError except (TypeError, NotImplementedError) as msg: raise NotImplementedError("The letterplace implementation is not available for the free algebra you requested") if PolRing is not None: if degrees is None: return (PolRing,) from sage.all import TermOrder T = PolRing.term_order() + TermOrder('lex',1) varnames = list(PolRing.variable_names()) newname = 'x' while newname in varnames: newname += '_' varnames.append(newname) return tuple(degrees),PolynomialRing(PolRing.base(), varnames, sparse=sparse, order=T, implementation=implementation if implementation != 'letterplace' else None) # normalise the generator names from sage.all import Integer if isinstance(arg1, (int, long, Integer)): arg1, arg2 = arg2, arg1 if not names is None: arg1 = names elif not name is None: arg1 = name if arg2 is None: arg2 = len(arg1) names = sage.structure.parent_gens.normalize_names(arg2, arg1) return base_ring, names
def create_key(self, base_ring, arg1=None, arg2=None, sparse=None, order='degrevlex', names=None, name=None, implementation=None, degrees=None): """ Create the key under which a free algebra is stored. TESTS:: sage: FreeAlgebra.create_key(GF(5),['x','y','z']) (Finite Field of size 5, ('x', 'y', 'z')) sage: FreeAlgebra.create_key(GF(5),['x','y','z'],3) (Finite Field of size 5, ('x', 'y', 'z')) sage: FreeAlgebra.create_key(GF(5),3,'xyz') (Finite Field of size 5, ('x', 'y', 'z')) sage: FreeAlgebra.create_key(GF(5),['x','y','z'], implementation='letterplace') (Multivariate Polynomial Ring in x, y, z over Finite Field of size 5,) sage: FreeAlgebra.create_key(GF(5),['x','y','z'],3, implementation='letterplace') (Multivariate Polynomial Ring in x, y, z over Finite Field of size 5,) sage: FreeAlgebra.create_key(GF(5),3,'xyz', implementation='letterplace') (Multivariate Polynomial Ring in x, y, z over Finite Field of size 5,) sage: FreeAlgebra.create_key(GF(5),3,'xyz', implementation='letterplace', degrees=[1,2,3]) ((1, 2, 3), Multivariate Polynomial Ring in x, y, z, x_ over Finite Field of size 5) """ if arg1 is None and arg2 is None and names is None: # this is used for pickling if degrees is None: return (base_ring,) return tuple(degrees),base_ring # test if we can use libSingular/letterplace if implementation == "letterplace": args = [arg for arg in (arg1, arg2) if arg is not None] kwds = dict(sparse=sparse, order=order, implementation="singular") if name is not None: kwds["name"] = name if names is not None: kwds["names"] = names PolRing = PolynomialRing(base_ring, *args, **kwds) if degrees is None: return (PolRing,) from sage.all import TermOrder T = PolRing.term_order() + TermOrder('lex',1) varnames = list(PolRing.variable_names()) newname = 'x' while newname in varnames: newname += '_' varnames.append(newname) R = PolynomialRing( PolRing.base(), varnames, sparse=sparse, order=T) return tuple(degrees), R # normalise the generator names from sage.all import Integer if isinstance(arg1, (Integer,) + integer_types): arg1, arg2 = arg2, arg1 if not names is None: arg1 = names elif not name is None: arg1 = name if arg2 is None: arg2 = len(arg1) names = normalize_names(arg2, arg1) return base_ring, names
class Operator(object): ### Static parameters _op_preference = 0 ####################################################### ### INIT METHOD AND GETTERS ####################################################### def __init__(self, base, input, derivate = foo_derivative): ''' This method allows the user to instantiate a new object of type Operator. This method must be extended in each child-class of Operator. INPUT: - base: the Structure where the coefficients of the operator will be - input: the input data for the operator. The format must be checked in each class. ''' ## Computing the polynomial associated ring try: self.__pol_var = 'n' i = -1 names = [str(gen) for gen in base.base_ring().gens()] while(self.__pol_var in names): i += 1 self.__pol_var = 'n' + str(i) self.__polynomialRing = PolynomialRing(base.base_ring(), self.__pol_var) except RuntimeError: self.__pol_var = 'n' self.__polynomialRing = PolynomialRing(base.base_ring(),self.__pol_var) ## Computing the Ring_w_Sequence associated to base if(isinstance(base, Ring_w_Sequence)): self.__base = base else: self.__base = Wrap_w_Sequence_Ring(base) self._original_base = base ## Declaring private fields self.__derivate = derivate def base(self): return self.__base def derivate(self): return self.__derivate def _get_preference(self): return self._op_preference ### Getter methods def order(self): ''' This method allows the user to get the order of the operator. This method must be extended in each child-class of Operator. ''' raise NotImplementedError('Method not implemented -- Abstract class asked') def coefficients(self): ''' This method allows the user to get the coefficients of the operator. This method must be extended in each child-class of Operator. ''' raise NotImplementedError('Method not implemented -- Abstract class asked') def coefficient(self, i): if (i < 0): raise IndexError('The argument must be a number greater or equal to zero') elif (i <= self.order()): return self.coefficients()[i] else: return 0 @cached_method def companion(self): field = self.base().fraction_field() coefficients = [field(el) for el in self.coefficients()] ## We divide by the leading coefficient coefficients = [-(coefficients[i]/coefficients[-1]) for i in range(len(coefficients)-1)] ## Trying to reduce the elements try: for i in range(len(coefficients)): coefficients[i].reduce() except (AttributeError, ArithmeticError): pass d = len(coefficients) ## Building the rows of our matrix rows = [[0 for i in range(d-1)] + [coefficients[0]]] for i in range(d-1): rows += [[kronecker_delta(i,j) for j in range(d-1)] + [coefficients[i+1]]] ## Returning the matrix return Matrix(field, rows) @cached_method def noetherian_ring(self, other=None): r''' Method that builds a noetherian ring that contains all the coefficients This method computes a differential noetherian ring in Sage that contains all the coefficients of ``self`` and ``other`` and where we can divide be the leading coefficients of both operators. This method will rely on the method :func:`~ajpastor.dd_functions.ddFunction.DDFunction.noetherian_ring`. This method currently does not work if ``self.base()`` is a DDRing. INPUT: * ``other``: other operator to compute the common noetherian ring at the same time * ``structure``: determines the format of the output. OUTPUT: The output have always 3 main entries: * The ring `R` that is the base of the Noetherian extension * The list of elements `\alpha_i` that will generate the final ring. * The list of generators of `S`. In the case of ``structure`` being ``True``, a fourth entry will be added with the ring `R[x_1,\ldots,x_n]_S` where the variable `x_i` represent the element `\alpha_i`. ''' from ajpastor.dd_functions import is_DDRing if(is_DDRing(self.base())): raise NotImplementedError('Method not implemented. Class: %s' %self.__class__) c = [self.coefficients()[-1]] if(not (other is None)): c += [other.coefficients()[-1]] final_dens = [] for el in c: if((not el in self.base()) or (not self.base()(el).is_unit())): final_dens += [el] return self.base(), [], final_dens ####################################################### ####################################################### ### RECURSION POLYNOMIALS METHODS ####################################################### def get_recursion_polynomial(self, n): ''' Method to get a recursion polynomial associated with this operator. If the requested polynomial is greater than zero, then we will return a `forward` polynomial, but if the request is lesser than zero, we will return a backward polynomial, computing it if necessary. INPUT: - n: index of the recursion polynomial requested. OUTPUT: A polynomial on self.base()[nx] where x will be an integer such the variable nx is not in self.base().gens() ''' if(n >= 0): try: return self.forward(n) except IndexError: return 0 else: return self.backward(-n) @cached_method def forward(self, n): if(n < 0): raise IndexError("Forward polynomials have only positive index") elif(n > self.order()): return 0 elif(self.is_zero()): return 0 else: var = self.__polynomialRing.gens()[-1] return sum([self.base().sequence(self.coefficient(l),l-n)*falling_factorial(var+n,l) for l in range(n, self.order()+1)]) @cached_method def backward(self, n): if(n < 0): raise IndexError("Backward polynomials have only positive index") elif(self.is_zero()): return 0 var = self.__polynomialRing.gens()[-1] return sum([self.base().sequence(self.coefficient(l), l+n)*falling_factorial(var-n, l) for l in range(0,self.order()+1)]) def get_recursion_row(self,i): r = self.coefficients() d = self.order() row = [] #var = self.__polynomialRing.gens()[-1] ### First summation part #row += [sum([falling_factorial(k,l)*self.base().sequence(r[l],i-k+l) for l in range(0,k+1)]) for k in range(0,min(i,d))] #if(i<d): # ## Second summation part # row += [sum([falling_factorial(k+i,l)*self.base().sequence(r[l], l-k) for l in range(k,i+k+1)]) for k in range(d-i)] # ## Third summation part # row += [self.forward(k)(**{str(var):i}) for k in range(d-i,d+1)] #else: # ## Second summation part # row += [self.backward(i-d-k)(**{str(var):i}) for k in range(i-d)] # ## Third summation part # row += [self.forward(k)(**{str(var):i}) for k in range(0,d+1)] ## First summation part row += [sum([falling_factorial(k,l)*self.base().sequence(r[l],i-k+l) for l in range(0,k+1)]) for k in range(0,min(i,d))] if(i<d): ## Second summation part row += [sum([falling_factorial(k+i,l)*self.base().sequence(r[l], l-k) for l in range(k,i+k+1)]) for k in range(d-i)] ## Third summation part row += [self.__eval_pol(self.forward(k),i) for k in range(d-i,d+1)] else: ## Second summation part row += [self.__eval_pol(self.backward(i-d-k),i) for k in range(i-d)] ## Third summation part row += [self.__eval_pol(self.forward(k),i) for k in range(0,d+1)] return row def get_recursion_matrix(self, n): nrows = n+1 ncols = n+self.forward_order+1 rows = [] for i in range(nrows): row = self.get_recursion_row(i) rows += [[row[i] for i in range(min(len(row),ncols))] + [0 for i in range(ncols-len(row))]] return Matrix(self.__polynomialRing.base(), rows) ####################################################### ####################################################### ### SOLUTION SPACE METHODS ####################################################### @derived_property def dimension(self): return self.jp_matrix().right_kernel_matrix().nrows() @derived_property def forward_order(self): if(self.is_zero()): raise ValueError("The zero operator has not forward order") n = self.order() while(self.get_recursion_polynomial(n) == 0): n -= 1 return n @cached_method def jp_value(self): ## TODO Be careful with this computation: only valid is the base field are the rational jp_pol = self.get_recursion_polynomial(self.forward_order) return max([self.order()-self.forward_order] + get_integer_roots(jp_pol)) @cached_method def jp_matrix(self): return self.get_recursion_matrix(self.jp_value()) def get_jp_fo(self): return self.jp_value()+self.forward_order ####################################################### ####################################################### ### OPERATOR ARITHMETIC METHODS (ABSTRACT) ####################################################### def add(self, other): ''' This method allows the user to add two operators. This method must be extended in each child-class of Operator. ''' raise NotImplementedError('Method not implemented -- Abstract class asked') def scalar(self, other): ''' This method allows the user to do a (left)scalar multiplication. This method must be extended in each child-class of Operator. ''' raise NotImplementedError('Method not implemented -- Abstract class asked') def mult(self, other): ''' This method allows the user to multiply two operators. This method must be extended in each child-class of Operator. ''' raise NotImplementedError('Method not implemented -- Abstract class asked') def is_zero(self): ''' This method allows the user to know if this operator is the zero operator. This method must be extended in each child-class of Operator. ''' raise NotImplementedError('Method not implemented -- Abstract class asked') def derivative(self): ''' This method allows the user to derivate the operator (if possible). If not, it will raise a NotImplementedError. This method must be extended in each child-class of Operator. ''' raise NotImplementedError('This operator can not be derivated') ####################################################### ####################################################### ### SOLUTION ARITHMETHIC METHODS ####################################################### def add_solution(self, other): ''' This method computes a new operator such any solution of 'self == 0' plus any solution of 'other == 0' must satisfy. ''' ## If the input is not an operator, trying the casting if(not isinstance(other, Operator)): other = self.__class__(self.base(), other, self.derivate()) ## If the input is an Operator we guess the hightest preference type if(not isinstance(other, self.__class__)): if(other._get_preference() > self._get_preference()): return other.add_solution(self) other = self.__class__(self.base(), other, self.derivate()) return self._compute_add_solution(other) def mult_solution(self, other): ''' This method computes a new operator such any solution of 'self == 0' multiplied by any solution of 'other == 0' must satisfy. ''' ## If the input is not an operator, trying the casting if(not isinstance(other, Operator)): other = self.__class__(self.base(), other, self.derivate()) ## If the input is an Operator we guess the hightest preference type if(not isinstance(other, self.__class__)): if(other._get_preference() > self._get_preference()): return other.mult_solution(self) other = self.__class__(self.base(), other, self.derivate()) return self._compute_mult_solution(other) def derivative_solution(self): ''' This method computes a new operator such the derivative of any solution of 'self == 0' must satisfy. ''' return self._compute_derivative_solution() def integral_solution(self): ''' This method computes a new operator such any anti-derivative of any solution of 'self == 0' must satisfy. ''' return self._compute_integral_solution() def compose_solution(self, other): ''' Let c be the coefficients of 'self'. This method computes a differential operator such any solution 'f' of the equation with coefficients 'c(other^(-1))' composed with 'other' will satisfy. This method (awkward by definition) requires that 'other' is in self.base(). INPUT: - other: an element of 'self.base()' OUTPUT: - A new operator 'A' of the same class as 'self' with 'A.base() == self.base()' ERRORS: - 'TypeError' wil be raised if 'other' is not in 'self.base()' ''' ## If the input is not an operator, trying the casting if(not other in self.base()): raise TypeError("Element (%s) is not valid for compose with a solution of %s" %(other, str(self))) return self._compute_compose_solution(other) def _compute_add_solution(self, other): raise NotImplementedError('Method not implemented. Class: %s' %self.__class__) def _compute_mult_solution(self, other): ''' This method computes a new operator such any solution of 'self == 0' multiplied by any solution of 'other == 0' must satisfy. It assumes that other and self are exactly the same type. ''' raise NotImplementedError('Method not implemented. Class: %s' %self.__class__) def _compute_derivative_solution(self): ''' This method computes a new operator such the derivative of any solution of 'self == 0' must satisfy. ''' raise NotImplementedError('Method not implemented. Class: %s' %self.__class__) def _compute_integral_solution(self): ''' This method computes a new operator such any anti-derivative of any solution of 'self == 0' must satisfy. ''' raise NotImplementedError('Method not implemented. Class: %s' %self.__class__) def _compute_compose_solution(self, other): ''' This method computes a new operator that annihilates any solution of 'self' compose with any solution of 'other'. ''' raise NotImplementedError('Method not implemented. Class: %s' %self.__class__) ####################################################### ####################################################### ### SIMPLE ARITHMETHIC METHODS ####################################################### def simple_add_solution(self, other): r''' Method to compute the annihilator of the addition of solutions to two operators. This method computes a new operator `A` that annihilates all the functions that are the addition of the solutions of ``self`` and ``other``. This method takes care that the singularities of the new equation are the same as the singularities of the input. Currently this only work if the coefficients are univariate polynomials. ''' ## If the input is not an operator, trying the casting if(not isinstance(other, Operator)): other = self.__class__(self.base(), other, self.derivate()) ## If the input is an Operator we guess the hightest preference type if(not isinstance(other, self.__class__)): if(other._get_preference() > self._get_preference()): return other.add_solution(self) other = self.__class__(self.base(), other, self.derivate()) return self._compute_simple_add_solution(other) def simple_mult_solution(self, other): r''' Method to compute the annihilator of the product of solutions to two operators. This method computes a new operator `A` that annihilates all the functions that are the product of the solutions of ``self`` and ``other``. This method takes care that the singularities of the new equation are the same as the singularities of the input. Currently this only work if the coefficients are univariate polynomials. ''' ## If the input is not an operator, trying the casting if(not isinstance(other, Operator)): other = self.__class__(self.base(), other, self.derivate()) ## If the input is an Operator we guess the hightest preference type if(not isinstance(other, self.__class__)): if(other._get_preference() > self._get_preference()): return other.mult_solution(self) other = self.__class__(self.base(), other, self.derivate()) return self._compute_simple_mult_solution(other) def simple_derivative_solution(self): r''' Method to compute the annihilator of the derivative of solutions to ``self``. This method computes a new operator `A` that annihilates all the functions that are the derivative of the solutions of ``self``. This method takes care that the singularities of the new equation are the same as the singularities of the input. Currently this only work if the coefficients are univariate polynomials. ''' return self._compute_simple_derivative_solution() def _compute_simple_add_solution(self, other, bound=5): raise NotImplementedError('Method not implemented. Class: %s' %self.__class__) def _compute_simple_mult_solution(self, other, bound=5): raise NotImplementedError('Method not implemented. Class: %s' %self.__class__) def _compute_simple_derivative_solution(self, bound=5): raise NotImplementedError('Method not implemented. Class: %s' %self.__class__) ####################################################### ####################################################### ### MAGIC PYTHON METHODS ####################################################### def __getitem__(self, key): if(key > self.order() or key < 0): raise IndexError("No coefficient can be get bigger than the order or with negative index") return self.coefficient(key) def __call__(self, obj): ''' This method allows the user to apply the operator to an object. This method must be extended in each child-class of Operator. ''' try: res = obj.parent().zero() to_mult = obj for i in range(self.order()+1): res += self.coefficient(i)*to_mult to_mult = to_mult.derivative() return res except Exception: raise NotImplementedError('Method not implemented -- Abstract class asked') def __hash__(self): return hash(tuple(self.coefficients())) ### Addition def __add__(self, other): try: return self.add(other) except Exception: return NotImplemented ### Substraction def __sub__(self, other): try: #return self.add(other.scalar(-1)) return self.scalar(-1).add(other).scalar(-1) except Exception: return NotImplemented ### Multiplication def __mul__(self, other): try: return self.mult(other) except Exception: return NotImplemented def __pow__(self, other): if(not other in ZZ or other <= 0): return NotImplemented if(other == 1): return self else: return self.__mul__(self.__pow__(other-1)) ### Reverse addition def __radd__(self, other): return self.__add__(other) ### Reverse substraction def __rsub__(self, other): return self.scalar(-1).__add__(other) ### Reverse multiplication def __rmul__(self, other): try: if(not isinstance(other, Operator)): return self.scalar(other) return other.mult(self) except Exception: return NotImplemented ### Implicit addition def __iadd__(self, other): return self.__add__(other) ### Implicit substraction def __isub__(self, other): return self.__sub__(other) ### Implicit multiplication def __imul__(self, other): return self.__mul__(other) ### Unary '-' def __neg__(self): return self.scalar(-1) ####################################################### def __eval_pol(self,pol, val): try: return pol(**{self.__pol_var:val}) except RuntimeError: coeffs = pol.coefficients(False) return sum(coeffs[i]*val**i for i in range(len(coeffs)))